KAlarm Library
karecurrence.cpp
00001 /* 00002 * karecurrence.cpp - recurrence with special yearly February 29th handling 00003 * This file is part of kalarmcal library, which provides access to KAlarm 00004 * calendar data. 00005 * Copyright © 2005-2012 by David Jarvie <djarvie@kde.org> 00006 * 00007 * This library is free software; you can redistribute it and/or modify 00008 * it under the terms of the GNU Library General Public License as published 00009 * by the Free Software Foundation; either version 2 of the License, or (at 00010 * your option) any later version. 00011 * 00012 * This library is distributed in the hope that it will be useful, but WITHOUT 00013 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 00014 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public 00015 * License for more details. 00016 * 00017 * You should have received a copy of the GNU Library General Public License 00018 * along with this library; see the file COPYING.LIB. If not, write to the 00019 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 00020 * MA 02110-1301, USA. 00021 */ 00022 00023 #include "karecurrence.h" 00024 00025 #ifndef KALARMCAL_USE_KRESOURCES 00026 #include <kcalcore/recurrence.h> 00027 #include <kcalcore/icalformat.h> 00028 #else 00029 #include <kcal/recurrence.h> 00030 #include <kcal/icalformat.h> 00031 #endif 00032 #include <kglobal.h> 00033 #include <klocale.h> 00034 #include <kdebug.h> 00035 00036 #include <QBitArray> 00037 00038 00039 #ifndef KALARMCAL_USE_KRESOURCES 00040 using namespace KCalCore; 00041 #else 00042 using namespace KCal; 00043 #endif 00044 00045 namespace KAlarmCal 00046 { 00047 00048 class Recurrence_p : public Recurrence 00049 { 00050 public: 00051 using Recurrence::setNewRecurrenceType; 00052 Recurrence_p() : Recurrence() {} 00053 Recurrence_p(const Recurrence& r) : Recurrence(r) {} 00054 Recurrence_p(const Recurrence_p& r) : Recurrence(r) {} 00055 }; 00056 00057 class KARecurrence::Private 00058 { 00059 public: 00060 Private() 00061 : mFeb29Type(Feb29_None), mCachedType(-1) {} 00062 explicit Private(const Recurrence& r) 00063 : mRecurrence(r), mFeb29Type(Feb29_None), mCachedType(-1) {} 00064 void clear() 00065 { 00066 mRecurrence.clear(); 00067 mFeb29Type = Feb29_None; 00068 mCachedType = -1; 00069 } 00070 bool set(Type, int freq, int count, int f29, const KDateTime& start, const KDateTime& end); 00071 bool init(RecurrenceRule::PeriodType, int freq, int count, int feb29Type, const KDateTime& start, const KDateTime& end); 00072 void fix(); 00073 void writeRecurrence(const KARecurrence* q, Recurrence& recur) const; 00074 KDateTime endDateTime() const; 00075 int combineDurations(const RecurrenceRule*, const RecurrenceRule*, QDate& end) const; 00076 00077 static Feb29Type mDefaultFeb29; 00078 Recurrence_p mRecurrence; 00079 Feb29Type mFeb29Type; // yearly recurrence on Feb 29th (leap years) / Mar 1st (non-leap years) 00080 mutable int mCachedType; 00081 }; 00082 00083 00084 /*============================================================================= 00085 = Class KARecurrence 00086 = The purpose of this class is to represent the restricted range of recurrence 00087 = types which are handled by KAlarm, and to translate between these and the 00088 = libkcal Recurrence class. In particular, it handles yearly recurrences on 00089 = 29th February specially: 00090 = 00091 = KARecurrence allows annual 29th February recurrences to fall on 28th 00092 = February or 1st March, or not at all, in non-leap years. It allows such 00093 = 29th February recurrences to be combined with the 29th of other months in 00094 = a simple way, represented simply as the 29th of multiple months including 00095 = February. For storage in the libkcal calendar, the 29th day of the month 00096 = recurrence for other months is combined with a last-day-of-February or a 00097 = 60th-day-of-the-year recurrence rule, thereby conforming to RFC2445. 00098 =============================================================================*/ 00099 00100 00101 KARecurrence::Feb29Type KARecurrence::Private::mDefaultFeb29 = KARecurrence::Feb29_None; 00102 00103 00104 KARecurrence::KARecurrence() 00105 : d(new Private) 00106 { } 00107 00108 KARecurrence::KARecurrence(const Recurrence& r) 00109 : d(new Private(r)) 00110 { 00111 fix(); 00112 } 00113 00114 KARecurrence::KARecurrence(const KARecurrence& r) 00115 : d(new Private(*r.d)) 00116 { } 00117 00118 KARecurrence::~KARecurrence() 00119 { 00120 delete d; 00121 } 00122 00123 KARecurrence& KARecurrence::operator=(const KARecurrence& r) 00124 { 00125 if (&r != this) 00126 *d = *r.d; 00127 return *this; 00128 } 00129 00130 bool KARecurrence::operator==(const KARecurrence& r) const 00131 { 00132 return d->mRecurrence == r.d->mRecurrence 00133 && d->mFeb29Type == r.d->mFeb29Type; 00134 } 00135 00136 KARecurrence::Feb29Type KARecurrence::feb29Type() const 00137 { 00138 return d->mFeb29Type; 00139 } 00140 00141 KARecurrence::Feb29Type KARecurrence::defaultFeb29Type() 00142 { 00143 return Private::mDefaultFeb29; 00144 } 00145 00146 void KARecurrence::setDefaultFeb29Type(Feb29Type t) 00147 { 00148 Private::mDefaultFeb29 = t; 00149 } 00150 00151 /****************************************************************************** 00152 * Set up a KARecurrence from recurrence parameters, using the start date to 00153 * determine the recurrence day/month as appropriate. 00154 * Only a restricted subset of recurrence types is allowed. 00155 * Reply = true if successful. 00156 */ 00157 bool KARecurrence::set(Type t, int freq, int count, const KDateTime& start, const KDateTime& end) 00158 { 00159 return d->set(t, freq, count, -1, start, end); 00160 } 00161 00162 bool KARecurrence::set(Type t, int freq, int count, const KDateTime& start, const KDateTime& end, Feb29Type f29) 00163 { 00164 return d->set(t, freq, count, f29, start, end); 00165 } 00166 00167 bool KARecurrence::Private::set(Type recurType, int freq, int count, int f29, const KDateTime& start, const KDateTime& end) 00168 { 00169 mCachedType = -1; 00170 RecurrenceRule::PeriodType rrtype; 00171 switch (recurType) 00172 { 00173 case MINUTELY: rrtype = RecurrenceRule::rMinutely; break; 00174 case DAILY: rrtype = RecurrenceRule::rDaily; break; 00175 case WEEKLY: rrtype = RecurrenceRule::rWeekly; break; 00176 case MONTHLY_DAY: rrtype = RecurrenceRule::rMonthly; break; 00177 case ANNUAL_DATE: rrtype = RecurrenceRule::rYearly; break; 00178 case NO_RECUR: rrtype = RecurrenceRule::rNone; break; 00179 default: 00180 return false; 00181 } 00182 if (!init(rrtype, freq, count, f29, start, end)) 00183 return false; 00184 switch (recurType) 00185 { 00186 case WEEKLY: 00187 { 00188 QBitArray days(7); 00189 days.setBit(start.date().dayOfWeek() - 1); 00190 mRecurrence.addWeeklyDays(days); 00191 break; 00192 } 00193 case MONTHLY_DAY: 00194 mRecurrence.addMonthlyDate(start.date().day()); 00195 break; 00196 case ANNUAL_DATE: 00197 mRecurrence.addYearlyDate(start.date().day()); 00198 mRecurrence.addYearlyMonth(start.date().month()); 00199 break; 00200 default: 00201 break; 00202 } 00203 return true; 00204 } 00205 00206 /****************************************************************************** 00207 * Initialise a KARecurrence from recurrence parameters. 00208 * Reply = true if successful. 00209 */ 00210 bool KARecurrence::init(RecurrenceRule::PeriodType t, int freq, int count, const KDateTime& start, const KDateTime& end) 00211 { 00212 return d->init(t, freq, count, -1, start, end); 00213 } 00214 00215 bool KARecurrence::init(RecurrenceRule::PeriodType t, int freq, int count, const KDateTime& start, const KDateTime& end, Feb29Type f29) 00216 { 00217 return d->init(t, freq, count, f29, start, end); 00218 } 00219 00220 bool KARecurrence::Private::init(RecurrenceRule::PeriodType recurType, int freq, int count, int f29, const KDateTime& start, 00221 const KDateTime& end) 00222 { 00223 clear(); 00224 Feb29Type feb29Type = (f29 == -1) ? mDefaultFeb29 : static_cast<Feb29Type>(f29); 00225 if (count < -1) 00226 return false; 00227 bool dateOnly = start.isDateOnly(); 00228 if (!count && ((!dateOnly && !end.isValid()) 00229 || (dateOnly && !end.date().isValid()))) 00230 return false; 00231 switch (recurType) 00232 { 00233 case RecurrenceRule::rMinutely: 00234 case RecurrenceRule::rDaily: 00235 case RecurrenceRule::rWeekly: 00236 case RecurrenceRule::rMonthly: 00237 case RecurrenceRule::rYearly: 00238 break; 00239 case RecurrenceRule::rNone: 00240 return true; 00241 default: 00242 return false; 00243 } 00244 mRecurrence.setNewRecurrenceType(recurType, freq); 00245 if (count) 00246 mRecurrence.setDuration(count); 00247 else if (dateOnly) 00248 mRecurrence.setEndDate(end.date()); 00249 else 00250 mRecurrence.setEndDateTime(end); 00251 KDateTime startdt = start; 00252 if (recurType == RecurrenceRule::rYearly 00253 && (feb29Type == Feb29_Feb28 || feb29Type == Feb29_Mar1)) 00254 { 00255 int year = startdt.date().year(); 00256 if (!QDate::isLeapYear(year) 00257 && startdt.date().dayOfYear() == (feb29Type == Feb29_Mar1 ? 60 : 59)) 00258 { 00259 /* The event start date is February 28th or March 1st, but it 00260 * is a recurrence on February 29th (recurring on February 28th 00261 * or March 1st in non-leap years). Adjust the start date to 00262 * be on February 29th in the last previous leap year. 00263 * This is necessary because KARecurrence represents all types 00264 * of 29th February recurrences by a simple 29th February. 00265 */ 00266 while (!QDate::isLeapYear(--year)) ; 00267 startdt.setDate(QDate(year, 2, 29)); 00268 } 00269 mFeb29Type = feb29Type; 00270 } 00271 mRecurrence.setStartDateTime(startdt); // sets recurrence all-day if date-only 00272 return true; 00273 } 00274 00275 /****************************************************************************** 00276 * Initialise the recurrence from an iCalendar RRULE string. 00277 */ 00278 bool KARecurrence::set(const QString& icalRRULE) 00279 { 00280 static QString RRULE = QLatin1String("RRULE:"); 00281 d->clear(); 00282 if (icalRRULE.isEmpty()) 00283 return true; 00284 ICalFormat format; 00285 if (!format.fromString(d->mRecurrence.defaultRRule(true), 00286 (icalRRULE.startsWith(RRULE) ? icalRRULE.mid(RRULE.length()) : icalRRULE))) 00287 return false; 00288 fix(); 00289 return true; 00290 } 00291 00292 void KARecurrence::clear() 00293 { 00294 d->clear(); 00295 } 00296 00297 /****************************************************************************** 00298 * Must be called after presetting with a KCal::Recurrence, to convert the 00299 * recurrence to KARecurrence types: 00300 * - Convert hourly recurrences to minutely. 00301 * - Remove all but the first day in yearly date recurrences. 00302 * - Check for yearly recurrences falling on February 29th and adjust them as 00303 * necessary. A 29th of the month rule can be combined with either a 60th day 00304 * of the year rule or a last day of February rule. 00305 */ 00306 void KARecurrence::fix() 00307 { 00308 d->fix(); 00309 } 00310 00311 void KARecurrence::Private::fix() 00312 { 00313 mCachedType = -1; 00314 mFeb29Type = Feb29_None; 00315 int convert = 0; 00316 int days[2] = { 0, 0 }; 00317 RecurrenceRule* rrules[2]; 00318 RecurrenceRule::List rrulelist = mRecurrence.rRules(); 00319 int rri = 0; 00320 int rrend = rrulelist.count(); 00321 for (int i = 0; i < 2 && rri < rrend; ++i, ++rri) 00322 { 00323 RecurrenceRule* rrule = rrulelist[rri]; 00324 rrules[i] = rrule; 00325 bool stop = true; 00326 int rtype = mRecurrence.recurrenceType(rrule); 00327 switch (rtype) 00328 { 00329 case Recurrence::rHourly: 00330 // Convert an hourly recurrence to a minutely one 00331 rrule->setRecurrenceType(RecurrenceRule::rMinutely); 00332 rrule->setFrequency(rrule->frequency() * 60); 00333 // fall through to rMinutely 00334 case Recurrence::rMinutely: 00335 case Recurrence::rDaily: 00336 case Recurrence::rWeekly: 00337 case Recurrence::rMonthlyDay: 00338 case Recurrence::rMonthlyPos: 00339 case Recurrence::rYearlyPos: 00340 if (!convert) 00341 ++rri; // remove all rules except the first 00342 break; 00343 case Recurrence::rOther: 00344 if (dailyType(rrule)) 00345 { // it's a daily rule with BYDAYS 00346 if (!convert) 00347 ++rri; // remove all rules except the first 00348 } 00349 break; 00350 case Recurrence::rYearlyDay: 00351 { 00352 // Ensure that the yearly day number is 60 (i.e. Feb 29th/Mar 1st) 00353 if (convert) 00354 { 00355 // This is the second rule. 00356 // Ensure that it can be combined with the first one. 00357 if (days[0] != 29 00358 || rrule->frequency() != rrules[0]->frequency() 00359 || rrule->startDt() != rrules[0]->startDt()) 00360 break; 00361 } 00362 QList<int> ds = rrule->byYearDays(); 00363 if (!ds.isEmpty() && ds.first() == 60) 00364 { 00365 ++convert; // this rule needs to be converted 00366 days[i] = 60; 00367 stop = false; 00368 break; 00369 } 00370 break; // not day 60, so remove this rule 00371 } 00372 case Recurrence::rYearlyMonth: 00373 { 00374 QList<int> ds = rrule->byMonthDays(); 00375 if (!ds.isEmpty()) 00376 { 00377 int day = ds.first(); 00378 if (convert) 00379 { 00380 // This is the second rule. 00381 // Ensure that it can be combined with the first one. 00382 if (day == days[0] || (day == -1 && days[0] == 60) 00383 || rrule->frequency() != rrules[0]->frequency() 00384 || rrule->startDt() != rrules[0]->startDt()) 00385 break; 00386 } 00387 if (ds.count() > 1) 00388 { 00389 ds.clear(); // remove all but the first day 00390 ds.append(day); 00391 rrule->setByMonthDays(ds); 00392 } 00393 if (day == -1) 00394 { 00395 // Last day of the month - only combine if it's February 00396 QList<int> months = rrule->byMonths(); 00397 if (months.count() != 1 || months.first() != 2) 00398 day = 0; 00399 } 00400 if (day == 29 || day == -1) 00401 { 00402 ++convert; // this rule may need to be converted 00403 days[i] = day; 00404 stop = false; 00405 break; 00406 } 00407 } 00408 if (!convert) 00409 ++rri; 00410 break; 00411 } 00412 default: 00413 break; 00414 } 00415 if (stop) 00416 break; 00417 } 00418 00419 // Remove surplus rules 00420 for ( ; rri < rrend; ++rri) 00421 mRecurrence.deleteRRule(rrulelist[rri]); 00422 00423 QDate end; 00424 int count; 00425 QList<int> months; 00426 if (convert == 2) 00427 { 00428 // There are two yearly recurrence rules to combine into a February 29th recurrence. 00429 // Combine the two recurrence rules into a single rYearlyMonth rule falling on Feb 29th. 00430 // Find the duration of the two RRULEs combined, using the shorter of the two if they differ. 00431 if (days[0] != 29) 00432 { 00433 // Swap the two rules so that the 29th rule is the first 00434 RecurrenceRule* rr = rrules[0]; 00435 rrules[0] = rrules[1]; // the 29th rule 00436 rrules[1] = rr; 00437 int d = days[0]; 00438 days[0] = days[1]; 00439 days[1] = d; // the non-29th day 00440 } 00441 // If February is included in the 29th rule, remove it to avoid duplication 00442 months = rrules[0]->byMonths(); 00443 if (months.removeAll(2)) 00444 rrules[0]->setByMonths(months); 00445 00446 count = combineDurations(rrules[0], rrules[1], end); 00447 mFeb29Type = (days[1] == 60) ? Feb29_Mar1 : Feb29_Feb28; 00448 } 00449 else if (convert == 1 && days[0] == 60) 00450 { 00451 // There is a single 60th day of the year rule. 00452 // Convert it to a February 29th recurrence. 00453 count = mRecurrence.duration(); 00454 if (!count) 00455 end = mRecurrence.endDate(); 00456 mFeb29Type = Feb29_Mar1; 00457 } 00458 else 00459 return; 00460 00461 // Create the new February 29th recurrence 00462 mRecurrence.setNewRecurrenceType(RecurrenceRule::rYearly, mRecurrence.frequency()); 00463 RecurrenceRule* rrule = mRecurrence.defaultRRule(); 00464 months.append(2); 00465 rrule->setByMonths(months); 00466 QList<int> ds; 00467 ds.append(29); 00468 rrule->setByMonthDays(ds); 00469 if (count) 00470 mRecurrence.setDuration(count); 00471 else 00472 mRecurrence.setEndDate(end); 00473 } 00474 00475 /****************************************************************************** 00476 * Initialise a KCal::Recurrence to be the same as this instance. 00477 * Additional recurrence rules are created as necessary if it recurs on Feb 29th. 00478 */ 00479 void KARecurrence::writeRecurrence(Recurrence& recur) const 00480 { 00481 d->writeRecurrence(this, recur); 00482 } 00483 00484 void KARecurrence::Private::writeRecurrence(const KARecurrence* q, Recurrence& recur) const 00485 { 00486 recur.clear(); 00487 recur.setStartDateTime(mRecurrence.startDateTime()); 00488 recur.setExDates(mRecurrence.exDates()); 00489 recur.setExDateTimes(mRecurrence.exDateTimes()); 00490 const RecurrenceRule* rrule = mRecurrence.defaultRRuleConst(); 00491 if (!rrule) 00492 return; 00493 int freq = mRecurrence.frequency(); 00494 int count = mRecurrence.duration(); 00495 static_cast<Recurrence_p*>(&recur)->setNewRecurrenceType(rrule->recurrenceType(), freq); 00496 if (count) 00497 recur.setDuration(count); 00498 else 00499 recur.setEndDateTime(endDateTime()); 00500 switch (q->type()) 00501 { 00502 case DAILY: 00503 if (rrule->byDays().isEmpty()) 00504 break; 00505 // fall through to rWeekly 00506 case WEEKLY: 00507 case MONTHLY_POS: 00508 recur.defaultRRule(true)->setByDays(rrule->byDays()); 00509 break; 00510 case MONTHLY_DAY: 00511 recur.defaultRRule(true)->setByMonthDays(rrule->byMonthDays()); 00512 break; 00513 case ANNUAL_POS: 00514 recur.defaultRRule(true)->setByMonths(rrule->byMonths()); 00515 recur.defaultRRule()->setByDays(rrule->byDays()); 00516 break; 00517 case ANNUAL_DATE: 00518 { 00519 QList<int> months = rrule->byMonths(); 00520 QList<int> days = mRecurrence.monthDays(); 00521 bool special = (mFeb29Type != Feb29_None && !days.isEmpty() 00522 && days.first() == 29 && months.removeAll(2)); 00523 RecurrenceRule* rrule1 = recur.defaultRRule(); 00524 rrule1->setByMonths(months); 00525 rrule1->setByMonthDays(days); 00526 if (!special) 00527 break; 00528 00529 // It recurs on the 29th February. 00530 // Create an additional 60th day of the year, or last day of February, rule. 00531 RecurrenceRule* rrule2 = new RecurrenceRule(); 00532 rrule2->setRecurrenceType(RecurrenceRule::rYearly); 00533 rrule2->setFrequency(freq); 00534 rrule2->setStartDt(mRecurrence.startDateTime()); 00535 rrule2->setAllDay(mRecurrence.allDay()); 00536 if (!count) 00537 rrule2->setEndDt(endDateTime()); 00538 if (mFeb29Type == Feb29_Mar1) 00539 { 00540 QList<int> ds; 00541 ds.append(60); 00542 rrule2->setByYearDays(ds); 00543 } 00544 else 00545 { 00546 QList<int> ds; 00547 ds.append(-1); 00548 rrule2->setByMonthDays(ds); 00549 QList<int> ms; 00550 ms.append(2); 00551 rrule2->setByMonths(ms); 00552 } 00553 00554 if (months.isEmpty()) 00555 { 00556 // Only February recurs. 00557 // Replace the RRULE and keep the recurrence count the same. 00558 if (count) 00559 rrule2->setDuration(count); 00560 recur.unsetRecurs(); 00561 } 00562 else 00563 { 00564 // Months other than February also recur on the 29th. 00565 // Remove February from the list and add a separate RRULE for February. 00566 if (count) 00567 { 00568 rrule1->setDuration(-1); 00569 rrule2->setDuration(-1); 00570 if (count > 0) 00571 { 00572 /* Adjust counts in the two rules to keep the correct occurrence total. 00573 * Note that durationTo() always includes the start date. Since for an 00574 * individual RRULE the start date may not actually be included, we need 00575 * to decrement the count if the start date doesn't actually recur in 00576 * this RRULE. 00577 * Note that if the count is small, one of the rules may not recur at 00578 * all. In that case, retain it so that the February 29th characteristic 00579 * is not lost should the user later change the recurrence count. 00580 */ 00581 KDateTime end = endDateTime(); 00582 int count1 = rrule1->durationTo(end) 00583 - (rrule1->recursOn(mRecurrence.startDate(), mRecurrence.startDateTime().timeSpec()) ? 0 : 1); 00584 if (count1 > 0) 00585 rrule1->setDuration(count1); 00586 else 00587 rrule1->setEndDt(mRecurrence.startDateTime()); 00588 int count2 = rrule2->durationTo(end) 00589 - (rrule2->recursOn(mRecurrence.startDate(), mRecurrence.startDateTime().timeSpec()) ? 0 : 1); 00590 if (count2 > 0) 00591 rrule2->setDuration(count2); 00592 else 00593 rrule2->setEndDt(mRecurrence.startDateTime()); 00594 } 00595 } 00596 } 00597 recur.addRRule(rrule2); 00598 break; 00599 } 00600 default: 00601 break; 00602 } 00603 } 00604 00605 KDateTime KARecurrence::startDateTime() const 00606 { 00607 return d->mRecurrence.startDateTime(); 00608 } 00609 00610 QDate KARecurrence::startDate() const 00611 { 00612 return d->mRecurrence.startDate(); 00613 } 00614 00615 void KARecurrence::setStartDateTime(const KDateTime& dt, bool dateOnly) 00616 { 00617 d->mRecurrence.setStartDateTime(dt); 00618 if (dateOnly) 00619 d->mRecurrence.setAllDay(true); 00620 } 00621 00622 /****************************************************************************** 00623 * Return the date/time of the last recurrence. 00624 */ 00625 KDateTime KARecurrence::endDateTime() const 00626 { 00627 return d->endDateTime(); 00628 } 00629 00630 KDateTime KARecurrence::Private::endDateTime() const 00631 { 00632 if (mFeb29Type == Feb29_None || mRecurrence.duration() <= 1) 00633 { 00634 /* Either it doesn't have any special February 29th treatment, 00635 * it's infinite (count = -1), the end date is specified 00636 * (count = 0), or it ends on the start date (count = 1). 00637 * So just use the normal KCal end date calculation. 00638 */ 00639 return mRecurrence.endDateTime(); 00640 } 00641 00642 /* Create a temporary recurrence rule to find the end date. 00643 * In a standard KCal recurrence, the 29th February only occurs once every 00644 * 4 years. So shift the temporary recurrence date to the 28th to ensure 00645 * that it occurs every year, thus giving the correct occurrence count. 00646 */ 00647 RecurrenceRule* rrule = new RecurrenceRule(); 00648 rrule->setRecurrenceType(RecurrenceRule::rYearly); 00649 KDateTime dt = mRecurrence.startDateTime(); 00650 QDate da = dt.date(); 00651 switch (da.day()) 00652 { 00653 case 29: 00654 // The start date is definitely a recurrence date, so shift 00655 // start date to the temporary recurrence date of the 28th 00656 da.setYMD(da.year(), da.month(), 28); 00657 break; 00658 case 28: 00659 if (da.month() != 2 || mFeb29Type != Feb29_Feb28 || QDate::isLeapYear(da.year())) 00660 { 00661 // Start date is not a recurrence date, so shift it to 27th 00662 da.setYMD(da.year(), da.month(), 27); 00663 } 00664 break; 00665 case 1: 00666 if (da.month() == 3 && mFeb29Type == Feb29_Mar1 && !QDate::isLeapYear(da.year())) 00667 { 00668 // Start date is a March 1st recurrence date, so shift 00669 // start date to the temporary recurrence date of the 28th 00670 da.setYMD(da.year(), 2, 28); 00671 } 00672 break; 00673 default: 00674 break; 00675 } 00676 dt.setDate(da); 00677 rrule->setStartDt(dt); 00678 rrule->setAllDay(mRecurrence.allDay()); 00679 rrule->setFrequency(mRecurrence.frequency()); 00680 rrule->setDuration(mRecurrence.duration()); 00681 QList<int> ds; 00682 ds.append(28); 00683 rrule->setByMonthDays(ds); 00684 rrule->setByMonths(mRecurrence.defaultRRuleConst()->byMonths()); 00685 dt = rrule->endDt(); 00686 delete rrule; 00687 00688 // We've found the end date for a recurrence on the 28th. Unless that date 00689 // is a real February 28th recurrence, adjust to the actual recurrence date. 00690 if (mFeb29Type == Feb29_Feb28 && dt.date().month() == 2 && !QDate::isLeapYear(dt.date().year())) 00691 return dt; 00692 return dt.addDays(1); 00693 } 00694 00695 /****************************************************************************** 00696 * Return the date of the last recurrence. 00697 */ 00698 QDate KARecurrence::endDate() const 00699 { 00700 KDateTime end = endDateTime(); 00701 return end.isValid() ? end.date() : QDate(); 00702 } 00703 00704 void KARecurrence::setEndDate(const QDate& endDate) 00705 { 00706 d->mRecurrence.setEndDate(endDate); 00707 } 00708 00709 void KARecurrence::setEndDateTime(const KDateTime& endDateTime) 00710 { 00711 d->mRecurrence.setEndDateTime(endDateTime); 00712 } 00713 00714 bool KARecurrence::allDay() const 00715 { 00716 return d->mRecurrence.allDay(); 00717 } 00718 00719 void KARecurrence::setRecurReadOnly(bool readOnly) 00720 { 00721 d->mRecurrence.setRecurReadOnly(readOnly); 00722 } 00723 00724 bool KARecurrence::recurReadOnly() const 00725 { 00726 return d->mRecurrence.recurReadOnly(); 00727 } 00728 00729 bool KARecurrence::recurs() const 00730 { 00731 return d->mRecurrence.recurs(); 00732 } 00733 00734 QBitArray KARecurrence::days() const 00735 { 00736 return d->mRecurrence.days(); 00737 } 00738 00739 QList<RecurrenceRule::WDayPos> KARecurrence::monthPositions() const 00740 { 00741 return d->mRecurrence.monthPositions(); 00742 } 00743 00744 QList<int> KARecurrence::monthDays() const 00745 { 00746 return d->mRecurrence.monthDays(); 00747 } 00748 00749 QList<int> KARecurrence::yearDays() const 00750 { 00751 return d->mRecurrence.yearDays(); 00752 } 00753 00754 QList<int> KARecurrence::yearDates() const 00755 { 00756 return d->mRecurrence.yearDates(); 00757 } 00758 00759 QList<int> KARecurrence::yearMonths() const 00760 { 00761 return d->mRecurrence.yearMonths(); 00762 } 00763 00764 QList<RecurrenceRule::WDayPos> KARecurrence::yearPositions() const 00765 { 00766 return d->mRecurrence.yearPositions(); 00767 } 00768 00769 void KARecurrence::addWeeklyDays(const QBitArray& days) 00770 { 00771 d->mRecurrence.addWeeklyDays(days); 00772 } 00773 00774 void KARecurrence::addYearlyDay(int day) 00775 { 00776 d->mRecurrence.addYearlyDay(day); 00777 } 00778 00779 void KARecurrence::addYearlyDate(int date) 00780 { 00781 d->mRecurrence.addYearlyDate(date); 00782 } 00783 00784 void KARecurrence::addYearlyMonth(short month) 00785 { 00786 d->mRecurrence.addYearlyMonth(month); 00787 } 00788 00789 void KARecurrence::addYearlyPos(short pos, const QBitArray& days) 00790 { 00791 d->mRecurrence.addYearlyPos(pos, days); 00792 } 00793 00794 void KARecurrence::addMonthlyPos(short pos, const QBitArray& days) 00795 { 00796 d->mRecurrence.addMonthlyPos(pos, days); 00797 } 00798 00799 void KARecurrence::addMonthlyPos(short pos, ushort day) 00800 { 00801 d->mRecurrence.addMonthlyPos(pos, day); 00802 } 00803 00804 void KARecurrence::addMonthlyDate(short day) 00805 { 00806 d->mRecurrence.addMonthlyDate(day); 00807 } 00808 00809 /****************************************************************************** 00810 * Get the next time the recurrence occurs, strictly after a specified time. 00811 */ 00812 KDateTime KARecurrence::getNextDateTime(const KDateTime& preDateTime) const 00813 { 00814 switch (type()) 00815 { 00816 case ANNUAL_DATE: 00817 case ANNUAL_POS: 00818 { 00819 Recurrence recur; 00820 writeRecurrence(recur); 00821 return recur.getNextDateTime(preDateTime); 00822 } 00823 default: 00824 return d->mRecurrence.getNextDateTime(preDateTime); 00825 } 00826 } 00827 00828 /****************************************************************************** 00829 * Get the previous time the recurrence occurred, strictly before a specified time. 00830 */ 00831 KDateTime KARecurrence::getPreviousDateTime(const KDateTime& afterDateTime) const 00832 { 00833 switch (type()) 00834 { 00835 case ANNUAL_DATE: 00836 case ANNUAL_POS: 00837 { 00838 Recurrence recur; 00839 writeRecurrence(recur); 00840 return recur.getPreviousDateTime(afterDateTime); 00841 } 00842 default: 00843 return d->mRecurrence.getPreviousDateTime(afterDateTime); 00844 } 00845 } 00846 00847 /****************************************************************************** 00848 * Return whether the event will recur on the specified date. 00849 * The start date only returns true if it matches the recurrence rules. 00850 */ 00851 bool KARecurrence::recursOn(const QDate& dt, const KDateTime::Spec& timeSpec) const 00852 { 00853 if (!d->mRecurrence.recursOn(dt, timeSpec)) 00854 return false; 00855 if (dt != d->mRecurrence.startDate()) 00856 return true; 00857 // We know now that it isn't in EXDATES or EXRULES, 00858 // so we just need to check if it's in RDATES or RRULES 00859 if (d->mRecurrence.rDates().contains(dt)) 00860 return true; 00861 RecurrenceRule::List rulelist = d->mRecurrence.rRules(); 00862 for (int rri = 0, rrend = rulelist.count(); rri < rrend; ++rri) 00863 if (rulelist[rri]->recursOn(dt, timeSpec)) 00864 return true; 00865 DateTimeList dtlist = d->mRecurrence.rDateTimes(); 00866 for (int dti = 0, dtend = dtlist.count(); dti < dtend; ++dti) 00867 if (dtlist[dti].date() == dt) 00868 return true; 00869 return false; 00870 } 00871 00872 bool KARecurrence::recursAt(const KDateTime& dt) const 00873 { 00874 return d->mRecurrence.recursAt(dt); 00875 } 00876 00877 TimeList KARecurrence::recurTimesOn(const QDate& date, const KDateTime::Spec& timeSpec) const 00878 { 00879 return d->mRecurrence.recurTimesOn(date, timeSpec); 00880 } 00881 00882 DateTimeList KARecurrence::timesInInterval(const KDateTime& start, const KDateTime& end) const 00883 { 00884 return d->mRecurrence.timesInInterval(start, end); 00885 } 00886 00887 int KARecurrence::frequency() const 00888 { 00889 return d->mRecurrence.frequency(); 00890 } 00891 00892 void KARecurrence::setFrequency(int freq) 00893 { 00894 d->mRecurrence.setFrequency(freq); 00895 } 00896 00897 int KARecurrence::duration() const 00898 { 00899 return d->mRecurrence.duration(); 00900 } 00901 00902 void KARecurrence::setDuration(int duration) 00903 { 00904 d->mRecurrence.setDuration(duration); 00905 } 00906 00907 int KARecurrence::durationTo(const KDateTime& dt) const 00908 { 00909 return d->mRecurrence.durationTo(dt); 00910 } 00911 00912 int KARecurrence::durationTo(const QDate& date) const 00913 { 00914 return d->mRecurrence.durationTo(date); 00915 } 00916 00917 /****************************************************************************** 00918 * Find the duration of two RRULEs combined. 00919 * Use the shorter of the two if they differ. 00920 */ 00921 int KARecurrence::Private::combineDurations(const RecurrenceRule* rrule1, const RecurrenceRule* rrule2, QDate& end) const 00922 { 00923 int count1 = rrule1->duration(); 00924 int count2 = rrule2->duration(); 00925 if (count1 == -1 && count2 == -1) 00926 return -1; 00927 00928 // One of the RRULEs may not recur at all if the recurrence count is small. 00929 // In this case, its end date will have been set to the start date. 00930 if (count1 && !count2 && rrule2->endDt().date() == mRecurrence.startDateTime().date()) 00931 return count1; 00932 if (count2 && !count1 && rrule1->endDt().date() == mRecurrence.startDateTime().date()) 00933 return count2; 00934 00935 /* The duration counts will be different even for RRULEs of the same length, 00936 * because the first RRULE only actually occurs every 4 years. So we need to 00937 * compare the end dates. 00938 */ 00939 if (!count1 || !count2) 00940 count1 = count2 = 0; 00941 // Get the two rules sorted by end date. 00942 KDateTime end1 = rrule1->endDt(); 00943 KDateTime end2 = rrule2->endDt(); 00944 if (end1.date() == end2.date()) 00945 { 00946 end = end1.date(); 00947 return count1 + count2; 00948 } 00949 const RecurrenceRule* rr1; // earlier end date 00950 const RecurrenceRule* rr2; // later end date 00951 if (end2.isValid() 00952 && (!end1.isValid() || end1.date() > end2.date())) 00953 { 00954 // Swap the two rules to make rr1 have the earlier end date 00955 rr1 = rrule2; 00956 rr2 = rrule1; 00957 KDateTime e = end1; 00958 end1 = end2; 00959 end2 = e; 00960 } 00961 else 00962 { 00963 rr1 = rrule1; 00964 rr2 = rrule2; 00965 } 00966 00967 // Get the date of the next occurrence after the end of the earlier ending rule 00968 RecurrenceRule rr(*rr1); 00969 rr.setDuration(-1); 00970 KDateTime next1(rr.getNextDate(end1)); 00971 next1.setDateOnly(true); 00972 if (!next1.isValid()) 00973 end = end1.date(); 00974 else 00975 { 00976 if (end2.isValid() && next1 > end2) 00977 { 00978 // The next occurrence after the end of the earlier ending rule 00979 // is later than the end of the later ending rule. So simply use 00980 // the end date of the later rule. 00981 end = end2.date(); 00982 return count1 + count2; 00983 } 00984 QDate prev2 = rr2->getPreviousDate(next1).date(); 00985 end = (prev2 > end1.date()) ? prev2 : end1.date(); 00986 } 00987 if (count2) 00988 count2 = rr2->durationTo(end); 00989 return count1 + count2; 00990 } 00991 00992 /****************************************************************************** 00993 * Return the longest interval between recurrences. 00994 * Reply = 0 if it never recurs. 00995 */ 00996 Duration KARecurrence::longestInterval() const 00997 { 00998 int freq = d->mRecurrence.frequency(); 00999 switch (type()) 01000 { 01001 case MINUTELY: 01002 return Duration(freq * 60, Duration::Seconds); 01003 01004 case DAILY: 01005 { 01006 QList<RecurrenceRule::WDayPos> days = d->mRecurrence.defaultRRuleConst()->byDays(); 01007 if (days.isEmpty()) 01008 return Duration(freq, Duration::Days); 01009 01010 // After applying the frequency, the specified days of the week 01011 // further restrict when the recurrence occurs. 01012 // So the maximum interval may be greater than the frequency. 01013 bool ds[7] = { false, false, false, false, false, false, false }; 01014 for (int i = 0, end = days.count(); i < end; ++i) 01015 if (days[i].pos() == 0) 01016 ds[days[i].day() - 1] = true; 01017 if (freq % 7) 01018 { 01019 // It will recur on every day of the week in some week or other 01020 // (except for those days which are excluded). 01021 int first = -1; 01022 int last = -1; 01023 int maxgap = 1; 01024 for (int i = 0; i < freq*7; i += freq) 01025 { 01026 if (ds[i % 7]) 01027 { 01028 if (first < 0) 01029 first = i; 01030 else if (i - last > maxgap) 01031 maxgap = i - last; 01032 last = i; 01033 } 01034 } 01035 int wrap = freq*7 - last + first; 01036 if (wrap > maxgap) 01037 maxgap = wrap; 01038 return Duration(maxgap, Duration::Days); 01039 } 01040 else 01041 { 01042 // It will recur on the same day of the week every time. 01043 // Ensure that the day is a day which is not excluded. 01044 if (ds[d->mRecurrence.startDate().dayOfWeek() - 1]) 01045 return Duration(freq, Duration::Days); 01046 break; 01047 } 01048 } 01049 case WEEKLY: 01050 { 01051 // Find which days of the week it recurs on, and if on more than 01052 // one, reduce the maximum interval accordingly. 01053 QBitArray ds = d->mRecurrence.days(); 01054 int first = -1; 01055 int last = -1; 01056 int maxgap = 1; 01057 // Use the user's definition of the week, starting at the 01058 // day of the week specified by the user's locale. 01059 int weekStart = KGlobal::locale()->weekStartDay() - 1; // zero-based 01060 for (int i = 0; i < 7; ++i) 01061 { 01062 // Get the standard KDE day-of-week number (zero-based) 01063 // for the day-of-week number in the user's locale. 01064 if (ds.testBit((i + weekStart) % 7)) 01065 { 01066 if (first < 0) 01067 first = i; 01068 else if (i - last > maxgap) 01069 maxgap = i - last; 01070 last = i; 01071 } 01072 } 01073 if (first < 0) 01074 break; // no days recur 01075 int span = last - first; 01076 if (freq > 1) 01077 return Duration(freq*7 - span, Duration::Days); 01078 if (7 - span > maxgap) 01079 return Duration(7 - span, Duration::Days); 01080 return Duration(maxgap, Duration::Days); 01081 } 01082 case MONTHLY_DAY: 01083 case MONTHLY_POS: 01084 return Duration(freq * 31, Duration::Days); 01085 01086 case ANNUAL_DATE: 01087 case ANNUAL_POS: 01088 { 01089 // Find which months of the year it recurs on, and if on more than 01090 // one, reduce the maximum interval accordingly. 01091 const QList<int> months = d->mRecurrence.yearMonths(); // month list is sorted 01092 if (months.isEmpty()) 01093 break; // no months recur 01094 if (months.count() == 1) 01095 return Duration(freq * 365, Duration::Days); 01096 int first = -1; 01097 int last = -1; 01098 int maxgap = 0; 01099 for (int i = 0, end = months.count(); i < end; ++i) 01100 { 01101 if (first < 0) 01102 first = months[i]; 01103 else 01104 { 01105 int span = QDate(2001, last, 1).daysTo(QDate(2001, months[i], 1)); 01106 if (span > maxgap) 01107 maxgap = span; 01108 } 01109 last = months[i]; 01110 } 01111 int span = QDate(2001, first, 1).daysTo(QDate(2001, last, 1)); 01112 if (freq > 1) 01113 return Duration(freq*365 - span, Duration::Days); 01114 if (365 - span > maxgap) 01115 return Duration(365 - span, Duration::Days); 01116 return Duration(maxgap, Duration::Days); 01117 } 01118 default: 01119 break; 01120 } 01121 return 0; 01122 } 01123 01124 /****************************************************************************** 01125 * Return the interval between recurrences, if the interval between successive 01126 * occurrences does not vary. 01127 * Reply = 0 if recurrence does not occur at fixed intervals. 01128 */ 01129 Duration KARecurrence::regularInterval() const 01130 { 01131 int freq = d->mRecurrence.frequency(); 01132 switch (type()) 01133 { 01134 case MINUTELY: 01135 return Duration(freq * 60, Duration::Seconds); 01136 case DAILY: 01137 { 01138 QList<RecurrenceRule::WDayPos> days = d->mRecurrence.defaultRRuleConst()->byDays(); 01139 if (days.isEmpty()) 01140 return Duration(freq, Duration::Days); 01141 // After applying the frequency, the specified days of the week 01142 // further restrict when the recurrence occurs. 01143 // Find which days occur, and count the number of days which occur. 01144 bool ds[7] = { false, false, false, false, false, false, false }; 01145 for (int i = 0, end = days.count(); i < end; ++i) 01146 if (days[i].pos() == 0) 01147 ds[days[i].day() - 1] = true; 01148 if (!(freq % 7)) 01149 { 01150 // It will recur on the same day of the week every time. 01151 // Check whether that day is in the list of included days. 01152 if (ds[d->mRecurrence.startDate().dayOfWeek() - 1]) 01153 return Duration(freq, Duration::Days); 01154 break; 01155 } 01156 int n = 0; // number of days which occur 01157 for (int i = 0; i < 7; ++i) 01158 if (ds[i]) 01159 ++n; 01160 if (n == 7) 01161 return Duration(freq, Duration::Days); // every day is included 01162 if (n == 1) 01163 return Duration(freq * 7, Duration::Days); // only one day of the week is included 01164 break; 01165 } 01166 case WEEKLY: 01167 { 01168 QList<RecurrenceRule::WDayPos> days = d->mRecurrence.defaultRRuleConst()->byDays(); 01169 if (days.isEmpty()) 01170 return Duration(freq * 7, Duration::Days); 01171 // The specified days of the week occur every week in which the 01172 // recurrence occurs. 01173 // Find which days occur, and count the number of days which occur. 01174 bool ds[7] = { false, false, false, false, false, false, false }; 01175 for (int i = 0, end = days.count(); i < end; ++i) 01176 if (days[i].pos() == 0) 01177 ds[days[i].day() - 1] = true; 01178 int n = 0; // number of days which occur 01179 for (int i = 0; i < 7; ++i) 01180 if (ds[i]) 01181 ++n; 01182 if (n == 7) 01183 { 01184 if (freq == 1) 01185 return Duration(freq, Duration::Days); // every day is included 01186 break; 01187 } 01188 if (n == 1) 01189 return Duration(freq * 7, Duration::Days); // only one day of the week is included 01190 break; 01191 } 01192 default: 01193 break; 01194 } 01195 return 0; 01196 } 01197 01198 DateTimeList KARecurrence::exDateTimes() const 01199 { 01200 return d->mRecurrence.exDateTimes(); 01201 } 01202 01203 DateList KARecurrence::exDates() const 01204 { 01205 return d->mRecurrence.exDates(); 01206 } 01207 01208 void KARecurrence::setExDateTimes(const DateTimeList& exdates) 01209 { 01210 d->mRecurrence.setExDateTimes(exdates); 01211 } 01212 01213 void KARecurrence::setExDates(const DateList& exdates) 01214 { 01215 d->mRecurrence.setExDates(exdates); 01216 } 01217 01218 void KARecurrence::addExDateTime(const KDateTime& exdate) 01219 { 01220 d->mRecurrence.addExDateTime(exdate); 01221 } 01222 01223 void KARecurrence::addExDate(const QDate& exdate) 01224 { 01225 d->mRecurrence.addExDate(exdate); 01226 } 01227 01228 void KARecurrence::shiftTimes(const KDateTime::Spec& oldSpec, const KDateTime::Spec& newSpec) 01229 { 01230 d->mRecurrence.shiftTimes(oldSpec, newSpec); 01231 } 01232 01233 RecurrenceRule* KARecurrence::defaultRRuleConst() const 01234 { 01235 return d->mRecurrence.defaultRRuleConst(); 01236 } 01237 01238 /****************************************************************************** 01239 * Return the recurrence's period type. 01240 */ 01241 KARecurrence::Type KARecurrence::type() const 01242 { 01243 if (d->mCachedType == -1) 01244 d->mCachedType = type(d->mRecurrence.defaultRRuleConst()); 01245 return static_cast<Type>(d->mCachedType); 01246 } 01247 01248 /****************************************************************************** 01249 * Return the recurrence rule type. 01250 */ 01251 KARecurrence::Type KARecurrence::type(const RecurrenceRule* rrule) 01252 { 01253 switch (Recurrence::recurrenceType(rrule)) 01254 { 01255 case Recurrence::rMinutely: return MINUTELY; 01256 case Recurrence::rDaily: return DAILY; 01257 case Recurrence::rWeekly: return WEEKLY; 01258 case Recurrence::rMonthlyDay: return MONTHLY_DAY; 01259 case Recurrence::rMonthlyPos: return MONTHLY_POS; 01260 case Recurrence::rYearlyMonth: return ANNUAL_DATE; 01261 case Recurrence::rYearlyPos: return ANNUAL_POS; 01262 default: 01263 if (dailyType(rrule)) 01264 return DAILY; 01265 return NO_RECUR; 01266 } 01267 } 01268 01269 /****************************************************************************** 01270 * Check if the rule is a daily rule with or without BYDAYS specified. 01271 */ 01272 bool KARecurrence::dailyType(const RecurrenceRule* rrule) 01273 { 01274 if (rrule->recurrenceType() != RecurrenceRule::rDaily 01275 || !rrule->bySeconds().isEmpty() 01276 || !rrule->byMinutes().isEmpty() 01277 || !rrule->byHours().isEmpty() 01278 || !rrule->byWeekNumbers().isEmpty() 01279 || !rrule->byMonthDays().isEmpty() 01280 || !rrule->byMonths().isEmpty() 01281 || !rrule->bySetPos().isEmpty() 01282 || !rrule->byYearDays().isEmpty()) 01283 return false; 01284 QList<RecurrenceRule::WDayPos> days = rrule->byDays(); 01285 if (days.isEmpty()) 01286 return true; 01287 // Check that all the positions are zero (i.e. every time) 01288 bool found = false; 01289 for (int i = 0, end = days.count(); i < end; ++i) 01290 { 01291 if (days[i].pos() != 0) 01292 return false; 01293 found = true; 01294 } 01295 return found; 01296 } 01297 01298 } // namespace KAlarmCal 01299 01300 // vim: et sw=4:
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Mon May 14 2012 05:13:36 by doxygen 1.7.5 written by Dimitri van Heesch, © 1997-2006
Documentation copyright © 1996-2012 The KDE developers.
Generated on Mon May 14 2012 05:13:36 by doxygen 1.7.5 written by Dimitri van Heesch, © 1997-2006
KDE's Doxygen guidelines are available online.