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

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