• Skip to content
  • Skip to link menu
KDE 4.3 API Reference
  • KDE API Reference
  • KDE-PIM Libraries
  • Sitemap
  • 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;
00733     int mDuration;
00734     KDateTime mDateEnd;
00735 
00736     QList<int> mBySeconds;     // values: second 0-59
00737     QList<int> mByMinutes;     // values: minute 0-59
00738     QList<int> mByHours;       // values: hour 0-23
00739 
00740     QList<WDayPos> mByDays;   // n-th weekday of the month or year
00741     QList<int> mByMonthDays;   // values: day -31 to -1 and 1-31
00742     QList<int> mByYearDays;    // values: day -366 to -1 and 1-366
00743     QList<int> mByWeekNumbers; // values: week -53 to -1 and 1-53
00744     QList<int> mByMonths;      // values: month 1-12
00745     QList<int> mBySetPos;      // values: position -366 to -1 and 1-366
00746     short mWeekStart;               // first day of the week (1=Monday, 7=Sunday)
00747 
00748     Constraint::List mConstraints;
00749     QList<RuleObserver*> mObservers;
00750 
00751     // Cache for duration
00752     mutable DateTimeList mCachedDates;
00753     mutable KDateTime mCachedDateEnd;
00754     mutable KDateTime mCachedLastDate;   // when mCachedDateEnd invalid, last date checked
00755     mutable bool mCached;
00756 
00757     bool mIsReadOnly;
00758     bool mAllDay;
00759     bool mNoByRules;        // no BySeconds, ByMinutes, ... rules exist
00760     uint mTimedRepetition;  // repeats at a regular number of seconds interval, or 0
00761 };
00762 
00763 RecurrenceRule::Private::Private( RecurrenceRule *parent, const Private &p )
00764   : mParent( parent ),
00765     mRRule( p.mRRule ),
00766     mPeriod( p.mPeriod ),
00767     mDateStart( p.mDateStart ),
00768     mFrequency( p.mFrequency ),
00769     mDuration( p.mDuration ),
00770     mDateEnd( p.mDateEnd ),
00771 
00772     mBySeconds( p.mBySeconds ),
00773     mByMinutes( p.mByMinutes ),
00774     mByHours( p.mByHours ),
00775     mByDays( p.mByDays ),
00776     mByMonthDays( p.mByMonthDays ),
00777     mByYearDays( p.mByYearDays ),
00778     mByWeekNumbers( p.mByWeekNumbers ),
00779     mByMonths( p.mByMonths ),
00780     mBySetPos( p.mBySetPos ),
00781     mWeekStart( p.mWeekStart ),
00782 
00783     mIsReadOnly( p.mIsReadOnly ),
00784     mAllDay( p.mAllDay )
00785 {
00786     setDirty();
00787 }
00788 
00789 RecurrenceRule::Private &RecurrenceRule::Private::operator=( const Private &p )
00790 {
00791   // check for self assignment
00792   if ( &p == this ) {
00793     return *this;
00794   }
00795 
00796   mRRule = p.mRRule;
00797   mPeriod = p.mPeriod;
00798   mDateStart = p.mDateStart;
00799   mFrequency = p.mFrequency;
00800   mDuration = p.mDuration;
00801   mDateEnd = p.mDateEnd;
00802 
00803   mBySeconds = p.mBySeconds;
00804   mByMinutes = p.mByMinutes;
00805   mByHours = p.mByHours;
00806   mByDays = p.mByDays;
00807   mByMonthDays = p.mByMonthDays;
00808   mByYearDays = p.mByYearDays;
00809   mByWeekNumbers = p.mByWeekNumbers;
00810   mByMonths = p.mByMonths;
00811   mBySetPos = p.mBySetPos;
00812   mWeekStart = p.mWeekStart;
00813 
00814   mIsReadOnly = p.mIsReadOnly;
00815   mAllDay = p.mAllDay;
00816 
00817   setDirty();
00818 
00819   return *this;
00820 }
00821 
00822 bool RecurrenceRule::Private::operator==( const Private &r ) const
00823 {
00824   return
00825     mPeriod == r.mPeriod &&
00826     mDateStart == r.mDateStart &&
00827     mDuration == r.mDuration &&
00828     mDateEnd == r.mDateEnd &&
00829     mFrequency == r.mFrequency &&
00830     mIsReadOnly == r.mIsReadOnly &&
00831     mAllDay == r.mAllDay &&
00832     mBySeconds == r.mBySeconds &&
00833     mByMinutes == r.mByMinutes &&
00834     mByHours == r.mByHours &&
00835     mByDays == r.mByDays &&
00836     mByMonthDays == r.mByMonthDays &&
00837     mByYearDays == r.mByYearDays &&
00838     mByWeekNumbers == r.mByWeekNumbers &&
00839     mByMonths == r.mByMonths &&
00840     mBySetPos == r.mBySetPos &&
00841     mWeekStart == r.mWeekStart;
00842 }
00843 
00844 void RecurrenceRule::Private::clear()
00845 {
00846   if ( mIsReadOnly ) {
00847     return;
00848   }
00849   mPeriod = rNone;
00850   mBySeconds.clear();
00851   mByMinutes.clear();
00852   mByHours.clear();
00853   mByDays.clear();
00854   mByMonthDays.clear();
00855   mByYearDays.clear();
00856   mByWeekNumbers.clear();
00857   mByMonths.clear();
00858   mBySetPos.clear();
00859   mWeekStart = 1;
00860 
00861   setDirty();
00862 }
00863 
00864 void RecurrenceRule::Private::setDirty()
00865 {
00866   buildConstraints();
00867   mCached = false;
00868   mCachedDates.clear();
00869   for ( int i = 0, iend = mObservers.count();  i < iend;  ++i ) {
00870     if ( mObservers[i] ) {
00871       mObservers[i]->recurrenceChanged( mParent );
00872     }
00873   }
00874 }
00875 //@endcond
00876 
00877 /**************************************************************************
00878  *                              RecurrenceRule                            *
00879  **************************************************************************/
00880 
00881 RecurrenceRule::RecurrenceRule()
00882   : d( new Private( this ) )
00883 {
00884 }
00885 
00886 RecurrenceRule::RecurrenceRule( const RecurrenceRule &r )
00887   : d( new Private( this, *r.d ) )
00888 {
00889 }
00890 
00891 RecurrenceRule::~RecurrenceRule()
00892 {
00893   delete d;
00894 }
00895 
00896 bool RecurrenceRule::operator==( const RecurrenceRule &r ) const
00897 {
00898   return *d == *r.d;
00899 }
00900 
00901 RecurrenceRule &RecurrenceRule::operator=( const RecurrenceRule &r )
00902 {
00903   // check for self assignment
00904   if ( &r == this ) {
00905     return *this;
00906   }
00907 
00908   *d = *r.d;
00909 
00910   return *this;
00911 }
00912 
00913 void RecurrenceRule::addObserver( RuleObserver *observer )
00914 {
00915   if ( !d->mObservers.contains( observer ) ) {
00916     d->mObservers.append( observer );
00917   }
00918 }
00919 
00920 void RecurrenceRule::removeObserver( RuleObserver *observer )
00921 {
00922   if ( d->mObservers.contains( observer ) ) {
00923     d->mObservers.removeAll( observer );
00924   }
00925 }
00926 
00927 void RecurrenceRule::setRecurrenceType( PeriodType period )
00928 {
00929   if ( isReadOnly() ) {
00930     return;
00931   }
00932   d->mPeriod = period;
00933   d->setDirty();
00934 }
00935 
00936 KDateTime RecurrenceRule::endDt( bool *result ) const
00937 {
00938   if ( result ) {
00939     *result = false;
00940   }
00941   if ( d->mPeriod == rNone ) {
00942     return KDateTime();
00943   }
00944   if ( d->mDuration < 0 ) {
00945     return KDateTime();
00946   }
00947   if ( d->mDuration == 0 ) {
00948     if ( result ) {
00949       *result = true;
00950     }
00951     return d->mDateEnd;
00952   }
00953 
00954   // N occurrences. Check if we have a full cache. If so, return the cached end date.
00955   if ( !d->mCached ) {
00956     // If not enough occurrences can be found (i.e. inconsistent constraints)
00957     if ( !d->buildCache() ) {
00958       return KDateTime();
00959     }
00960   }
00961   if ( result ) {
00962     *result = true;
00963   }
00964   return d->mCachedDateEnd;
00965 }
00966 
00967 void RecurrenceRule::setEndDt( const KDateTime &dateTime )
00968 {
00969   if ( isReadOnly() ) {
00970     return;
00971   }
00972   d->mDateEnd = dateTime;
00973   d->mDuration = 0; // set to 0 because there is an end date/time
00974   d->setDirty();
00975 }
00976 
00977 void RecurrenceRule::setDuration( int duration )
00978 {
00979   if ( isReadOnly() ) {
00980     return;
00981   }
00982   d->mDuration = duration;
00983   d->setDirty();
00984 }
00985 
00986 void RecurrenceRule::setAllDay( bool allDay )
00987 {
00988   if ( isReadOnly() ) {
00989     return;
00990   }
00991   d->mAllDay = allDay;
00992   d->setDirty();
00993 }
00994 
00995 void RecurrenceRule::clear()
00996 {
00997   d->clear();
00998 }
00999 
01000 void RecurrenceRule::setDirty()
01001 {
01002   d->setDirty();
01003 }
01004 
01005 void RecurrenceRule::setStartDt( const KDateTime &start )
01006 {
01007   if ( isReadOnly() ) {
01008     return;
01009   }
01010   d->mDateStart = start;
01011   d->setDirty();
01012 }
01013 
01014 void RecurrenceRule::setFrequency( int freq )
01015 {
01016   if ( isReadOnly() || freq <= 0 ) {
01017     return;
01018   }
01019   d->mFrequency = freq;
01020   d->setDirty();
01021 }
01022 
01023 void RecurrenceRule::setBySeconds( const QList<int> bySeconds )
01024 {
01025   if ( isReadOnly() ) {
01026     return;
01027   }
01028   d->mBySeconds = bySeconds;
01029   d->setDirty();
01030 }
01031 
01032 void RecurrenceRule::setByMinutes( const QList<int> byMinutes )
01033 {
01034   if ( isReadOnly() ) {
01035     return;
01036   }
01037   d->mByMinutes = byMinutes;
01038   d->setDirty();
01039 }
01040 
01041 void RecurrenceRule::setByHours( const QList<int> byHours )
01042 {
01043   if ( isReadOnly() ) {
01044     return;
01045   }
01046   d->mByHours = byHours;
01047   d->setDirty();
01048 }
01049 
01050 void RecurrenceRule::setByDays( const QList<WDayPos> byDays )
01051 {
01052   if ( isReadOnly() ) {
01053     return;
01054   }
01055   d->mByDays = byDays;
01056   d->setDirty();
01057 }
01058 
01059 void RecurrenceRule::setByMonthDays( const QList<int> byMonthDays )
01060 {
01061   if ( isReadOnly() ) {
01062     return;
01063   }
01064   d->mByMonthDays = byMonthDays;
01065   d->setDirty();
01066 }
01067 
01068 void RecurrenceRule::setByYearDays( const QList<int> byYearDays )
01069 {
01070   if ( isReadOnly() ) {
01071     return;
01072   }
01073   d->mByYearDays = byYearDays;
01074   d->setDirty();
01075 }
01076 
01077 void RecurrenceRule::setByWeekNumbers( const QList<int> byWeekNumbers )
01078 {
01079   if ( isReadOnly() ) {
01080     return;
01081   }
01082   d->mByWeekNumbers = byWeekNumbers;
01083   d->setDirty();
01084 }
01085 
01086 void RecurrenceRule::setByMonths( const QList<int> byMonths )
01087 {
01088   if ( isReadOnly() ) {
01089     return;
01090   }
01091   d->mByMonths = byMonths;
01092   d->setDirty();
01093 }
01094 
01095 void RecurrenceRule::setBySetPos( const QList<int> bySetPos )
01096 {
01097   if ( isReadOnly() ) {
01098     return;
01099   }
01100   d->mBySetPos = bySetPos;
01101   d->setDirty();
01102 }
01103 
01104 void RecurrenceRule::setWeekStart( short weekStart )
01105 {
01106   if ( isReadOnly() ) {
01107     return;
01108   }
01109   d->mWeekStart = weekStart;
01110   d->setDirty();
01111 }
01112 
01113 void RecurrenceRule::shiftTimes( const KDateTime::Spec &oldSpec, const KDateTime::Spec &newSpec )
01114 {
01115   d->mDateStart = d->mDateStart.toTimeSpec( oldSpec );
01116   d->mDateStart.setTimeSpec( newSpec );
01117   if ( d->mDuration == 0 ) {
01118     d->mDateEnd = d->mDateEnd.toTimeSpec( oldSpec );
01119     d->mDateEnd.setTimeSpec( newSpec );
01120   }
01121   d->setDirty();
01122 }
01123 
01124 // Taken from recurrence.cpp
01125 // int RecurrenceRule::maxIterations() const
01126 // {
01127 //   /* Find the maximum number of iterations which may be needed to reach the
01128 //    * next actual occurrence of a monthly or yearly recurrence.
01129 //    * More than one iteration may be needed if, for example, it's the 29th February,
01130 //    * the 31st day of the month or the 5th Monday, and the month being checked is
01131 //    * February or a 30-day month.
01132 //    * The following recurrences may never occur:
01133 //    * - For rMonthlyDay: if the frequency is a whole number of years.
01134 //    * - For rMonthlyPos: if the frequency is an even whole number of years.
01135 //    * - For rYearlyDay, rYearlyMonth: if the frequeny is a multiple of 4 years.
01136 //    * - For rYearlyPos: if the frequency is an even number of years.
01137 //    * The maximum number of iterations needed, assuming that it does actually occur,
01138 //    * was found empirically.
01139 //    */
01140 //   switch (recurs) {
01141 //     case rMonthlyDay:
01142 //       return (rFreq % 12) ? 6 : 8;
01143 //
01144 //     case rMonthlyPos:
01145 //       if (rFreq % 12 == 0) {
01146 //         // Some of these frequencies may never occur
01147 //         return (rFreq % 84 == 0) ? 364         // frequency = multiple of 7 years
01148 //              : (rFreq % 48 == 0) ? 7           // frequency = multiple of 4 years
01149 //              : (rFreq % 24 == 0) ? 14 : 28;    // frequency = multiple of 2 or 1 year
01150 //       }
01151 //       // All other frequencies will occur sometime
01152 //       if (rFreq > 120)
01153 //         return 364;    // frequencies of > 10 years will hit the date limit first
01154 //       switch (rFreq) {
01155 //         case 23:   return 50;
01156 //         case 46:   return 38;
01157 //         case 56:   return 138;
01158 //         case 66:   return 36;
01159 //         case 89:   return 54;
01160 //         case 112:  return 253;
01161 //         default:   return 25;       // most frequencies will need < 25 iterations
01162 //       }
01163 //
01164 //     case rYearlyMonth:
01165 //     case rYearlyDay:
01166 //       return 8;          // only 29th Feb or day 366 will need more than one iteration
01167 //
01168 //     case rYearlyPos:
01169 //       if (rFreq % 7 == 0)
01170 //         return 364;    // frequencies of a multiple of 7 years will hit the date limit first
01171 //       if (rFreq % 2 == 0) {
01172 //         // Some of these frequencies may never occur
01173 //         return (rFreq % 4 == 0) ? 7 : 14;    // frequency = even number of years
01174 //       }
01175 //       return 28;
01176 //   }
01177 //   return 1;
01178 // }
01179 
01180 //@cond PRIVATE
01181 void RecurrenceRule::Private::buildConstraints()
01182 {
01183   mTimedRepetition = 0;
01184   mNoByRules = mBySetPos.isEmpty();
01185   mConstraints.clear();
01186   Constraint con( mDateStart.timeSpec() );
01187   if ( mWeekStart > 0 ) {
01188     con.setWeekstart( mWeekStart );
01189   }
01190   mConstraints.append( con );
01191 
01192   int c, cend;
01193   int i, iend;
01194   Constraint::List tmp;
01195 
01196   #define intConstraint( list, setElement ) \
01197   if ( !list.isEmpty() ) { \
01198     mNoByRules = false; \
01199     iend = list.count(); \
01200     if ( iend == 1 ) { \
01201       for ( c = 0, cend = mConstraints.count();  c < cend;  ++c ) { \
01202         mConstraints[c].setElement( list[0] ); \
01203       } \
01204     } else { \
01205       for ( c = 0, cend = mConstraints.count();  c < cend;  ++c ) { \
01206         for ( i = 0;  i < iend;  ++i ) { \
01207           con = mConstraints[c]; \
01208           con.setElement( list[i] ); \
01209           tmp.append( con ); \
01210         } \
01211       } \
01212       mConstraints = tmp; \
01213       tmp.clear(); \
01214     } \
01215   }
01216 
01217   intConstraint( mBySeconds, setSecond );
01218   intConstraint( mByMinutes, setMinute );
01219   intConstraint( mByHours, setHour );
01220   intConstraint( mByMonthDays, setDay );
01221   intConstraint( mByMonths, setMonth );
01222   intConstraint( mByYearDays, setYearday );
01223   intConstraint( mByWeekNumbers, setWeeknumber );
01224   #undef intConstraint
01225 
01226   if ( !mByDays.isEmpty() ) {
01227     mNoByRules = false;
01228     for ( c = 0, cend = mConstraints.count();  c < cend;  ++c ) {
01229       for ( i = 0, iend = mByDays.count();  i < iend;  ++i ) {
01230         con = mConstraints[c];
01231         con.setWeekday( mByDays[i].day() );
01232         con.setWeekdaynr( mByDays[i].pos() );
01233         tmp.append( con );
01234       }
01235     }
01236     mConstraints = tmp;
01237     tmp.clear();
01238   }
01239 
01240   #define fixConstraint( setElement, value ) \
01241   { \
01242     for ( c = 0, cend = mConstraints.count();  c < cend;  ++c ) { \
01243       mConstraints[c].setElement( value );                        \
01244     } \
01245   }
01246   // Now determine missing values from DTSTART. This can speed up things,
01247   // because we have more restrictions and save some loops.
01248 
01249   // TODO: Does RFC 2445 intend to restrict the weekday in all cases of weekly?
01250   if ( mPeriod == rWeekly && mByDays.isEmpty() ) {
01251     fixConstraint( setWeekday, mDateStart.date().dayOfWeek() );
01252   }
01253 
01254   // Really fall through in the cases, because all smaller time intervals are
01255   // constrained from dtstart
01256   switch ( mPeriod ) {
01257   case rYearly:
01258     if ( mByDays.isEmpty() && mByWeekNumbers.isEmpty() &&
01259          mByYearDays.isEmpty() && mByMonths.isEmpty() ) {
01260       fixConstraint( setMonth, mDateStart.date().month() );
01261     }
01262   case rMonthly:
01263     if ( mByDays.isEmpty() && mByWeekNumbers.isEmpty() &&
01264          mByYearDays.isEmpty() && mByMonthDays.isEmpty() ) {
01265       fixConstraint( setDay, mDateStart.date().day() );
01266     }
01267   case rWeekly:
01268   case rDaily:
01269     if ( mByHours.isEmpty() ) {
01270       fixConstraint( setHour, mDateStart.time().hour() );
01271     }
01272   case rHourly:
01273     if ( mByMinutes.isEmpty() ) {
01274       fixConstraint( setMinute, mDateStart.time().minute() );
01275     }
01276   case rMinutely:
01277     if ( mBySeconds.isEmpty() ) {
01278       fixConstraint( setSecond, mDateStart.time().second() );
01279     }
01280   case rSecondly:
01281   default:
01282     break;
01283   }
01284   #undef fixConstraint
01285 
01286   if ( mNoByRules ) {
01287     switch ( mPeriod ) {
01288       case rHourly:
01289         mTimedRepetition = mFrequency * 3600;
01290         break;
01291       case rMinutely:
01292         mTimedRepetition = mFrequency * 60;
01293         break;
01294       case rSecondly:
01295         mTimedRepetition = mFrequency;
01296         break;
01297       default:
01298         break;
01299     }
01300   } else {
01301     for ( c = 0, cend = mConstraints.count(); c < cend; ) {
01302       if ( mConstraints[c].isConsistent( mPeriod ) ) {
01303         ++c;
01304       } else {
01305         mConstraints.removeAt( c );
01306         --cend;
01307       }
01308     }
01309   }
01310 }
01311 
01312 // Build and cache a list of all occurrences.
01313 // Only call buildCache() if mDuration > 0.
01314 bool RecurrenceRule::Private::buildCache() const
01315 {
01316   // Build the list of all occurrences of this event (we need that to determine
01317   // the end date!)
01318   Constraint interval( getNextValidDateInterval( mDateStart, mPeriod ) );
01319   QDateTime next;
01320 
01321   DateTimeList dts = datesForInterval( interval, mPeriod );
01322   // Only use dates after the event has started (start date is only included
01323   // if it matches)
01324   int i = dts.findLT( mDateStart );
01325   if ( i >= 0 ) {
01326     dts.erase( dts.begin(), dts.begin() + i + 1 );
01327   }
01328 
01329   int loopnr = 0;
01330   int dtnr = dts.count();
01331   // some validity checks to avoid infinite loops (i.e. if we have
01332   // done this loop already 10000 times, bail out )
01333   while ( loopnr < LOOP_LIMIT && dtnr < mDuration ) {
01334     interval.increase( mPeriod, mFrequency );
01335     // The returned date list is already sorted!
01336     dts += datesForInterval( interval, mPeriod );
01337     dtnr = dts.count();
01338     ++loopnr;
01339   }
01340   if ( dts.count() > mDuration ) {
01341     // we have picked up more occurrences than necessary, remove them
01342     dts.erase( dts.begin() + mDuration, dts.end() );
01343   }
01344   mCached = true;
01345   mCachedDates = dts;
01346 
01347 // it = dts.begin();
01348 // while ( it != dts.end() ) {
01349 //   kDebug() << "            -=>" << dumpTime(*it);
01350 //   ++it;
01351 // }
01352   if ( int( dts.count() ) == mDuration ) {
01353     mCachedDateEnd = dts.last();
01354     return true;
01355   } else {
01356     // The cached date list is incomplete
01357     mCachedDateEnd = KDateTime();
01358     mCachedLastDate = interval.intervalDateTime( mPeriod );
01359     return false;
01360   }
01361 }
01362 //@endcond
01363 
01364 bool RecurrenceRule::dateMatchesRules( const KDateTime &kdt ) const
01365 {
01366   KDateTime dt = kdt.toTimeSpec( d->mDateStart.timeSpec() );
01367   for ( int i = 0, iend = d->mConstraints.count();  i < iend;  ++i ) {
01368     if ( d->mConstraints[i].matches( dt, recurrenceType() ) ) {
01369       return true;
01370     }
01371   }
01372   return false;
01373 }
01374 
01375 bool RecurrenceRule::recursOn( const QDate &qd, const KDateTime::Spec &timeSpec ) const
01376 {
01377   int i, iend;
01378   if ( allDay() ) {
01379     // It's a date-only rule, so it has no time specification.
01380     // Therefore ignore 'timeSpec'.
01381     if ( qd < d->mDateStart.date() ) {
01382       return false;
01383     }
01384     // Start date is only included if it really matches
01385     QDate endDate;
01386     if ( d->mDuration >= 0 ) {
01387       endDate =  endDt().date();
01388       if ( qd > endDate ) {
01389         return false;
01390       }
01391     }
01392 
01393     // The date must be in an appropriate interval (getNextValidDateInterval),
01394     // Plus it must match at least one of the constraints
01395     bool match = false;
01396     for ( i = 0, iend = d->mConstraints.count();  i < iend && !match;  ++i ) {
01397       match = d->mConstraints[i].matches( qd, recurrenceType() );
01398     }
01399     if ( !match ) {
01400       return false;
01401     }
01402 
01403     KDateTime start( qd, QTime( 0, 0, 0 ), d->mDateStart.timeSpec() );
01404     Constraint interval( d->getNextValidDateInterval( start, recurrenceType() ) );
01405     // Constraint::matches is quite efficient, so first check if it can occur at
01406     // all before we calculate all actual dates.
01407     if ( !interval.matches( qd, recurrenceType() ) ) {
01408       return false;
01409     }
01410     // We really need to obtain the list of dates in this interval, since
01411     // otherwise BYSETPOS will not work (i.e. the date will match the interval,
01412     // but BYSETPOS selects only one of these matching dates!
01413     KDateTime end = start.addDays(1);
01414     do {
01415       DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
01416       for ( i = 0, iend = dts.count();  i < iend;  ++i ) {
01417         if ( dts[i].date() >= qd ) {
01418           return dts[i].date() == qd;
01419         }
01420       }
01421       interval.increase( recurrenceType(), frequency() );
01422     } while ( interval.intervalDateTime( recurrenceType() ) < end );
01423     return false;
01424   }
01425 
01426   // It's a date-time rule, so we need to take the time specification into account.
01427   KDateTime start( qd, QTime( 0, 0, 0 ), timeSpec );
01428   KDateTime end = start.addDays( 1 ).toTimeSpec( d->mDateStart.timeSpec() );
01429   start = start.toTimeSpec( d->mDateStart.timeSpec() );
01430   if ( end < d->mDateStart ) {
01431     return false;
01432   }
01433   if ( start < d->mDateStart ) {
01434     start = d->mDateStart;
01435   }
01436 
01437   // Start date is only included if it really matches
01438   if ( d->mDuration >= 0 ) {
01439     KDateTime endRecur = endDt();
01440     if ( endRecur.isValid() ) {
01441       if ( start > endRecur ) {
01442         return false;
01443       }
01444       if ( end > endRecur ) {
01445         end = endRecur;    // limit end-of-day time to end of recurrence rule
01446       }
01447     }
01448   }
01449 
01450   if ( d->mTimedRepetition ) {
01451     // It's a simple sub-daily recurrence with no constraints
01452     int n = static_cast<int>( ( d->mDateStart.secsTo_long( start ) - 1 ) % d->mTimedRepetition );
01453     return start.addSecs( d->mTimedRepetition - n ) < end;
01454   }
01455 
01456   // Find the start and end dates in the time spec for the rule
01457   QDate startDay = start.date();
01458   QDate endDay = end.addSecs( -1 ).date();
01459   int dayCount = startDay.daysTo( endDay ) + 1;
01460 
01461   // The date must be in an appropriate interval (getNextValidDateInterval),
01462   // Plus it must match at least one of the constraints
01463   bool match = false;
01464   for ( i = 0, iend = d->mConstraints.count();  i < iend && !match;  ++i ) {
01465     match = d->mConstraints[i].matches( startDay, recurrenceType() );
01466     for ( int day = 1;  day < dayCount && !match;  ++day ) {
01467       match = d->mConstraints[i].matches( startDay.addDays( day ), recurrenceType() );
01468     }
01469   }
01470   if ( !match ) {
01471     return false;
01472   }
01473 
01474   Constraint interval( d->getNextValidDateInterval( start, recurrenceType() ) );
01475   // Constraint::matches is quite efficient, so first check if it can occur at
01476   // all before we calculate all actual dates.
01477   match = false;
01478   Constraint intervalm = interval;
01479   do {
01480     match = intervalm.matches( startDay, recurrenceType() );
01481     for ( int day = 1;  day < dayCount && !match;  ++day ) {
01482       match = intervalm.matches( startDay.addDays( day ), recurrenceType() );
01483     }
01484     if ( match ) {
01485       break;
01486     }
01487     intervalm.increase( recurrenceType(), frequency() );
01488   } while ( intervalm.intervalDateTime( recurrenceType() ) < end );
01489   if ( !match ) {
01490     return false;
01491   }
01492 
01493   // We really need to obtain the list of dates in this interval, since
01494   // otherwise BYSETPOS will not work (i.e. the date will match the interval,
01495   // but BYSETPOS selects only one of these matching dates!
01496   do {
01497     DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
01498     int i = dts.findGE( start );
01499     if ( i >= 0 ) {
01500       return dts[i] <= end;
01501     }
01502     interval.increase( recurrenceType(), frequency() );
01503   } while ( interval.intervalDateTime( recurrenceType() ) < end );
01504 
01505   return false;
01506 }
01507 
01508 bool RecurrenceRule::recursAt( const KDateTime &kdt ) const
01509 {
01510   // Convert to the time spec used by this recurrence rule
01511   KDateTime dt( kdt.toTimeSpec( d->mDateStart.timeSpec() ) );
01512 
01513   if ( allDay() ) {
01514     return recursOn( dt.date(), dt.timeSpec() );
01515   }
01516   if ( dt < d->mDateStart ) {
01517     return false;
01518   }
01519   // Start date is only included if it really matches
01520   if ( d->mDuration >= 0 && dt > endDt() ) {
01521     return false;
01522   }
01523 
01524   if ( d->mTimedRepetition ) {
01525     // It's a simple sub-daily recurrence with no constraints
01526     return !( d->mDateStart.secsTo_long( dt ) % d->mTimedRepetition );
01527   }
01528 
01529   // The date must be in an appropriate interval (getNextValidDateInterval),
01530   // Plus it must match at least one of the constraints
01531   if ( !dateMatchesRules( dt ) ) {
01532     return false;
01533   }
01534   // if it recurs every interval, speed things up...
01535 //   if ( d->mFrequency == 1 && d->mBySetPos.isEmpty() && d->mByDays.isEmpty() ) return true;
01536   Constraint interval( d->getNextValidDateInterval( dt, recurrenceType() ) );
01537   // TODO_Recurrence: Does this work with BySetPos???
01538   if ( interval.matches( dt, recurrenceType() ) ) {
01539     return true;
01540   }
01541   return false;
01542 }
01543 
01544 TimeList RecurrenceRule::recurTimesOn( const QDate &date, const KDateTime::Spec &timeSpec ) const
01545 {
01546   TimeList lst;
01547   if ( allDay() ) {
01548     return lst;
01549   }
01550   KDateTime start( date, QTime( 0, 0, 0 ), timeSpec );
01551   KDateTime end = start.addDays( 1 ).addSecs( -1 );
01552   DateTimeList dts = timesInInterval( start, end );   // returns between start and end inclusive
01553   for ( int i = 0, iend = dts.count();  i < iend;  ++i ) {
01554     lst += dts[i].toTimeSpec( timeSpec ).time();
01555   }
01556   return lst;
01557 }
01558 
01560 int RecurrenceRule::durationTo( const KDateTime &dt ) const
01561 {
01562   // Convert to the time spec used by this recurrence rule
01563   KDateTime toDate( dt.toTimeSpec( d->mDateStart.timeSpec() ) );
01564   // Easy cases:
01565   // either before start, or after all recurrences and we know their number
01566   if ( toDate < d->mDateStart ) {
01567     return 0;
01568   }
01569   // Start date is only included if it really matches
01570   if ( d->mDuration > 0 && toDate >= endDt() ) {
01571     return d->mDuration;
01572   }
01573 
01574   if ( d->mTimedRepetition ) {
01575     // It's a simple sub-daily recurrence with no constraints
01576     return static_cast<int>( d->mDateStart.secsTo_long( toDate ) / d->mTimedRepetition );
01577   }
01578 
01579   return timesInInterval( d->mDateStart, toDate ).count();
01580 }
01581 
01582 int RecurrenceRule::durationTo( const QDate &date ) const
01583 {
01584   return durationTo( KDateTime( date, QTime( 23, 59, 59 ), d->mDateStart.timeSpec() ) );
01585 }
01586 
01587 KDateTime RecurrenceRule::getPreviousDate( const KDateTime &afterDate ) const
01588 {
01589   // Convert to the time spec used by this recurrence rule
01590   KDateTime toDate( afterDate.toTimeSpec( d->mDateStart.timeSpec() ) );
01591 
01592   // Invalid starting point, or beyond end of recurrence
01593   if ( !toDate.isValid() || toDate < d->mDateStart ) {
01594     return KDateTime();
01595   }
01596 
01597   if ( d->mTimedRepetition ) {
01598     // It's a simple sub-daily recurrence with no constraints
01599     KDateTime prev = toDate;
01600     if ( d->mDuration >= 0 && endDt().isValid() && toDate > endDt() ) {
01601       prev = endDt().addSecs( 1 ).toTimeSpec( d->mDateStart.timeSpec() );
01602     }
01603     int n = static_cast<int>( ( d->mDateStart.secsTo_long( prev ) - 1 ) % d->mTimedRepetition );
01604     if ( n < 0 ) {
01605       return KDateTime();  // before recurrence start
01606     }
01607     prev = prev.addSecs( -n - 1 );
01608     return prev >= d->mDateStart ? prev : KDateTime();
01609   }
01610 
01611   // If we have a cache (duration given), use that
01612   if ( d->mDuration > 0 ) {
01613     if ( !d->mCached ) {
01614       d->buildCache();
01615     }
01616     int i = d->mCachedDates.findLT( toDate );
01617     if ( i >= 0 ) {
01618       return d->mCachedDates[i];
01619     }
01620     return KDateTime();
01621   }
01622 
01623   KDateTime prev = toDate;
01624   if ( d->mDuration >= 0 && endDt().isValid() && toDate > endDt() ) {
01625     prev = endDt().addSecs( 1 ).toTimeSpec( d->mDateStart.timeSpec() );
01626   }
01627 
01628   Constraint interval( d->getPreviousValidDateInterval( prev, recurrenceType() ) );
01629   DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
01630   int i = dts.findLT( prev );
01631   if ( i >= 0 ) {
01632     return ( dts[i] >= d->mDateStart ) ? dts[i] : KDateTime();
01633   }
01634 
01635   // Previous interval. As soon as we find an occurrence, we're done.
01636   while ( interval.intervalDateTime( recurrenceType() ) > d->mDateStart ) {
01637     interval.increase( recurrenceType(), -int( frequency() ) );
01638     // The returned date list is sorted
01639     DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
01640     // The list is sorted, so take the last one.
01641     if ( !dts.isEmpty() ) {
01642       prev = dts.last();
01643       if ( prev.isValid() && prev >= d->mDateStart ) {
01644         return prev;
01645       } else {
01646         return KDateTime();
01647       }
01648     }
01649   }
01650   return KDateTime();
01651 }
01652 
01653 KDateTime RecurrenceRule::getNextDate( const KDateTime &preDate ) const
01654 {
01655   // Convert to the time spec used by this recurrence rule
01656   KDateTime fromDate( preDate.toTimeSpec( d->mDateStart.timeSpec() ) );
01657   // Beyond end of recurrence
01658   if ( d->mDuration >= 0 && endDt().isValid() && fromDate >= endDt() ) {
01659     return KDateTime();
01660   }
01661 
01662   // Start date is only included if it really matches
01663   if ( fromDate < d->mDateStart ) {
01664     fromDate = d->mDateStart.addSecs( -1 );
01665   }
01666 
01667   if ( d->mTimedRepetition ) {
01668     // It's a simple sub-daily recurrence with no constraints
01669     int n = static_cast<int>( ( d->mDateStart.secsTo_long( fromDate ) + 1 ) % d->mTimedRepetition );
01670     KDateTime next = fromDate.addSecs( d->mTimedRepetition - n + 1 );
01671     return d->mDuration < 0 || !endDt().isValid() || next <= endDt() ? next : KDateTime();
01672   }
01673 
01674   if ( d->mDuration > 0 ) {
01675     if ( !d->mCached ) {
01676       d->buildCache();
01677     }
01678     int i = d->mCachedDates.findGT( fromDate );
01679     if ( i >= 0 ) {
01680       return d->mCachedDates[i];
01681     }
01682   }
01683 
01684   KDateTime end = endDt();
01685   Constraint interval( d->getNextValidDateInterval( fromDate, recurrenceType() ) );
01686   DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
01687   int i = dts.findGT( fromDate );
01688   if ( i >= 0 ) {
01689     return ( d->mDuration < 0 || dts[i] <= end ) ? dts[i] : KDateTime();
01690   }
01691   interval.increase( recurrenceType(), frequency() );
01692   if ( d->mDuration >= 0 && interval.intervalDateTime( recurrenceType() ) > end ) {
01693     return KDateTime();
01694   }
01695 
01696   // Increase the interval. The first occurrence that we find is the result (if
01697   // if's before the end date).
01698   // TODO: some validity checks to avoid infinite loops for contradictory constraints
01699   int loop = 0;
01700   do {
01701     DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
01702     if ( dts.count() > 0 ) {
01703       KDateTime ret( dts[0] );
01704       if ( d->mDuration >= 0 && ret > end ) {
01705         return KDateTime();
01706       } else {
01707         return ret;
01708       }
01709     }
01710     interval.increase( recurrenceType(), frequency() );
01711   } while ( ++loop < LOOP_LIMIT &&
01712             ( d->mDuration < 0 || interval.intervalDateTime( recurrenceType() ) < end ) );
01713   return KDateTime();
01714 }
01715 
01716 DateTimeList RecurrenceRule::timesInInterval( const KDateTime &dtStart,
01717                                               const KDateTime &dtEnd ) const
01718 {
01719   KDateTime start = dtStart.toTimeSpec( d->mDateStart.timeSpec() );
01720   KDateTime end = dtEnd.toTimeSpec( d->mDateStart.timeSpec() );
01721   DateTimeList result;
01722   if ( end < d->mDateStart ) {
01723     return result;    // before start of recurrence
01724   }
01725   KDateTime enddt = end;
01726   if ( d->mDuration >= 0 ) {
01727     KDateTime endRecur = endDt();
01728     if ( endRecur.isValid() ) {
01729       if ( start >= endRecur ) {
01730         return result;    // beyond end of recurrence
01731       }
01732       if ( end > endRecur ) {
01733         enddt = endRecur;    // limit end time to end of recurrence rule
01734       }
01735     }
01736   }
01737 
01738   if ( d->mTimedRepetition ) {
01739     // It's a simple sub-daily recurrence with no constraints
01740     int n = static_cast<int>( ( d->mDateStart.secsTo_long( start ) - 1 ) % d->mTimedRepetition );
01741     KDateTime dt = start.addSecs( d->mTimedRepetition - n );
01742     if ( dt < enddt ) {
01743       n = static_cast<int>( ( dt.secsTo_long( enddt ) - 1 ) / d->mTimedRepetition ) + 1;
01744       // limit n by a sane value else we can "explode".
01745       n = qMin( n, LOOP_LIMIT );
01746       for ( int i = 0;  i < n;  dt = dt.addSecs( d->mTimedRepetition ), ++i ) {
01747         result += dt;
01748       }
01749     }
01750     return result;
01751   }
01752 
01753   KDateTime st = start;
01754   bool done = false;
01755   if ( d->mDuration > 0 ) {
01756     if ( !d->mCached ) {
01757       d->buildCache();
01758     }
01759     if ( d->mCachedDateEnd.isValid() && start >= d->mCachedDateEnd ) {
01760       return result;    // beyond end of recurrence
01761     }
01762     int i = d->mCachedDates.findGE( start );
01763     if ( i >= 0 ) {
01764       int iend = d->mCachedDates.findGT( enddt, i );
01765       if ( iend < 0 ) {
01766         iend = d->mCachedDates.count();
01767       } else {
01768         done = true;
01769       }
01770       while ( i < iend ) {
01771         result += d->mCachedDates[i++];
01772       }
01773     }
01774     if ( d->mCachedDateEnd.isValid() ) {
01775       done = true;
01776     } else if ( !result.isEmpty() ) {
01777       result += KDateTime();    // indicate that the returned list is incomplete
01778       done = true;
01779     }
01780     if ( done ) {
01781       return result;
01782     }
01783     // We don't have any result yet, but we reached the end of the incomplete cache
01784     st = d->mCachedLastDate.addSecs( 1 );
01785   }
01786 
01787   Constraint interval( d->getNextValidDateInterval( st, recurrenceType() ) );
01788   int loop = 0;
01789   do {
01790     DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
01791     int i = 0;
01792     int iend = dts.count();
01793     if ( loop == 0 ) {
01794       i = dts.findGE( st );
01795       if ( i < 0 ) {
01796         i = iend;
01797       }
01798     }
01799     int j = dts.findGT( enddt, i );
01800     if ( j >= 0 ) {
01801       iend = j;
01802       loop = LOOP_LIMIT;
01803     }
01804     while ( i < iend ) {
01805       result += dts[i++];
01806     }
01807     // Increase the interval.
01808     interval.increase( recurrenceType(), frequency() );
01809   } while ( ++loop < LOOP_LIMIT &&
01810             interval.intervalDateTime( recurrenceType() ) < end );
01811   return result;
01812 }
01813 
01814 //@cond PRIVATE
01815 // Find the date/time of the occurrence at or before a date/time,
01816 // for a given period type.
01817 // Return a constraint whose value appropriate to 'type', is set to
01818 // the value contained in the date/time.
01819 Constraint RecurrenceRule::Private::getPreviousValidDateInterval( const KDateTime &dt,
01820                                                                   PeriodType type ) const
01821 {
01822   long periods = 0;
01823   KDateTime start = mDateStart;
01824   KDateTime nextValid( start );
01825   int modifier = 1;
01826   KDateTime toDate( dt.toTimeSpec( start.timeSpec() ) );
01827   // for super-daily recurrences, don't care about the time part
01828 
01829   // Find the #intervals since the dtstart and round to the next multiple of
01830   // the frequency
01831   switch ( type ) {
01832     // Really fall through for sub-daily, since the calculations only differ
01833     // by the factor 60 and 60*60! Same for weekly and daily (factor 7)
01834   case rHourly:
01835     modifier *= 60;
01836   case rMinutely:
01837     modifier *= 60;
01838   case rSecondly:
01839     periods = static_cast<int>( start.secsTo_long( toDate ) / modifier );
01840     // round it down to the next lower multiple of frequency:
01841     if ( mFrequency > 0 ) {
01842       periods = ( periods / mFrequency ) * mFrequency;
01843     }
01844     nextValid = start.addSecs( modifier * periods );
01845     break;
01846   case rWeekly:
01847     toDate = toDate.addDays( -( 7 + toDate.date().dayOfWeek() - mWeekStart ) % 7 );
01848     start = start.addDays( -( 7 + start.date().dayOfWeek() - mWeekStart ) % 7 );
01849     modifier *= 7;
01850   case rDaily:
01851     periods = start.daysTo( toDate ) / modifier;
01852     // round it down to the next lower multiple of frequency:
01853     if ( mFrequency > 0 ) {
01854       periods = ( periods / mFrequency ) * mFrequency;
01855     }
01856     nextValid = start.addDays( modifier * periods );
01857     break;
01858   case rMonthly:
01859   {
01860     periods = 12 * ( toDate.date().year() - start.date().year() ) +
01861               ( toDate.date().month() - start.date().month() );
01862     // round it down to the next lower multiple of frequency:
01863     if ( mFrequency > 0 ) {
01864       periods = ( periods / mFrequency ) * mFrequency;
01865     }
01866     // set the day to the first day of the month, so we don't have problems
01867     // with non-existent days like Feb 30 or April 31
01868     start.setDate( QDate( start.date().year(), start.date().month(), 1 ) );
01869     nextValid.setDate( start.date().addMonths( periods ) );
01870     break; }
01871   case rYearly:
01872     periods = ( toDate.date().year() - start.date().year() );
01873     // round it down to the next lower multiple of frequency:
01874     if ( mFrequency > 0 ) {
01875       periods = ( periods / mFrequency ) * mFrequency;
01876     }
01877     nextValid.setDate( start.date().addYears( periods ) );
01878     break;
01879   default:
01880     break;
01881   }
01882 
01883   return Constraint( nextValid, type, mWeekStart );
01884 }
01885 
01886 // Find the date/time of the next occurrence at or after a date/time,
01887 // for a given period type.
01888 // Return a constraint whose value appropriate to 'type', is set to the
01889 // value contained in the date/time.
01890 Constraint RecurrenceRule::Private::getNextValidDateInterval( const KDateTime &dt,
01891                                                               PeriodType type ) const
01892 {
01893   // TODO: Simplify this!
01894   long periods = 0;
01895   KDateTime start = mDateStart;
01896   KDateTime nextValid( start );
01897   int modifier = 1;
01898   KDateTime toDate( dt.toTimeSpec( start.timeSpec() ) );
01899   // for super-daily recurrences, don't care about the time part
01900 
01901   // Find the #intervals since the dtstart and round to the next multiple of
01902   // the frequency
01903   switch ( type ) {
01904     // Really fall through for sub-daily, since the calculations only differ
01905     // by the factor 60 and 60*60! Same for weekly and daily (factor 7)
01906   case rHourly:
01907     modifier *= 60;
01908   case rMinutely:
01909     modifier *= 60;
01910   case rSecondly:
01911     periods = static_cast<int>( start.secsTo_long( toDate ) / modifier );
01912     periods = qMax( 0L, periods );
01913     if ( periods > 0 && mFrequency > 0 ) {
01914       periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
01915     }
01916     nextValid = start.addSecs( modifier * periods );
01917     break;
01918   case rWeekly:
01919     // correct both start date and current date to start of week
01920     toDate = toDate.addDays( -( 7 + toDate.date().dayOfWeek() - mWeekStart ) % 7 );
01921     start = start.addDays( -( 7 + start.date().dayOfWeek() - mWeekStart ) % 7 );
01922     modifier *= 7;
01923   case rDaily:
01924     periods = start.daysTo( toDate ) / modifier;
01925     periods = qMax( 0L, periods );
01926     if ( periods > 0 && mFrequency > 0 ) {
01927       periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
01928     }
01929     nextValid = start.addDays( modifier * periods );
01930     break;
01931   case rMonthly:
01932   {
01933     periods = 12 * ( toDate.date().year() - start.date().year() ) +
01934               ( toDate.date().month() - start.date().month() );
01935     periods = qMax( 0L, periods );
01936     if ( periods > 0 && mFrequency > 0 ) {
01937       periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
01938     }
01939     // set the day to the first day of the month, so we don't have problems
01940     // with non-existent days like Feb 30 or April 31
01941     start.setDate( QDate( start.date().year(), start.date().month(), 1 ) );
01942     nextValid.setDate( start.date().addMonths( periods ) );
01943     break;
01944   }
01945   case rYearly:
01946     periods = ( toDate.date().year() - start.date().year() );
01947     periods = qMax( 0L, periods );
01948     if ( periods > 0 && mFrequency > 0 ) {
01949       periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
01950     }
01951     nextValid.setDate( start.date().addYears( periods ) );
01952     break;
01953   default:
01954     break;
01955   }
01956 
01957   return Constraint( nextValid, type, mWeekStart );
01958 }
01959 
01960 DateTimeList RecurrenceRule::Private::datesForInterval( const Constraint &interval,
01961                                                         PeriodType type ) const
01962 {
01963   /* -) Loop through constraints,
01964      -) merge interval with each constraint
01965      -) if merged constraint is not consistent => ignore that constraint
01966      -) if complete => add that one date to the date list
01967      -) Loop through all missing fields => For each add the resulting
01968   */
01969   DateTimeList lst;
01970   for ( int i = 0, iend = mConstraints.count();  i < iend;  ++i ) {
01971     Constraint merged( interval );
01972     if ( merged.merge( mConstraints[i] ) ) {
01973       // If the information is incomplete, we can't use this constraint
01974       if ( merged.year > 0 && merged.hour >= 0 && merged.minute >= 0 && merged.second >= 0 ) {
01975         // We have a valid constraint, so get all datetimes that match it andd
01976         // append it to all date/times of this interval
01977         QList<KDateTime> lstnew = merged.dateTimes( type );
01978         lst += lstnew;
01979       }
01980     }
01981   }
01982   // Sort it so we can apply the BySetPos. Also some logic relies on this being sorted
01983   lst.sortUnique();
01984 
01985 /*if ( lst.isEmpty() ) {
01986   kDebug() << "         No Dates in Interval";
01987 } else {
01988   kDebug() << "         Dates:";
01989   for ( int i = 0, iend = lst.count();  i < iend;  ++i ) {
01990     kDebug()<< "              -)" << dumpTime(lst[i]);
01991   }
01992   kDebug() << "       ---------------------";
01993 }*/
01994   if ( !mBySetPos.isEmpty() ) {
01995     DateTimeList tmplst = lst;
01996     lst.clear();
01997     for ( int i = 0, iend = mBySetPos.count();  i < iend;  ++i ) {
01998       int pos = mBySetPos[i];
01999       if ( pos > 0 ) {
02000         --pos;
02001       }
02002       if ( pos < 0 ) {
02003         pos += tmplst.count();
02004       }
02005       if ( pos >= 0 && pos < tmplst.count() ) {
02006         lst.append( tmplst[pos] );
02007       }
02008     }
02009     lst.sortUnique();
02010   }
02011 
02012   return lst;
02013 }
02014 //@endcond
02015 
02016 void RecurrenceRule::dump() const
02017 {
02018 #ifndef NDEBUG
02019   kDebug();
02020   if ( !d->mRRule.isEmpty() ) {
02021     kDebug() << "   RRULE=" << d->mRRule;
02022   }
02023   kDebug() << "   Read-Only:" << isReadOnly();
02024 
02025   kDebug() << "   Period type:" << recurrenceType()
02026            << ", frequency:" << frequency();
02027   kDebug() << "   #occurrences:" << duration();
02028   kDebug() << "   start date:" << dumpTime( startDt() )
02029            << ", end date:" << dumpTime( endDt() );
02030 
02031 #define dumpByIntList(list,label) \
02032   if ( !list.isEmpty() ) {\
02033     QStringList lst;\
02034     for ( int i = 0, iend = list.count();  i < iend;  ++i ) {\
02035       lst.append( QString::number( list[i] ) );\
02036     }\
02037     kDebug() << "  " << label << lst.join( ", " );\
02038   }
02039   dumpByIntList( d->mBySeconds, "BySeconds:  " );
02040   dumpByIntList( d->mByMinutes, "ByMinutes:  " );
02041   dumpByIntList( d->mByHours, "ByHours:    " );
02042   if ( !d->mByDays.isEmpty() ) {
02043     QStringList lst;
02044     for ( int i = 0, iend = d->mByDays.count();  i < iend;  ++i ) {\
02045       lst.append( ( d->mByDays[i].pos() ? QString::number( d->mByDays[i].pos() ) : "" ) +
02046                    DateHelper::dayName( d->mByDays[i].day() ) );
02047     }
02048     kDebug() << "   ByDays:    " << lst.join( ", " );
02049   }
02050   dumpByIntList( d->mByMonthDays, "ByMonthDays:" );
02051   dumpByIntList( d->mByYearDays, "ByYearDays: " );
02052   dumpByIntList( d->mByWeekNumbers, "ByWeekNr:   " );
02053   dumpByIntList( d->mByMonths, "ByMonths:   " );
02054   dumpByIntList( d->mBySetPos, "BySetPos:   " );
02055   #undef dumpByIntList
02056 
02057   kDebug() << "   Week start:" << DateHelper::dayName( d->mWeekStart );
02058 
02059   kDebug() << "   Constraints:";
02060   // dump constraints
02061   for ( int i = 0, iend = d->mConstraints.count();  i < iend;  ++i ) {
02062     d->mConstraints[i].dump();
02063   }
02064 #endif
02065 }
02066 
02067 //@cond PRIVATE
02068 void Constraint::dump() const
02069 {
02070   kDebug() << "     ~> Y=" << year
02071            << ", M=" << month
02072            << ", D=" << day
02073            << ", H=" << hour
02074            << ", m=" << minute
02075            << ", S=" << second
02076            << ", wd=" << weekday
02077            << ",#wd=" << weekdaynr
02078            << ", #w=" << weeknumber
02079            << ", yd=" << yearday;
02080 }
02081 //@endcond
02082 
02083 QString dumpTime( const KDateTime &dt )
02084 {
02085 #ifndef NDEBUG
02086   if ( !dt.isValid() ) {
02087     return QString();
02088   }
02089   QString result;
02090   if ( dt.isDateOnly() ) {
02091     result = dt.toString( "%a %Y-%m-%d %:Z" );
02092   } else {
02093     result = dt.toString( "%a %Y-%m-%d %H:%M:%S %:Z" );
02094     if ( dt.isSecondOccurrence() ) {
02095       result += QLatin1String( " (2nd)" );
02096     }
02097   }
02098   if ( dt.timeSpec() == KDateTime::Spec::ClockTime() ) {
02099     result += QLatin1String( "Clock" );
02100   }
02101   return result;
02102 #else
02103   Q_UNUSED( dt );
02104   return QString();
02105 #endif
02106 }
02107 
02108 KDateTime RecurrenceRule::startDt() const
02109 {
02110   return d->mDateStart;
02111 }
02112 
02113 RecurrenceRule::PeriodType RecurrenceRule::recurrenceType() const
02114 {
02115   return d->mPeriod;
02116 }
02117 
02118 uint RecurrenceRule::frequency() const
02119 {
02120   return d->mFrequency;
02121 }
02122 
02123 int RecurrenceRule::duration() const
02124 {
02125   return d->mDuration;
02126 }
02127 
02128 QString RecurrenceRule::rrule() const
02129 {
02130   return d->mRRule;
02131 }
02132 
02133 void RecurrenceRule::setRRule( const QString &rrule )
02134 {
02135   d->mRRule = rrule;
02136 }
02137 
02138 bool RecurrenceRule::isReadOnly() const
02139 {
02140   return d->mIsReadOnly;
02141 }
02142 
02143 void RecurrenceRule::setReadOnly( bool readOnly )
02144 {
02145   d->mIsReadOnly = readOnly;
02146 }
02147 
02148 bool RecurrenceRule::recurs() const
02149 {
02150   return d->mPeriod != rNone;
02151 }
02152 
02153 bool RecurrenceRule::allDay() const
02154 {
02155   return d->mAllDay;
02156 }
02157 
02158 const QList<int> &RecurrenceRule::bySeconds() const
02159 {
02160   return d->mBySeconds;
02161 }
02162 
02163 const QList<int> &RecurrenceRule::byMinutes() const
02164 {
02165   return d->mByMinutes;
02166 }
02167 
02168 const QList<int> &RecurrenceRule::byHours() const
02169 {
02170   return d->mByHours;
02171 }
02172 
02173 const QList<RecurrenceRule::WDayPos> &RecurrenceRule::byDays() const
02174 {
02175   return d->mByDays;
02176 }
02177 
02178 const QList<int> &RecurrenceRule::byMonthDays() const
02179 {
02180   return d->mByMonthDays;
02181 }
02182 
02183 const QList<int> &RecurrenceRule::byYearDays() const
02184 {
02185   return d->mByYearDays;
02186 }
02187 
02188 const QList<int> &RecurrenceRule::byWeekNumbers() const
02189 {
02190   return d->mByWeekNumbers;
02191 }
02192 
02193 const QList<int> &RecurrenceRule::byMonths() const
02194 {
02195   return d->mByMonths;
02196 }
02197 
02198 const QList<int> &RecurrenceRule::bySetPos() const
02199 {
02200   return d->mBySetPos;
02201 }
02202 
02203 short RecurrenceRule::weekStart() const
02204 {
02205   return d->mWeekStart;
02206 }

KCal Library

Skip menu "KCal 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
  • kabc
  • kblog
  • kcal
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  • kldap
  • kmime
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Generated for KDE-PIM Libraries by doxygen 1.5.9
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