• Skip to content
  • Skip to link menu
KDE 4.8 API Reference
  • KDE API Reference
  • KDE-PIM Libraries
  • KDE Home
  • Contact Us
 

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-2011 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 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 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:

KAlarm Library

Skip menu "KAlarm Library"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • Related Pages

KDE-PIM Libraries

Skip menu "KDE-PIM Libraries"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Generated for KDE-PIM Libraries by doxygen 1.7.6.1
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal