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   // Check if it might recur today at all.
00286   bool recurs = false;
00287   if ( startDate() == qd ) recurs = true;
00288   for ( RecurrenceRule::List::ConstIterator rr = mRRules.begin(); rr != mRRules.end(); ++rr ) {
00289     recurs = recurs || (*rr)->recursOn( qd );
00290   }
00291   // If we already know it recurs, no need to check the rdate list too.
00292   if ( !recurs ) {
00293     for ( DateTimeList::ConstIterator rit = mRDateTimes.begin();
00294           rit != mRDateTimes.end(); ++rit ) {
00295       if ( (*rit).date() == qd ) {
00296         recurs = true;
00297         break;
00298       }
00299     }
00300   }
00301   // If the event wouldn't recur at all, simply return false, don't check ex*
00302   if ( !recurs ) return false;
00303 
00304   // Check if there are any times for this day excluded, either by exdate or exrule:
00305   bool exon = false;
00306   for ( DateTimeList::ConstIterator exit = mExDateTimes.begin();
00307         exit != mExDateTimes.end(); ++exit ) {
00308     if ( (*exit).date() == qd ) {
00309       exon = true;
00310       break;
00311     }
00312   }
00313   if ( !doesFloat() ) {     // we have already checked floating times above
00314     for ( RecurrenceRule::List::ConstIterator rr = mExRules.begin(); rr != mExRules.end(); ++rr ) {
00315       exon = exon || (*rr)->recursOn( qd );
00316     }
00317   }
00318 
00319   if ( !exon ) {
00320     // Simple case, nothing on that day excluded, return the value from before
00321     return recurs;
00322   } else {
00323     // Harder part: I don't think there is any way other than to calculate the
00324     // whole list of items for that day.
00325     TimeList timesForDay( recurTimesOn( qd ) );
00326     return !timesForDay.isEmpty();
00327   }
00328 }
00329 
00330 bool Recurrence::recursAt( const QDateTime &dt ) const
00331 {
00332   // if it's excluded anyway, don't bother to check if it recurs at all.
00333   if ( mExDateTimes.contains( dt )) return false;
00334   if ( mExDates.contains( dt.date() )) return false;
00335   for ( RecurrenceRule::List::ConstIterator rr = mExRules.begin(); rr != mExRules.end(); ++rr ) {
00336     if ( (*rr)->recursAt( dt ) ) return false;
00337   }
00338 
00339   // Check explicit recurrences, then rrules.
00340   bool occurs = ( startDateTime() == dt ) || mRDateTimes.contains( dt );
00341   if ( occurs )
00342     return true;
00343   for ( RecurrenceRule::List::ConstIterator rr = mRRules.begin(); rr != mRRules.end(); ++rr ) {
00344     if ( (*rr)->recursAt( dt ) ) return true;
00345   }
00346 
00347   return false;
00348 }
00349 
00353 QDateTime Recurrence::endDateTime() const
00354 {
00355   DateTimeList dts;
00356   dts << startDateTime();
00357   if ( !mRDates.isEmpty() ) dts << QDateTime( mRDates.last(), QTime( 0, 0, 0 ) );
00358   if ( !mRDateTimes.isEmpty() ) dts << mRDateTimes.last();
00359   for ( RecurrenceRule::List::ConstIterator rr = mRRules.begin(); rr != mRRules.end(); ++rr ) {
00360     QDateTime rl( (*rr)->endDt() );
00361     // if any of the rules is infinite, the whole recurrence is
00362     if ( !rl.isValid() ) return QDateTime();
00363     dts << rl;
00364   }
00365   qSortUnique( dts );
00366   if ( dts.isEmpty() ) return QDateTime();
00367   else return dts.last();
00368 }
00369 
00373 QDate Recurrence::endDate() const
00374 {
00375   QDateTime end( endDateTime() );
00376   if ( end.isValid() ) { return end.date(); }
00377   else return QDate();
00378 }
00379 
00380 void Recurrence::setEndDate( const QDate &date )
00381 {
00382   if ( doesFloat() )
00383     setEndDateTime( QDateTime( date, QTime( 23, 59, 59 ) ) );
00384   else
00385     setEndDateTime( QDateTime( date, mStartDateTime.time() ) );
00386 }
00387 
00388 void Recurrence::setEndDateTime( const QDateTime &dateTime )
00389 {
00390   if ( mRecurReadOnly ) return;
00391   RecurrenceRule *rrule = defaultRRule( true );
00392   if ( !rrule ) return;
00393   rrule->setEndDt( dateTime );
00394   updated();
00395 }
00396 
00397 int Recurrence::duration() const
00398 {
00399   RecurrenceRule *rrule = defaultRRuleConst();
00400   if ( rrule ) return rrule->duration();
00401   else return 0;
00402 }
00403 
00404 // int Recurrence::durationTo( const QDate &/*date*/ ) const
00405 // {
00406 //   return 0;
00407 // }
00408 
00409 int Recurrence::durationTo( const QDateTime &datetime ) const
00410 {
00411   // Emulate old behavior: This is just an interface to the first rule!
00412   RecurrenceRule *rrule = defaultRRuleConst();
00413   if ( !rrule ) return 0;
00414   else return rrule->durationTo( datetime );
00415 }
00416 
00417 void Recurrence::setDuration( int duration )
00418 {
00419   if ( mRecurReadOnly ) return;
00420   RecurrenceRule *rrule = defaultRRule( true );
00421   if ( !rrule ) return;
00422   rrule->setDuration( duration );
00423   updated();
00424 }
00425 
00426 void Recurrence::unsetRecurs()
00427 {
00428   if ( mRecurReadOnly ) return;
00429   mRRules.clearAll();
00430   updated();
00431 }
00432 
00433 void Recurrence::clear()
00434 {
00435   if ( mRecurReadOnly ) return;
00436   mRRules.clearAll();
00437   mExRules.clearAll();
00438   mRDates.clear();
00439   mRDateTimes.clear();
00440   mExDates.clear();
00441   mExDateTimes.clear();
00442   mCachedType = rMax;
00443   updated();
00444 }
00445 
00446 void Recurrence::setStartDateTime( const QDateTime &start )
00447 {
00448   if ( mRecurReadOnly ) return;
00449   mStartDateTime = start;
00450   setFloats( false );   // set all RRULEs and EXRULEs
00451 
00452   for ( RecurrenceRule::List::ConstIterator rr = mRRules.begin(); rr != mRRules.end(); ++rr ) {
00453     (*rr)->setStartDt( start );
00454   }
00455   for ( RecurrenceRule::List::ConstIterator rr = mExRules.begin(); rr != mExRules.end(); ++rr ) {
00456     (*rr)->setStartDt( start );
00457   }
00458   updated();
00459 }
00460 
00461 void Recurrence::setStartDate( const QDate &start )
00462 {
00463   setStartDateTime( QDateTime( start, QTime(0,0,0) ) );
00464   setFloats( true );
00465 }
00466 
00467 int Recurrence::frequency() const
00468 {
00469   RecurrenceRule *rrule = defaultRRuleConst();
00470   if ( rrule ) return rrule->frequency();
00471   else return 0;
00472 }
00473 
00474 // Emulate the old behaviour. Make this methods just an interface to the
00475 // first rrule
00476 void Recurrence::setFrequency( int freq )
00477 {
00478   if ( mRecurReadOnly || freq <= 0 ) return;
00479   RecurrenceRule *rrule = defaultRRule( true );
00480   if ( rrule )
00481     rrule->setFrequency( freq );
00482   updated();
00483 }
00484 
00485 
00486 // WEEKLY
00487 
00488 int Recurrence::weekStart() const
00489 {
00490   RecurrenceRule *rrule = defaultRRuleConst();
00491   if ( rrule ) return rrule->weekStart();
00492   else return 1;
00493 }
00494 
00495 // Emulate the old behavior
00496 QBitArray Recurrence::days() const
00497 {
00498   QBitArray days( 7 );
00499   days.fill( 0 );
00500   RecurrenceRule *rrule = defaultRRuleConst();
00501   if ( rrule ) {
00502     QValueList<RecurrenceRule::WDayPos> bydays = rrule->byDays();
00503     for ( QValueListConstIterator<RecurrenceRule::WDayPos> it = bydays.begin();
00504           it != bydays.end(); ++it ) {
00505       if ( (*it).pos() == 0 ) {
00506         days.setBit( (*it).day() - 1 );
00507       }
00508     }
00509   }
00510   return days;
00511 }
00512 
00513 
00514 // MONTHLY
00515 
00516 // Emulate the old behavior
00517 QValueList<int> Recurrence::monthDays() const
00518 {
00519   RecurrenceRule *rrule = defaultRRuleConst();
00520   if ( rrule ) return rrule->byMonthDays();
00521   else return QValueList<int>();
00522 }
00523 
00524 // Emulate the old behavior
00525 QValueList<RecurrenceRule::WDayPos> Recurrence::monthPositions() const
00526 {
00527   RecurrenceRule *rrule = defaultRRuleConst();
00528   if ( rrule ) return rrule->byDays();
00529   else return QValueList<RecurrenceRule::WDayPos>();
00530 }
00531 
00532 
00533 // YEARLY
00534 
00535 QValueList<int> Recurrence::yearDays() const
00536 {
00537   RecurrenceRule *rrule = defaultRRuleConst();
00538   if ( rrule ) return rrule->byYearDays();
00539   else return QValueList<int>();
00540 }
00541 
00542 QValueList<int> Recurrence::yearDates() const
00543 {
00544   return monthDays();
00545 }
00546 
00547 QValueList<int> Recurrence::yearMonths() const
00548 {
00549   RecurrenceRule *rrule = defaultRRuleConst();
00550   if ( rrule ) return rrule->byMonths();
00551   else return QValueList<int>();
00552 }
00553 
00554 QValueList<RecurrenceRule::WDayPos> Recurrence::yearPositions() const
00555 {
00556   return monthPositions();
00557 }
00558 
00559 
00560 
00561 RecurrenceRule *Recurrence::setNewRecurrenceType( RecurrenceRule::PeriodType type, int freq )
00562 {
00563   if ( mRecurReadOnly || freq <= 0 ) return 0;
00564   mRRules.clearAll();
00565   updated();
00566   RecurrenceRule *rrule = defaultRRule( true );
00567   if ( !rrule ) return 0;
00568   rrule->setRecurrenceType( type );
00569   rrule->setFrequency( freq );
00570   rrule->setDuration( -1 );
00571   return rrule;
00572 }
00573 
00574 void Recurrence::setMinutely( int _rFreq )
00575 {
00576   if ( setNewRecurrenceType( RecurrenceRule::rMinutely, _rFreq ) )
00577     updated();
00578 }
00579 
00580 void Recurrence::setHourly( int _rFreq )
00581 {
00582   if ( setNewRecurrenceType( RecurrenceRule::rHourly, _rFreq ) )
00583     updated();
00584 }
00585 
00586 void Recurrence::setDaily( int _rFreq )
00587 {
00588   if ( setNewRecurrenceType( RecurrenceRule::rDaily, _rFreq ) )
00589     updated();
00590 }
00591 
00592 void Recurrence::setWeekly( int freq, int weekStart )
00593 {
00594   RecurrenceRule *rrule = setNewRecurrenceType( RecurrenceRule::rWeekly, freq );
00595   if ( !rrule ) return;
00596   rrule->setWeekStart( weekStart );
00597   updated();
00598 }
00599 
00600 void Recurrence::setWeekly( int freq, const QBitArray &days, int weekStart )
00601 {
00602   setWeekly( freq, weekStart );
00603   addMonthlyPos( 0, days );
00604 }
00605 
00606 void Recurrence::addWeeklyDays( const QBitArray &days )
00607 {
00608   addMonthlyPos( 0, days );
00609 }
00610 
00611 void Recurrence::setMonthly( int freq )
00612 {
00613   if ( setNewRecurrenceType( RecurrenceRule::rMonthly, freq ) )
00614     updated();
00615 }
00616 
00617 void Recurrence::addMonthlyPos( short pos, const QBitArray &days )
00618 {
00619   // Allow 53 for yearly!
00620   if ( mRecurReadOnly || pos > 53 || pos < -53 ) return;
00621   RecurrenceRule *rrule = defaultRRule( false );
00622   if ( !rrule ) return;
00623   bool changed = false;
00624   QValueList<RecurrenceRule::WDayPos> positions = rrule->byDays();
00625 
00626   for ( int i = 0; i < 7; ++i ) {
00627     if ( days.testBit(i) ) {
00628       RecurrenceRule::WDayPos p( pos, i + 1 );
00629       if ( !positions.contains( p ) ) {
00630         changed = true;
00631         positions.append( p );
00632       }
00633     }
00634   }
00635   if ( changed ) {
00636     rrule->setByDays( positions );
00637     updated();
00638   }
00639 }
00640 
00641 
00642 void Recurrence::addMonthlyPos( short pos, ushort day )
00643 {
00644   // Allow 53 for yearly!
00645   if ( mRecurReadOnly || pos > 53 || pos < -53 ) return;
00646   RecurrenceRule *rrule = defaultRRule( false );
00647   if ( !rrule ) return;
00648   QValueList<RecurrenceRule::WDayPos> positions = rrule->byDays();
00649 
00650   RecurrenceRule::WDayPos p( pos, day );
00651   if ( !positions.contains( p ) ) {
00652     positions.append( p );
00653     rrule->setByDays( positions );
00654     updated();
00655   }
00656 }
00657 
00658 
00659 void Recurrence::addMonthlyDate( short day )
00660 {
00661   if ( mRecurReadOnly || day > 31 || day < -31 ) return;
00662   RecurrenceRule *rrule = defaultRRule( true );
00663   if ( !rrule ) return;
00664 
00665   QValueList<int> monthDays = rrule->byMonthDays();
00666   if ( !monthDays.contains( day ) ) {
00667     monthDays.append( day );
00668     rrule->setByMonthDays( monthDays );
00669     updated();
00670   }
00671 }
00672 
00673 void Recurrence::setYearly( int freq )
00674 {
00675   if ( setNewRecurrenceType( RecurrenceRule::rYearly, freq ) )
00676     updated();
00677 }
00678 
00679 
00680 // Daynumber within year
00681 void Recurrence::addYearlyDay( int day )
00682 {
00683   RecurrenceRule *rrule = defaultRRule( false ); // It must already exist!
00684   if ( !rrule ) return;
00685 
00686   QValueList<int> days = rrule->byYearDays();
00687   if ( !days.contains( day ) ) {
00688     days << day;
00689     rrule->setByYearDays( days );
00690     updated();
00691   }
00692 }
00693 
00694 // day part of date within year
00695 void Recurrence::addYearlyDate( int day )
00696 {
00697   addMonthlyDate( day );
00698 }
00699 
00700 // day part of date within year, given as position (n-th weekday)
00701 void Recurrence::addYearlyPos( short pos, const QBitArray &days )
00702 {
00703   addMonthlyPos( pos, days );
00704 }
00705 
00706 
00707 // month part of date within year
00708 void Recurrence::addYearlyMonth( short month )
00709 {
00710   if ( mRecurReadOnly || month < 1 || month > 12 ) return;
00711   RecurrenceRule *rrule = defaultRRule( false );
00712   if ( !rrule ) return;
00713 
00714   QValueList<int> months = rrule->byMonths();
00715   if ( !months.contains(month) ) {
00716     months << month;
00717     rrule->setByMonths( months );
00718     updated();
00719   }
00720 }
00721 
00722 
00723 TimeList Recurrence::recurTimesOn( const QDate &date ) const
00724 {
00725   TimeList times;
00726   // The whole day is excepted
00727   if ( mExDates.contains( date ) ) return times;
00728   // EXRULE takes precedence over RDATE entries, so for floating events,
00729   // a matching excule also excludes the whole day automatically
00730   if ( doesFloat() ) {
00731     for ( RecurrenceRule::List::ConstIterator rr = mExRules.begin(); rr != mExRules.end(); ++rr ) {
00732       if ( (*rr)->recursOn( date ) )
00733         return times;
00734     }
00735   }
00736 
00737   if ( startDate() == date ) times << startDateTime().time();
00738   bool foundDate = false;
00739   for ( DateTimeList::ConstIterator it = mRDateTimes.begin();
00740         it != mRDateTimes.end(); ++it ) {
00741     if ( (*it).date() == date ) {
00742       times << (*it).time();
00743       foundDate = true;
00744     } else if (foundDate) break; // <= Assume that the rdatetime list is sorted
00745   }
00746   for ( RecurrenceRule::List::ConstIterator rr = mRRules.begin(); rr != mRRules.end(); ++rr ) {
00747     times += (*rr)->recurTimesOn( date );
00748   }
00749   qSortUnique( times );
00750 
00751   foundDate = false;
00752   TimeList extimes;
00753   for ( DateTimeList::ConstIterator it = mExDateTimes.begin();
00754         it != mExDateTimes.end(); ++it ) {
00755     if ( (*it).date() == date ) {
00756       extimes << (*it).time();
00757       foundDate = true;
00758     } else if (foundDate) break;
00759   }
00760   if ( !doesFloat() ) {     // we have already checked floating times above
00761     for ( RecurrenceRule::List::ConstIterator rr = mExRules.begin(); rr != mExRules.end(); ++rr ) {
00762       extimes += (*rr)->recurTimesOn( date );
00763     }
00764   }
00765   qSortUnique( extimes );
00766 
00767   for ( TimeList::Iterator it = extimes.begin(); it != extimes.end(); ++it ) {
00768     times.remove( (*it) );
00769   }
00770   return times;
00771 }
00772 
00773 DateTimeList Recurrence::timesInInterval( const QDateTime &start, const QDateTime &end ) const
00774 {
00775   int i, count;
00776   DateTimeList times;
00777   for ( i = 0, count = mRRules.count();  i < count;  ++i ) {
00778     times += mRRules[i]->timesInInterval( start, end );
00779   }
00780 
00781   // add rdatetimes that fit in the interval
00782   for ( i = 0, count = mRDateTimes.count();  i < count;  ++i ) {
00783     if ( mRDateTimes[i] >= start && mRDateTimes[i] <= end ) {
00784       times += mRDateTimes[i];
00785     }
00786   }
00787 
00788   // add rdates that fit in the interval
00789   QDateTime qdt( mStartDateTime );
00790   for ( i = 0, count = mRDates.count();  i < count;  ++i ) {
00791     qdt.setDate( mRDates[i] );
00792     if ( qdt >= start && qdt <= end ) {
00793       times += qdt;
00794     }
00795   }
00796 
00797   // Recurrence::timesInInterval(...) doesn't explicitly add mStartDateTime to the list
00798   // of times to be returned. It calls mRRules[i]->timesInInterval(...) which include
00799   // mStartDateTime.
00800   // So, If we have rdates/rdatetimes but don't have any rrule we must explicitly
00801   // add mStartDateTime to the list, otherwise we won't see the first occurrence.
00802   if ( ( !mRDates.isEmpty() || !mRDateTimes.isEmpty() ) &&
00803        mRRules.isEmpty() &&
00804        start <= mStartDateTime &&
00805        end >= mStartDateTime ) {
00806     times += mStartDateTime;
00807   }
00808 
00809   qSortUnique( times );
00810 
00811   // Remove excluded times
00812   int idt = 0;
00813   int enddt = times.count();
00814   for ( i = 0, count = mExDates.count();  i < count && idt < enddt;  ++i ) {
00815     while ( idt < enddt && times[idt].date() < mExDates[i] ) ++idt;
00816     while ( idt < enddt && times[idt].date() == mExDates[i] ) {
00817       times.remove( times.at( idt ) );
00818       --enddt;
00819     }
00820   }
00821   DateTimeList extimes;
00822   for ( i = 0, count = mExRules.count();  i < count;  ++i ) {
00823     extimes += mExRules[i]->timesInInterval( start, end );
00824   }
00825   extimes += mExDateTimes;
00826   qSortUnique( extimes );
00827 
00828   int st = 0;
00829   for ( i = 0, count = extimes.count();  i < count;  ++i ) {
00830     int j = removeSorted( times, extimes[i], st );
00831     if ( j >= 0 ) {
00832       st = j;
00833     }
00834   }
00835 
00836   return times;
00837 }
00838 
00839 QDateTime Recurrence::getNextDateTime( const QDateTime &preDateTime ) const
00840 {
00841 //kdDebug(5800) << " Recurrence::getNextDateTime after " << preDateTime << endl;
00842   QDateTime nextDT = preDateTime;
00843   // prevent infinite loops, e.g. when an exrule extinguishes an rrule (e.g.
00844   // the exrule is identical to the rrule). If an occurrence is found, break
00845   // out of the loop by returning that QDateTime
00846 // TODO_Recurrence: Is a loop counter of 1000 really okay? I mean for secondly
00847 // recurrence, an exdate might exclude more than 1000 intervals!
00848   int loop = 0;
00849   while ( loop < 1000 ) {
00850     // Outline of the algo:
00851     //   1) Find the next date/time after preDateTime when the event could recur
00852     //     1.0) Add the start date if it's after preDateTime
00853     //     1.1) Use the next occurrence from the explicit RDATE lists
00854     //     1.2) Add the next recurrence for each of the RRULEs
00855     //   2) Take the earliest recurrence of these = QDateTime nextDT
00856     //   3) If that date/time is not excluded, either explicitly by an EXDATE or
00857     //      by an EXRULE, return nextDT as the next date/time of the recurrence
00858     //   4) If it's excluded, start all at 1), but starting at nextDT (instead
00859     //      of preDateTime). Loop at most 1000 times.
00860     ++loop;
00861     // First, get the next recurrence from the RDate lists
00862     DateTimeList dates;
00863     if ( nextDT < startDateTime() ) dates << startDateTime();
00864     DateTimeList::ConstIterator it = mRDateTimes.begin();
00865     // Assume that the rdatetime list is sorted
00866     while ( it != mRDateTimes.end() && (*it) <= nextDT ) ++it;
00867     if ( it != mRDateTimes.end() ) dates << (*it);
00868 
00869 /*kdDebug(5800) << "    nextDT: " << nextDT << ", startDT: " << startDateTime() << endl;
00870 kdDebug(5800) << "   getNextDateTime: found " << dates.count() << " RDATES and DTSTART in loop " << loop << endl;*/
00871     DateList::ConstIterator dit = mRDates.begin();
00872     while ( dit != mRDates.end() && QDateTime( (*dit), startDateTime().time() ) <= nextDT ) ++dit;
00873     if ( dit != mRDates.end() ) dates << QDateTime( (*dit), startDateTime().time() );
00874 
00875     // Add the next occurrences from all RRULEs.
00876     for ( RecurrenceRule::List::ConstIterator rr = mRRules.begin(); rr != mRRules.end(); ++rr ) {
00877       QDateTime dt = (*rr)->getNextDate( nextDT );
00878       if ( dt.isValid() ) dates << dt;
00879     }
00880 
00881     // Take the first of these (all others can't be used later on)
00882     qSortUnique( dates );
00883 // kdDebug(5800) << "   getNextDateTime: found " << dates.count() << " dates in loop " << loop << endl;
00884 
00885     if ( dates.isEmpty() ) return QDateTime();
00886     nextDT = dates.first();
00887 
00888     // Check if that date/time is excluded explicitly or by an exrule:
00889     if ( !mExDates.contains( nextDT.date() ) && !mExDateTimes.contains( nextDT ) ) {
00890 // kdDebug(5800) << "   NextDT" << nextDT << " not excluded by EXDATE " << endl;
00891       bool allowed = true;
00892       for ( RecurrenceRule::List::ConstIterator rr = mExRules.begin(); rr != mExRules.end(); ++rr ) {
00893         allowed = allowed && !( (*rr)->recursAt( nextDT ) );
00894       }
00895 // kdDebug(5800) << "   NextDT " << nextDT << ", allowed=" << allowed << endl;
00896       if ( allowed ) return nextDT;
00897     }
00898   }
00899 
00900   // Couldn't find a valid occurrences in 1000 loops, something is wrong!
00901   return QDateTime();
00902 }
00903 
00904 QDateTime Recurrence::getPreviousDateTime( const QDateTime &afterDateTime ) const
00905 {
00906   QDateTime prevDT = afterDateTime;
00907   // prevent infinite loops, e.g. when an exrule extinguishes an rrule (e.g.
00908   // the exrule is identical to the rrule). If an occurrence is found, break
00909   // out of the loop by returning that QDateTime
00910   int loop = 0;
00911   while ( loop < 1000 ) {
00912     // Outline of the algo:
00913     //   1) Find the next date/time after preDateTime when the event could recur
00914     //     1.1) Use the next occurrence from the explicit RDATE lists
00915     //     1.2) Add the next recurrence for each of the RRULEs
00916     //   2) Take the earliest recurrence of these = QDateTime nextDT
00917     //   3) If that date/time is not excluded, either explicitly by an EXDATE or
00918     //      by an EXRULE, return nextDT as the next date/time of the recurrence
00919     //   4) If it's excluded, start all at 1), but starting at nextDT (instead
00920     //      of preDateTime). Loop at most 1000 times.
00921     ++loop;
00922     // First, get the next recurrence from the RDate lists
00923     DateTimeList dates;
00924     if ( prevDT > startDateTime() ) dates << startDateTime();
00925 
00926     DateTimeList::ConstIterator dtit = mRDateTimes.end();
00927     if ( dtit != mRDateTimes.begin() ) {
00928       do {
00929         --dtit;
00930       } while ( dtit != mRDateTimes.begin() && (*dtit) >= prevDT );
00931       if ( (*dtit) < prevDT ) dates << (*dtit);
00932     }
00933 
00934     DateList::ConstIterator dit = mRDates.end();
00935     if ( dit != mRDates.begin() ) {
00936       do {
00937         --dit;
00938       } while ( dit != mRDates.begin() && QDateTime((*dit), startDateTime().time()) >= prevDT );
00939       if ( QDateTime((*dit), startDateTime().time()) < prevDT )
00940         dates << QDateTime( (*dit), startDateTime().time() );
00941     }
00942 
00943     // Add the previous occurrences from all RRULEs.
00944     for ( RecurrenceRule::List::ConstIterator rr = mRRules.begin(); rr != mRRules.end(); ++rr ) {
00945       QDateTime dt = (*rr)->getPreviousDate( prevDT );
00946       if ( dt.isValid() ) dates << dt;
00947     }
00948 //kdDebug(5800) << "   getPreviousDateTime: found " << dates.count() << " dates in loop " << loop << endl;
00949 
00950     // Take the last of these (all others can't be used later on)
00951     qSortUnique( dates );
00952     if ( dates.isEmpty() ) return QDateTime();
00953     prevDT = dates.last();
00954 
00955     // Check if that date/time is excluded explicitly or by an exrule:
00956     if ( !mExDates.contains( prevDT.date() ) && !mExDateTimes.contains( prevDT ) ) {
00957       bool allowed = true;
00958       for ( RecurrenceRule::List::ConstIterator rr = mExRules.begin(); rr != mExRules.end(); ++rr ) {
00959         allowed = allowed && !( (*rr)->recursAt( prevDT ) );
00960       }
00961       if ( allowed ) return prevDT;
00962     }
00963   }
00964 
00965   // Couldn't find a valid occurrences in 1000 loops, something is wrong!
00966   return QDateTime();
00967 }
00968 
00969 
00970 /***************************** PROTECTED FUNCTIONS ***************************/
00971 
00972 
00973 RecurrenceRule::List Recurrence::rRules() const
00974 {
00975   return mRRules;
00976 }
00977 
00978 void Recurrence::addRRule( RecurrenceRule *rrule )
00979 {
00980   if ( mRecurReadOnly || !rrule ) return;
00981   rrule->setFloats( mFloating );
00982   mRRules.append( rrule );
00983   rrule->addObserver( this );
00984   updated();
00985 }
00986 
00987 void Recurrence::removeRRule( RecurrenceRule *rrule )
00988 {
00989   if (mRecurReadOnly) return;
00990   mRRules.remove( rrule );
00991   rrule->removeObserver( this );
00992   updated();
00993 }
00994 
00995 RecurrenceRule::List Recurrence::exRules() const
00996 {
00997   return mExRules;
00998 }
00999 
01000 void Recurrence::addExRule( RecurrenceRule *exrule )
01001 {
01002   if ( mRecurReadOnly || !exrule ) return;
01003   exrule->setFloats( mFloating );
01004   mExRules.append( exrule );
01005   exrule->addObserver( this );
01006   updated();
01007 }
01008 
01009 void Recurrence::removeExRule( RecurrenceRule *exrule )
01010 {
01011   if (mRecurReadOnly) return;
01012   mExRules.remove( exrule );
01013   exrule->removeObserver( this );
01014   updated();
01015 }
01016 
01017 
01018 DateTimeList Recurrence::rDateTimes() const
01019 {
01020   return mRDateTimes;
01021 }
01022 
01023 void Recurrence::setRDateTimes( const DateTimeList &rdates )
01024 {
01025   if ( mRecurReadOnly ) return;
01026   mRDateTimes = rdates;
01027   qSortUnique( mRDateTimes );
01028   updated();
01029 }
01030 
01031 void Recurrence::addRDateTime( const QDateTime &rdate )
01032 {
01033   if ( mRecurReadOnly ) return;
01034   mRDateTimes.append( rdate );
01035   qSortUnique( mRDateTimes );
01036   updated();
01037 }
01038 
01039 
01040 DateList Recurrence::rDates() const
01041 {
01042   return mRDates;
01043 }
01044 
01045 void Recurrence::setRDates( const DateList &rdates )
01046 {
01047   if ( mRecurReadOnly ) return;
01048   mRDates = rdates;
01049   qSortUnique( mRDates );
01050   updated();
01051 }
01052 
01053 void Recurrence::addRDate( const QDate &rdate )
01054 {
01055   if ( mRecurReadOnly ) return;
01056   mRDates.append( rdate );
01057   qSortUnique( mRDates );
01058   updated();
01059 }
01060 
01061 
01062 DateTimeList Recurrence::exDateTimes() const
01063 {
01064   return mExDateTimes;
01065 }
01066 
01067 void Recurrence::setExDateTimes( const DateTimeList &exdates )
01068 {
01069   if ( mRecurReadOnly ) return;
01070   mExDateTimes = exdates;
01071   qSortUnique( mExDateTimes );
01072 }
01073 
01074 void Recurrence::addExDateTime( const QDateTime &exdate )
01075 {
01076   if ( mRecurReadOnly ) return;
01077   mExDateTimes.append( exdate );
01078   qSortUnique( mExDateTimes );
01079   updated();
01080 }
01081 
01082 
01083 DateList Recurrence::exDates() const
01084 {
01085   return mExDates;
01086 }
01087 
01088 void Recurrence::setExDates( const DateList &exdates )
01089 {
01090   if ( mRecurReadOnly ) return;
01091   mExDates = exdates;
01092   qSortUnique( mExDates );
01093   updated();
01094 }
01095 
01096 void Recurrence::addExDate( const QDate &exdate )
01097 {
01098   if ( mRecurReadOnly ) return;
01099   mExDates.append( exdate );
01100   qSortUnique( mExDates );
01101   updated();
01102 }
01103 
01104 void Recurrence::recurrenceChanged( RecurrenceRule * )
01105 {
01106   updated();
01107 }
01108 
01109 
01110 // %%%%%%%%%%%%%%%%%% end:Recurrencerule %%%%%%%%%%%%%%%%%%
01111 
01112 void Recurrence::dump() const
01113 {
01114   kdDebug(5800) << "Recurrence::dump():" << endl;
01115 
01116   kdDebug(5800) << "  -) " << mRRules.count() << " RRULEs: " << endl;
01117   for ( RecurrenceRule::List::ConstIterator rr = mRRules.begin(); rr != mRRules.end(); ++rr ) {
01118     kdDebug(5800) << "    -) RecurrenceRule : " << endl;
01119     (*rr)->dump();
01120   }
01121   kdDebug(5800) << "  -) " << mExRules.count() << " EXRULEs: " << endl;
01122   for ( RecurrenceRule::List::ConstIterator rr = mExRules.begin(); rr != mExRules.end(); ++rr ) {
01123     kdDebug(5800) << "    -) ExceptionRule : " << endl;
01124     (*rr)->dump();
01125   }
01126 
01127 
01128   kdDebug(5800) << endl << "  -) " << mRDates.count() << " Recurrence Dates: " << endl;
01129   for ( DateList::ConstIterator it = mRDates.begin(); it != mRDates.end(); ++it ) {
01130     kdDebug(5800) << "     " << (*it) << endl;
01131   }
01132   kdDebug(5800) << endl << "  -) " << mRDateTimes.count() << " Recurrence Date/Times: " << endl;
01133   for ( DateTimeList::ConstIterator it = mRDateTimes.begin(); it != mRDateTimes.end(); ++it ) {
01134     kdDebug(5800) << "     " << (*it) << endl;
01135   }
01136   kdDebug(5800) << endl << "  -) " << mExDates.count() << " Exceptions Dates: " << endl;
01137   for ( DateList::ConstIterator it = mExDates.begin(); it != mExDates.end(); ++it ) {
01138     kdDebug(5800) << "     " << (*it) << endl;
01139   }
01140   kdDebug(5800) << endl << "  -) " << mExDateTimes.count() << " Exception Date/Times: " << endl;
01141   for ( DateTimeList::ConstIterator it = mExDateTimes.begin(); it != mExDateTimes.end(); ++it ) {
01142     kdDebug(5800) << "     " << (*it) << endl;
01143   }
01144 }
KDE Home | KDE Accessibility Home | Description of Access Keys