kalarm

karecurrence.cpp

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