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