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 class DateHelper
00047 {
00048 public:
00049 #ifndef NDEBUG
00050 static QString dayName( short day );
00051 #endif
00052 static QDate getNthWeek( int year, int weeknumber, short weekstart = 1 );
00053 static int weekNumbersInYear( int year, short weekstart = 1 );
00054 static int getWeekNumber( const QDate &date, short weekstart, int *year = 0 );
00055 static int getWeekNumberNeg( const QDate &date, short weekstart, int *year = 0 );
00056 };
00057
00058 #ifndef NDEBUG
00059
00060
00061 QString DateHelper::dayName( short day )
00062 {
00063 switch ( day ) {
00064 case 1:
00065 return "MO";
00066 case 2:
00067 return "TU";
00068 case 3:
00069 return "WE";
00070 case 4:
00071 return "TH";
00072 case 5:
00073 return "FR";
00074 case 6:
00075 return "SA";
00076 case 7:
00077 return "SU";
00078 default:
00079 return "??";
00080 }
00081 }
00082 #endif
00083
00084 QDate DateHelper::getNthWeek( int year, int weeknumber, short weekstart )
00085 {
00086 if ( weeknumber == 0 ) {
00087 return QDate();
00088 }
00089
00090
00091 QDate dt( year, 1, 4 );
00092 int adjust = -( 7 + dt.dayOfWeek() - weekstart ) % 7;
00093 if ( weeknumber > 0 ) {
00094 dt = dt.addDays( 7 * (weeknumber-1) + adjust );
00095 } else if ( weeknumber < 0 ) {
00096 dt = dt.addYears( 1 );
00097 dt = dt.addDays( 7 * weeknumber + adjust );
00098 }
00099 return dt;
00100 }
00101
00102 int DateHelper::getWeekNumber( const QDate &date, short weekstart, int *year )
00103 {
00104 if ( year ) {
00105 *year = date.year();
00106 }
00107 QDate dt( date.year(), 1, 4 );
00108 dt = dt.addDays( -( 7 + dt.dayOfWeek() - weekstart ) % 7 );
00109 QDate dtn( date.year()+1, 1, 4 );
00110 dtn = dtn.addDays( -( 7 + dtn.dayOfWeek() - weekstart ) % 7 );
00111
00112 int daysto = dt.daysTo( date );
00113 int dayston = dtn.daysTo( date );
00114 if ( daysto < 0 ) {
00115 if ( year ) {
00116 *year = date.year()-1;
00117 }
00118 dt = QDate( date.year() - 1, 1, 4 );
00119 dt = dt.addDays( -( 7 + dt.dayOfWeek() - weekstart ) % 7 );
00120 daysto = dt.daysTo( date );
00121 } else if ( dayston >= 0 ) {
00122
00123 if ( year ) {
00124 *year = date.year() + 1;
00125 }
00126 dt = dtn;
00127 daysto = dayston;
00128 }
00129 return daysto / 7 + 1;
00130 }
00131
00132 int DateHelper::weekNumbersInYear( int year, short weekstart )
00133 {
00134 QDate dt( year, 1, weekstart );
00135 QDate dt1( year + 1, 1, weekstart );
00136 return dt.daysTo( dt1 ) / 7;
00137 }
00138
00139
00140 int DateHelper::getWeekNumberNeg( const QDate &date, short weekstart, int *year )
00141 {
00142 int weekpos = getWeekNumber( date, weekstart, year );
00143 return weekNumbersInYear( *year, weekstart ) - weekpos - 1;
00144 }
00145
00146
00147
00148
00149
00150
00151 class Constraint
00152 {
00153 public:
00154 typedef QList<Constraint> List;
00155
00156 explicit Constraint( KDateTime::Spec, int wkst = 1 );
00157 Constraint( const KDateTime &dt, RecurrenceRule::PeriodType type, int wkst );
00158 void clear();
00159
00160 int year;
00161 int month;
00162 int day;
00163 int hour;
00164 int minute;
00165 int second;
00166 int weekday;
00167 int weekdaynr;
00168 int weeknumber;
00169 int yearday;
00170 int weekstart;
00171 KDateTime::Spec timespec;
00172 bool secondOccurrence;
00173
00174 bool readDateTime( const KDateTime &dt, RecurrenceRule::PeriodType type );
00175 bool matches( const QDate &dt, RecurrenceRule::PeriodType type ) const;
00176 bool matches( const KDateTime &dt, RecurrenceRule::PeriodType type ) const;
00177 bool isConsistent() const;
00178 bool isConsistent( RecurrenceRule::PeriodType period ) const;
00179 bool increase( RecurrenceRule::PeriodType type, int freq );
00180 KDateTime intervalDateTime( RecurrenceRule::PeriodType type ) const;
00181 QList<KDateTime> dateTimes( RecurrenceRule::PeriodType type ) const;
00182 void appendDateTime( const QDate &date, const QTime &time, QList<KDateTime> &list ) const;
00183 void dump() const;
00184 };
00185
00186 Constraint::Constraint( KDateTime::Spec spec, int wkst )
00187 : weekstart( wkst ),
00188 timespec( spec )
00189 {
00190 clear();
00191 }
00192
00193 Constraint::Constraint( const KDateTime &dt, RecurrenceRule::PeriodType type, int wkst )
00194 : weekstart( wkst ),
00195 timespec( dt.timeSpec() )
00196 {
00197 readDateTime( dt, type );
00198 }
00199
00200 void Constraint::clear()
00201 {
00202 year = 0;
00203 month = 0;
00204 day = 0;
00205 hour = -1;
00206 minute = -1;
00207 second = -1;
00208 weekday = 0;
00209 weekdaynr = 0;
00210 weeknumber = 0;
00211 yearday = 0;
00212 secondOccurrence = false;
00213 }
00214
00215 bool Constraint::matches( const QDate &dt, RecurrenceRule::PeriodType type ) const
00216 {
00217
00218
00219
00220 if ( weeknumber == 0 ) {
00221 if ( year > 0 && year != dt.year() ) {
00222 return false;
00223 }
00224 } else {
00225 int y;
00226 if ( weeknumber > 0 &&
00227 weeknumber != DateHelper::getWeekNumber( dt, weekstart, &y ) ) {
00228 return false;
00229 }
00230 if ( weeknumber < 0 &&
00231 weeknumber != DateHelper::getWeekNumberNeg( dt, weekstart, &y ) ) {
00232 return false;
00233 }
00234 if ( year > 0 && year != y ) {
00235 return false;
00236 }
00237 }
00238
00239 if ( month > 0 && month != dt.month() ) {
00240 return false;
00241 }
00242 if ( day > 0 && day != dt.day() ) {
00243 return false;
00244 }
00245 if ( day < 0 && dt.day() != ( dt.daysInMonth() + day + 1 ) ) {
00246 return false;
00247 }
00248 if ( weekday > 0 ) {
00249 if ( weekday != dt.dayOfWeek() ) {
00250 return false;
00251 }
00252 if ( weekdaynr != 0 ) {
00253
00254
00255 bool inMonth = ( type == RecurrenceRule::rMonthly ) ||
00256 ( type == RecurrenceRule::rYearly && month > 0 );
00257
00258 if ( weekdaynr > 0 && inMonth &&
00259 weekdaynr != ( dt.day() - 1 ) / 7 + 1 ) {
00260 return false;
00261 }
00262 if ( weekdaynr < 0 && inMonth &&
00263 weekdaynr != -( ( dt.daysInMonth() - dt.day() ) / 7 + 1 ) ) {
00264 return false;
00265 }
00266
00267 if ( weekdaynr > 0 && !inMonth &&
00268 weekdaynr != ( dt.dayOfYear() - 1 ) / 7 + 1 ) {
00269 return false;
00270 }
00271 if ( weekdaynr < 0 && !inMonth &&
00272 weekdaynr != -( ( dt.daysInYear() - dt.dayOfYear() ) / 7 + 1 ) ) {
00273 return false;
00274 }
00275 }
00276 }
00277 if ( yearday > 0 && yearday != dt.dayOfYear() ) {
00278 return false;
00279 }
00280 if ( yearday < 0 && yearday != dt.daysInYear() - dt.dayOfYear() + 1 ) {
00281 return false;
00282 }
00283 return true;
00284 }
00285
00286
00287
00288
00289 bool Constraint::matches( const KDateTime &dt, RecurrenceRule::PeriodType type ) const
00290 {
00291 if ( !matches( dt.date(), type ) ) {
00292 return false;
00293 }
00294 if ( hour >= 0 && ( hour != dt.time().hour() || secondOccurrence != dt.isSecondOccurrence() ) ) {
00295 return false;
00296 }
00297 if ( minute >= 0 && minute != dt.time().minute() ) {
00298 return false;
00299 }
00300 if ( second >= 0 && second != dt.time().second() ) {
00301 return false;
00302 }
00303 return true;
00304 }
00305
00306 bool Constraint::isConsistent( RecurrenceRule::PeriodType ) const
00307 {
00308
00309 return true;
00310 }
00311
00312
00313
00314 KDateTime Constraint::intervalDateTime( RecurrenceRule::PeriodType type ) const
00315 {
00316 QTime t( 0, 0, 0 );
00317 QDate d( year, (month>0)?month:1, (day>0)?day:1 );
00318 if ( day < 0 ) {
00319 d = d.addDays( d.daysInMonth() + day );
00320 }
00321 switch ( type ) {
00322 case RecurrenceRule::rSecondly:
00323 t.setHMS( hour, minute, second );
00324 break;
00325 case RecurrenceRule::rMinutely:
00326 t.setHMS( hour, minute, 0 );
00327 break;
00328 case RecurrenceRule::rHourly:
00329 t.setHMS( hour, 0, 0 );
00330 break;
00331 case RecurrenceRule::rDaily:
00332 break;
00333 case RecurrenceRule::rWeekly:
00334 d = DateHelper::getNthWeek( year, weeknumber, weekstart );
00335 break;
00336 case RecurrenceRule::rMonthly:
00337 d.setYMD( year, month, 1 );
00338 break;
00339 case RecurrenceRule::rYearly:
00340 d.setYMD( year, 1, 1 );
00341 break;
00342 default:
00343 break;
00344 }
00345 KDateTime result( d, t, timespec );
00346 if ( secondOccurrence ) {
00347 result.setSecondOccurrence( true );
00348 }
00349 return result;
00350 }
00351
00352
00353
00354
00355
00356
00357
00358
00359
00360
00361
00362
00363
00364
00365
00366
00367
00368
00369 QList<KDateTime> Constraint::dateTimes( RecurrenceRule::PeriodType type ) const
00370 {
00371 QList<KDateTime> result;
00372 bool done = false;
00373 if ( !isConsistent( type ) ) {
00374 return result;
00375 }
00376
00377
00378 QTime tm( hour, minute, second );
00379
00380 if ( !done && day > 0 && month > 0 ) {
00381 appendDateTime( QDate( year, month, day ), tm, result );
00382 done = true;
00383 }
00384 if ( !done && day < 0 && month > 0 ) {
00385 QDate d( year, month, 1 );
00386 appendDateTime( d.addDays( d.daysInMonth() + day ), tm, result );
00387 done = true;
00388 }
00389
00390 if ( !done && weekday == 0 && weeknumber == 0 && yearday == 0 ) {
00391
00392 uint mstart = ( month > 0 ) ? month : 1;
00393 uint mend = ( month <= 0 ) ? 12 : month;
00394 for ( uint m = mstart; m <= mend; ++m ) {
00395 uint dstart, dend;
00396 if ( day > 0 ) {
00397 dstart = dend = day;
00398 } else if ( day < 0 ) {
00399 QDate date( year, month, 1 );
00400 dstart = dend = date.daysInMonth() + day + 1;
00401 } else {
00402 QDate date( year, month, 1 );
00403 dstart = 1;
00404 dend = date.daysInMonth();
00405 }
00406 for ( uint d = dstart; d <= dend; ++d ) {
00407 appendDateTime( QDate( year, m, d ), tm, result );
00408 }
00409 }
00410 done = true;
00411 }
00412
00413
00414
00415 if ( !done && yearday != 0 ) {
00416
00417 QDate d( year + ( ( yearday > 0 ) ? 0 : 1 ), 1, 1 );
00418 d = d.addDays( yearday - ( ( yearday > 0 ) ? 1 : 0 ) );
00419 appendDateTime( d, tm, result );
00420 done = true;
00421 }
00422
00423
00424 if ( !done && weeknumber != 0 ) {
00425 QDate wst( DateHelper::getNthWeek( year, weeknumber, weekstart ) );
00426 if ( weekday != 0 ) {
00427 wst = wst.addDays( ( 7 + weekday - weekstart ) % 7 );
00428 appendDateTime( wst, tm, result );
00429 } else {
00430 for ( int i = 0; i < 7; ++i ) {
00431 appendDateTime( wst, tm, result );
00432 wst = wst.addDays( 1 );
00433 }
00434 }
00435 done = true;
00436 }
00437
00438
00439 if ( !done && weekday != 0 ) {
00440 QDate dt( year, 1, 1 );
00441
00442
00443 int maxloop = 53;
00444 bool inMonth = ( type == RecurrenceRule::rMonthly ) ||
00445 ( type == RecurrenceRule::rYearly && month > 0 );
00446 if ( inMonth && month > 0 ) {
00447 dt = QDate( year, month, 1 );
00448 maxloop = 5;
00449 }
00450 if ( weekdaynr < 0 ) {
00451
00452 if ( inMonth ) {
00453 dt = dt.addMonths( 1 );
00454 } else {
00455 dt = dt.addYears( 1 );
00456 }
00457 }
00458 int adj = ( 7 + weekday - dt.dayOfWeek() ) % 7;
00459 dt = dt.addDays( adj );
00460
00461 if ( weekdaynr > 0 ) {
00462 dt = dt.addDays( ( weekdaynr - 1 ) * 7 );
00463 appendDateTime( dt, tm, result );
00464 } else if ( weekdaynr < 0 ) {
00465 dt = dt.addDays( weekdaynr * 7 );
00466 appendDateTime( dt, tm, result );
00467 } else {
00468
00469 for ( int i = 0; i < maxloop; ++i ) {
00470 appendDateTime( dt, tm, result );
00471 dt = dt.addDays( 7 );
00472 }
00473 }
00474 }
00475
00476
00477 QList<KDateTime> valid;
00478 for ( int i = 0, iend = result.count(); i < iend; ++i ) {
00479 if ( matches( result[i], type ) ) {
00480 valid.append( result[i] );
00481 }
00482 }
00483
00484
00485 return valid;
00486 }
00487
00488 void Constraint::appendDateTime( const QDate &date, const QTime &time,
00489 QList<KDateTime> &list ) const
00490 {
00491 KDateTime dt( date, time, timespec );
00492 if ( dt.isValid() ) {
00493 if ( secondOccurrence ) {
00494 dt.setSecondOccurrence( true );
00495 }
00496 list.append( dt );
00497 }
00498 }
00499
00500 bool Constraint::increase( RecurrenceRule::PeriodType type, int freq )
00501 {
00502
00503 KDateTime dt( intervalDateTime( type ) );
00504
00505
00506 switch ( type ) {
00507 case RecurrenceRule::rSecondly:
00508 dt = dt.addSecs( freq );
00509 break;
00510 case RecurrenceRule::rMinutely:
00511 dt = dt.addSecs( 60 * freq );
00512 break;
00513 case RecurrenceRule::rHourly:
00514 dt = dt.addSecs( 3600 * freq );
00515 break;
00516 case RecurrenceRule::rDaily:
00517 dt = dt.addDays( freq );
00518 break;
00519 case RecurrenceRule::rWeekly:
00520 dt = dt.addDays( 7 * freq );
00521 break;
00522 case RecurrenceRule::rMonthly:
00523 dt = dt.addMonths( freq );
00524 break;
00525 case RecurrenceRule::rYearly:
00526 dt = dt.addYears( freq );
00527 break;
00528 default:
00529 break;
00530 }
00531
00532 readDateTime( dt, type );
00533
00534 return true;
00535 }
00536
00537
00538 bool Constraint::readDateTime( const KDateTime &dt, RecurrenceRule::PeriodType type )
00539 {
00540 clear();
00541 switch ( type ) {
00542
00543 case RecurrenceRule::rSecondly:
00544 second = dt.time().second();
00545 case RecurrenceRule::rMinutely:
00546 minute = dt.time().minute();
00547 case RecurrenceRule::rHourly:
00548 hour = dt.time().hour();
00549 secondOccurrence = dt.isSecondOccurrence();
00550 case RecurrenceRule::rDaily:
00551 day = dt.date().day();
00552 case RecurrenceRule::rMonthly:
00553 month = dt.date().month();
00554 case RecurrenceRule::rYearly:
00555 year = dt.date().year();
00556 break;
00557 case RecurrenceRule::rWeekly:
00558
00559 weeknumber = DateHelper::getWeekNumber( dt.date(), weekstart, &year );
00560 break;
00561 default:
00562 break;
00563 }
00564 return true;
00565 }
00566
00567
00568
00569
00570
00571
00572
00573 class KCal::RecurrenceRule::Private
00574 {
00575 public:
00576 Private( RecurrenceRule *parent )
00577 : mParent( parent ),
00578 mPeriod( rNone ),
00579 mFrequency( 0 ),
00580 mWeekStart( 1 ),
00581 mIsReadOnly( false ),
00582 mAllDay( false )
00583 {}
00584
00585 Private( RecurrenceRule *parent, const Private &p )
00586 : mParent( parent ),
00587 mRRule( p.mRRule ),
00588 mPeriod( p.mPeriod ),
00589 mDateStart( p.mDateStart ),
00590 mFrequency( p.mFrequency ),
00591 mDuration( p.mDuration ),
00592 mDateEnd( p.mDateEnd ),
00593
00594 mBySeconds( p.mBySeconds ),
00595 mByMinutes( p.mByMinutes ),
00596 mByHours( p.mByHours ),
00597 mByDays( p.mByDays ),
00598 mByMonthDays( p.mByMonthDays ),
00599 mByYearDays( p.mByYearDays ),
00600 mByWeekNumbers( p.mByWeekNumbers ),
00601 mByMonths( p.mByMonths ),
00602 mBySetPos( p.mBySetPos ),
00603 mWeekStart( p.mWeekStart ),
00604
00605 mIsReadOnly( p.mIsReadOnly ),
00606 mAllDay( p.mAllDay )
00607 {
00608 setDirty();
00609 }
00610
00611 bool operator==( const Private &other ) const;
00612 void clear();
00613 void setDirty();
00614 void buildConstraints();
00615 bool buildCache() const;
00616 Constraint getNextValidDateInterval( const KDateTime &preDate, PeriodType type ) const;
00617 Constraint getPreviousValidDateInterval( const KDateTime &afterDate, PeriodType type ) const;
00618 DateTimeList datesForInterval( const Constraint &interval, PeriodType type ) const;
00619 bool mergeIntervalConstraint( Constraint *merged, const Constraint &conit,
00620 const Constraint &interval ) const;
00621
00622 RecurrenceRule *mParent;
00623 QString mRRule;
00624 PeriodType mPeriod;
00625 KDateTime mDateStart;
00626
00627 uint mFrequency;
00632 int mDuration;
00633 KDateTime mDateEnd;
00634
00635 QList<int> mBySeconds;
00636 QList<int> mByMinutes;
00637 QList<int> mByHours;
00638
00639 QList<WDayPos> mByDays;
00640 QList<int> mByMonthDays;
00641 QList<int> mByYearDays;
00642 QList<int> mByWeekNumbers;
00643 QList<int> mByMonths;
00644 QList<int> mBySetPos;
00645 short mWeekStart;
00646
00647 Constraint::List mConstraints;
00648 QList<RuleObserver*> mObservers;
00649
00650
00651 mutable DateTimeList mCachedDates;
00652 mutable KDateTime mCachedDateEnd;
00653 mutable KDateTime mCachedLastDate;
00654 mutable bool mCached;
00655
00656 bool mDirty;
00657 bool mIsReadOnly;
00658 bool mAllDay;
00659 };
00660
00661 bool RecurrenceRule::Private::operator==( const Private &r ) const
00662 {
00663 return
00664 mPeriod == r.mPeriod &&
00665 mDateStart == r.mDateStart &&
00666 mDuration == r.mDuration &&
00667 mDateEnd == r.mDateEnd &&
00668 mFrequency == r.mFrequency &&
00669 mIsReadOnly == r.mIsReadOnly &&
00670 mAllDay == r.mAllDay &&
00671 mBySeconds == r.mBySeconds &&
00672 mByMinutes == r.mByMinutes &&
00673 mByHours == r.mByHours &&
00674 mByDays == r.mByDays &&
00675 mByMonthDays == r.mByMonthDays &&
00676 mByYearDays == r.mByYearDays &&
00677 mByWeekNumbers == r.mByWeekNumbers &&
00678 mByMonths == r.mByMonths &&
00679 mBySetPos == r.mBySetPos &&
00680 mWeekStart == r.mWeekStart;
00681 }
00682
00683 void RecurrenceRule::Private::clear()
00684 {
00685 if ( mIsReadOnly ) {
00686 return;
00687 }
00688 mPeriod = rNone;
00689 mBySeconds.clear();
00690 mByMinutes.clear();
00691 mByHours.clear();
00692 mByDays.clear();
00693 mByMonthDays.clear();
00694 mByYearDays.clear();
00695 mByWeekNumbers.clear();
00696 mByMonths.clear();
00697 mBySetPos.clear();
00698 mWeekStart = 1;
00699
00700 setDirty();
00701 }
00702
00703 void RecurrenceRule::Private::setDirty()
00704 {
00705 mConstraints.clear();
00706 buildConstraints();
00707 mDirty = true;
00708 mCached = false;
00709 mCachedDates.clear();
00710 for ( int i = 0, iend = mObservers.count(); i < iend; ++i ) {
00711 if ( mObservers[i] ) {
00712 mObservers[i]->recurrenceChanged( mParent );
00713 }
00714 }
00715 }
00716
00717
00718
00719
00720
00721
00722 RecurrenceRule::RecurrenceRule()
00723 : d( new Private( this ) )
00724 {
00725 }
00726
00727 RecurrenceRule::RecurrenceRule( const RecurrenceRule &r )
00728 : d( new Private( this, *r.d ) )
00729 {
00730 }
00731
00732 RecurrenceRule::~RecurrenceRule()
00733 {
00734 delete d;
00735 }
00736
00737 bool RecurrenceRule::operator==( const RecurrenceRule &r ) const
00738 {
00739 return *d == *r.d;
00740 }
00741
00742 void RecurrenceRule::addObserver( RuleObserver *observer )
00743 {
00744 if ( !d->mObservers.contains( observer ) ) {
00745 d->mObservers.append( observer );
00746 }
00747 }
00748
00749 void RecurrenceRule::removeObserver( RuleObserver *observer )
00750 {
00751 if ( d->mObservers.contains( observer ) ) {
00752 d->mObservers.removeAll( observer );
00753 }
00754 }
00755
00756 void RecurrenceRule::setRecurrenceType( PeriodType period )
00757 {
00758 if ( isReadOnly() ) {
00759 return;
00760 }
00761 d->mPeriod = period;
00762 d->setDirty();
00763 }
00764
00765 KDateTime RecurrenceRule::endDt( bool *result ) const
00766 {
00767 if ( result ) {
00768 *result = false;
00769 }
00770 if ( d->mPeriod == rNone ) {
00771 return KDateTime();
00772 }
00773 if ( d->mDuration < 0 ) {
00774 return KDateTime();
00775 }
00776 if ( d->mDuration == 0 ) {
00777 if ( result ) {
00778 *result = true;
00779 }
00780 return d->mDateEnd;
00781 }
00782
00783
00784 if ( !d->mCached ) {
00785
00786 if ( !d->buildCache() ) {
00787 return KDateTime();
00788 }
00789 }
00790 if ( result ) {
00791 *result = true;
00792 }
00793 return d->mCachedDateEnd;
00794 }
00795
00796 void RecurrenceRule::setEndDt( const KDateTime &dateTime )
00797 {
00798 if ( isReadOnly() ) {
00799 return;
00800 }
00801 d->mDateEnd = dateTime;
00802 d->mDuration = 0;
00803 d->setDirty();
00804 }
00805
00806 void RecurrenceRule::setDuration( int duration )
00807 {
00808 if ( isReadOnly() ) {
00809 return;
00810 }
00811 d->mDuration = duration;
00812 d->setDirty();
00813 }
00814
00815 void RecurrenceRule::setAllDay( bool allDay )
00816 {
00817 if ( isReadOnly() ) {
00818 return;
00819 }
00820 d->mAllDay = allDay;
00821 d->setDirty();
00822 }
00823
00824 void RecurrenceRule::clear()
00825 {
00826 d->clear();
00827 }
00828
00829 void RecurrenceRule::setDirty()
00830 {
00831 d->setDirty();
00832 }
00833
00834 void RecurrenceRule::setStartDt( const KDateTime &start )
00835 {
00836 if ( isReadOnly() ) {
00837 return;
00838 }
00839 d->mDateStart = start;
00840 d->setDirty();
00841 }
00842
00843 void RecurrenceRule::setFrequency( int freq )
00844 {
00845 if ( isReadOnly() || freq <= 0 ) {
00846 return;
00847 }
00848 d->mFrequency = freq;
00849 d->setDirty();
00850 }
00851
00852 void RecurrenceRule::setBySeconds( const QList<int> bySeconds )
00853 {
00854 if ( isReadOnly() ) {
00855 return;
00856 }
00857 d->mBySeconds = bySeconds;
00858 d->setDirty();
00859 }
00860
00861 void RecurrenceRule::setByMinutes( const QList<int> byMinutes )
00862 {
00863 if ( isReadOnly() ) {
00864 return;
00865 }
00866 d->mByMinutes = byMinutes;
00867 d->setDirty();
00868 }
00869
00870 void RecurrenceRule::setByHours( const QList<int> byHours )
00871 {
00872 if ( isReadOnly() ) {
00873 return;
00874 }
00875 d->mByHours = byHours;
00876 d->setDirty();
00877 }
00878
00879 void RecurrenceRule::setByDays( const QList<WDayPos> byDays )
00880 {
00881 if ( isReadOnly() ) {
00882 return;
00883 }
00884 d->mByDays = byDays;
00885 d->setDirty();
00886 }
00887
00888 void RecurrenceRule::setByMonthDays( const QList<int> byMonthDays )
00889 {
00890 if ( isReadOnly() ) {
00891 return;
00892 }
00893 d->mByMonthDays = byMonthDays;
00894 d->setDirty();
00895 }
00896
00897 void RecurrenceRule::setByYearDays( const QList<int> byYearDays )
00898 {
00899 if ( isReadOnly() ) {
00900 return;
00901 }
00902 d->mByYearDays = byYearDays;
00903 d->setDirty();
00904 }
00905
00906 void RecurrenceRule::setByWeekNumbers( const QList<int> byWeekNumbers )
00907 {
00908 if ( isReadOnly() ) {
00909 return;
00910 }
00911 d->mByWeekNumbers = byWeekNumbers;
00912 d->setDirty();
00913 }
00914
00915 void RecurrenceRule::setByMonths( const QList<int> byMonths )
00916 {
00917 if ( isReadOnly() ) {
00918 return;
00919 }
00920 d->mByMonths = byMonths;
00921 d->setDirty();
00922 }
00923
00924 void RecurrenceRule::setBySetPos( const QList<int> bySetPos )
00925 {
00926 if ( isReadOnly() ) {
00927 return;
00928 }
00929 d->mBySetPos = bySetPos;
00930 d->setDirty();
00931 }
00932
00933 void RecurrenceRule::setWeekStart( short weekStart )
00934 {
00935 if ( isReadOnly() ) {
00936 return;
00937 }
00938 d->mWeekStart = weekStart;
00939 d->setDirty();
00940 }
00941
00942 void RecurrenceRule::shiftTimes( const KDateTime::Spec &oldSpec, const KDateTime::Spec &newSpec )
00943 {
00944 d->mDateStart = d->mDateStart.toTimeSpec( oldSpec );
00945 d->mDateStart.setTimeSpec( newSpec );
00946 if ( d->mDuration == 0 ) {
00947 d->mDateEnd = d->mDateEnd.toTimeSpec( oldSpec );
00948 d->mDateEnd.setTimeSpec( newSpec );
00949 }
00950 d->setDirty();
00951 }
00952
00953
00954
00955
00956
00957
00958
00959
00960
00961
00962
00963
00964
00965
00966
00967
00968
00969
00970
00971
00972
00973
00974
00975
00976
00977
00978
00979
00980
00981
00982
00983
00984
00985
00986
00987
00988
00989
00990
00991
00992
00993
00994
00995
00996
00997
00998
00999
01000
01001
01002
01003
01004
01005
01006
01007
01008
01009
01010 void RecurrenceRule::Private::buildConstraints()
01011 {
01012 mConstraints.clear();
01013 Constraint con( mDateStart.timeSpec() );
01014 if ( mWeekStart > 0 ) {
01015 con.weekstart = mWeekStart;
01016 }
01017 mConstraints.append( con );
01018
01019 int c, cend;
01020 int i, iend;
01021 Constraint::List tmp;
01022
01023 #define intConstraint( list, element ) \
01024 if ( !list.isEmpty() ) { \
01025 for ( c = 0, cend = mConstraints.count(); c < cend; ++c ) { \
01026 for ( i = 0, iend = list.count(); i < iend; ++i ) { \
01027 con = mConstraints[c]; \
01028 con.element = list[i]; \
01029 tmp.append( con ); \
01030 } \
01031 } \
01032 mConstraints = tmp; \
01033 tmp.clear(); \
01034 }
01035
01036 intConstraint( mBySeconds, second );
01037 intConstraint( mByMinutes, minute );
01038 intConstraint( mByHours, hour );
01039 intConstraint( mByMonthDays, day );
01040 intConstraint( mByMonths, month );
01041 intConstraint( mByYearDays, yearday );
01042 intConstraint( mByWeekNumbers, weeknumber );
01043 #undef intConstraint
01044
01045 if ( !mByDays.isEmpty() ) {
01046 for ( c = 0, cend = mConstraints.count(); c < cend; ++c ) {
01047 for ( i = 0, iend = mByDays.count(); i < iend; ++i ) {
01048 con = mConstraints[c];
01049 con.weekday = mByDays[i].day();
01050 con.weekdaynr = mByDays[i].pos();
01051 tmp.append( con );
01052 }
01053 }
01054 mConstraints = tmp;
01055 tmp.clear();
01056 }
01057
01058 #define fixConstraint( element, value ) \
01059 { \
01060 tmp.clear(); \
01061 for ( c = 0, cend = mConstraints.count(); c < cend; ++c ) { \
01062 con = mConstraints[c]; \
01063 con.element = value; \
01064 tmp.append( con ); \
01065 } \
01066 mConstraints = tmp; \
01067 }
01068
01069
01070
01071
01072 if ( mPeriod == rWeekly && mByDays.isEmpty() ) {
01073 fixConstraint( weekday, mDateStart.date().dayOfWeek() );
01074 }
01075
01076
01077
01078 switch ( mPeriod ) {
01079 case rYearly:
01080 if ( mByDays.isEmpty() && mByWeekNumbers.isEmpty() &&
01081 mByYearDays.isEmpty() && mByMonths.isEmpty() ) {
01082 fixConstraint( month, mDateStart.date().month() );
01083 }
01084 case rMonthly:
01085 if ( mByDays.isEmpty() && mByWeekNumbers.isEmpty() &&
01086 mByYearDays.isEmpty() && mByMonthDays.isEmpty() ) {
01087 fixConstraint( day, mDateStart.date().day() );
01088 }
01089 case rWeekly:
01090 case rDaily:
01091 if ( mByHours.isEmpty() ) {
01092 fixConstraint( hour, mDateStart.time().hour() );
01093 }
01094 case rHourly:
01095 if ( mByMinutes.isEmpty() ) {
01096 fixConstraint( minute, mDateStart.time().minute() );
01097 }
01098 case rMinutely:
01099 if ( mBySeconds.isEmpty() ) {
01100 fixConstraint( second, mDateStart.time().second() );
01101 }
01102 case rSecondly:
01103 default:
01104 break;
01105 }
01106 #undef fixConstraint
01107
01108 for ( c = 0, cend = mConstraints.count(); c < cend; ) {
01109 if ( mConstraints[c].isConsistent( mPeriod ) ) {
01110 ++c;
01111 } else {
01112 mConstraints.removeAt( c );
01113 --cend;
01114 }
01115 }
01116 }
01117
01118
01119
01120 bool RecurrenceRule::Private::buildCache() const
01121 {
01122
01123
01124 Constraint interval( getNextValidDateInterval( mDateStart, mPeriod ) );
01125 QDateTime next;
01126
01127 DateTimeList dts = datesForInterval( interval, mPeriod );
01128
01129
01130 int i = dts.findLT( mDateStart );
01131 if ( i >= 0 ) {
01132 dts.erase( dts.begin(), dts.begin() + i + 1 );
01133 }
01134
01135 int loopnr = 0;
01136 int dtnr = dts.count();
01137
01138
01139 while ( loopnr < LOOP_LIMIT && dtnr < mDuration ) {
01140 interval.increase( mPeriod, mFrequency );
01141
01142 dts += datesForInterval( interval, mPeriod );
01143 dtnr = dts.count();
01144 ++loopnr;
01145 }
01146 if ( dts.count() > mDuration ) {
01147
01148 dts.erase( dts.begin() + mDuration, dts.end() );
01149 }
01150 mCached = true;
01151 mCachedDates = dts;
01152
01153
01154
01155
01156
01157
01158 if ( int( dts.count() ) == mDuration ) {
01159 mCachedDateEnd = dts.last();
01160 return true;
01161 } else {
01162
01163 mCachedDateEnd = KDateTime();
01164 mCachedLastDate = interval.intervalDateTime( mPeriod );
01165 return false;
01166 }
01167 }
01168
01169
01170 bool RecurrenceRule::dateMatchesRules( const KDateTime &kdt ) const
01171 {
01172 KDateTime dt = kdt.toTimeSpec( d->mDateStart.timeSpec() );
01173 for ( int i = 0, iend = d->mConstraints.count(); i < iend; ++i ) {
01174 if ( d->mConstraints[i].matches( dt, recurrenceType() ) ) {
01175 return true;
01176 }
01177 }
01178 return false;
01179 }
01180
01181 bool RecurrenceRule::recursOn( const QDate &qd, const KDateTime::Spec &timeSpec ) const
01182 {
01183 int i, iend;
01184 if ( allDay() ) {
01185
01186
01187 if ( qd < startDt().date() ) {
01188 return false;
01189 }
01190
01191 QDate endDate;
01192 if ( d->mDuration >= 0 ) {
01193 endDate = endDt().date();
01194 if ( qd > endDate ) {
01195 return false;
01196 }
01197 }
01198
01199
01200
01201 bool match = false;
01202 for ( i = 0, iend = d->mConstraints.count(); i < iend && !match; ++i ) {
01203 match = d->mConstraints[i].matches( qd, recurrenceType() );
01204 }
01205 if ( !match ) {
01206 return false;
01207 }
01208
01209 KDateTime start( qd, QTime( 0, 0, 0 ), d->mDateStart.timeSpec() );
01210 Constraint interval( d->getNextValidDateInterval( start, recurrenceType() ) );
01211
01212
01213 if ( !interval.matches( qd, recurrenceType() ) ) {
01214 return false;
01215 }
01216
01217
01218
01219 KDateTime end = start.addDays(1);
01220 do {
01221 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
01222 for ( i = 0, iend = dts.count(); i < iend; ++i ) {
01223 if ( dts[i].date() >= qd ) {
01224 return dts[i].date() == qd;
01225 }
01226 }
01227 interval.increase( recurrenceType(), frequency() );
01228 } while ( interval.intervalDateTime( recurrenceType() ) < end );
01229 return false;
01230 }
01231
01232
01233 KDateTime start( qd, QTime( 0, 0, 0 ), timeSpec );
01234 KDateTime end = start.addDays( 1 );
01235 if ( end < startDt() ) {
01236 return false;
01237 }
01238
01239
01240 if ( d->mDuration >= 0 ) {
01241 KDateTime endRecur = endDt();
01242 if ( endRecur.isValid() ) {
01243 if ( start > endRecur ) {
01244 return false;
01245 }
01246 if ( end > endRecur ) {
01247 end = endRecur;
01248 }
01249 }
01250 }
01251
01252
01253 QDate startDay = start.toTimeSpec( d->mDateStart.timeSpec() ).date();
01254 QDate endDay = end.toTimeSpec( d->mDateStart.timeSpec() ).addSecs( -1 ).date();
01255 int dayCount = startDay.daysTo( endDay ) + 1;
01256
01257
01258
01259 bool match = false;
01260 for ( i = 0, iend = d->mConstraints.count(); i < iend && !match; ++i ) {
01261 match = d->mConstraints[i].matches( startDay, recurrenceType() );
01262 for ( int day = 1; day < dayCount && !match; ++day ) {
01263 match = d->mConstraints[i].matches( startDay.addDays( day ), recurrenceType() );
01264 }
01265 }
01266 if ( !match ) {
01267 return false;
01268 }
01269
01270 Constraint interval( d->getNextValidDateInterval( start, recurrenceType() ) );
01271
01272
01273 match = false;
01274 Constraint intervalm = interval;
01275 do {
01276 match = intervalm.matches( startDay, recurrenceType() );
01277 for ( int day = 1; day < dayCount && !match; ++day ) {
01278 match = intervalm.matches( startDay.addDays( day ), recurrenceType() );
01279 }
01280 if ( match ) {
01281 break;
01282 }
01283 intervalm.increase( recurrenceType(), frequency() );
01284 } while ( intervalm.intervalDateTime( recurrenceType() ) < end );
01285 if ( !match ) {
01286 return false;
01287 }
01288
01289
01290
01291
01292 do {
01293 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
01294 int i = dts.findGE( start );
01295 if ( i >= 0 ) {
01296 return dts[i] <= end;
01297 }
01298 interval.increase( recurrenceType(), frequency() );
01299 } while ( interval.intervalDateTime( recurrenceType() ) < end );
01300
01301 return false;
01302 }
01303
01304 bool RecurrenceRule::recursAt( const KDateTime &kdt ) const
01305 {
01306
01307 KDateTime dt( kdt.toTimeSpec( d->mDateStart.timeSpec() ) );
01308
01309 if ( allDay() ) {
01310 return recursOn( dt.date(), dt.timeSpec() );
01311 }
01312 if ( dt < startDt() ) {
01313 return false;
01314 }
01315
01316 if ( d->mDuration >= 0 && dt > endDt() ) {
01317 return false;
01318 }
01319
01320
01321
01322 if ( !dateMatchesRules( dt ) ) {
01323 return false;
01324 }
01325
01326
01327 Constraint interval( d->getNextValidDateInterval( dt, recurrenceType() ) );
01328
01329 if ( interval.matches( dt, recurrenceType() ) ) {
01330 return true;
01331 }
01332 return false;
01333 }
01334
01335 TimeList RecurrenceRule::recurTimesOn( const QDate &date, const KDateTime::Spec &timeSpec ) const
01336 {
01337 TimeList lst;
01338 if ( allDay() ) {
01339 return lst;
01340 }
01341 KDateTime start( date, QTime( 0, 0, 0 ), timeSpec );
01342 KDateTime end = start.addDays( 1 ).addSecs( -1 );
01343 DateTimeList dts = timesInInterval( start, end );
01344 for ( int i = 0, iend = dts.count(); i < iend; ++i ) {
01345 lst += dts[i].toTimeSpec( timeSpec ).time();
01346 }
01347 return lst;
01348 }
01349
01351 int RecurrenceRule::durationTo( const KDateTime &dt ) const
01352 {
01353
01354 KDateTime toDate( dt.toTimeSpec( d->mDateStart.timeSpec() ) );
01355
01356
01357 if ( toDate < startDt() ) {
01358 return 0;
01359 }
01360
01361 if ( d->mDuration > 0 && toDate >= endDt() ) {
01362 return d->mDuration;
01363 }
01364
01365 return timesInInterval( startDt(), toDate ).count();
01366 }
01367
01368 int RecurrenceRule::durationTo( const QDate &date ) const
01369 {
01370 return durationTo( KDateTime( date, QTime( 23, 59, 59 ), d->mDateStart.timeSpec() ) );
01371 }
01372
01373 KDateTime RecurrenceRule::getPreviousDate( const KDateTime &afterDate ) const
01374 {
01375
01376 KDateTime toDate( afterDate.toTimeSpec( d->mDateStart.timeSpec() ) );
01377
01378
01379 if ( !toDate.isValid() || toDate < startDt() ) {
01380 return KDateTime();
01381 }
01382
01383
01384 if ( d->mDuration > 0 ) {
01385 if ( !d->mCached ) {
01386 d->buildCache();
01387 }
01388 int i = d->mCachedDates.findLT( toDate );
01389 if ( i >= 0 ) {
01390 return d->mCachedDates[i];
01391 }
01392 return KDateTime();
01393 }
01394
01395 KDateTime prev = toDate;
01396 if ( d->mDuration >= 0 && endDt().isValid() && toDate > endDt() ) {
01397 prev = endDt().addSecs( 1 ).toTimeSpec( d->mDateStart.timeSpec() );
01398 }
01399
01400 Constraint interval( d->getPreviousValidDateInterval( prev, recurrenceType() ) );
01401 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
01402 int i = dts.findLT( prev );
01403 if ( i >= 0 ) {
01404 return ( dts[i] >= startDt() ) ? dts[i] : KDateTime();
01405 }
01406
01407
01408 while ( interval.intervalDateTime( recurrenceType() ) > startDt() ) {
01409 interval.increase( recurrenceType(), -int( frequency() ) );
01410
01411 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
01412
01413 if ( !dts.isEmpty() ) {
01414 prev = dts.last();
01415 if ( prev.isValid() && prev >= startDt() ) {
01416 return prev;
01417 } else {
01418 return KDateTime();
01419 }
01420 }
01421 }
01422 return KDateTime();
01423 }
01424
01425 KDateTime RecurrenceRule::getNextDate( const KDateTime &preDate ) const
01426 {
01427
01428 KDateTime fromDate( preDate.toTimeSpec( d->mDateStart.timeSpec() ) );
01429
01430 if ( d->mDuration >= 0 && endDt().isValid() && fromDate >= endDt() ) {
01431 return KDateTime();
01432 }
01433
01434
01435 if ( fromDate < startDt() ) {
01436 fromDate = startDt().addSecs( -1 );
01437 }
01438
01439 if ( d->mDuration > 0 ) {
01440 if ( !d->mCached ) {
01441 d->buildCache();
01442 }
01443 int i = d->mCachedDates.findGT( fromDate );
01444 if ( i >= 0 ) {
01445 return d->mCachedDates[i];
01446 }
01447 }
01448
01449 KDateTime end = endDt();
01450 Constraint interval( d->getNextValidDateInterval( fromDate, recurrenceType() ) );
01451 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
01452 int i = dts.findGT( fromDate );
01453 if ( i >= 0 ) {
01454 return ( d->mDuration < 0 || dts[i] <= end ) ? dts[i] : KDateTime();
01455 }
01456 interval.increase( recurrenceType(), frequency() );
01457 if ( d->mDuration >= 0 && interval.intervalDateTime( recurrenceType() ) > end ) {
01458 return KDateTime();
01459 }
01460
01461
01462
01463
01464 int loop = 0;
01465 do {
01466 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
01467 if ( dts.count() > 0 ) {
01468 KDateTime ret( dts[0] );
01469 if ( d->mDuration >= 0 && ret > end ) {
01470 return KDateTime();
01471 } else {
01472 return ret;
01473 }
01474 }
01475 interval.increase( recurrenceType(), frequency() );
01476 } while ( ++loop < LOOP_LIMIT &&
01477 ( d->mDuration < 0 || interval.intervalDateTime( recurrenceType() ) < end ) );
01478 return KDateTime();
01479 }
01480
01481 DateTimeList RecurrenceRule::timesInInterval( const KDateTime &start, const KDateTime &end ) const
01482 {
01483 DateTimeList result;
01484 if ( end < startDt() ) {
01485 return result;
01486 }
01487 KDateTime enddt = end;
01488 if ( d->mDuration >= 0 ) {
01489 KDateTime endRecur = endDt();
01490 if ( endRecur.isValid() ) {
01491 if ( start >= endRecur ) {
01492 return result;
01493 }
01494 if ( end > endRecur ) {
01495 enddt = endRecur;
01496 }
01497 }
01498 }
01499
01500 KDateTime st = start;
01501 bool done = false;
01502 if ( d->mDuration > 0 ) {
01503 if ( !d->mCached ) {
01504 d->buildCache();
01505 }
01506 if ( d->mCachedDateEnd.isValid() && start >= d->mCachedDateEnd ) {
01507 return result;
01508 }
01509 int i = d->mCachedDates.findGE( start );
01510 if ( i >= 0 ) {
01511 int iend = d->mCachedDates.findGT( enddt, i );
01512 if ( iend < 0 ) {
01513 iend = d->mCachedDates.count();
01514 } else {
01515 done = true;
01516 }
01517 while ( i < iend ) {
01518 result += d->mCachedDates[i++];
01519 }
01520 }
01521 if ( d->mCachedDateEnd.isValid() ) {
01522 done = true;
01523 } else if ( !result.isEmpty() ) {
01524 result += KDateTime();
01525 done = true;
01526 }
01527 if ( done ) {
01528 return result;
01529 }
01530
01531 st = d->mCachedLastDate.addSecs( 1 );
01532 }
01533
01534 Constraint interval( d->getNextValidDateInterval( st, recurrenceType() ) );
01535 int loop = 0;
01536 do {
01537 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
01538 int i = 0;
01539 int iend = dts.count();
01540 if ( loop == 0 ) {
01541 i = dts.findGE( st );
01542 if ( i < 0 ) {
01543 i = iend;
01544 }
01545 }
01546 int j = dts.findGT( enddt, i );
01547 if ( j >= 0 ) {
01548 iend = j;
01549 loop = LOOP_LIMIT;
01550 }
01551 while ( i < iend ) {
01552 result += dts[i++];
01553 }
01554
01555 interval.increase( recurrenceType(), frequency() );
01556 } while ( ++loop < LOOP_LIMIT &&
01557 interval.intervalDateTime( recurrenceType() ) < end );
01558 return result;
01559 }
01560
01561
01562
01563
01564
01565
01566 Constraint RecurrenceRule::Private::getPreviousValidDateInterval( const KDateTime &dt,
01567 PeriodType type ) const
01568 {
01569 long periods = 0;
01570 KDateTime start = mDateStart;
01571 KDateTime nextValid( start );
01572 int modifier = 1;
01573 KDateTime toDate( dt.toTimeSpec( start.timeSpec() ) );
01574
01575
01576
01577
01578 switch ( type ) {
01579
01580
01581 case rHourly:
01582 modifier *= 60;
01583 case rMinutely:
01584 modifier *= 60;
01585 case rSecondly:
01586 periods = static_cast<int>( start.secsTo_long( toDate ) / modifier );
01587
01588 periods = ( periods / mFrequency ) * mFrequency;
01589 nextValid = start.addSecs( modifier * periods );
01590 break;
01591 case rWeekly:
01592 toDate = toDate.addDays( -( 7 + toDate.date().dayOfWeek() - mWeekStart ) % 7 );
01593 start = start.addDays( -( 7 + start.date().dayOfWeek() - mWeekStart ) % 7 );
01594 modifier *= 7;
01595 case rDaily:
01596 periods = start.daysTo( toDate ) / modifier;
01597
01598 periods = ( periods / mFrequency ) * mFrequency;
01599 nextValid = start.addDays( modifier * periods );
01600 break;
01601 case rMonthly:
01602 {
01603 periods = 12 * ( toDate.date().year() - start.date().year() ) +
01604 ( toDate.date().month() - start.date().month() );
01605
01606 periods = ( periods / mFrequency ) * mFrequency;
01607
01608
01609 start.setDate( QDate( start.date().year(), start.date().month(), 1 ) );
01610 nextValid.setDate( start.date().addMonths( periods ) );
01611 break; }
01612 case rYearly:
01613 periods = ( toDate.date().year() - start.date().year() );
01614
01615 periods = ( periods / mFrequency ) * mFrequency;
01616 nextValid.setDate( start.date().addYears( periods ) );
01617 break;
01618 default:
01619 break;
01620 }
01621
01622 return Constraint( nextValid, type, mWeekStart );
01623 }
01624
01625
01626
01627
01628
01629 Constraint RecurrenceRule::Private::getNextValidDateInterval( const KDateTime &dt,
01630 PeriodType type ) const
01631 {
01632
01633 long periods = 0;
01634 KDateTime start = mDateStart;
01635 KDateTime nextValid( start );
01636 int modifier = 1;
01637 KDateTime toDate( dt.toTimeSpec( start.timeSpec() ) );
01638
01639
01640
01641
01642 switch ( type ) {
01643
01644
01645 case rHourly:
01646 modifier *= 60;
01647 case rMinutely:
01648 modifier *= 60;
01649 case rSecondly:
01650 periods = static_cast<int>( start.secsTo_long( toDate ) / modifier );
01651 periods = qMax( 0L, periods );
01652 if ( periods > 0 ) {
01653 periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
01654 }
01655 nextValid = start.addSecs( modifier * periods );
01656 break;
01657 case rWeekly:
01658
01659 toDate = toDate.addDays( -( 7 + toDate.date().dayOfWeek() - mWeekStart ) % 7 );
01660 start = start.addDays( -( 7 + start.date().dayOfWeek() - mWeekStart ) % 7 );
01661 modifier *= 7;
01662 case rDaily:
01663 periods = start.daysTo( toDate ) / modifier;
01664 periods = qMax( 0L, periods );
01665 if ( periods > 0 ) {
01666 periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
01667 }
01668 nextValid = start.addDays( modifier * periods );
01669 break;
01670 case rMonthly:
01671 {
01672 periods = 12 * ( toDate.date().year() - start.date().year() ) +
01673 ( toDate.date().month() - start.date().month() );
01674 periods = qMax( 0L, periods );
01675 if ( periods > 0 ) {
01676 periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
01677 }
01678
01679
01680 start.setDate( QDate( start.date().year(), start.date().month(), 1 ) );
01681 nextValid.setDate( start.date().addMonths( periods ) );
01682 break;
01683 }
01684 case rYearly:
01685 periods = ( toDate.date().year() - start.date().year() );
01686 periods = qMax( 0L, periods );
01687 if ( periods > 0 ) {
01688 periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
01689 }
01690 nextValid.setDate( start.date().addYears( periods ) );
01691 break;
01692 default:
01693 break;
01694 }
01695
01696 return Constraint( nextValid, type, mWeekStart );
01697 }
01698
01699 bool RecurrenceRule::Private::mergeIntervalConstraint( Constraint *merged,
01700 const Constraint &conit,
01701 const Constraint &interval ) const
01702 {
01703 Constraint result( interval );
01704
01705 #define mergeConstraint( name, cmparison ) \
01706 if ( conit.name cmparison ) { \
01707 if ( !( result.name cmparison ) || result.name == conit.name ) { \
01708 result.name = conit.name; \
01709 } else { \
01710 return false; \
01711 } \
01712 }
01713
01714 mergeConstraint( year, > 0 );
01715 mergeConstraint( month, > 0 );
01716 mergeConstraint( day, != 0 );
01717 mergeConstraint( hour, >= 0 );
01718 mergeConstraint( minute, >= 0 );
01719 mergeConstraint( second, >= 0 );
01720
01721 mergeConstraint( weekday, != 0 );
01722 mergeConstraint( weekdaynr, != 0 );
01723 mergeConstraint( weeknumber, != 0 );
01724 mergeConstraint( yearday, != 0 );
01725
01726 #undef mergeConstraint
01727 if ( merged ) {
01728 *merged = result;
01729 }
01730 return true;
01731 }
01732
01733 DateTimeList RecurrenceRule::Private::datesForInterval( const Constraint &interval,
01734 PeriodType type ) const
01735 {
01736
01737
01738
01739
01740
01741
01742 DateTimeList lst;
01743 for ( int i = 0, iend = mConstraints.count(); i < iend; ++i ) {
01744 Constraint merged( mDateStart.timeSpec() );
01745 bool mergeok = mergeIntervalConstraint( &merged, mConstraints[i], interval );
01746
01747 if ( merged.year <= 0 || merged.hour < 0 || merged.minute < 0 || merged.second < 0 ) {
01748 mergeok = false;
01749 }
01750 if ( mergeok ) {
01751
01752
01753 QList<KDateTime> lstnew = merged.dateTimes( type );
01754 lst += lstnew;
01755 }
01756 }
01757
01758 lst.sortUnique();
01759
01760
01761
01762
01763
01764
01765
01766
01767
01768
01769 if ( !mBySetPos.isEmpty() ) {
01770 DateTimeList tmplst = lst;
01771 lst.clear();
01772 for ( int i = 0, iend = mBySetPos.count(); i < iend; ++i ) {
01773 int pos = mBySetPos[i];
01774 if ( pos > 0 ) {
01775 --pos;
01776 }
01777 if ( pos < 0 ) {
01778 pos += tmplst.count();
01779 }
01780 if ( pos >= 0 && pos < tmplst.count() ) {
01781 lst.append( tmplst[pos] );
01782 }
01783 }
01784 lst.sortUnique();
01785 }
01786
01787 return lst;
01788 }
01789
01790
01791 void RecurrenceRule::dump() const
01792 {
01793 #ifndef NDEBUG
01794 kDebug(5800) << "RecurrenceRule::dump():";
01795 if ( !d->mRRule.isEmpty() ) {
01796 kDebug(5800) << " RRULE=" << d->mRRule;
01797 }
01798 kDebug(5800) << " Read-Only:" << isReadOnly()
01799 << ", dirty:" << d->mDirty;
01800
01801 kDebug(5800) << " Period type:" << recurrenceType()
01802 << ", frequency:" << frequency();
01803 kDebug(5800) << " #occurrences:" << duration();
01804 kDebug(5800) << " start date:" << dumpTime( startDt() )
01805 << ", end date:" << dumpTime( endDt() );
01806
01807 #define dumpByIntList(list,label) \
01808 if ( !list.isEmpty() ) {\
01809 QStringList lst;\
01810 for ( int i = 0, iend = list.count(); i < iend; ++i ) {\
01811 lst.append( QString::number( list[i] ) );\
01812 }\
01813 kDebug(5800) << " " << label << lst.join( ", " );\
01814 }
01815 dumpByIntList( d->mBySeconds, "BySeconds: " );
01816 dumpByIntList( d->mByMinutes, "ByMinutes: " );
01817 dumpByIntList( d->mByHours, "ByHours: " );
01818 if ( !d->mByDays.isEmpty() ) {
01819 QStringList lst;
01820 for ( int i = 0, iend = d->mByDays.count(); i < iend; ++i ) {\
01821 lst.append( ( d->mByDays[i].pos() ? QString::number( d->mByDays[i].pos() ) : "" ) +
01822 DateHelper::dayName( d->mByDays[i].day() ) );
01823 }
01824 kDebug(5800) << " ByDays: " << lst.join( ", " );
01825 }
01826 dumpByIntList( d->mByMonthDays, "ByMonthDays:" );
01827 dumpByIntList( d->mByYearDays, "ByYearDays: " );
01828 dumpByIntList( d->mByWeekNumbers, "ByWeekNr: " );
01829 dumpByIntList( d->mByMonths, "ByMonths: " );
01830 dumpByIntList( d->mBySetPos, "BySetPos: " );
01831 #undef dumpByIntList
01832
01833 kDebug(5800) << " Week start:" << DateHelper::dayName( d->mWeekStart );
01834
01835 kDebug(5800) << " Constraints:";
01836
01837 for ( int i = 0, iend = d->mConstraints.count(); i < iend; ++i ) {
01838 d->mConstraints[i].dump();
01839 }
01840 #endif
01841 }
01842
01843
01844 void Constraint::dump() const
01845 {
01846 kDebug(5800) << " ~> Y=" << year
01847 << ", M=" << month
01848 << ", D=" << day
01849 << ", H=" << hour
01850 << ", m=" << minute
01851 << ", S=" << second
01852 << ", wd=" << weekday
01853 << ",#wd=" << weekdaynr
01854 << ", #w=" << weeknumber
01855 << ", yd=" << yearday;
01856 }
01857
01858
01859 QString dumpTime( const KDateTime &dt )
01860 {
01861 if ( !dt.isValid() ) {
01862 return QString();
01863 }
01864 QString result;
01865 if ( dt.isDateOnly() ) {
01866 result = dt.toString( "%a %Y-%m-%d %:Z" );
01867 } else {
01868 result = dt.toString( "%a %Y-%m-%d %H:%M:%S %:Z" );
01869 if ( dt.isSecondOccurrence() ) {
01870 result += QLatin1String( " (2nd)" );
01871 }
01872 }
01873 if ( dt.timeSpec() == KDateTime::Spec::ClockTime() ) {
01874 result += QLatin1String( "Clock" );
01875 }
01876 return result;
01877 }
01878
01879 KDateTime RecurrenceRule::startDt() const
01880 {
01881 return d->mDateStart;
01882 }
01883
01884 RecurrenceRule::PeriodType RecurrenceRule::recurrenceType() const
01885 {
01886 return d->mPeriod;
01887 }
01888
01889 uint RecurrenceRule::frequency() const
01890 {
01891 return d->mFrequency;
01892 }
01893
01894 int RecurrenceRule::duration() const
01895 {
01896 return d->mDuration;
01897 }
01898
01899 QString RecurrenceRule::rrule() const
01900 {
01901 return d->mRRule;
01902 }
01903
01904 void RecurrenceRule::setRRule( const QString &rrule )
01905 {
01906 d->mRRule = rrule;
01907 }
01908
01909 bool RecurrenceRule::isReadOnly() const
01910 {
01911 return d->mIsReadOnly;
01912 }
01913
01914 void RecurrenceRule::setReadOnly( bool readOnly )
01915 {
01916 d->mIsReadOnly = readOnly;
01917 }
01918
01919 bool RecurrenceRule::recurs() const
01920 {
01921 return d->mPeriod != rNone;
01922 }
01923
01924 bool RecurrenceRule::allDay() const
01925 {
01926 return d->mAllDay;
01927 }
01928
01929 const QList<int> &RecurrenceRule::bySeconds() const
01930 {
01931 return d->mBySeconds;
01932 }
01933
01934 const QList<int> &RecurrenceRule::byMinutes() const
01935 {
01936 return d->mByMinutes;
01937 }
01938
01939 const QList<int> &RecurrenceRule::byHours() const
01940 {
01941 return d->mByHours;
01942 }
01943
01944 const QList<RecurrenceRule::WDayPos> &RecurrenceRule::byDays() const
01945 {
01946 return d->mByDays;
01947 }
01948
01949 const QList<int> &RecurrenceRule::byMonthDays() const
01950 {
01951 return d->mByMonthDays;
01952 }
01953
01954 const QList<int> &RecurrenceRule::byYearDays() const
01955 {
01956 return d->mByYearDays;
01957 }
01958
01959 const QList<int> &RecurrenceRule::byWeekNumbers() const
01960 {
01961 return d->mByWeekNumbers;
01962 }
01963
01964 const QList<int> &RecurrenceRule::byMonths() const
01965 {
01966 return d->mByMonths;
01967 }
01968
01969 const QList<int> &RecurrenceRule::bySetPos() const
01970 {
01971 return d->mBySetPos;
01972 }
01973
01974 short RecurrenceRule::weekStart() const
01975 {
01976 return d->mWeekStart;
01977 }