00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
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
00038 const int LOOP_LIMIT = 10000;
00039
00040 static QString dumpTime( const KDateTime &dt );
00041
00042
00043
00044
00045
00046
00047
00048
00049
00050
00051
00052
00053
00054
00055
00056
00057
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
00069
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
00086
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
00117 QDate dt( year, 1, 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 );
00132 dt = dt.addDays( -( 7 + dt.dayOfWeek() - weekstart ) % 7 );
00133
00134 int daysto = dt.daysTo( date );
00135 if ( daysto < 0 ) {
00136
00137 --y;
00138 dt = QDate( y, 1, 4 );
00139 dt = dt.addDays( -( 7 + dt.dayOfWeek() - weekstart ) % 7 );
00140 daysto = dt.daysTo( date );
00141 } else if ( daysto > 355 ) {
00142
00143 QDate dtn( y+1, 1, 4 );
00144 dtn = dtn.addDays( -( 7 + dtn.dayOfWeek() - weekstart ) % 7 );
00145 int dayston = dtn.daysTo( date );
00146 if ( dayston >= 0 ) {
00147
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
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
00172
00173
00174
00175
00176
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;
00247 int month;
00248 int day;
00249 int hour;
00250 int minute;
00251 int second;
00252 int weekday;
00253 int weekdaynr;
00254 int weeknumber;
00255 int yearday;
00256 int weekstart;
00257 KDateTime::Spec timespec;
00258 bool secondOccurrence;
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
00311
00312
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
00347
00348 if ( ( type == RecurrenceRule::rMonthly ) ||
00349 ( type == RecurrenceRule::rYearly && month > 0 ) ) {
00350
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
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
00382
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 ) const
00397 {
00398
00399 return true;
00400 }
00401
00402
00403
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
00480
00481
00482
00483
00484
00485
00486
00487
00488
00489
00490
00491
00492
00493
00494
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
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
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
00540
00541 if ( !done && yearday != 0 ) {
00542
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
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
00565 if ( !done && weekday != 0 ) {
00566 QDate dt( year, 1, 1 );
00567
00568
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
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 );
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
00595 for ( int i = 0; i < maxloop; ++i ) {
00596 appendDateTime( dt, tm, result );
00597 dt = dt.addDays( 7 );
00598 }
00599 }
00600 }
00601
00602
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
00610
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
00629 intervalDateTime( type );
00630
00631
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
00658 readDateTime( cachedDt, type );
00659 useCachedDt = true;
00660
00661 return true;
00662 }
00663
00664
00665 bool Constraint::readDateTime( const KDateTime &dt, RecurrenceRule::PeriodType type )
00666 {
00667 switch ( type ) {
00668
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
00685 weeknumber = DateHelper::getWeekNumber( dt.date(), weekstart, &year );
00686 break;
00687 default:
00688 break;
00689 }
00690 useCachedDt = false;
00691 return true;
00692 }
00693
00694
00695
00696
00697
00698
00699
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;
00726 PeriodType mPeriod;
00727 KDateTime mDateStart;
00728
00729 uint mFrequency;
00733 int mDuration;
00734 KDateTime mDateEnd;
00735
00736 QList<int> mBySeconds;
00737 QList<int> mByMinutes;
00738 QList<int> mByHours;
00739
00740 QList<WDayPos> mByDays;
00741 QList<int> mByMonthDays;
00742 QList<int> mByYearDays;
00743 QList<int> mByWeekNumbers;
00744 QList<int> mByMonths;
00745 QList<int> mBySetPos;
00746 short mWeekStart;
00747
00748 Constraint::List mConstraints;
00749 QList<RuleObserver*> mObservers;
00750
00751
00752 mutable DateTimeList mCachedDates;
00753 mutable KDateTime mCachedDateEnd;
00754 mutable KDateTime mCachedLastDate;
00755 mutable bool mCached;
00756
00757 bool mIsReadOnly;
00758 bool mAllDay;
00759 bool mNoByRules;
00760 uint mTimedRepetition;
00761 };
00762
00763 RecurrenceRule::Private::Private( RecurrenceRule *parent, const Private &p )
00764 : mParent( parent ),
00765 mRRule( p.mRRule ),
00766 mPeriod( p.mPeriod ),
00767 mDateStart( p.mDateStart ),
00768 mFrequency( p.mFrequency ),
00769 mDuration( p.mDuration ),
00770 mDateEnd( p.mDateEnd ),
00771
00772 mBySeconds( p.mBySeconds ),
00773 mByMinutes( p.mByMinutes ),
00774 mByHours( p.mByHours ),
00775 mByDays( p.mByDays ),
00776 mByMonthDays( p.mByMonthDays ),
00777 mByYearDays( p.mByYearDays ),
00778 mByWeekNumbers( p.mByWeekNumbers ),
00779 mByMonths( p.mByMonths ),
00780 mBySetPos( p.mBySetPos ),
00781 mWeekStart( p.mWeekStart ),
00782
00783 mIsReadOnly( p.mIsReadOnly ),
00784 mAllDay( p.mAllDay )
00785 {
00786 setDirty();
00787 }
00788
00789 RecurrenceRule::Private &RecurrenceRule::Private::operator=( const Private &p )
00790 {
00791
00792 if ( &p == this ) {
00793 return *this;
00794 }
00795
00796 mRRule = p.mRRule;
00797 mPeriod = p.mPeriod;
00798 mDateStart = p.mDateStart;
00799 mFrequency = p.mFrequency;
00800 mDuration = p.mDuration;
00801 mDateEnd = p.mDateEnd;
00802
00803 mBySeconds = p.mBySeconds;
00804 mByMinutes = p.mByMinutes;
00805 mByHours = p.mByHours;
00806 mByDays = p.mByDays;
00807 mByMonthDays = p.mByMonthDays;
00808 mByYearDays = p.mByYearDays;
00809 mByWeekNumbers = p.mByWeekNumbers;
00810 mByMonths = p.mByMonths;
00811 mBySetPos = p.mBySetPos;
00812 mWeekStart = p.mWeekStart;
00813
00814 mIsReadOnly = p.mIsReadOnly;
00815 mAllDay = p.mAllDay;
00816
00817 setDirty();
00818
00819 return *this;
00820 }
00821
00822 bool RecurrenceRule::Private::operator==( const Private &r ) const
00823 {
00824 return
00825 mPeriod == r.mPeriod &&
00826 mDateStart == r.mDateStart &&
00827 mDuration == r.mDuration &&
00828 mDateEnd == r.mDateEnd &&
00829 mFrequency == r.mFrequency &&
00830 mIsReadOnly == r.mIsReadOnly &&
00831 mAllDay == r.mAllDay &&
00832 mBySeconds == r.mBySeconds &&
00833 mByMinutes == r.mByMinutes &&
00834 mByHours == r.mByHours &&
00835 mByDays == r.mByDays &&
00836 mByMonthDays == r.mByMonthDays &&
00837 mByYearDays == r.mByYearDays &&
00838 mByWeekNumbers == r.mByWeekNumbers &&
00839 mByMonths == r.mByMonths &&
00840 mBySetPos == r.mBySetPos &&
00841 mWeekStart == r.mWeekStart;
00842 }
00843
00844 void RecurrenceRule::Private::clear()
00845 {
00846 if ( mIsReadOnly ) {
00847 return;
00848 }
00849 mPeriod = rNone;
00850 mBySeconds.clear();
00851 mByMinutes.clear();
00852 mByHours.clear();
00853 mByDays.clear();
00854 mByMonthDays.clear();
00855 mByYearDays.clear();
00856 mByWeekNumbers.clear();
00857 mByMonths.clear();
00858 mBySetPos.clear();
00859 mWeekStart = 1;
00860
00861 setDirty();
00862 }
00863
00864 void RecurrenceRule::Private::setDirty()
00865 {
00866 buildConstraints();
00867 mCached = false;
00868 mCachedDates.clear();
00869 for ( int i = 0, iend = mObservers.count(); i < iend; ++i ) {
00870 if ( mObservers[i] ) {
00871 mObservers[i]->recurrenceChanged( mParent );
00872 }
00873 }
00874 }
00875
00876
00877
00878
00879
00880
00881 RecurrenceRule::RecurrenceRule()
00882 : d( new Private( this ) )
00883 {
00884 }
00885
00886 RecurrenceRule::RecurrenceRule( const RecurrenceRule &r )
00887 : d( new Private( this, *r.d ) )
00888 {
00889 }
00890
00891 RecurrenceRule::~RecurrenceRule()
00892 {
00893 delete d;
00894 }
00895
00896 bool RecurrenceRule::operator==( const RecurrenceRule &r ) const
00897 {
00898 return *d == *r.d;
00899 }
00900
00901 RecurrenceRule &RecurrenceRule::operator=( const RecurrenceRule &r )
00902 {
00903
00904 if ( &r == this ) {
00905 return *this;
00906 }
00907
00908 *d = *r.d;
00909
00910 return *this;
00911 }
00912
00913 void RecurrenceRule::addObserver( RuleObserver *observer )
00914 {
00915 if ( !d->mObservers.contains( observer ) ) {
00916 d->mObservers.append( observer );
00917 }
00918 }
00919
00920 void RecurrenceRule::removeObserver( RuleObserver *observer )
00921 {
00922 if ( d->mObservers.contains( observer ) ) {
00923 d->mObservers.removeAll( observer );
00924 }
00925 }
00926
00927 void RecurrenceRule::setRecurrenceType( PeriodType period )
00928 {
00929 if ( isReadOnly() ) {
00930 return;
00931 }
00932 d->mPeriod = period;
00933 d->setDirty();
00934 }
00935
00936 KDateTime RecurrenceRule::endDt( bool *result ) const
00937 {
00938 if ( result ) {
00939 *result = false;
00940 }
00941 if ( d->mPeriod == rNone ) {
00942 return KDateTime();
00943 }
00944 if ( d->mDuration < 0 ) {
00945 return KDateTime();
00946 }
00947 if ( d->mDuration == 0 ) {
00948 if ( result ) {
00949 *result = true;
00950 }
00951 return d->mDateEnd;
00952 }
00953
00954
00955 if ( !d->mCached ) {
00956
00957 if ( !d->buildCache() ) {
00958 return KDateTime();
00959 }
00960 }
00961 if ( result ) {
00962 *result = true;
00963 }
00964 return d->mCachedDateEnd;
00965 }
00966
00967 void RecurrenceRule::setEndDt( const KDateTime &dateTime )
00968 {
00969 if ( isReadOnly() ) {
00970 return;
00971 }
00972 d->mDateEnd = dateTime;
00973 d->mDuration = 0;
00974 d->setDirty();
00975 }
00976
00977 void RecurrenceRule::setDuration( int duration )
00978 {
00979 if ( isReadOnly() ) {
00980 return;
00981 }
00982 d->mDuration = duration;
00983 d->setDirty();
00984 }
00985
00986 void RecurrenceRule::setAllDay( bool allDay )
00987 {
00988 if ( isReadOnly() ) {
00989 return;
00990 }
00991 d->mAllDay = allDay;
00992 d->setDirty();
00993 }
00994
00995 void RecurrenceRule::clear()
00996 {
00997 d->clear();
00998 }
00999
01000 void RecurrenceRule::setDirty()
01001 {
01002 d->setDirty();
01003 }
01004
01005 void RecurrenceRule::setStartDt( const KDateTime &start )
01006 {
01007 if ( isReadOnly() ) {
01008 return;
01009 }
01010 d->mDateStart = start;
01011 d->setDirty();
01012 }
01013
01014 void RecurrenceRule::setFrequency( int freq )
01015 {
01016 if ( isReadOnly() || freq <= 0 ) {
01017 return;
01018 }
01019 d->mFrequency = freq;
01020 d->setDirty();
01021 }
01022
01023 void RecurrenceRule::setBySeconds( const QList<int> bySeconds )
01024 {
01025 if ( isReadOnly() ) {
01026 return;
01027 }
01028 d->mBySeconds = bySeconds;
01029 d->setDirty();
01030 }
01031
01032 void RecurrenceRule::setByMinutes( const QList<int> byMinutes )
01033 {
01034 if ( isReadOnly() ) {
01035 return;
01036 }
01037 d->mByMinutes = byMinutes;
01038 d->setDirty();
01039 }
01040
01041 void RecurrenceRule::setByHours( const QList<int> byHours )
01042 {
01043 if ( isReadOnly() ) {
01044 return;
01045 }
01046 d->mByHours = byHours;
01047 d->setDirty();
01048 }
01049
01050 void RecurrenceRule::setByDays( const QList<WDayPos> byDays )
01051 {
01052 if ( isReadOnly() ) {
01053 return;
01054 }
01055 d->mByDays = byDays;
01056 d->setDirty();
01057 }
01058
01059 void RecurrenceRule::setByMonthDays( const QList<int> byMonthDays )
01060 {
01061 if ( isReadOnly() ) {
01062 return;
01063 }
01064 d->mByMonthDays = byMonthDays;
01065 d->setDirty();
01066 }
01067
01068 void RecurrenceRule::setByYearDays( const QList<int> byYearDays )
01069 {
01070 if ( isReadOnly() ) {
01071 return;
01072 }
01073 d->mByYearDays = byYearDays;
01074 d->setDirty();
01075 }
01076
01077 void RecurrenceRule::setByWeekNumbers( const QList<int> byWeekNumbers )
01078 {
01079 if ( isReadOnly() ) {
01080 return;
01081 }
01082 d->mByWeekNumbers = byWeekNumbers;
01083 d->setDirty();
01084 }
01085
01086 void RecurrenceRule::setByMonths( const QList<int> byMonths )
01087 {
01088 if ( isReadOnly() ) {
01089 return;
01090 }
01091 d->mByMonths = byMonths;
01092 d->setDirty();
01093 }
01094
01095 void RecurrenceRule::setBySetPos( const QList<int> bySetPos )
01096 {
01097 if ( isReadOnly() ) {
01098 return;
01099 }
01100 d->mBySetPos = bySetPos;
01101 d->setDirty();
01102 }
01103
01104 void RecurrenceRule::setWeekStart( short weekStart )
01105 {
01106 if ( isReadOnly() ) {
01107 return;
01108 }
01109 d->mWeekStart = weekStart;
01110 d->setDirty();
01111 }
01112
01113 void RecurrenceRule::shiftTimes( const KDateTime::Spec &oldSpec, const KDateTime::Spec &newSpec )
01114 {
01115 d->mDateStart = d->mDateStart.toTimeSpec( oldSpec );
01116 d->mDateStart.setTimeSpec( newSpec );
01117 if ( d->mDuration == 0 ) {
01118 d->mDateEnd = d->mDateEnd.toTimeSpec( oldSpec );
01119 d->mDateEnd.setTimeSpec( newSpec );
01120 }
01121 d->setDirty();
01122 }
01123
01124
01125
01126
01127
01128
01129
01130
01131
01132
01133
01134
01135
01136
01137
01138
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 void RecurrenceRule::Private::buildConstraints()
01182 {
01183 mTimedRepetition = 0;
01184 mNoByRules = mBySetPos.isEmpty();
01185 mConstraints.clear();
01186 Constraint con( mDateStart.timeSpec() );
01187 if ( mWeekStart > 0 ) {
01188 con.setWeekstart( mWeekStart );
01189 }
01190 mConstraints.append( con );
01191
01192 int c, cend;
01193 int i, iend;
01194 Constraint::List tmp;
01195
01196 #define intConstraint( list, setElement ) \
01197 if ( !list.isEmpty() ) { \
01198 mNoByRules = false; \
01199 iend = list.count(); \
01200 if ( iend == 1 ) { \
01201 for ( c = 0, cend = mConstraints.count(); c < cend; ++c ) { \
01202 mConstraints[c].setElement( list[0] ); \
01203 } \
01204 } else { \
01205 for ( c = 0, cend = mConstraints.count(); c < cend; ++c ) { \
01206 for ( i = 0; i < iend; ++i ) { \
01207 con = mConstraints[c]; \
01208 con.setElement( list[i] ); \
01209 tmp.append( con ); \
01210 } \
01211 } \
01212 mConstraints = tmp; \
01213 tmp.clear(); \
01214 } \
01215 }
01216
01217 intConstraint( mBySeconds, setSecond );
01218 intConstraint( mByMinutes, setMinute );
01219 intConstraint( mByHours, setHour );
01220 intConstraint( mByMonthDays, setDay );
01221 intConstraint( mByMonths, setMonth );
01222 intConstraint( mByYearDays, setYearday );
01223 intConstraint( mByWeekNumbers, setWeeknumber );
01224 #undef intConstraint
01225
01226 if ( !mByDays.isEmpty() ) {
01227 mNoByRules = false;
01228 for ( c = 0, cend = mConstraints.count(); c < cend; ++c ) {
01229 for ( i = 0, iend = mByDays.count(); i < iend; ++i ) {
01230 con = mConstraints[c];
01231 con.setWeekday( mByDays[i].day() );
01232 con.setWeekdaynr( mByDays[i].pos() );
01233 tmp.append( con );
01234 }
01235 }
01236 mConstraints = tmp;
01237 tmp.clear();
01238 }
01239
01240 #define fixConstraint( setElement, value ) \
01241 { \
01242 for ( c = 0, cend = mConstraints.count(); c < cend; ++c ) { \
01243 mConstraints[c].setElement( value ); \
01244 } \
01245 }
01246
01247
01248
01249
01250 if ( mPeriod == rWeekly && mByDays.isEmpty() ) {
01251 fixConstraint( setWeekday, mDateStart.date().dayOfWeek() );
01252 }
01253
01254
01255
01256 switch ( mPeriod ) {
01257 case rYearly:
01258 if ( mByDays.isEmpty() && mByWeekNumbers.isEmpty() &&
01259 mByYearDays.isEmpty() && mByMonths.isEmpty() ) {
01260 fixConstraint( setMonth, mDateStart.date().month() );
01261 }
01262 case rMonthly:
01263 if ( mByDays.isEmpty() && mByWeekNumbers.isEmpty() &&
01264 mByYearDays.isEmpty() && mByMonthDays.isEmpty() ) {
01265 fixConstraint( setDay, mDateStart.date().day() );
01266 }
01267 case rWeekly:
01268 case rDaily:
01269 if ( mByHours.isEmpty() ) {
01270 fixConstraint( setHour, mDateStart.time().hour() );
01271 }
01272 case rHourly:
01273 if ( mByMinutes.isEmpty() ) {
01274 fixConstraint( setMinute, mDateStart.time().minute() );
01275 }
01276 case rMinutely:
01277 if ( mBySeconds.isEmpty() ) {
01278 fixConstraint( setSecond, mDateStart.time().second() );
01279 }
01280 case rSecondly:
01281 default:
01282 break;
01283 }
01284 #undef fixConstraint
01285
01286 if ( mNoByRules ) {
01287 switch ( mPeriod ) {
01288 case rHourly:
01289 mTimedRepetition = mFrequency * 3600;
01290 break;
01291 case rMinutely:
01292 mTimedRepetition = mFrequency * 60;
01293 break;
01294 case rSecondly:
01295 mTimedRepetition = mFrequency;
01296 break;
01297 default:
01298 break;
01299 }
01300 } else {
01301 for ( c = 0, cend = mConstraints.count(); c < cend; ) {
01302 if ( mConstraints[c].isConsistent( mPeriod ) ) {
01303 ++c;
01304 } else {
01305 mConstraints.removeAt( c );
01306 --cend;
01307 }
01308 }
01309 }
01310 }
01311
01312
01313
01314 bool RecurrenceRule::Private::buildCache() const
01315 {
01316
01317
01318 Constraint interval( getNextValidDateInterval( mDateStart, mPeriod ) );
01319 QDateTime next;
01320
01321 DateTimeList dts = datesForInterval( interval, mPeriod );
01322
01323
01324 int i = dts.findLT( mDateStart );
01325 if ( i >= 0 ) {
01326 dts.erase( dts.begin(), dts.begin() + i + 1 );
01327 }
01328
01329 int loopnr = 0;
01330 int dtnr = dts.count();
01331
01332
01333 while ( loopnr < LOOP_LIMIT && dtnr < mDuration ) {
01334 interval.increase( mPeriod, mFrequency );
01335
01336 dts += datesForInterval( interval, mPeriod );
01337 dtnr = dts.count();
01338 ++loopnr;
01339 }
01340 if ( dts.count() > mDuration ) {
01341
01342 dts.erase( dts.begin() + mDuration, dts.end() );
01343 }
01344 mCached = true;
01345 mCachedDates = dts;
01346
01347
01348
01349
01350
01351
01352 if ( int( dts.count() ) == mDuration ) {
01353 mCachedDateEnd = dts.last();
01354 return true;
01355 } else {
01356
01357 mCachedDateEnd = KDateTime();
01358 mCachedLastDate = interval.intervalDateTime( mPeriod );
01359 return false;
01360 }
01361 }
01362
01363
01364 bool RecurrenceRule::dateMatchesRules( const KDateTime &kdt ) const
01365 {
01366 KDateTime dt = kdt.toTimeSpec( d->mDateStart.timeSpec() );
01367 for ( int i = 0, iend = d->mConstraints.count(); i < iend; ++i ) {
01368 if ( d->mConstraints[i].matches( dt, recurrenceType() ) ) {
01369 return true;
01370 }
01371 }
01372 return false;
01373 }
01374
01375 bool RecurrenceRule::recursOn( const QDate &qd, const KDateTime::Spec &timeSpec ) const
01376 {
01377 int i, iend;
01378 if ( allDay() ) {
01379
01380
01381 if ( qd < d->mDateStart.date() ) {
01382 return false;
01383 }
01384
01385 QDate endDate;
01386 if ( d->mDuration >= 0 ) {
01387 endDate = endDt().date();
01388 if ( qd > endDate ) {
01389 return false;
01390 }
01391 }
01392
01393
01394
01395 bool match = false;
01396 for ( i = 0, iend = d->mConstraints.count(); i < iend && !match; ++i ) {
01397 match = d->mConstraints[i].matches( qd, recurrenceType() );
01398 }
01399 if ( !match ) {
01400 return false;
01401 }
01402
01403 KDateTime start( qd, QTime( 0, 0, 0 ), d->mDateStart.timeSpec() );
01404 Constraint interval( d->getNextValidDateInterval( start, recurrenceType() ) );
01405
01406
01407 if ( !interval.matches( qd, recurrenceType() ) ) {
01408 return false;
01409 }
01410
01411
01412
01413 KDateTime end = start.addDays(1);
01414 do {
01415 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
01416 for ( i = 0, iend = dts.count(); i < iend; ++i ) {
01417 if ( dts[i].date() >= qd ) {
01418 return dts[i].date() == qd;
01419 }
01420 }
01421 interval.increase( recurrenceType(), frequency() );
01422 } while ( interval.intervalDateTime( recurrenceType() ) < end );
01423 return false;
01424 }
01425
01426
01427 KDateTime start( qd, QTime( 0, 0, 0 ), timeSpec );
01428 KDateTime end = start.addDays( 1 ).toTimeSpec( d->mDateStart.timeSpec() );
01429 start = start.toTimeSpec( d->mDateStart.timeSpec() );
01430 if ( end < d->mDateStart ) {
01431 return false;
01432 }
01433 if ( start < d->mDateStart ) {
01434 start = d->mDateStart;
01435 }
01436
01437
01438 if ( d->mDuration >= 0 ) {
01439 KDateTime endRecur = endDt();
01440 if ( endRecur.isValid() ) {
01441 if ( start > endRecur ) {
01442 return false;
01443 }
01444 if ( end > endRecur ) {
01445 end = endRecur;
01446 }
01447 }
01448 }
01449
01450 if ( d->mTimedRepetition ) {
01451
01452 int n = static_cast<int>( ( d->mDateStart.secsTo_long( start ) - 1 ) % d->mTimedRepetition );
01453 return start.addSecs( d->mTimedRepetition - n ) < end;
01454 }
01455
01456
01457 QDate startDay = start.date();
01458 QDate endDay = end.addSecs( -1 ).date();
01459 int dayCount = startDay.daysTo( endDay ) + 1;
01460
01461
01462
01463 bool match = false;
01464 for ( i = 0, iend = d->mConstraints.count(); i < iend && !match; ++i ) {
01465 match = d->mConstraints[i].matches( startDay, recurrenceType() );
01466 for ( int day = 1; day < dayCount && !match; ++day ) {
01467 match = d->mConstraints[i].matches( startDay.addDays( day ), recurrenceType() );
01468 }
01469 }
01470 if ( !match ) {
01471 return false;
01472 }
01473
01474 Constraint interval( d->getNextValidDateInterval( start, recurrenceType() ) );
01475
01476
01477 match = false;
01478 Constraint intervalm = interval;
01479 do {
01480 match = intervalm.matches( startDay, recurrenceType() );
01481 for ( int day = 1; day < dayCount && !match; ++day ) {
01482 match = intervalm.matches( startDay.addDays( day ), recurrenceType() );
01483 }
01484 if ( match ) {
01485 break;
01486 }
01487 intervalm.increase( recurrenceType(), frequency() );
01488 } while ( intervalm.intervalDateTime( recurrenceType() ) < end );
01489 if ( !match ) {
01490 return false;
01491 }
01492
01493
01494
01495
01496 do {
01497 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
01498 int i = dts.findGE( start );
01499 if ( i >= 0 ) {
01500 return dts[i] <= end;
01501 }
01502 interval.increase( recurrenceType(), frequency() );
01503 } while ( interval.intervalDateTime( recurrenceType() ) < end );
01504
01505 return false;
01506 }
01507
01508 bool RecurrenceRule::recursAt( const KDateTime &kdt ) const
01509 {
01510
01511 KDateTime dt( kdt.toTimeSpec( d->mDateStart.timeSpec() ) );
01512
01513 if ( allDay() ) {
01514 return recursOn( dt.date(), dt.timeSpec() );
01515 }
01516 if ( dt < d->mDateStart ) {
01517 return false;
01518 }
01519
01520 if ( d->mDuration >= 0 && dt > endDt() ) {
01521 return false;
01522 }
01523
01524 if ( d->mTimedRepetition ) {
01525
01526 return !( d->mDateStart.secsTo_long( dt ) % d->mTimedRepetition );
01527 }
01528
01529
01530
01531 if ( !dateMatchesRules( dt ) ) {
01532 return false;
01533 }
01534
01535
01536 Constraint interval( d->getNextValidDateInterval( dt, recurrenceType() ) );
01537
01538 if ( interval.matches( dt, recurrenceType() ) ) {
01539 return true;
01540 }
01541 return false;
01542 }
01543
01544 TimeList RecurrenceRule::recurTimesOn( const QDate &date, const KDateTime::Spec &timeSpec ) const
01545 {
01546 TimeList lst;
01547 if ( allDay() ) {
01548 return lst;
01549 }
01550 KDateTime start( date, QTime( 0, 0, 0 ), timeSpec );
01551 KDateTime end = start.addDays( 1 ).addSecs( -1 );
01552 DateTimeList dts = timesInInterval( start, end );
01553 for ( int i = 0, iend = dts.count(); i < iend; ++i ) {
01554 lst += dts[i].toTimeSpec( timeSpec ).time();
01555 }
01556 return lst;
01557 }
01558
01560 int RecurrenceRule::durationTo( const KDateTime &dt ) const
01561 {
01562
01563 KDateTime toDate( dt.toTimeSpec( d->mDateStart.timeSpec() ) );
01564
01565
01566 if ( toDate < d->mDateStart ) {
01567 return 0;
01568 }
01569
01570 if ( d->mDuration > 0 && toDate >= endDt() ) {
01571 return d->mDuration;
01572 }
01573
01574 if ( d->mTimedRepetition ) {
01575
01576 return static_cast<int>( d->mDateStart.secsTo_long( toDate ) / d->mTimedRepetition );
01577 }
01578
01579 return timesInInterval( d->mDateStart, toDate ).count();
01580 }
01581
01582 int RecurrenceRule::durationTo( const QDate &date ) const
01583 {
01584 return durationTo( KDateTime( date, QTime( 23, 59, 59 ), d->mDateStart.timeSpec() ) );
01585 }
01586
01587 KDateTime RecurrenceRule::getPreviousDate( const KDateTime &afterDate ) const
01588 {
01589
01590 KDateTime toDate( afterDate.toTimeSpec( d->mDateStart.timeSpec() ) );
01591
01592
01593 if ( !toDate.isValid() || toDate < d->mDateStart ) {
01594 return KDateTime();
01595 }
01596
01597 if ( d->mTimedRepetition ) {
01598
01599 KDateTime prev = toDate;
01600 if ( d->mDuration >= 0 && endDt().isValid() && toDate > endDt() ) {
01601 prev = endDt().addSecs( 1 ).toTimeSpec( d->mDateStart.timeSpec() );
01602 }
01603 int n = static_cast<int>( ( d->mDateStart.secsTo_long( prev ) - 1 ) % d->mTimedRepetition );
01604 if ( n < 0 ) {
01605 return KDateTime();
01606 }
01607 prev = prev.addSecs( -n - 1 );
01608 return prev >= d->mDateStart ? prev : KDateTime();
01609 }
01610
01611
01612 if ( d->mDuration > 0 ) {
01613 if ( !d->mCached ) {
01614 d->buildCache();
01615 }
01616 int i = d->mCachedDates.findLT( toDate );
01617 if ( i >= 0 ) {
01618 return d->mCachedDates[i];
01619 }
01620 return KDateTime();
01621 }
01622
01623 KDateTime prev = toDate;
01624 if ( d->mDuration >= 0 && endDt().isValid() && toDate > endDt() ) {
01625 prev = endDt().addSecs( 1 ).toTimeSpec( d->mDateStart.timeSpec() );
01626 }
01627
01628 Constraint interval( d->getPreviousValidDateInterval( prev, recurrenceType() ) );
01629 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
01630 int i = dts.findLT( prev );
01631 if ( i >= 0 ) {
01632 return ( dts[i] >= d->mDateStart ) ? dts[i] : KDateTime();
01633 }
01634
01635
01636 while ( interval.intervalDateTime( recurrenceType() ) > d->mDateStart ) {
01637 interval.increase( recurrenceType(), -int( frequency() ) );
01638
01639 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
01640
01641 if ( !dts.isEmpty() ) {
01642 prev = dts.last();
01643 if ( prev.isValid() && prev >= d->mDateStart ) {
01644 return prev;
01645 } else {
01646 return KDateTime();
01647 }
01648 }
01649 }
01650 return KDateTime();
01651 }
01652
01653 KDateTime RecurrenceRule::getNextDate( const KDateTime &preDate ) const
01654 {
01655
01656 KDateTime fromDate( preDate.toTimeSpec( d->mDateStart.timeSpec() ) );
01657
01658 if ( d->mDuration >= 0 && endDt().isValid() && fromDate >= endDt() ) {
01659 return KDateTime();
01660 }
01661
01662
01663 if ( fromDate < d->mDateStart ) {
01664 fromDate = d->mDateStart.addSecs( -1 );
01665 }
01666
01667 if ( d->mTimedRepetition ) {
01668
01669 int n = static_cast<int>( ( d->mDateStart.secsTo_long( fromDate ) + 1 ) % d->mTimedRepetition );
01670 KDateTime next = fromDate.addSecs( d->mTimedRepetition - n + 1 );
01671 return d->mDuration < 0 || !endDt().isValid() || next <= endDt() ? next : KDateTime();
01672 }
01673
01674 if ( d->mDuration > 0 ) {
01675 if ( !d->mCached ) {
01676 d->buildCache();
01677 }
01678 int i = d->mCachedDates.findGT( fromDate );
01679 if ( i >= 0 ) {
01680 return d->mCachedDates[i];
01681 }
01682 }
01683
01684 KDateTime end = endDt();
01685 Constraint interval( d->getNextValidDateInterval( fromDate, recurrenceType() ) );
01686 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
01687 int i = dts.findGT( fromDate );
01688 if ( i >= 0 ) {
01689 return ( d->mDuration < 0 || dts[i] <= end ) ? dts[i] : KDateTime();
01690 }
01691 interval.increase( recurrenceType(), frequency() );
01692 if ( d->mDuration >= 0 && interval.intervalDateTime( recurrenceType() ) > end ) {
01693 return KDateTime();
01694 }
01695
01696
01697
01698
01699 int loop = 0;
01700 do {
01701 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
01702 if ( dts.count() > 0 ) {
01703 KDateTime ret( dts[0] );
01704 if ( d->mDuration >= 0 && ret > end ) {
01705 return KDateTime();
01706 } else {
01707 return ret;
01708 }
01709 }
01710 interval.increase( recurrenceType(), frequency() );
01711 } while ( ++loop < LOOP_LIMIT &&
01712 ( d->mDuration < 0 || interval.intervalDateTime( recurrenceType() ) < end ) );
01713 return KDateTime();
01714 }
01715
01716 DateTimeList RecurrenceRule::timesInInterval( const KDateTime &dtStart,
01717 const KDateTime &dtEnd ) const
01718 {
01719 KDateTime start = dtStart.toTimeSpec( d->mDateStart.timeSpec() );
01720 KDateTime end = dtEnd.toTimeSpec( d->mDateStart.timeSpec() );
01721 DateTimeList result;
01722 if ( end < d->mDateStart ) {
01723 return result;
01724 }
01725 KDateTime enddt = end;
01726 if ( d->mDuration >= 0 ) {
01727 KDateTime endRecur = endDt();
01728 if ( endRecur.isValid() ) {
01729 if ( start >= endRecur ) {
01730 return result;
01731 }
01732 if ( end > endRecur ) {
01733 enddt = endRecur;
01734 }
01735 }
01736 }
01737
01738 if ( d->mTimedRepetition ) {
01739
01740 int n = static_cast<int>( ( d->mDateStart.secsTo_long( start ) - 1 ) % d->mTimedRepetition );
01741 KDateTime dt = start.addSecs( d->mTimedRepetition - n );
01742 if ( dt < enddt ) {
01743 n = static_cast<int>( ( dt.secsTo_long( enddt ) - 1 ) / d->mTimedRepetition ) + 1;
01744
01745 n = qMin( n, LOOP_LIMIT );
01746 for ( int i = 0; i < n; dt = dt.addSecs( d->mTimedRepetition ), ++i ) {
01747 result += dt;
01748 }
01749 }
01750 return result;
01751 }
01752
01753 KDateTime st = start;
01754 bool done = false;
01755 if ( d->mDuration > 0 ) {
01756 if ( !d->mCached ) {
01757 d->buildCache();
01758 }
01759 if ( d->mCachedDateEnd.isValid() && start >= d->mCachedDateEnd ) {
01760 return result;
01761 }
01762 int i = d->mCachedDates.findGE( start );
01763 if ( i >= 0 ) {
01764 int iend = d->mCachedDates.findGT( enddt, i );
01765 if ( iend < 0 ) {
01766 iend = d->mCachedDates.count();
01767 } else {
01768 done = true;
01769 }
01770 while ( i < iend ) {
01771 result += d->mCachedDates[i++];
01772 }
01773 }
01774 if ( d->mCachedDateEnd.isValid() ) {
01775 done = true;
01776 } else if ( !result.isEmpty() ) {
01777 result += KDateTime();
01778 done = true;
01779 }
01780 if ( done ) {
01781 return result;
01782 }
01783
01784 st = d->mCachedLastDate.addSecs( 1 );
01785 }
01786
01787 Constraint interval( d->getNextValidDateInterval( st, recurrenceType() ) );
01788 int loop = 0;
01789 do {
01790 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
01791 int i = 0;
01792 int iend = dts.count();
01793 if ( loop == 0 ) {
01794 i = dts.findGE( st );
01795 if ( i < 0 ) {
01796 i = iend;
01797 }
01798 }
01799 int j = dts.findGT( enddt, i );
01800 if ( j >= 0 ) {
01801 iend = j;
01802 loop = LOOP_LIMIT;
01803 }
01804 while ( i < iend ) {
01805 result += dts[i++];
01806 }
01807
01808 interval.increase( recurrenceType(), frequency() );
01809 } while ( ++loop < LOOP_LIMIT &&
01810 interval.intervalDateTime( recurrenceType() ) < end );
01811 return result;
01812 }
01813
01814
01815
01816
01817
01818
01819 Constraint RecurrenceRule::Private::getPreviousValidDateInterval( const KDateTime &dt,
01820 PeriodType type ) const
01821 {
01822 long periods = 0;
01823 KDateTime start = mDateStart;
01824 KDateTime nextValid( start );
01825 int modifier = 1;
01826 KDateTime toDate( dt.toTimeSpec( start.timeSpec() ) );
01827
01828
01829
01830
01831 switch ( type ) {
01832
01833
01834 case rHourly:
01835 modifier *= 60;
01836 case rMinutely:
01837 modifier *= 60;
01838 case rSecondly:
01839 periods = static_cast<int>( start.secsTo_long( toDate ) / modifier );
01840
01841 if ( mFrequency > 0 ) {
01842 periods = ( periods / mFrequency ) * mFrequency;
01843 }
01844 nextValid = start.addSecs( modifier * periods );
01845 break;
01846 case rWeekly:
01847 toDate = toDate.addDays( -( 7 + toDate.date().dayOfWeek() - mWeekStart ) % 7 );
01848 start = start.addDays( -( 7 + start.date().dayOfWeek() - mWeekStart ) % 7 );
01849 modifier *= 7;
01850 case rDaily:
01851 periods = start.daysTo( toDate ) / modifier;
01852
01853 if ( mFrequency > 0 ) {
01854 periods = ( periods / mFrequency ) * mFrequency;
01855 }
01856 nextValid = start.addDays( modifier * periods );
01857 break;
01858 case rMonthly:
01859 {
01860 periods = 12 * ( toDate.date().year() - start.date().year() ) +
01861 ( toDate.date().month() - start.date().month() );
01862
01863 if ( mFrequency > 0 ) {
01864 periods = ( periods / mFrequency ) * mFrequency;
01865 }
01866
01867
01868 start.setDate( QDate( start.date().year(), start.date().month(), 1 ) );
01869 nextValid.setDate( start.date().addMonths( periods ) );
01870 break; }
01871 case rYearly:
01872 periods = ( toDate.date().year() - start.date().year() );
01873
01874 if ( mFrequency > 0 ) {
01875 periods = ( periods / mFrequency ) * mFrequency;
01876 }
01877 nextValid.setDate( start.date().addYears( periods ) );
01878 break;
01879 default:
01880 break;
01881 }
01882
01883 return Constraint( nextValid, type, mWeekStart );
01884 }
01885
01886
01887
01888
01889
01890 Constraint RecurrenceRule::Private::getNextValidDateInterval( const KDateTime &dt,
01891 PeriodType type ) const
01892 {
01893
01894 long periods = 0;
01895 KDateTime start = mDateStart;
01896 KDateTime nextValid( start );
01897 int modifier = 1;
01898 KDateTime toDate( dt.toTimeSpec( start.timeSpec() ) );
01899
01900
01901
01902
01903 switch ( type ) {
01904
01905
01906 case rHourly:
01907 modifier *= 60;
01908 case rMinutely:
01909 modifier *= 60;
01910 case rSecondly:
01911 periods = static_cast<int>( start.secsTo_long( toDate ) / modifier );
01912 periods = qMax( 0L, periods );
01913 if ( periods > 0 && mFrequency > 0 ) {
01914 periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
01915 }
01916 nextValid = start.addSecs( modifier * periods );
01917 break;
01918 case rWeekly:
01919
01920 toDate = toDate.addDays( -( 7 + toDate.date().dayOfWeek() - mWeekStart ) % 7 );
01921 start = start.addDays( -( 7 + start.date().dayOfWeek() - mWeekStart ) % 7 );
01922 modifier *= 7;
01923 case rDaily:
01924 periods = start.daysTo( toDate ) / modifier;
01925 periods = qMax( 0L, periods );
01926 if ( periods > 0 && mFrequency > 0 ) {
01927 periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
01928 }
01929 nextValid = start.addDays( modifier * periods );
01930 break;
01931 case rMonthly:
01932 {
01933 periods = 12 * ( toDate.date().year() - start.date().year() ) +
01934 ( toDate.date().month() - start.date().month() );
01935 periods = qMax( 0L, periods );
01936 if ( periods > 0 && mFrequency > 0 ) {
01937 periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
01938 }
01939
01940
01941 start.setDate( QDate( start.date().year(), start.date().month(), 1 ) );
01942 nextValid.setDate( start.date().addMonths( periods ) );
01943 break;
01944 }
01945 case rYearly:
01946 periods = ( toDate.date().year() - start.date().year() );
01947 periods = qMax( 0L, periods );
01948 if ( periods > 0 && mFrequency > 0 ) {
01949 periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
01950 }
01951 nextValid.setDate( start.date().addYears( periods ) );
01952 break;
01953 default:
01954 break;
01955 }
01956
01957 return Constraint( nextValid, type, mWeekStart );
01958 }
01959
01960 DateTimeList RecurrenceRule::Private::datesForInterval( const Constraint &interval,
01961 PeriodType type ) const
01962 {
01963
01964
01965
01966
01967
01968
01969 DateTimeList lst;
01970 for ( int i = 0, iend = mConstraints.count(); i < iend; ++i ) {
01971 Constraint merged( interval );
01972 if ( merged.merge( mConstraints[i] ) ) {
01973
01974 if ( merged.year > 0 && merged.hour >= 0 && merged.minute >= 0 && merged.second >= 0 ) {
01975
01976
01977 QList<KDateTime> lstnew = merged.dateTimes( type );
01978 lst += lstnew;
01979 }
01980 }
01981 }
01982
01983 lst.sortUnique();
01984
01985
01986
01987
01988
01989
01990
01991
01992
01993
01994 if ( !mBySetPos.isEmpty() ) {
01995 DateTimeList tmplst = lst;
01996 lst.clear();
01997 for ( int i = 0, iend = mBySetPos.count(); i < iend; ++i ) {
01998 int pos = mBySetPos[i];
01999 if ( pos > 0 ) {
02000 --pos;
02001 }
02002 if ( pos < 0 ) {
02003 pos += tmplst.count();
02004 }
02005 if ( pos >= 0 && pos < tmplst.count() ) {
02006 lst.append( tmplst[pos] );
02007 }
02008 }
02009 lst.sortUnique();
02010 }
02011
02012 return lst;
02013 }
02014
02015
02016 void RecurrenceRule::dump() const
02017 {
02018 #ifndef NDEBUG
02019 kDebug();
02020 if ( !d->mRRule.isEmpty() ) {
02021 kDebug() << " RRULE=" << d->mRRule;
02022 }
02023 kDebug() << " Read-Only:" << isReadOnly();
02024
02025 kDebug() << " Period type:" << recurrenceType()
02026 << ", frequency:" << frequency();
02027 kDebug() << " #occurrences:" << duration();
02028 kDebug() << " start date:" << dumpTime( startDt() )
02029 << ", end date:" << dumpTime( endDt() );
02030
02031 #define dumpByIntList(list,label) \
02032 if ( !list.isEmpty() ) {\
02033 QStringList lst;\
02034 for ( int i = 0, iend = list.count(); i < iend; ++i ) {\
02035 lst.append( QString::number( list[i] ) );\
02036 }\
02037 kDebug() << " " << label << lst.join( ", " );\
02038 }
02039 dumpByIntList( d->mBySeconds, "BySeconds: " );
02040 dumpByIntList( d->mByMinutes, "ByMinutes: " );
02041 dumpByIntList( d->mByHours, "ByHours: " );
02042 if ( !d->mByDays.isEmpty() ) {
02043 QStringList lst;
02044 for ( int i = 0, iend = d->mByDays.count(); i < iend; ++i ) {\
02045 lst.append( ( d->mByDays[i].pos() ? QString::number( d->mByDays[i].pos() ) : "" ) +
02046 DateHelper::dayName( d->mByDays[i].day() ) );
02047 }
02048 kDebug() << " ByDays: " << lst.join( ", " );
02049 }
02050 dumpByIntList( d->mByMonthDays, "ByMonthDays:" );
02051 dumpByIntList( d->mByYearDays, "ByYearDays: " );
02052 dumpByIntList( d->mByWeekNumbers, "ByWeekNr: " );
02053 dumpByIntList( d->mByMonths, "ByMonths: " );
02054 dumpByIntList( d->mBySetPos, "BySetPos: " );
02055 #undef dumpByIntList
02056
02057 kDebug() << " Week start:" << DateHelper::dayName( d->mWeekStart );
02058
02059 kDebug() << " Constraints:";
02060
02061 for ( int i = 0, iend = d->mConstraints.count(); i < iend; ++i ) {
02062 d->mConstraints[i].dump();
02063 }
02064 #endif
02065 }
02066
02067
02068 void Constraint::dump() const
02069 {
02070 kDebug() << " ~> Y=" << year
02071 << ", M=" << month
02072 << ", D=" << day
02073 << ", H=" << hour
02074 << ", m=" << minute
02075 << ", S=" << second
02076 << ", wd=" << weekday
02077 << ",#wd=" << weekdaynr
02078 << ", #w=" << weeknumber
02079 << ", yd=" << yearday;
02080 }
02081
02082
02083 QString dumpTime( const KDateTime &dt )
02084 {
02085 #ifndef NDEBUG
02086 if ( !dt.isValid() ) {
02087 return QString();
02088 }
02089 QString result;
02090 if ( dt.isDateOnly() ) {
02091 result = dt.toString( "%a %Y-%m-%d %:Z" );
02092 } else {
02093 result = dt.toString( "%a %Y-%m-%d %H:%M:%S %:Z" );
02094 if ( dt.isSecondOccurrence() ) {
02095 result += QLatin1String( " (2nd)" );
02096 }
02097 }
02098 if ( dt.timeSpec() == KDateTime::Spec::ClockTime() ) {
02099 result += QLatin1String( "Clock" );
02100 }
02101 return result;
02102 #else
02103 Q_UNUSED( dt );
02104 return QString();
02105 #endif
02106 }
02107
02108 KDateTime RecurrenceRule::startDt() const
02109 {
02110 return d->mDateStart;
02111 }
02112
02113 RecurrenceRule::PeriodType RecurrenceRule::recurrenceType() const
02114 {
02115 return d->mPeriod;
02116 }
02117
02118 uint RecurrenceRule::frequency() const
02119 {
02120 return d->mFrequency;
02121 }
02122
02123 int RecurrenceRule::duration() const
02124 {
02125 return d->mDuration;
02126 }
02127
02128 QString RecurrenceRule::rrule() const
02129 {
02130 return d->mRRule;
02131 }
02132
02133 void RecurrenceRule::setRRule( const QString &rrule )
02134 {
02135 d->mRRule = rrule;
02136 }
02137
02138 bool RecurrenceRule::isReadOnly() const
02139 {
02140 return d->mIsReadOnly;
02141 }
02142
02143 void RecurrenceRule::setReadOnly( bool readOnly )
02144 {
02145 d->mIsReadOnly = readOnly;
02146 }
02147
02148 bool RecurrenceRule::recurs() const
02149 {
02150 return d->mPeriod != rNone;
02151 }
02152
02153 bool RecurrenceRule::allDay() const
02154 {
02155 return d->mAllDay;
02156 }
02157
02158 const QList<int> &RecurrenceRule::bySeconds() const
02159 {
02160 return d->mBySeconds;
02161 }
02162
02163 const QList<int> &RecurrenceRule::byMinutes() const
02164 {
02165 return d->mByMinutes;
02166 }
02167
02168 const QList<int> &RecurrenceRule::byHours() const
02169 {
02170 return d->mByHours;
02171 }
02172
02173 const QList<RecurrenceRule::WDayPos> &RecurrenceRule::byDays() const
02174 {
02175 return d->mByDays;
02176 }
02177
02178 const QList<int> &RecurrenceRule::byMonthDays() const
02179 {
02180 return d->mByMonthDays;
02181 }
02182
02183 const QList<int> &RecurrenceRule::byYearDays() const
02184 {
02185 return d->mByYearDays;
02186 }
02187
02188 const QList<int> &RecurrenceRule::byWeekNumbers() const
02189 {
02190 return d->mByWeekNumbers;
02191 }
02192
02193 const QList<int> &RecurrenceRule::byMonths() const
02194 {
02195 return d->mByMonths;
02196 }
02197
02198 const QList<int> &RecurrenceRule::bySetPos() const
02199 {
02200 return d->mBySetPos;
02201 }
02202
02203 short RecurrenceRule::weekStart() const
02204 {
02205 return d->mWeekStart;
02206 }