libkcal

recurrencerule.cpp

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