00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022 #include "recurrencerule.h"
00023
00024 #include <KDebug>
00025
00026 #include <QtCore/QStringList>
00027
00028 using namespace KCalCore;
00029
00030
00031 const int LOOP_LIMIT = 10000;
00032
00033 static QString dumpTime( const KDateTime &dt );
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00047
00048
00049
00050
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
00062
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
00079
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
00110 QDate dt( year, 1, 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 );
00125 dt = dt.addDays( -( 7 + dt.dayOfWeek() - weekstart ) % 7 );
00126
00127 int daysto = dt.daysTo( date );
00128 if ( daysto < 0 ) {
00129
00130 --y;
00131 dt = QDate( y, 1, 4 );
00132 dt = dt.addDays( -( 7 + dt.dayOfWeek() - weekstart ) % 7 );
00133 daysto = dt.daysTo( date );
00134 } else if ( daysto > 355 ) {
00135
00136 QDate dtn( y+1, 1, 4 );
00137 dtn = dtn.addDays( -( 7 + dtn.dayOfWeek() - weekstart ) % 7 );
00138 int dayston = dtn.daysTo( date );
00139 if ( dayston >= 0 ) {
00140
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
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
00165
00166
00167
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
00182
00183
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;
00254 int month;
00255 int day;
00256 int hour;
00257 int minute;
00258 int second;
00259 int weekday;
00260 int weekdaynr;
00261 int weeknumber;
00262 int yearday;
00263 int weekstart;
00264 KDateTime::Spec timespec;
00265 bool secondOccurrence;
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
00318
00319
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
00354
00355 if ( ( type == RecurrenceRule::rMonthly ) ||
00356 ( type == RecurrenceRule::rYearly && month > 0 ) ) {
00357
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
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
00389
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 ) const
00404 {
00405
00406 return true;
00407 }
00408
00409
00410
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
00487
00488
00489
00490
00491
00492
00493
00494
00495
00496
00497
00498
00499
00500
00501
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
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
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
00547
00548 if ( !done && yearday != 0 ) {
00549
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
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
00572 if ( !done && weekday != 0 ) {
00573 QDate dt( year, 1, 1 );
00574
00575
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
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 );
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
00602 for ( int i = 0; i < maxloop; ++i ) {
00603 appendDateTime( dt, tm, result );
00604 dt = dt.addDays( 7 );
00605 }
00606 }
00607 }
00608
00609
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
00617
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
00636 intervalDateTime( type );
00637
00638
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
00665 readDateTime( cachedDt, type );
00666 useCachedDt = true;
00667
00668 return true;
00669 }
00670
00671
00672 bool Constraint::readDateTime( const KDateTime &dt, RecurrenceRule::PeriodType type )
00673 {
00674 switch ( type ) {
00675
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
00692 weeknumber = DateHelper::getWeekNumber( dt.date(), weekstart, &year );
00693 break;
00694 default:
00695 break;
00696 }
00697 useCachedDt = false;
00698 return true;
00699 }
00700
00701
00702
00703
00704
00705
00706
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;
00736 PeriodType mPeriod;
00737 KDateTime mDateStart;
00738
00739 uint mFrequency;
00744 int mDuration;
00745 KDateTime mDateEnd;
00746
00747 QList<int> mBySeconds;
00748 QList<int> mByMinutes;
00749 QList<int> mByHours;
00750
00751 QList<WDayPos> mByDays;
00752 QList<int> mByMonthDays;
00753 QList<int> mByYearDays;
00754 QList<int> mByWeekNumbers;
00755 QList<int> mByMonths;
00756 QList<int> mBySetPos;
00757 short mWeekStart;
00758
00759 Constraint::List mConstraints;
00760 QList<RuleObserver*> mObservers;
00761
00762
00763 mutable DateTimeList mCachedDates;
00764 mutable KDateTime mCachedDateEnd;
00765 mutable KDateTime mCachedLastDate;
00766 mutable bool mCached;
00767
00768 bool mIsReadOnly;
00769 bool mAllDay;
00770 bool mNoByRules;
00771 uint mTimedRepetition;
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
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
00893
00894
00895
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
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
00972 if ( !d->mCached ) {
00973
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;
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
01142
01143
01144
01145
01146
01147
01148
01149
01150
01151
01152
01153
01154
01155
01156
01157
01158
01159
01160
01161
01162
01163
01164
01165
01166
01167
01168
01169
01170
01171
01172
01173
01174
01175
01176
01177
01178
01179
01180
01181
01182
01183
01184
01185
01186
01187
01188
01189
01190
01191
01192
01193
01194
01195
01196
01197
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
01264
01265
01266
01267 if ( mPeriod == rWeekly && mByDays.isEmpty() ) {
01268 fixConstraint( setWeekday, mDateStart.date().dayOfWeek() );
01269 }
01270
01271
01272
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
01330
01331 bool RecurrenceRule::Private::buildCache() const
01332 {
01333
01334
01335 Constraint interval( getNextValidDateInterval( mDateStart, mPeriod ) );
01336 QDateTime next;
01337
01338 DateTimeList dts = datesForInterval( interval, mPeriod );
01339
01340
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
01349
01350 while ( loopnr < LOOP_LIMIT && dtnr < mDuration ) {
01351 interval.increase( mPeriod, mFrequency );
01352
01353 dts += datesForInterval( interval, mPeriod );
01354 dtnr = dts.count();
01355 ++loopnr;
01356 }
01357 if ( dts.count() > mDuration ) {
01358
01359 dts.erase( dts.begin() + mDuration, dts.end() );
01360 }
01361 mCached = true;
01362 mCachedDates = dts;
01363
01364
01365
01366
01367
01368
01369 if ( int( dts.count() ) == mDuration ) {
01370 mCachedDateEnd = dts.last();
01371 return true;
01372 } else {
01373
01374 mCachedDateEnd = KDateTime();
01375 mCachedLastDate = interval.intervalDateTime( mPeriod );
01376 return false;
01377 }
01378 }
01379
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
01397
01398 if ( qd < d->mDateStart.date() ) {
01399 return false;
01400 }
01401
01402 QDate endDate;
01403 if ( d->mDuration >= 0 ) {
01404 endDate = endDt().date();
01405 if ( qd > endDate ) {
01406 return false;
01407 }
01408 }
01409
01410
01411
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
01423
01424 if ( !interval.matches( qd, recurrenceType() ) ) {
01425 return false;
01426 }
01427
01428
01429
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
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
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;
01463 }
01464 }
01465 }
01466
01467 if ( d->mTimedRepetition ) {
01468
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
01474 QDate startDay = start.date();
01475 QDate endDay = end.addSecs( -1 ).date();
01476 int dayCount = startDay.daysTo( endDay ) + 1;
01477
01478
01479
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
01493
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
01511
01512
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
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
01537 if ( d->mDuration >= 0 && dt > endDt() ) {
01538 return false;
01539 }
01540
01541 if ( d->mTimedRepetition ) {
01542
01543 return !( d->mDateStart.secsTo_long( dt ) % d->mTimedRepetition );
01544 }
01545
01546
01547
01548 if ( !dateMatchesRules( dt ) ) {
01549 return false;
01550 }
01551
01552
01553 Constraint interval( d->getNextValidDateInterval( dt, recurrenceType() ) );
01554
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 );
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
01580 KDateTime toDate( dt.toTimeSpec( d->mDateStart.timeSpec() ) );
01581
01582
01583 if ( toDate < d->mDateStart ) {
01584 return 0;
01585 }
01586
01587 if ( d->mDuration > 0 && toDate >= endDt() ) {
01588 return d->mDuration;
01589 }
01590
01591 if ( d->mTimedRepetition ) {
01592
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
01607 KDateTime toDate( afterDate.toTimeSpec( d->mDateStart.timeSpec() ) );
01608
01609
01610 if ( !toDate.isValid() || toDate < d->mDateStart ) {
01611 return KDateTime();
01612 }
01613
01614 if ( d->mTimedRepetition ) {
01615
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();
01623 }
01624 prev = prev.addSecs( -n - 1 );
01625 return prev >= d->mDateStart ? prev : KDateTime();
01626 }
01627
01628
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
01653 while ( interval.intervalDateTime( recurrenceType() ) > d->mDateStart ) {
01654 interval.increase( recurrenceType(), -int( frequency() ) );
01655
01656 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
01657
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
01673 KDateTime fromDate( preDate.toTimeSpec( d->mDateStart.timeSpec() ) );
01674
01675 if ( d->mDuration >= 0 && endDt().isValid() && fromDate >= endDt() ) {
01676 return KDateTime();
01677 }
01678
01679
01680 if ( fromDate < d->mDateStart ) {
01681 fromDate = d->mDateStart.addSecs( -1 );
01682 }
01683
01684 if ( d->mTimedRepetition ) {
01685
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
01714
01715
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;
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;
01748 }
01749 if ( end > endRecur ) {
01750 enddt = endRecur;
01751 }
01752 }
01753 }
01754
01755 if ( d->mTimedRepetition ) {
01756
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
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;
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();
01795 done = true;
01796 }
01797 if ( done ) {
01798 return result;
01799 }
01800
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
01825 interval.increase( recurrenceType(), frequency() );
01826 } while ( ++loop < LOOP_LIMIT &&
01827 interval.intervalDateTime( recurrenceType() ) < end );
01828 return result;
01829 }
01830
01831
01832
01833
01834
01835
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
01845
01846
01847
01848 switch ( type ) {
01849
01850
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
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
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
01880 if ( mFrequency > 0 ) {
01881 periods = ( periods / mFrequency ) * mFrequency;
01882 }
01883
01884
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
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
01904
01905
01906
01907 Constraint RecurrenceRule::Private::getNextValidDateInterval( const KDateTime &dt,
01908 PeriodType type ) const
01909 {
01910
01911 long periods = 0;
01912 KDateTime start = mDateStart;
01913 KDateTime nextValid( start );
01914 int modifier = 1;
01915 KDateTime toDate( dt.toTimeSpec( start.timeSpec() ) );
01916
01917
01918
01919
01920 switch ( type ) {
01921
01922
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
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
01957
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
01981
01982
01983
01984
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
01991 if ( merged.year > 0 && merged.hour >= 0 && merged.minute >= 0 && merged.second >= 0 ) {
01992
01993
01994 QList<KDateTime> lstnew = merged.dateTimes( type );
01995 lst += lstnew;
01996 }
01997 }
01998 }
01999
02000 lst.sortUnique();
02001
02002
02003
02004
02005
02006
02007
02008
02009
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
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 );
02074
02075 kDebug() << " Constraints:";
02076
02077 for ( int i = 0, iend = d->mConstraints.count(); i < iend; ++i ) {
02078 d->mConstraints[i].dump();
02079 }
02080 #endif
02081 }
02082
02083
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
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 }