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 <tdeglobal.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 TQt 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 TQDateTime 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 = TQMIN( 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 = TQMAX( 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 = TQMAX( 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 = TQMAX( 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 = TQMAX( 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 }