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

KCalCore Library

recurrencerule.cpp
00001 /*
00002   This file is part of the kcalcore library.
00003 
00004   Copyright (c) 2005 Reinhold Kainhofer <reinhold@kainhofe.com>
00005   Copyright (c) 2006-2008 David Jarvie <djarvie@kde.org>
00006 
00007   This library is free software; you can redistribute it and/or
00008   modify it under the terms of the GNU Library General Public
00009   License as published by the Free Software Foundation; either
00010   version 2 of the License, or (at your option) any later version.
00011 
00012   This library is distributed in the hope that it will be useful,
00013   but WITHOUT ANY WARRANTY; without even the implied warranty of
00014   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00015   Library General Public License for more details.
00016 
00017   You should have received a copy of the GNU Library General Public License
00018   along with this library; see the file COPYING.LIB.  If not, write to
00019   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00020   Boston, MA 02110-1301, USA.
00021 */
00022 #include "recurrencerule.h"
00023 
00024 #include <KDebug>
00025 
00026 #include <QtCore/QStringList>
00027 
00028 using namespace KCalCore;
00029 
00030 // Maximum number of intervals to process
00031 const int LOOP_LIMIT = 10000;
00032 
00033 static QString dumpTime( const KDateTime &dt );   // for debugging
00034 
00035 /*=========================================================================
00036 =                                                                         =
00037 = IMPORTANT CODING NOTE:                                                  =
00038 =                                                                         =
00039 = Recurrence handling code is time critical, especially for sub-daily     =
00040 = recurrences. For example, if getNextDate() is called repeatedly to      =
00041 = check all consecutive occurrences over a few years, on a slow machine   =
00042 = this could take many seconds to complete in the worst case. Simple      =
00043 = sub-daily recurrences are optimised by use of mTimedRepetition.         =
00044 =                                                                         =
00045 ==========================================================================*/
00046 
00047 /**************************************************************************
00048  *                               DateHelper                               *
00049  **************************************************************************/
00050 //@cond PRIVATE
00051 class DateHelper
00052 {
00053   public:
00054 #ifndef NDEBUG
00055     static QString dayName( short day );
00056 #endif
00057     static QDate getNthWeek( int year, int weeknumber, short weekstart = 1 );
00058     static int weekNumbersInYear( int year, short weekstart = 1 );
00059     static int getWeekNumber( const QDate &date, short weekstart, int *year = 0 );
00060     static int getWeekNumberNeg( const QDate &date, short weekstart, int *year = 0 );
00061     // Convert to QDate, allowing for day < 0.
00062     // month and day must be non-zero.
00063     static QDate getDate( int year, int month, int day )
00064     {
00065       if ( day >= 0 ) {
00066         return QDate( year, month, day );
00067       } else {
00068         if ( ++month > 12 ) {
00069           month = 1;
00070           ++year;
00071         }
00072         return QDate( year, month, 1 ).addDays( day );
00073       }
00074     }
00075 };
00076 
00077 #ifndef NDEBUG
00078 // TODO: Move to a general library / class, as we need the same in the iCal
00079 //       generator and in the xcal format
00080 QString DateHelper::dayName( short day )
00081 {
00082   switch ( day ) {
00083   case 1:
00084     return "MO";
00085   case 2:
00086     return "TU";
00087   case 3:
00088     return "WE";
00089   case 4:
00090     return "TH";
00091   case 5:
00092     return "FR";
00093   case 6:
00094     return "SA";
00095   case 7:
00096     return "SU";
00097   default:
00098     return "??";
00099   }
00100 }
00101 #endif
00102 
00103 QDate DateHelper::getNthWeek( int year, int weeknumber, short weekstart )
00104 {
00105   if ( weeknumber == 0 ) {
00106     return QDate();
00107   }
00108 
00109   // Adjust this to the first day of week #1 of the year and add 7*weekno days.
00110   QDate dt( year, 1, 4 ); // Week #1 is the week that contains Jan 4
00111   int adjust = -( 7 + dt.dayOfWeek() - weekstart ) % 7;
00112   if ( weeknumber > 0 ) {
00113     dt = dt.addDays( 7 * (weeknumber-1) + adjust );
00114   } else if ( weeknumber < 0 ) {
00115     dt = dt.addYears( 1 );
00116     dt = dt.addDays( 7 * weeknumber + adjust );
00117   }
00118   return dt;
00119 }
00120 
00121 int DateHelper::getWeekNumber( const QDate &date, short weekstart, int *year )
00122 {
00123   int y = date.year();
00124   QDate dt( y, 1, 4 ); // <= definitely in week #1
00125   dt = dt.addDays( -( 7 + dt.dayOfWeek() - weekstart ) % 7 ); // begin of week #1
00126 
00127   int daysto = dt.daysTo( date );
00128   if ( daysto < 0 ) {
00129     // in first week of year
00130     --y;
00131     dt = QDate( y, 1, 4 );
00132     dt = dt.addDays( -( 7 + dt.dayOfWeek() - weekstart ) % 7 ); // begin of week #1
00133     daysto = dt.daysTo( date );
00134   } else if ( daysto > 355 ) {
00135     // near the end of the year - check if it's next year
00136     QDate dtn( y+1, 1, 4 ); // <= definitely first week of next year
00137     dtn = dtn.addDays( -( 7 + dtn.dayOfWeek() - weekstart ) % 7 );
00138     int dayston = dtn.daysTo( date );
00139     if ( dayston >= 0 ) {
00140       // in first week of next year;
00141       ++y;
00142       daysto = dayston;
00143     }
00144   }
00145   if ( year ) {
00146     *year = y;
00147   }
00148   return daysto / 7 + 1;
00149 }
00150 
00151 int DateHelper::weekNumbersInYear( int year, short weekstart )
00152 {
00153   QDate dt( year, 1, weekstart );
00154   QDate dt1( year + 1, 1, weekstart );
00155   return dt.daysTo( dt1 ) / 7;
00156 }
00157 
00158 // Week number from the end of the year
00159 int DateHelper::getWeekNumberNeg( const QDate &date, short weekstart, int *year )
00160 {
00161   int weekpos = getWeekNumber( date, weekstart, year );
00162   return weekNumbersInYear( *year, weekstart ) - weekpos - 1;
00163 }
00164 //@endcond
00165 
00166 /**************************************************************************
00167  *                               WDayPos                                  *
00168  **************************************************************************/
00169 
00170 bool RecurrenceRule::WDayPos::operator==( const RecurrenceRule::WDayPos &pos2 ) const
00171 {
00172   return mDay == pos2.mDay && mPos == pos2.mPos;
00173 }
00174 
00175 bool RecurrenceRule::WDayPos::operator!=( const RecurrenceRule::WDayPos &pos2 ) const
00176 {
00177   return !operator==( pos2 );
00178 }
00179 
00180 /**************************************************************************
00181  *                               Constraint                               *
00182  **************************************************************************/
00183 //@cond PRIVATE
00184 class Constraint
00185 {
00186   public:
00187     typedef QList<Constraint> List;
00188 
00189     explicit Constraint( KDateTime::Spec, int wkst = 1 );
00190     Constraint( const KDateTime &dt, RecurrenceRule::PeriodType type, int wkst );
00191     void clear();
00192     void setYear( int n )
00193     {
00194       year = n;
00195       useCachedDt = false;
00196     }
00197     void setMonth( int n )
00198     {
00199       month = n;
00200       useCachedDt = false;
00201     }
00202     void setDay( int n )
00203     {
00204       day = n;
00205       useCachedDt = false;
00206     }
00207     void setHour( int n )
00208     {
00209       hour = n;
00210       useCachedDt = false;
00211     }
00212     void setMinute( int n )
00213     {
00214       minute = n;
00215       useCachedDt = false;
00216     }
00217     void setSecond( int n )
00218     {
00219       second = n;
00220       useCachedDt = false;
00221     }
00222     void setWeekday( int n )
00223     {
00224       weekday = n;
00225       useCachedDt = false;
00226     }
00227     void setWeekdaynr( int n )
00228     {
00229       weekdaynr = n;
00230       useCachedDt = false;
00231     }
00232     void setWeeknumber( int n )
00233     {
00234       weeknumber = n;
00235       useCachedDt = false;
00236     }
00237     void setYearday( int n )
00238     {
00239       yearday = n;
00240       useCachedDt = false;
00241     }
00242     void setWeekstart( int n )
00243     {
00244       weekstart = n;
00245       useCachedDt = false;
00246     }
00247     void setSecondOccurrence( int n )
00248     {
00249       secondOccurrence = n;
00250       useCachedDt = false;
00251     }
00252 
00253     int year;       // 0 means unspecified
00254     int month;      // 0 means unspecified
00255     int day;        // 0 means unspecified
00256     int hour;       // -1 means unspecified
00257     int minute;     // -1 means unspecified
00258     int second;     // -1 means unspecified
00259     int weekday;    //  0 means unspecified
00260     int weekdaynr;  // index of weekday in month/year (0=unspecified)
00261     int weeknumber; //  0 means unspecified
00262     int yearday;    //  0 means unspecified
00263     int weekstart;  //  first day of week (1=monday, 7=sunday, 0=unspec.)
00264     KDateTime::Spec timespec;   // time zone etc. to use
00265     bool secondOccurrence;  // the time is the second occurrence during daylight savings shift
00266 
00267     bool readDateTime( const KDateTime &dt, RecurrenceRule::PeriodType type );
00268     bool matches( const QDate &dt, RecurrenceRule::PeriodType type ) const;
00269     bool matches( const KDateTime &dt, RecurrenceRule::PeriodType type ) const;
00270     bool merge( const Constraint &interval );
00271     bool isConsistent() const;
00272     bool isConsistent( RecurrenceRule::PeriodType period ) const;
00273     bool increase( RecurrenceRule::PeriodType type, int freq );
00274     KDateTime intervalDateTime( RecurrenceRule::PeriodType type ) const;
00275     QList<KDateTime> dateTimes( RecurrenceRule::PeriodType type ) const;
00276     void appendDateTime( const QDate &date, const QTime &time, QList<KDateTime> &list ) const;
00277     void dump() const;
00278 
00279   private:
00280     mutable bool useCachedDt;
00281     mutable KDateTime cachedDt;
00282 };
00283 
00284 Constraint::Constraint( KDateTime::Spec spec, int wkst )
00285   : weekstart( wkst ),
00286     timespec( spec )
00287 {
00288   clear();
00289 }
00290 
00291 Constraint::Constraint( const KDateTime &dt, RecurrenceRule::PeriodType type, int wkst )
00292   : weekstart( wkst ),
00293     timespec( dt.timeSpec() )
00294 {
00295   clear();
00296   readDateTime( dt, type );
00297 }
00298 
00299 void Constraint::clear()
00300 {
00301   year = 0;
00302   month = 0;
00303   day = 0;
00304   hour = -1;
00305   minute = -1;
00306   second = -1;
00307   weekday = 0;
00308   weekdaynr = 0;
00309   weeknumber = 0;
00310   yearday = 0;
00311   secondOccurrence = false;
00312   useCachedDt = false;
00313 }
00314 
00315 bool Constraint::matches( const QDate &dt, RecurrenceRule::PeriodType type ) const
00316 {
00317   // If the event recurs in week 53 or 1, the day might not belong to the same
00318   // year as the week it is in. E.g. Jan 1, 2005 is in week 53 of year 2004.
00319   // So we can't simply check the year in that case!
00320   if ( weeknumber == 0 ) {
00321     if ( year > 0 && year != dt.year() ) {
00322       return false;
00323     }
00324   } else {
00325     int y;
00326     if ( weeknumber > 0 &&
00327          weeknumber != DateHelper::getWeekNumber( dt, weekstart, &y ) ) {
00328       return false;
00329     }
00330     if ( weeknumber < 0 &&
00331          weeknumber != DateHelper::getWeekNumberNeg( dt, weekstart, &y ) ) {
00332       return false;
00333     }
00334     if ( year > 0 && year != y ) {
00335       return false;
00336     }
00337   }
00338 
00339   if ( month > 0 && month != dt.month() ) {
00340     return false;
00341   }
00342   if ( day > 0 && day != dt.day() ) {
00343     return false;
00344   }
00345   if ( day < 0 && dt.day() != ( dt.daysInMonth() + day + 1 ) ) {
00346     return false;
00347   }
00348   if ( weekday > 0 ) {
00349     if ( weekday != dt.dayOfWeek() ) {
00350       return false;
00351     }
00352     if ( weekdaynr != 0 ) {
00353       // If it's a yearly recurrence and a month is given, the position is
00354       // still in the month, not in the year.
00355       if ( ( type == RecurrenceRule::rMonthly ) ||
00356            ( type == RecurrenceRule::rYearly && month > 0 ) ) {
00357         // Monthly
00358         if ( weekdaynr > 0 &&
00359              weekdaynr != ( dt.day() - 1 ) / 7 + 1 ) {
00360           return false;
00361         }
00362         if ( weekdaynr < 0 &&
00363              weekdaynr != -( ( dt.daysInMonth() - dt.day() ) / 7 + 1 ) ) {
00364           return false;
00365         }
00366       } else {
00367         // Yearly
00368         if ( weekdaynr > 0 &&
00369              weekdaynr != ( dt.dayOfYear() - 1 ) / 7 + 1 ) {
00370           return false;
00371         }
00372         if ( weekdaynr < 0 &&
00373              weekdaynr != -( ( dt.daysInYear() - dt.dayOfYear() ) / 7 + 1 ) ) {
00374           return false;
00375         }
00376       }
00377     }
00378   }
00379   if ( yearday > 0 && yearday != dt.dayOfYear() ) {
00380     return false;
00381   }
00382   if ( yearday < 0 && yearday != dt.daysInYear() - dt.dayOfYear() + 1 ) {
00383     return false;
00384   }
00385   return true;
00386 }
00387 
00388 /* Check for a match with the specified date/time.
00389  * The date/time's time specification must correspond with that of the start date/time.
00390  */
00391 bool Constraint::matches( const KDateTime &dt, RecurrenceRule::PeriodType type ) const
00392 {
00393   if ( ( hour >= 0 && ( hour != dt.time().hour() ||
00394                         secondOccurrence != dt.isSecondOccurrence() ) ) ||
00395        ( minute >= 0 && minute != dt.time().minute() ) ||
00396        ( second >= 0 && second != dt.time().second() ) ||
00397        !matches( dt.date(), type ) ) {
00398     return false;
00399   }
00400   return true;
00401 }
00402 
00403 bool Constraint::isConsistent( RecurrenceRule::PeriodType /*period*/) const
00404 {
00405   // TODO: Check for consistency, e.g. byyearday=3 and bymonth=10
00406   return true;
00407 }
00408 
00409 // Return a date/time set to the constraint values, but with those parts less
00410 // significant than the given period type set to 1 (for dates) or 0 (for times).
00411 KDateTime Constraint::intervalDateTime( RecurrenceRule::PeriodType type ) const
00412 {
00413   if ( useCachedDt ) {
00414     return cachedDt;
00415   }
00416   QDate d;
00417   QTime t( 0, 0, 0 );
00418   bool subdaily = true;
00419   switch ( type ) {
00420     case RecurrenceRule::rSecondly:
00421       t.setHMS( hour, minute, second );
00422       break;
00423     case RecurrenceRule::rMinutely:
00424       t.setHMS( hour, minute, 0 );
00425       break;
00426     case RecurrenceRule::rHourly:
00427       t.setHMS( hour, 0, 0 );
00428       break;
00429     case RecurrenceRule::rDaily:
00430       break;
00431     case RecurrenceRule::rWeekly:
00432       d = DateHelper::getNthWeek( year, weeknumber, weekstart );
00433       subdaily = false;
00434       break;
00435     case RecurrenceRule::rMonthly:
00436       d.setYMD( year, month, 1 );
00437       subdaily = false;
00438       break;
00439     case RecurrenceRule::rYearly:
00440       d.setYMD( year, 1, 1 );
00441       subdaily = false;
00442       break;
00443     default:
00444       break;
00445   }
00446   if ( subdaily ) {
00447     d = DateHelper::getDate( year, (month>0)?month:1, day?day:1 );
00448   }
00449   cachedDt = KDateTime( d, t, timespec );
00450   if ( secondOccurrence ) {
00451     cachedDt.setSecondOccurrence( true );
00452   }
00453   useCachedDt = true;
00454   return cachedDt;
00455 }
00456 
00457 bool Constraint::merge( const Constraint &interval )
00458 {
00459 #define mergeConstraint( name, cmparison ) \
00460   if ( interval.name cmparison ) { \
00461     if ( !( name cmparison ) ) { \
00462       name = interval.name; \
00463     } else if ( name != interval.name ) { \
00464       return false;\
00465     } \
00466   }
00467 
00468   useCachedDt = false;
00469 
00470   mergeConstraint( year, > 0 );
00471   mergeConstraint( month, > 0 );
00472   mergeConstraint( day, != 0 );
00473   mergeConstraint( hour, >= 0 );
00474   mergeConstraint( minute, >= 0 );
00475   mergeConstraint( second, >= 0 );
00476 
00477   mergeConstraint( weekday, != 0 );
00478   mergeConstraint( weekdaynr, != 0 );
00479   mergeConstraint( weeknumber, != 0 );
00480   mergeConstraint( yearday, != 0 );
00481 
00482 #undef mergeConstraint
00483   return true;
00484 }
00485 
00486 //           Y  M  D | H  Mn S | WD #WD | WN | YD
00487 // required:
00488 //           x       | x  x  x |        |    |
00489 // 0) Trivial: Exact date given, maybe other restrictions
00490 //           x  x  x | x  x  x |        |    |
00491 // 1) Easy case: no weekly restrictions -> at most a loop through possible dates
00492 //           x  +  + | x  x  x |  -  -  |  - |  -
00493 // 2) Year day is given -> date known
00494 //           x       | x  x  x |        |    |  +
00495 // 3) week number is given -> loop through all days of that week. Further
00496 //    restrictions will be applied in the end, when we check all dates for
00497 //    consistency with the constraints
00498 //           x       | x  x  x |        |  + | (-)
00499 // 4) week day is specified ->
00500 //           x       | x  x  x |  x  ?  | (-)| (-)
00501 // 5) All possiblecases have already been treated, so this must be an error!
00502 
00503 QList<KDateTime> Constraint::dateTimes( RecurrenceRule::PeriodType type ) const
00504 {
00505   QList<KDateTime> result;
00506   bool done = false;
00507   if ( !isConsistent( type ) ) {
00508     return result;
00509   }
00510 
00511   // TODO_Recurrence: Handle all-day
00512   QTime tm( hour, minute, second );
00513 
00514   if ( !done && day && month > 0 ) {
00515     appendDateTime( DateHelper::getDate( year, month, day ), tm, result );
00516     done = true;
00517   }
00518 
00519   if ( !done && weekday == 0 && weeknumber == 0 && yearday == 0 ) {
00520     // Easy case: date is given, not restrictions by week or yearday
00521     uint mstart = ( month > 0 ) ? month : 1;
00522     uint mend = ( month <= 0 ) ? 12 : month;
00523     for ( uint m = mstart; m <= mend; ++m ) {
00524       uint dstart, dend;
00525       if ( day > 0 ) {
00526         dstart = dend = day;
00527       } else if ( day < 0 ) {
00528         QDate date( year, month, 1 );
00529         dstart = dend = date.daysInMonth() + day + 1;
00530       } else {
00531         QDate date( year, month, 1 );
00532         dstart = 1;
00533         dend = date.daysInMonth();
00534       }
00535       uint d = dstart;
00536       for ( QDate dt( year, m, dstart ); ; dt = dt.addDays( 1 ) ) {
00537         appendDateTime( dt, tm, result );
00538         if ( ++d > dend ) {
00539           break;
00540         }
00541       }
00542     }
00543     done = true;
00544   }
00545 
00546   // Else: At least one of the week / yearday restrictions was given...
00547   // If we have a yearday (and of course a year), we know the exact date
00548   if ( !done && yearday != 0 ) {
00549     // yearday < 0 means from end of year, so we'll need Jan 1 of the next year
00550     QDate d( year + ( ( yearday > 0 ) ? 0 : 1 ), 1, 1 );
00551     d = d.addDays( yearday - ( ( yearday > 0 ) ? 1 : 0 ) );
00552     appendDateTime( d, tm, result );
00553     done = true;
00554   }
00555 
00556   // Else: If we have a weeknumber, we have at most 7 possible dates, loop through them
00557   if ( !done && weeknumber != 0 ) {
00558     QDate wst( DateHelper::getNthWeek( year, weeknumber, weekstart ) );
00559     if ( weekday != 0 ) {
00560       wst = wst.addDays( ( 7 + weekday - weekstart ) % 7 );
00561       appendDateTime( wst, tm, result );
00562     } else {
00563       for ( int i = 0; i < 7; ++i ) {
00564         appendDateTime( wst, tm, result );
00565         wst = wst.addDays( 1 );
00566       }
00567     }
00568     done = true;
00569   }
00570 
00571   // weekday is given
00572   if ( !done && weekday != 0 ) {
00573     QDate dt( year, 1, 1 );
00574     // If type == yearly and month is given, pos is still in month not year!
00575     // TODO_Recurrence: Correct handling of n-th  BYDAY...
00576     int maxloop = 53;
00577     bool inMonth = ( type == RecurrenceRule::rMonthly ) ||
00578                    ( type == RecurrenceRule::rYearly && month > 0 );
00579     if ( inMonth && month > 0 ) {
00580       dt = QDate( year, month, 1 );
00581       maxloop = 5;
00582     }
00583     if ( weekdaynr < 0 ) {
00584       // From end of period (month, year) => relative to begin of next period
00585       if ( inMonth ) {
00586         dt = dt.addMonths( 1 );
00587       } else {
00588         dt = dt.addYears( 1 );
00589       }
00590     }
00591     int adj = ( 7 + weekday - dt.dayOfWeek() ) % 7;
00592     dt = dt.addDays( adj ); // correct first weekday of the period
00593 
00594     if ( weekdaynr > 0 ) {
00595       dt = dt.addDays( ( weekdaynr - 1 ) * 7 );
00596       appendDateTime( dt, tm, result );
00597     } else if ( weekdaynr < 0 ) {
00598       dt = dt.addDays( weekdaynr * 7 );
00599       appendDateTime( dt, tm, result );
00600     } else {
00601       // loop through all possible weeks, non-matching will be filtered later
00602       for ( int i = 0; i < maxloop; ++i ) {
00603         appendDateTime( dt, tm, result );
00604         dt = dt.addDays( 7 );
00605       }
00606     }
00607   } // weekday != 0
00608 
00609   // Only use those times that really match all other constraints, too
00610   QList<KDateTime> valid;
00611   for ( int i = 0, iend = result.count();  i < iend;  ++i ) {
00612     if ( matches( result[i], type ) ) {
00613       valid.append( result[i] );
00614     }
00615   }
00616   // Don't sort it here, would be unnecessary work. The results from all
00617   // constraints will be merged to one big list of the interval. Sort that one!
00618   return valid;
00619 }
00620 
00621 void Constraint::appendDateTime( const QDate &date, const QTime &time,
00622                                  QList<KDateTime> &list ) const
00623 {
00624   KDateTime dt( date, time, timespec );
00625   if ( dt.isValid() ) {
00626     if ( secondOccurrence ) {
00627       dt.setSecondOccurrence( true );
00628     }
00629     list.append( dt );
00630   }
00631 }
00632 
00633 bool Constraint::increase( RecurrenceRule::PeriodType type, int freq )
00634 {
00635   // convert the first day of the interval to KDateTime
00636   intervalDateTime( type );
00637 
00638   // Now add the intervals
00639   switch ( type ) {
00640     case RecurrenceRule::rSecondly:
00641       cachedDt = cachedDt.addSecs( freq );
00642       break;
00643     case RecurrenceRule::rMinutely:
00644       cachedDt = cachedDt.addSecs( 60 * freq );
00645       break;
00646     case RecurrenceRule::rHourly:
00647       cachedDt = cachedDt.addSecs( 3600 * freq );
00648       break;
00649     case RecurrenceRule::rDaily:
00650       cachedDt = cachedDt.addDays( freq );
00651       break;
00652     case RecurrenceRule::rWeekly:
00653       cachedDt = cachedDt.addDays( 7 * freq );
00654       break;
00655     case RecurrenceRule::rMonthly:
00656       cachedDt = cachedDt.addMonths( freq );
00657       break;
00658     case RecurrenceRule::rYearly:
00659       cachedDt = cachedDt.addYears( freq );
00660       break;
00661     default:
00662       break;
00663   }
00664   // Convert back from KDateTime to the Constraint class
00665   readDateTime( cachedDt, type );
00666   useCachedDt = true;   // readDateTime() resets this
00667 
00668   return true;
00669 }
00670 
00671 // Set the constraint's value appropriate to 'type', to the value contained in a date/time.
00672 bool Constraint::readDateTime( const KDateTime &dt, RecurrenceRule::PeriodType type )
00673 {
00674   switch ( type ) {
00675     // Really fall through! Only weekly needs to be treated differently!
00676   case RecurrenceRule::rSecondly:
00677     second = dt.time().second();
00678   case RecurrenceRule::rMinutely:
00679     minute = dt.time().minute();
00680   case RecurrenceRule::rHourly:
00681     hour = dt.time().hour();
00682     secondOccurrence = dt.isSecondOccurrence();
00683   case RecurrenceRule::rDaily:
00684     day = dt.date().day();
00685   case RecurrenceRule::rMonthly:
00686     month = dt.date().month();
00687   case RecurrenceRule::rYearly:
00688     year = dt.date().year();
00689     break;
00690   case RecurrenceRule::rWeekly:
00691     // Determine start day of the current week, calculate the week number from that
00692     weeknumber = DateHelper::getWeekNumber( dt.date(), weekstart, &year );
00693     break;
00694   default:
00695     break;
00696   }
00697   useCachedDt = false;
00698   return true;
00699 }
00700 //@endcond
00701 
00702 /**************************************************************************
00703  *                        RecurrenceRule::Private                         *
00704  **************************************************************************/
00705 
00706 //@cond PRIVATE
00707 class KCalCore::RecurrenceRule::Private
00708 {
00709   public:
00710     Private( RecurrenceRule *parent )
00711       : mParent( parent ),
00712         mPeriod( rNone ),
00713         mFrequency( 0 ),
00714         mDuration( -1 ),
00715         mWeekStart( 1 ),
00716         mIsReadOnly( false ),
00717         mAllDay( false )
00718     {
00719         setDirty();
00720     }
00721 
00722     Private( RecurrenceRule *parent, const Private &p );
00723 
00724     Private &operator=( const Private &other );
00725     bool operator==( const Private &other ) const;
00726     void clear();
00727     void setDirty();
00728     void buildConstraints();
00729     bool buildCache() const;
00730     Constraint getNextValidDateInterval( const KDateTime &preDate, PeriodType type ) const;
00731     Constraint getPreviousValidDateInterval( const KDateTime &afterDate, PeriodType type ) const;
00732     DateTimeList datesForInterval( const Constraint &interval, PeriodType type ) const;
00733 
00734     RecurrenceRule *mParent;
00735     QString mRRule;            // RRULE string
00736     PeriodType mPeriod;
00737     KDateTime mDateStart;      // start of recurrence (but mDateStart is not an occurrence
00738                                // unless it matches the rule)
00739     uint mFrequency;
00744     int mDuration;
00745     KDateTime mDateEnd;
00746 
00747     QList<int> mBySeconds;     // values: second 0-59
00748     QList<int> mByMinutes;     // values: minute 0-59
00749     QList<int> mByHours;       // values: hour 0-23
00750 
00751     QList<WDayPos> mByDays;   // n-th weekday of the month or year
00752     QList<int> mByMonthDays;   // values: day -31 to -1 and 1-31
00753     QList<int> mByYearDays;    // values: day -366 to -1 and 1-366
00754     QList<int> mByWeekNumbers; // values: week -53 to -1 and 1-53
00755     QList<int> mByMonths;      // values: month 1-12
00756     QList<int> mBySetPos;      // values: position -366 to -1 and 1-366
00757     short mWeekStart;               // first day of the week (1=Monday, 7=Sunday)
00758 
00759     Constraint::List mConstraints;
00760     QList<RuleObserver*> mObservers;
00761 
00762     // Cache for duration
00763     mutable DateTimeList mCachedDates;
00764     mutable KDateTime mCachedDateEnd;
00765     mutable KDateTime mCachedLastDate;   // when mCachedDateEnd invalid, last date checked
00766     mutable bool mCached;
00767 
00768     bool mIsReadOnly;
00769     bool mAllDay;
00770     bool mNoByRules;        // no BySeconds, ByMinutes, ... rules exist
00771     uint mTimedRepetition;  // repeats at a regular number of seconds interval, or 0
00772 };
00773 
00774 RecurrenceRule::Private::Private( RecurrenceRule *parent, const Private &p )
00775   : mParent( parent ),
00776     mRRule( p.mRRule ),
00777     mPeriod( p.mPeriod ),
00778     mDateStart( p.mDateStart ),
00779     mFrequency( p.mFrequency ),
00780     mDuration( p.mDuration ),
00781     mDateEnd( p.mDateEnd ),
00782 
00783     mBySeconds( p.mBySeconds ),
00784     mByMinutes( p.mByMinutes ),
00785     mByHours( p.mByHours ),
00786     mByDays( p.mByDays ),
00787     mByMonthDays( p.mByMonthDays ),
00788     mByYearDays( p.mByYearDays ),
00789     mByWeekNumbers( p.mByWeekNumbers ),
00790     mByMonths( p.mByMonths ),
00791     mBySetPos( p.mBySetPos ),
00792     mWeekStart( p.mWeekStart ),
00793 
00794     mIsReadOnly( p.mIsReadOnly ),
00795     mAllDay( p.mAllDay ),
00796     mNoByRules( p.mNoByRules )
00797 {
00798     setDirty();
00799 }
00800 
00801 RecurrenceRule::Private &RecurrenceRule::Private::operator=( const Private &p )
00802 {
00803   // check for self assignment
00804   if ( &p == this ) {
00805     return *this;
00806   }
00807 
00808   mRRule = p.mRRule;
00809   mPeriod = p.mPeriod;
00810   mDateStart = p.mDateStart;
00811   mFrequency = p.mFrequency;
00812   mDuration = p.mDuration;
00813   mDateEnd = p.mDateEnd;
00814 
00815   mBySeconds = p.mBySeconds;
00816   mByMinutes = p.mByMinutes;
00817   mByHours = p.mByHours;
00818   mByDays = p.mByDays;
00819   mByMonthDays = p.mByMonthDays;
00820   mByYearDays = p.mByYearDays;
00821   mByWeekNumbers = p.mByWeekNumbers;
00822   mByMonths = p.mByMonths;
00823   mBySetPos = p.mBySetPos;
00824   mWeekStart = p.mWeekStart;
00825 
00826   mIsReadOnly = p.mIsReadOnly;
00827   mAllDay = p.mAllDay;
00828   mNoByRules = p.mNoByRules;
00829 
00830   setDirty();
00831 
00832   return *this;
00833 }
00834 
00835 bool RecurrenceRule::Private::operator==( const Private &r ) const
00836 {
00837   return
00838     mPeriod == r.mPeriod &&
00839     ( ( mDateStart == r.mDateStart ) ||
00840       ( !mDateStart.isValid() && !r.mDateStart.isValid() ) ) &&
00841     mDuration == r.mDuration &&
00842     ( ( mDateEnd == r.mDateEnd ) ||
00843       ( !mDateEnd.isValid() && !r.mDateEnd.isValid() ) ) &&
00844     mFrequency == r.mFrequency &&
00845     mIsReadOnly == r.mIsReadOnly &&
00846     mAllDay == r.mAllDay &&
00847     mBySeconds == r.mBySeconds &&
00848     mByMinutes == r.mByMinutes &&
00849     mByHours == r.mByHours &&
00850     mByDays == r.mByDays &&
00851     mByMonthDays == r.mByMonthDays &&
00852     mByYearDays == r.mByYearDays &&
00853     mByWeekNumbers == r.mByWeekNumbers &&
00854     mByMonths == r.mByMonths &&
00855     mBySetPos == r.mBySetPos &&
00856     mWeekStart == r.mWeekStart &&
00857     mNoByRules == r.mNoByRules;
00858 }
00859 
00860 void RecurrenceRule::Private::clear()
00861 {
00862   if ( mIsReadOnly ) {
00863     return;
00864   }
00865   mPeriod = rNone;
00866   mBySeconds.clear();
00867   mByMinutes.clear();
00868   mByHours.clear();
00869   mByDays.clear();
00870   mByMonthDays.clear();
00871   mByYearDays.clear();
00872   mByWeekNumbers.clear();
00873   mByMonths.clear();
00874   mBySetPos.clear();
00875   mWeekStart = 1;
00876   mNoByRules = false;
00877 
00878   setDirty();
00879 }
00880 
00881 void RecurrenceRule::Private::setDirty()
00882 {
00883   buildConstraints();
00884   mCached = false;
00885   mCachedDates.clear();
00886   for ( int i = 0, iend = mObservers.count();  i < iend;  ++i ) {
00887     if ( mObservers[i] ) {
00888       mObservers[i]->recurrenceChanged( mParent );
00889     }
00890   }
00891 }
00892 //@endcond
00893 
00894 /**************************************************************************
00895  *                              RecurrenceRule                            *
00896  **************************************************************************/
00897 
00898 RecurrenceRule::RecurrenceRule()
00899   : d( new Private( this ) )
00900 {
00901 }
00902 
00903 RecurrenceRule::RecurrenceRule( const RecurrenceRule &r )
00904   : d( new Private( this, *r.d ) )
00905 {
00906 }
00907 
00908 RecurrenceRule::~RecurrenceRule()
00909 {
00910   delete d;
00911 }
00912 
00913 bool RecurrenceRule::operator==( const RecurrenceRule &r ) const
00914 {
00915   return *d == *r.d;
00916 }
00917 
00918 RecurrenceRule &RecurrenceRule::operator=( const RecurrenceRule &r )
00919 {
00920   // check for self assignment
00921   if ( &r == this ) {
00922     return *this;
00923   }
00924 
00925   *d = *r.d;
00926 
00927   return *this;
00928 }
00929 
00930 void RecurrenceRule::addObserver( RuleObserver *observer )
00931 {
00932   if ( !d->mObservers.contains( observer ) ) {
00933     d->mObservers.append( observer );
00934   }
00935 }
00936 
00937 void RecurrenceRule::removeObserver( RuleObserver *observer )
00938 {
00939   if ( d->mObservers.contains( observer ) ) {
00940     d->mObservers.removeAll( observer );
00941   }
00942 }
00943 
00944 void RecurrenceRule::setRecurrenceType( PeriodType period )
00945 {
00946   if ( isReadOnly() ) {
00947     return;
00948   }
00949   d->mPeriod = period;
00950   d->setDirty();
00951 }
00952 
00953 KDateTime RecurrenceRule::endDt( bool *result ) const
00954 {
00955   if ( result ) {
00956     *result = false;
00957   }
00958   if ( d->mPeriod == rNone ) {
00959     return KDateTime();
00960   }
00961   if ( d->mDuration < 0 ) {
00962     return KDateTime();
00963   }
00964   if ( d->mDuration == 0 ) {
00965     if ( result ) {
00966       *result = true;
00967     }
00968     return d->mDateEnd;
00969   }
00970 
00971   // N occurrences. Check if we have a full cache. If so, return the cached end date.
00972   if ( !d->mCached ) {
00973     // If not enough occurrences can be found (i.e. inconsistent constraints)
00974     if ( !d->buildCache() ) {
00975       return KDateTime();
00976     }
00977   }
00978   if ( result ) {
00979     *result = true;
00980   }
00981   return d->mCachedDateEnd;
00982 }
00983 
00984 void RecurrenceRule::setEndDt( const KDateTime &dateTime )
00985 {
00986   if ( isReadOnly() ) {
00987     return;
00988   }
00989   d->mDateEnd = dateTime;
00990   d->mDuration = 0; // set to 0 because there is an end date/time
00991   d->setDirty();
00992 }
00993 
00994 void RecurrenceRule::setDuration( int duration )
00995 {
00996   if ( isReadOnly() ) {
00997     return;
00998   }
00999   d->mDuration = duration;
01000   d->setDirty();
01001 }
01002 
01003 void RecurrenceRule::setAllDay( bool allDay )
01004 {
01005   if ( isReadOnly() ) {
01006     return;
01007   }
01008   d->mAllDay = allDay;
01009   d->setDirty();
01010 }
01011 
01012 void RecurrenceRule::clear()
01013 {
01014   d->clear();
01015 }
01016 
01017 void RecurrenceRule::setDirty()
01018 {
01019   d->setDirty();
01020 }
01021 
01022 void RecurrenceRule::setStartDt( const KDateTime &start )
01023 {
01024   if ( isReadOnly() ) {
01025     return;
01026   }
01027   d->mDateStart = start;
01028   d->setDirty();
01029 }
01030 
01031 void RecurrenceRule::setFrequency( int freq )
01032 {
01033   if ( isReadOnly() || freq <= 0 ) {
01034     return;
01035   }
01036   d->mFrequency = freq;
01037   d->setDirty();
01038 }
01039 
01040 void RecurrenceRule::setBySeconds( const QList<int> &bySeconds )
01041 {
01042   if ( isReadOnly() ) {
01043     return;
01044   }
01045   d->mBySeconds = bySeconds;
01046   d->setDirty();
01047 }
01048 
01049 void RecurrenceRule::setByMinutes( const QList<int> &byMinutes )
01050 {
01051   if ( isReadOnly() ) {
01052     return;
01053   }
01054   d->mByMinutes = byMinutes;
01055   d->setDirty();
01056 }
01057 
01058 void RecurrenceRule::setByHours( const QList<int> &byHours )
01059 {
01060   if ( isReadOnly() ) {
01061     return;
01062   }
01063   d->mByHours = byHours;
01064   d->setDirty();
01065 }
01066 
01067 void RecurrenceRule::setByDays( const QList<WDayPos> &byDays )
01068 {
01069   if ( isReadOnly() ) {
01070     return;
01071   }
01072   d->mByDays = byDays;
01073   d->setDirty();
01074 }
01075 
01076 void RecurrenceRule::setByMonthDays( const QList<int> &byMonthDays )
01077 {
01078   if ( isReadOnly() ) {
01079     return;
01080   }
01081   d->mByMonthDays = byMonthDays;
01082   d->setDirty();
01083 }
01084 
01085 void RecurrenceRule::setByYearDays( const QList<int> &byYearDays )
01086 {
01087   if ( isReadOnly() ) {
01088     return;
01089   }
01090   d->mByYearDays = byYearDays;
01091   d->setDirty();
01092 }
01093 
01094 void RecurrenceRule::setByWeekNumbers( const QList<int> &byWeekNumbers )
01095 {
01096   if ( isReadOnly() ) {
01097     return;
01098   }
01099   d->mByWeekNumbers = byWeekNumbers;
01100   d->setDirty();
01101 }
01102 
01103 void RecurrenceRule::setByMonths( const QList<int> &byMonths )
01104 {
01105   if ( isReadOnly() ) {
01106     return;
01107   }
01108   d->mByMonths = byMonths;
01109   d->setDirty();
01110 }
01111 
01112 void RecurrenceRule::setBySetPos( const QList<int> &bySetPos )
01113 {
01114   if ( isReadOnly() ) {
01115     return;
01116   }
01117   d->mBySetPos = bySetPos;
01118   d->setDirty();
01119 }
01120 
01121 void RecurrenceRule::setWeekStart( short weekStart )
01122 {
01123   if ( isReadOnly() ) {
01124     return;
01125   }
01126   d->mWeekStart = weekStart;
01127   d->setDirty();
01128 }
01129 
01130 void RecurrenceRule::shiftTimes( const KDateTime::Spec &oldSpec, const KDateTime::Spec &newSpec )
01131 {
01132   d->mDateStart = d->mDateStart.toTimeSpec( oldSpec );
01133   d->mDateStart.setTimeSpec( newSpec );
01134   if ( d->mDuration == 0 ) {
01135     d->mDateEnd = d->mDateEnd.toTimeSpec( oldSpec );
01136     d->mDateEnd.setTimeSpec( newSpec );
01137   }
01138   d->setDirty();
01139 }
01140 
01141 // Taken from recurrence.cpp
01142 // int RecurrenceRule::maxIterations() const
01143 // {
01144 //   /* Find the maximum number of iterations which may be needed to reach the
01145 //    * next actual occurrence of a monthly or yearly recurrence.
01146 //    * More than one iteration may be needed if, for example, it's the 29th February,
01147 //    * the 31st day of the month or the 5th Monday, and the month being checked is
01148 //    * February or a 30-day month.
01149 //    * The following recurrences may never occur:
01150 //    * - For rMonthlyDay: if the frequency is a whole number of years.
01151 //    * - For rMonthlyPos: if the frequency is an even whole number of years.
01152 //    * - For rYearlyDay, rYearlyMonth: if the frequeny is a multiple of 4 years.
01153 //    * - For rYearlyPos: if the frequency is an even number of years.
01154 //    * The maximum number of iterations needed, assuming that it does actually occur,
01155 //    * was found empirically.
01156 //    */
01157 //   switch (recurs) {
01158 //     case rMonthlyDay:
01159 //       return (rFreq % 12) ? 6 : 8;
01160 //
01161 //     case rMonthlyPos:
01162 //       if (rFreq % 12 == 0) {
01163 //         // Some of these frequencies may never occur
01164 //         return (rFreq % 84 == 0) ? 364         // frequency = multiple of 7 years
01165 //              : (rFreq % 48 == 0) ? 7           // frequency = multiple of 4 years
01166 //              : (rFreq % 24 == 0) ? 14 : 28;    // frequency = multiple of 2 or 1 year
01167 //       }
01168 //       // All other frequencies will occur sometime
01169 //       if (rFreq > 120)
01170 //         return 364;    // frequencies of > 10 years will hit the date limit first
01171 //       switch (rFreq) {
01172 //         case 23:   return 50;
01173 //         case 46:   return 38;
01174 //         case 56:   return 138;
01175 //         case 66:   return 36;
01176 //         case 89:   return 54;
01177 //         case 112:  return 253;
01178 //         default:   return 25;       // most frequencies will need < 25 iterations
01179 //       }
01180 //
01181 //     case rYearlyMonth:
01182 //     case rYearlyDay:
01183 //       return 8;          // only 29th Feb or day 366 will need more than one iteration
01184 //
01185 //     case rYearlyPos:
01186 //       if (rFreq % 7 == 0)
01187 //         return 364;    // frequencies of a multiple of 7 years will hit the date limit first
01188 //       if (rFreq % 2 == 0) {
01189 //         // Some of these frequencies may never occur
01190 //         return (rFreq % 4 == 0) ? 7 : 14;    // frequency = even number of years
01191 //       }
01192 //       return 28;
01193 //   }
01194 //   return 1;
01195 // }
01196 
01197 //@cond PRIVATE
01198 void RecurrenceRule::Private::buildConstraints()
01199 {
01200   mTimedRepetition = 0;
01201   mNoByRules = mBySetPos.isEmpty();
01202   mConstraints.clear();
01203   Constraint con( mDateStart.timeSpec() );
01204   if ( mWeekStart > 0 ) {
01205     con.setWeekstart( mWeekStart );
01206   }
01207   mConstraints.append( con );
01208 
01209   int c, cend;
01210   int i, iend;
01211   Constraint::List tmp;
01212 
01213   #define intConstraint( list, setElement ) \
01214   if ( !list.isEmpty() ) { \
01215     mNoByRules = false; \
01216     iend = list.count(); \
01217     if ( iend == 1 ) { \
01218       for ( c = 0, cend = mConstraints.count();  c < cend;  ++c ) { \
01219         mConstraints[c].setElement( list[0] ); \
01220       } \
01221     } else { \
01222       for ( c = 0, cend = mConstraints.count();  c < cend;  ++c ) { \
01223         for ( i = 0;  i < iend;  ++i ) { \
01224           con = mConstraints[c]; \
01225           con.setElement( list[i] ); \
01226           tmp.append( con ); \
01227         } \
01228       } \
01229       mConstraints = tmp; \
01230       tmp.clear(); \
01231     } \
01232   }
01233 
01234   intConstraint( mBySeconds, setSecond );
01235   intConstraint( mByMinutes, setMinute );
01236   intConstraint( mByHours, setHour );
01237   intConstraint( mByMonthDays, setDay );
01238   intConstraint( mByMonths, setMonth );
01239   intConstraint( mByYearDays, setYearday );
01240   intConstraint( mByWeekNumbers, setWeeknumber );
01241   #undef intConstraint
01242 
01243   if ( !mByDays.isEmpty() ) {
01244     mNoByRules = false;
01245     for ( c = 0, cend = mConstraints.count();  c < cend;  ++c ) {
01246       for ( i = 0, iend = mByDays.count();  i < iend;  ++i ) {
01247         con = mConstraints[c];
01248         con.setWeekday( mByDays[i].day() );
01249         con.setWeekdaynr( mByDays[i].pos() );
01250         tmp.append( con );
01251       }
01252     }
01253     mConstraints = tmp;
01254     tmp.clear();
01255   }
01256 
01257   #define fixConstraint( setElement, value ) \
01258   { \
01259     for ( c = 0, cend = mConstraints.count();  c < cend;  ++c ) { \
01260       mConstraints[c].setElement( value );                        \
01261     } \
01262   }
01263   // Now determine missing values from DTSTART. This can speed up things,
01264   // because we have more restrictions and save some loops.
01265 
01266   // TODO: Does RFC 2445 intend to restrict the weekday in all cases of weekly?
01267   if ( mPeriod == rWeekly && mByDays.isEmpty() ) {
01268     fixConstraint( setWeekday, mDateStart.date().dayOfWeek() );
01269   }
01270 
01271   // Really fall through in the cases, because all smaller time intervals are
01272   // constrained from dtstart
01273   switch ( mPeriod ) {
01274   case rYearly:
01275     if ( mByDays.isEmpty() && mByWeekNumbers.isEmpty() &&
01276          mByYearDays.isEmpty() && mByMonths.isEmpty() ) {
01277       fixConstraint( setMonth, mDateStart.date().month() );
01278     }
01279   case rMonthly:
01280     if ( mByDays.isEmpty() && mByWeekNumbers.isEmpty() &&
01281          mByYearDays.isEmpty() && mByMonthDays.isEmpty() ) {
01282       fixConstraint( setDay, mDateStart.date().day() );
01283     }
01284   case rWeekly:
01285   case rDaily:
01286     if ( mByHours.isEmpty() ) {
01287       fixConstraint( setHour, mDateStart.time().hour() );
01288     }
01289   case rHourly:
01290     if ( mByMinutes.isEmpty() ) {
01291       fixConstraint( setMinute, mDateStart.time().minute() );
01292     }
01293   case rMinutely:
01294     if ( mBySeconds.isEmpty() ) {
01295       fixConstraint( setSecond, mDateStart.time().second() );
01296     }
01297   case rSecondly:
01298   default:
01299     break;
01300   }
01301   #undef fixConstraint
01302 
01303   if ( mNoByRules ) {
01304     switch ( mPeriod ) {
01305       case rHourly:
01306         mTimedRepetition = mFrequency * 3600;
01307         break;
01308       case rMinutely:
01309         mTimedRepetition = mFrequency * 60;
01310         break;
01311       case rSecondly:
01312         mTimedRepetition = mFrequency;
01313         break;
01314       default:
01315         break;
01316     }
01317   } else {
01318     for ( c = 0, cend = mConstraints.count(); c < cend; ) {
01319       if ( mConstraints[c].isConsistent( mPeriod ) ) {
01320         ++c;
01321       } else {
01322         mConstraints.removeAt( c );
01323         --cend;
01324       }
01325     }
01326   }
01327 }
01328 
01329 // Build and cache a list of all occurrences.
01330 // Only call buildCache() if mDuration > 0.
01331 bool RecurrenceRule::Private::buildCache() const
01332 {
01333   // Build the list of all occurrences of this event (we need that to determine
01334   // the end date!)
01335   Constraint interval( getNextValidDateInterval( mDateStart, mPeriod ) );
01336   QDateTime next;
01337 
01338   DateTimeList dts = datesForInterval( interval, mPeriod );
01339   // Only use dates after the event has started (start date is only included
01340   // if it matches)
01341   int i = dts.findLT( mDateStart );
01342   if ( i >= 0 ) {
01343     dts.erase( dts.begin(), dts.begin() + i + 1 );
01344   }
01345 
01346   int loopnr = 0;
01347   int dtnr = dts.count();
01348   // some validity checks to avoid infinite loops (i.e. if we have
01349   // done this loop already 10000 times, bail out )
01350   while ( loopnr < LOOP_LIMIT && dtnr < mDuration ) {
01351     interval.increase( mPeriod, mFrequency );
01352     // The returned date list is already sorted!
01353     dts += datesForInterval( interval, mPeriod );
01354     dtnr = dts.count();
01355     ++loopnr;
01356   }
01357   if ( dts.count() > mDuration ) {
01358     // we have picked up more occurrences than necessary, remove them
01359     dts.erase( dts.begin() + mDuration, dts.end() );
01360   }
01361   mCached = true;
01362   mCachedDates = dts;
01363 
01364 // it = dts.begin();
01365 // while ( it != dts.end() ) {
01366 //   kDebug() << "            -=>" << dumpTime(*it);
01367 //   ++it;
01368 // }
01369   if ( int( dts.count() ) == mDuration ) {
01370     mCachedDateEnd = dts.last();
01371     return true;
01372   } else {
01373     // The cached date list is incomplete
01374     mCachedDateEnd = KDateTime();
01375     mCachedLastDate = interval.intervalDateTime( mPeriod );
01376     return false;
01377   }
01378 }
01379 //@endcond
01380 
01381 bool RecurrenceRule::dateMatchesRules( const KDateTime &kdt ) const
01382 {
01383   KDateTime dt = kdt.toTimeSpec( d->mDateStart.timeSpec() );
01384   for ( int i = 0, iend = d->mConstraints.count();  i < iend;  ++i ) {
01385     if ( d->mConstraints[i].matches( dt, recurrenceType() ) ) {
01386       return true;
01387     }
01388   }
01389   return false;
01390 }
01391 
01392 bool RecurrenceRule::recursOn( const QDate &qd, const KDateTime::Spec &timeSpec ) const
01393 {
01394   int i, iend;
01395   if ( allDay() ) {
01396     // It's a date-only rule, so it has no time specification.
01397     // Therefore ignore 'timeSpec'.
01398     if ( qd < d->mDateStart.date() ) {
01399       return false;
01400     }
01401     // Start date is only included if it really matches
01402     QDate endDate;
01403     if ( d->mDuration >= 0 ) {
01404       endDate =  endDt().date();
01405       if ( qd > endDate ) {
01406         return false;
01407       }
01408     }
01409 
01410     // The date must be in an appropriate interval (getNextValidDateInterval),
01411     // Plus it must match at least one of the constraints
01412     bool match = false;
01413     for ( i = 0, iend = d->mConstraints.count();  i < iend && !match;  ++i ) {
01414       match = d->mConstraints[i].matches( qd, recurrenceType() );
01415     }
01416     if ( !match ) {
01417       return false;
01418     }
01419 
01420     KDateTime start( qd, QTime( 0, 0, 0 ), d->mDateStart.timeSpec() );
01421     Constraint interval( d->getNextValidDateInterval( start, recurrenceType() ) );
01422     // Constraint::matches is quite efficient, so first check if it can occur at
01423     // all before we calculate all actual dates.
01424     if ( !interval.matches( qd, recurrenceType() ) ) {
01425       return false;
01426     }
01427     // We really need to obtain the list of dates in this interval, since
01428     // otherwise BYSETPOS will not work (i.e. the date will match the interval,
01429     // but BYSETPOS selects only one of these matching dates!
01430     KDateTime end = start.addDays(1);
01431     do {
01432       DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
01433       for ( i = 0, iend = dts.count();  i < iend;  ++i ) {
01434         if ( dts[i].date() >= qd ) {
01435           return dts[i].date() == qd;
01436         }
01437       }
01438       interval.increase( recurrenceType(), frequency() );
01439     } while ( interval.intervalDateTime( recurrenceType() ) < end );
01440     return false;
01441   }
01442 
01443   // It's a date-time rule, so we need to take the time specification into account.
01444   KDateTime start( qd, QTime( 0, 0, 0 ), timeSpec );
01445   KDateTime end = start.addDays( 1 ).toTimeSpec( d->mDateStart.timeSpec() );
01446   start = start.toTimeSpec( d->mDateStart.timeSpec() );
01447   if ( end < d->mDateStart ) {
01448     return false;
01449   }
01450   if ( start < d->mDateStart ) {
01451     start = d->mDateStart;
01452   }
01453 
01454   // Start date is only included if it really matches
01455   if ( d->mDuration >= 0 ) {
01456     KDateTime endRecur = endDt();
01457     if ( endRecur.isValid() ) {
01458       if ( start > endRecur ) {
01459         return false;
01460       }
01461       if ( end > endRecur ) {
01462         end = endRecur;    // limit end-of-day time to end of recurrence rule
01463       }
01464     }
01465   }
01466 
01467   if ( d->mTimedRepetition ) {
01468     // It's a simple sub-daily recurrence with no constraints
01469     int n = static_cast<int>( ( d->mDateStart.secsTo_long( start ) - 1 ) % d->mTimedRepetition );
01470     return start.addSecs( d->mTimedRepetition - n ) < end;
01471   }
01472 
01473   // Find the start and end dates in the time spec for the rule
01474   QDate startDay = start.date();
01475   QDate endDay = end.addSecs( -1 ).date();
01476   int dayCount = startDay.daysTo( endDay ) + 1;
01477 
01478   // The date must be in an appropriate interval (getNextValidDateInterval),
01479   // Plus it must match at least one of the constraints
01480   bool match = false;
01481   for ( i = 0, iend = d->mConstraints.count();  i < iend && !match;  ++i ) {
01482     match = d->mConstraints[i].matches( startDay, recurrenceType() );
01483     for ( int day = 1;  day < dayCount && !match;  ++day ) {
01484       match = d->mConstraints[i].matches( startDay.addDays( day ), recurrenceType() );
01485     }
01486   }
01487   if ( !match ) {
01488     return false;
01489   }
01490 
01491   Constraint interval( d->getNextValidDateInterval( start, recurrenceType() ) );
01492   // Constraint::matches is quite efficient, so first check if it can occur at
01493   // all before we calculate all actual dates.
01494   match = false;
01495   Constraint intervalm = interval;
01496   do {
01497     match = intervalm.matches( startDay, recurrenceType() );
01498     for ( int day = 1;  day < dayCount && !match;  ++day ) {
01499       match = intervalm.matches( startDay.addDays( day ), recurrenceType() );
01500     }
01501     if ( match ) {
01502       break;
01503     }
01504     intervalm.increase( recurrenceType(), frequency() );
01505   } while ( intervalm.intervalDateTime( recurrenceType() ) < end );
01506   if ( !match ) {
01507     return false;
01508   }
01509 
01510   // We really need to obtain the list of dates in this interval, since
01511   // otherwise BYSETPOS will not work (i.e. the date will match the interval,
01512   // but BYSETPOS selects only one of these matching dates!
01513   do {
01514     DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
01515     int i = dts.findGE( start );
01516     if ( i >= 0 ) {
01517       return dts[i] <= end;
01518     }
01519     interval.increase( recurrenceType(), frequency() );
01520   } while ( interval.intervalDateTime( recurrenceType() ) < end );
01521 
01522   return false;
01523 }
01524 
01525 bool RecurrenceRule::recursAt( const KDateTime &kdt ) const
01526 {
01527   // Convert to the time spec used by this recurrence rule
01528   KDateTime dt( kdt.toTimeSpec( d->mDateStart.timeSpec() ) );
01529 
01530   if ( allDay() ) {
01531     return recursOn( dt.date(), dt.timeSpec() );
01532   }
01533   if ( dt < d->mDateStart ) {
01534     return false;
01535   }
01536   // Start date is only included if it really matches
01537   if ( d->mDuration >= 0 && dt > endDt() ) {
01538     return false;
01539   }
01540 
01541   if ( d->mTimedRepetition ) {
01542     // It's a simple sub-daily recurrence with no constraints
01543     return !( d->mDateStart.secsTo_long( dt ) % d->mTimedRepetition );
01544   }
01545 
01546   // The date must be in an appropriate interval (getNextValidDateInterval),
01547   // Plus it must match at least one of the constraints
01548   if ( !dateMatchesRules( dt ) ) {
01549     return false;
01550   }
01551   // if it recurs every interval, speed things up...
01552 //   if ( d->mFrequency == 1 && d->mBySetPos.isEmpty() && d->mByDays.isEmpty() ) return true;
01553   Constraint interval( d->getNextValidDateInterval( dt, recurrenceType() ) );
01554   // TODO_Recurrence: Does this work with BySetPos???
01555   if ( interval.matches( dt, recurrenceType() ) ) {
01556     return true;
01557   }
01558   return false;
01559 }
01560 
01561 TimeList RecurrenceRule::recurTimesOn( const QDate &date, const KDateTime::Spec &timeSpec ) const
01562 {
01563   TimeList lst;
01564   if ( allDay() ) {
01565     return lst;
01566   }
01567   KDateTime start( date, QTime( 0, 0, 0 ), timeSpec );
01568   KDateTime end = start.addDays( 1 ).addSecs( -1 );
01569   DateTimeList dts = timesInInterval( start, end );   // returns between start and end inclusive
01570   for ( int i = 0, iend = dts.count();  i < iend;  ++i ) {
01571     lst += dts[i].toTimeSpec( timeSpec ).time();
01572   }
01573   return lst;
01574 }
01575 
01577 int RecurrenceRule::durationTo( const KDateTime &dt ) const
01578 {
01579   // Convert to the time spec used by this recurrence rule
01580   KDateTime toDate( dt.toTimeSpec( d->mDateStart.timeSpec() ) );
01581   // Easy cases:
01582   // either before start, or after all recurrences and we know their number
01583   if ( toDate < d->mDateStart ) {
01584     return 0;
01585   }
01586   // Start date is only included if it really matches
01587   if ( d->mDuration > 0 && toDate >= endDt() ) {
01588     return d->mDuration;
01589   }
01590 
01591   if ( d->mTimedRepetition ) {
01592     // It's a simple sub-daily recurrence with no constraints
01593     return static_cast<int>( d->mDateStart.secsTo_long( toDate ) / d->mTimedRepetition );
01594   }
01595 
01596   return timesInInterval( d->mDateStart, toDate ).count();
01597 }
01598 
01599 int RecurrenceRule::durationTo( const QDate &date ) const
01600 {
01601   return durationTo( KDateTime( date, QTime( 23, 59, 59 ), d->mDateStart.timeSpec() ) );
01602 }
01603 
01604 KDateTime RecurrenceRule::getPreviousDate( const KDateTime &afterDate ) const
01605 {
01606   // Convert to the time spec used by this recurrence rule
01607   KDateTime toDate( afterDate.toTimeSpec( d->mDateStart.timeSpec() ) );
01608 
01609   // Invalid starting point, or beyond end of recurrence
01610   if ( !toDate.isValid() || toDate < d->mDateStart ) {
01611     return KDateTime();
01612   }
01613 
01614   if ( d->mTimedRepetition ) {
01615     // It's a simple sub-daily recurrence with no constraints
01616     KDateTime prev = toDate;
01617     if ( d->mDuration >= 0 && endDt().isValid() && toDate > endDt() ) {
01618       prev = endDt().addSecs( 1 ).toTimeSpec( d->mDateStart.timeSpec() );
01619     }
01620     int n = static_cast<int>( ( d->mDateStart.secsTo_long( prev ) - 1 ) % d->mTimedRepetition );
01621     if ( n < 0 ) {
01622       return KDateTime();  // before recurrence start
01623     }
01624     prev = prev.addSecs( -n - 1 );
01625     return prev >= d->mDateStart ? prev : KDateTime();
01626   }
01627 
01628   // If we have a cache (duration given), use that
01629   if ( d->mDuration > 0 ) {
01630     if ( !d->mCached ) {
01631       d->buildCache();
01632     }
01633     int i = d->mCachedDates.findLT( toDate );
01634     if ( i >= 0 ) {
01635       return d->mCachedDates[i];
01636     }
01637     return KDateTime();
01638   }
01639 
01640   KDateTime prev = toDate;
01641   if ( d->mDuration >= 0 && endDt().isValid() && toDate > endDt() ) {
01642     prev = endDt().addSecs( 1 ).toTimeSpec( d->mDateStart.timeSpec() );
01643   }
01644 
01645   Constraint interval( d->getPreviousValidDateInterval( prev, recurrenceType() ) );
01646   DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
01647   int i = dts.findLT( prev );
01648   if ( i >= 0 ) {
01649     return ( dts[i] >= d->mDateStart ) ? dts[i] : KDateTime();
01650   }
01651 
01652   // Previous interval. As soon as we find an occurrence, we're done.
01653   while ( interval.intervalDateTime( recurrenceType() ) > d->mDateStart ) {
01654     interval.increase( recurrenceType(), -int( frequency() ) );
01655     // The returned date list is sorted
01656     DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
01657     // The list is sorted, so take the last one.
01658     if ( !dts.isEmpty() ) {
01659       prev = dts.last();
01660       if ( prev.isValid() && prev >= d->mDateStart ) {
01661         return prev;
01662       } else {
01663         return KDateTime();
01664       }
01665     }
01666   }
01667   return KDateTime();
01668 }
01669 
01670 KDateTime RecurrenceRule::getNextDate( const KDateTime &preDate ) const
01671 {
01672   // Convert to the time spec used by this recurrence rule
01673   KDateTime fromDate( preDate.toTimeSpec( d->mDateStart.timeSpec() ) );
01674   // Beyond end of recurrence
01675   if ( d->mDuration >= 0 && endDt().isValid() && fromDate >= endDt() ) {
01676     return KDateTime();
01677   }
01678 
01679   // Start date is only included if it really matches
01680   if ( fromDate < d->mDateStart ) {
01681     fromDate = d->mDateStart.addSecs( -1 );
01682   }
01683 
01684   if ( d->mTimedRepetition ) {
01685     // It's a simple sub-daily recurrence with no constraints
01686     int n = static_cast<int>( ( d->mDateStart.secsTo_long( fromDate ) + 1 ) % d->mTimedRepetition );
01687     KDateTime next = fromDate.addSecs( d->mTimedRepetition - n + 1 );
01688     return d->mDuration < 0 || !endDt().isValid() || next <= endDt() ? next : KDateTime();
01689   }
01690 
01691   if ( d->mDuration > 0 ) {
01692     if ( !d->mCached ) {
01693       d->buildCache();
01694     }
01695     int i = d->mCachedDates.findGT( fromDate );
01696     if ( i >= 0 ) {
01697       return d->mCachedDates[i];
01698     }
01699   }
01700 
01701   KDateTime end = endDt();
01702   Constraint interval( d->getNextValidDateInterval( fromDate, recurrenceType() ) );
01703   DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
01704   int i = dts.findGT( fromDate );
01705   if ( i >= 0 ) {
01706     return ( d->mDuration < 0 || dts[i] <= end ) ? dts[i] : KDateTime();
01707   }
01708   interval.increase( recurrenceType(), frequency() );
01709   if ( d->mDuration >= 0 && interval.intervalDateTime( recurrenceType() ) > end ) {
01710     return KDateTime();
01711   }
01712 
01713   // Increase the interval. The first occurrence that we find is the result (if
01714   // if's before the end date).
01715   // TODO: some validity checks to avoid infinite loops for contradictory constraints
01716   int loop = 0;
01717   do {
01718     DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
01719     if ( dts.count() > 0 ) {
01720       KDateTime ret( dts[0] );
01721       if ( d->mDuration >= 0 && ret > end ) {
01722         return KDateTime();
01723       } else {
01724         return ret;
01725       }
01726     }
01727     interval.increase( recurrenceType(), frequency() );
01728   } while ( ++loop < LOOP_LIMIT &&
01729             ( d->mDuration < 0 || interval.intervalDateTime( recurrenceType() ) < end ) );
01730   return KDateTime();
01731 }
01732 
01733 DateTimeList RecurrenceRule::timesInInterval( const KDateTime &dtStart,
01734                                               const KDateTime &dtEnd ) const
01735 {
01736   KDateTime start = dtStart.toTimeSpec( d->mDateStart.timeSpec() );
01737   KDateTime end = dtEnd.toTimeSpec( d->mDateStart.timeSpec() );
01738   DateTimeList result;
01739   if ( end < d->mDateStart ) {
01740     return result;    // before start of recurrence
01741   }
01742   KDateTime enddt = end;
01743   if ( d->mDuration >= 0 ) {
01744     KDateTime endRecur = endDt();
01745     if ( endRecur.isValid() ) {
01746       if ( start > endRecur ) {
01747         return result;    // beyond end of recurrence
01748       }
01749       if ( end > endRecur ) {
01750         enddt = endRecur;    // limit end time to end of recurrence rule
01751       }
01752     }
01753   }
01754 
01755   if ( d->mTimedRepetition ) {
01756     // It's a simple sub-daily recurrence with no constraints
01757     int n = static_cast<int>( ( d->mDateStart.secsTo_long( start ) - 1 ) % d->mTimedRepetition );
01758     KDateTime dt = start.addSecs( d->mTimedRepetition - n );
01759     if ( dt < enddt ) {
01760       n = static_cast<int>( ( dt.secsTo_long( enddt ) - 1 ) / d->mTimedRepetition ) + 1;
01761       // limit n by a sane value else we can "explode".
01762       n = qMin( n, LOOP_LIMIT );
01763       for ( int i = 0;  i < n;  dt = dt.addSecs( d->mTimedRepetition ), ++i ) {
01764         result += dt;
01765       }
01766     }
01767     return result;
01768   }
01769 
01770   KDateTime st = start;
01771   bool done = false;
01772   if ( d->mDuration > 0 ) {
01773     if ( !d->mCached ) {
01774       d->buildCache();
01775     }
01776     if ( d->mCachedDateEnd.isValid() && start > d->mCachedDateEnd ) {
01777       return result;    // beyond end of recurrence
01778     }
01779     int i = d->mCachedDates.findGE( start );
01780     if ( i >= 0 ) {
01781       int iend = d->mCachedDates.findGT( enddt, i );
01782       if ( iend < 0 ) {
01783         iend = d->mCachedDates.count();
01784       } else {
01785         done = true;
01786       }
01787       while ( i < iend ) {
01788         result += d->mCachedDates[i++];
01789       }
01790     }
01791     if ( d->mCachedDateEnd.isValid() ) {
01792       done = true;
01793     } else if ( !result.isEmpty() ) {
01794       result += KDateTime();    // indicate that the returned list is incomplete
01795       done = true;
01796     }
01797     if ( done ) {
01798       return result;
01799     }
01800     // We don't have any result yet, but we reached the end of the incomplete cache
01801     st = d->mCachedLastDate.addSecs( 1 );
01802   }
01803 
01804   Constraint interval( d->getNextValidDateInterval( st, recurrenceType() ) );
01805   int loop = 0;
01806   do {
01807     DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
01808     int i = 0;
01809     int iend = dts.count();
01810     if ( loop == 0 ) {
01811       i = dts.findGE( st );
01812       if ( i < 0 ) {
01813         i = iend;
01814       }
01815     }
01816     int j = dts.findGT( enddt, i );
01817     if ( j >= 0 ) {
01818       iend = j;
01819       loop = LOOP_LIMIT;
01820     }
01821     while ( i < iend ) {
01822       result += dts[i++];
01823     }
01824     // Increase the interval.
01825     interval.increase( recurrenceType(), frequency() );
01826   } while ( ++loop < LOOP_LIMIT &&
01827             interval.intervalDateTime( recurrenceType() ) < end );
01828   return result;
01829 }
01830 
01831 //@cond PRIVATE
01832 // Find the date/time of the occurrence at or before a date/time,
01833 // for a given period type.
01834 // Return a constraint whose value appropriate to 'type', is set to
01835 // the value contained in the date/time.
01836 Constraint RecurrenceRule::Private::getPreviousValidDateInterval( const KDateTime &dt,
01837                                                                   PeriodType type ) const
01838 {
01839   long periods = 0;
01840   KDateTime start = mDateStart;
01841   KDateTime nextValid( start );
01842   int modifier = 1;
01843   KDateTime toDate( dt.toTimeSpec( start.timeSpec() ) );
01844   // for super-daily recurrences, don't care about the time part
01845 
01846   // Find the #intervals since the dtstart and round to the next multiple of
01847   // the frequency
01848   switch ( type ) {
01849     // Really fall through for sub-daily, since the calculations only differ
01850     // by the factor 60 and 60*60! Same for weekly and daily (factor 7)
01851   case rHourly:
01852     modifier *= 60;
01853   case rMinutely:
01854     modifier *= 60;
01855   case rSecondly:
01856     periods = static_cast<int>( start.secsTo_long( toDate ) / modifier );
01857     // round it down to the next lower multiple of frequency:
01858     if ( mFrequency > 0 ) {
01859       periods = ( periods / mFrequency ) * mFrequency;
01860     }
01861     nextValid = start.addSecs( modifier * periods );
01862     break;
01863   case rWeekly:
01864     toDate = toDate.addDays( -( 7 + toDate.date().dayOfWeek() - mWeekStart ) % 7 );
01865     start = start.addDays( -( 7 + start.date().dayOfWeek() - mWeekStart ) % 7 );
01866     modifier *= 7;
01867   case rDaily:
01868     periods = start.daysTo( toDate ) / modifier;
01869     // round it down to the next lower multiple of frequency:
01870     if ( mFrequency > 0 ) {
01871       periods = ( periods / mFrequency ) * mFrequency;
01872     }
01873     nextValid = start.addDays( modifier * periods );
01874     break;
01875   case rMonthly:
01876   {
01877     periods = 12 * ( toDate.date().year() - start.date().year() ) +
01878               ( toDate.date().month() - start.date().month() );
01879     // round it down to the next lower multiple of frequency:
01880     if ( mFrequency > 0 ) {
01881       periods = ( periods / mFrequency ) * mFrequency;
01882     }
01883     // set the day to the first day of the month, so we don't have problems
01884     // with non-existent days like Feb 30 or April 31
01885     start.setDate( QDate( start.date().year(), start.date().month(), 1 ) );
01886     nextValid.setDate( start.date().addMonths( periods ) );
01887     break; }
01888   case rYearly:
01889     periods = ( toDate.date().year() - start.date().year() );
01890     // round it down to the next lower multiple of frequency:
01891     if ( mFrequency > 0 ) {
01892       periods = ( periods / mFrequency ) * mFrequency;
01893     }
01894     nextValid.setDate( start.date().addYears( periods ) );
01895     break;
01896   default:
01897     break;
01898   }
01899 
01900   return Constraint( nextValid, type, mWeekStart );
01901 }
01902 
01903 // Find the date/time of the next occurrence at or after a date/time,
01904 // for a given period type.
01905 // Return a constraint whose value appropriate to 'type', is set to the
01906 // value contained in the date/time.
01907 Constraint RecurrenceRule::Private::getNextValidDateInterval( const KDateTime &dt,
01908                                                               PeriodType type ) const
01909 {
01910   // TODO: Simplify this!
01911   long periods = 0;
01912   KDateTime start = mDateStart;
01913   KDateTime nextValid( start );
01914   int modifier = 1;
01915   KDateTime toDate( dt.toTimeSpec( start.timeSpec() ) );
01916   // for super-daily recurrences, don't care about the time part
01917 
01918   // Find the #intervals since the dtstart and round to the next multiple of
01919   // the frequency
01920   switch ( type ) {
01921     // Really fall through for sub-daily, since the calculations only differ
01922     // by the factor 60 and 60*60! Same for weekly and daily (factor 7)
01923   case rHourly:
01924     modifier *= 60;
01925   case rMinutely:
01926     modifier *= 60;
01927   case rSecondly:
01928     periods = static_cast<int>( start.secsTo_long( toDate ) / modifier );
01929     periods = qMax( 0L, periods );
01930     if ( periods > 0 && mFrequency > 0 ) {
01931       periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
01932     }
01933     nextValid = start.addSecs( modifier * periods );
01934     break;
01935   case rWeekly:
01936     // correct both start date and current date to start of week
01937     toDate = toDate.addDays( -( 7 + toDate.date().dayOfWeek() - mWeekStart ) % 7 );
01938     start = start.addDays( -( 7 + start.date().dayOfWeek() - mWeekStart ) % 7 );
01939     modifier *= 7;
01940   case rDaily:
01941     periods = start.daysTo( toDate ) / modifier;
01942     periods = qMax( 0L, periods );
01943     if ( periods > 0 && mFrequency > 0 ) {
01944       periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
01945     }
01946     nextValid = start.addDays( modifier * periods );
01947     break;
01948   case rMonthly:
01949   {
01950     periods = 12 * ( toDate.date().year() - start.date().year() ) +
01951               ( toDate.date().month() - start.date().month() );
01952     periods = qMax( 0L, periods );
01953     if ( periods > 0 && mFrequency > 0 ) {
01954       periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
01955     }
01956     // set the day to the first day of the month, so we don't have problems
01957     // with non-existent days like Feb 30 or April 31
01958     start.setDate( QDate( start.date().year(), start.date().month(), 1 ) );
01959     nextValid.setDate( start.date().addMonths( periods ) );
01960     break;
01961   }
01962   case rYearly:
01963     periods = ( toDate.date().year() - start.date().year() );
01964     periods = qMax( 0L, periods );
01965     if ( periods > 0 && mFrequency > 0 ) {
01966       periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
01967     }
01968     nextValid.setDate( start.date().addYears( periods ) );
01969     break;
01970   default:
01971     break;
01972   }
01973 
01974   return Constraint( nextValid, type, mWeekStart );
01975 }
01976 
01977 DateTimeList RecurrenceRule::Private::datesForInterval( const Constraint &interval,
01978                                                         PeriodType type ) const
01979 {
01980   /* -) Loop through constraints,
01981      -) merge interval with each constraint
01982      -) if merged constraint is not consistent => ignore that constraint
01983      -) if complete => add that one date to the date list
01984      -) Loop through all missing fields => For each add the resulting
01985   */
01986   DateTimeList lst;
01987   for ( int i = 0, iend = mConstraints.count();  i < iend;  ++i ) {
01988     Constraint merged( interval );
01989     if ( merged.merge( mConstraints[i] ) ) {
01990       // If the information is incomplete, we can't use this constraint
01991       if ( merged.year > 0 && merged.hour >= 0 && merged.minute >= 0 && merged.second >= 0 ) {
01992         // We have a valid constraint, so get all datetimes that match it andd
01993         // append it to all date/times of this interval
01994         QList<KDateTime> lstnew = merged.dateTimes( type );
01995         lst += lstnew;
01996       }
01997     }
01998   }
01999   // Sort it so we can apply the BySetPos. Also some logic relies on this being sorted
02000   lst.sortUnique();
02001 
02002 /*if ( lst.isEmpty() ) {
02003   kDebug() << "         No Dates in Interval";
02004 } else {
02005   kDebug() << "         Dates:";
02006   for ( int i = 0, iend = lst.count();  i < iend;  ++i ) {
02007     kDebug()<< "              -)" << dumpTime(lst[i]);
02008   }
02009   kDebug() << "       ---------------------";
02010 }*/
02011   if ( !mBySetPos.isEmpty() ) {
02012     DateTimeList tmplst = lst;
02013     lst.clear();
02014     for ( int i = 0, iend = mBySetPos.count();  i < iend;  ++i ) {
02015       int pos = mBySetPos[i];
02016       if ( pos > 0 ) {
02017         --pos;
02018       }
02019       if ( pos < 0 ) {
02020         pos += tmplst.count();
02021       }
02022       if ( pos >= 0 && pos < tmplst.count() ) {
02023         lst.append( tmplst[pos] );
02024       }
02025     }
02026     lst.sortUnique();
02027   }
02028 
02029   return lst;
02030 }
02031 //@endcond
02032 
02033 void RecurrenceRule::dump() const
02034 {
02035 #ifndef NDEBUG
02036   kDebug();
02037   if ( !d->mRRule.isEmpty() ) {
02038     kDebug() << "   RRULE=" << d->mRRule;
02039   }
02040   kDebug() << "   Read-Only:" << isReadOnly();
02041 
02042   kDebug() << "   Period type:" << int( recurrenceType() ) << ", frequency:" << frequency();
02043   kDebug() << "   #occurrences:" << duration();
02044   kDebug() << "   start date:" << dumpTime( startDt() )
02045            << ", end date:" << dumpTime( endDt() );
02046 
02047 #define dumpByIntList(list,label) \
02048   if ( !list.isEmpty() ) {\
02049     QStringList lst;\
02050     for ( int i = 0, iend = list.count();  i < iend;  ++i ) {\
02051       lst.append( QString::number( list[i] ) );\
02052     }\
02053     kDebug() << "  " << label << lst.join( ", " );\
02054   }
02055   dumpByIntList( d->mBySeconds, "BySeconds:  " );
02056   dumpByIntList( d->mByMinutes, "ByMinutes:  " );
02057   dumpByIntList( d->mByHours, "ByHours:    " );
02058   if ( !d->mByDays.isEmpty() ) {
02059     QStringList lst;
02060     for ( int i = 0, iend = d->mByDays.count();  i < iend;  ++i ) {\
02061       lst.append( ( d->mByDays[i].pos() ? QString::number( d->mByDays[i].pos() ) : "" ) +
02062                    DateHelper::dayName( d->mByDays[i].day() ) );
02063     }
02064     kDebug() << "   ByDays:    " << lst.join( ", " );
02065   }
02066   dumpByIntList( d->mByMonthDays, "ByMonthDays:" );
02067   dumpByIntList( d->mByYearDays, "ByYearDays: " );
02068   dumpByIntList( d->mByWeekNumbers, "ByWeekNr:   " );
02069   dumpByIntList( d->mByMonths, "ByMonths:   " );
02070   dumpByIntList( d->mBySetPos, "BySetPos:   " );
02071   #undef dumpByIntList
02072 
02073   kDebug() << "   Week start:" << DateHelper::dayName( d->mWeekStart ); //krazy:exclude=kdebug
02074 
02075   kDebug() << "   Constraints:";
02076   // dump constraints
02077   for ( int i = 0, iend = d->mConstraints.count();  i < iend;  ++i ) {
02078     d->mConstraints[i].dump();
02079   }
02080 #endif
02081 }
02082 
02083 //@cond PRIVATE
02084 void Constraint::dump() const
02085 {
02086   kDebug() << "     ~> Y=" << year
02087            << ", M=" << month
02088            << ", D=" << day
02089            << ", H=" << hour
02090            << ", m=" << minute
02091            << ", S=" << second
02092            << ", wd=" << weekday
02093            << ",#wd=" << weekdaynr
02094            << ", #w=" << weeknumber
02095            << ", yd=" << yearday;
02096 }
02097 //@endcond
02098 
02099 QString dumpTime( const KDateTime &dt )
02100 {
02101 #ifndef NDEBUG
02102   if ( !dt.isValid() ) {
02103     return QString();
02104   }
02105   QString result;
02106   if ( dt.isDateOnly() ) {
02107     result = dt.toString( "%a %Y-%m-%d %:Z" );
02108   } else {
02109     result = dt.toString( "%a %Y-%m-%d %H:%M:%S %:Z" );
02110     if ( dt.isSecondOccurrence() ) {
02111       result += QLatin1String( " (2nd)" );
02112     }
02113   }
02114   if ( dt.timeSpec() == KDateTime::Spec::ClockTime() ) {
02115     result += QLatin1String( "Clock" );
02116   }
02117   return result;
02118 #else
02119   Q_UNUSED( dt );
02120   return QString();
02121 #endif
02122 }
02123 
02124 KDateTime RecurrenceRule::startDt() const
02125 {
02126   return d->mDateStart;
02127 }
02128 
02129 RecurrenceRule::PeriodType RecurrenceRule::recurrenceType() const
02130 {
02131   return d->mPeriod;
02132 }
02133 
02134 uint RecurrenceRule::frequency() const
02135 {
02136   return d->mFrequency;
02137 }
02138 
02139 int RecurrenceRule::duration() const
02140 {
02141   return d->mDuration;
02142 }
02143 
02144 QString RecurrenceRule::rrule() const
02145 {
02146   return d->mRRule;
02147 }
02148 
02149 void RecurrenceRule::setRRule( const QString &rrule )
02150 {
02151   d->mRRule = rrule;
02152 }
02153 
02154 bool RecurrenceRule::isReadOnly() const
02155 {
02156   return d->mIsReadOnly;
02157 }
02158 
02159 void RecurrenceRule::setReadOnly( bool readOnly )
02160 {
02161   d->mIsReadOnly = readOnly;
02162 }
02163 
02164 bool RecurrenceRule::recurs() const
02165 {
02166   return d->mPeriod != rNone;
02167 }
02168 
02169 bool RecurrenceRule::allDay() const
02170 {
02171   return d->mAllDay;
02172 }
02173 
02174 const QList<int> &RecurrenceRule::bySeconds() const
02175 {
02176   return d->mBySeconds;
02177 }
02178 
02179 const QList<int> &RecurrenceRule::byMinutes() const
02180 {
02181   return d->mByMinutes;
02182 }
02183 
02184 const QList<int> &RecurrenceRule::byHours() const
02185 {
02186   return d->mByHours;
02187 }
02188 
02189 const QList<RecurrenceRule::WDayPos> &RecurrenceRule::byDays() const
02190 {
02191   return d->mByDays;
02192 }
02193 
02194 const QList<int> &RecurrenceRule::byMonthDays() const
02195 {
02196   return d->mByMonthDays;
02197 }
02198 
02199 const QList<int> &RecurrenceRule::byYearDays() const
02200 {
02201   return d->mByYearDays;
02202 }
02203 
02204 const QList<int> &RecurrenceRule::byWeekNumbers() const
02205 {
02206   return d->mByWeekNumbers;
02207 }
02208 
02209 const QList<int> &RecurrenceRule::byMonths() const
02210 {
02211   return d->mByMonths;
02212 }
02213 
02214 const QList<int> &RecurrenceRule::bySetPos() const
02215 {
02216   return d->mBySetPos;
02217 }
02218 
02219 short RecurrenceRule::weekStart() const
02220 {
02221   return d->mWeekStart;
02222 }
02223 
02224 RecurrenceRule::RuleObserver::~RuleObserver()
02225 {
02226 }
02227 
02228 RecurrenceRule::WDayPos::WDayPos( int ps, short dy )
02229   : mDay( dy ), mPos( ps )
02230 {
02231 }
02232 
02233 void RecurrenceRule::WDayPos::setDay( short dy )
02234 {
02235   mDay = dy;
02236 }
02237 
02238 short RecurrenceRule::WDayPos::day() const
02239 {
02240   return mDay;
02241 }
02242 
02243 void RecurrenceRule::WDayPos::setPos( int ps )
02244 {
02245   mPos = ps;
02246 }
02247 
02248 int RecurrenceRule::WDayPos::pos() const
02249 {
02250   return mPos;
02251 }

KCalCore Library

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

KDE-PIM Libraries

Skip menu "KDE-PIM Libraries"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Generated for KDE-PIM Libraries by doxygen 1.7.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