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