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 #include <qdatetime.h>
00028 #include <qstringlist.h>
00029
00030 #include <limits.h>
00031 #include <math.h>
00032
00033 using namespace KCal;
00034
00035
00036
00037
00038
00058 long long ownSecsTo( const QDateTime &dt1, const QDateTime &dt2 )
00059 {
00060 long long res = static_cast<long long>( dt1.date().daysTo( dt2.date() ) ) * 24*3600;
00061 res += dt1.time().secsTo( dt2.time() );
00062 return res;
00063 }
00064
00065
00066
00067
00068
00069
00070
00071
00072 class DateHelper {
00073 public:
00074 #ifndef NDEBUG
00075 static QString dayName( short day );
00076 #endif
00077 static QDate getNthWeek( int year, int weeknumber, short weekstart = 1 );
00078 static int weekNumbersInYear( int year, short weekstart = 1 );
00079 static int getWeekNumber( const QDate &date, short weekstart, int *year = 0 );
00080 static int getWeekNumberNeg( const QDate &date, short weekstart, int *year = 0 );
00081 };
00082
00083
00084 #ifndef NDEBUG
00085 QString DateHelper::dayName( short day )
00086 {
00087 switch ( day ) {
00088 case 1: return "MO"; break;
00089 case 2: return "TU"; break;
00090 case 3: return "WE"; break;
00091 case 4: return "TH"; break;
00092 case 5: return "FR"; break;
00093 case 6: return "SA"; break;
00094 case 7: return "SU"; break;
00095 default: return "??";
00096 }
00097 }
00098 #endif
00099
00100
00101 QDate DateHelper::getNthWeek( int year, int weeknumber, short weekstart )
00102 {
00103 if ( weeknumber == 0 ) return QDate();
00104
00105 QDate dt( year, 1, 4 );
00106 int adjust = -(7 + dt.dayOfWeek() - weekstart) % 7;
00107 if ( weeknumber > 0 ) {
00108 dt = dt.addDays( 7 * (weeknumber-1) + adjust );
00109 } else if ( weeknumber < 0 ) {
00110 dt = dt.addYears( 1 );
00111 dt = dt.addDays( 7 * weeknumber + adjust );
00112 }
00113 return dt;
00114 }
00115
00116
00117 int DateHelper::getWeekNumber( const QDate &date, short weekstart, int *year )
00118 {
00119
00120 if ( year ) *year = date.year();
00121 QDate dt( date.year(), 1, 4 );
00122 dt = dt.addDays( -(7 + dt.dayOfWeek() - weekstart) % 7 );
00123 QDate dtn( date.year()+1, 1, 4 );
00124 dtn = dtn.addDays( -(7 + dtn.dayOfWeek() - weekstart) % 7 );
00125
00126 int daysto = dt.daysTo( date );
00127 int dayston = dtn.daysTo( date );
00128 if ( daysto < 0 ) {
00129 if ( year ) *year = date.year()-1;
00130 dt = QDate( date.year()-1, 1, 4 );
00131 dt = dt.addDays( -(7 + dt.dayOfWeek() - weekstart) % 7 );
00132 daysto = dt.daysTo( date );
00133 } else if ( dayston >= 0 ) {
00134
00135 if ( year ) *year = date.year() + 1;
00136 dt = dtn;
00137 daysto = dayston;
00138 }
00139 return daysto / 7 + 1;
00140 }
00141
00142 int DateHelper::weekNumbersInYear( int year, short weekstart )
00143 {
00144 QDate dt( year, 1, weekstart );
00145 QDate dt1( year + 1, 1, weekstart );
00146 return dt.daysTo( dt1 ) / 7;
00147 }
00148
00149
00150 int DateHelper::getWeekNumberNeg( const QDate &date, short weekstart, int *year )
00151 {
00152 int weekpos = getWeekNumber( date, weekstart, year );
00153 return weekNumbersInYear( *year, weekstart ) - weekpos - 1;
00154 }
00155
00156
00157
00158
00159
00160
00161
00162
00163
00164
00165 RecurrenceRule::Constraint::Constraint( int wkst )
00166 {
00167 weekstart = wkst;
00168 clear();
00169 }
00170
00171 RecurrenceRule::Constraint::Constraint( const QDateTime &preDate, PeriodType type, int wkst )
00172 {
00173 weekstart = wkst;
00174 readDateTime( preDate, type );
00175 }
00176
00177 void RecurrenceRule::Constraint::clear()
00178 {
00179 year = 0;
00180 month = 0;
00181 day = 0;
00182 hour = -1;
00183 minute = -1;
00184 second = -1;
00185 weekday = 0;
00186 weekdaynr = 0;
00187 weeknumber = 0;
00188 yearday = 0;
00189 }
00190
00191 bool RecurrenceRule::Constraint::matches( const QDate &dt, RecurrenceRule::PeriodType type ) const
00192 {
00193
00194
00195
00196 if ( weeknumber == 0 ) {
00197 if ( year > 0 && year != dt.year() ) return false;
00198 } else {
00199 int y;
00200 if ( weeknumber > 0 &&
00201 weeknumber != DateHelper::getWeekNumber( dt, weekstart, &y ) ) return false;
00202 if ( weeknumber < 0 &&
00203 weeknumber != DateHelper::getWeekNumberNeg( dt, weekstart, &y ) ) return false;
00204 if ( year > 0 && year != y ) return false;
00205 }
00206
00207 if ( month > 0 && month != dt.month() ) return false;
00208 if ( day > 0 && day != dt.day() ) return false;
00209 if ( day < 0 && dt.day() != (dt.daysInMonth() + day + 1 ) ) return false;
00210 if ( weekday > 0 ) {
00211 if ( weekday != dt.dayOfWeek() ) return false;
00212 if ( weekdaynr != 0 ) {
00213
00214
00215 bool inMonth = (type == rMonthly) || ( type == rYearly && month > 0 );
00216
00217 if ( weekdaynr > 0 && inMonth &&
00218 weekdaynr != (dt.day() - 1)/7 + 1 ) return false;
00219 if ( weekdaynr < 0 && inMonth &&
00220 weekdaynr != -((dt.daysInMonth() - dt.day() )/7 + 1 ) )
00221 return false;
00222
00223 if ( weekdaynr > 0 && !inMonth &&
00224 weekdaynr != (dt.dayOfYear() - 1)/7 + 1 ) return false;
00225 if ( weekdaynr < 0 && !inMonth &&
00226 weekdaynr != -((dt.daysInYear() - dt.dayOfYear() )/7 + 1 ) )
00227 return false;
00228 }
00229 }
00230 if ( yearday > 0 && yearday != dt.dayOfYear() ) return false;
00231 if ( yearday < 0 && yearday != dt.daysInYear() - dt.dayOfYear() + 1 )
00232 return false;
00233 return true;
00234 }
00235
00236 bool RecurrenceRule::Constraint::matches( const QDateTime &dt, RecurrenceRule::PeriodType type ) const
00237 {
00238 if ( !matches( dt.date(), type ) ) return false;
00239 if ( hour >= 0 && hour != dt.time().hour() ) return false;
00240 if ( minute >= 0 && minute != dt.time().minute() ) return false;
00241 if ( second >= 0 && second != dt.time().second() ) return false;
00242 return true;
00243 }
00244
00245 bool RecurrenceRule::Constraint::isConsistent( PeriodType ) const
00246 {
00247
00248 return true;
00249 }
00250
00251 QDateTime RecurrenceRule::Constraint::intervalDateTime( RecurrenceRule::PeriodType type ) const
00252 {
00253 QDateTime dt;
00254 dt.setTime( QTime( 0, 0, 0 ) );
00255 dt.setDate( QDate( year, (month>0)?month:1, (day>0)?day:1 ) );
00256 if ( day < 0 )
00257 dt = dt.addDays( dt.date().daysInMonth() + day );
00258 switch ( type ) {
00259 case rSecondly:
00260 dt.setTime( QTime( hour, minute, second ) ); break;
00261 case rMinutely:
00262 dt.setTime( QTime( hour, minute, 1 ) ); break;
00263 case rHourly:
00264 dt.setTime( QTime( hour, 1, 1 ) ); break;
00265 case rDaily:
00266 break;
00267 case rWeekly:
00268 dt = DateHelper::getNthWeek( year, weeknumber, weekstart ); break;
00269 case rMonthly:
00270 dt.setDate( QDate( year, month, 1 ) ); break;
00271 case rYearly:
00272 dt.setDate( QDate( year, 1, 1 ) ); break;
00273 default:
00274 break;
00275 }
00276 return dt;
00277 }
00278
00279
00280
00281
00282
00283
00284
00285
00286
00287
00288
00289
00290
00291
00292
00293
00294
00295
00296
00297 DateTimeList RecurrenceRule::Constraint::dateTimes( RecurrenceRule::PeriodType type ) const
00298 {
00299
00300 DateTimeList result;
00301 bool done = false;
00302
00303 QTime tm( hour, minute, second );
00304 if ( !isConsistent( type ) ) return result;
00305
00306 if ( !done && day > 0 && month > 0 ) {
00307 QDateTime dt( QDate( year, month, day ), tm );
00308 if ( dt.isValid() ) result.append( dt );
00309 done = true;
00310 }
00311 if ( !done && day < 0 && month > 0 ) {
00312 QDateTime dt( QDate( year, month, 1 ), tm );
00313 dt = dt.addDays( dt.date().daysInMonth() + day );
00314 if ( dt.isValid() ) result.append( dt );
00315 done = true;
00316 }
00317
00318
00319 if ( !done && weekday == 0 && weeknumber == 0 && yearday == 0 ) {
00320
00321 uint mstart = (month>0) ? month : 1;
00322 uint mend = (month <= 0) ? 12 : month;
00323 for ( uint m = mstart; m <= mend; ++m ) {
00324 uint dstart, dend;
00325 if ( day > 0 ) {
00326 dstart = dend = day;
00327 } else if ( day < 0 ) {
00328 QDate date( year, month, 1 );
00329 dstart = dend = date.daysInMonth() + day + 1;
00330 } else {
00331 QDate date( year, month, 1 );
00332 dstart = 1;
00333 dend = date.daysInMonth();
00334 }
00335 for ( uint d = dstart; d <= dend; ++d ) {
00336 QDateTime dt( QDate( year, m, d ), tm );
00337 if ( dt.isValid() ) result.append( dt );
00338 }
00339 }
00340 done = true;
00341 }
00342
00343
00344
00345 if ( !done && yearday != 0 ) {
00346
00347 QDate d( year + ((yearday>0)?0:1), 1, 1 );
00348 d = d.addDays( yearday - ((yearday>0)?1:0) );
00349 result.append( QDateTime( d, tm ) );
00350 done = true;
00351 }
00352
00353
00354 if ( !done && weeknumber != 0 ) {
00355 QDate wst( DateHelper::getNthWeek( year, weeknumber, weekstart ) );
00356 if ( weekday != 0 ) {
00357 wst = wst.addDays( (7 + weekday - weekstart ) % 7 );
00358 result.append( QDateTime( wst, tm ) );
00359 } else {
00360 for ( int i = 0; i < 7; ++i ) {
00361 result.append( QDateTime( wst, tm ) );
00362 wst = wst.addDays( 1 );
00363 }
00364 }
00365 done = true;
00366 }
00367
00368
00369 if ( !done && weekday != 0 ) {
00370 QDate dt( year, 1, 1 );
00371
00372
00373 int maxloop = 53;
00374 bool inMonth = ( type == rMonthly) || ( type == rYearly && month > 0 );
00375 if ( inMonth && month > 0 ) {
00376 dt = QDate( year, month, 1 );
00377 maxloop = 5;
00378 }
00379 if ( weekdaynr < 0 ) {
00380
00381 if ( inMonth )
00382 dt = dt.addMonths( 1 );
00383 else
00384 dt = dt.addYears( 1 );
00385 }
00386 int adj = ( 7 + weekday - dt.dayOfWeek() ) % 7;
00387 dt = dt.addDays( adj );
00388
00389 if ( weekdaynr > 0 ) {
00390 dt = dt.addDays( ( weekdaynr - 1 ) * 7 );
00391 result.append( QDateTime( dt, tm ) );
00392 } else if ( weekdaynr < 0 ) {
00393 dt = dt.addDays( weekdaynr * 7 );
00394 result.append( QDateTime( dt, tm ) );
00395 } else {
00396
00397 for ( int i = 0; i < maxloop; ++i ) {
00398 result.append( QDateTime( dt, tm ) );
00399 dt = dt.addDays( 7 );
00400 }
00401 }
00402 }
00403
00404
00405
00406 DateTimeList valid;
00407 DateTimeList::Iterator it;
00408 for ( it = result.begin(); it != result.end(); ++it ) {
00409 if ( matches( *it, type ) ) valid.append( *it );
00410 }
00411
00412
00413 return valid;
00414 }
00415
00416
00417 bool RecurrenceRule::Constraint::increase( RecurrenceRule::PeriodType type, int freq )
00418 {
00419
00420
00421 QDateTime dt( intervalDateTime( type ) );
00422
00423
00424 switch ( type ) {
00425 case rSecondly:
00426 dt = dt.addSecs( freq ); break;
00427 case rMinutely:
00428 dt = dt.addSecs( 60*freq ); break;
00429 case rHourly:
00430 dt = dt.addSecs( 3600 * freq ); break;
00431 case rDaily:
00432 dt = dt.addDays( freq ); break;
00433 case rWeekly:
00434 dt = dt.addDays( 7*freq ); break;
00435 case rMonthly:
00436 dt = dt.addMonths( freq ); break;
00437 case rYearly:
00438 dt = dt.addYears( freq ); break;
00439 default:
00440 break;
00441 }
00442
00443 readDateTime( dt, type );
00444
00445 return true;
00446 }
00447
00448 bool RecurrenceRule::Constraint::readDateTime( const QDateTime &preDate, PeriodType type )
00449 {
00450 clear();
00451 switch ( type ) {
00452
00453 case rSecondly:
00454 second = preDate.time().second();
00455 case rMinutely:
00456 minute = preDate.time().minute();
00457 case rHourly:
00458 hour = preDate.time().hour();
00459 case rDaily:
00460 day = preDate.date().day();
00461 case rMonthly:
00462 month = preDate.date().month();
00463 case rYearly:
00464 year = preDate.date().year();
00465 break;
00466
00467 case rWeekly:
00468
00469 weeknumber = DateHelper::getWeekNumber( preDate.date(), weekstart, &year );
00470 break;
00471 default:
00472 break;
00473 }
00474 return true;
00475 }
00476
00477
00478 RecurrenceRule::RecurrenceRule( )
00479 : mPeriod( rNone ), mFrequency( 0 ), mIsReadOnly( false ),
00480 mFloating( false ),
00481 mWeekStart(1)
00482 {
00483 }
00484
00485 RecurrenceRule::RecurrenceRule( const RecurrenceRule &r )
00486 {
00487 mRRule = r.mRRule;
00488 mPeriod = r.mPeriod;
00489 mDateStart = r.mDateStart;
00490 mDuration = r.mDuration;
00491 mDateEnd = r.mDateEnd;
00492 mFrequency = r.mFrequency;
00493
00494 mIsReadOnly = r.mIsReadOnly;
00495 mFloating = r.mFloating;
00496
00497 mBySeconds = r.mBySeconds;
00498 mByMinutes = r.mByMinutes;
00499 mByHours = r.mByHours;
00500 mByDays = r.mByDays;
00501 mByMonthDays = r.mByMonthDays;
00502 mByYearDays = r.mByYearDays;
00503 mByWeekNumbers = r.mByWeekNumbers;
00504 mByMonths = r.mByMonths;
00505 mBySetPos = r.mBySetPos;
00506 mWeekStart = r.mWeekStart;
00507
00508 setDirty();
00509 }
00510
00511 RecurrenceRule::~RecurrenceRule()
00512 {
00513 }
00514
00515 bool RecurrenceRule::operator==( const RecurrenceRule& r ) const
00516 {
00517 if ( mPeriod != r.mPeriod ) return false;
00518 if ( mDateStart != r.mDateStart ) return false;
00519 if ( mDuration != r.mDuration ) return false;
00520 if ( mDateEnd != r.mDateEnd ) return false;
00521 if ( mFrequency != r.mFrequency ) return false;
00522
00523 if ( mIsReadOnly != r.mIsReadOnly ) return false;
00524 if ( mFloating != r.mFloating ) return false;
00525
00526 if ( mBySeconds != r.mBySeconds ) return false;
00527 if ( mByMinutes != r.mByMinutes ) return false;
00528 if ( mByHours != r.mByHours ) return false;
00529 if ( mByDays != r.mByDays ) return false;
00530 if ( mByMonthDays != r.mByMonthDays ) return false;
00531 if ( mByYearDays != r.mByYearDays ) return false;
00532 if ( mByWeekNumbers != r.mByWeekNumbers ) return false;
00533 if ( mByMonths != r.mByMonths ) return false;
00534 if ( mBySetPos != r.mBySetPos ) return false;
00535 if ( mWeekStart != r.mWeekStart ) return false;
00536
00537 return true;
00538 }
00539
00540 void RecurrenceRule::addObserver( Observer *observer )
00541 {
00542 if ( !mObservers.contains( observer ) )
00543 mObservers.append( observer );
00544 }
00545
00546 void RecurrenceRule::removeObserver( Observer *observer )
00547 {
00548 if ( mObservers.contains( observer ) )
00549 mObservers.remove( observer );
00550 }
00551
00552
00553
00554 void RecurrenceRule::setRecurrenceType( PeriodType period )
00555 {
00556 if ( isReadOnly() ) return;
00557 mPeriod = period;
00558 setDirty();
00559 }
00560
00561
00562 QDateTime RecurrenceRule::endDt( bool *result ) const
00563 {
00564 if ( result ) *result = false;
00565 if ( mPeriod == rNone ) return QDateTime();
00566 if ( mDuration < 0 ) {
00567 if ( result ) result = false;
00568 return QDateTime();
00569 } else if ( mDuration == 0 ) {
00570 return mDateEnd;
00571 } else {
00572
00573 if ( ! mCached ) {
00574
00575 if ( !buildCache() ) {
00576 if ( result ) result = false;
00577 return QDateTime();
00578 }
00579 }
00580 return mCachedDateEnd;
00581 }
00582 return QDateTime();
00583 }
00584
00585 void RecurrenceRule::setEndDt( const QDateTime &dateTime )
00586 {
00587 if ( isReadOnly() ) return;
00588 mDateEnd = dateTime;
00589 mDuration = 0;
00590 setDirty();
00591 }
00592
00593 void RecurrenceRule::setDuration(int duration)
00594 {
00595 if ( isReadOnly() ) return;
00596 mDuration = duration;
00597 setDirty();
00598 }
00599
00600 void RecurrenceRule::setFloats( bool floats )
00601 {
00602 if ( isReadOnly() ) return;
00603 mFloating = floats;
00604 setDirty();
00605 }
00606
00607 void RecurrenceRule::clear()
00608 {
00609 if ( isReadOnly() ) return;
00610 mPeriod = rNone;
00611 mBySeconds.clear();
00612 mByMinutes.clear();
00613 mByHours.clear();
00614 mByDays.clear();
00615 mByMonthDays.clear();
00616 mByYearDays.clear();
00617 mByWeekNumbers.clear();
00618 mByMonths.clear();
00619 mBySetPos.clear();
00620 mWeekStart = 1;
00621
00622 setDirty();
00623 }
00624
00625 void RecurrenceRule::setDirty()
00626 {
00627 mConstraints.clear();
00628 buildConstraints();
00629 mDirty = true;
00630 mCached = false;
00631 mCachedDates.clear();
00632 for ( QValueList<Observer*>::ConstIterator it = mObservers.begin();
00633 it != mObservers.end(); ++it ) {
00634 if ( (*it) ) (*it)->recurrenceChanged( this );
00635 }
00636 }
00637
00638 void RecurrenceRule::setStartDt( const QDateTime &start )
00639 {
00640 if ( isReadOnly() ) return;
00641 mDateStart = start;
00642 setDirty();
00643 }
00644
00645 void RecurrenceRule::setFrequency(int freq)
00646 {
00647 if ( isReadOnly() || freq <= 0 ) return;
00648 mFrequency = freq;
00649 setDirty();
00650 }
00651
00652 void RecurrenceRule::setBySeconds( const QValueList<int> bySeconds )
00653 {
00654 if ( isReadOnly() ) return;
00655 mBySeconds = bySeconds;
00656 setDirty();
00657 }
00658
00659 void RecurrenceRule::setByMinutes( const QValueList<int> byMinutes )
00660 {
00661 if ( isReadOnly() ) return;
00662 mByMinutes = byMinutes;
00663 setDirty();
00664 }
00665
00666 void RecurrenceRule::setByHours( const QValueList<int> byHours )
00667 {
00668 if ( isReadOnly() ) return;
00669 mByHours = byHours;
00670 setDirty();
00671 }
00672
00673
00674 void RecurrenceRule::setByDays( const QValueList<WDayPos> byDays )
00675 {
00676 if ( isReadOnly() ) return;
00677 mByDays = byDays;
00678 setDirty();
00679 }
00680
00681 void RecurrenceRule::setByMonthDays( const QValueList<int> byMonthDays )
00682 {
00683 if ( isReadOnly() ) return;
00684 mByMonthDays = byMonthDays;
00685 setDirty();
00686 }
00687
00688 void RecurrenceRule::setByYearDays( const QValueList<int> byYearDays )
00689 {
00690 if ( isReadOnly() ) return;
00691 mByYearDays = byYearDays;
00692 setDirty();
00693 }
00694
00695 void RecurrenceRule::setByWeekNumbers( const QValueList<int> byWeekNumbers )
00696 {
00697 if ( isReadOnly() ) return;
00698 mByWeekNumbers = byWeekNumbers;
00699 setDirty();
00700 }
00701
00702 void RecurrenceRule::setByMonths( const QValueList<int> byMonths )
00703 {
00704 if ( isReadOnly() ) return;
00705 mByMonths = byMonths;
00706 setDirty();
00707 }
00708
00709 void RecurrenceRule::setBySetPos( const QValueList<int> bySetPos )
00710 {
00711 if ( isReadOnly() ) return;
00712 mBySetPos = bySetPos;
00713 setDirty();
00714 }
00715
00716 void RecurrenceRule::setWeekStart( short weekStart )
00717 {
00718 if ( isReadOnly() ) return;
00719 mWeekStart = weekStart;
00720 setDirty();
00721 }
00722
00723
00724
00725
00726
00727
00728
00729
00730
00731
00732
00733
00734
00735
00736
00737
00738
00739
00740
00741
00742
00743
00744
00745
00746
00747
00748
00749
00750
00751
00752
00753
00754
00755
00756
00757
00758
00759
00760
00761
00762
00763
00764
00765
00766
00767
00768
00769
00770
00771
00772
00773
00774
00775
00776
00777
00778
00779
00780
00781 void RecurrenceRule::buildConstraints()
00782 {
00783 mConstraints.clear();
00784 Constraint con;
00785 if ( mWeekStart > 0 ) con.weekstart = mWeekStart;
00786 mConstraints.append( con );
00787
00788 Constraint::List tmp;
00789 Constraint::List::const_iterator it;
00790 QValueList<int>::const_iterator intit;
00791
00792 #define intConstraint( list, element ) \
00793 if ( !list.isEmpty() ) { \
00794 for ( it = mConstraints.constBegin(); it != mConstraints.constEnd(); ++it ) { \
00795 for ( intit = list.constBegin(); intit != list.constEnd(); ++intit ) { \
00796 con = (*it); \
00797 con.element = (*intit); \
00798 tmp.append( con ); \
00799 } \
00800 } \
00801 mConstraints = tmp; \
00802 tmp.clear(); \
00803 }
00804
00805 intConstraint( mBySeconds, second );
00806 intConstraint( mByMinutes, minute );
00807 intConstraint( mByHours, hour );
00808 intConstraint( mByMonthDays, day );
00809 intConstraint( mByMonths, month );
00810 intConstraint( mByYearDays, yearday );
00811 intConstraint( mByWeekNumbers, weeknumber );
00812 #undef intConstraint
00813
00814 if ( !mByDays.isEmpty() ) {
00815 for ( it = mConstraints.constBegin(); it != mConstraints.constEnd(); ++it ) {
00816 QValueList<WDayPos>::const_iterator dayit;
00817 for ( dayit = mByDays.constBegin(); dayit != mByDays.constEnd(); ++dayit ) {
00818 con = (*it);
00819 con.weekday = (*dayit).day();
00820 con.weekdaynr = (*dayit).pos();
00821 tmp.append( con );
00822 }
00823 }
00824 mConstraints = tmp;
00825 tmp.clear();
00826 }
00827
00828 #define fixConstraint( element, value ) \
00829 { \
00830 tmp.clear(); \
00831 for ( it = mConstraints.constBegin(); it != mConstraints.constEnd(); ++it ) { \
00832 con = (*it); con.element = value; tmp.append( con ); \
00833 } \
00834 mConstraints = tmp; \
00835 }
00836
00837
00838
00839
00840 if ( mPeriod == rWeekly && mByDays.isEmpty() ) {
00841 fixConstraint( weekday, mDateStart.date().dayOfWeek() );
00842 }
00843
00844
00845
00846 switch ( mPeriod ) {
00847 case rYearly:
00848 if ( mByDays.isEmpty() && mByWeekNumbers.isEmpty() && mByYearDays.isEmpty() && mByMonths.isEmpty() ) {
00849 fixConstraint( month, mDateStart.date().month() );
00850 }
00851 case rMonthly:
00852 if ( mByDays.isEmpty() && mByWeekNumbers.isEmpty() && mByYearDays.isEmpty() && mByMonthDays.isEmpty() ) {
00853 fixConstraint( day, mDateStart.date().day() );
00854 }
00855
00856 case rWeekly:
00857 case rDaily:
00858 if ( mByHours.isEmpty() ) {
00859 fixConstraint( hour, mDateStart.time().hour() );
00860 }
00861 case rHourly:
00862 if ( mByMinutes.isEmpty() ) {
00863 fixConstraint( minute, mDateStart.time().minute() );
00864 }
00865 case rMinutely:
00866 if ( mBySeconds.isEmpty() ) {
00867 fixConstraint( second, mDateStart.time().second() );
00868 }
00869 case rSecondly:
00870 default:
00871 break;
00872 }
00873 #undef fixConstraint
00874
00875 Constraint::List::Iterator conit = mConstraints.begin();
00876 while ( conit != mConstraints.end() ) {
00877 if ( (*conit).isConsistent( mPeriod ) ) {
00878 ++conit;
00879 } else {
00880 conit = mConstraints.remove( conit );
00881 }
00882 }
00883 }
00884
00885 bool RecurrenceRule::buildCache() const
00886 {
00887 kdDebug(5800) << " RecurrenceRule::buildCache: " << endl;
00888
00889
00890 Constraint interval( getNextValidDateInterval( startDt(), recurrenceType() ) );
00891 QDateTime next;
00892
00893 DateTimeList dts = datesForInterval( interval, recurrenceType() );
00894 DateTimeList::Iterator it = dts.begin();
00895
00896
00897 while ( it != dts.end() ) {
00898 if ( (*it) < startDt() ) it = dts.remove( it );
00899 else ++it;
00900 }
00901
00902
00903
00904 int loopnr = 0;
00905 int dtnr = dts.count();
00906
00907
00908 while ( loopnr < 10000 && dtnr < mDuration ) {
00909 interval.increase( recurrenceType(), frequency() );
00910
00911 dts += datesForInterval( interval, recurrenceType() );
00912 dtnr = dts.count();
00913 ++loopnr;
00914 }
00915 if ( int(dts.count()) > mDuration ) {
00916
00917 it = dts.at( mDuration );
00918 while ( it != dts.end() ) it = dts.remove( it );
00919 }
00920 mCached = true;
00921 mCachedDates = dts;
00922
00923 kdDebug(5800) << " Finished Building Cache, cache has " << dts.count() << " entries:" << endl;
00924
00925
00926
00927
00928
00929 if ( int(dts.count()) == mDuration ) {
00930 mCachedDateEnd = dts.last();
00931 return true;
00932 } else {
00933 mCachedDateEnd = QDateTime();
00934 return false;
00935 }
00936 }
00937
00938 bool RecurrenceRule::dateMatchesRules( const QDateTime &qdt ) const
00939 {
00940 bool match = false;
00941 for ( Constraint::List::ConstIterator it = mConstraints.begin();
00942 it!=mConstraints.end(); ++it ) {
00943 match = match || ( (*it).matches( qdt, recurrenceType() ) );
00944 }
00945 return match;
00946 }
00947
00948 bool RecurrenceRule::recursOn( const QDate &qd ) const
00949 {
00950
00951 if ( qd < startDt().date() ) return false;
00952
00953
00954 if ( mDuration >= 0 && qd > endDt().date() ) return false;
00955
00956
00957
00958 bool match = false;
00959 for ( Constraint::List::ConstIterator it = mConstraints.begin();
00960 it!=mConstraints.end(); ++it ) {
00961 match = match || ( (*it).matches( qd, recurrenceType() ) );
00962 }
00963 if ( !match ) return false;
00964 QDateTime tmp( qd, QTime( 0, 0, 0 ) );
00965 Constraint interval( getNextValidDateInterval( tmp, recurrenceType() ) );
00966
00967
00968 if ( !interval.matches( qd, recurrenceType() ) ) return false;
00969
00970
00971
00972 DateTimeList times = datesForInterval( interval, recurrenceType() );
00973 DateTimeList::ConstIterator it = times.begin();
00974 while ( ( it != times.end() ) && ( (*it).date() < qd ) )
00975 ++it;
00976 if ( it != times.end() ) {
00977
00978 if ( mDuration >= 0 && (*it) > endDt() )
00979 return false;
00980 if ( (*it).date() == qd )
00981 return true;
00982 }
00983 return false;
00984 }
00985
00986
00987 bool RecurrenceRule::recursAt( const QDateTime &qd ) const
00988 {
00989
00990 if ( doesFloat() ) return recursOn( qd.date() );
00991 if ( qd < startDt() ) return false;
00992
00993
00994 if ( mDuration >= 0 && qd > endDt() ) return false;
00995
00996
00997
00998 bool match = dateMatchesRules( qd );
00999 if ( !match ) return false;
01000
01001
01002 Constraint interval( getNextValidDateInterval( qd, recurrenceType() ) );
01003
01004 if ( interval.matches( qd, recurrenceType() ) ) return true;
01005
01006 return false;
01007 }
01008
01009
01010 TimeList RecurrenceRule::recurTimesOn( const QDate &date ) const
01011 {
01012
01013 TimeList lst;
01014 if ( !recursOn( date ) ) return lst;
01015
01016 if ( doesFloat() ) return lst;
01017
01018 QDateTime dt( date, QTime( 0, 0, 0 ) );
01019 bool valid = dt.isValid() && ( dt.date() == date );
01020 while ( valid ) {
01021
01022 dt = getNextDate( dt );
01023 valid = dt.isValid() && ( dt.date() == date );
01024 if ( valid ) lst.append( dt.time() );
01025 }
01026 return lst;
01027 }
01028
01030 int RecurrenceRule::durationTo( const QDateTime &dt ) const
01031 {
01032
01033
01034
01035 if ( dt < startDt() ) return 0;
01036
01037
01038 if ( mDuration > 0 && dt >= endDt() ) return mDuration;
01039
01040 QDateTime next( startDt() );
01041 int found = 0;
01042 while ( next.isValid() && next <= dt ) {
01043 ++found;
01044 next = getNextDate( next );
01045 }
01046 return found;
01047 }
01048
01049
01050 QDateTime RecurrenceRule::getPreviousDate( const QDateTime& afterDate ) const
01051 {
01052
01053
01054 if ( afterDate < startDt() )
01055 return QDateTime();
01056
01057
01058 QDateTime prev;
01059 if ( mDuration > 0 ) {
01060 if ( !mCached ) buildCache();
01061 DateTimeList::ConstIterator it = mCachedDates.begin();
01062 while ( it != mCachedDates.end() && (*it) < afterDate ) {
01063 prev = *it;
01064 ++it;
01065 }
01066 if ( prev.isValid() && prev < afterDate ) return prev;
01067 else return QDateTime();
01068 }
01069
01070
01071 prev = afterDate;
01072 if ( mDuration >= 0 && endDt().isValid() && afterDate > endDt() )
01073 prev = endDt().addSecs( 1 );
01074
01075 Constraint interval( getPreviousValidDateInterval( prev, recurrenceType() ) );
01076
01077
01078 DateTimeList dts = datesForInterval( interval, recurrenceType() );
01079 DateTimeList::Iterator dtit = dts.end();
01080 if ( dtit != dts.begin() ) {
01081 do {
01082 --dtit;
01083 } while ( dtit != dts.begin() && (*dtit) >= prev );
01084 if ( (*dtit) < prev ) {
01085 if ( (*dtit) >= startDt() ) return (*dtit);
01086 else return QDateTime();
01087 }
01088 }
01089
01090
01091 while ( interval.intervalDateTime( recurrenceType() ) > startDt() ) {
01092 interval.increase( recurrenceType(), -frequency() );
01093
01094
01095
01096 DateTimeList dts = datesForInterval( interval, recurrenceType() );
01097
01098 if ( dts.count() > 0 ) {
01099 prev = dts.last();
01100 if ( prev.isValid() && prev >= startDt() ) return prev;
01101 else return QDateTime();
01102 }
01103 }
01104 return QDateTime();
01105 }
01106
01107
01108 QDateTime RecurrenceRule::getNextDate( const QDateTime &preDate ) const
01109 {
01110
01111
01112 if ( mDuration >= 0 && endDt().isValid() && preDate >= endDt() )
01113 return QDateTime();
01114
01115
01116
01117 if ( mDuration > 0 ) {
01118 if ( !mCached ) buildCache();
01119 DateTimeList::ConstIterator it = mCachedDates.begin();
01120 while ( it != mCachedDates.end() && (*it) <= preDate ) ++it;
01121 if ( it != mCachedDates.end() ) {
01122
01123 return (*it);
01124 }
01125 }
01126
01127
01128 Constraint interval( getNextValidDateInterval( preDate, recurrenceType() ) );
01129 DateTimeList dts = datesForInterval( interval, recurrenceType() );
01130 DateTimeList::Iterator dtit = dts.begin();
01131 while ( dtit != dts.end() && (*dtit) <= preDate ) ++dtit;
01132 if ( dtit != dts.end() ) {
01133 if ( mDuration >= 0 && (*dtit) > endDt() ) return QDateTime();
01134 else return (*dtit);
01135 }
01136
01137
01138
01139
01140 int loopnr = 0;
01141 while ( loopnr < 10000 ) {
01142 interval.increase( recurrenceType(), frequency() );
01143 DateTimeList dts = datesForInterval( interval, recurrenceType() );
01144 if ( dts.count() > 0 ) {
01145 QDateTime ret( dts.first() );
01146 if ( mDuration >= 0 && ret > endDt() ) return QDateTime();
01147 else return ret;
01148 }
01149 ++loopnr;
01150 }
01151 return QDateTime();
01152 }
01153
01154 RecurrenceRule::Constraint RecurrenceRule::getPreviousValidDateInterval( const QDateTime &preDate, PeriodType type ) const
01155 {
01156
01157 long periods = 0;
01158 QDateTime nextValid = startDt();
01159 QDateTime start = startDt();
01160 int modifier = 1;
01161 QDateTime toDate( preDate );
01162
01163
01164
01165
01166
01167
01168
01169 switch ( type ) {
01170
01171
01172 case rHourly: modifier *= 60;
01173 case rMinutely: modifier *= 60;
01174 case rSecondly:
01175 periods = ownSecsTo( start, toDate ) / modifier;
01176
01177 periods = ( periods / frequency() ) * frequency();
01178 nextValid = start.addSecs( modifier * periods );
01179 break;
01180
01181 case rWeekly:
01182 toDate = toDate.addDays( -(7 + toDate.date().dayOfWeek() - mWeekStart) % 7 );
01183 start = start.addDays( -(7 + start.date().dayOfWeek() - mWeekStart) % 7 );
01184 modifier *= 7;
01185 case rDaily:
01186 periods = start.daysTo( toDate ) / modifier;
01187
01188 periods = ( periods / frequency() ) * frequency();
01189 nextValid = start.addDays( modifier * periods );
01190 break;
01191
01192 case rMonthly: {
01193 periods = 12*( toDate.date().year() - start.date().year() ) +
01194 ( toDate.date().month() - start.date().month() );
01195
01196 periods = ( periods / frequency() ) * frequency();
01197
01198
01199 start.setDate( QDate( start.date().year(), start.date().month(), 1 ) );
01200 nextValid.setDate( start.date().addMonths( periods ) );
01201 break; }
01202 case rYearly:
01203 periods = ( toDate.date().year() - start.date().year() );
01204
01205 periods = ( periods / frequency() ) * frequency();
01206 nextValid.setDate( start.date().addYears( periods ) );
01207 break;
01208 default:
01209 break;
01210 }
01211
01212
01213 return Constraint( nextValid, type, mWeekStart );
01214 }
01215
01216 RecurrenceRule::Constraint RecurrenceRule::getNextValidDateInterval( const QDateTime &preDate, PeriodType type ) const
01217 {
01218
01219
01220 long periods = 0;
01221 QDateTime start = startDt();
01222 QDateTime nextValid( start );
01223 int modifier = 1;
01224 QDateTime toDate( preDate );
01225
01226
01227
01228
01229
01230
01231
01232 switch ( type ) {
01233
01234
01235 case rHourly: modifier *= 60;
01236 case rMinutely: modifier *= 60;
01237 case rSecondly:
01238 periods = ownSecsTo( start, toDate ) / modifier;
01239 if ( periods > 0 )
01240 periods += ( frequency() - 1 - ( (periods - 1) % frequency() ) );
01241 nextValid = start.addSecs( modifier * periods );
01242 break;
01243
01244 case rWeekly:
01245
01246 toDate = toDate.addDays( -(7 + toDate.date().dayOfWeek() - mWeekStart) % 7 );
01247 start = start.addDays( -(7 + start.date().dayOfWeek() - mWeekStart) % 7 );
01248 modifier *= 7;
01249 case rDaily:
01250 periods = start.daysTo( toDate ) / modifier;
01251 if ( periods > 0 )
01252 periods += (frequency() - 1 - ( (periods - 1) % frequency() ) );
01253 nextValid = start.addDays( modifier * periods );
01254 break;
01255
01256 case rMonthly: {
01257 periods = 12*( toDate.date().year() - start.date().year() ) +
01258 ( toDate.date().month() - start.date().month() );
01259 if ( periods > 0 )
01260 periods += (frequency() - 1 - ( (periods - 1) % frequency() ) );
01261
01262
01263 start.setDate( QDate( start.date().year(), start.date().month(), 1 ) );
01264 nextValid.setDate( start.date().addMonths( periods ) );
01265 break; }
01266 case rYearly:
01267 periods = ( toDate.date().year() - start.date().year() );
01268 if ( periods > 0 )
01269 periods += ( frequency() - 1 - ( (periods - 1) % frequency() ) );
01270 nextValid.setDate( start.date().addYears( periods ) );
01271 break;
01272 default:
01273 break;
01274 }
01275
01276
01277 return Constraint( nextValid, type, mWeekStart );
01278 }
01279
01280 bool RecurrenceRule::mergeIntervalConstraint( Constraint *merged,
01281 const Constraint &conit, const Constraint &interval ) const
01282 {
01283 Constraint result( interval );
01284
01285 #define mergeConstraint( name, cmparison ) \
01286 if ( conit.name cmparison ) { \
01287 if ( !(result.name cmparison) || result.name == conit.name ) { \
01288 result.name = conit.name; \
01289 } else return false;\
01290 }
01291
01292 mergeConstraint( year, > 0 );
01293 mergeConstraint( month, > 0 );
01294 mergeConstraint( day, != 0 );
01295 mergeConstraint( hour, >= 0 );
01296 mergeConstraint( minute, >= 0 );
01297 mergeConstraint( second, >= 0 );
01298
01299 mergeConstraint( weekday, != 0 );
01300 mergeConstraint( weekdaynr, != 0 );
01301 mergeConstraint( weeknumber, != 0 );
01302 mergeConstraint( yearday, != 0 );
01303
01304 #undef mergeConstraint
01305 if ( merged ) *merged = result;
01306 return true;
01307 }
01308
01309
01310 DateTimeList RecurrenceRule::datesForInterval( const Constraint &interval, PeriodType type ) const
01311 {
01312
01313
01314
01315
01316
01317
01318
01319
01320 DateTimeList lst;
01321 Constraint::List::ConstIterator conit = mConstraints.begin();
01322 for ( ; conit != mConstraints.end(); ++conit ) {
01323 Constraint merged;
01324 bool mergeok = mergeIntervalConstraint( &merged, *conit, interval );
01325
01326 if ( merged.year <= 0 || merged.hour < 0 || merged.minute < 0 || merged.second < 0 )
01327 mergeok = false;
01328 if ( mergeok ) {
01329
01330
01331
01332
01333 DateTimeList lstnew = merged.dateTimes( type );
01334 lst += lstnew;
01335 }
01336 }
01337
01338 qSortUnique( lst );
01339
01340
01341
01342
01343
01344
01345
01346
01347
01348
01349
01350 if ( !mBySetPos.isEmpty() ) {
01351 DateTimeList tmplst = lst;
01352 lst.clear();
01353 QValueList<int>::ConstIterator it;
01354 for ( it = mBySetPos.begin(); it != mBySetPos.end(); ++it ) {
01355 int pos = *it;
01356 if ( pos > 0 ) --pos;
01357 if ( pos < 0 ) pos += tmplst.count();
01358 if ( pos >= 0 && uint(pos) < tmplst.count() ) {
01359 lst.append( tmplst[pos] );
01360 }
01361 }
01362 qSortUnique( lst );
01363 }
01364
01365 return lst;
01366 }
01367
01368
01369 void RecurrenceRule::dump() const
01370 {
01371 #ifndef NDEBUG
01372 kdDebug(5800) << "RecurrenceRule::dump():" << endl;
01373 if ( !mRRule.isEmpty() )
01374 kdDebug(5800) << " RRULE=" << mRRule << endl;
01375 kdDebug(5800) << " Read-Only: " << isReadOnly() <<
01376 ", dirty: " << mDirty << endl;
01377
01378 kdDebug(5800) << " Period type: " << recurrenceType() << ", frequency: " << frequency() << endl;
01379 kdDebug(5800) << " #occurrences: " << duration() << endl;
01380 kdDebug(5800) << " start date: " << startDt() <<", end date: " << endDt() << endl;
01381
01382
01383 #define dumpByIntList(list,label) \
01384 if ( !list.isEmpty() ) {\
01385 QStringList lst;\
01386 for ( QValueList<int>::ConstIterator it = list.begin();\
01387 it != list.end(); ++it ) {\
01388 lst.append( QString::number( *it ) );\
01389 }\
01390 kdDebug(5800) << " " << label << lst.join(", ") << endl;\
01391 }
01392 dumpByIntList( mBySeconds, "BySeconds: " );
01393 dumpByIntList( mByMinutes, "ByMinutes: " );
01394 dumpByIntList( mByHours, "ByHours: " );
01395 if ( !mByDays.isEmpty() ) {
01396 QStringList lst;
01397 for ( QValueList<WDayPos>::ConstIterator it = mByDays.begin();
01398 it != mByDays.end(); ++it ) {
01399 lst.append( ( ((*it).pos()!=0) ? QString::number( (*it).pos() ) : "" ) +
01400 DateHelper::dayName( (*it).day() ) );
01401 }
01402 kdDebug(5800) << " ByDays: " << lst.join(", ") << endl;
01403 }
01404 dumpByIntList( mByMonthDays, "ByMonthDays:" );
01405 dumpByIntList( mByYearDays, "ByYearDays: " );
01406 dumpByIntList( mByWeekNumbers,"ByWeekNr: " );
01407 dumpByIntList( mByMonths, "ByMonths: " );
01408 dumpByIntList( mBySetPos, "BySetPos: " );
01409 #undef dumpByIntList
01410
01411 kdDebug(5800) << " Week start: " << DateHelper::dayName( mWeekStart ) << endl;
01412
01413 kdDebug(5800) << " Constraints:" << endl;
01414
01415 for ( Constraint::List::ConstIterator it = mConstraints.begin();
01416 it!=mConstraints.end(); ++it ) {
01417 (*it).dump();
01418 }
01419 #endif
01420 }
01421
01422 void RecurrenceRule::Constraint::dump() const
01423 {
01424 kdDebug(5800) << " ~> Y="<<year<<", M="<<month<<", D="<<day<<", H="<<hour<<", m="<<minute<<", S="<<second<<", wd="<<weekday<<",#wd="<<weekdaynr<<", #w="<<weeknumber<<", yd="<<yearday<<endl;
01425 }