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