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 QDateTime RecurrenceRule::endDt( bool *result ) const
00562 {
00563 if ( result ) *result = false;
00564 if ( mPeriod == rNone ) return QDateTime();
00565 if ( mDuration < 0 ) return QDateTime();
00566 if ( mDuration == 0 ) {
00567 if ( result ) *result = true;
00568 return mDateEnd;
00569 }
00570
00571 if ( ! mCached ) {
00572
00573 if ( !buildCache() ) return QDateTime();
00574 }
00575 if ( result ) *result = true;
00576 return mCachedDateEnd;
00577 }
00578
00579 void RecurrenceRule::setEndDt( const QDateTime &dateTime )
00580 {
00581 if ( isReadOnly() ) return;
00582 mDateEnd = dateTime;
00583 mDuration = 0;
00584 setDirty();
00585 }
00586
00587 void RecurrenceRule::setDuration(int duration)
00588 {
00589 if ( isReadOnly() ) return;
00590 mDuration = duration;
00591 setDirty();
00592 }
00593
00594 void RecurrenceRule::setFloats( bool floats )
00595 {
00596 if ( isReadOnly() ) return;
00597 mFloating = floats;
00598 setDirty();
00599 }
00600
00601 void RecurrenceRule::clear()
00602 {
00603 if ( isReadOnly() ) return;
00604 mPeriod = rNone;
00605 mBySeconds.clear();
00606 mByMinutes.clear();
00607 mByHours.clear();
00608 mByDays.clear();
00609 mByMonthDays.clear();
00610 mByYearDays.clear();
00611 mByWeekNumbers.clear();
00612 mByMonths.clear();
00613 mBySetPos.clear();
00614 mWeekStart = 1;
00615
00616 setDirty();
00617 }
00618
00619 void RecurrenceRule::setDirty()
00620 {
00621 mConstraints.clear();
00622 buildConstraints();
00623 mDirty = true;
00624 mCached = false;
00625 mCachedDates.clear();
00626 for ( QValueList<Observer*>::ConstIterator it = mObservers.begin();
00627 it != mObservers.end(); ++it ) {
00628 if ( (*it) ) (*it)->recurrenceChanged( this );
00629 }
00630 }
00631
00632 void RecurrenceRule::setStartDt( const QDateTime &start )
00633 {
00634 if ( isReadOnly() ) return;
00635 mDateStart = start;
00636 setDirty();
00637 }
00638
00639 void RecurrenceRule::setFrequency(int freq)
00640 {
00641 if ( isReadOnly() || freq <= 0 ) return;
00642 mFrequency = freq;
00643 setDirty();
00644 }
00645
00646 void RecurrenceRule::setBySeconds( const QValueList<int> bySeconds )
00647 {
00648 if ( isReadOnly() ) return;
00649 mBySeconds = bySeconds;
00650 setDirty();
00651 }
00652
00653 void RecurrenceRule::setByMinutes( const QValueList<int> byMinutes )
00654 {
00655 if ( isReadOnly() ) return;
00656 mByMinutes = byMinutes;
00657 setDirty();
00658 }
00659
00660 void RecurrenceRule::setByHours( const QValueList<int> byHours )
00661 {
00662 if ( isReadOnly() ) return;
00663 mByHours = byHours;
00664 setDirty();
00665 }
00666
00667
00668 void RecurrenceRule::setByDays( const QValueList<WDayPos> byDays )
00669 {
00670 if ( isReadOnly() ) return;
00671 mByDays = byDays;
00672 setDirty();
00673 }
00674
00675 void RecurrenceRule::setByMonthDays( const QValueList<int> byMonthDays )
00676 {
00677 if ( isReadOnly() ) return;
00678 mByMonthDays = byMonthDays;
00679 setDirty();
00680 }
00681
00682 void RecurrenceRule::setByYearDays( const QValueList<int> byYearDays )
00683 {
00684 if ( isReadOnly() ) return;
00685 mByYearDays = byYearDays;
00686 setDirty();
00687 }
00688
00689 void RecurrenceRule::setByWeekNumbers( const QValueList<int> byWeekNumbers )
00690 {
00691 if ( isReadOnly() ) return;
00692 mByWeekNumbers = byWeekNumbers;
00693 setDirty();
00694 }
00695
00696 void RecurrenceRule::setByMonths( const QValueList<int> byMonths )
00697 {
00698 if ( isReadOnly() ) return;
00699 mByMonths = byMonths;
00700 setDirty();
00701 }
00702
00703 void RecurrenceRule::setBySetPos( const QValueList<int> bySetPos )
00704 {
00705 if ( isReadOnly() ) return;
00706 mBySetPos = bySetPos;
00707 setDirty();
00708 }
00709
00710 void RecurrenceRule::setWeekStart( short weekStart )
00711 {
00712 if ( isReadOnly() ) return;
00713 mWeekStart = weekStart;
00714 setDirty();
00715 }
00716
00717
00718
00719
00720
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 void RecurrenceRule::buildConstraints()
00776 {
00777 mConstraints.clear();
00778 Constraint con;
00779 if ( mWeekStart > 0 ) con.weekstart = mWeekStart;
00780 mConstraints.append( con );
00781
00782 Constraint::List tmp;
00783 Constraint::List::const_iterator it;
00784 QValueList<int>::const_iterator intit;
00785
00786 #define intConstraint( list, element ) \
00787 if ( !list.isEmpty() ) { \
00788 for ( it = mConstraints.constBegin(); it != mConstraints.constEnd(); ++it ) { \
00789 for ( intit = list.constBegin(); intit != list.constEnd(); ++intit ) { \
00790 con = (*it); \
00791 con.element = (*intit); \
00792 tmp.append( con ); \
00793 } \
00794 } \
00795 mConstraints = tmp; \
00796 tmp.clear(); \
00797 }
00798
00799 intConstraint( mBySeconds, second );
00800 intConstraint( mByMinutes, minute );
00801 intConstraint( mByHours, hour );
00802 intConstraint( mByMonthDays, day );
00803 intConstraint( mByMonths, month );
00804 intConstraint( mByYearDays, yearday );
00805 intConstraint( mByWeekNumbers, weeknumber );
00806 #undef intConstraint
00807
00808 if ( !mByDays.isEmpty() ) {
00809 for ( it = mConstraints.constBegin(); it != mConstraints.constEnd(); ++it ) {
00810 QValueList<WDayPos>::const_iterator dayit;
00811 for ( dayit = mByDays.constBegin(); dayit != mByDays.constEnd(); ++dayit ) {
00812 con = (*it);
00813 con.weekday = (*dayit).day();
00814 con.weekdaynr = (*dayit).pos();
00815 tmp.append( con );
00816 }
00817 }
00818 mConstraints = tmp;
00819 tmp.clear();
00820 }
00821
00822 #define fixConstraint( element, value ) \
00823 { \
00824 tmp.clear(); \
00825 for ( it = mConstraints.constBegin(); it != mConstraints.constEnd(); ++it ) { \
00826 con = (*it); con.element = value; tmp.append( con ); \
00827 } \
00828 mConstraints = tmp; \
00829 }
00830
00831
00832
00833
00834 if ( mPeriod == rWeekly && mByDays.isEmpty() ) {
00835 fixConstraint( weekday, mDateStart.date().dayOfWeek() );
00836 }
00837
00838
00839
00840 switch ( mPeriod ) {
00841 case rYearly:
00842 if ( mByDays.isEmpty() && mByWeekNumbers.isEmpty() && mByYearDays.isEmpty() && mByMonths.isEmpty() ) {
00843 fixConstraint( month, mDateStart.date().month() );
00844 }
00845 case rMonthly:
00846 if ( mByDays.isEmpty() && mByWeekNumbers.isEmpty() && mByYearDays.isEmpty() && mByMonthDays.isEmpty() ) {
00847 fixConstraint( day, mDateStart.date().day() );
00848 }
00849
00850 case rWeekly:
00851 case rDaily:
00852 if ( mByHours.isEmpty() ) {
00853 fixConstraint( hour, mDateStart.time().hour() );
00854 }
00855 case rHourly:
00856 if ( mByMinutes.isEmpty() ) {
00857 fixConstraint( minute, mDateStart.time().minute() );
00858 }
00859 case rMinutely:
00860 if ( mBySeconds.isEmpty() ) {
00861 fixConstraint( second, mDateStart.time().second() );
00862 }
00863 case rSecondly:
00864 default:
00865 break;
00866 }
00867 #undef fixConstraint
00868
00869 Constraint::List::Iterator conit = mConstraints.begin();
00870 while ( conit != mConstraints.end() ) {
00871 if ( (*conit).isConsistent( mPeriod ) ) {
00872 ++conit;
00873 } else {
00874 conit = mConstraints.remove( conit );
00875 }
00876 }
00877 }
00878
00879 bool RecurrenceRule::buildCache() const
00880 {
00881 kdDebug(5800) << " RecurrenceRule::buildCache: " << endl;
00882
00883
00884 Constraint interval( getNextValidDateInterval( startDt(), recurrenceType() ) );
00885 QDateTime next;
00886
00887 DateTimeList dts = datesForInterval( interval, recurrenceType() );
00888 DateTimeList::Iterator it = dts.begin();
00889
00890
00891 while ( it != dts.end() ) {
00892 if ( (*it) < startDt() ) it = dts.remove( it );
00893 else ++it;
00894 }
00895
00896
00897
00898 int loopnr = 0;
00899 int dtnr = dts.count();
00900
00901
00902 while ( loopnr < 10000 && dtnr < mDuration ) {
00903 interval.increase( recurrenceType(), frequency() );
00904
00905 dts += datesForInterval( interval, recurrenceType() );
00906 dtnr = dts.count();
00907 ++loopnr;
00908 }
00909 if ( int(dts.count()) > mDuration ) {
00910
00911 it = dts.at( mDuration );
00912 while ( it != dts.end() ) it = dts.remove( it );
00913 }
00914 mCached = true;
00915 mCachedDates = dts;
00916
00917 kdDebug(5800) << " Finished Building Cache, cache has " << dts.count() << " entries:" << endl;
00918
00919
00920
00921
00922
00923 if ( int(dts.count()) == mDuration ) {
00924 mCachedDateEnd = dts.last();
00925 return true;
00926 } else {
00927 mCachedDateEnd = QDateTime();
00928 return false;
00929 }
00930 }
00931
00932 bool RecurrenceRule::dateMatchesRules( const QDateTime &qdt ) const
00933 {
00934 bool match = false;
00935 for ( Constraint::List::ConstIterator it = mConstraints.begin();
00936 it!=mConstraints.end(); ++it ) {
00937 match = match || ( (*it).matches( qdt, recurrenceType() ) );
00938 }
00939 return match;
00940 }
00941
00942 bool RecurrenceRule::recursOn( const QDate &qd ) const
00943 {
00944
00945 if ( qd < startDt().date() ) return false;
00946
00947
00948 if ( mDuration >= 0 && qd > endDt().date() ) return false;
00949
00950
00951
00952 bool match = false;
00953 for ( Constraint::List::ConstIterator it = mConstraints.begin();
00954 it!=mConstraints.end(); ++it ) {
00955 match = match || ( (*it).matches( qd, recurrenceType() ) );
00956 }
00957 if ( !match ) return false;
00958 QDateTime tmp( qd, QTime( 0, 0, 0 ) );
00959 Constraint interval( getNextValidDateInterval( tmp, recurrenceType() ) );
00960
00961
00962 if ( !interval.matches( qd, recurrenceType() ) ) return false;
00963
00964
00965
00966 DateTimeList times = datesForInterval( interval, recurrenceType() );
00967 DateTimeList::ConstIterator it = times.begin();
00968 while ( ( it != times.end() ) && ( (*it).date() < qd ) )
00969 ++it;
00970 if ( it != times.end() ) {
00971
00972 if ( mDuration >= 0 && (*it) > endDt() )
00973 return false;
00974 if ( (*it).date() == qd )
00975 return true;
00976 }
00977 return false;
00978 }
00979
00980
00981 bool RecurrenceRule::recursAt( const QDateTime &qd ) const
00982 {
00983
00984 if ( doesFloat() ) return recursOn( qd.date() );
00985 if ( qd < startDt() ) return false;
00986
00987
00988 if ( mDuration >= 0 && qd > endDt() ) return false;
00989
00990
00991
00992 bool match = dateMatchesRules( qd );
00993 if ( !match ) return false;
00994
00995
00996 Constraint interval( getNextValidDateInterval( qd, recurrenceType() ) );
00997
00998 if ( interval.matches( qd, recurrenceType() ) ) return true;
00999
01000 return false;
01001 }
01002
01003
01004 TimeList RecurrenceRule::recurTimesOn( const QDate &date ) const
01005 {
01006
01007 TimeList lst;
01008 if ( !recursOn( date ) ) return lst;
01009
01010 if ( doesFloat() ) return lst;
01011
01012 QDateTime dt( date, QTime( 0, 0, 0 ) );
01013 bool valid = dt.isValid() && ( dt.date() == date );
01014 while ( valid ) {
01015
01016 dt = getNextDate( dt );
01017 valid = dt.isValid() && ( dt.date() == date );
01018 if ( valid ) lst.append( dt.time() );
01019 }
01020 return lst;
01021 }
01022
01024 int RecurrenceRule::durationTo( const QDateTime &dt ) const
01025 {
01026
01027
01028
01029 if ( dt < startDt() ) return 0;
01030
01031
01032 if ( mDuration > 0 && dt >= endDt() ) return mDuration;
01033
01034 QDateTime next( startDt() );
01035 int found = 0;
01036 while ( next.isValid() && next <= dt ) {
01037 ++found;
01038 next = getNextDate( next );
01039 }
01040 return found;
01041 }
01042
01043
01044 QDateTime RecurrenceRule::getPreviousDate( const QDateTime& afterDate ) const
01045 {
01046
01047
01048 if ( afterDate < startDt() )
01049 return QDateTime();
01050
01051
01052 QDateTime prev;
01053 if ( mDuration > 0 ) {
01054 if ( !mCached ) buildCache();
01055 DateTimeList::ConstIterator it = mCachedDates.begin();
01056 while ( it != mCachedDates.end() && (*it) < afterDate ) {
01057 prev = *it;
01058 ++it;
01059 }
01060 if ( prev.isValid() && prev < afterDate ) return prev;
01061 else return QDateTime();
01062 }
01063
01064
01065 prev = afterDate;
01066 if ( mDuration >= 0 && endDt().isValid() && afterDate > endDt() )
01067 prev = endDt().addSecs( 1 );
01068
01069 Constraint interval( getPreviousValidDateInterval( prev, recurrenceType() ) );
01070
01071
01072 DateTimeList dts = datesForInterval( interval, recurrenceType() );
01073 DateTimeList::Iterator dtit = dts.end();
01074 if ( dtit != dts.begin() ) {
01075 do {
01076 --dtit;
01077 } while ( dtit != dts.begin() && (*dtit) >= prev );
01078 if ( (*dtit) < prev ) {
01079 if ( (*dtit) >= startDt() ) return (*dtit);
01080 else return QDateTime();
01081 }
01082 }
01083
01084
01085 while ( interval.intervalDateTime( recurrenceType() ) > startDt() ) {
01086 interval.increase( recurrenceType(), -frequency() );
01087
01088
01089
01090 DateTimeList dts = datesForInterval( interval, recurrenceType() );
01091
01092 if ( dts.count() > 0 ) {
01093 prev = dts.last();
01094 if ( prev.isValid() && prev >= startDt() ) return prev;
01095 else return QDateTime();
01096 }
01097 }
01098 return QDateTime();
01099 }
01100
01101
01102 QDateTime RecurrenceRule::getNextDate( const QDateTime &preDate ) const
01103 {
01104
01105
01106 if ( mDuration >= 0 && endDt().isValid() && preDate >= endDt() )
01107 return QDateTime();
01108
01109
01110 QDateTime adjustedPreDate;
01111 if ( preDate < startDt() )
01112 adjustedPreDate = startDt().addSecs( -1 );
01113 else
01114 adjustedPreDate = preDate;
01115
01116 if ( mDuration > 0 ) {
01117 if ( !mCached ) buildCache();
01118 DateTimeList::ConstIterator it = mCachedDates.begin();
01119 while ( it != mCachedDates.end() && (*it) <= adjustedPreDate ) ++it;
01120 if ( it != mCachedDates.end() ) {
01121
01122 return (*it);
01123 }
01124 }
01125
01126
01127 Constraint interval( getNextValidDateInterval( adjustedPreDate, recurrenceType() ) );
01128 DateTimeList dts = datesForInterval( interval, recurrenceType() );
01129 DateTimeList::Iterator dtit = dts.begin();
01130 while ( dtit != dts.end() && (*dtit) <= adjustedPreDate ) ++dtit;
01131 if ( dtit != dts.end() ) {
01132 if ( mDuration >= 0 && (*dtit) > endDt() ) return QDateTime();
01133 else return (*dtit);
01134 }
01135
01136
01137
01138
01139 int loopnr = 0;
01140 while ( loopnr < 10000 ) {
01141 interval.increase( recurrenceType(), frequency() );
01142 DateTimeList dts = datesForInterval( interval, recurrenceType() );
01143 if ( dts.count() > 0 ) {
01144 QDateTime ret( dts.first() );
01145 if ( mDuration >= 0 && ret > endDt() ) return QDateTime();
01146 else return ret;
01147 }
01148 ++loopnr;
01149 }
01150 return QDateTime();
01151 }
01152
01153 RecurrenceRule::Constraint RecurrenceRule::getPreviousValidDateInterval( const QDateTime &preDate, PeriodType type ) const
01154 {
01155
01156 long periods = 0;
01157 QDateTime nextValid = startDt();
01158 QDateTime start = startDt();
01159 int modifier = 1;
01160 QDateTime toDate( preDate );
01161
01162
01163
01164
01165
01166
01167
01168 switch ( type ) {
01169
01170
01171 case rHourly: modifier *= 60;
01172 case rMinutely: modifier *= 60;
01173 case rSecondly:
01174 periods = ownSecsTo( start, toDate ) / modifier;
01175
01176 periods = ( periods / frequency() ) * frequency();
01177 nextValid = start.addSecs( modifier * periods );
01178 break;
01179
01180 case rWeekly:
01181 toDate = toDate.addDays( -(7 + toDate.date().dayOfWeek() - mWeekStart) % 7 );
01182 start = start.addDays( -(7 + start.date().dayOfWeek() - mWeekStart) % 7 );
01183 modifier *= 7;
01184 case rDaily:
01185 periods = start.daysTo( toDate ) / modifier;
01186
01187 periods = ( periods / frequency() ) * frequency();
01188 nextValid = start.addDays( modifier * periods );
01189 break;
01190
01191 case rMonthly: {
01192 periods = 12*( toDate.date().year() - start.date().year() ) +
01193 ( toDate.date().month() - start.date().month() );
01194
01195 periods = ( periods / frequency() ) * frequency();
01196
01197
01198 start.setDate( QDate( start.date().year(), start.date().month(), 1 ) );
01199 nextValid.setDate( start.date().addMonths( periods ) );
01200 break; }
01201 case rYearly:
01202 periods = ( toDate.date().year() - start.date().year() );
01203
01204 periods = ( periods / frequency() ) * frequency();
01205 nextValid.setDate( start.date().addYears( periods ) );
01206 break;
01207 default:
01208 break;
01209 }
01210
01211
01212 return Constraint( nextValid, type, mWeekStart );
01213 }
01214
01215 RecurrenceRule::Constraint RecurrenceRule::getNextValidDateInterval( const QDateTime &preDate, PeriodType type ) const
01216 {
01217
01218 kdDebug(5800) << " (o) getNextValidDateInterval after " << preDate << ", type=" << type << endl;
01219 long periods = 0;
01220 QDateTime start = startDt();
01221 QDateTime nextValid( start );
01222 int modifier = 1;
01223 QDateTime toDate( preDate );
01224
01225
01226
01227
01228
01229
01230
01231 switch ( type ) {
01232
01233
01234 case rHourly: modifier *= 60;
01235 case rMinutely: modifier *= 60;
01236 case rSecondly:
01237 periods = ownSecsTo( start, toDate ) / modifier;
01238 periods = QMAX( 0, periods);
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 periods = QMAX( 0, periods);
01252 if ( periods > 0 )
01253 periods += (frequency() - 1 - ( (periods - 1) % frequency() ) );
01254 nextValid = start.addDays( modifier * periods );
01255 break;
01256
01257 case rMonthly: {
01258 periods = 12*( toDate.date().year() - start.date().year() ) +
01259 ( toDate.date().month() - start.date().month() );
01260 periods = QMAX( 0, periods);
01261 if ( periods > 0 )
01262 periods += (frequency() - 1 - ( (periods - 1) % frequency() ) );
01263
01264
01265 start.setDate( QDate( start.date().year(), start.date().month(), 1 ) );
01266 nextValid.setDate( start.date().addMonths( periods ) );
01267 break; }
01268 case rYearly:
01269 periods = ( toDate.date().year() - start.date().year() );
01270 periods = QMAX( 0, periods);
01271 if ( periods > 0 )
01272 periods += ( frequency() - 1 - ( (periods - 1) % frequency() ) );
01273 nextValid.setDate( start.date().addYears( periods ) );
01274 break;
01275 default:
01276 break;
01277 }
01278
01279
01280 return Constraint( nextValid, type, mWeekStart );
01281 }
01282
01283 bool RecurrenceRule::mergeIntervalConstraint( Constraint *merged,
01284 const Constraint &conit, const Constraint &interval ) const
01285 {
01286 Constraint result( interval );
01287
01288 #define mergeConstraint( name, cmparison ) \
01289 if ( conit.name cmparison ) { \
01290 if ( !(result.name cmparison) || result.name == conit.name ) { \
01291 result.name = conit.name; \
01292 } else return false;\
01293 }
01294
01295 mergeConstraint( year, > 0 );
01296 mergeConstraint( month, > 0 );
01297 mergeConstraint( day, != 0 );
01298 mergeConstraint( hour, >= 0 );
01299 mergeConstraint( minute, >= 0 );
01300 mergeConstraint( second, >= 0 );
01301
01302 mergeConstraint( weekday, != 0 );
01303 mergeConstraint( weekdaynr, != 0 );
01304 mergeConstraint( weeknumber, != 0 );
01305 mergeConstraint( yearday, != 0 );
01306
01307 #undef mergeConstraint
01308 if ( merged ) *merged = result;
01309 return true;
01310 }
01311
01312
01313 DateTimeList RecurrenceRule::datesForInterval( const Constraint &interval, PeriodType type ) const
01314 {
01315
01316
01317
01318
01319
01320
01321
01322
01323 DateTimeList lst;
01324 Constraint::List::ConstIterator conit = mConstraints.begin();
01325 for ( ; conit != mConstraints.end(); ++conit ) {
01326 Constraint merged;
01327 bool mergeok = mergeIntervalConstraint( &merged, *conit, interval );
01328
01329 if ( merged.year <= 0 || merged.hour < 0 || merged.minute < 0 || merged.second < 0 )
01330 mergeok = false;
01331 if ( mergeok ) {
01332
01333
01334
01335
01336 DateTimeList lstnew = merged.dateTimes( type );
01337 lst += lstnew;
01338 }
01339 }
01340
01341 qSortUnique( lst );
01342
01343
01344
01345
01346
01347
01348
01349
01350
01351
01352
01353 if ( !mBySetPos.isEmpty() ) {
01354 DateTimeList tmplst = lst;
01355 lst.clear();
01356 QValueList<int>::ConstIterator it;
01357 for ( it = mBySetPos.begin(); it != mBySetPos.end(); ++it ) {
01358 int pos = *it;
01359 if ( pos > 0 ) --pos;
01360 if ( pos < 0 ) pos += tmplst.count();
01361 if ( pos >= 0 && uint(pos) < tmplst.count() ) {
01362 lst.append( tmplst[pos] );
01363 }
01364 }
01365 qSortUnique( lst );
01366 }
01367
01368 return lst;
01369 }
01370
01371
01372 void RecurrenceRule::dump() const
01373 {
01374 #ifndef NDEBUG
01375 kdDebug(5800) << "RecurrenceRule::dump():" << endl;
01376 if ( !mRRule.isEmpty() )
01377 kdDebug(5800) << " RRULE=" << mRRule << endl;
01378 kdDebug(5800) << " Read-Only: " << isReadOnly() <<
01379 ", dirty: " << mDirty << endl;
01380
01381 kdDebug(5800) << " Period type: " << recurrenceType() << ", frequency: " << frequency() << endl;
01382 kdDebug(5800) << " #occurrences: " << duration() << endl;
01383 kdDebug(5800) << " start date: " << startDt() <<", end date: " << endDt() << endl;
01384
01385
01386 #define dumpByIntList(list,label) \
01387 if ( !list.isEmpty() ) {\
01388 QStringList lst;\
01389 for ( QValueList<int>::ConstIterator it = list.begin();\
01390 it != list.end(); ++it ) {\
01391 lst.append( QString::number( *it ) );\
01392 }\
01393 kdDebug(5800) << " " << label << lst.join(", ") << endl;\
01394 }
01395 dumpByIntList( mBySeconds, "BySeconds: " );
01396 dumpByIntList( mByMinutes, "ByMinutes: " );
01397 dumpByIntList( mByHours, "ByHours: " );
01398 if ( !mByDays.isEmpty() ) {
01399 QStringList lst;
01400 for ( QValueList<WDayPos>::ConstIterator it = mByDays.begin();
01401 it != mByDays.end(); ++it ) {
01402 lst.append( ( ((*it).pos()!=0) ? QString::number( (*it).pos() ) : "" ) +
01403 DateHelper::dayName( (*it).day() ) );
01404 }
01405 kdDebug(5800) << " ByDays: " << lst.join(", ") << endl;
01406 }
01407 dumpByIntList( mByMonthDays, "ByMonthDays:" );
01408 dumpByIntList( mByYearDays, "ByYearDays: " );
01409 dumpByIntList( mByWeekNumbers,"ByWeekNr: " );
01410 dumpByIntList( mByMonths, "ByMonths: " );
01411 dumpByIntList( mBySetPos, "BySetPos: " );
01412 #undef dumpByIntList
01413
01414 kdDebug(5800) << " Week start: " << DateHelper::dayName( mWeekStart ) << endl;
01415
01416 kdDebug(5800) << " Constraints:" << endl;
01417
01418 for ( Constraint::List::ConstIterator it = mConstraints.begin();
01419 it!=mConstraints.end(); ++it ) {
01420 (*it).dump();
01421 }
01422 #endif
01423 }
01424
01425 void RecurrenceRule::Constraint::dump() const
01426 {
01427 kdDebug(5800) << " ~> Y="<<year<<", M="<<month<<", D="<<day<<", H="<<hour<<", m="<<minute<<", S="<<second<<", wd="<<weekday<<",#wd="<<weekdaynr<<", #w="<<weeknumber<<", yd="<<yearday<<endl;
01428 }