• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.8.3 API Reference
  • KDE Home
  • Contact Us
 

KCal Library

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

KDE's Doxygen guidelines are available online.

KCal Library

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

kdepimlibs-4.8.3 API Reference

Skip menu "kdepimlibs-4.8.3 API Reference"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kalarmcal
  • 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
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal