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