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 }