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

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.7.1
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