• Skip to content
  • Skip to link menu
KDE 4.2 API Reference
  • KDE API Reference
  • kdepim
  • Sitemap
  • Contact Us
 

kalarm

karecurrence.cpp

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

kalarm

Skip menu "kalarm"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members

kdepim

Skip menu "kdepim"
  • akonadi
  •   clients
  •   kabc
  •   kcal
  •   kcm
  • akregator
  • console
  •   kabcclient
  •   konsolekalendar
  • kaddressbook
  • kalarm
  •   lib
  • kdgantt
  • kdgantt1
  • kjots
  • kleopatra
  • kmail
  • kmobiletools
  • knode
  • knotes
  • kontact
  • kontactinterfaces
  • korganizer
  •   korgac
  • kpilot
  • ktimetracker
  •   doc
  • libkdepim
  • libkholidays
  • libkleo
  • libkpgp
  • maildir
Generated for kdepim by doxygen 1.5.4
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal