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

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
  • kimap
  • kioslave
  •   imap4
  •   mbox
  • kldap
  • kmime
  • kpimidentities
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Generated for KDE-PIM Libraries by doxygen 1.5.8
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