kalarm

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