libkcal

recurrencerule.cpp

Go to the documentation of this file.
00001 /*
00002     This file is part of libkcal.
00003 
00004     Copyright (c) 2005 Reinhold Kainhofer <reinhold@kainhofe.com>
00005 
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 #include <qdatetime.h>
00028 #include <qstringlist.h>
00029 
00030 #include <limits.h>
00031 #include <math.h>
00032 
00033 using namespace KCal;
00034 
00035 
00036 // FIXME: If Qt is ever changed so that QDateTime:::addSecs takes into account
00037 //        DST shifts, we need to use our own addSecs method, too, since we
00038 //        need to caalculate things in UTC!
00058 long long ownSecsTo( const QDateTime &dt1, const QDateTime &dt2 )
00059 {
00060   long long res = static_cast<long long>( dt1.date().daysTo( dt2.date() ) ) * 24*3600;
00061   res += dt1.time().secsTo( dt2.time() );
00062   return res;
00063 }
00064 
00065 
00066 
00067 /**************************************************************************
00068  *                               DateHelper                               *
00069  **************************************************************************/
00070 
00071 
00072 class DateHelper {
00073   public:
00074 #ifndef NDEBUG
00075     static QString dayName( short day );
00076 #endif
00077     static QDate getNthWeek( int year, int weeknumber, short weekstart = 1 );
00078     static int weekNumbersInYear( int year, short weekstart = 1 );
00079     static int getWeekNumber( const QDate &date, short weekstart, int *year = 0 );
00080     static int getWeekNumberNeg( const QDate &date, short weekstart, int *year = 0 );
00081 };
00082 
00083 
00084 #ifndef NDEBUG
00085 QString DateHelper::dayName( short day )
00086 {
00087   switch ( day ) {
00088     case 1: return "MO"; break;
00089     case 2: return "TU"; break;
00090     case 3: return "WE"; break;
00091     case 4: return "TH"; break;
00092     case 5: return "FR"; break;
00093     case 6: return "SA"; break;
00094     case 7: return "SU"; break;
00095     default: return "??";
00096   }
00097 }
00098 #endif
00099 
00100 
00101 QDate DateHelper::getNthWeek( int year, int weeknumber, short weekstart )
00102 {
00103   if ( weeknumber == 0 ) return QDate();
00104   // Adjust this to the first day of week #1 of the year and add 7*weekno days.
00105   QDate dt( year, 1, 4 ); // Week #1 is the week that contains Jan 4
00106   int adjust = -(7 + dt.dayOfWeek() - weekstart) % 7;
00107   if ( weeknumber > 0 ) {
00108     dt = dt.addDays( 7 * (weeknumber-1) + adjust );
00109   } else if ( weeknumber < 0 ) {
00110     dt = dt.addYears( 1 );
00111     dt = dt.addDays( 7 * weeknumber + adjust );
00112   }
00113   return dt;
00114 }
00115 
00116 
00117 int DateHelper::getWeekNumber( const QDate &date, short weekstart, int *year )
00118 {
00119 // kdDebug(5800) << "Getting week number for " << date << " with weekstart="<<weekstart<<endl;
00120   if ( year ) *year = date.year();
00121   QDate dt( date.year(), 1, 4 ); // <= definitely in week #1
00122   dt = dt.addDays( -(7 + dt.dayOfWeek() - weekstart) % 7 ); // begin of week #1
00123   QDate dtn( date.year()+1, 1, 4 ); // <= definitely first week of next year
00124   dtn = dtn.addDays( -(7 + dtn.dayOfWeek() - weekstart) % 7 );
00125 
00126   int daysto = dt.daysTo( date );
00127   int dayston = dtn.daysTo( date );
00128   if ( daysto < 0 ) {
00129     if ( year ) *year = date.year()-1;
00130     dt = QDate( date.year()-1, 1, 4 );
00131     dt = dt.addDays( -(7 + dt.dayOfWeek() - weekstart) % 7 ); // begin of week #1
00132     daysto = dt.daysTo( date );
00133   } else if ( dayston >= 0 ) {
00134     // in first week of next year;
00135     if ( year ) *year = date.year() + 1;
00136     dt = dtn;
00137     daysto = dayston;
00138   }
00139   return daysto / 7 + 1;
00140 }
00141 
00142 int DateHelper::weekNumbersInYear( int year, short weekstart )
00143 {
00144   QDate dt( year, 1, weekstart );
00145   QDate dt1( year + 1, 1, weekstart );
00146   return dt.daysTo( dt1 ) / 7;
00147 }
00148 
00149 // Week number from the end of the year
00150 int DateHelper::getWeekNumberNeg( const QDate &date, short weekstart, int *year )
00151 {
00152   int weekpos = getWeekNumber( date, weekstart, year );
00153   return weekNumbersInYear( *year, weekstart ) - weekpos - 1;
00154 }
00155 
00156 
00157 
00158 
00159 
00160 /**************************************************************************
00161  *                       RecurrenceRule::Constraint                       *
00162  **************************************************************************/
00163 
00164 
00165 RecurrenceRule::Constraint::Constraint( int wkst )
00166 {
00167   weekstart = wkst;
00168   clear();
00169 }
00170 
00171 RecurrenceRule::Constraint::Constraint( const QDateTime &preDate, PeriodType type, int wkst )
00172 {
00173   weekstart = wkst;
00174   readDateTime( preDate, type );
00175 }
00176 
00177 void RecurrenceRule::Constraint::clear()
00178 {
00179   year = 0;
00180   month = 0;
00181   day = 0;
00182   hour = -1;
00183   minute = -1;
00184   second = -1;
00185   weekday = 0;
00186   weekdaynr = 0;
00187   weeknumber = 0;
00188   yearday = 0;
00189 }
00190 
00191 bool RecurrenceRule::Constraint::matches( const QDate &dt, RecurrenceRule::PeriodType type ) const
00192 {
00193   // If the event recurs in week 53 or 1, the day might not belong to the same
00194   // year as the week it is in. E.g. Jan 1, 2005 is in week 53 of year 2004.
00195   // So we can't simply check the year in that case!
00196   if ( weeknumber == 0 ) {
00197     if ( year > 0 && year != dt.year() ) return false;
00198   } else {
00199     int y;
00200     if ( weeknumber > 0 &&
00201          weeknumber != DateHelper::getWeekNumber( dt, weekstart, &y ) ) return false;
00202     if ( weeknumber < 0 &&
00203          weeknumber != DateHelper::getWeekNumberNeg( dt, weekstart, &y ) ) return false;
00204     if ( year > 0 && year != y ) return false;
00205   }
00206 
00207   if ( month > 0 && month != dt.month() ) return false;
00208   if ( day > 0 && day != dt.day() ) return false;
00209   if ( day < 0 && dt.day() != (dt.daysInMonth() + day + 1 ) ) return false;
00210   if ( weekday > 0 ) {
00211     if ( weekday != dt.dayOfWeek() ) return false;
00212     if ( weekdaynr != 0 ) {
00213       // If it's a yearly recurrence and a month is given, the position is
00214       // still in the month, not in the year.
00215       bool inMonth = (type == rMonthly) || ( type == rYearly && month > 0 );
00216       // Monthly
00217       if ( weekdaynr > 0 && inMonth &&
00218            weekdaynr != (dt.day() - 1)/7 + 1 ) return false;
00219       if ( weekdaynr < 0 && inMonth &&
00220            weekdaynr != -((dt.daysInMonth() - dt.day() )/7 + 1 ) )
00221         return false;
00222       // Yearly
00223       if ( weekdaynr > 0 && !inMonth &&
00224            weekdaynr != (dt.dayOfYear() - 1)/7 + 1 ) return false;
00225       if ( weekdaynr < 0 && !inMonth &&
00226            weekdaynr != -((dt.daysInYear() - dt.dayOfYear() )/7 + 1 ) )
00227         return false;
00228     }
00229   }
00230   if ( yearday > 0 && yearday != dt.dayOfYear() ) return false;
00231   if ( yearday < 0 && yearday != dt.daysInYear() - dt.dayOfYear() + 1 )
00232     return false;
00233   return true;
00234 }
00235 
00236 bool RecurrenceRule::Constraint::matches( const QDateTime &dt, RecurrenceRule::PeriodType type ) const
00237 {
00238   if ( !matches( dt.date(), type ) ) return false;
00239   if ( hour >= 0 && hour != dt.time().hour() ) return false;
00240   if ( minute >= 0 && minute != dt.time().minute() ) return false;
00241   if ( second >= 0 && second != dt.time().second() ) return false;
00242   return true;
00243 }
00244 
00245 bool RecurrenceRule::Constraint::isConsistent( PeriodType /*period*/) const
00246 {
00247   // TODO: Check for consistency, e.g. byyearday=3 and bymonth=10
00248   return true;
00249 }
00250 
00251 QDateTime RecurrenceRule::Constraint::intervalDateTime( RecurrenceRule::PeriodType type ) const
00252 {
00253   QDateTime dt;
00254   dt.setTime( QTime( 0, 0, 0 ) );
00255   dt.setDate( QDate( year, (month>0)?month:1, (day>0)?day:1 ) );
00256   if ( day < 0 )
00257     dt = dt.addDays( dt.date().daysInMonth() + day );
00258   switch ( type ) {
00259     case rSecondly:
00260       dt.setTime( QTime( hour, minute, second ) ); break;
00261     case rMinutely:
00262       dt.setTime( QTime( hour, minute, 1 ) ); break;
00263     case rHourly:
00264       dt.setTime( QTime( hour, 1, 1 ) ); break;
00265     case rDaily:
00266       break;
00267     case rWeekly:
00268       dt = DateHelper::getNthWeek( year, weeknumber, weekstart ); break;
00269     case rMonthly:
00270       dt.setDate( QDate( year, month, 1 ) ); break;
00271     case rYearly:
00272       dt.setDate( QDate( year, 1, 1 ) ); break;
00273     default:
00274       break;
00275   }
00276   return dt;
00277 }
00278 
00279 
00280 //           Y  M  D | H  Mn S | WD #WD | WN | YD
00281 // required:
00282 //           x       | x  x  x |        |    |
00283 // 0) Trivial: Exact date given, maybe other restrictions
00284 //           x  x  x | x  x  x |        |    |
00285 // 1) Easy case: no weekly restrictions -> at most a loop through possible dates
00286 //           x  +  + | x  x  x |  -  -  |  - |  -
00287 // 2) Year day is given -> date known
00288 //           x       | x  x  x |        |    |  +
00289 // 3) week number is given -> loop through all days of that week. Further
00290 //    restrictions will be applied in the end, when we check all dates for
00291 //    consistency with the constraints
00292 //           x       | x  x  x |        |  + | (-)
00293 // 4) week day is specified ->
00294 //           x       | x  x  x |  x  ?  | (-)| (-)
00295 // 5) All possiblecases have already been treated, so this must be an error!
00296 
00297 DateTimeList RecurrenceRule::Constraint::dateTimes( RecurrenceRule::PeriodType type ) const
00298 {
00299 // kdDebug(5800) << "              RecurrenceRule::Constraint::dateTimes: " << endl;
00300   DateTimeList result;
00301   bool done = false;
00302   // TODO_Recurrence: Handle floating
00303   QTime tm( hour, minute, second );
00304   if ( !isConsistent( type ) ) return result;
00305 
00306   if ( !done && day > 0 && month > 0 ) {
00307     QDateTime dt( QDate( year, month, day ), tm );
00308     if ( dt.isValid() ) result.append( dt );
00309     done = true;
00310   }
00311   if ( !done && day < 0 && month > 0 ) {
00312     QDateTime dt( QDate( year, month, 1 ), tm );
00313     dt = dt.addDays( dt.date().daysInMonth() + day );
00314     if ( dt.isValid() ) result.append( dt );
00315     done = true;
00316   }
00317 
00318 
00319   if ( !done && weekday == 0 && weeknumber == 0 && yearday == 0 ) {
00320     // Easy case: date is given, not restrictions by week or yearday
00321     uint mstart = (month>0) ? month : 1;
00322     uint mend = (month <= 0) ? 12 : month;
00323     for ( uint m = mstart; m <= mend; ++m ) {
00324       uint dstart, dend;
00325       if ( day > 0 ) {
00326         dstart = dend = day;
00327       } else if ( day < 0 ) {
00328         QDate date( year, month, 1 );
00329         dstart = dend = date.daysInMonth() + day + 1;
00330       } else {
00331         QDate date( year, month, 1 );
00332         dstart = 1;
00333         dend = date.daysInMonth();
00334       }
00335       for ( uint d = dstart; d <= dend; ++d ) {
00336         QDateTime dt( QDate( year, m, d ), tm );
00337         if ( dt.isValid() ) result.append( dt );
00338       }
00339     }
00340     done = true;
00341   }
00342 
00343   // Else: At least one of the week / yearday restrictions was given...
00344   // If we have a yearday (and of course a year), we know the exact date
00345   if ( !done && yearday != 0 ) {
00346     // yearday < 0 means from end of year, so we'll need Jan 1 of the next year
00347     QDate d( year + ((yearday>0)?0:1), 1, 1 );
00348     d = d.addDays( yearday - ((yearday>0)?1:0) );
00349     result.append( QDateTime( d, tm ) );
00350     done = true;
00351   }
00352 
00353   // Else: If we have a weeknumber, we have at most 7 possible dates, loop through them
00354   if ( !done && weeknumber != 0 ) {
00355     QDate wst( DateHelper::getNthWeek( year, weeknumber, weekstart ) );
00356     if ( weekday != 0 ) {
00357       wst = wst.addDays( (7 + weekday - weekstart ) % 7 );
00358       result.append( QDateTime( wst, tm ) );
00359     } else {
00360       for ( int i = 0; i < 7; ++i ) {
00361         result.append( QDateTime( wst, tm ) );
00362         wst = wst.addDays( 1 );
00363       }
00364     }
00365     done = true;
00366   }
00367 
00368   // weekday is given
00369   if ( !done && weekday != 0 ) {
00370     QDate dt( year, 1, 1 );
00371     // If type == yearly and month is given, pos is still in month not year!
00372     // TODO_Recurrence: Correct handling of n-th  BYDAY...
00373     int maxloop = 53;
00374     bool inMonth = ( type == rMonthly) || ( type == rYearly && month > 0 );
00375     if ( inMonth && month > 0 ) {
00376       dt = QDate( year, month, 1 );
00377       maxloop = 5;
00378     }
00379     if ( weekdaynr < 0 ) {
00380       // From end of period (month, year) => relative to begin of next period
00381       if ( inMonth )
00382         dt = dt.addMonths( 1 );
00383       else
00384         dt = dt.addYears( 1 );
00385     }
00386     int adj = ( 7 + weekday - dt.dayOfWeek() ) % 7;
00387     dt = dt.addDays( adj ); // correct first weekday of the period
00388 
00389     if ( weekdaynr > 0 ) {
00390       dt = dt.addDays( ( weekdaynr - 1 ) * 7 );
00391       result.append( QDateTime( dt, tm ) );
00392     } else if ( weekdaynr < 0 ) {
00393       dt = dt.addDays( weekdaynr * 7 );
00394       result.append( QDateTime( dt, tm ) );
00395     } else {
00396       // loop through all possible weeks, non-matching will be filtered later
00397       for ( int i = 0; i < maxloop; ++i ) {
00398         result.append( QDateTime( dt, tm ) );
00399         dt = dt.addDays( 7 );
00400       }
00401     }
00402   } // weekday != 0
00403 
00404 
00405   // Only use those times that really match all other constraints, too
00406   DateTimeList valid;
00407   DateTimeList::Iterator it;
00408   for ( it = result.begin(); it != result.end(); ++it ) {
00409     if ( matches( *it, type ) ) valid.append( *it );
00410   }
00411   // Don't sort it here, would be unnecessary work. The results from all
00412   // constraints will be merged to one big list of the interval. Sort that one!
00413   return valid;
00414 }
00415 
00416 
00417 bool RecurrenceRule::Constraint::increase( RecurrenceRule::PeriodType type, int freq )
00418 {
00419   // convert the first day of the interval to QDateTime
00420   // Sub-daily types need to be converted to UTC to correctly handle DST shifts
00421   QDateTime dt( intervalDateTime( type ) );
00422 
00423   // Now add the intervals
00424   switch ( type ) {
00425     case rSecondly:
00426       dt = dt.addSecs( freq ); break;
00427     case rMinutely:
00428       dt = dt.addSecs( 60*freq ); break;
00429     case rHourly:
00430       dt = dt.addSecs( 3600 * freq ); break;
00431     case rDaily:
00432       dt = dt.addDays( freq ); break;
00433     case rWeekly:
00434       dt = dt.addDays( 7*freq ); break;
00435     case rMonthly:
00436       dt = dt.addMonths( freq ); break;
00437     case rYearly:
00438       dt = dt.addYears( freq ); break;
00439     default:
00440       break;
00441   }
00442   // Convert back from QDateTime to the Constraint class
00443   readDateTime( dt, type );
00444 
00445   return true;
00446 }
00447 
00448 bool RecurrenceRule::Constraint::readDateTime( const QDateTime &preDate, PeriodType type )
00449 {
00450   clear();
00451   switch ( type ) {
00452     // Really fall through! Only weekly needs to be treated differentely!
00453     case rSecondly:
00454       second = preDate.time().second();
00455     case rMinutely:
00456       minute = preDate.time().minute();
00457     case rHourly:
00458       hour = preDate.time().hour();
00459     case rDaily:
00460       day = preDate.date().day();
00461     case rMonthly:
00462       month = preDate.date().month();
00463     case rYearly:
00464       year = preDate.date().year();
00465       break;
00466 
00467     case rWeekly:
00468       // Determine start day of the current week, calculate the week number from that
00469       weeknumber = DateHelper::getWeekNumber( preDate.date(), weekstart, &year );
00470       break;
00471     default:
00472       break;
00473   }
00474   return true;
00475 }
00476 
00477 
00478 RecurrenceRule::RecurrenceRule( )
00479 : mPeriod( rNone ), mFrequency( 0 ), mIsReadOnly( false ),
00480   mFloating( false ),
00481   mWeekStart(1)
00482 {
00483 }
00484 
00485 RecurrenceRule::RecurrenceRule( const RecurrenceRule &r )
00486 {
00487   mRRule = r.mRRule;
00488   mPeriod = r.mPeriod;
00489   mDateStart = r.mDateStart;
00490   mDuration = r.mDuration;
00491   mDateEnd = r.mDateEnd;
00492   mFrequency = r.mFrequency;
00493 
00494   mIsReadOnly = r.mIsReadOnly;
00495   mFloating = r.mFloating;
00496 
00497   mBySeconds = r.mBySeconds;
00498   mByMinutes = r.mByMinutes;
00499   mByHours = r.mByHours;
00500   mByDays = r.mByDays;
00501   mByMonthDays = r.mByMonthDays;
00502   mByYearDays = r.mByYearDays;
00503   mByWeekNumbers = r.mByWeekNumbers;
00504   mByMonths = r.mByMonths;
00505   mBySetPos = r.mBySetPos;
00506   mWeekStart = r.mWeekStart;
00507 
00508   setDirty();
00509 }
00510 
00511 RecurrenceRule::~RecurrenceRule()
00512 {
00513 }
00514 
00515 bool RecurrenceRule::operator==( const RecurrenceRule& r ) const
00516 {
00517   if ( mPeriod != r.mPeriod ) return false;
00518   if ( mDateStart != r.mDateStart ) return false;
00519   if ( mDuration != r.mDuration ) return false;
00520   if ( mDateEnd != r.mDateEnd ) return false;
00521   if ( mFrequency != r.mFrequency ) return false;
00522 
00523   if ( mIsReadOnly != r.mIsReadOnly ) return false;
00524   if ( mFloating != r.mFloating ) return false;
00525 
00526   if ( mBySeconds != r.mBySeconds ) return false;
00527   if ( mByMinutes != r.mByMinutes ) return false;
00528   if ( mByHours != r.mByHours ) return false;
00529   if ( mByDays != r.mByDays ) return false;
00530   if ( mByMonthDays != r.mByMonthDays ) return false;
00531   if ( mByYearDays != r.mByYearDays ) return false;
00532   if ( mByWeekNumbers != r.mByWeekNumbers ) return false;
00533   if ( mByMonths != r.mByMonths ) return false;
00534   if ( mBySetPos != r.mBySetPos ) return false;
00535   if ( mWeekStart != r.mWeekStart ) return false;
00536 
00537   return true;
00538 }
00539 
00540 void RecurrenceRule::addObserver( Observer *observer )
00541 {
00542   if ( !mObservers.contains( observer ) )
00543     mObservers.append( observer );
00544 }
00545 
00546 void RecurrenceRule::removeObserver( Observer *observer )
00547 {
00548   if ( mObservers.contains( observer ) )
00549     mObservers.remove( observer );
00550 }
00551 
00552 
00553 
00554 void RecurrenceRule::setRecurrenceType( PeriodType period )
00555 {
00556   if ( isReadOnly() ) return;
00557   mPeriod = period;
00558   setDirty();
00559 }
00560 
00561 QDateTime RecurrenceRule::endDt( bool *result ) const
00562 {
00563   if ( result ) *result = false;
00564   if ( mPeriod == rNone ) return QDateTime();
00565   if ( mDuration < 0 ) return QDateTime();
00566   if ( mDuration == 0 ) {
00567     if ( result ) *result = true;
00568     return mDateEnd;
00569   }
00570   // N occurrences. Check if we have a full cache. If so, return the cached end date.
00571   if ( ! mCached ) {
00572     // If not enough occurrences can be found (i.e. inconsistent constraints)
00573     if ( !buildCache() ) return QDateTime();
00574   }
00575   if ( result ) *result = true;
00576   return mCachedDateEnd;
00577 }
00578 
00579 void RecurrenceRule::setEndDt( const QDateTime &dateTime )
00580 {
00581   if ( isReadOnly() ) return;
00582   mDateEnd = dateTime;
00583   mDuration = 0; // set to 0 because there is an end date/time
00584   setDirty();
00585 }
00586 
00587 void RecurrenceRule::setDuration(int duration)
00588 {
00589   if ( isReadOnly() ) return;
00590   mDuration = duration;
00591   setDirty();
00592 }
00593 
00594 void RecurrenceRule::setFloats( bool floats )
00595 {
00596   if ( isReadOnly() ) return;
00597   mFloating = floats;
00598   setDirty();
00599 }
00600 
00601 void RecurrenceRule::clear()
00602 {
00603   if ( isReadOnly() ) return;
00604   mPeriod = rNone;
00605   mBySeconds.clear();
00606   mByMinutes.clear();
00607   mByHours.clear();
00608   mByDays.clear();
00609   mByMonthDays.clear();
00610   mByYearDays.clear();
00611   mByWeekNumbers.clear();
00612   mByMonths.clear();
00613   mBySetPos.clear();
00614   mWeekStart = 1;
00615 
00616   setDirty();
00617 }
00618 
00619 void RecurrenceRule::setDirty()
00620 {
00621   mConstraints.clear();
00622   buildConstraints();
00623   mDirty = true;
00624   mCached = false;
00625   mCachedDates.clear();
00626   for ( QValueList<Observer*>::ConstIterator it = mObservers.begin();
00627         it != mObservers.end(); ++it ) {
00628     if ( (*it) ) (*it)->recurrenceChanged( this );
00629   }
00630 }
00631 
00632 void RecurrenceRule::setStartDt( const QDateTime &start )
00633 {
00634   if ( isReadOnly() ) return;
00635   mDateStart = start;
00636   setDirty();
00637 }
00638 
00639 void RecurrenceRule::setFrequency(int freq)
00640 {
00641   if ( isReadOnly() || freq <= 0 ) return;
00642   mFrequency = freq;
00643   setDirty();
00644 }
00645 
00646 void RecurrenceRule::setBySeconds( const QValueList<int> bySeconds )
00647 {
00648   if ( isReadOnly() ) return;
00649   mBySeconds = bySeconds;
00650   setDirty();
00651 }
00652 
00653 void RecurrenceRule::setByMinutes( const QValueList<int> byMinutes )
00654 {
00655   if ( isReadOnly() ) return;
00656   mByMinutes = byMinutes;
00657   setDirty();
00658 }
00659 
00660 void RecurrenceRule::setByHours( const QValueList<int> byHours )
00661 {
00662   if ( isReadOnly() ) return;
00663   mByHours = byHours;
00664   setDirty();
00665 }
00666 
00667 
00668 void RecurrenceRule::setByDays( const QValueList<WDayPos> byDays )
00669 {
00670   if ( isReadOnly() ) return;
00671   mByDays = byDays;
00672   setDirty();
00673 }
00674 
00675 void RecurrenceRule::setByMonthDays( const QValueList<int> byMonthDays )
00676 {
00677   if ( isReadOnly() ) return;
00678   mByMonthDays = byMonthDays;
00679   setDirty();
00680 }
00681 
00682 void RecurrenceRule::setByYearDays( const QValueList<int> byYearDays )
00683 {
00684   if ( isReadOnly() ) return;
00685   mByYearDays = byYearDays;
00686   setDirty();
00687 }
00688 
00689 void RecurrenceRule::setByWeekNumbers( const QValueList<int> byWeekNumbers )
00690 {
00691   if ( isReadOnly() ) return;
00692   mByWeekNumbers = byWeekNumbers;
00693   setDirty();
00694 }
00695 
00696 void RecurrenceRule::setByMonths( const QValueList<int> byMonths )
00697 {
00698   if ( isReadOnly() ) return;
00699   mByMonths = byMonths;
00700   setDirty();
00701 }
00702 
00703 void RecurrenceRule::setBySetPos( const QValueList<int> bySetPos )
00704 {
00705   if ( isReadOnly() ) return;
00706   mBySetPos = bySetPos;
00707   setDirty();
00708 }
00709 
00710 void RecurrenceRule::setWeekStart( short weekStart )
00711 {
00712   if ( isReadOnly() ) return;
00713   mWeekStart = weekStart;
00714   setDirty();
00715 }
00716 
00717 
00718 
00719 // Taken from recurrence.cpp
00720 // int RecurrenceRule::maxIterations() const
00721 // {
00722 //   /* Find the maximum number of iterations which may be needed to reach the
00723 //    * next actual occurrence of a monthly or yearly recurrence.
00724 //    * More than one iteration may be needed if, for example, it's the 29th February,
00725 //    * the 31st day of the month or the 5th Monday, and the month being checked is
00726 //    * February or a 30-day month.
00727 //    * The following recurrences may never occur:
00728 //    * - For rMonthlyDay: if the frequency is a whole number of years.
00729 //    * - For rMonthlyPos: if the frequency is an even whole number of years.
00730 //    * - For rYearlyDay, rYearlyMonth: if the frequeny is a multiple of 4 years.
00731 //    * - For rYearlyPos: if the frequency is an even number of years.
00732 //    * The maximum number of iterations needed, assuming that it does actually occur,
00733 //    * was found empirically.
00734 //    */
00735 //   switch (recurs) {
00736 //     case rMonthlyDay:
00737 //       return (rFreq % 12) ? 6 : 8;
00738 //
00739 //     case rMonthlyPos:
00740 //       if (rFreq % 12 == 0) {
00741 //         // Some of these frequencies may never occur
00742 //         return (rFreq % 84 == 0) ? 364         // frequency = multiple of 7 years
00743 //              : (rFreq % 48 == 0) ? 7           // frequency = multiple of 4 years
00744 //              : (rFreq % 24 == 0) ? 14 : 28;    // frequency = multiple of 2 or 1 year
00745 //       }
00746 //       // All other frequencies will occur sometime
00747 //       if (rFreq > 120)
00748 //         return 364;    // frequencies of > 10 years will hit the date limit first
00749 //       switch (rFreq) {
00750 //         case 23:   return 50;
00751 //         case 46:   return 38;
00752 //         case 56:   return 138;
00753 //         case 66:   return 36;
00754 //         case 89:   return 54;
00755 //         case 112:  return 253;
00756 //         default:   return 25;       // most frequencies will need < 25 iterations
00757 //       }
00758 //
00759 //     case rYearlyMonth:
00760 //     case rYearlyDay:
00761 //       return 8;          // only 29th Feb or day 366 will need more than one iteration
00762 //
00763 //     case rYearlyPos:
00764 //       if (rFreq % 7 == 0)
00765 //         return 364;    // frequencies of a multiple of 7 years will hit the date limit first
00766 //       if (rFreq % 2 == 0) {
00767 //         // Some of these frequencies may never occur
00768 //         return (rFreq % 4 == 0) ? 7 : 14;    // frequency = even number of years
00769 //       }
00770 //       return 28;
00771 //   }
00772 //   return 1;
00773 // }
00774 
00775 void RecurrenceRule::buildConstraints()
00776 {
00777   mConstraints.clear();
00778   Constraint con;
00779   if ( mWeekStart > 0 ) con.weekstart = mWeekStart;
00780   mConstraints.append( con );
00781 
00782   Constraint::List tmp;
00783   Constraint::List::const_iterator it;
00784   QValueList<int>::const_iterator intit;
00785 
00786   #define intConstraint( list, element ) \
00787   if ( !list.isEmpty() ) { \
00788     for ( it = mConstraints.constBegin(); it != mConstraints.constEnd(); ++it ) { \
00789       for ( intit = list.constBegin(); intit != list.constEnd(); ++intit ) { \
00790         con = (*it); \
00791         con.element = (*intit); \
00792         tmp.append( con ); \
00793       } \
00794     } \
00795     mConstraints = tmp; \
00796     tmp.clear(); \
00797   }
00798 
00799   intConstraint( mBySeconds, second );
00800   intConstraint( mByMinutes, minute );
00801   intConstraint( mByHours, hour );
00802   intConstraint( mByMonthDays, day );
00803   intConstraint( mByMonths, month );
00804   intConstraint( mByYearDays, yearday );
00805   intConstraint( mByWeekNumbers, weeknumber );
00806   #undef intConstraint
00807 
00808   if ( !mByDays.isEmpty() ) {
00809     for ( it = mConstraints.constBegin(); it != mConstraints.constEnd(); ++it ) {
00810       QValueList<WDayPos>::const_iterator dayit;
00811       for ( dayit = mByDays.constBegin(); dayit != mByDays.constEnd(); ++dayit ) {
00812         con = (*it);
00813         con.weekday = (*dayit).day();
00814         con.weekdaynr = (*dayit).pos();
00815         tmp.append( con );
00816       }
00817     }
00818     mConstraints = tmp;
00819     tmp.clear();
00820   }
00821 
00822   #define fixConstraint( element, value ) \
00823   { \
00824     tmp.clear(); \
00825     for ( it = mConstraints.constBegin(); it != mConstraints.constEnd(); ++it ) { \
00826       con = (*it); con.element = value; tmp.append( con ); \
00827     } \
00828     mConstraints = tmp; \
00829   }
00830   // Now determine missing values from DTSTART. This can speed up things,
00831   // because we have more restrictions and save some loops.
00832 
00833   // TODO: Does RFC 2445 intend to restrict the weekday in all cases of weekly?
00834   if ( mPeriod == rWeekly && mByDays.isEmpty() ) {
00835     fixConstraint( weekday, mDateStart.date().dayOfWeek() );
00836   }
00837 
00838   // Really fall through in the cases, because all smaller time intervals are
00839   // constrained from dtstart
00840   switch ( mPeriod ) {
00841     case rYearly:
00842       if ( mByDays.isEmpty() && mByWeekNumbers.isEmpty() && mByYearDays.isEmpty() && mByMonths.isEmpty() ) {
00843         fixConstraint( month, mDateStart.date().month() );
00844       }
00845     case rMonthly:
00846       if ( mByDays.isEmpty() && mByWeekNumbers.isEmpty() && mByYearDays.isEmpty() && mByMonthDays.isEmpty() ) {
00847         fixConstraint( day, mDateStart.date().day() );
00848       }
00849 
00850     case rWeekly:
00851     case rDaily:
00852       if ( mByHours.isEmpty() ) {
00853         fixConstraint( hour, mDateStart.time().hour() );
00854       }
00855     case rHourly:
00856       if ( mByMinutes.isEmpty() ) {
00857         fixConstraint( minute, mDateStart.time().minute() );
00858       }
00859     case rMinutely:
00860       if ( mBySeconds.isEmpty() ) {
00861         fixConstraint( second, mDateStart.time().second() );
00862       }
00863     case rSecondly:
00864     default:
00865       break;
00866   }
00867   #undef fixConstraint
00868 
00869   Constraint::List::Iterator conit = mConstraints.begin();
00870   while ( conit != mConstraints.end() ) {
00871     if ( (*conit).isConsistent( mPeriod ) ) {
00872       ++conit;
00873     } else {
00874       conit = mConstraints.remove( conit );
00875     }
00876   }
00877 }
00878 
00879 bool RecurrenceRule::buildCache() const
00880 {
00881 kdDebug(5800) << "         RecurrenceRule::buildCache: " << endl;
00882   // Build the list of all occurrences of this event (we need that to determine
00883   // the end date!)
00884   Constraint interval( getNextValidDateInterval( startDt(), recurrenceType() ) );
00885   QDateTime next;
00886 
00887   DateTimeList dts = datesForInterval( interval, recurrenceType() );
00888   DateTimeList::Iterator it = dts.begin();
00889   // Only use dates after the event has started (start date is only included
00890   // if it matches)
00891   while ( it != dts.end() ) {
00892     if ( (*it) < startDt() ) it =  dts.remove( it );
00893     else ++it;
00894   }
00895 //  dts.prepend( startDt() ); // the start date is always the first occurrence
00896 
00897 
00898   int loopnr = 0;
00899   int dtnr = dts.count();
00900   // some validity checks to avoid infinite loops (i.e. if we have
00901   // done this loop already 10000 times and found no occurrence, bail out )
00902   while ( loopnr < 10000 && dtnr < mDuration ) {
00903     interval.increase( recurrenceType(), frequency() );
00904     // The returned date list is already sorted!
00905     dts += datesForInterval( interval, recurrenceType() );
00906     dtnr = dts.count();
00907     ++loopnr;
00908   }
00909   if ( int(dts.count()) > mDuration ) {
00910     // we have picked up more occurrences than necessary, remove them
00911     it = dts.at( mDuration );
00912     while ( it != dts.end() ) it = dts.remove( it );
00913   }
00914   mCached = true;
00915   mCachedDates = dts;
00916 
00917 kdDebug(5800) << "    Finished Building Cache, cache has " << dts.count() << " entries:" << endl;
00918 // it = dts.begin();
00919 // while ( it != dts.end() ) {
00920 //   kdDebug(5800) << "            -=> " << (*it) << endl;
00921 //   ++it;
00922 // }
00923   if ( int(dts.count()) == mDuration ) {
00924     mCachedDateEnd = dts.last();
00925     return true;
00926   } else {
00927     mCachedDateEnd = QDateTime();
00928     return false;
00929   }
00930 }
00931 
00932 bool RecurrenceRule::dateMatchesRules( const QDateTime &qdt ) const
00933 {
00934   bool match = false;
00935   for ( Constraint::List::ConstIterator it = mConstraints.begin();
00936         it!=mConstraints.end(); ++it ) {
00937     match = match || ( (*it).matches( qdt, recurrenceType() ) );
00938   }
00939   return match;
00940 }
00941 
00942 bool RecurrenceRule::recursOn( const QDate &qd ) const
00943 {
00944 //  kdDebug(5800) << "         RecurrenceRule::recursOn: " << qd << endl;
00945   if ( qd < startDt().date() ) return false;
00946   // Start date is only included if it really matches
00947 //   if ( qd == startDt().date() ) return true;
00948   if ( mDuration >= 0 && qd > endDt().date() ) return false;
00949 
00950   // The date must be in an appropriate interval (getNextValidDateInterval),
00951   // Plus it must match at least one of the constraints
00952   bool match = false;
00953   for ( Constraint::List::ConstIterator it = mConstraints.begin();
00954         it!=mConstraints.end(); ++it ) {
00955     match = match  || ( (*it).matches( qd, recurrenceType() ) );
00956   }
00957   if ( !match ) return false;
00958   QDateTime tmp( qd, QTime( 0, 0, 0 ) );
00959   Constraint interval( getNextValidDateInterval( tmp, recurrenceType() ) );
00960   // Constraint::matches is quite efficient, so first check if it can occur at
00961   // all before we calculate all actual dates.
00962   if ( !interval.matches( qd, recurrenceType() ) ) return false;
00963   // We really need to obtain the list of dates in this interval, since
00964   // otherwise BYSETPOS will not work (i.e. the date will match the interval,
00965   // but BYSETPOS selects only one of these matching dates!
00966   DateTimeList times = datesForInterval( interval, recurrenceType() );
00967   DateTimeList::ConstIterator it = times.begin();
00968   while ( ( it != times.end() ) && ( (*it).date() < qd ) )
00969     ++it;
00970   if ( it != times.end() ) {
00971     // If we are beyond the end...
00972     if ( mDuration >= 0 && (*it) > endDt() )
00973       return false;
00974     if ( (*it).date() == qd )
00975       return true;
00976   }
00977   return false;
00978 }
00979 
00980 
00981 bool RecurrenceRule::recursAt( const QDateTime &qd ) const
00982 {
00983 // kdDebug(5800) << "         RecurrenceRule::recursAt: " << qd << endl;
00984   if ( doesFloat() ) return recursOn( qd.date() );
00985   if ( qd < startDt() ) return false;
00986   // Start date is only included if it really matches
00987 //   if ( qd == startDt() ) return true;
00988   if ( mDuration >= 0 && qd > endDt() ) return false;
00989 
00990   // The date must be in an appropriate interval (getNextValidDateInterval),
00991   // Plus it must match at least one of the constraints
00992   bool match = dateMatchesRules( qd );
00993   if ( !match ) return false;
00994   // if it recurs every interval, speed things up...
00995 //   if ( mFrequency == 1 && mBySetPos.isEmpty() && mByDays.isEmpty() ) return true;
00996   Constraint interval( getNextValidDateInterval( qd, recurrenceType() ) );
00997   // TODO_Recurrence: Does this work with BySetPos???
00998   if ( interval.matches( qd, recurrenceType() ) ) return true;
00999 
01000   return false;
01001 }
01002 
01003 
01004 TimeList RecurrenceRule::recurTimesOn( const QDate &date ) const
01005 {
01006 // kdDebug(5800) << "         RecurrenceRule::recurTimesOn: " << date << endl;
01007   TimeList lst;
01008   if ( !recursOn( date ) ) return lst;
01009 
01010   if ( doesFloat() ) return lst;
01011 
01012   QDateTime dt( date, QTime( 0, 0, 0 ) );
01013   bool valid = dt.isValid() && ( dt.date() == date );
01014   while ( valid ) {
01015     // TODO: Add a flag so that the date is never increased!
01016     dt = getNextDate( dt );
01017     valid = dt.isValid() && ( dt.date() == date );
01018     if ( valid ) lst.append( dt.time() );
01019   }
01020   return lst;
01021 }
01022 
01024 int RecurrenceRule::durationTo( const QDateTime &dt ) const
01025 {
01026 // kdDebug(5800) << "         RecurrenceRule::durationTo: " << dt << endl;
01027   // Easy cases: either before start, or after all recurrences and we know
01028   // their number
01029   if ( dt < startDt() ) return 0;
01030   // Start date is only included if it really matches
01031 //   if ( dt == startDt() ) return 1;
01032   if ( mDuration > 0 && dt >= endDt() ) return mDuration;
01033 
01034   QDateTime next( startDt() );
01035   int found = 0;
01036   while ( next.isValid() && next <= dt ) {
01037     ++found;
01038     next = getNextDate( next );
01039   }
01040   return found;
01041 }
01042 
01043 
01044 QDateTime RecurrenceRule::getPreviousDate( const QDateTime& afterDate ) const
01045 {
01046 // kdDebug(5800) << "         RecurrenceRule::getPreviousDate: " << afterDate << endl;
01047   // Beyond end of recurrence
01048   if ( afterDate < startDt() )
01049     return QDateTime();
01050 
01051   // If we have a cache (duration given), use that
01052   QDateTime prev;
01053   if ( mDuration > 0 ) {
01054     if ( !mCached ) buildCache();
01055     DateTimeList::ConstIterator it = mCachedDates.begin();
01056     while ( it != mCachedDates.end() && (*it) < afterDate ) {
01057       prev = *it;
01058       ++it;
01059     }
01060     if ( prev.isValid() && prev < afterDate ) return prev;
01061     else return QDateTime();
01062   }
01063 
01064 // kdDebug(5800) << "    getNext date after " << preDate << endl;
01065   prev = afterDate;
01066   if ( mDuration >= 0 && endDt().isValid() && afterDate > endDt() )
01067     prev = endDt().addSecs( 1 );
01068 
01069   Constraint interval( getPreviousValidDateInterval( prev, recurrenceType() ) );
01070 // kdDebug(5800) << "Previous Valid Date Interval for date " << prev << ": " << endl;
01071 // interval.dump();
01072   DateTimeList dts = datesForInterval( interval, recurrenceType() );
01073   DateTimeList::Iterator dtit = dts.end();
01074   if ( dtit != dts.begin() ) {
01075     do {
01076       --dtit;
01077     } while ( dtit != dts.begin() && (*dtit) >= prev );
01078     if ( (*dtit) < prev ) {
01079       if ( (*dtit) >= startDt() ) return (*dtit);
01080       else return QDateTime();
01081     }
01082   }
01083 
01084   // Previous interval. As soon as we find an occurrence, we're done.
01085   while ( interval.intervalDateTime( recurrenceType() ) > startDt() ) {
01086     interval.increase( recurrenceType(), -frequency() );
01087 // kdDebug(5800) << "Decreased interval: " << endl;
01088 // interval.dump();
01089     // The returned date list is sorted
01090     DateTimeList dts = datesForInterval( interval, recurrenceType() );
01091     // The list is sorted, so take the last one.
01092     if ( dts.count() > 0 ) {
01093       prev = dts.last();
01094       if ( prev.isValid() && prev >= startDt() ) return prev;
01095       else return QDateTime();
01096     }
01097   }
01098   return QDateTime();
01099 }
01100 
01101 
01102 QDateTime RecurrenceRule::getNextDate( const QDateTime &preDate ) const
01103 {
01104 // kdDebug(5800) << "         RecurrenceRule::getNextDate: " << preDate << endl;
01105   // Beyond end of recurrence
01106   if ( mDuration >= 0 && endDt().isValid() && preDate >= endDt() )
01107     return QDateTime();
01108 
01109   // Start date is only included if it really matches
01110   QDateTime adjustedPreDate;
01111   if ( preDate < startDt() )
01112     adjustedPreDate = startDt().addSecs( -1 );
01113   else
01114     adjustedPreDate = preDate;
01115 
01116   if ( mDuration > 0 ) {
01117     if ( !mCached ) buildCache();
01118     DateTimeList::ConstIterator it = mCachedDates.begin();
01119     while ( it != mCachedDates.end() && (*it) <= adjustedPreDate ) ++it;
01120     if ( it != mCachedDates.end() ) {
01121 //  kdDebug(5800) << "    getNext date after " << adjustedPreDate << ", cached date: " << *it << endl;
01122       return (*it);
01123     }
01124   }
01125 
01126 // kdDebug(5800) << "    getNext date after " << adjustedPreDate << endl;
01127   Constraint interval( getNextValidDateInterval( adjustedPreDate, recurrenceType() ) );
01128   DateTimeList dts = datesForInterval( interval, recurrenceType() );
01129   DateTimeList::Iterator dtit = dts.begin();
01130   while ( dtit != dts.end() && (*dtit) <= adjustedPreDate ) ++dtit;
01131   if ( dtit != dts.end() ) {
01132     if ( mDuration >= 0 && (*dtit) > endDt() ) return QDateTime();
01133     else return (*dtit);
01134   }
01135 
01136   // Increase the interval. The first occurrence that we find is the result (if
01137   // if's before the end date).
01138     // TODO: some validity checks to avoid infinite loops for contradictory constraints
01139   int loopnr = 0;
01140   while ( loopnr < 10000 ) {
01141     interval.increase( recurrenceType(), frequency() );
01142     DateTimeList dts = datesForInterval( interval, recurrenceType() );
01143     if ( dts.count() > 0 ) {
01144       QDateTime ret( dts.first() );
01145       if ( mDuration >= 0 && ret > endDt() ) return QDateTime();
01146       else return ret;
01147     }
01148     ++loopnr;
01149   }
01150   return QDateTime();
01151 }
01152 
01153 RecurrenceRule::Constraint RecurrenceRule::getPreviousValidDateInterval( const QDateTime &preDate, PeriodType type ) const
01154 {
01155 // kdDebug(5800) << "       (o) getPreviousValidDateInterval after " << preDate << ", type=" << type << endl;
01156   long periods = 0;
01157   QDateTime nextValid = startDt();
01158   QDateTime start = startDt();
01159   int modifier = 1;
01160   QDateTime toDate( preDate );
01161   // for super-daily recurrences, don't care about the time part
01162 
01163   // Find the #intervals since the dtstart and round to the next multiple of
01164   // the frequency
01165       // FIXME: All sub-daily periods need to convert to UTC, do the calculations
01166       //        in UTC, then convert back to the local time zone. Otherwise,
01167       //        recurrences across DST changes will be determined wrongly
01168   switch ( type ) {
01169     // Really fall through for sub-daily, since the calculations only differ
01170     // by the factor 60 and 60*60! Same for weekly and daily (factor 7)
01171     case rHourly:   modifier *= 60;
01172     case rMinutely: modifier *= 60;
01173     case rSecondly:
01174         periods = ownSecsTo( start, toDate ) / modifier;
01175         // round it down to the next lower multiple of frequency():
01176         periods = ( periods / frequency() ) * frequency();
01177         nextValid = start.addSecs( modifier * periods );
01178         break;
01179 
01180     case rWeekly:
01181         toDate = toDate.addDays( -(7 + toDate.date().dayOfWeek() - mWeekStart) % 7 );
01182         start = start.addDays( -(7 + start.date().dayOfWeek() - mWeekStart) % 7 );
01183         modifier *= 7;
01184     case rDaily:
01185         periods = start.daysTo( toDate ) / modifier;
01186         // round it down to the next lower multiple of frequency():
01187         periods = ( periods / frequency() ) * frequency();
01188         nextValid = start.addDays( modifier * periods );
01189         break;
01190 
01191     case rMonthly: {
01192         periods = 12*( toDate.date().year() - start.date().year() ) +
01193              ( toDate.date().month() - start.date().month() );
01194         // round it down to the next lower multiple of frequency():
01195         periods = ( periods / frequency() ) * frequency();
01196         // set the day to the first day of the month, so we don't have problems
01197         // with non-existent days like Feb 30 or April 31
01198         start.setDate( QDate( start.date().year(), start.date().month(), 1 ) );
01199         nextValid.setDate( start.date().addMonths( periods ) );
01200         break; }
01201     case rYearly:
01202         periods = ( toDate.date().year() - start.date().year() );
01203         // round it down to the next lower multiple of frequency():
01204         periods = ( periods / frequency() ) * frequency();
01205         nextValid.setDate( start.date().addYears( periods ) );
01206         break;
01207     default:
01208         break;
01209   }
01210 // kdDebug(5800) << "    ~~~> date in previous interval is: : " << nextValid << endl;
01211 
01212   return Constraint( nextValid, type, mWeekStart );
01213 }
01214 
01215 RecurrenceRule::Constraint RecurrenceRule::getNextValidDateInterval( const QDateTime &preDate, PeriodType type ) const
01216 {
01217   // TODO: Simplify this!
01218   kdDebug(5800) << "       (o) getNextValidDateInterval after " << preDate << ", type=" << type << endl;
01219   long periods = 0;
01220   QDateTime start = startDt();
01221   QDateTime nextValid( start );
01222   int modifier = 1;
01223   QDateTime toDate( preDate );
01224   // for super-daily recurrences, don't care about the time part
01225 
01226   // Find the #intervals since the dtstart and round to the next multiple of
01227   // the frequency
01228       // FIXME: All sub-daily periods need to convert to UTC, do the calculations
01229       //        in UTC, then convert back to the local time zone. Otherwise,
01230       //        recurrences across DST changes will be determined wrongly
01231   switch ( type ) {
01232     // Really fall through for sub-daily, since the calculations only differ
01233     // by the factor 60 and 60*60! Same for weekly and daily (factor 7)
01234     case rHourly:   modifier *= 60;
01235     case rMinutely: modifier *= 60;
01236     case rSecondly:
01237         periods = ownSecsTo( start, toDate ) / modifier;
01238         periods = QMAX( 0, periods);
01239         if ( periods > 0 )
01240           periods += ( frequency() - 1 - ( (periods - 1) % frequency() ) );
01241         nextValid = start.addSecs( modifier * periods );
01242         break;
01243 
01244     case rWeekly:
01245         // correct both start date and current date to start of week
01246         toDate = toDate.addDays( -(7 + toDate.date().dayOfWeek() - mWeekStart) % 7 );
01247         start = start.addDays( -(7 + start.date().dayOfWeek() - mWeekStart) % 7 );
01248         modifier *= 7;
01249     case rDaily:
01250         periods = start.daysTo( toDate ) / modifier;
01251         periods = QMAX( 0, periods);
01252         if ( periods > 0 )
01253           periods += (frequency() - 1 - ( (periods - 1) % frequency() ) );
01254         nextValid = start.addDays( modifier * periods );
01255         break;
01256 
01257     case rMonthly: {
01258         periods = 12*( toDate.date().year() - start.date().year() ) +
01259              ( toDate.date().month() - start.date().month() );
01260         periods = QMAX( 0, periods);
01261         if ( periods > 0 )
01262           periods += (frequency() - 1 - ( (periods - 1) % frequency() ) );
01263         // set the day to the first day of the month, so we don't have problems
01264         // with non-existent days like Feb 30 or April 31
01265         start.setDate( QDate( start.date().year(), start.date().month(), 1 ) );
01266         nextValid.setDate( start.date().addMonths( periods ) );
01267         break; }
01268     case rYearly:
01269         periods = ( toDate.date().year() - start.date().year() );
01270         periods = QMAX( 0, periods);
01271         if ( periods > 0 )
01272           periods += ( frequency() - 1 - ( (periods - 1) % frequency() ) );
01273         nextValid.setDate( start.date().addYears( periods ) );
01274         break;
01275     default:
01276         break;
01277   }
01278 // kdDebug(5800) << "    ~~~> date in next interval is: : " << nextValid << endl;
01279 
01280   return Constraint( nextValid, type, mWeekStart );
01281 }
01282 
01283 bool RecurrenceRule::mergeIntervalConstraint( Constraint *merged,
01284           const Constraint &conit, const Constraint &interval ) const
01285 {
01286   Constraint result( interval );
01287 
01288 #define mergeConstraint( name, cmparison ) \
01289   if ( conit.name cmparison ) { \
01290     if ( !(result.name cmparison) || result.name == conit.name ) { \
01291       result.name = conit.name; \
01292     } else return false;\
01293   }
01294 
01295   mergeConstraint( year, > 0 );
01296   mergeConstraint( month, > 0 );
01297   mergeConstraint( day, != 0 );
01298   mergeConstraint( hour, >= 0 );
01299   mergeConstraint( minute, >= 0 );
01300   mergeConstraint( second, >= 0 );
01301 
01302   mergeConstraint( weekday, != 0 );
01303   mergeConstraint( weekdaynr, != 0 );
01304   mergeConstraint( weeknumber, != 0 );
01305   mergeConstraint( yearday, != 0 );
01306 
01307   #undef mergeConstraint
01308   if ( merged ) *merged = result;
01309   return true;
01310 }
01311 
01312 
01313 DateTimeList RecurrenceRule::datesForInterval( const Constraint &interval, PeriodType type ) const
01314 {
01315   /* -) Loop through constraints,
01316      -) merge interval with each constraint
01317      -) if merged constraint is not consistent => ignore that constraint
01318      -) if complete => add that one date to the date list
01319      -) Loop through all missing fields => For each add the resulting
01320   */
01321 // kdDebug(5800) << "         RecurrenceRule::datesForInterval: " << endl;
01322 // interval.dump();
01323   DateTimeList lst;
01324   Constraint::List::ConstIterator conit = mConstraints.begin();
01325   for ( ; conit != mConstraints.end(); ++conit ) {
01326     Constraint merged;
01327     bool mergeok = mergeIntervalConstraint( &merged, *conit, interval );
01328     // If the information is incomplete, we can't use this constraint
01329     if ( merged.year <= 0 || merged.hour < 0 || merged.minute < 0 || merged.second < 0 )
01330       mergeok = false;
01331     if ( mergeok ) {
01332 // kdDebug(5800) << "      -) merged constraint: " << endl;
01333 // merged.dump();
01334       // We have a valid constraint, so get all datetimes that match it andd
01335       // append it to all date/times of this interval
01336       DateTimeList lstnew = merged.dateTimes( type );
01337       lst += lstnew;
01338     }
01339   }
01340   // Sort it so we can apply the BySetPos. Also some logic relies on this being sorted
01341   qSortUnique( lst );
01342 
01343 
01344 /*if ( lst.isEmpty() ) {
01345   kdDebug(5800) << "         No Dates in Interval " << endl;
01346 } else {
01347   kdDebug(5800) << "         Dates: " << endl;
01348   for ( DateTimeList::Iterator it = lst.begin(); it != lst.end(); ++it ) {
01349     kdDebug(5800)<< "              -) " << (*it).toString() << endl;
01350   }
01351   kdDebug(5800) << "       ---------------------" << endl;
01352 }*/
01353   if ( !mBySetPos.isEmpty() ) {
01354     DateTimeList tmplst = lst;
01355     lst.clear();
01356     QValueList<int>::ConstIterator it;
01357     for ( it = mBySetPos.begin(); it != mBySetPos.end(); ++it ) {
01358       int pos = *it;
01359       if ( pos > 0 ) --pos;
01360       if ( pos < 0 ) pos += tmplst.count();
01361       if ( pos >= 0 && uint(pos) < tmplst.count() ) {
01362         lst.append( tmplst[pos] );
01363       }
01364     }
01365     qSortUnique( lst );
01366   }
01367 
01368   return lst;
01369 }
01370 
01371 
01372 void RecurrenceRule::dump() const
01373 {
01374 #ifndef NDEBUG
01375   kdDebug(5800) << "RecurrenceRule::dump():" << endl;
01376   if ( !mRRule.isEmpty() )
01377     kdDebug(5800) << "   RRULE=" << mRRule << endl;
01378   kdDebug(5800) << "   Read-Only: " << isReadOnly() <<
01379                    ", dirty: " << mDirty << endl;
01380 
01381   kdDebug(5800) << "   Period type: " << recurrenceType() << ", frequency: " << frequency() << endl;
01382   kdDebug(5800) << "   #occurrences: " << duration() << endl;
01383   kdDebug(5800) << "   start date: " << startDt() <<", end date: " << endDt() << endl;
01384 
01385 
01386 #define dumpByIntList(list,label) \
01387   if ( !list.isEmpty() ) {\
01388     QStringList lst;\
01389     for ( QValueList<int>::ConstIterator it = list.begin();\
01390           it != list.end(); ++it ) {\
01391       lst.append( QString::number( *it ) );\
01392     }\
01393     kdDebug(5800) << "   " << label << lst.join(", ") << endl;\
01394   }
01395   dumpByIntList( mBySeconds,    "BySeconds:  " );
01396   dumpByIntList( mByMinutes,    "ByMinutes:  " );
01397   dumpByIntList( mByHours,      "ByHours:    " );
01398   if ( !mByDays.isEmpty() ) {
01399     QStringList lst;
01400     for ( QValueList<WDayPos>::ConstIterator it = mByDays.begin();
01401           it != mByDays.end(); ++it ) {
01402       lst.append( ( ((*it).pos()!=0) ? QString::number( (*it).pos() ) : "" ) +
01403                    DateHelper::dayName( (*it).day() ) );
01404     }
01405     kdDebug(5800) << "   ByDays:     " << lst.join(", ") << endl;
01406   }
01407   dumpByIntList( mByMonthDays,  "ByMonthDays:" );
01408   dumpByIntList( mByYearDays,   "ByYearDays: " );
01409   dumpByIntList( mByWeekNumbers,"ByWeekNr:   " );
01410   dumpByIntList( mByMonths,     "ByMonths:   " );
01411   dumpByIntList( mBySetPos,     "BySetPos:   " );
01412   #undef dumpByIntList
01413 
01414   kdDebug(5800) << "   Week start: " << DateHelper::dayName( mWeekStart ) << endl;
01415 
01416   kdDebug(5800) << "   Constraints:" << endl;
01417   // dump constraints
01418   for ( Constraint::List::ConstIterator it = mConstraints.begin();
01419         it!=mConstraints.end(); ++it ) {
01420     (*it).dump();
01421   }
01422 #endif
01423 }
01424 
01425 void RecurrenceRule::Constraint::dump() const
01426 {
01427   kdDebug(5800) << "     ~> Y="<<year<<", M="<<month<<", D="<<day<<", H="<<hour<<", m="<<minute<<", S="<<second<<", wd="<<weekday<<",#wd="<<weekdaynr<<", #w="<<weeknumber<<", yd="<<yearday<<endl;
01428 }