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