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