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