libkcal

recurrence.cpp

00001 /*
00002     This file is part of libkcal.
00003 
00004     Copyright (c) 1998 Preston Brown <pbrown@kde.org>
00005     Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
00006     Copyright (c) 2002 David Jarvie <software@astrojar.org.uk>
00007     Copyright (C) 2005 Reinhold Kainhofer <kainhofer@kde.org>
00008 
00009     This library is free software; you can redistribute it and/or
00010     modify it under the terms of the GNU Library General Public
00011     License as published by the Free Software Foundation; either
00012     version 2 of the License, or (at your option) any later version.
00013 
00014     This library is distributed in the hope that it will be useful,
00015     but WITHOUT ANY WARRANTY; without even the implied warranty of
00016     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00017     Library General Public License for more details.
00018 
00019     You should have received a copy of the GNU Library General Public License
00020     along with this library; see the file COPYING.LIB.  If not, write to
00021     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00022     Boston, MA 02110-1301, USA.
00023 */
00024 
00025 #include <limits.h>
00026 
00027 #include <kdebug.h>
00028 #include <kglobal.h>
00029 #include <klocale.h>
00030 #include <qbitarray.h>
00031 
00032 #include "recurrence.h"
00033 #include "recurrencerule.h"
00034 
00035 using namespace KCal;
00036 
00037 
00038 Recurrence::Recurrence()
00039 : mFloating( false ),
00040   mRecurReadOnly(false),
00041   mCachedType(rMax)
00042 {
00043   mExRules.setAutoDelete( true );
00044   mRRules.setAutoDelete( true );
00045 }
00046 
00047 Recurrence::Recurrence( const Recurrence &r )
00048 : RecurrenceRule::Observer(),
00049   mRDateTimes( r.mRDateTimes ), mRDates( r.mRDates ),
00050   mExDateTimes( r.mExDateTimes ), mExDates( r.mExDates ),
00051   mStartDateTime( r.mStartDateTime ),
00052   mFloating( r.mFloating ),
00053   mRecurReadOnly(r.mRecurReadOnly),
00054   mCachedType( r.mCachedType )
00055 {
00056   mExRules.setAutoDelete( true );
00057   mRRules.setAutoDelete( true );
00058   RecurrenceRule::List::ConstIterator rr;
00059   for ( rr = r.mRRules.begin(); rr != r.mRRules.end(); ++rr ) {
00060     RecurrenceRule *rule = new RecurrenceRule( *(*rr) );
00061     mRRules.append( rule );
00062     rule->addObserver( this );
00063   }
00064   for ( rr = r.mExRules.begin(); rr != r.mExRules.end(); ++rr ) {
00065     RecurrenceRule *rule = new RecurrenceRule( *(*rr) );
00066     mExRules.append( rule );
00067     rule->addObserver( this );
00068   }
00069 }
00070 
00071 Recurrence::~Recurrence()
00072 {
00073 }
00074 
00075 
00076 
00077 bool Recurrence::operator==( const Recurrence& r2 ) const
00078 {
00079   if ( mStartDateTime != r2.mStartDateTime
00080   ||   mFloating != r2.mFloating
00081   ||   mRecurReadOnly != r2.mRecurReadOnly )
00082     return false;
00083   if ( mExDates != r2.mExDates ) return false;
00084   if ( mExDateTimes != r2.mExDateTimes ) return false;
00085   if ( mRDates != r2.mRDates ) return false;
00086   if ( mRDateTimes != r2.mRDateTimes ) return false;
00087 
00088 // Compare the rrules, exrules! Assume they have the same order... This only
00089 // matters if we have more than one rule (which shouldn't be the default anyway)
00090   if ( mRRules.count() != r2.mRRules.count() ) return false;
00091   RecurrenceRule::List::ConstIterator rit1 = mRRules.begin();
00092   RecurrenceRule::List::ConstIterator rit2 = r2.mRRules.begin();
00093 
00094   while ( rit1 != mRRules.end() && rit2 != r2.mRRules.end() ) {
00095     // dereference the iterator to the RecurrenceRule*, and that once again
00096     // to RecurrenceRule...
00097     if ( *(*rit1) != *(*rit2) ) return false;
00098     ++rit1;
00099     ++rit2;
00100   }
00101   RecurrenceRule::List::ConstIterator exit1 = mExRules.begin();
00102   RecurrenceRule::List::ConstIterator exit2 = r2.mExRules.begin();
00103 
00104   while ( exit1 != mExRules.end() && exit2 != r2.mExRules.end() ) {
00105     // dereference the iterator to the RecurrenceRule*, and that once again
00106     // to RecurrenceRule...
00107     if ( *(*exit1) != *(*exit2) ) return false;
00108     ++exit1;
00109     ++exit2;
00110   }
00111   return true;
00112 }
00113 
00114 void Recurrence::addObserver( Observer *observer )
00115 {
00116   if ( !mObservers.contains( observer ) )
00117     mObservers.append( observer );
00118 }
00119 
00120 void Recurrence::removeObserver( Observer *observer )
00121 {
00122   if ( mObservers.contains( observer ) )
00123     mObservers.remove( observer );
00124 }
00125 
00126 
00127 QDateTime Recurrence::startDateTime() const
00128 {
00129   if ( mFloating )
00130     return QDateTime( mStartDateTime.date(), QTime( 0, 0, 0 ) );
00131   else return mStartDateTime;
00132 }
00133 
00134 void Recurrence::setFloats( bool floats )
00135 {
00136   if ( mRecurReadOnly ) return;
00137   if ( floats == mFloating ) return;
00138   mFloating = floats;
00139 
00140 
00141   RecurrenceRule::List::ConstIterator it;
00142   for ( it = mRRules.begin(); it != mRRules.end(); ++it ) {
00143     (*it)->setFloats( floats );
00144   }
00145 
00146   RecurrenceRule::List::ConstIterator it1;
00147   for ( it1 = mExRules.begin(); it1 != mExRules.end(); ++it1 ) {
00148     (*it1)->setFloats( floats );
00149   }
00150   updated();
00151 }
00152 
00153 RecurrenceRule *Recurrence::defaultRRule( bool create ) const
00154 {
00155   if ( mRRules.isEmpty() ) {
00156     if ( !create || mRecurReadOnly ) return 0;
00157     RecurrenceRule *rrule = new RecurrenceRule();
00158     rrule->setStartDt( startDateTime() );
00159     const_cast<KCal::Recurrence*>(this)->addRRule( rrule );
00160     return rrule;
00161   } else {
00162     return mRRules.first();
00163   }
00164 }
00165 
00166 RecurrenceRule *Recurrence::defaultRRuleConst() const
00167 {
00168   if ( mRRules.isEmpty() ) {
00169     return 0;
00170   } else {
00171     return mRRules.first();
00172   }
00173 }
00174 
00175 void Recurrence::updated()
00176 {
00177   // recurrenceType() re-calculates the type if it's rMax
00178   mCachedType = rMax;
00179   for ( QValueList<Observer*>::ConstIterator it = mObservers.begin();
00180         it != mObservers.end(); ++it ) {
00181     if ( (*it) ) (*it)->recurrenceUpdated( this );
00182   }
00183 }
00184 
00185 bool Recurrence::doesRecur() const
00186 {
00187   return !mRRules.isEmpty() || !mRDates.isEmpty() || !mRDateTimes.isEmpty();
00188 }
00189 
00190 ushort Recurrence::recurrenceType() const
00191 {
00192   if ( mCachedType == rMax ) {
00193     mCachedType = recurrenceType( defaultRRuleConst() );
00194   }
00195   return mCachedType;
00196 }
00197 
00198 ushort Recurrence::recurrenceType( const RecurrenceRule *rrule )
00199 {
00200   if ( !rrule ) return rNone;
00201   RecurrenceRule::PeriodType type = rrule->recurrenceType();
00202 
00203   // BYSETPOS, BYWEEKNUMBER and BYSECOND were not supported in old versions
00204   if ( !rrule->bySetPos().isEmpty() )
00205     return rOther;
00206   if ( !rrule->bySeconds().isEmpty() )
00207     return rOther;
00208   if ( !rrule->byWeekNumbers().isEmpty() )
00209     return rOther;
00210 
00211   // It wasn't possible to set BYMINUTES, BYHOUR etc. by the old code. So if
00212   // it's set, it's none of the old types
00213   if ( !rrule->byMinutes().isEmpty() )
00214     return rOther;
00215   if ( !rrule->byHours().isEmpty() )
00216     return rOther;
00217 
00218   // Possible combinations were:
00219   // BYDAY: with WEEKLY, MONTHLY, YEARLY
00220   // BYMONTHDAY: with MONTHLY, YEARLY
00221   // BYMONTH: with YEARLY
00222   // BYYEARDAY: with YEARLY
00223   if ( !rrule->byYearDays().isEmpty() && type != RecurrenceRule::rYearly )
00224     return rOther;
00225   if ( !rrule->byMonths().isEmpty() && type != RecurrenceRule::rYearly )
00226     return rOther;
00227   if ( !rrule->byDays().isEmpty() ) {
00228     if ( type != RecurrenceRule::rYearly && type != RecurrenceRule::rMonthly &&
00229          type != RecurrenceRule::rWeekly )
00230       return rOther;
00231   }
00232 
00233   switch ( type ) {
00234     case RecurrenceRule::rNone:     return rNone;
00235     case RecurrenceRule::rMinutely: return rMinutely;
00236     case RecurrenceRule::rHourly:   return rHourly;
00237     case RecurrenceRule::rDaily:    return rDaily;
00238     case RecurrenceRule::rWeekly:   return rWeekly;
00239     case RecurrenceRule::rMonthly: {
00240         if ( rrule->byDays().isEmpty() ) return rMonthlyDay;
00241         else if ( rrule->byMonthDays().isEmpty() ) return rMonthlyPos;
00242         else return rOther; // both position and date specified
00243       }
00244     case RecurrenceRule::rYearly: {
00245         // Possible combinations:
00246         //   rYearlyMonth: [BYMONTH &] BYMONTHDAY
00247         //   rYearlyDay: BYYEARDAY
00248         //   rYearlyPos: [BYMONTH &] BYDAY
00249         if ( !rrule->byDays().isEmpty() ) {
00250           // can only by rYearlyPos
00251           if ( rrule->byMonthDays().isEmpty() && rrule->byYearDays().isEmpty() )
00252             return rYearlyPos;
00253           else return rOther;
00254         } else if ( !rrule->byYearDays().isEmpty() ) {
00255           // Can only be rYearlyDay
00256           if ( rrule->byMonths().isEmpty() && rrule->byMonthDays().isEmpty() )
00257             return rYearlyDay;
00258           else return rOther;
00259         } else {
00260           return rYearlyMonth;
00261         }
00262         break;
00263       }
00264      default: return rOther;
00265   }
00266   return rOther;
00267 }
00268 
00269 bool Recurrence::recursOn(const QDate &qd) const
00270 {
00271   TimeList tms;
00272   // First handle dates. Exrules override
00273   if ( mExDates.contains( qd ) ) return false;
00274   // For all-day events a matching exrule excludes the whole day
00275   // since exclusions take precedence over inclusions, we know it can't occur on that day.
00276   if ( doesFloat() ) {
00277     for ( RecurrenceRule::List::ConstIterator rr = mExRules.begin(); rr != mExRules.end(); ++rr ) {
00278       if ( (*rr)->recursOn( qd ) )
00279         return false;
00280     }
00281   }
00282 
00283   if ( mRDates.contains( qd ) ) return true;
00284 
00285   bool recurs = false;
00286 
00287   for ( RecurrenceRule::List::ConstIterator rr = mRRules.begin(); rr != mRRules.end(); ++rr ) {
00288     recurs = recurs || (*rr)->recursOn( qd );
00289   }
00290   // If we already know it recurs, no need to check the rdate list too.
00291   if ( !recurs ) {
00292     for ( DateTimeList::ConstIterator rit = mRDateTimes.begin();
00293           rit != mRDateTimes.end(); ++rit ) {
00294       if ( (*rit).date() == qd ) {
00295         recurs = true;
00296         break;
00297       }
00298     }
00299   }
00300   // If the event wouldn't recur at all, simply return false, don't check ex*
00301   if ( !recurs ) return false;
00302 
00303   // Check if there are any times for this day excluded, either by exdate or exrule:
00304   bool exon = false;
00305   for ( DateTimeList::ConstIterator exit = mExDateTimes.begin();
00306         exit != mExDateTimes.end(); ++exit ) {
00307     if ( (*exit).date() == qd ) {
00308       exon = true;
00309       break;
00310     }
00311   }
00312   if ( !doesFloat() ) {     // we have already checked floating times above
00313     for ( RecurrenceRule::List::ConstIterator rr = mExRules.begin(); rr != mExRules.end(); ++rr ) {
00314       exon = exon || (*rr)->recursOn( qd );
00315     }
00316   }
00317 
00318   if ( !exon ) {
00319     // Simple case, nothing on that day excluded, return the value from before
00320     return recurs;
00321   } else {
00322     // Harder part: I don't think there is any way other than to calculate the
00323     // whole list of items for that day.
00324     TimeList timesForDay( recurTimesOn( qd ) );
00325     return !timesForDay.isEmpty();
00326   }
00327 }
00328 
00329 bool Recurrence::recursAt( const QDateTime &dt ) const
00330 {
00331   // if it's excluded anyway, don't bother to check if it recurs at all.
00332   if ( mExDateTimes.contains( dt )) return false;
00333   if ( mExDates.contains( dt.date() )) return false;
00334   for ( RecurrenceRule::List::ConstIterator rr = mExRules.begin(); rr != mExRules.end(); ++rr ) {
00335     if ( (*rr)->recursAt( dt ) ) return false;
00336   }
00337 
00338   // Check explicit recurrences, then rrules.
00339   bool occurs = ( startDateTime() == dt ) || mRDateTimes.contains( dt );
00340   if ( occurs )
00341     return true;
00342   for ( RecurrenceRule::List::ConstIterator rr = mRRules.begin(); rr != mRRules.end(); ++rr ) {
00343     if ( (*rr)->recursAt( dt ) ) return true;
00344   }
00345 
00346   return false;
00347 }
00348 
00352 QDateTime Recurrence::endDateTime() const
00353 {
00354   DateTimeList dts;
00355   dts << startDateTime();
00356   if ( !mRDates.isEmpty() ) dts << QDateTime( mRDates.last(), QTime( 0, 0, 0 ) );
00357   if ( !mRDateTimes.isEmpty() ) dts << mRDateTimes.last();
00358   for ( RecurrenceRule::List::ConstIterator rr = mRRules.begin(); rr != mRRules.end(); ++rr ) {
00359     QDateTime rl( (*rr)->endDt() );
00360     // if any of the rules is infinite, the whole recurrence is
00361     if ( !rl.isValid() ) return QDateTime();
00362     dts << rl;
00363   }
00364   qSortUnique( dts );
00365   if ( dts.isEmpty() ) return QDateTime();
00366   else return dts.last();
00367 }
00368 
00372 QDate Recurrence::endDate() const
00373 {
00374   QDateTime end( endDateTime() );
00375   if ( end.isValid() ) { return end.date(); }
00376   else return QDate();
00377 }
00378 
00379 void Recurrence::setEndDate( const QDate &date )
00380 {
00381   if ( doesFloat() )
00382     setEndDateTime( QDateTime( date, QTime( 23, 59, 59 ) ) );
00383   else
00384     setEndDateTime( QDateTime( date, mStartDateTime.time() ) );
00385 }
00386 
00387 void Recurrence::setEndDateTime( const QDateTime &dateTime )
00388 {
00389   if ( mRecurReadOnly ) return;
00390   RecurrenceRule *rrule = defaultRRule( true );
00391   if ( !rrule ) return;
00392   rrule->setEndDt( dateTime );
00393   updated();
00394 }
00395 
00396 int Recurrence::duration() const
00397 {
00398   RecurrenceRule *rrule = defaultRRuleConst();
00399   if ( rrule ) return rrule->duration();
00400   else return 0;
00401 }
00402 
00403 // int Recurrence::durationTo( const QDate &/*date*/ ) const
00404 // {
00405 //   return 0;
00406 // }
00407 
00408 int Recurrence::durationTo( const QDateTime &datetime ) const
00409 {
00410   // Emulate old behavior: This is just an interface to the first rule!
00411   RecurrenceRule *rrule = defaultRRuleConst();
00412   if ( !rrule ) return 0;
00413   else return rrule->durationTo( datetime );
00414 }
00415 
00416 void Recurrence::setDuration( int duration )
00417 {
00418   if ( mRecurReadOnly ) return;
00419   RecurrenceRule *rrule = defaultRRule( true );
00420   if ( !rrule ) return;
00421   rrule->setDuration( duration );
00422   updated();
00423 }
00424 
00425 void Recurrence::unsetRecurs()
00426 {
00427   if ( mRecurReadOnly ) return;
00428   mRRules.clearAll();
00429   updated();
00430 }
00431 
00432 void Recurrence::clear()
00433 {
00434   if ( mRecurReadOnly ) return;
00435   mRRules.clearAll();
00436   mExRules.clearAll();
00437   mRDates.clear();
00438   mRDateTimes.clear();
00439   mExDates.clear();
00440   mExDateTimes.clear();
00441   mCachedType = rMax;
00442   updated();
00443 }
00444 
00445 void Recurrence::setStartDateTime( const QDateTime &start )
00446 {
00447   if ( mRecurReadOnly ) return;
00448   mStartDateTime = start;
00449   setFloats( false );   // set all RRULEs and EXRULEs
00450 
00451   for ( RecurrenceRule::List::ConstIterator rr = mRRules.begin(); rr != mRRules.end(); ++rr ) {
00452     (*rr)->setStartDt( start );
00453   }
00454   for ( RecurrenceRule::List::ConstIterator rr = mExRules.begin(); rr != mExRules.end(); ++rr ) {
00455     (*rr)->setStartDt( start );
00456   }
00457   updated();
00458 }
00459 
00460 void Recurrence::setStartDate( const QDate &start )
00461 {
00462   setStartDateTime( QDateTime( start, QTime(0,0,0) ) );
00463   setFloats( true );
00464 }
00465 
00466 int Recurrence::frequency() const
00467 {
00468   RecurrenceRule *rrule = defaultRRuleConst();
00469   if ( rrule ) return rrule->frequency();
00470   else return 0;
00471 }
00472 
00473 // Emulate the old behaviour. Make this methods just an interface to the
00474 // first rrule
00475 void Recurrence::setFrequency( int freq )
00476 {
00477   if ( mRecurReadOnly || freq <= 0 ) return;
00478   RecurrenceRule *rrule = defaultRRule( true );
00479   if ( rrule )
00480     rrule->setFrequency( freq );
00481   updated();
00482 }
00483 
00484 
00485 // WEEKLY
00486 
00487 int Recurrence::weekStart() const
00488 {
00489   RecurrenceRule *rrule = defaultRRuleConst();
00490   if ( rrule ) return rrule->weekStart();
00491   else return 1;
00492 }
00493 
00494 // Emulate the old behavior
00495 QBitArray Recurrence::days() const
00496 {
00497   QBitArray days( 7 );
00498   days.fill( 0 );
00499   RecurrenceRule *rrule = defaultRRuleConst();
00500   if ( rrule ) {
00501     QValueList<RecurrenceRule::WDayPos> bydays = rrule->byDays();
00502     for ( QValueListConstIterator<RecurrenceRule::WDayPos> it = bydays.begin();
00503           it != bydays.end(); ++it ) {
00504       if ( (*it).pos() == 0 ) {
00505         days.setBit( (*it).day() - 1 );
00506       }
00507     }
00508   }
00509   return days;
00510 }
00511 
00512 
00513 // MONTHLY
00514 
00515 // Emulate the old behavior
00516 QValueList<int> Recurrence::monthDays() const
00517 {
00518   RecurrenceRule *rrule = defaultRRuleConst();
00519   if ( rrule ) return rrule->byMonthDays();
00520   else return QValueList<int>();
00521 }
00522 
00523 // Emulate the old behavior
00524 QValueList<RecurrenceRule::WDayPos> Recurrence::monthPositions() const
00525 {
00526   RecurrenceRule *rrule = defaultRRuleConst();
00527   if ( rrule ) return rrule->byDays();
00528   else return QValueList<RecurrenceRule::WDayPos>();
00529 }
00530 
00531 
00532 // YEARLY
00533 
00534 QValueList<int> Recurrence::yearDays() const
00535 {
00536   RecurrenceRule *rrule = defaultRRuleConst();
00537   if ( rrule ) return rrule->byYearDays();
00538   else return QValueList<int>();
00539 }
00540 
00541 QValueList<int> Recurrence::yearDates() const
00542 {
00543   return monthDays();
00544 }
00545 
00546 QValueList<int> Recurrence::yearMonths() const
00547 {
00548   RecurrenceRule *rrule = defaultRRuleConst();
00549   if ( rrule ) return rrule->byMonths();
00550   else return QValueList<int>();
00551 }
00552 
00553 QValueList<RecurrenceRule::WDayPos> Recurrence::yearPositions() const
00554 {
00555   return monthPositions();
00556 }
00557 
00558 
00559 
00560 RecurrenceRule *Recurrence::setNewRecurrenceType( RecurrenceRule::PeriodType type, int freq )
00561 {
00562   if ( mRecurReadOnly || freq <= 0 ) return 0;
00563   mRRules.clearAll();
00564   updated();
00565   RecurrenceRule *rrule = defaultRRule( true );
00566   if ( !rrule ) return 0;
00567   rrule->setRecurrenceType( type );
00568   rrule->setFrequency( freq );
00569   rrule->setDuration( -1 );
00570   return rrule;
00571 }
00572 
00573 void Recurrence::setMinutely( int _rFreq )
00574 {
00575   if ( setNewRecurrenceType( RecurrenceRule::rMinutely, _rFreq ) )
00576     updated();
00577 }
00578 
00579 void Recurrence::setHourly( int _rFreq )
00580 {
00581   if ( setNewRecurrenceType( RecurrenceRule::rHourly, _rFreq ) )
00582     updated();
00583 }
00584 
00585 void Recurrence::setDaily( int _rFreq )
00586 {
00587   if ( setNewRecurrenceType( RecurrenceRule::rDaily, _rFreq ) )
00588     updated();
00589 }
00590 
00591 void Recurrence::setWeekly( int freq, int weekStart )
00592 {
00593   RecurrenceRule *rrule = setNewRecurrenceType( RecurrenceRule::rWeekly, freq );
00594   if ( !rrule ) return;
00595   rrule->setWeekStart( weekStart );
00596   updated();
00597 }
00598 
00599 void Recurrence::setWeekly( int freq, const QBitArray &days, int weekStart )
00600 {
00601   setWeekly( freq, weekStart );
00602   addMonthlyPos( 0, days );
00603 }
00604 
00605 void Recurrence::addWeeklyDays( const QBitArray &days )
00606 {
00607   addMonthlyPos( 0, days );
00608 }
00609 
00610 void Recurrence::setMonthly( int freq )
00611 {
00612   if ( setNewRecurrenceType( RecurrenceRule::rMonthly, freq ) )
00613     updated();
00614 }
00615 
00616 void Recurrence::addMonthlyPos( short pos, const QBitArray &days )
00617 {
00618   // Allow 53 for yearly!
00619   if ( mRecurReadOnly || pos > 53 || pos < -53 ) return;
00620   RecurrenceRule *rrule = defaultRRule( false );
00621   if ( !rrule ) return;
00622   bool changed = false;
00623   QValueList<RecurrenceRule::WDayPos> positions = rrule->byDays();
00624 
00625   for ( int i = 0; i < 7; ++i ) {
00626     if ( days.testBit(i) ) {
00627       RecurrenceRule::WDayPos p( pos, i + 1 );
00628       if ( !positions.contains( p ) ) {
00629         changed = true;
00630         positions.append( p );
00631       }
00632     }
00633   }
00634   if ( changed ) {
00635     rrule->setByDays( positions );
00636     updated();
00637   }
00638 }
00639 
00640 
00641 void Recurrence::addMonthlyPos( short pos, ushort day )
00642 {
00643   // Allow 53 for yearly!
00644   if ( mRecurReadOnly || pos > 53 || pos < -53 ) return;
00645   RecurrenceRule *rrule = defaultRRule( false );
00646   if ( !rrule ) return;
00647   QValueList<RecurrenceRule::WDayPos> positions = rrule->byDays();
00648 
00649   RecurrenceRule::WDayPos p( pos, day );
00650   if ( !positions.contains( p ) ) {
00651     positions.append( p );
00652     rrule->setByDays( positions );
00653     updated();
00654   }
00655 }
00656 
00657 
00658 void Recurrence::addMonthlyDate( short day )
00659 {
00660   if ( mRecurReadOnly || day > 31 || day < -31 ) return;
00661   RecurrenceRule *rrule = defaultRRule( true );
00662   if ( !rrule ) return;
00663 
00664   QValueList<int> monthDays = rrule->byMonthDays();
00665   if ( !monthDays.contains( day ) ) {
00666     monthDays.append( day );
00667     rrule->setByMonthDays( monthDays );
00668     updated();
00669   }
00670 }
00671 
00672 void Recurrence::setYearly( int freq )
00673 {
00674   if ( setNewRecurrenceType( RecurrenceRule::rYearly, freq ) )
00675     updated();
00676 }
00677 
00678 
00679 // Daynumber within year
00680 void Recurrence::addYearlyDay( int day )
00681 {
00682   RecurrenceRule *rrule = defaultRRule( false ); // It must already exist!
00683   if ( !rrule ) return;
00684 
00685   QValueList<int> days = rrule->byYearDays();
00686   if ( !days.contains( day ) ) {
00687     days << day;
00688     rrule->setByYearDays( days );
00689     updated();
00690   }
00691 }
00692 
00693 // day part of date within year
00694 void Recurrence::addYearlyDate( int day )
00695 {
00696   addMonthlyDate( day );
00697 }
00698 
00699 // day part of date within year, given as position (n-th weekday)
00700 void Recurrence::addYearlyPos( short pos, const QBitArray &days )
00701 {
00702   addMonthlyPos( pos, days );
00703 }
00704 
00705 
00706 // month part of date within year
00707 void Recurrence::addYearlyMonth( short month )
00708 {
00709   if ( mRecurReadOnly || month < 1 || month > 12 ) return;
00710   RecurrenceRule *rrule = defaultRRule( false );
00711   if ( !rrule ) return;
00712 
00713   QValueList<int> months = rrule->byMonths();
00714   if ( !months.contains(month) ) {
00715     months << month;
00716     rrule->setByMonths( months );
00717     updated();
00718   }
00719 }
00720 
00721 
00722 TimeList Recurrence::recurTimesOn( const QDate &date ) const
00723 {
00724   TimeList times;
00725   // The whole day is excepted
00726   if ( mExDates.contains( date ) ) return times;
00727   // EXRULE takes precedence over RDATE entries, so for floating events,
00728   // a matching excule also excludes the whole day automatically
00729   if ( doesFloat() ) {
00730     for ( RecurrenceRule::List::ConstIterator rr = mExRules.begin(); rr != mExRules.end(); ++rr ) {
00731       if ( (*rr)->recursOn( date ) )
00732         return times;
00733     }
00734   }
00735 
00736   if ( startDate() == date ) times << startDateTime().time();
00737   bool foundDate = false;
00738   for ( DateTimeList::ConstIterator it = mRDateTimes.begin();
00739         it != mRDateTimes.end(); ++it ) {
00740     if ( (*it).date() == date ) {
00741       times << (*it).time();
00742       foundDate = true;
00743     } else if (foundDate) break; // <= Assume that the rdatetime list is sorted
00744   }
00745   for ( RecurrenceRule::List::ConstIterator rr = mRRules.begin(); rr != mRRules.end(); ++rr ) {
00746     times += (*rr)->recurTimesOn( date );
00747   }
00748   qSortUnique( times );
00749 
00750   foundDate = false;
00751   TimeList extimes;
00752   for ( DateTimeList::ConstIterator it = mExDateTimes.begin();
00753         it != mExDateTimes.end(); ++it ) {
00754     if ( (*it).date() == date ) {
00755       extimes << (*it).time();
00756       foundDate = true;
00757     } else if (foundDate) break;
00758   }
00759   if ( !doesFloat() ) {     // we have already checked floating times above
00760     for ( RecurrenceRule::List::ConstIterator rr = mExRules.begin(); rr != mExRules.end(); ++rr ) {
00761       extimes += (*rr)->recurTimesOn( date );
00762     }
00763   }
00764   qSortUnique( extimes );
00765 
00766   for ( TimeList::Iterator it = extimes.begin(); it != extimes.end(); ++it ) {
00767     times.remove( (*it) );
00768   }
00769   return times;
00770 }
00771 
00772 DateTimeList Recurrence::timesInInterval( const QDateTime &start, const QDateTime &end ) const
00773 {
00774   int i, count;
00775   DateTimeList times;
00776   for ( i = 0, count = mRRules.count();  i < count;  ++i ) {
00777     times += mRRules[i]->timesInInterval( start, end );
00778   }
00779 
00780   // add rdatetimes that fit in the interval
00781   for ( i = 0, count = mRDateTimes.count();  i < count;  ++i ) {
00782     if ( mRDateTimes[i] >= start && mRDateTimes[i] <= end ) {
00783       times += mRDateTimes[i];
00784     }
00785   }
00786 
00787   // add rdates that fit in the interval
00788   QDateTime qdt( mStartDateTime );
00789   for ( i = 0, count = mRDates.count();  i < count;  ++i ) {
00790     qdt.setDate( mRDates[i] );
00791     if ( qdt >= start && qdt <= end ) {
00792       times += qdt;
00793     }
00794   }
00795 
00796   // Recurrence::timesInInterval(...) doesn't explicitly add mStartDateTime to the list
00797   // of times to be returned. It calls mRRules[i]->timesInInterval(...) which include
00798   // mStartDateTime.
00799   // So, If we have rdates/rdatetimes but don't have any rrule we must explicitly
00800   // add mStartDateTime to the list, otherwise we won't see the first occurrence.
00801   if ( ( !mRDates.isEmpty() || !mRDateTimes.isEmpty() ) &&
00802        mRRules.isEmpty() &&
00803        start <= mStartDateTime &&
00804        end >= mStartDateTime ) {
00805     times += mStartDateTime;
00806   }
00807 
00808   qSortUnique( times );
00809 
00810   // Remove excluded times
00811   int idt = 0;
00812   int enddt = times.count();
00813   for ( i = 0, count = mExDates.count();  i < count && idt < enddt;  ++i ) {
00814     while ( idt < enddt && times[idt].date() < mExDates[i] ) ++idt;
00815     while ( idt < enddt && times[idt].date() == mExDates[i] ) {
00816       times.remove( times.at( idt ) );
00817       --enddt;
00818     }
00819   }
00820   DateTimeList extimes;
00821   for ( i = 0, count = mExRules.count();  i < count;  ++i ) {
00822     extimes += mExRules[i]->timesInInterval( start, end );
00823   }
00824   extimes += mExDateTimes;
00825   qSortUnique( extimes );
00826 
00827   int st = 0;
00828   for ( i = 0, count = extimes.count();  i < count;  ++i ) {
00829     int j = removeSorted( times, extimes[i], st );
00830     if ( j >= 0 ) {
00831       st = j;
00832     }
00833   }
00834 
00835   return times;
00836 }
00837 
00838 QDateTime Recurrence::getNextDateTime( const QDateTime &preDateTime ) const
00839 {
00840 //kdDebug(5800) << " Recurrence::getNextDateTime after " << preDateTime << endl;
00841   QDateTime nextDT = preDateTime;
00842   // prevent infinite loops, e.g. when an exrule extinguishes an rrule (e.g.
00843   // the exrule is identical to the rrule). If an occurrence is found, break
00844   // out of the loop by returning that QDateTime
00845 // TODO_Recurrence: Is a loop counter of 1000 really okay? I mean for secondly
00846 // recurrence, an exdate might exclude more than 1000 intervals!
00847   int loop = 0;
00848   while ( loop < 1000 ) {
00849     // Outline of the algo:
00850     //   1) Find the next date/time after preDateTime when the event could recur
00851     //     1.0) Add the start date if it's after preDateTime
00852     //     1.1) Use the next occurrence from the explicit RDATE lists
00853     //     1.2) Add the next recurrence for each of the RRULEs
00854     //   2) Take the earliest recurrence of these = QDateTime nextDT
00855     //   3) If that date/time is not excluded, either explicitly by an EXDATE or
00856     //      by an EXRULE, return nextDT as the next date/time of the recurrence
00857     //   4) If it's excluded, start all at 1), but starting at nextDT (instead
00858     //      of preDateTime). Loop at most 1000 times.
00859     ++loop;
00860     // First, get the next recurrence from the RDate lists
00861     DateTimeList dates;
00862     if ( nextDT < startDateTime() ) dates << startDateTime();
00863     DateTimeList::ConstIterator it = mRDateTimes.begin();
00864     // Assume that the rdatetime list is sorted
00865     while ( it != mRDateTimes.end() && (*it) <= nextDT ) ++it;
00866     if ( it != mRDateTimes.end() ) dates << (*it);
00867 
00868 /*kdDebug(5800) << "    nextDT: " << nextDT << ", startDT: " << startDateTime() << endl;
00869 kdDebug(5800) << "   getNextDateTime: found " << dates.count() << " RDATES and DTSTART in loop " << loop << endl;*/
00870     DateList::ConstIterator dit = mRDates.begin();
00871     while ( dit != mRDates.end() && QDateTime( (*dit), startDateTime().time() ) <= nextDT ) ++dit;
00872     if ( dit != mRDates.end() ) dates << QDateTime( (*dit), startDateTime().time() );
00873 
00874     // Add the next occurrences from all RRULEs.
00875     for ( RecurrenceRule::List::ConstIterator rr = mRRules.begin(); rr != mRRules.end(); ++rr ) {
00876       QDateTime dt = (*rr)->getNextDate( nextDT );
00877       if ( dt.isValid() ) dates << dt;
00878     }
00879 
00880     // Take the first of these (all others can't be used later on)
00881     qSortUnique( dates );
00882 // kdDebug(5800) << "   getNextDateTime: found " << dates.count() << " dates in loop " << loop << endl;
00883 
00884     if ( dates.isEmpty() ) return QDateTime();
00885     nextDT = dates.first();
00886 
00887     // Check if that date/time is excluded explicitly or by an exrule:
00888     if ( !mExDates.contains( nextDT.date() ) && !mExDateTimes.contains( nextDT ) ) {
00889 // kdDebug(5800) << "   NextDT" << nextDT << " not excluded by EXDATE " << endl;
00890       bool allowed = true;
00891       for ( RecurrenceRule::List::ConstIterator rr = mExRules.begin(); rr != mExRules.end(); ++rr ) {
00892         allowed = allowed && !( (*rr)->recursAt( nextDT ) );
00893       }
00894 // kdDebug(5800) << "   NextDT " << nextDT << ", allowed=" << allowed << endl;
00895       if ( allowed ) return nextDT;
00896     }
00897   }
00898 
00899   // Couldn't find a valid occurrences in 1000 loops, something is wrong!
00900   return QDateTime();
00901 }
00902 
00903 QDateTime Recurrence::getPreviousDateTime( const QDateTime &afterDateTime ) const
00904 {
00905   QDateTime prevDT = afterDateTime;
00906   // prevent infinite loops, e.g. when an exrule extinguishes an rrule (e.g.
00907   // the exrule is identical to the rrule). If an occurrence is found, break
00908   // out of the loop by returning that QDateTime
00909   int loop = 0;
00910   while ( loop < 1000 ) {
00911     // Outline of the algo:
00912     //   1) Find the next date/time after preDateTime when the event could recur
00913     //     1.1) Use the next occurrence from the explicit RDATE lists
00914     //     1.2) Add the next recurrence for each of the RRULEs
00915     //   2) Take the earliest recurrence of these = QDateTime nextDT
00916     //   3) If that date/time is not excluded, either explicitly by an EXDATE or
00917     //      by an EXRULE, return nextDT as the next date/time of the recurrence
00918     //   4) If it's excluded, start all at 1), but starting at nextDT (instead
00919     //      of preDateTime). Loop at most 1000 times.
00920     ++loop;
00921     // First, get the next recurrence from the RDate lists
00922     DateTimeList dates;
00923     if ( prevDT > startDateTime() ) dates << startDateTime();
00924 
00925     DateTimeList::ConstIterator dtit = mRDateTimes.end();
00926     if ( dtit != mRDateTimes.begin() ) {
00927       do {
00928         --dtit;
00929       } while ( dtit != mRDateTimes.begin() && (*dtit) >= prevDT );
00930       if ( (*dtit) < prevDT ) dates << (*dtit);
00931     }
00932 
00933     DateList::ConstIterator dit = mRDates.end();
00934     if ( dit != mRDates.begin() ) {
00935       do {
00936         --dit;
00937       } while ( dit != mRDates.begin() && QDateTime((*dit), startDateTime().time()) >= prevDT );
00938       if ( QDateTime((*dit), startDateTime().time()) < prevDT )
00939         dates << QDateTime( (*dit), startDateTime().time() );
00940     }
00941 
00942     // Add the previous occurrences from all RRULEs.
00943     for ( RecurrenceRule::List::ConstIterator rr = mRRules.begin(); rr != mRRules.end(); ++rr ) {
00944       QDateTime dt = (*rr)->getPreviousDate( prevDT );
00945       if ( dt.isValid() ) dates << dt;
00946     }
00947 //kdDebug(5800) << "   getPreviousDateTime: found " << dates.count() << " dates in loop " << loop << endl;
00948 
00949     // Take the last of these (all others can't be used later on)
00950     qSortUnique( dates );
00951     if ( dates.isEmpty() ) return QDateTime();
00952     prevDT = dates.last();
00953 
00954     // Check if that date/time is excluded explicitly or by an exrule:
00955     if ( !mExDates.contains( prevDT.date() ) && !mExDateTimes.contains( prevDT ) ) {
00956       bool allowed = true;
00957       for ( RecurrenceRule::List::ConstIterator rr = mExRules.begin(); rr != mExRules.end(); ++rr ) {
00958         allowed = allowed && !( (*rr)->recursAt( prevDT ) );
00959       }
00960       if ( allowed ) return prevDT;
00961     }
00962   }
00963 
00964   // Couldn't find a valid occurrences in 1000 loops, something is wrong!
00965   return QDateTime();
00966 }
00967 
00968 
00969 /***************************** PROTECTED FUNCTIONS ***************************/
00970 
00971 
00972 RecurrenceRule::List Recurrence::rRules() const
00973 {
00974   return mRRules;
00975 }
00976 
00977 void Recurrence::addRRule( RecurrenceRule *rrule )
00978 {
00979   if ( mRecurReadOnly || !rrule ) return;
00980   rrule->setFloats( mFloating );
00981   mRRules.append( rrule );
00982   rrule->addObserver( this );
00983   updated();
00984 }
00985 
00986 void Recurrence::removeRRule( RecurrenceRule *rrule )
00987 {
00988   if (mRecurReadOnly) return;
00989   mRRules.remove( rrule );
00990   rrule->removeObserver( this );
00991   updated();
00992 }
00993 
00994 RecurrenceRule::List Recurrence::exRules() const
00995 {
00996   return mExRules;
00997 }
00998 
00999 void Recurrence::addExRule( RecurrenceRule *exrule )
01000 {
01001   if ( mRecurReadOnly || !exrule ) return;
01002   exrule->setFloats( mFloating );
01003   mExRules.append( exrule );
01004   exrule->addObserver( this );
01005   updated();
01006 }
01007 
01008 void Recurrence::removeExRule( RecurrenceRule *exrule )
01009 {
01010   if (mRecurReadOnly) return;
01011   mExRules.remove( exrule );
01012   exrule->removeObserver( this );
01013   updated();
01014 }
01015 
01016 
01017 DateTimeList Recurrence::rDateTimes() const
01018 {
01019   return mRDateTimes;
01020 }
01021 
01022 void Recurrence::setRDateTimes( const DateTimeList &rdates )
01023 {
01024   if ( mRecurReadOnly ) return;
01025   mRDateTimes = rdates;
01026   qSortUnique( mRDateTimes );
01027   updated();
01028 }
01029 
01030 void Recurrence::addRDateTime( const QDateTime &rdate )
01031 {
01032   if ( mRecurReadOnly ) return;
01033   mRDateTimes.append( rdate );
01034   qSortUnique( mRDateTimes );
01035   updated();
01036 }
01037 
01038 
01039 DateList Recurrence::rDates() const
01040 {
01041   return mRDates;
01042 }
01043 
01044 void Recurrence::setRDates( const DateList &rdates )
01045 {
01046   if ( mRecurReadOnly ) return;
01047   mRDates = rdates;
01048   qSortUnique( mRDates );
01049   updated();
01050 }
01051 
01052 void Recurrence::addRDate( const QDate &rdate )
01053 {
01054   if ( mRecurReadOnly ) return;
01055   mRDates.append( rdate );
01056   qSortUnique( mRDates );
01057   updated();
01058 }
01059 
01060 
01061 DateTimeList Recurrence::exDateTimes() const
01062 {
01063   return mExDateTimes;
01064 }
01065 
01066 void Recurrence::setExDateTimes( const DateTimeList &exdates )
01067 {
01068   if ( mRecurReadOnly ) return;
01069   mExDateTimes = exdates;
01070   qSortUnique( mExDateTimes );
01071 }
01072 
01073 void Recurrence::addExDateTime( const QDateTime &exdate )
01074 {
01075   if ( mRecurReadOnly ) return;
01076   mExDateTimes.append( exdate );
01077   qSortUnique( mExDateTimes );
01078   updated();
01079 }
01080 
01081 
01082 DateList Recurrence::exDates() const
01083 {
01084   return mExDates;
01085 }
01086 
01087 void Recurrence::setExDates( const DateList &exdates )
01088 {
01089   if ( mRecurReadOnly ) return;
01090   mExDates = exdates;
01091   qSortUnique( mExDates );
01092   updated();
01093 }
01094 
01095 void Recurrence::addExDate( const QDate &exdate )
01096 {
01097   if ( mRecurReadOnly ) return;
01098   mExDates.append( exdate );
01099   qSortUnique( mExDates );
01100   updated();
01101 }
01102 
01103 void Recurrence::recurrenceChanged( RecurrenceRule * )
01104 {
01105   updated();
01106 }
01107 
01108 
01109 // %%%%%%%%%%%%%%%%%% end:Recurrencerule %%%%%%%%%%%%%%%%%%
01110 
01111 void Recurrence::dump() const
01112 {
01113   kdDebug(5800) << "Recurrence::dump():" << endl;
01114 
01115   kdDebug(5800) << "  -) " << mRRules.count() << " RRULEs: " << endl;
01116   for ( RecurrenceRule::List::ConstIterator rr = mRRules.begin(); rr != mRRules.end(); ++rr ) {
01117     kdDebug(5800) << "    -) RecurrenceRule : " << endl;
01118     (*rr)->dump();
01119   }
01120   kdDebug(5800) << "  -) " << mExRules.count() << " EXRULEs: " << endl;
01121   for ( RecurrenceRule::List::ConstIterator rr = mExRules.begin(); rr != mExRules.end(); ++rr ) {
01122     kdDebug(5800) << "    -) ExceptionRule : " << endl;
01123     (*rr)->dump();
01124   }
01125 
01126 
01127   kdDebug(5800) << endl << "  -) " << mRDates.count() << " Recurrence Dates: " << endl;
01128   for ( DateList::ConstIterator it = mRDates.begin(); it != mRDates.end(); ++it ) {
01129     kdDebug(5800) << "     " << (*it) << endl;
01130   }
01131   kdDebug(5800) << endl << "  -) " << mRDateTimes.count() << " Recurrence Date/Times: " << endl;
01132   for ( DateTimeList::ConstIterator it = mRDateTimes.begin(); it != mRDateTimes.end(); ++it ) {
01133     kdDebug(5800) << "     " << (*it) << endl;
01134   }
01135   kdDebug(5800) << endl << "  -) " << mExDates.count() << " Exceptions Dates: " << endl;
01136   for ( DateList::ConstIterator it = mExDates.begin(); it != mExDates.end(); ++it ) {
01137     kdDebug(5800) << "     " << (*it) << endl;
01138   }
01139   kdDebug(5800) << endl << "  -) " << mExDateTimes.count() << " Exception Date/Times: " << endl;
01140   for ( DateTimeList::ConstIterator it = mExDateTimes.begin(); it != mExDateTimes.end(); ++it ) {
01141     kdDebug(5800) << "     " << (*it) << endl;
01142   }
01143 }
KDE Home | KDE Accessibility Home | Description of Access Keys