karecurrence.cpp
00001 /* 00002 * karecurrence.cpp - recurrence with special yearly February 29th handling 00003 * Program: kalarm 00004 * Copyright © 2005,2006,2008 by David Jarvie <djarvie@kde.org> 00005 * 00006 * This program is free software; you can redistribute it and/or modify 00007 * it under the terms of the GNU General Public License as published by 00008 * the Free Software Foundation; either version 2 of the License, or 00009 * (at your option) any later version. 00010 * 00011 * This program is distributed in the hope that it will be useful, 00012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00014 * GNU General Public License for more details. 00015 * 00016 * You should have received a copy of the GNU General Public License along 00017 * with this program; if not, write to the Free Software Foundation, Inc., 00018 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 00019 */ 00020 00021 #include "kalarm.h" 00022 00023 #include <tqbitarray.h> 00024 #include <kdebug.h> 00025 00026 #include <libkcal/icalformat.h> 00027 00028 #include "datetime.h" 00029 #include "functions.h" 00030 #include "karecurrence.h" 00031 00032 using namespace KCal; 00033 00034 /*============================================================================= 00035 = Class KARecurrence 00036 = The purpose of this class is to represent the restricted range of recurrence 00037 = types which are handled by KAlarm, and to translate between these and the 00038 = libkcal Recurrence class. In particular, it handles yearly recurrences on 00039 = 29th February specially: 00040 = 00041 = KARecurrence allows annual 29th February recurrences to fall on 28th 00042 = February or 1st March, or not at all, in non-leap years. It allows such 00043 = 29th February recurrences to be combined with the 29th of other months in 00044 = a simple way, represented simply as the 29th of multiple months including 00045 = February. For storage in the libkcal calendar, the 29th day of the month 00046 = recurrence for other months is combined with a last-day-of-February or a 00047 = 60th-day-of-the-year recurrence rule, thereby conforming to RFC2445. 00048 =============================================================================*/ 00049 00050 00051 KARecurrence::Feb29Type KARecurrence::mDefaultFeb29 = KARecurrence::FEB29_FEB29; 00052 00053 00054 /****************************************************************************** 00055 * Set up a KARecurrence from recurrence parameters, using the start date to 00056 * determine the recurrence day/month as appropriate. 00057 * Only a restricted subset of recurrence types is allowed. 00058 * Reply = true if successful. 00059 */ 00060 bool KARecurrence::set(Type recurType, int freq, int count, int f29, const DateTime& start, const TQDateTime& end) 00061 { 00062 mCachedType = -1; 00063 RecurrenceRule::PeriodType rrtype; 00064 switch (recurType) 00065 { 00066 case MINUTELY: rrtype = RecurrenceRule::rMinutely; break; 00067 case DAILY: rrtype = RecurrenceRule::rDaily; break; 00068 case WEEKLY: rrtype = RecurrenceRule::rWeekly; break; 00069 case MONTHLY_DAY: rrtype = RecurrenceRule::rMonthly; break; 00070 case ANNUAL_DATE: rrtype = RecurrenceRule::rYearly; break; 00071 case NO_RECUR: rrtype = RecurrenceRule::rNone; break; 00072 default: 00073 return false; 00074 } 00075 if (!init(rrtype, freq, count, f29, start, end)) 00076 return false; 00077 switch (recurType) 00078 { 00079 case WEEKLY: 00080 { 00081 TQBitArray days(7); 00082 days.setBit(start.date().dayOfWeek() - 1); 00083 addWeeklyDays(days); 00084 break; 00085 } 00086 case MONTHLY_DAY: 00087 addMonthlyDate(start.date().day()); 00088 break; 00089 case ANNUAL_DATE: 00090 addYearlyDate(start.date().day()); 00091 addYearlyMonth(start.date().month()); 00092 break; 00093 default: 00094 break; 00095 } 00096 return true; 00097 } 00098 00099 /****************************************************************************** 00100 * Initialise a KARecurrence from recurrence parameters. 00101 * Reply = true if successful. 00102 */ 00103 bool KARecurrence::init(RecurrenceRule::PeriodType recurType, int freq, int count, int f29, const DateTime& start, 00104 const TQDateTime& end) 00105 { 00106 mCachedType = -1; 00107 Feb29Type feb29Type = (f29 == -1) ? mDefaultFeb29 : static_cast<Feb29Type>(f29); 00108 mFeb29Type = FEB29_FEB29; 00109 clear(); 00110 if (count < -1) 00111 return false; 00112 bool dateOnly = start.isDateOnly(); 00113 if (!count && ((!dateOnly && !end.isValid()) 00114 || (dateOnly && !end.date().isValid()))) 00115 return false; 00116 switch (recurType) 00117 { 00118 case RecurrenceRule::rMinutely: 00119 case RecurrenceRule::rDaily: 00120 case RecurrenceRule::rWeekly: 00121 case RecurrenceRule::rMonthly: 00122 case RecurrenceRule::rYearly: 00123 break; 00124 case rNone: 00125 return true; 00126 default: 00127 return false; 00128 } 00129 setNewRecurrenceType(recurType, freq); 00130 if (count) 00131 setDuration(count); 00132 else if (dateOnly) 00133 setEndDate(end.date()); 00134 else 00135 setEndDateTime(end); 00136 TQDateTime startdt = start.dateTime(); 00137 if ((recurType == RecurrenceRule::rYearly 00138 && feb29Type == FEB29_FEB28) || feb29Type == FEB29_MAR1) 00139 { 00140 int year = startdt.date().year(); 00141 if (!TQDate::leapYear(year) 00142 && startdt.date().dayOfYear() == (feb29Type == FEB29_MAR1 ? 60 : 59)) 00143 { 00144 /* The event start date is February 28th or March 1st, but it 00145 * is a recurrence on February 29th (recurring on February 28th 00146 * or March 1st in non-leap years). Adjust the start date to 00147 * be on February 29th in the last previous leap year. 00148 * This is necessary because KARecurrence represents all types 00149 * of 29th February recurrences by a simple 29th February. 00150 */ 00151 while (!TQDate::leapYear(--year)) ; 00152 startdt.setDate(TQDate(year, 2, 29)); 00153 } 00154 mFeb29Type = feb29Type; 00155 } 00156 if (dateOnly) 00157 setStartDate(startdt.date()); 00158 else 00159 setStartDateTime(startdt); 00160 return true; 00161 } 00162 00163 /****************************************************************************** 00164 * Initialise the recurrence from an iCalendar RRULE string. 00165 */ 00166 bool KARecurrence::set(const TQString& icalRRULE) 00167 { 00168 static TQString RRULE = TQString::fromLatin1("RRULE:"); 00169 mCachedType = -1; 00170 clear(); 00171 if (icalRRULE.isEmpty()) 00172 return true; 00173 ICalFormat format; 00174 if (!format.fromString(defaultRRule(true), 00175 (icalRRULE.startsWith(RRULE) ? icalRRULE.mid(RRULE.length()) : icalRRULE))) 00176 return false; 00177 fix(); 00178 return true; 00179 } 00180 00181 /****************************************************************************** 00182 * Must be called after presetting with a KCal::Recurrence, to convert the 00183 * recurrence to KARecurrence types: 00184 * - Convert hourly recurrences to minutely. 00185 * - Remove all but the first day in yearly date recurrences. 00186 * - Check for yearly recurrences falling on February 29th and adjust them as 00187 * necessary. A 29th of the month rule can be combined with either a 60th day 00188 * of the year rule or a last day of February rule. 00189 */ 00190 void KARecurrence::fix() 00191 { 00192 mCachedType = -1; 00193 mFeb29Type = FEB29_FEB29; 00194 int convert = 0; 00195 int days[2] = { 0, 0 }; 00196 RecurrenceRule* rrules[2]; 00197 RecurrenceRule::List rrulelist = rRules(); 00198 RecurrenceRule::List::ConstIterator rr = rrulelist.begin(); 00199 for (int i = 0; i < 2 && rr != rrulelist.end(); ++i, ++rr) 00200 { 00201 RecurrenceRule* rrule = *rr; 00202 rrules[i] = rrule; 00203 bool stop = true; 00204 int rtype = recurrenceType(rrule); 00205 switch (rtype) 00206 { 00207 case rHourly: 00208 // Convert an hourly recurrence to a minutely one 00209 rrule->setRecurrenceType(RecurrenceRule::rMinutely); 00210 rrule->setFrequency(rrule->frequency() * 60); 00211 // fall through to rMinutely 00212 case rMinutely: 00213 case rDaily: 00214 case rWeekly: 00215 case rMonthlyDay: 00216 case rMonthlyPos: 00217 case rYearlyPos: 00218 if (!convert) 00219 ++rr; // remove all rules except the first 00220 break; 00221 case rOther: 00222 if (dailyType(rrule)) 00223 { // it's a daily rule with BYDAYS 00224 if (!convert) 00225 ++rr; // remove all rules except the first 00226 } 00227 break; 00228 case rYearlyDay: 00229 { 00230 // Ensure that the yearly day number is 60 (i.e. Feb 29th/Mar 1st) 00231 if (convert) 00232 { 00233 // This is the second rule. 00234 // Ensure that it can be combined with the first one. 00235 if (days[0] != 29 00236 || rrule->frequency() != rrules[0]->frequency() 00237 || rrule->startDt() != rrules[0]->startDt()) 00238 break; 00239 } 00240 TQValueList<int> ds = rrule->byYearDays(); 00241 if (!ds.isEmpty() && ds.first() == 60) 00242 { 00243 ++convert; // this rule needs to be converted 00244 days[i] = 60; 00245 stop = false; 00246 break; 00247 } 00248 break; // not day 60, so remove this rule 00249 } 00250 case rYearlyMonth: 00251 { 00252 TQValueList<int> ds = rrule->byMonthDays(); 00253 if (!ds.isEmpty()) 00254 { 00255 int day = ds.first(); 00256 if (convert) 00257 { 00258 // This is the second rule. 00259 // Ensure that it can be combined with the first one. 00260 if (day == days[0] || (day == -1 && days[0] == 60) 00261 || rrule->frequency() != rrules[0]->frequency() 00262 || rrule->startDt() != rrules[0]->startDt()) 00263 break; 00264 } 00265 if (ds.count() > 1) 00266 { 00267 ds.clear(); // remove all but the first day 00268 ds.append(day); 00269 rrule->setByMonthDays(ds); 00270 } 00271 if (day == -1) 00272 { 00273 // Last day of the month - only combine if it's February 00274 TQValueList<int> months = rrule->byMonths(); 00275 if (months.count() != 1 || months.first() != 2) 00276 day = 0; 00277 } 00278 if (day == 29 || day == -1) 00279 { 00280 ++convert; // this rule may need to be converted 00281 days[i] = day; 00282 stop = false; 00283 break; 00284 } 00285 } 00286 if (!convert) 00287 ++rr; 00288 break; 00289 } 00290 default: 00291 break; 00292 } 00293 if (stop) 00294 break; 00295 } 00296 00297 // Remove surplus rules 00298 for ( ; rr != rrulelist.end(); ++rr) 00299 { 00300 removeRRule(*rr); 00301 delete *rr; 00302 } 00303 00304 TQDate end; 00305 int count; 00306 TQValueList<int> months; 00307 if (convert == 2) 00308 { 00309 // There are two yearly recurrence rules to combine into a February 29th recurrence. 00310 // Combine the two recurrence rules into a single rYearlyMonth rule falling on Feb 29th. 00311 // Find the duration of the two RRULEs combined, using the shorter of the two if they differ. 00312 if (days[0] != 29) 00313 { 00314 // Swap the two rules so that the 29th rule is the first 00315 RecurrenceRule* rr = rrules[0]; 00316 rrules[0] = rrules[1]; // the 29th rule 00317 rrules[1] = rr; 00318 int d = days[0]; 00319 days[0] = days[1]; 00320 days[1] = d; // the non-29th day 00321 } 00322 // If February is included in the 29th rule, remove it to avoid duplication 00323 months = rrules[0]->byMonths(); 00324 if (months.remove(2)) 00325 rrules[0]->setByMonths(months); 00326 00327 count = combineDurations(rrules[0], rrules[1], end); 00328 mFeb29Type = (days[1] == 60) ? FEB29_MAR1 : FEB29_FEB28; 00329 } 00330 else if (convert == 1 && days[0] == 60) 00331 { 00332 // There is a single 60th day of the year rule. 00333 // Convert it to a February 29th recurrence. 00334 count = duration(); 00335 if (!count) 00336 end = endDate(); 00337 mFeb29Type = FEB29_MAR1; 00338 } 00339 else 00340 return; 00341 00342 // Create the new February 29th recurrence 00343 setNewRecurrenceType(RecurrenceRule::rYearly, frequency()); 00344 RecurrenceRule* rrule = defaultRRule(); 00345 months.append(2); 00346 rrule->setByMonths(months); 00347 TQValueList<int> ds; 00348 ds.append(29); 00349 rrule->setByMonthDays(ds); 00350 if (count) 00351 setDuration(count); 00352 else 00353 setEndDate(end); 00354 } 00355 00356 /****************************************************************************** 00357 * Get the next time the recurrence occurs, strictly after a specified time. 00358 */ 00359 TQDateTime KARecurrence::getNextDateTime(const TQDateTime& preDateTime) const 00360 { 00361 switch (type()) 00362 { 00363 case ANNUAL_DATE: 00364 case ANNUAL_POS: 00365 { 00366 Recurrence recur; 00367 writeRecurrence(recur); 00368 return recur.getNextDateTime(preDateTime); 00369 } 00370 default: 00371 return Recurrence::getNextDateTime(preDateTime); 00372 } 00373 } 00374 00375 /****************************************************************************** 00376 * Get the previous time the recurrence occurred, strictly before a specified time. 00377 */ 00378 TQDateTime KARecurrence::getPreviousDateTime(const TQDateTime& afterDateTime) const 00379 { 00380 switch (type()) 00381 { 00382 case ANNUAL_DATE: 00383 case ANNUAL_POS: 00384 { 00385 Recurrence recur; 00386 writeRecurrence(recur); 00387 return recur.getPreviousDateTime(afterDateTime); 00388 } 00389 default: 00390 return Recurrence::getPreviousDateTime(afterDateTime); 00391 } 00392 } 00393 00394 /****************************************************************************** 00395 * Initialise a KCal::Recurrence to be the same as this instance. 00396 * Additional recurrence rules are created as necessary if it recurs on Feb 29th. 00397 */ 00398 void KARecurrence::writeRecurrence(KCal::Recurrence& recur) const 00399 { 00400 recur.clear(); 00401 recur.setStartDateTime(startDateTime()); 00402 recur.setExDates(exDates()); 00403 recur.setExDateTimes(exDateTimes()); 00404 const RecurrenceRule* rrule = defaultRRuleConst(); 00405 if (!rrule) 00406 return; 00407 int freq = frequency(); 00408 int count = duration(); 00409 static_cast<KARecurrence*>(&recur)->setNewRecurrenceType(rrule->recurrenceType(), freq); 00410 if (count) 00411 recur.setDuration(count); 00412 else 00413 recur.setEndDateTime(endDateTime()); 00414 switch (type()) 00415 { 00416 case DAILY: 00417 if (rrule->byDays().isEmpty()) 00418 break; 00419 // fall through to rWeekly 00420 case WEEKLY: 00421 case MONTHLY_POS: 00422 recur.defaultRRule(true)->setByDays(rrule->byDays()); 00423 break; 00424 case MONTHLY_DAY: 00425 recur.defaultRRule(true)->setByMonthDays(rrule->byMonthDays()); 00426 break; 00427 case ANNUAL_POS: 00428 recur.defaultRRule(true)->setByMonths(rrule->byMonths()); 00429 recur.defaultRRule()->setByDays(rrule->byDays()); 00430 break; 00431 case ANNUAL_DATE: 00432 { 00433 TQValueList<int> months = rrule->byMonths(); 00434 TQValueList<int> days = monthDays(); 00435 bool special = (mFeb29Type != FEB29_FEB29 && !days.isEmpty() 00436 && days.first() == 29 && months.remove(2)); 00437 RecurrenceRule* rrule1 = recur.defaultRRule(); 00438 rrule1->setByMonths(months); 00439 rrule1->setByMonthDays(days); 00440 if (!special) 00441 break; 00442 00443 // It recurs on the 29th February. 00444 // Create an additional 60th day of the year, or last day of February, rule. 00445 RecurrenceRule* rrule2 = new RecurrenceRule(); 00446 rrule2->setRecurrenceType(RecurrenceRule::rYearly); 00447 rrule2->setFrequency(freq); 00448 rrule2->setStartDt(startDateTime()); 00449 rrule2->setFloats(doesFloat()); 00450 if (!count) 00451 rrule2->setEndDt(endDateTime()); 00452 if (mFeb29Type == FEB29_MAR1) 00453 { 00454 TQValueList<int> ds; 00455 ds.append(60); 00456 rrule2->setByYearDays(ds); 00457 } 00458 else 00459 { 00460 TQValueList<int> ds; 00461 ds.append(-1); 00462 rrule2->setByMonthDays(ds); 00463 TQValueList<int> ms; 00464 ms.append(2); 00465 rrule2->setByMonths(ms); 00466 } 00467 00468 if (months.isEmpty()) 00469 { 00470 // Only February recurs. 00471 // Replace the RRULE and keep the recurrence count the same. 00472 if (count) 00473 rrule2->setDuration(count); 00474 recur.unsetRecurs(); 00475 } 00476 else 00477 { 00478 // Months other than February also recur on the 29th. 00479 // Remove February from the list and add a separate RRULE for February. 00480 if (count) 00481 { 00482 rrule1->setDuration(-1); 00483 rrule2->setDuration(-1); 00484 if (count > 0) 00485 { 00486 /* Adjust counts in the two rules to keep the correct occurrence total. 00487 * Note that durationTo() always includes the start date. Since for an 00488 * individual RRULE the start date may not actually be included, we need 00489 * to decrement the count if the start date doesn't actually recur in 00490 * this RRULE. 00491 * Note that if the count is small, one of the rules may not recur at 00492 * all. In that case, retain it so that the February 29th characteristic 00493 * is not lost should the user later change the recurrence count. 00494 */ 00495 TQDateTime end = endDateTime(); 00496 kdDebug()<<"29th recurrence: count="<<count<<", end date="<<end.toString()<<endl; 00497 int count1 = rrule1->durationTo(end) 00498 - (rrule1->recursOn(startDate()) ? 0 : 1); 00499 if (count1 > 0) 00500 rrule1->setDuration(count1); 00501 else 00502 rrule1->setEndDt(startDateTime()); 00503 int count2 = rrule2->durationTo(end) 00504 - (rrule2->recursOn(startDate()) ? 0 : 1); 00505 if (count2 > 0) 00506 rrule2->setDuration(count2); 00507 else 00508 rrule2->setEndDt(startDateTime()); 00509 } 00510 } 00511 } 00512 recur.addRRule(rrule2); 00513 break; 00514 } 00515 default: 00516 break; 00517 } 00518 } 00519 00520 /****************************************************************************** 00521 * Return the date/time of the last recurrence. 00522 */ 00523 TQDateTime KARecurrence::endDateTime() const 00524 { 00525 if (mFeb29Type == FEB29_FEB29 || duration() <= 1) 00526 { 00527 /* Either it doesn't have any special February 29th treatment, 00528 * it's infinite (count = -1), the end date is specified 00529 * (count = 0), or it ends on the start date (count = 1). 00530 * So just use the normal KCal end date calculation. 00531 */ 00532 return Recurrence::endDateTime(); 00533 } 00534 00535 /* Create a temporary recurrence rule to find the end date. 00536 * In a standard KCal recurrence, the 29th February only occurs once every 00537 * 4 years. So shift the temporary recurrence date to the 28th to ensure 00538 * that it occurs every year, thus giving the correct occurrence count. 00539 */ 00540 RecurrenceRule* rrule = new RecurrenceRule(); 00541 rrule->setRecurrenceType(RecurrenceRule::rYearly); 00542 TQDateTime dt = startDateTime(); 00543 TQDate d = dt.date(); 00544 switch (d.day()) 00545 { 00546 case 29: 00547 // The start date is definitely a recurrence date, so shift 00548 // start date to the temporary recurrence date of the 28th 00549 d.setYMD(d.year(), d.month(), 28); 00550 break; 00551 case 28: 00552 if (d.month() != 2 || mFeb29Type != FEB29_FEB28 || TQDate::leapYear(d.year())) 00553 { 00554 // Start date is not a recurrence date, so shift it to 27th 00555 d.setYMD(d.year(), d.month(), 27); 00556 } 00557 break; 00558 case 1: 00559 if (d.month() == 3 && mFeb29Type == FEB29_MAR1 && !TQDate::leapYear(d.year())) 00560 { 00561 // Start date is a March 1st recurrence date, so shift 00562 // start date to the temporary recurrence date of the 28th 00563 d.setYMD(d.year(), 2, 28); 00564 } 00565 break; 00566 default: 00567 break; 00568 } 00569 dt.setDate(d); 00570 rrule->setStartDt(dt); 00571 rrule->setFloats(doesFloat()); 00572 rrule->setFrequency(frequency()); 00573 rrule->setDuration(duration()); 00574 TQValueList<int> ds; 00575 ds.append(28); 00576 rrule->setByMonthDays(ds); 00577 rrule->setByMonths(defaultRRuleConst()->byMonths()); 00578 dt = rrule->endDt(); 00579 delete rrule; 00580 00581 // We've found the end date for a recurrence on the 28th. Unless that date 00582 // is a real February 28th recurrence, adjust to the actual recurrence date. 00583 if (mFeb29Type == FEB29_FEB28 && dt.date().month() == 2 && !TQDate::leapYear(dt.date().year())) 00584 return dt; 00585 return dt.addDays(1); 00586 } 00587 00588 /****************************************************************************** 00589 * Return the date/time of the last recurrence. 00590 */ 00591 TQDate KARecurrence::endDate() const 00592 { 00593 TQDateTime end = endDateTime(); 00594 return end.isValid() ? end.date() : TQDate(); 00595 } 00596 00597 /****************************************************************************** 00598 * Return whether the event will recur on the specified date. 00599 * The start date only returns true if it matches the recurrence rules. 00600 */ 00601 bool KARecurrence::recursOn(const TQDate& dt) const 00602 { 00603 if (!Recurrence::recursOn(dt)) 00604 return false; 00605 if (dt != startDate()) 00606 return true; 00607 // We know now that it isn't in EXDATES or EXRULES, 00608 // so we just need to check if it's in RDATES or RRULES 00609 if (rDates().contains(dt)) 00610 return true; 00611 RecurrenceRule::List rulelist = rRules(); 00612 for (RecurrenceRule::List::ConstIterator rr = rulelist.begin(); rr != rulelist.end(); ++rr) 00613 if ((*rr)->recursOn(dt)) 00614 return true; 00615 DateTimeList dtlist = rDateTimes(); 00616 for (DateTimeList::ConstIterator rdt = dtlist.begin(); rdt != dtlist.end(); ++rdt) 00617 if ((*rdt).date() == dt) 00618 return true; 00619 return false; 00620 } 00621 00622 /****************************************************************************** 00623 * Find the duration of two RRULEs combined. 00624 * Use the shorter of the two if they differ. 00625 */ 00626 int KARecurrence::combineDurations(const RecurrenceRule* rrule1, const RecurrenceRule* rrule2, TQDate& end) const 00627 { 00628 int count1 = rrule1->duration(); 00629 int count2 = rrule2->duration(); 00630 if (count1 == -1 && count2 == -1) 00631 return -1; 00632 00633 // One of the RRULEs may not recur at all if the recurrence count is small. 00634 // In this case, its end date will have been set to the start date. 00635 if (count1 && !count2 && rrule2->endDt().date() == startDateTime().date()) 00636 return count1; 00637 if (count2 && !count1 && rrule1->endDt().date() == startDateTime().date()) 00638 return count2; 00639 00640 /* The duration counts will be different even for RRULEs of the same length, 00641 * because the first RRULE only actually occurs every 4 years. So we need to 00642 * compare the end dates. 00643 */ 00644 if (!count1 || !count2) 00645 count1 = count2 = 0; 00646 // Get the two rules sorted by end date. 00647 TQDateTime end1 = rrule1->endDt(); 00648 TQDateTime end2 = rrule2->endDt(); 00649 if (end1.date() == end2.date()) 00650 { 00651 end = end1.date(); 00652 return count1 + count2; 00653 } 00654 const RecurrenceRule* rr1; // earlier end date 00655 const RecurrenceRule* rr2; // later end date 00656 if (end2.isValid() 00657 && (!end1.isValid() || end1.date() > end2.date())) 00658 { 00659 // Swap the two rules to make rr1 have the earlier end date 00660 rr1 = rrule2; 00661 rr2 = rrule1; 00662 TQDateTime e = end1; 00663 end1 = end2; 00664 end2 = e; 00665 } 00666 else 00667 { 00668 rr1 = rrule1; 00669 rr2 = rrule2; 00670 } 00671 00672 // Get the date of the next occurrence after the end of the earlier ending rule 00673 RecurrenceRule rr(*rr1); 00674 rr.setDuration(-1); 00675 TQDateTime next1(rr.getNextDate(end1).date()); 00676 if (!next1.isValid()) 00677 end = end1.date(); 00678 else 00679 { 00680 if (end2.isValid() && next1 > end2) 00681 { 00682 // The next occurrence after the end of the earlier ending rule 00683 // is later than the end of the later ending rule. So simply use 00684 // the end date of the later rule. 00685 end = end2.date(); 00686 return count1 + count2; 00687 } 00688 TQDate prev2 = rr2->getPreviousDate(next1).date(); 00689 end = (prev2 > end1.date()) ? prev2 : end1.date(); 00690 } 00691 if (count2) 00692 count2 = rr2->durationTo(end); 00693 return count1 + count2; 00694 } 00695 00696 /****************************************************************************** 00697 * Return the longest interval (in minutes) between recurrences. 00698 * Reply = 0 if it never recurs. 00699 */ 00700 int KARecurrence::longestInterval() const 00701 { 00702 int freq = frequency(); 00703 switch (type()) 00704 { 00705 case MINUTELY: 00706 return freq; 00707 00708 case DAILY: 00709 { 00710 TQValueList<RecurrenceRule::WDayPos> days = defaultRRuleConst()->byDays(); 00711 if (days.isEmpty()) 00712 return freq * 1440; 00713 00714 // It recurs only on certain days of the week, so the maximum interval 00715 // may be greater than the frequency. 00716 bool ds[7] = { false, false, false, false, false, false, false }; 00717 for (TQValueList<RecurrenceRule::WDayPos>::ConstIterator it = days.begin(); it != days.end(); ++it) 00718 if ((*it).pos() == 0) 00719 ds[(*it).day() - 1] = true; 00720 if (freq % 7) 00721 { 00722 // It will recur on every day of the week in some week or other 00723 // (except for those days which are excluded). 00724 int first = -1; 00725 int last = -1; 00726 int maxgap = 1; 00727 for (int i = 0; i < freq*7; i += freq) 00728 { 00729 if (ds[i % 7]) 00730 { 00731 if (first < 0) 00732 first = i; 00733 else if (i - last > maxgap) 00734 maxgap = i - last; 00735 last = i; 00736 } 00737 } 00738 int wrap = freq*7 - last + first; 00739 if (wrap > maxgap) 00740 maxgap = wrap; 00741 return maxgap * 1440; 00742 } 00743 else 00744 { 00745 // It will recur on the same day of the week every time. 00746 // Ensure that the day is a day which is not excluded. 00747 return ds[startDate().dayOfWeek() - 1] ? freq * 1440 : 0; 00748 } 00749 } 00750 case WEEKLY: 00751 { 00752 // Find which days of the week it recurs on, and if on more than 00753 // one, reduce the maximum interval accordingly. 00754 TQBitArray ds = days(); 00755 int first = -1; 00756 int last = -1; 00757 int maxgap = 1; 00758 for (int i = 0; i < 7; ++i) 00759 { 00760 if (ds.testBit(KAlarm::localeDayInWeek_to_weekDay(i) - 1)) 00761 { 00762 if (first < 0) 00763 first = i; 00764 else if (i - last > maxgap) 00765 maxgap = i - last; 00766 last = i; 00767 } 00768 } 00769 if (first < 0) 00770 break; // no days recur 00771 int span = last - first; 00772 if (freq > 1) 00773 return (freq*7 - span) * 1440; 00774 if (7 - span > maxgap) 00775 return (7 - span) * 1440; 00776 return maxgap * 1440; 00777 } 00778 case MONTHLY_DAY: 00779 case MONTHLY_POS: 00780 return freq * 1440 * 31; 00781 00782 case ANNUAL_DATE: 00783 case ANNUAL_POS: 00784 { 00785 // Find which months of the year it recurs on, and if on more than 00786 // one, reduce the maximum interval accordingly. 00787 const TQValueList<int> months = yearMonths(); // month list is sorted 00788 if (months.isEmpty()) 00789 break; // no months recur 00790 if (months.count() == 1) 00791 return freq * 1440 * 365; 00792 int first = -1; 00793 int last = -1; 00794 int maxgap = 0; 00795 for (TQValueListConstIterator<int> it = months.begin(); it != months.end(); ++it) 00796 { 00797 if (first < 0) 00798 first = *it; 00799 else 00800 { 00801 int span = TQDate(2001, last, 1).daysTo(TQDate(2001, *it, 1)); 00802 if (span > maxgap) 00803 maxgap = span; 00804 } 00805 last = *it; 00806 } 00807 int span = TQDate(2001, first, 1).daysTo(TQDate(2001, last, 1)); 00808 if (freq > 1) 00809 return (freq*365 - span) * 1440; 00810 if (365 - span > maxgap) 00811 return (365 - span) * 1440; 00812 return maxgap * 1440; 00813 } 00814 default: 00815 break; 00816 } 00817 return 0; 00818 } 00819 00820 /****************************************************************************** 00821 * Return the recurrence's period type. 00822 */ 00823 KARecurrence::Type KARecurrence::type() const 00824 { 00825 if (mCachedType == -1) 00826 mCachedType = type(defaultRRuleConst()); 00827 return static_cast<Type>(mCachedType); 00828 } 00829 00830 KARecurrence::Type KARecurrence::type(const RecurrenceRule* rrule) 00831 { 00832 switch (recurrenceType(rrule)) 00833 { 00834 case rMinutely: return MINUTELY; 00835 case rDaily: return DAILY; 00836 case rWeekly: return WEEKLY; 00837 case rMonthlyDay: return MONTHLY_DAY; 00838 case rMonthlyPos: return MONTHLY_POS; 00839 case rYearlyMonth: return ANNUAL_DATE; 00840 case rYearlyPos: return ANNUAL_POS; 00841 default: 00842 if (dailyType(rrule)) 00843 return DAILY; 00844 return NO_RECUR; 00845 } 00846 } 00847 00848 /****************************************************************************** 00849 * Check if the rule is a daily rule with or without BYDAYS specified. 00850 */ 00851 bool KARecurrence::dailyType(const RecurrenceRule* rrule) 00852 { 00853 if (rrule->recurrenceType() != RecurrenceRule::rDaily 00854 || !rrule->bySeconds().isEmpty() 00855 || !rrule->byMinutes().isEmpty() 00856 || !rrule->byHours().isEmpty() 00857 || !rrule->byWeekNumbers().isEmpty() 00858 || !rrule->byMonthDays().isEmpty() 00859 || !rrule->byMonths().isEmpty() 00860 || !rrule->bySetPos().isEmpty() 00861 || !rrule->byYearDays().isEmpty()) 00862 return false; 00863 TQValueList<RecurrenceRule::WDayPos> days = rrule->byDays(); 00864 if (days.isEmpty()) 00865 return true; 00866 // Check that all the positions are zero (i.e. every time) 00867 bool found = false; 00868 for (TQValueList<RecurrenceRule::WDayPos>::ConstIterator it = days.begin(); it != days.end(); ++it) 00869 { 00870 if ((*it).pos() != 0) 00871 return false; 00872 found = true; 00873 } 00874 return found; 00875 00876 }