kalarm

karecurrence.cpp

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