• Skip to content
  • Skip to link menu
KDE 4.2 API Reference
  • KDE API Reference
  • KDE-PIM Libraries
  • Sitemap
  • Contact Us
 

KCal Library

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,2006 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 "recurrence.h"
00026 #include "recurrencerule.h"
00027 
00028 #include <kglobal.h>
00029 #include <klocale.h>
00030 #include <kdebug.h>
00031 
00032 #include <QtCore/QList>
00033 #include <QtCore/QBitArray>
00034 
00035 #include <limits.h>
00036 
00037 using namespace KCal;
00038 
00039 //@cond PRIVATE
00040 class KCal::Recurrence::Private
00041 {
00042   public:
00043     Private()
00044       : mCachedType( rMax ),
00045         mAllDay( false ),
00046         mRecurReadOnly( false )
00047     {
00048         mExRules.setAutoDelete( true );
00049         mRRules.setAutoDelete( true );
00050     }
00051 
00052     Private( const Private &p )
00053       : mRDateTimes( p.mRDateTimes ),
00054         mRDates( p.mRDates ),
00055         mExDateTimes( p.mExDateTimes ),
00056         mExDates( p.mExDates ),
00057         mStartDateTime( p.mStartDateTime ),
00058         mCachedType( p.mCachedType ),
00059         mAllDay( p.mAllDay ),
00060         mRecurReadOnly( p.mRecurReadOnly )
00061     {
00062         mExRules.setAutoDelete( true );
00063         mRRules.setAutoDelete( true );
00064     }
00065 
00066     bool operator==( const Private &p ) const;
00067 
00068     RecurrenceRule::List mExRules;
00069     RecurrenceRule::List mRRules;
00070     DateTimeList mRDateTimes;
00071     DateList mRDates;
00072     DateTimeList mExDateTimes;
00073     DateList mExDates;
00074     KDateTime mStartDateTime;    // date/time of first recurrence
00075     QList<RecurrenceObserver*> mObservers;
00076 
00077     // Cache the type of the recurrence with the old system (e.g. MonthlyPos)
00078     mutable ushort mCachedType;
00079 
00080     bool mAllDay;                // the recurrence has no time, just a date
00081     bool mRecurReadOnly;
00082 };
00083 
00084 bool Recurrence::Private::operator==( const Recurrence::Private &p ) const
00085 {
00086   if ( mStartDateTime != p.mStartDateTime ||
00087        mAllDay != p.mAllDay ||
00088        mRecurReadOnly != p.mRecurReadOnly ||
00089        mExDates != p.mExDates ||
00090        mExDateTimes != p.mExDateTimes ||
00091        mRDates != p.mRDates ||
00092        mRDateTimes != p.mRDateTimes ) {
00093     return false;
00094   }
00095 
00096 // Compare the rrules, exrules! Assume they have the same order... This only
00097 // matters if we have more than one rule (which shouldn't be the default anyway)
00098   int i;
00099   int end = mRRules.count();
00100   if ( end != p.mRRules.count() ) {
00101     return false;
00102   }
00103   for ( i = 0;  i < end;  ++i ) {
00104     if ( *mRRules[i] != *p.mRRules[i] ) {
00105       return false;
00106     }
00107   }
00108   end = mExRules.count();
00109   if ( end != p.mExRules.count() ) {
00110     return false;
00111   }
00112   for ( i = 0;  i < end;  ++i ) {
00113     if ( *mExRules[i] != *p.mExRules[i] ) {
00114       return false;
00115     }
00116   }
00117   return true;
00118 }
00119 //@endcond
00120 
00121 Recurrence::Recurrence()
00122   : d( new KCal::Recurrence::Private() )
00123 {
00124 }
00125 
00126 Recurrence::Recurrence( const Recurrence &r )
00127   : RecurrenceRule::RuleObserver(),
00128     d( new KCal::Recurrence::Private( *r.d ) )
00129 {
00130   int i, end;
00131   for ( i = 0, end = r.d->mRRules.count();  i < end;  ++i ) {
00132     RecurrenceRule *rule = new RecurrenceRule( *r.d->mRRules[i] );
00133     d->mRRules.append( rule );
00134     rule->addObserver( this );
00135   }
00136   for ( i = 0, end = r.d->mExRules.count();  i < end;  ++i ) {
00137     RecurrenceRule *rule = new RecurrenceRule( *r.d->mExRules[i] );
00138     d->mExRules.append( rule );
00139     rule->addObserver( this );
00140   }
00141 }
00142 
00143 Recurrence::~Recurrence()
00144 {
00145   delete d;
00146 }
00147 
00148 bool Recurrence::operator==( const Recurrence &r2 ) const
00149 {
00150   return *d == *r2.d;
00151 }
00152 
00153 void Recurrence::addObserver( RecurrenceObserver *observer )
00154 {
00155   if ( !d->mObservers.contains( observer ) ) {
00156     d->mObservers.append( observer );
00157   }
00158 }
00159 
00160 void Recurrence::removeObserver( RecurrenceObserver *observer )
00161 {
00162   if ( d->mObservers.contains( observer ) ) {
00163     d->mObservers.removeAll( observer );
00164   }
00165 }
00166 
00167 KDateTime Recurrence::startDateTime() const
00168 {
00169   return d->mStartDateTime;
00170 }
00171 
00172 bool Recurrence::allDay() const
00173 {
00174   return d->mAllDay;
00175 }
00176 
00177 void Recurrence::setAllDay( bool allDay )
00178 {
00179   if ( d->mRecurReadOnly || allDay == d->mAllDay ) {
00180     return;
00181   }
00182 
00183   d->mAllDay = allDay;
00184   for ( int i = 0, end = d->mRRules.count();  i < end;  ++i ) {
00185     d->mRRules[i]->setAllDay( allDay );
00186   }
00187   for ( int i = 0, end = d->mExRules.count();  i < end;  ++i ) {
00188     d->mExRules[i]->setAllDay( allDay );
00189   }
00190   updated();
00191 }
00192 
00193 RecurrenceRule *Recurrence::defaultRRule( bool create ) const
00194 {
00195   if ( d->mRRules.isEmpty() ) {
00196     if ( !create || d->mRecurReadOnly ) {
00197       return 0;
00198     }
00199     RecurrenceRule *rrule = new RecurrenceRule();
00200     rrule->setStartDt( startDateTime() );
00201     const_cast<KCal::Recurrence*>(this)->addRRule( rrule );
00202     return rrule;
00203   } else {
00204     return d->mRRules[0];
00205   }
00206 }
00207 
00208 RecurrenceRule *Recurrence::defaultRRuleConst() const
00209 {
00210   return d->mRRules.isEmpty() ? 0 : d->mRRules[0];
00211 }
00212 
00213 void Recurrence::updated()
00214 {
00215   // recurrenceType() re-calculates the type if it's rMax
00216   d->mCachedType = rMax;
00217   for ( int i = 0, end = d->mObservers.count();  i < end;  ++i ) {
00218     if ( d->mObservers[i] ) {
00219       d->mObservers[i]->recurrenceUpdated( this );
00220     }
00221   }
00222 }
00223 
00224 bool Recurrence::recurs() const
00225 {
00226   return !d->mRRules.isEmpty() || !d->mRDates.isEmpty() || !d->mRDateTimes.isEmpty();
00227 }
00228 
00229 ushort Recurrence::recurrenceType() const
00230 {
00231   if ( d->mCachedType == rMax ) {
00232     d->mCachedType = recurrenceType( defaultRRuleConst() );
00233   }
00234   return d->mCachedType;
00235 }
00236 
00237 ushort Recurrence::recurrenceType( const RecurrenceRule *rrule )
00238 {
00239   if ( !rrule ) {
00240     return rNone;
00241   }
00242   RecurrenceRule::PeriodType type = rrule->recurrenceType();
00243 
00244   // BYSETPOS, BYWEEKNUMBER and BYSECOND were not supported in old versions
00245   if ( !rrule->bySetPos().isEmpty() ||
00246        !rrule->bySeconds().isEmpty() ||
00247        !rrule->byWeekNumbers().isEmpty() ) {
00248     return rOther;
00249   }
00250 
00251   // It wasn't possible to set BYMINUTES, BYHOUR etc. by the old code. So if
00252   // it's set, it's none of the old types
00253   if ( !rrule->byMinutes().isEmpty() || !rrule->byHours().isEmpty() ) {
00254     return rOther;
00255   }
00256 
00257   // Possible combinations were:
00258   // BYDAY: with WEEKLY, MONTHLY, YEARLY
00259   // BYMONTHDAY: with MONTHLY, YEARLY
00260   // BYMONTH: with YEARLY
00261   // BYYEARDAY: with YEARLY
00262   if ( ( !rrule->byYearDays().isEmpty() && type != RecurrenceRule::rYearly ) ||
00263        ( !rrule->byMonths().isEmpty() && type != RecurrenceRule::rYearly ) ) {
00264     return rOther;
00265   }
00266   if ( !rrule->byDays().isEmpty() ) {
00267     if ( type != RecurrenceRule::rYearly &&
00268          type != RecurrenceRule::rMonthly &&
00269          type != RecurrenceRule::rWeekly ) {
00270       return rOther;
00271     }
00272   }
00273 
00274   switch ( type ) {
00275   case RecurrenceRule::rNone:
00276     return rNone;
00277   case RecurrenceRule::rMinutely:
00278     return rMinutely;
00279   case RecurrenceRule::rHourly:
00280     return rHourly;
00281   case RecurrenceRule::rDaily:
00282     return rDaily;
00283   case RecurrenceRule::rWeekly:
00284     return rWeekly;
00285   case RecurrenceRule::rMonthly:
00286   {
00287     if ( rrule->byDays().isEmpty() ) {
00288       return rMonthlyDay;
00289     } else if ( rrule->byMonthDays().isEmpty() ) {
00290       return rMonthlyPos;
00291     } else {
00292       return rOther; // both position and date specified
00293     }
00294   }
00295   case RecurrenceRule::rYearly:
00296   {
00297     // Possible combinations:
00298     //   rYearlyMonth: [BYMONTH &] BYMONTHDAY
00299     //   rYearlyDay: BYYEARDAY
00300     //   rYearlyPos: [BYMONTH &] BYDAY
00301     if ( !rrule->byDays().isEmpty() ) {
00302       // can only by rYearlyPos
00303       if ( rrule->byMonthDays().isEmpty() && rrule->byYearDays().isEmpty() ) {
00304         return rYearlyPos;
00305       } else {
00306         return rOther;
00307       }
00308     } else if ( !rrule->byYearDays().isEmpty() ) {
00309       // Can only be rYearlyDay
00310       if ( rrule->byMonths().isEmpty() && rrule->byMonthDays().isEmpty() ) {
00311         return rYearlyDay;
00312       } else {
00313         return rOther;
00314       }
00315     } else {
00316       return rYearlyMonth;
00317     }
00318     break;
00319   }
00320   default: return rOther;
00321   }
00322   return rOther;
00323 }
00324 
00325 bool Recurrence::recursOn( const QDate &qd, const KDateTime::Spec &timeSpec ) const
00326 {
00327   // Don't waste time if date is before the start of the recurrence
00328   if ( KDateTime( qd, QTime( 23, 59, 59 ), timeSpec ) < d->mStartDateTime ) {
00329     return false;
00330   }
00331 
00332   // First handle dates. Exrules override
00333   if ( d->mExDates.containsSorted( qd ) ) {
00334     return false;
00335   }
00336 
00337   int i, end;
00338   TimeList tms;
00339   // For all-day events a matching exrule excludes the whole day
00340   // since exclusions take precedence over inclusions, we know it can't occur on that day.
00341   if ( allDay() ) {
00342     for ( i = 0, end = d->mExRules.count();  i < end;  ++i ) {
00343       if ( d->mExRules[i]->recursOn( qd, timeSpec ) ) {
00344         return false;
00345       }
00346     }
00347   }
00348 
00349   if ( d->mRDates.containsSorted( qd ) ) {
00350     return true;
00351   }
00352 
00353   // Check if it might recur today at all.
00354   bool recurs = ( startDate() == qd );
00355   for ( i = 0, end = d->mRDateTimes.count();  i < end && !recurs;  ++i ) {
00356     recurs = ( d->mRDateTimes[i].toTimeSpec( timeSpec ).date() == qd );
00357   }
00358   for ( i = 0, end = d->mRRules.count();  i < end && !recurs;  ++i ) {
00359     recurs = d->mRRules[i]->recursOn( qd, timeSpec );
00360   }
00361   // If the event wouldn't recur at all, simply return false, don't check ex*
00362   if ( !recurs ) {
00363     return false;
00364   }
00365 
00366   // Check if there are any times for this day excluded, either by exdate or exrule:
00367   bool exon = false;
00368   for ( i = 0, end = d->mExDateTimes.count();  i < end && !exon;  ++i ) {
00369     exon = ( d->mExDateTimes[i].toTimeSpec( timeSpec ).date() == qd );
00370   }
00371   if ( !allDay() ) {     // we have already checked all-day times above
00372     for ( i = 0, end = d->mExRules.count();  i < end && !exon;  ++i ) {
00373       exon = d->mExRules[i]->recursOn( qd, timeSpec );
00374     }
00375   }
00376 
00377   if ( !exon ) {
00378     // Simple case, nothing on that day excluded, return the value from before
00379     return recurs;
00380   } else {
00381     // Harder part: I don't think there is any way other than to calculate the
00382     // whole list of items for that day.
00383 //TODO: consider whether it would be more efficient to call
00384 //      Rule::recurTimesOn() instead of Rule::recursOn() from the start
00385     TimeList timesForDay( recurTimesOn( qd, timeSpec ) );
00386     return !timesForDay.isEmpty();
00387   }
00388 }
00389 
00390 bool Recurrence::recursAt( const KDateTime &dt ) const
00391 {
00392   // Convert to recurrence's time zone for date comparisons, and for more efficient time comparisons
00393   KDateTime dtrecur = dt.toTimeSpec( d->mStartDateTime.timeSpec() );
00394 
00395   // if it's excluded anyway, don't bother to check if it recurs at all.
00396   if ( d->mExDateTimes.containsSorted( dtrecur ) ||
00397        d->mExDates.containsSorted( dtrecur.date() ) ) {
00398     return false;
00399   }
00400   int i, end;
00401   for ( i = 0, end = d->mExRules.count();  i < end;  ++i ) {
00402     if ( d->mExRules[i]->recursAt( dtrecur ) ) {
00403       return false;
00404     }
00405   }
00406 
00407   // Check explicit recurrences, then rrules.
00408   if ( startDateTime() == dtrecur || d->mRDateTimes.containsSorted( dtrecur ) ) {
00409     return true;
00410   }
00411   for ( i = 0, end = d->mRRules.count();  i < end;  ++i ) {
00412     if ( d->mRRules[i]->recursAt( dtrecur ) ) {
00413       return true;
00414     }
00415   }
00416 
00417   return false;
00418 }
00419 
00423 KDateTime Recurrence::endDateTime() const
00424 {
00425   DateTimeList dts;
00426   dts << startDateTime();
00427   if ( !d->mRDates.isEmpty() ) {
00428     dts << KDateTime( d->mRDates.last(), QTime( 0, 0, 0 ), d->mStartDateTime.timeSpec() );
00429   }
00430   if ( !d->mRDateTimes.isEmpty() ) {
00431     dts << d->mRDateTimes.last();
00432   }
00433   for ( int i = 0, end = d->mRRules.count();  i < end;  ++i ) {
00434     KDateTime rl( d->mRRules[i]->endDt() );
00435     // if any of the rules is infinite, the whole recurrence is
00436     if ( !rl.isValid() ) {
00437       return KDateTime();
00438     }
00439     dts << rl;
00440   }
00441   dts.sortUnique();
00442   return dts.isEmpty() ? KDateTime() : dts.last();
00443 }
00444 
00448 QDate Recurrence::endDate() const
00449 {
00450   KDateTime end( endDateTime() );
00451   return end.isValid() ? end.date() : QDate();
00452 }
00453 
00454 void Recurrence::setEndDate( const QDate &date )
00455 {
00456   KDateTime dt( date, d->mStartDateTime.time(), d->mStartDateTime.timeSpec() );
00457   if ( allDay() ) {
00458     dt.setTime( QTime( 23, 59, 59 ) );
00459   }
00460   setEndDateTime( dt );
00461 }
00462 
00463 void Recurrence::setEndDateTime( const KDateTime &dateTime )
00464 {
00465   if ( d->mRecurReadOnly ) {
00466     return;
00467   }
00468   RecurrenceRule *rrule = defaultRRule( true );
00469   if ( !rrule ) {
00470     return;
00471   }
00472   rrule->setEndDt( dateTime );
00473   updated();
00474 }
00475 
00476 int Recurrence::duration() const
00477 {
00478   RecurrenceRule *rrule = defaultRRuleConst();
00479   return rrule ? rrule->duration() : 0;
00480 }
00481 
00482 int Recurrence::durationTo( const KDateTime &datetime ) const
00483 {
00484   // Emulate old behavior: This is just an interface to the first rule!
00485   RecurrenceRule *rrule = defaultRRuleConst();
00486   return rrule ? rrule->durationTo( datetime ) : 0;
00487 }
00488 
00489 int Recurrence::durationTo( const QDate &date ) const
00490 {
00491   return durationTo( KDateTime( date, QTime( 23, 59, 59 ), d->mStartDateTime.timeSpec() ) );
00492 }
00493 
00494 void Recurrence::setDuration( int duration )
00495 {
00496   if ( d->mRecurReadOnly ) {
00497     return;
00498   }
00499 
00500   RecurrenceRule *rrule = defaultRRule( true );
00501   if ( !rrule ) {
00502     return;
00503   }
00504   rrule->setDuration( duration );
00505   updated();
00506 }
00507 
00508 void Recurrence::shiftTimes( const KDateTime::Spec &oldSpec, const KDateTime::Spec &newSpec )
00509 {
00510   if ( d->mRecurReadOnly ) {
00511     return;
00512   }
00513 
00514   d->mStartDateTime = d->mStartDateTime.toTimeSpec( oldSpec );
00515   d->mStartDateTime.setTimeSpec( newSpec );
00516 
00517   int i, end;
00518   for ( i = 0, end = d->mRDateTimes.count();  i < end;  ++i ) {
00519     d->mRDateTimes[i] = d->mRDateTimes[i].toTimeSpec( oldSpec );
00520     d->mRDateTimes[i].setTimeSpec( newSpec );
00521   }
00522   for ( i = 0, end = d->mExDateTimes.count();  i < end;  ++i ) {
00523     d->mExDateTimes[i] = d->mExDateTimes[i].toTimeSpec( oldSpec );
00524     d->mExDateTimes[i].setTimeSpec( newSpec );
00525   }
00526   for ( i = 0, end = d->mRRules.count();  i < end;  ++i ) {
00527     d->mRRules[i]->shiftTimes( oldSpec, newSpec );
00528   }
00529   for ( i = 0, end = d->mExRules.count();  i < end;  ++i ) {
00530     d->mExRules[i]->shiftTimes( oldSpec, newSpec );
00531   }
00532 }
00533 
00534 void Recurrence::unsetRecurs()
00535 {
00536   if ( d->mRecurReadOnly ) {
00537     return;
00538   }
00539   d->mRRules.clear();
00540   updated();
00541 }
00542 
00543 void Recurrence::clear()
00544 {
00545   if ( d->mRecurReadOnly ) {
00546     return;
00547   }
00548   d->mRRules.clearAll();
00549   d->mExRules.clearAll();
00550   d->mRDates.clear();
00551   d->mRDateTimes.clear();
00552   d->mExDates.clear();
00553   d->mExDateTimes.clear();
00554   d->mCachedType = rMax;
00555   updated();
00556 }
00557 
00558 void Recurrence::setRecurReadOnly( bool readOnly )
00559 {
00560   d->mRecurReadOnly = readOnly;
00561 }
00562 
00563 bool Recurrence::recurReadOnly() const
00564 {
00565   return d->mRecurReadOnly;
00566 }
00567 
00568 QDate Recurrence::startDate() const
00569 {
00570   return d->mStartDateTime.date();
00571 }
00572 
00573 void Recurrence::setStartDateTime( const KDateTime &start )
00574 {
00575   if ( d->mRecurReadOnly ) {
00576     return;
00577   }
00578   d->mStartDateTime = start;
00579   setAllDay( start.isDateOnly() );   // set all RRULEs and EXRULEs
00580 
00581   int i, end;
00582   for ( i = 0, end = d->mRRules.count();  i < end;  ++i ) {
00583     d->mRRules[i]->setStartDt( start );
00584   }
00585   for ( i = 0, end = d->mExRules.count();  i < end;  ++i ) {
00586     d->mExRules[i]->setStartDt( start );
00587   }
00588   updated();
00589 }
00590 
00591 int Recurrence::frequency() const
00592 {
00593   RecurrenceRule *rrule = defaultRRuleConst();
00594   return rrule ? rrule->frequency() : 0;
00595 }
00596 
00597 // Emulate the old behaviour. Make this methods just an interface to the
00598 // first rrule
00599 void Recurrence::setFrequency( int freq )
00600 {
00601   if ( d->mRecurReadOnly || freq <= 0 ) {
00602     return;
00603   }
00604 
00605   RecurrenceRule *rrule = defaultRRule( true );
00606   if ( rrule ) {
00607     rrule->setFrequency( freq );
00608   }
00609   updated();
00610 }
00611 
00612 // WEEKLY
00613 
00614 int Recurrence::weekStart() const
00615 {
00616   RecurrenceRule *rrule = defaultRRuleConst();
00617   return rrule ? rrule->weekStart() : 1;
00618 }
00619 
00620 // Emulate the old behavior
00621 QBitArray Recurrence::days() const
00622 {
00623   QBitArray days( 7 );
00624   days.fill( 0 );
00625   RecurrenceRule *rrule = defaultRRuleConst();
00626   if ( rrule ) {
00627     QList<RecurrenceRule::WDayPos> bydays = rrule->byDays();
00628     for ( int i = 0; i < bydays.size(); ++i ) {
00629       if ( bydays.at(i).pos() == 0 ) {
00630         days.setBit( bydays.at( i ).day() - 1 );
00631       }
00632     }
00633   }
00634   return days;
00635 }
00636 
00637 // MONTHLY
00638 
00639 // Emulate the old behavior
00640 QList<int> Recurrence::monthDays() const
00641 {
00642   RecurrenceRule *rrule = defaultRRuleConst();
00643   if ( rrule ) {
00644     return rrule->byMonthDays();
00645   } else {
00646     return QList<int>();
00647   }
00648 }
00649 
00650 // Emulate the old behavior
00651 QList<RecurrenceRule::WDayPos> Recurrence::monthPositions() const
00652 {
00653   RecurrenceRule *rrule = defaultRRuleConst();
00654   return rrule ? rrule->byDays() : QList<RecurrenceRule::WDayPos>();
00655 }
00656 
00657 // YEARLY
00658 
00659 QList<int> Recurrence::yearDays() const
00660 {
00661   RecurrenceRule *rrule = defaultRRuleConst();
00662   return rrule ? rrule->byYearDays() : QList<int>();
00663 }
00664 
00665 QList<int> Recurrence::yearDates() const
00666 {
00667   return monthDays();
00668 }
00669 
00670 QList<int> Recurrence::yearMonths() const
00671 {
00672   RecurrenceRule *rrule = defaultRRuleConst();
00673   return rrule ? rrule->byMonths() : QList<int>();
00674 }
00675 
00676 QList<RecurrenceRule::WDayPos> Recurrence::yearPositions() const
00677 {
00678   return monthPositions();
00679 }
00680 
00681 RecurrenceRule *Recurrence::setNewRecurrenceType( RecurrenceRule::PeriodType type, int freq )
00682 {
00683   if ( d->mRecurReadOnly || freq <= 0 ) {
00684     return 0;
00685   }
00686 
00687   d->mRRules.clearAll();
00688   updated();
00689   RecurrenceRule *rrule = defaultRRule( true );
00690   if ( !rrule ) {
00691     return 0;
00692   }
00693   rrule->setRecurrenceType( type );
00694   rrule->setFrequency( freq );
00695   rrule->setDuration( -1 );
00696   return rrule;
00697 }
00698 
00699 void Recurrence::setMinutely( int _rFreq )
00700 {
00701   if ( setNewRecurrenceType( RecurrenceRule::rMinutely, _rFreq ) ) {
00702     updated();
00703   }
00704 }
00705 
00706 void Recurrence::setHourly( int _rFreq )
00707 {
00708   if ( setNewRecurrenceType( RecurrenceRule::rHourly, _rFreq ) ) {
00709     updated();
00710   }
00711 }
00712 
00713 void Recurrence::setDaily( int _rFreq )
00714 {
00715   if ( setNewRecurrenceType( RecurrenceRule::rDaily, _rFreq ) ) {
00716     updated();
00717   }
00718 }
00719 
00720 void Recurrence::setWeekly( int freq, int weekStart )
00721 {
00722   RecurrenceRule *rrule = setNewRecurrenceType( RecurrenceRule::rWeekly, freq );
00723   if ( !rrule ) {
00724     return;
00725   }
00726   rrule->setWeekStart( weekStart );
00727   updated();
00728 }
00729 
00730 void Recurrence::setWeekly( int freq, const QBitArray &days, int weekStart )
00731 {
00732   setWeekly( freq, weekStart );
00733   addMonthlyPos( 0, days );
00734 }
00735 
00736 void Recurrence::addWeeklyDays( const QBitArray &days )
00737 {
00738   addMonthlyPos( 0, days );
00739 }
00740 
00741 void Recurrence::setMonthly( int freq )
00742 {
00743   if ( setNewRecurrenceType( RecurrenceRule::rMonthly, freq ) ) {
00744     updated();
00745   }
00746 }
00747 
00748 void Recurrence::addMonthlyPos( short pos, const QBitArray &days )
00749 {
00750   // Allow 53 for yearly!
00751   if ( d->mRecurReadOnly || pos > 53 || pos < -53 ) {
00752     return;
00753   }
00754 
00755   RecurrenceRule *rrule = defaultRRule( false );
00756   if ( !rrule ) {
00757     return;
00758   }
00759   bool changed = false;
00760   QList<RecurrenceRule::WDayPos> positions = rrule->byDays();
00761 
00762   for ( int i = 0; i < 7; ++i ) {
00763     if ( days.testBit(i) ) {
00764       RecurrenceRule::WDayPos p( pos, i + 1 );
00765       if ( !positions.contains( p ) ) {
00766         changed = true;
00767         positions.append( p );
00768       }
00769     }
00770   }
00771   if ( changed ) {
00772     rrule->setByDays( positions );
00773     updated();
00774   }
00775 }
00776 
00777 void Recurrence::addMonthlyPos( short pos, ushort day )
00778 {
00779   // Allow 53 for yearly!
00780   if ( d->mRecurReadOnly || pos > 53 || pos < -53 ) {
00781     return;
00782   }
00783 
00784   RecurrenceRule *rrule = defaultRRule( false );
00785   if ( !rrule ) {
00786     return;
00787   }
00788   QList<RecurrenceRule::WDayPos> positions = rrule->byDays();
00789 
00790   RecurrenceRule::WDayPos p( pos, day );
00791   if ( !positions.contains( p ) ) {
00792     positions.append( p );
00793     rrule->setByDays( positions );
00794     updated();
00795   }
00796 }
00797 
00798 void Recurrence::addMonthlyDate( short day )
00799 {
00800   if ( d->mRecurReadOnly || day > 31 || day < -31 ) {
00801     return;
00802   }
00803 
00804   RecurrenceRule *rrule = defaultRRule( true );
00805   if ( !rrule ) {
00806     return;
00807   }
00808 
00809   QList<int> monthDays = rrule->byMonthDays();
00810   if ( !monthDays.contains( day ) ) {
00811     monthDays.append( day );
00812     rrule->setByMonthDays( monthDays );
00813     updated();
00814   }
00815 }
00816 
00817 void Recurrence::setYearly( int freq )
00818 {
00819   if ( setNewRecurrenceType( RecurrenceRule::rYearly, freq ) ) {
00820     updated();
00821   }
00822 }
00823 
00824 // Daynumber within year
00825 void Recurrence::addYearlyDay( int day )
00826 {
00827   RecurrenceRule *rrule = defaultRRule( false ); // It must already exist!
00828   if ( !rrule ) {
00829     return;
00830   }
00831 
00832   QList<int> days = rrule->byYearDays();
00833   if ( !days.contains( day ) ) {
00834     days << day;
00835     rrule->setByYearDays( days );
00836     updated();
00837   }
00838 }
00839 
00840 // day part of date within year
00841 void Recurrence::addYearlyDate( int day )
00842 {
00843   addMonthlyDate( day );
00844 }
00845 
00846 // day part of date within year, given as position (n-th weekday)
00847 void Recurrence::addYearlyPos( short pos, const QBitArray &days )
00848 {
00849   addMonthlyPos( pos, days );
00850 }
00851 
00852 // month part of date within year
00853 void Recurrence::addYearlyMonth( short month )
00854 {
00855   if ( d->mRecurReadOnly || month < 1 || month > 12 ) {
00856     return;
00857   }
00858 
00859   RecurrenceRule *rrule = defaultRRule( false );
00860   if ( !rrule ) {
00861     return;
00862   }
00863 
00864   QList<int> months = rrule->byMonths();
00865   if ( !months.contains(month) ) {
00866     months << month;
00867     rrule->setByMonths( months );
00868     updated();
00869   }
00870 }
00871 
00872 TimeList Recurrence::recurTimesOn( const QDate &date, const KDateTime::Spec &timeSpec ) const
00873 {
00874 // kDebug() << "recurTimesOn(" << date << ")";
00875   int i, end;
00876   TimeList times;
00877 
00878   // The whole day is excepted
00879   if ( d->mExDates.containsSorted( date ) ) {
00880     return times;
00881   }
00882 
00883   // EXRULE takes precedence over RDATE entries, so for all-day events,
00884   // a matching excule also excludes the whole day automatically
00885   if ( allDay() ) {
00886     for ( i = 0, end = d->mExRules.count();  i < end;  ++i ) {
00887       if ( d->mExRules[i]->recursOn( date, timeSpec ) ) {
00888         return times;
00889       }
00890     }
00891   }
00892 
00893   KDateTime dt = startDateTime().toTimeSpec( timeSpec );
00894   if ( dt.date() == date ) {
00895     times << dt.time();
00896   }
00897 
00898   bool foundDate = false;
00899   for ( i = 0, end = d->mRDateTimes.count();  i < end;  ++i ) {
00900     dt = d->mRDateTimes[i].toTimeSpec( timeSpec );
00901     if ( dt.date() == date ) {
00902       times << dt.time();
00903       foundDate = true;
00904     } else if (foundDate) break; // <= Assume that the rdatetime list is sorted
00905   }
00906   for ( i = 0, end = d->mRRules.count();  i < end;  ++i ) {
00907     times += d->mRRules[i]->recurTimesOn( date, timeSpec );
00908   }
00909   times.sortUnique();
00910 
00911   foundDate = false;
00912   TimeList extimes;
00913   for ( i = 0, end = d->mExDateTimes.count();  i < end;  ++i ) {
00914     dt = d->mExDateTimes[i].toTimeSpec( timeSpec );
00915     if ( dt.date() == date ) {
00916       extimes << dt.time();
00917       foundDate = true;
00918     } else if (foundDate) break;
00919   }
00920   if ( !allDay() ) {     // we have already checked all-day times above
00921     for ( i = 0, end = d->mExRules.count();  i < end;  ++i ) {
00922       extimes += d->mExRules[i]->recurTimesOn( date, timeSpec );
00923     }
00924   }
00925   extimes.sortUnique();
00926 
00927   int st = 0;
00928   for ( i = 0, end = extimes.count();  i < end;  ++i ) {
00929     int j = times.removeSorted( extimes[i], st );
00930     if ( j >= 0 ) {
00931       st = j;
00932     }
00933   }
00934   return times;
00935 }
00936 
00937 DateTimeList Recurrence::timesInInterval( const KDateTime &start, const KDateTime &end ) const
00938 {
00939   int i, count;
00940   DateTimeList times;
00941   for ( i = 0, count = d->mRRules.count();  i < count;  ++i ) {
00942     times += d->mRRules[i]->timesInInterval( start, end );
00943   }
00944   times += d->mRDateTimes;
00945   KDateTime kdt( startDateTime() );
00946   for ( i = 0, count = d->mRDates.count();  i < count;  ++i ) {
00947     kdt.setDate( d->mRDates[i] );
00948     times += kdt;
00949   }
00950   times.sortUnique();
00951 
00952   // Remove excluded times
00953   int idt = 0;
00954   int enddt = times.count();
00955   for ( i = 0, count = d->mExDates.count();  i < count && idt < enddt;  ++i ) {
00956     while ( idt < enddt && times[idt].date() < d->mExDates[i] ) ++idt;
00957     while ( idt < enddt && times[idt].date() == d->mExDates[i] ) {
00958       times.removeAt(idt);
00959       --enddt;
00960     }
00961   }
00962   DateTimeList extimes;
00963   for ( i = 0, count = d->mExRules.count();  i < count;  ++i ) {
00964     extimes += d->mExRules[i]->timesInInterval( start, end );
00965   }
00966   extimes += d->mExDateTimes;
00967   extimes.sortUnique();
00968 
00969   int st = 0;
00970   for ( i = 0, count = extimes.count();  i < count;  ++i ) {
00971     int j = times.removeSorted( extimes[i], st );
00972     if ( j >= 0 ) {
00973       st = j;
00974     }
00975   }
00976 
00977   return times;
00978 }
00979 
00980 KDateTime Recurrence::getNextDateTime( const KDateTime &preDateTime ) const
00981 {
00982   KDateTime nextDT = preDateTime;
00983   // prevent infinite loops, e.g. when an exrule extinguishes an rrule (e.g.
00984   // the exrule is identical to the rrule). If an occurrence is found, break
00985   // out of the loop by returning that KDateTime
00986 // TODO_Recurrence: Is a loop counter of 1000 really okay? I mean for secondly
00987 // recurrence, an exdate might exclude more than 1000 intervals!
00988   int loop = 0;
00989   while ( loop < 1000 ) {
00990     // Outline of the algo:
00991     //   1) Find the next date/time after preDateTime when the event could recur
00992     //     1.0) Add the start date if it's after preDateTime
00993     //     1.1) Use the next occurrence from the explicit RDATE lists
00994     //     1.2) Add the next recurrence for each of the RRULEs
00995     //   2) Take the earliest recurrence of these = KDateTime nextDT
00996     //   3) If that date/time is not excluded, either explicitly by an EXDATE or
00997     //      by an EXRULE, return nextDT as the next date/time of the recurrence
00998     //   4) If it's excluded, start all at 1), but starting at nextDT (instead
00999     //      of preDateTime). Loop at most 1000 times.
01000     ++loop;
01001     // First, get the next recurrence from the RDate lists
01002     DateTimeList dates;
01003     if ( nextDT < startDateTime() ) {
01004       dates << startDateTime();
01005     }
01006 
01007     int end;
01008     // Assume that the rdatetime list is sorted
01009     int i = d->mRDateTimes.findGT( nextDT );
01010     if ( i >= 0 ) {
01011       dates << d->mRDateTimes[i];
01012     }
01013 
01014     KDateTime kdt( startDateTime() );
01015     for ( i = 0, end = d->mRDates.count();  i < end;  ++i ) {
01016       kdt.setDate( d->mRDates[i] );
01017       if ( kdt > nextDT ) {
01018         dates << kdt;
01019         break;
01020       }
01021     }
01022 
01023     // Add the next occurrences from all RRULEs.
01024     for ( i = 0, end = d->mRRules.count();  i < end;  ++i ) {
01025       KDateTime dt = d->mRRules[i]->getNextDate( nextDT );
01026       if ( dt.isValid() ) {
01027         dates << dt;
01028       }
01029     }
01030 
01031     // Take the first of these (all others can't be used later on)
01032     dates.sortUnique();
01033     if ( dates.isEmpty() ) {
01034       return KDateTime();
01035     }
01036     nextDT = dates.first();
01037 
01038     // Check if that date/time is excluded explicitly or by an exrule:
01039     if ( !d->mExDates.containsSorted( nextDT.date() ) &&
01040          !d->mExDateTimes.containsSorted( nextDT ) ) {
01041       bool allowed = true;
01042       for ( i = 0, end = d->mExRules.count();  i < end;  ++i ) {
01043         allowed = allowed && !( d->mExRules[i]->recursAt( nextDT ) );
01044       }
01045       if ( allowed ) {
01046         return nextDT;
01047       }
01048     }
01049   }
01050 
01051   // Couldn't find a valid occurrences in 1000 loops, something is wrong!
01052   return KDateTime();
01053 }
01054 
01055 KDateTime Recurrence::getPreviousDateTime( const KDateTime &afterDateTime ) const
01056 {
01057   KDateTime prevDT = afterDateTime;
01058   // prevent infinite loops, e.g. when an exrule extinguishes an rrule (e.g.
01059   // the exrule is identical to the rrule). If an occurrence is found, break
01060   // out of the loop by returning that KDateTime
01061   int loop = 0;
01062   while ( loop < 1000 ) {
01063     // Outline of the algo:
01064     //   1) Find the next date/time after preDateTime when the event could recur
01065     //     1.1) Use the next occurrence from the explicit RDATE lists
01066     //     1.2) Add the next recurrence for each of the RRULEs
01067     //   2) Take the earliest recurrence of these = KDateTime nextDT
01068     //   3) If that date/time is not excluded, either explicitly by an EXDATE or
01069     //      by an EXRULE, return nextDT as the next date/time of the recurrence
01070     //   4) If it's excluded, start all at 1), but starting at nextDT (instead
01071     //      of preDateTime). Loop at most 1000 times.
01072     ++loop;
01073     // First, get the next recurrence from the RDate lists
01074     DateTimeList dates;
01075     if ( prevDT > startDateTime() ) {
01076       dates << startDateTime();
01077     }
01078 
01079     int i = d->mRDateTimes.findLT( prevDT );
01080     if ( i >= 0 ) {
01081       dates << d->mRDateTimes[i];
01082     }
01083 
01084     KDateTime kdt( startDateTime() );
01085     for ( i = d->mRDates.count();  --i >= 0; ) {
01086       kdt.setDate( d->mRDates[i] );
01087       if ( kdt < prevDT ) {
01088         dates << kdt;
01089         break;
01090       }
01091     }
01092 
01093     // Add the previous occurrences from all RRULEs.
01094     int end;
01095     for ( i = 0, end = d->mRRules.count();  i < end;  ++i ) {
01096       KDateTime dt = d->mRRules[i]->getPreviousDate( prevDT );
01097       if ( dt.isValid() ) {
01098         dates << dt;
01099       }
01100     }
01101 
01102     // Take the last of these (all others can't be used later on)
01103     dates.sortUnique();
01104     if ( dates.isEmpty() ) {
01105       return KDateTime();
01106     }
01107     prevDT = dates.last();
01108 
01109     // Check if that date/time is excluded explicitly or by an exrule:
01110     if ( !d->mExDates.containsSorted( prevDT.date() ) &&
01111          !d->mExDateTimes.containsSorted( prevDT ) ) {
01112       bool allowed = true;
01113       for ( i = 0, end = d->mExRules.count();  i < end;  ++i ) {
01114         allowed = allowed && !( d->mExRules[i]->recursAt( prevDT ) );
01115       }
01116       if ( allowed ) {
01117         return prevDT;
01118       }
01119     }
01120   }
01121 
01122   // Couldn't find a valid occurrences in 1000 loops, something is wrong!
01123   return KDateTime();
01124 }
01125 
01126 /***************************** PROTECTED FUNCTIONS ***************************/
01127 
01128 RecurrenceRule::List Recurrence::rRules() const
01129 {
01130   return d->mRRules;
01131 }
01132 
01133 void Recurrence::addRRule( RecurrenceRule *rrule )
01134 {
01135   if ( d->mRecurReadOnly || !rrule ) {
01136     return;
01137   }
01138 
01139   rrule->setAllDay( d->mAllDay );
01140   d->mRRules.append( rrule );
01141   rrule->addObserver( this );
01142   updated();
01143 }
01144 
01145 void Recurrence::removeRRule( RecurrenceRule *rrule )
01146 {
01147   if (d->mRecurReadOnly) {
01148     return;
01149   }
01150 
01151   d->mRRules.removeAll( rrule );
01152   rrule->removeObserver( this );
01153   updated();
01154 }
01155 
01156 void Recurrence::deleteRRule( RecurrenceRule *rrule )
01157 {
01158   if (d->mRecurReadOnly) {
01159     return;
01160   }
01161 
01162   d->mRRules.removeAll( rrule );
01163   delete rrule;
01164   updated();
01165 }
01166 
01167 RecurrenceRule::List Recurrence::exRules() const
01168 {
01169   return d->mExRules;
01170 }
01171 
01172 void Recurrence::addExRule( RecurrenceRule *exrule )
01173 {
01174   if ( d->mRecurReadOnly || !exrule ) {
01175     return;
01176   }
01177 
01178   exrule->setAllDay( d->mAllDay );
01179   d->mExRules.append( exrule );
01180   exrule->addObserver( this );
01181   updated();
01182 }
01183 
01184 void Recurrence::removeExRule( RecurrenceRule *exrule )
01185 {
01186   if ( d->mRecurReadOnly ) {
01187     return;
01188   }
01189 
01190   d->mExRules.removeAll( exrule );
01191   exrule->removeObserver( this );
01192   updated();
01193 }
01194 
01195 void Recurrence::deleteExRule( RecurrenceRule *exrule )
01196 {
01197   if ( d->mRecurReadOnly ) {
01198     return;
01199   }
01200 
01201   d->mExRules.removeAll( exrule );
01202   delete exrule;
01203   updated();
01204 }
01205 
01206 DateTimeList Recurrence::rDateTimes() const
01207 {
01208   return d->mRDateTimes;
01209 }
01210 
01211 void Recurrence::setRDateTimes( const DateTimeList &rdates )
01212 {
01213   if ( d->mRecurReadOnly ) {
01214     return;
01215   }
01216 
01217   d->mRDateTimes = rdates;
01218   d->mRDateTimes.sortUnique();
01219   updated();
01220 }
01221 
01222 void Recurrence::addRDateTime( const KDateTime &rdate )
01223 {
01224   if ( d->mRecurReadOnly ) {
01225     return;
01226   }
01227 
01228   d->mRDateTimes.insertSorted( rdate );
01229   updated();
01230 }
01231 
01232 DateList Recurrence::rDates() const
01233 {
01234   return d->mRDates;
01235 }
01236 
01237 void Recurrence::setRDates( const DateList &rdates )
01238 {
01239   if ( d->mRecurReadOnly ) {
01240     return;
01241   }
01242 
01243   d->mRDates = rdates;
01244   d->mRDates.sortUnique();
01245   updated();
01246 }
01247 
01248 void Recurrence::addRDate( const QDate &rdate )
01249 {
01250   if ( d->mRecurReadOnly ) {
01251     return;
01252   }
01253 
01254   d->mRDates.insertSorted( rdate );
01255   updated();
01256 }
01257 
01258 DateTimeList Recurrence::exDateTimes() const
01259 {
01260   return d->mExDateTimes;
01261 }
01262 
01263 void Recurrence::setExDateTimes( const DateTimeList &exdates )
01264 {
01265   if ( d->mRecurReadOnly ) {
01266     return;
01267   }
01268 
01269   d->mExDateTimes = exdates;
01270   d->mExDateTimes.sortUnique();
01271 }
01272 
01273 void Recurrence::addExDateTime( const KDateTime &exdate )
01274 {
01275   if ( d->mRecurReadOnly ) {
01276     return;
01277   }
01278 
01279   d->mExDateTimes.insertSorted( exdate );
01280   updated();
01281 }
01282 
01283 DateList Recurrence::exDates() const
01284 {
01285   return d->mExDates;
01286 }
01287 
01288 void Recurrence::setExDates( const DateList &exdates )
01289 {
01290   if ( d->mRecurReadOnly ) {
01291     return;
01292   }
01293 
01294   d->mExDates = exdates;
01295   d->mExDates.sortUnique();
01296   updated();
01297 }
01298 
01299 void Recurrence::addExDate( const QDate &exdate )
01300 {
01301   if ( d->mRecurReadOnly ) {
01302     return;
01303   }
01304 
01305   d->mExDates.insertSorted( exdate );
01306   updated();
01307 }
01308 
01309 void Recurrence::recurrenceChanged( RecurrenceRule * )
01310 {
01311   updated();
01312 }
01313 
01314 // %%%%%%%%%%%%%%%%%% end:Recurrencerule %%%%%%%%%%%%%%%%%%
01315 
01316 void Recurrence::dump() const
01317 {
01318   kDebug();
01319 
01320   int i;
01321   int count = d->mRRules.count();
01322   kDebug() << "  -)" << count << "RRULEs:";
01323   for ( i = 0;  i < count;  ++i ) {
01324     kDebug() << "    -) RecurrenceRule: ";
01325     d->mRRules[i]->dump();
01326   }
01327   count = d->mExRules.count();
01328   kDebug() << "  -)" << count << "EXRULEs:";
01329   for ( i = 0;  i < count;  ++i ) {
01330     kDebug() << "    -) ExceptionRule :";
01331     d->mExRules[i]->dump();
01332   }
01333 
01334   count = d->mRDates.count();
01335   kDebug() << endl << "  -)" << count << "Recurrence Dates:";
01336   for ( i = 0;  i < count;  ++i ) {
01337     kDebug() << "    " << d->mRDates[i];
01338   }
01339   count = d->mRDateTimes.count();
01340   kDebug() << endl << "  -)" << count << "Recurrence Date/Times:";
01341   for ( i = 0;  i < count;  ++i ) {
01342     kDebug() << "    " << d->mRDateTimes[i].dateTime();
01343   }
01344   count = d->mExDates.count();
01345   kDebug() << endl << "  -)" << count << "Exceptions Dates:";
01346   for ( i = 0;  i < count;  ++i ) {
01347     kDebug() << "    " << d->mExDates[i];
01348   }
01349   count = d->mExDateTimes.count();
01350   kDebug() << endl << "  -)" << count << "Exception Date/Times:";
01351   for ( i = 0;  i < count;  ++i ) {
01352     kDebug() << "    " << d->mExDateTimes[i].dateTime();
01353   }
01354 }

KCal Library

Skip menu "KCal Library"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

KDE-PIM Libraries

Skip menu "KDE-PIM Libraries"
  • akonadi
  • kabc
  • kblog
  • kcal
  • kimap
  • kioslave
  •   imap4
  •   mbox
  • kldap
  • kmime
  • kpimidentities
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Generated for KDE-PIM Libraries by doxygen 1.5.6
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal