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