libkcal Library API Documentation

recurrence.cpp

00001 /* 00002 This file is part of libkcal. 00003 Copyright (c) 1998 Preston Brown 00004 Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org> 00005 Copyright (c) 2002 David Jarvie <software@astrojar.org.uk> 00006 00007 This library is free software; you can redistribute it and/or 00008 modify it under the terms of the GNU Library General Public 00009 License as published by the Free Software Foundation; either 00010 version 2 of the License, or (at your option) any later version. 00011 00012 This library is distributed in the hope that it will be useful, 00013 but WITHOUT ANY WARRANTY; without even the implied warranty of 00014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00015 Library General Public License for more details. 00016 00017 You should have received a copy of the GNU Library General Public License 00018 along with this library; see the file COPYING.LIB. If not, write to 00019 the Free Software Foundation, Inc., 59 Temple Place - Suite 330, 00020 Boston, MA 02111-1307, USA. 00021 */ 00022 00023 #include <limits.h> 00024 00025 #include <kdebug.h> 00026 #include <kglobal.h> 00027 #include <klocale.h> 00028 00029 #include "incidence.h" 00030 00031 #include "recurrence.h" 00032 00033 using namespace KCal; 00034 00035 const QDate Recurrence::MAX_DATE(3000, 1, 1); // Maximum date = 1-Jan-3000 00036 Recurrence::Feb29Type Recurrence::mFeb29YearlyDefaultType = Recurrence::rMar1; 00037 00038 00039 Recurrence::Recurrence(Incidence *parent, int compatVersion) 00040 : recurs(rNone), // by default, it's not a recurring event 00041 rWeekStart(1), // default is Monday 00042 rDays(7), 00043 mUseCachedEndDT(false), 00044 mFloats(parent ? parent->doesFloat() : false), 00045 mRecurReadOnly(false), 00046 mFeb29YearlyType(mFeb29YearlyDefaultType), 00047 mCompatVersion(compatVersion ? compatVersion : INT_MAX), 00048 mCompatRecurs(rNone), 00049 mCompatDuration(0), 00050 mParent(parent) 00051 { 00052 rMonthDays.setAutoDelete( true ); 00053 rMonthPositions.setAutoDelete( true ); 00054 rYearNums.setAutoDelete( true ); 00055 } 00056 00057 Recurrence::Recurrence(const Recurrence &r, Incidence *parent) 00058 : recurs(r.recurs), 00059 rWeekStart(r.rWeekStart), 00060 rDays(r.rDays.copy()), 00061 rFreq(r.rFreq), 00062 rDuration(r.rDuration), 00063 rEndDateTime(r.rEndDateTime), 00064 mCachedEndDT(r.mCachedEndDT), 00065 mUseCachedEndDT(r.mUseCachedEndDT), 00066 mRecurStart(r.mRecurStart), 00067 mFloats(r.mFloats), 00068 mRecurReadOnly(r.mRecurReadOnly), 00069 mFeb29YearlyType(r.mFeb29YearlyType), 00070 mCompatVersion(r.mCompatVersion), 00071 mCompatRecurs(r.mCompatRecurs), 00072 mCompatDuration(r.mCompatDuration), 00073 mParent(parent) 00074 { 00075 for (QPtrListIterator<rMonthPos> mp(r.rMonthPositions); mp.current(); ++mp) { 00076 rMonthPos *tmp = new rMonthPos; 00077 tmp->rPos = mp.current()->rPos; 00078 tmp->negative = mp.current()->negative; 00079 tmp->rDays = mp.current()->rDays.copy(); 00080 rMonthPositions.append(tmp); 00081 } 00082 for (QPtrListIterator<int> md(r.rMonthDays); md.current(); ++md) { 00083 int *tmp = new int; 00084 *tmp = *md.current(); 00085 rMonthDays.append(tmp); 00086 } 00087 for (QPtrListIterator<int> yn(r.rYearNums); yn.current(); ++yn) { 00088 int *tmp = new int; 00089 *tmp = *yn.current(); 00090 rYearNums.append(tmp); 00091 } 00092 rMonthDays.setAutoDelete( true ); 00093 rMonthPositions.setAutoDelete( true ); 00094 rYearNums.setAutoDelete( true ); 00095 } 00096 00097 Recurrence::~Recurrence() 00098 { 00099 } 00100 00101 00102 bool Recurrence::operator==( const Recurrence& r2 ) const 00103 { 00104 if ( recurs == rNone && r2.recurs == rNone ) 00105 return true; 00106 if ( recurs != r2.recurs 00107 || rFreq != r2.rFreq 00108 || rDuration != r2.rDuration 00109 || ( !rDuration && rEndDateTime != r2.rEndDateTime ) 00110 || mRecurStart != r2.mRecurStart 00111 || mFloats != r2.mFloats 00112 || mRecurReadOnly != r2.mRecurReadOnly ) 00113 return false; 00114 // no need to compare mCompat* and mParent 00115 // OK to compare the pointers 00116 switch ( recurs ) 00117 { 00118 case rWeekly: 00119 return rDays == r2.rDays 00120 && rWeekStart == r2.rWeekStart; 00121 case rMonthlyPos: 00122 return rMonthPositions == r2.rMonthPositions; 00123 case rMonthlyDay: 00124 return rMonthDays == r2.rMonthDays; 00125 case rYearlyPos: 00126 return rYearNums == r2.rYearNums 00127 && rMonthPositions == r2.rMonthPositions; 00128 case rYearlyMonth: 00129 return rYearNums == r2.rYearNums 00130 && rMonthDays == r2.rMonthDays 00131 && mFeb29YearlyType == r2.mFeb29YearlyType; 00132 case rYearlyDay: 00133 return rYearNums == r2.rYearNums; 00134 case rNone: 00135 case rMinutely: 00136 case rHourly: 00137 case rDaily: 00138 default: 00139 return true; 00140 } 00141 } 00142 00143 00144 void Recurrence::setCompatVersion(int version) 00145 { 00146 mCompatVersion = version ? version : INT_MAX; 00147 mUseCachedEndDT = false; 00148 } 00149 00150 ushort Recurrence::doesRecur() const 00151 { 00152 return recurs; 00153 } 00154 00155 bool Recurrence::recursOnPure(const QDate &qd) const 00156 { 00157 switch(recurs) { 00158 case rMinutely: 00159 return recursSecondly(qd, rFreq*60); 00160 case rHourly: 00161 return recursSecondly(qd, rFreq*3600); 00162 case rDaily: 00163 return recursDaily(qd); 00164 case rWeekly: 00165 return recursWeekly(qd); 00166 case rMonthlyPos: 00167 case rMonthlyDay: 00168 return recursMonthly(qd); 00169 case rYearlyMonth: 00170 return recursYearlyByMonth(qd); 00171 case rYearlyDay: 00172 return recursYearlyByDay(qd); 00173 case rYearlyPos: 00174 return recursYearlyByPos(qd); 00175 default: 00176 // catch-all. Should never get here. 00177 kdError(5800) << "Control should never reach here in recursOnPure()!" << endl; 00178 case rNone: 00179 return false; 00180 } // case 00181 } 00182 00183 bool Recurrence::recursAtPure(const QDateTime &dt) const 00184 { 00185 switch(recurs) { 00186 case rMinutely: 00187 return recursMinutelyAt(dt, rFreq); 00188 case rHourly: 00189 return recursMinutelyAt(dt, rFreq*60); 00190 default: 00191 if (dt.time() != mRecurStart.time()) 00192 return false; 00193 switch(recurs) { 00194 case rDaily: 00195 return recursDaily(dt.date()); 00196 case rWeekly: 00197 return recursWeekly(dt.date()); 00198 case rMonthlyPos: 00199 case rMonthlyDay: 00200 return recursMonthly(dt.date()); 00201 case rYearlyMonth: 00202 return recursYearlyByMonth(dt.date()); 00203 case rYearlyDay: 00204 return recursYearlyByDay(dt.date()); 00205 case rYearlyPos: 00206 return recursYearlyByPos(dt.date()); 00207 default: 00208 // catch-all. Should never get here. 00209 kdError(5800) << "Control should never reach here in recursAtPure()!" << endl; 00210 case rNone: 00211 return false; 00212 } 00213 } // case 00214 } 00215 00216 QDate Recurrence::endDate(bool *result) const 00217 { 00218 return endDateTime(result).date(); 00219 } 00220 00221 QDateTime Recurrence::endDateTime(bool *result) const 00222 { 00223 int count = 0; 00224 if (result) 00225 *result = true; 00226 QDate end; 00227 if (recurs != rNone) { 00228 if (rDuration < 0) 00229 return QDateTime(); // infinite recurrence 00230 if (rDuration == 0) 00231 return rEndDateTime; 00232 00233 // The end date is determined by the recurrence count 00234 if (mUseCachedEndDT) { 00235 if (result && !mCachedEndDT.isValid()) 00236 *result = false; // error - there is no recurrence 00237 return mCachedEndDT; // avoid potentially long calculation 00238 } 00239 00240 mUseCachedEndDT = true; 00241 switch (recurs) 00242 { 00243 case rMinutely: 00244 mCachedEndDT = mRecurStart.addSecs((rDuration-1)*rFreq*60); 00245 return mCachedEndDT; 00246 case rHourly: 00247 mCachedEndDT = mRecurStart.addSecs((rDuration-1)*rFreq*3600); 00248 return mCachedEndDT; 00249 case rDaily: 00250 mCachedEndDT = mRecurStart.addDays((rDuration-1)*rFreq); 00251 return mCachedEndDT; 00252 00253 case rWeekly: 00254 count = weeklyCalc(END_DATE_AND_COUNT, end); 00255 break; 00256 case rMonthlyPos: 00257 case rMonthlyDay: 00258 count = monthlyCalc(END_DATE_AND_COUNT, end); 00259 break; 00260 case rYearlyMonth: 00261 count = yearlyMonthCalc(END_DATE_AND_COUNT, end); 00262 break; 00263 case rYearlyDay: 00264 count = yearlyDayCalc(END_DATE_AND_COUNT, end); 00265 break; 00266 case rYearlyPos: 00267 count = yearlyPosCalc(END_DATE_AND_COUNT, end); 00268 break; 00269 default: 00270 // catch-all. Should never get here. 00271 kdError(5800) << "Control should never reach here in endDate()!" << endl; 00272 mUseCachedEndDT = false; 00273 break; 00274 } 00275 } 00276 if (!count) { 00277 if (result) 00278 *result = false; 00279 mCachedEndDT = QDateTime(); // error - there is no recurrence 00280 } 00281 else 00282 mCachedEndDT = QDateTime(end, mRecurStart.time()); 00283 return mCachedEndDT; 00284 } 00285 00286 QString Recurrence::endDateStr(bool shortfmt) const 00287 { 00288 return KGlobal::locale()->formatDate(rEndDateTime.date(),shortfmt); 00289 } 00290 00291 void Recurrence::setEndDate(const QDate &date) 00292 { 00293 setEndDateTime(QDateTime(date, mRecurStart.time())); 00294 } 00295 00296 void Recurrence::setEndDateTime(const QDateTime &dateTime) 00297 { 00298 if (mRecurReadOnly) return; 00299 rEndDateTime = dateTime; 00300 rDuration = 0; // set to 0 because there is an end date/time 00301 mCompatDuration = 0; 00302 mUseCachedEndDT = false; 00303 } 00304 00305 int Recurrence::duration() const 00306 { 00307 return rDuration; 00308 } 00309 00310 int Recurrence::durationTo(const QDate &date) const 00311 { 00312 QDate d = date; 00313 return recurCalc(COUNT_TO_DATE, d); 00314 } 00315 00316 int Recurrence::durationTo(const QDateTime &datetime) const 00317 { 00318 QDateTime dt = datetime; 00319 return recurCalc(COUNT_TO_DATE, dt); 00320 } 00321 00322 void Recurrence::setDuration(int _rDuration) 00323 { 00324 if (mRecurReadOnly) return; 00325 if (_rDuration > 0) { 00326 rDuration = _rDuration; 00327 // Compatibility mode is only needed when reading the calendar in ICalFormatImpl, 00328 // so explicitly setting the duration means no backwards compatibility is needed. 00329 mCompatDuration = 0; 00330 } 00331 mUseCachedEndDT = false; 00332 } 00333 00334 void Recurrence::unsetRecurs() 00335 { 00336 if (mRecurReadOnly) return; 00337 recurs = rNone; 00338 rMonthPositions.clear(); 00339 rMonthDays.clear(); 00340 rYearNums.clear(); 00341 mUseCachedEndDT = false; 00342 } 00343 00344 void Recurrence::setRecurStart(const QDateTime &start) 00345 { 00346 mRecurStart = start; 00347 mFloats = false; 00348 switch (recurs) 00349 { 00350 case rMinutely: 00351 case rHourly: 00352 break; 00353 case rDaily: 00354 case rWeekly: 00355 case rMonthlyPos: 00356 case rMonthlyDay: 00357 case rYearlyMonth: 00358 case rYearlyDay: 00359 case rYearlyPos: 00360 default: 00361 rEndDateTime.setTime(start.time()); 00362 break; 00363 } 00364 mUseCachedEndDT = false; 00365 } 00366 00367 void Recurrence::setRecurStart(const QDate &start) 00368 { 00369 mRecurStart.setDate(start); 00370 mRecurStart.setTime(QTime(0,0,0)); 00371 switch (recurs) 00372 { 00373 case rMinutely: 00374 case rHourly: 00375 break; 00376 case rDaily: 00377 case rWeekly: 00378 case rMonthlyPos: 00379 case rMonthlyDay: 00380 case rYearlyMonth: 00381 case rYearlyDay: 00382 case rYearlyPos: 00383 default: 00384 mFloats = true; 00385 break; 00386 } 00387 mUseCachedEndDT = false; 00388 } 00389 00390 void Recurrence::setFloats(bool f) 00391 { 00392 if (f && mFloats || !f && !mFloats) 00393 return; // no change 00394 00395 switch (recurs) 00396 { 00397 case rDaily: 00398 case rWeekly: 00399 case rMonthlyPos: 00400 case rMonthlyDay: 00401 case rYearlyMonth: 00402 case rYearlyDay: 00403 case rYearlyPos: 00404 break; 00405 case rMinutely: 00406 case rHourly: 00407 default: 00408 return; // can't set sub-daily to floating 00409 } 00410 mFloats = f; 00411 if (f) { 00412 mRecurStart.setTime(QTime(0,0,0)); 00413 rEndDateTime.setTime(QTime(0,0,0)); 00414 } 00415 mUseCachedEndDT = false; 00416 } 00417 00418 int Recurrence::frequency() const 00419 { 00420 return rFreq; 00421 } 00422 00423 void Recurrence::setFrequency(int freq) 00424 { 00425 if (mRecurReadOnly || freq <= 0) return; 00426 rFreq = freq; 00427 mUseCachedEndDT = false; 00428 } 00429 00430 const QBitArray &Recurrence::days() const 00431 { 00432 return rDays; 00433 } 00434 00435 const QPtrList<Recurrence::rMonthPos> &Recurrence::monthPositions() const 00436 { 00437 return rMonthPositions; 00438 } 00439 00440 const QPtrList<Recurrence::rMonthPos> &Recurrence::yearMonthPositions() const 00441 { 00442 return rMonthPositions; 00443 } 00444 00445 const QPtrList<int> &Recurrence::monthDays() const 00446 { 00447 return rMonthDays; 00448 } 00449 00450 void Recurrence::setMinutely(int _rFreq, int _rDuration) 00451 { 00452 if (mRecurReadOnly || _rFreq <= 0 || _rDuration == 0 || _rDuration < -1) 00453 return; 00454 setDailySub(rMinutely, _rFreq, _rDuration); 00455 } 00456 00457 void Recurrence::setMinutely(int _rFreq, const QDateTime &_rEndDateTime) 00458 { 00459 if (mRecurReadOnly || _rFreq <= 0) return; 00460 rEndDateTime = _rEndDateTime; 00461 setDailySub(rMinutely, _rFreq, 0); 00462 } 00463 00464 void Recurrence::setHourly(int _rFreq, int _rDuration) 00465 { 00466 if (mRecurReadOnly || _rFreq <= 0 || _rDuration == 0 || _rDuration < -1) 00467 return; 00468 setDailySub(rHourly, _rFreq, _rDuration); 00469 } 00470 00471 void Recurrence::setHourly(int _rFreq, const QDateTime &_rEndDateTime) 00472 { 00473 if (mRecurReadOnly || _rFreq <= 0) return; 00474 rEndDateTime = _rEndDateTime; 00475 setDailySub(rHourly, _rFreq, 0); 00476 } 00477 00478 void Recurrence::setDaily(int _rFreq, int _rDuration) 00479 { 00480 if (mRecurReadOnly || _rFreq <= 0 || _rDuration == 0 || _rDuration < -1) 00481 return; 00482 setDailySub(rDaily, _rFreq, _rDuration); 00483 } 00484 00485 void Recurrence::setDaily(int _rFreq, const QDate &_rEndDate) 00486 { 00487 if (mRecurReadOnly || _rFreq <= 0) return; 00488 rEndDateTime.setDate(_rEndDate); 00489 rEndDateTime.setTime(mRecurStart.time()); 00490 setDailySub(rDaily, _rFreq, 0); 00491 } 00492 00493 void Recurrence::setWeekly(int _rFreq, const QBitArray &_rDays, 00494 int _rDuration, int _rWeekStart) 00495 { 00496 if (mRecurReadOnly || _rFreq <= 0 || _rDuration == 0 || _rDuration < -1) 00497 return; 00498 mUseCachedEndDT = false; 00499 00500 recurs = rWeekly; 00501 rFreq = _rFreq; 00502 rDays = _rDays; 00503 rWeekStart = _rWeekStart; 00504 rDuration = _rDuration; 00505 if (mCompatVersion < 310 && _rDuration > 0) { 00506 // Backwards compatibility for KDE < 3.1. 00507 // rDuration was set to the number of time periods to recur, 00508 // with week start always on a Monday. 00509 // Convert this to the number of occurrences. 00510 mCompatDuration = _rDuration; 00511 int weeks = ((mCompatDuration-1)*7) + (7 - mRecurStart.date().dayOfWeek()); 00512 QDate end(mRecurStart.date().addDays(weeks * rFreq)); 00513 rDuration = INT_MAX; // ensure that weeklyCalc() does its job correctly 00514 rDuration = weeklyCalc(COUNT_TO_DATE, end); 00515 } else { 00516 mCompatDuration = 0; 00517 } 00518 rMonthPositions.clear(); 00519 rMonthDays.clear(); 00520 if (mParent) mParent->updated(); 00521 } 00522 00523 void Recurrence::setWeekly(int _rFreq, const QBitArray &_rDays, 00524 const QDate &_rEndDate, int _rWeekStart) 00525 { 00526 if (mRecurReadOnly || _rFreq <= 0) return; 00527 mUseCachedEndDT = false; 00528 00529 recurs = rWeekly; 00530 rFreq = _rFreq; 00531 rDays = _rDays; 00532 rWeekStart = _rWeekStart; 00533 rEndDateTime.setDate(_rEndDate); 00534 rEndDateTime.setTime(mRecurStart.time()); 00535 rDuration = 0; // set to 0 because there is an end date 00536 mCompatDuration = 0; 00537 rMonthPositions.clear(); 00538 rMonthDays.clear(); 00539 rYearNums.clear(); 00540 if (mParent) mParent->updated(); 00541 } 00542 00543 void Recurrence::setMonthly(short type, int _rFreq, int _rDuration) 00544 { 00545 if (mRecurReadOnly || _rFreq <= 0 || _rDuration == 0 || _rDuration < -1) 00546 return; 00547 mUseCachedEndDT = false; 00548 00549 recurs = type; 00550 rFreq = _rFreq; 00551 rDuration = _rDuration; 00552 if (mCompatVersion < 310) 00553 mCompatDuration = (_rDuration > 0) ? _rDuration : 0; 00554 rYearNums.clear(); 00555 if (mParent) mParent->updated(); 00556 } 00557 00558 void Recurrence::setMonthly(short type, int _rFreq, const QDate &_rEndDate) 00559 { 00560 if (mRecurReadOnly || _rFreq <= 0) return; 00561 mUseCachedEndDT = false; 00562 00563 recurs = type; 00564 rFreq = _rFreq; 00565 rEndDateTime.setDate(_rEndDate); 00566 rEndDateTime.setTime(mRecurStart.time()); 00567 rDuration = 0; // set to 0 because there is an end date 00568 mCompatDuration = 0; 00569 rYearNums.clear(); 00570 if (mParent) mParent->updated(); 00571 } 00572 00573 void Recurrence::addMonthlyPos(short _rPos, const QBitArray &_rDays) 00574 { 00575 if (recurs == rMonthlyPos) 00576 addMonthlyPos_(_rPos, _rDays); 00577 } 00578 00579 void Recurrence::addMonthlyPos_(short _rPos, const QBitArray &_rDays) 00580 { 00581 if (mRecurReadOnly 00582 || _rPos == 0 || _rPos > 5 || _rPos < -5) // invalid week number 00583 return; 00584 00585 mUseCachedEndDT = false; 00586 for (rMonthPos* it = rMonthPositions.first(); it; it = rMonthPositions.next()) { 00587 int itPos = it->negative ? -it->rPos : it->rPos; 00588 if (_rPos == itPos) { 00589 // This week is already in the list. 00590 // Combine the specified days with those in the list. 00591 it->rDays |= _rDays; 00592 if (mParent) mParent->updated(); 00593 return; 00594 } 00595 } 00596 // Add the new position to the list 00597 rMonthPos *tmpPos = new rMonthPos; 00598 if (_rPos > 0) { 00599 tmpPos->rPos = _rPos; 00600 tmpPos->negative = false; 00601 } else { 00602 tmpPos->rPos = -_rPos; // take abs() 00603 tmpPos->negative = true; 00604 } 00605 tmpPos->rDays = _rDays; 00606 tmpPos->rDays.detach(); 00607 rMonthPositions.append(tmpPos); 00608 00609 if (mCompatVersion < 310 && mCompatDuration > 0) { 00610 // Backwards compatibility for KDE < 3.1. 00611 // rDuration was set to the number of time periods to recur. 00612 // Convert this to the number of occurrences. 00613 int monthsAhead = (mCompatDuration-1) * rFreq; 00614 int month = mRecurStart.date().month() - 1 + monthsAhead; 00615 QDate end(mRecurStart.date().year() + month/12, month%12 + 1, 31); 00616 rDuration = INT_MAX; // ensure that recurCalc() does its job correctly 00617 rDuration = recurCalc(COUNT_TO_DATE, end); 00618 } 00619 00620 if (mParent) mParent->updated(); 00621 } 00622 00623 void Recurrence::addMonthlyDay(short _rDay) 00624 { 00625 if (mRecurReadOnly || (recurs != rMonthlyDay && recurs != rYearlyMonth) 00626 || _rDay == 0 || _rDay > 31 || _rDay < -31) // invalid day number 00627 return; 00628 for (int* it = rMonthDays.first(); it; it = rMonthDays.next()) { 00629 if (_rDay == *it) 00630 return; // this day is already in the list - avoid duplication 00631 } 00632 mUseCachedEndDT = false; 00633 00634 int *tmpDay = new int; 00635 *tmpDay = _rDay; 00636 rMonthDays.append(tmpDay); 00637 00638 if (mCompatVersion < 310 && mCompatDuration > 0) { 00639 // Backwards compatibility for KDE < 3.1. 00640 // rDuration was set to the number of time periods to recur. 00641 // Convert this to the number of occurrences. 00642 int monthsAhead = (mCompatDuration-1) * rFreq; 00643 int month = mRecurStart.date().month() - 1 + monthsAhead; 00644 QDate end(mRecurStart.date().year() + month/12, month%12 + 1, 31); 00645 rDuration = INT_MAX; // ensure that recurCalc() does its job correctly 00646 rDuration = recurCalc(COUNT_TO_DATE, end); 00647 } 00648 00649 if (mParent) mParent->updated(); 00650 } 00651 00652 void Recurrence::setYearly(int type, int _rFreq, int _rDuration) 00653 { 00654 if (mRecurReadOnly || _rFreq <= 0 || _rDuration == 0 || _rDuration < -1) 00655 return; 00656 if (mCompatVersion < 310) 00657 mCompatDuration = (_rDuration > 0) ? _rDuration : 0; 00658 setYearly_(type, mFeb29YearlyDefaultType, _rFreq, _rDuration); 00659 } 00660 00661 void Recurrence::setYearly(int type, int _rFreq, const QDate &_rEndDate) 00662 { 00663 if (mRecurReadOnly || _rFreq <= 0) return; 00664 rEndDateTime.setDate(_rEndDate); 00665 rEndDateTime.setTime(mRecurStart.time()); 00666 mCompatDuration = 0; 00667 setYearly_(type, mFeb29YearlyDefaultType, _rFreq, 0); 00668 } 00669 00670 void Recurrence::setYearlyByDate(Feb29Type type, int _rFreq, int _rDuration) 00671 { 00672 setYearlyByDate(0, type, _rFreq, _rDuration); 00673 } 00674 00675 void Recurrence::setYearlyByDate(Feb29Type type, int _rFreq, const QDate &_rEndDate) 00676 { 00677 setYearlyByDate(0, type, _rFreq, _rEndDate); 00678 } 00679 00680 void Recurrence::setYearlyByDate(int day, Feb29Type type, int _rFreq, int _rDuration) 00681 { 00682 if (mRecurReadOnly || _rFreq <= 0 || _rDuration == 0 || _rDuration < -1) 00683 return; 00684 if (mCompatVersion < 310) 00685 mCompatDuration = (_rDuration > 0) ? _rDuration : 0; 00686 setYearly_(rYearlyMonth, type, _rFreq, _rDuration); 00687 if (day) 00688 addMonthlyDay(day); 00689 } 00690 00691 void Recurrence::setYearlyByDate(int day, Feb29Type type, int _rFreq, const QDate &_rEndDate) 00692 { 00693 if (mRecurReadOnly || _rFreq <= 0) return; 00694 rEndDateTime.setDate(_rEndDate); 00695 rEndDateTime.setTime(mRecurStart.time()); 00696 mCompatDuration = 0; 00697 setYearly_(rYearlyMonth, type, _rFreq, 0); 00698 if (day) 00699 addMonthlyDay(day); 00700 } 00701 00702 void Recurrence::addYearlyMonthPos(short _rPos, const QBitArray &_rDays) 00703 { 00704 if (recurs == rYearlyPos) 00705 addMonthlyPos_(_rPos, _rDays); 00706 } 00707 00708 const QPtrList<int> &Recurrence::yearNums() const 00709 { 00710 return rYearNums; 00711 } 00712 00713 void Recurrence::addYearlyNum(short _rNum) 00714 { 00715 if (mRecurReadOnly 00716 || (recurs != rYearlyMonth && recurs != rYearlyDay && recurs != rYearlyPos) 00717 || _rNum <= 0) // invalid day/month number 00718 return; 00719 00720 if (mCompatVersion < 310 && mCompatRecurs == rYearlyDay) { 00721 // Backwards compatibility for KDE < 3.1. 00722 // Dates were stored as day numbers, with a fiddle to take account of leap years. 00723 // Convert the day number to a month. 00724 if (_rNum <= 0 || _rNum > 366 || (_rNum == 366 && mRecurStart.date().daysInYear() < 366)) 00725 return; // invalid day number 00726 _rNum = QDate(mRecurStart.date().year(), 1, 1).addDays(_rNum - 1).month(); 00727 } else 00728 if ((recurs == rYearlyMonth || recurs == rYearlyPos) && _rNum > 12 00729 || recurs == rYearlyDay && _rNum > 366) 00730 return; // invalid day number 00731 00732 uint i = 0; 00733 for (int* it = rYearNums.first(); it && _rNum >= *it; it = rYearNums.next()) { 00734 if (_rNum == *it) 00735 return; // this day/month is already in the list - avoid duplication 00736 ++i; 00737 } 00738 mUseCachedEndDT = false; 00739 00740 int *tmpNum = new int; 00741 *tmpNum = _rNum; 00742 rYearNums.insert(i, tmpNum); // insert the day/month in a sorted position 00743 00744 if (mCompatVersion < 310 && mCompatDuration > 0) { 00745 // Backwards compatibility for KDE < 3.1. 00746 // rDuration was set to the number of time periods to recur. 00747 // Convert this to the number of occurrences. 00748 QDate end(mRecurStart.date().year() + (mCompatDuration-1)*rFreq, 12, 31); 00749 rDuration = INT_MAX; // ensure that recurCalc() does its job correctly 00750 rDuration = recurCalc(COUNT_TO_DATE, end); 00751 } 00752 00753 if (mParent) mParent->updated(); 00754 } 00755 00756 00757 QValueList<QTime> Recurrence::recurTimesOn(const QDate &date) const 00758 { 00759 QValueList<QTime> times; 00760 switch (recurs) 00761 { 00762 case rMinutely: 00763 case rHourly: 00764 if ((date >= mRecurStart.date()) && 00765 ((rDuration > 0) && (date <= endDate()) || 00766 ((rDuration == 0) && (date <= rEndDateTime.date())) || 00767 (rDuration == -1))) { 00768 // The date queried falls within the range of the event. 00769 int secondFreq = rFreq * (recurs == rMinutely ? 60 : 3600); 00770 int after = mRecurStart.secsTo(QDateTime(date)) - 1; 00771 int count = (after + 24*3600) / secondFreq - after / secondFreq; 00772 if (count) { 00773 // It recurs at least once on the given date 00774 QTime t = mRecurStart.addSecs((after / secondFreq) * secondFreq).time(); 00775 while (--count >= 0) { 00776 t = t.addSecs(secondFreq); 00777 times.append(t); 00778 } 00779 } 00780 } 00781 break; 00782 case rDaily: 00783 case rWeekly: 00784 case rMonthlyPos: 00785 case rMonthlyDay: 00786 case rYearlyMonth: 00787 case rYearlyDay: 00788 case rYearlyPos: 00789 if (recursOnPure(date)) 00790 times.append(mRecurStart.time()); 00791 break; 00792 default: 00793 break; 00794 } 00795 return times; 00796 } 00797 00798 QDateTime Recurrence::getNextDateTime(const QDateTime &preDateTime, bool *last) const 00799 { 00800 int freq; 00801 switch (recurs) 00802 { 00803 case rMinutely: 00804 freq = rFreq * 60; 00805 break; 00806 case rHourly: 00807 freq = rFreq * 3600; 00808 break; 00809 case rDaily: 00810 case rWeekly: 00811 case rMonthlyPos: 00812 case rMonthlyDay: 00813 case rYearlyMonth: 00814 case rYearlyDay: 00815 case rYearlyPos: { 00816 QDate preDate = preDateTime.date(); 00817 if (!mFloats && mRecurStart.time() > preDateTime.time()) 00818 preDate = preDate.addDays(-1); 00819 return QDateTime(getNextDateNoTime(preDate, last), mRecurStart.time()); 00820 } 00821 default: 00822 return QDateTime(); 00823 } 00824 00825 // It's a sub-daily recurrence 00826 if (last) 00827 *last = false; 00828 if (preDateTime < mRecurStart) 00829 return mRecurStart; 00830 int count = mRecurStart.secsTo(preDateTime) / freq + 2; 00831 if (rDuration > 0) { 00832 if (count > rDuration) 00833 return QDateTime(); 00834 if (last && count == rDuration) 00835 *last = true; 00836 } 00837 QDateTime endtime = mRecurStart.addSecs((count - 1)*freq); 00838 if (rDuration == 0) { 00839 if (endtime > rEndDateTime) 00840 return QDateTime(); 00841 if (last && endtime == rEndDateTime) 00842 *last = true; 00843 } 00844 return endtime; 00845 } 00846 00847 QDate Recurrence::getNextDate(const QDate &preDate, bool *last) const 00848 { 00849 switch (recurs) 00850 { 00851 case rMinutely: 00852 case rHourly: 00853 return getNextDateTime(QDateTime(preDate, QTime(23,59,59)), last).date(); 00854 case rDaily: 00855 case rWeekly: 00856 case rMonthlyPos: 00857 case rMonthlyDay: 00858 case rYearlyMonth: 00859 case rYearlyDay: 00860 case rYearlyPos: 00861 return getNextDateNoTime(preDate, last); 00862 default: 00863 return QDate(); 00864 } 00865 } 00866 00867 00868 QDateTime Recurrence::getPreviousDateTime(const QDateTime &afterDateTime, bool *last) const 00869 { 00870 int freq; 00871 switch (recurs) 00872 { 00873 case rMinutely: 00874 freq = rFreq * 60; 00875 break; 00876 case rHourly: 00877 freq = rFreq * 3600; 00878 break; 00879 case rDaily: 00880 case rWeekly: 00881 case rMonthlyPos: 00882 case rMonthlyDay: 00883 case rYearlyMonth: 00884 case rYearlyDay: 00885 case rYearlyPos: { 00886 QDate afterDate = afterDateTime.date(); 00887 if (!mFloats && mRecurStart.time() < afterDateTime.time()) 00888 afterDate = afterDate.addDays(1); 00889 return QDateTime(getPreviousDateNoTime(afterDate, last), mRecurStart.time()); 00890 } 00891 default: 00892 return QDateTime(); 00893 } 00894 00895 // It's a sub-daily recurrence 00896 if (last) 00897 *last = false; 00898 if (afterDateTime <= mRecurStart) 00899 return QDateTime(); 00900 int count = (mRecurStart.secsTo(afterDateTime) - 1) / freq + 1; 00901 if (rDuration > 0) { 00902 if (count > rDuration) 00903 count = rDuration; 00904 if (last && count == rDuration) 00905 *last = true; 00906 } 00907 QDateTime endtime = mRecurStart.addSecs((count - 1)*freq); 00908 if (rDuration == 0) { 00909 if (endtime > rEndDateTime) 00910 endtime = rEndDateTime; 00911 if (last && endtime == rEndDateTime) 00912 *last = true; 00913 } 00914 return endtime; 00915 } 00916 00917 QDate Recurrence::getPreviousDate(const QDate &afterDate, bool *last) const 00918 { 00919 switch (recurs) 00920 { 00921 case rMinutely: 00922 case rHourly: 00923 return getPreviousDateTime(QDateTime(afterDate, QTime(0,0,0)), last).date(); 00924 case rDaily: 00925 case rWeekly: 00926 case rMonthlyPos: 00927 case rMonthlyDay: 00928 case rYearlyMonth: 00929 case rYearlyDay: 00930 case rYearlyPos: 00931 return getPreviousDateNoTime(afterDate, last); 00932 default: 00933 return QDate(); 00934 } 00935 } 00936 00937 00938 /***************************** PROTECTED FUNCTIONS ***************************/ 00939 00940 bool Recurrence::recursSecondly(const QDate &qd, int secondFreq) const 00941 { 00942 if ((qd >= mRecurStart.date()) && 00943 ((rDuration > 0) && (qd <= endDate()) || 00944 ((rDuration == 0) && (qd <= rEndDateTime.date())) || 00945 (rDuration == -1))) { 00946 // The date queried falls within the range of the event. 00947 if (secondFreq < 24*3600) 00948 return true; // the event recurs at least once each day 00949 int after = mRecurStart.secsTo(QDateTime(qd)) - 1; 00950 if (after / secondFreq != (after + 24*3600) / secondFreq) 00951 return true; 00952 } 00953 return false; 00954 } 00955 00956 bool Recurrence::recursMinutelyAt(const QDateTime &dt, int minuteFreq) const 00957 { 00958 if ((dt >= mRecurStart) && 00959 ((rDuration > 0) && (dt <= endDateTime()) || 00960 ((rDuration == 0) && (dt <= rEndDateTime)) || 00961 (rDuration == -1))) { 00962 // The time queried falls within the range of the event. 00963 if (((mRecurStart.secsTo(dt) / 60) % minuteFreq) == 0) 00964 return true; 00965 } 00966 return false; 00967 } 00968 00969 bool Recurrence::recursDaily(const QDate &qd) const 00970 { 00971 QDate dStart = mRecurStart.date(); 00972 if ((dStart.daysTo(qd) % rFreq) == 0) { 00973 // The date is a day which recurs 00974 if (qd >= dStart 00975 && ((rDuration > 0 && qd <= endDate()) || 00976 (rDuration == 0 && qd <= rEndDateTime.date()) || 00977 rDuration == -1)) { 00978 // The date queried falls within the range of the event. 00979 return true; 00980 } 00981 } 00982 return false; 00983 } 00984 00985 bool Recurrence::recursWeekly(const QDate &qd) const 00986 { 00987 int i = qd.dayOfWeek()-1; 00988 bool weekDayMatches = rDays.testBit( (uint) i); 00989 QDate dStart = mRecurStart.date(); 00990 if ( mParent && mParent->type() == "Todo" && weekDayMatches ) { 00991 dStart = dStart.addDays( qd.dayOfWeek() - mRecurStart.date().dayOfWeek() ); 00992 } 00993 00994 if ((dStart.daysTo(qd)/7) % rFreq == 0 && weekDayMatches ) { 00995 // The date is in a week which recurs 00996 if (qd >= dStart 00997 && ((rDuration > 0 && qd <= endDate()) || 00998 (rDuration == 0 && qd <= rEndDateTime.date()) || 00999 rDuration == -1)) { 01000 // The date queried falls within the range of the event. 01001 return true; 01002 } 01003 } 01004 return false; 01005 } 01006 01007 bool Recurrence::recursMonthly(const QDate &qd) const 01008 { 01009 QDate dStart = mRecurStart.date(); 01010 int year = qd.year(); 01011 int month = qd.month(); 01012 int day = qd.day(); 01013 // calculate how many months ahead this date is from the original 01014 // event's date 01015 int monthsAhead = (year - dStart.year()) * 12 + (month - dStart.month()); 01016 if ((monthsAhead % rFreq) == 0) { 01017 // The date is in a month which recurs 01018 if (qd >= dStart 01019 && ((rDuration > 0 && qd <= endDate()) || 01020 (rDuration == 0 && qd <= rEndDateTime.date()) || 01021 rDuration == -1)) { 01022 // The date queried falls within the range of the event. 01023 QValueList<int> days; 01024 int daysInMonth = qd.daysInMonth(); 01025 if (recurs == rMonthlyDay) 01026 getMonthlyDayDays(days, daysInMonth); 01027 else if (recurs == rMonthlyPos) 01028 getMonthlyPosDays(days, daysInMonth, QDate(year, month, 1).dayOfWeek()); 01029 for (QValueList<int>::Iterator it = days.begin(); it != days.end(); ++it) { 01030 if (*it == day) 01031 return true; 01032 } 01033 // no dates matched 01034 } 01035 } 01036 return false; 01037 } 01038 01039 bool Recurrence::recursYearlyByMonth(const QDate &qd) const 01040 { 01041 QDate dStart = mRecurStart.date(); 01042 int startDay = dStart.day(); 01043 if (rMonthDays.count()) 01044 startDay = *rMonthDays.getFirst(); 01045 int qday = qd.day(); 01046 int qmonth = qd.month(); 01047 int qyear = qd.year(); 01048 bool match = (qday == startDay); 01049 if (startDay < 0) 01050 match = (qday == qd.daysInMonth() + startDay + 1); 01051 if (!match && startDay == 29 && dStart.month() == 2) { 01052 // It's a recurrence on February 29th 01053 switch (mFeb29YearlyType) { 01054 case rFeb28: 01055 if (qday == 28 && qmonth == 2 && !QDate::leapYear(qyear)) 01056 match = true; 01057 break; 01058 case rMar1: 01059 if (qday == 1 && qmonth == 3 && !QDate::leapYear(qyear)) { 01060 qmonth = 2; 01061 match = true; 01062 } 01063 break; 01064 case rFeb29: 01065 break; 01066 } 01067 } 01068 01069 if (match) { 01070 // The day of the month matches. Calculate how many years ahead 01071 // this date is from the original event's date. 01072 int yearsAhead = (qyear - dStart.year()); 01073 if (yearsAhead % rFreq == 0) { 01074 // The date is in a year which recurs 01075 if (qd >= dStart 01076 && ((rDuration > 0 && qd <= endDate()) || 01077 (rDuration == 0 && qd <= rEndDateTime.date()) || 01078 rDuration == -1)) { 01079 // The date queried falls within the range of the event. 01080 int i = qmonth; 01081 for (QPtrListIterator<int> qlin(rYearNums); qlin.current(); ++qlin) { 01082 if (i == *qlin.current()) 01083 return true; 01084 } 01085 } 01086 } 01087 } 01088 return false; 01089 } 01090 01091 bool Recurrence::recursYearlyByPos(const QDate &qd) const 01092 { 01093 QDate dStart = mRecurStart.date(); 01094 int year = qd.year(); 01095 int month = qd.month(); 01096 int day = qd.day(); 01097 // calculate how many years ahead this date is from the original 01098 // event's date 01099 int yearsAhead = (year - dStart.year()); 01100 if (yearsAhead % rFreq == 0) { 01101 // The date is in a year which recurs 01102 if (qd >= dStart 01103 && ((rDuration > 0 && qd <= endDate()) || 01104 (rDuration == 0 && qd <= rEndDateTime.date()) || 01105 rDuration == -1)) { 01106 // The date queried falls within the range of the event. 01107 for (QPtrListIterator<int> qlin(rYearNums); qlin.current(); ++qlin) { 01108 if (month == *qlin.current()) { 01109 // The month recurs 01110 QValueList<int> days; 01111 getMonthlyPosDays(days, qd.daysInMonth(), QDate(year, month, 1).dayOfWeek()); 01112 for (QValueList<int>::Iterator it = days.begin(); it != days.end(); ++it) { 01113 if (*it == day) 01114 return true; 01115 } 01116 } 01117 } 01118 } 01119 } 01120 return false; 01121 } 01122 01123 bool Recurrence::recursYearlyByDay(const QDate &qd) const 01124 { 01125 QDate dStart = mRecurStart.date(); 01126 // calculate how many years ahead this date is from the original 01127 // event's date 01128 int yearsAhead = (qd.year() - dStart.year()); 01129 if (yearsAhead % rFreq == 0) { 01130 // The date is in a year which recurs 01131 if (qd >= dStart 01132 && ((rDuration > 0 && qd <= endDate()) || 01133 (rDuration == 0 && qd <= rEndDateTime.date()) || 01134 rDuration == -1)) { 01135 // The date queried falls within the range of the event. 01136 int i = qd.dayOfYear(); 01137 for (QPtrListIterator<int> qlin(rYearNums); qlin.current(); ++qlin) { 01138 if (i == *qlin.current()) 01139 return true; 01140 } 01141 } 01142 } 01143 return false; 01144 } 01145 01146 /* Get the date of the next recurrence, after the specified date. 01147 * If 'last' is non-null, '*last' is set to true if the next recurrence is the 01148 * last recurrence, else false. 01149 * Reply = date of next recurrence, or invalid date if none. 01150 */ 01151 QDate Recurrence::getNextDateNoTime(const QDate &preDate, bool *last) const 01152 { 01153 if (last) 01154 *last = false; 01155 QDate dStart = mRecurStart.date(); 01156 if (preDate < dStart) 01157 return dStart; 01158 QDate earliestDate = preDate.addDays(1); 01159 QDate nextDate; 01160 01161 switch (recurs) { 01162 case rDaily: 01163 nextDate = dStart.addDays((dStart.daysTo(preDate)/rFreq + 1) * rFreq); 01164 break; 01165 01166 case rWeekly: { 01167 QDate start = dStart.addDays(-((dStart.dayOfWeek() - rWeekStart + 7)%7)); // start of week for dStart 01168 int earliestDayOfWeek = earliestDate.dayOfWeek(); 01169 int weeksAhead = start.daysTo(earliestDate) / 7; 01170 int notThisWeek = weeksAhead % rFreq; // zero if this week is a recurring week 01171 weeksAhead -= notThisWeek; // latest week which recurred 01172 int weekday = 0; 01173 // First check for any remaining day this week, if this week is a recurring week 01174 if (!notThisWeek) 01175 weekday = getFirstDayInWeek(earliestDayOfWeek); 01176 // Check for a day in the next scheduled week 01177 if (!weekday) 01178 weekday = getFirstDayInWeek(rWeekStart) + rFreq*7; 01179 if (weekday) 01180 nextDate = start.addDays(weeksAhead*7 + weekday - 1); 01181 break; 01182 } 01183 case rMonthlyDay: 01184 case rMonthlyPos: { 01185 int startYear = dStart.year(); 01186 int startMonth = dStart.month(); // 1..12 01187 int earliestYear = earliestDate.year(); 01188 int monthsAhead = (earliestYear - startYear)*12 + earliestDate.month() - startMonth; 01189 int notThisMonth = monthsAhead % rFreq; // zero if this month is a recurring month 01190 monthsAhead -= notThisMonth; // latest month which recurred 01191 // Check for the first later day in the current month 01192 if (!notThisMonth) 01193 nextDate = getFirstDateInMonth(earliestDate); 01194 if (!nextDate.isValid()) { 01195 /* Check for a day in the next scheduled month. 01196 * The next check may fail if, for example, it's the 31st day of the month 01197 * or the 5th Monday, and the month being checked is February or a 30-day month, 01198 * so limit the number of iterations. 01199 */ 01200 QDate end = (rDuration >= 0) ? endDate() : MAX_DATE; 01201 int maxMonthsAhead = (end.year() - startYear)*12 + end.month() - startMonth; 01202 monthsAhead += rFreq; 01203 int maxIter = maxIterations(); 01204 for (int i = 0; i < maxIter && monthsAhead <= maxMonthsAhead; ++i) { 01205 int months = startMonth - 1 + monthsAhead; 01206 nextDate = getFirstDateInMonth(QDate(startYear + months/12, months%12 + 1, 1)); 01207 if (nextDate.isValid()) 01208 break; 01209 monthsAhead += rFreq; 01210 } 01211 } 01212 break; 01213 } 01214 case rYearlyMonth: 01215 case rYearlyPos: 01216 case rYearlyDay: { 01217 int startYear = dStart.year(); 01218 int yearsAhead = earliestDate.year() - startYear; 01219 int notThisYear = yearsAhead % rFreq; // zero if this year is a recurring year 01220 yearsAhead -= notThisYear; // latest year which recurred 01221 // Check for the first later date in the current year 01222 if (!notThisYear) 01223 nextDate = getFirstDateInYear(earliestDate); 01224 // Check for a date in the next scheduled year 01225 if (!nextDate.isValid()) { 01226 /* Check for a date in the next scheduled year. 01227 * The next check may fail if, for example, it's the 29th of February or the 5th 01228 * Monday, so limit the number of iterations. 01229 */ 01230 QDate end = (rDuration >= 0) ? endDate() : MAX_DATE; 01231 int maxYear = end.year(); 01232 startYear += yearsAhead + rFreq; 01233 int maxIter = maxIterations(); 01234 for (int i = 0; i < maxIter && startYear <= maxYear; ++i) { 01235 nextDate = getFirstDateInYear(QDate(startYear, 1, 1)); 01236 if (nextDate.isValid()) 01237 break; 01238 startYear += rFreq; 01239 } 01240 } 01241 break; 01242 } 01243 case rNone: 01244 default: 01245 return QDate(); 01246 } 01247 01248 if (rDuration >= 0 && nextDate.isValid()) { 01249 // Check that the date found is within the range of the recurrence 01250 QDate end = endDate(); 01251 if ( nextDate > end ) 01252 return QDate(); 01253 if (last && nextDate == end) 01254 *last = true; 01255 } 01256 01257 return nextDate; 01258 } 01259 01260 /* Get the date of the last previous recurrence, before the specified date. 01261 * Reply = date of previous recurrence, or invalid date if none. 01262 */ 01263 QDate Recurrence::getPreviousDateNoTime(const QDate &afterDate, bool *last) const 01264 { 01265 if (last) 01266 *last = false; 01267 QDate dStart = mRecurStart.date(); 01268 QDate latestDate = afterDate.addDays(-1); 01269 if (latestDate < dStart) 01270 return QDate(); 01271 QDate prevDate; 01272 01273 switch (recurs) { 01274 case rDaily: 01275 prevDate = dStart.addDays((dStart.daysTo(latestDate) / rFreq) * rFreq); 01276 break; 01277 01278 case rWeekly: { 01279 QDate start = dStart.addDays(-((dStart.dayOfWeek() - rWeekStart + 7)%7)); // start of week for dStart 01280 int latestDayOfWeek = latestDate.dayOfWeek(); 01281 int weeksAhead = start.daysTo(latestDate) / 7; 01282 int notThisWeek = weeksAhead % rFreq; // zero if this week is a recurring week 01283 weeksAhead -= notThisWeek; // latest week which recurred 01284 int weekday = 0; 01285 // First check for any previous day this week, if this week is a recurring week 01286 if (!notThisWeek) 01287 weekday = getLastDayInWeek(latestDayOfWeek); 01288 // Check for a day in the previous scheduled week 01289 if (!weekday) { 01290 if (!notThisWeek) 01291 weeksAhead -= rFreq; 01292 int weekEnd = (rWeekStart + 5)%7 + 1; 01293 weekday = getLastDayInWeek(weekEnd); 01294 } 01295 if (weekday) 01296 prevDate = start.addDays(weeksAhead*7 + weekday - 1); 01297 break; 01298 } 01299 case rMonthlyDay: 01300 case rMonthlyPos: { 01301 int startYear = dStart.year(); 01302 int startMonth = dStart.month(); // 1..12 01303 int latestYear = latestDate.year(); 01304 int monthsAhead = (latestYear - startYear)*12 + latestDate.month() - startMonth; 01305 int notThisMonth = monthsAhead % rFreq; // zero if this month is a recurring month 01306 monthsAhead -= notThisMonth; // latest month which recurred 01307 // Check for the last earlier day in the current month 01308 if (!notThisMonth) 01309 prevDate = getLastDateInMonth(latestDate); 01310 if (!prevDate.isValid()) { 01311 /* Check for a day in the previous scheduled month. 01312 * The next check may fail if, for example, it's the 31st day of the month 01313 * or the 5th Monday, and the month being checked is February or a 30-day month, 01314 * so limit the number of iterations. 01315 */ 01316 if (!notThisMonth) 01317 monthsAhead -= rFreq; 01318 int maxIter = maxIterations(); 01319 for (int i = 0; i < maxIter && monthsAhead >= 0; ++i) { 01320 int months = startMonth + monthsAhead; // get the zero-based month after the one that recurs 01321 prevDate = getLastDateInMonth(QDate(startYear + months/12, months%12 + 1, 1).addDays(-1)); 01322 if (prevDate.isValid()) 01323 break; 01324 monthsAhead -= rFreq; 01325 } 01326 } 01327 break; 01328 } 01329 case rYearlyMonth: 01330 case rYearlyPos: 01331 case rYearlyDay: { 01332 int startYear = dStart.year(); 01333 int yearsAhead = latestDate.year() - startYear; 01334 int notThisYear = yearsAhead % rFreq; // zero if this year is a recurring year 01335 yearsAhead -= notThisYear; // latest year which recurred 01336 // Check for the last earlier date in the current year 01337 if (!notThisYear) 01338 prevDate = getLastDateInYear(latestDate); 01339 if (!prevDate.isValid()) { 01340 /* Check for a date in the previous scheduled year. 01341 * The next check may fail if, for example, it's the 29th of February or the 5th 01342 * Monday, so limit the number of iterations. 01343 */ 01344 if (!notThisYear) 01345 yearsAhead -= rFreq; 01346 int maxIter = maxIterations(); 01347 for (int i = 0; i < maxIter && yearsAhead >= 0; ++i) { 01348 prevDate = getLastDateInYear(QDate(startYear + yearsAhead, 12, 31)); 01349 if (prevDate.isValid()) 01350 break; 01351 yearsAhead -= rFreq; 01352 } 01353 } 01354 break; 01355 } 01356 case rNone: 01357 default: 01358 return QDate(); 01359 } 01360 01361 if (prevDate.isValid()) { 01362 // Check that the date found is within the range of the recurrence 01363 if (prevDate < dStart) 01364 return QDate(); 01365 if (rDuration >= 0) { 01366 QDate end = endDate(); 01367 if (prevDate >= end) { 01368 if (last) 01369 *last = true; 01370 return end; 01371 } 01372 } 01373 } 01374 return prevDate; 01375 } 01376 01377 int Recurrence::maxIterations() const 01378 { 01379 /* Find the maximum number of iterations which may be needed to reach the 01380 * next actual occurrence of a monthly or yearly recurrence. 01381 * More than one iteration may be needed if, for example, it's the 29th February, 01382 * the 31st day of the month or the 5th Monday, and the month being checked is 01383 * February or a 30-day month. 01384 * The following recurrences may never occur: 01385 * - For rMonthlyDay: if the frequency is a whole number of years. 01386 * - For rMonthlyPos: if the frequency is an even whole number of years. 01387 * - For rYearlyDay, rYearlyMonth: if the frequeny is a multiple of 4 years. 01388 * - For rYearlyPos: if the frequency is an even number of years. 01389 * The maximum number of iterations needed, assuming that it does actually occur, 01390 * was found empirically. 01391 */ 01392 switch (recurs) { 01393 case rMonthlyDay: 01394 return (rFreq % 12) ? 6 : 8; 01395 01396 case rMonthlyPos: 01397 if (rFreq % 12 == 0) { 01398 // Some of these frequencies may never occur 01399 return (rFreq % 84 == 0) ? 364 // frequency = multiple of 7 years 01400 : (rFreq % 48 == 0) ? 7 // frequency = multiple of 4 years 01401 : (rFreq % 24 == 0) ? 14 : 28; // frequency = multiple of 2 or 1 year 01402 } 01403 // All other frequencies will occur sometime 01404 if (rFreq > 120) 01405 return 364; // frequencies of > 10 years will hit the date limit first 01406 switch (rFreq) { 01407 case 23: return 50; 01408 case 46: return 38; 01409 case 56: return 138; 01410 case 66: return 36; 01411 case 89: return 54; 01412 case 112: return 253; 01413 default: return 25; // most frequencies will need < 25 iterations 01414 } 01415 01416 case rYearlyMonth: 01417 case rYearlyDay: 01418 return 8; // only 29th Feb or day 366 will need more than one iteration 01419 01420 case rYearlyPos: 01421 if (rFreq % 7 == 0) 01422 return 364; // frequencies of a multiple of 7 years will hit the date limit first 01423 if (rFreq % 2 == 0) { 01424 // Some of these frequencies may never occur 01425 return (rFreq % 4 == 0) ? 7 : 14; // frequency = even number of years 01426 } 01427 return 28; 01428 } 01429 return 1; 01430 } 01431 01432 void Recurrence::setDailySub(short type, int freq, int duration) 01433 { 01434 mUseCachedEndDT = false; 01435 recurs = type; 01436 rFreq = freq; 01437 rDuration = duration; 01438 rMonthPositions.clear(); 01439 rMonthDays.clear(); 01440 rYearNums.clear(); 01441 if (type != rDaily) 01442 mFloats = false; // sub-daily types can't be floating 01443 01444 if (mParent) mParent->updated(); 01445 } 01446 01447 void Recurrence::setYearly_(short type, Feb29Type feb29type, int freq, int duration) 01448 { 01449 mUseCachedEndDT = false; 01450 recurs = type; 01451 if (mCompatVersion < 310 && type == rYearlyDay) { 01452 mCompatRecurs = rYearlyDay; 01453 recurs = rYearlyMonth; // convert old yearly-by-day to yearly-by-month 01454 feb29type = rMar1; // retain the same day number in the year 01455 } 01456 01457 mFeb29YearlyType = feb29type; 01458 rFreq = freq; 01459 rDuration = duration; 01460 if (type != rYearlyPos) 01461 rMonthPositions.clear(); 01462 rMonthDays.clear(); 01463 if (mParent) mParent->updated(); 01464 } 01465 01466 int Recurrence::recurCalc(PeriodFunc func, QDateTime &endtime) const 01467 { 01468 QDate enddate = endtime.date(); 01469 switch (func) { 01470 case END_DATE_AND_COUNT: 01471 if (rDuration < 0) { 01472 endtime = QDateTime(); 01473 return 0; // infinite recurrence 01474 } 01475 if (rDuration == 0) { 01476 endtime = rEndDateTime; 01477 func = COUNT_TO_DATE; 01478 } 01479 break; 01480 case COUNT_TO_DATE: 01481 // Count recurrences up to and including the specified date/time. 01482 if (endtime < mRecurStart) 01483 return 0; 01484 if (rDuration == 0 && endtime > rEndDateTime) 01485 enddate = rEndDateTime.date(); 01486 else if (!mFloats && mRecurStart.time() > endtime.time()) 01487 enddate = enddate.addDays(-1); 01488 break; 01489 case NEXT_AFTER_DATE: 01490 // Find next recurrence AFTER endtime 01491 if (endtime < mRecurStart) { 01492 endtime = mRecurStart; 01493 return 1; 01494 } 01495 if (rDuration == 0 && endtime >= rEndDateTime) { 01496 endtime = QDateTime(); 01497 return 0; 01498 } 01499 if (!mFloats && mRecurStart.time() > endtime.time()) 01500 enddate = enddate.addDays(-1); 01501 break; 01502 default: 01503 endtime = QDateTime(); 01504 return 0; 01505 } 01506 01507 int count = 0; // default = error 01508 bool timed = false; 01509 switch (recurs) { 01510 case rMinutely: 01511 timed = true; 01512 count = secondlyCalc(func, endtime, rFreq*60); 01513 break; 01514 case rHourly: 01515 timed = true; 01516 count = secondlyCalc(func, endtime, rFreq*3600); 01517 break; 01518 case rDaily: 01519 count = dailyCalc(func, enddate); 01520 break; 01521 case rWeekly: 01522 count = weeklyCalc(func, enddate); 01523 break; 01524 case rMonthlyPos: 01525 case rMonthlyDay: 01526 count = monthlyCalc(func, enddate); 01527 break; 01528 case rYearlyMonth: 01529 count = yearlyMonthCalc(func, enddate); 01530 break; 01531 case rYearlyPos: 01532 count = yearlyPosCalc(func, enddate); 01533 break; 01534 case rYearlyDay: 01535 count = yearlyDayCalc(func, enddate); 01536 break; 01537 default: 01538 break; 01539 } 01540 01541 switch (func) { 01542 case END_DATE_AND_COUNT: 01543 case NEXT_AFTER_DATE: 01544 if (count == 0) 01545 endtime = QDateTime(); 01546 else if (!timed) { 01547 endtime.setDate(enddate); 01548 endtime.setTime(mRecurStart.time()); 01549 } 01550 break; 01551 case COUNT_TO_DATE: 01552 break; 01553 } 01554 return count; 01555 } 01556 01557 int Recurrence::recurCalc(PeriodFunc func, QDate &enddate) const 01558 { 01559 QDateTime endtime(enddate, QTime(23,59,59)); 01560 switch (func) { 01561 case END_DATE_AND_COUNT: 01562 if (rDuration < 0) { 01563 enddate = QDate(); 01564 return 0; // infinite recurrence 01565 } 01566 if (rDuration == 0) { 01567 enddate = rEndDateTime.date(); 01568 func = COUNT_TO_DATE; 01569 } 01570 break; 01571 case COUNT_TO_DATE: 01572 // Count recurrences up to and including the specified date. 01573 if (enddate < mRecurStart.date()) 01574 return 0; 01575 if (rDuration == 0 && enddate > rEndDateTime.date()) { 01576 enddate = rEndDateTime.date(); 01577 endtime.setDate(enddate); 01578 } 01579 break; 01580 case NEXT_AFTER_DATE: 01581 if (enddate < mRecurStart.date()) { 01582 enddate = mRecurStart.date(); 01583 return 1; 01584 } 01585 if (rDuration == 0 && enddate >= rEndDateTime.date()) { 01586 enddate = QDate(); 01587 return 0; 01588 } 01589 break; 01590 default: 01591 enddate = QDate(); 01592 return 0; 01593 } 01594 01595 int count = 0; // default = error 01596 bool timed = false; 01597 switch (recurs) { 01598 case rMinutely: 01599 timed = true; 01600 count = secondlyCalc(func, endtime, rFreq*60); 01601 break; 01602 case rHourly: 01603 timed = true; 01604 count = secondlyCalc(func, endtime, rFreq*3600); 01605 break; 01606 case rDaily: 01607 count = dailyCalc(func, enddate); 01608 break; 01609 case rWeekly: 01610 count = weeklyCalc(func, enddate); 01611 break; 01612 case rMonthlyPos: 01613 case rMonthlyDay: 01614 count = monthlyCalc(func, enddate); 01615 break; 01616 case rYearlyMonth: 01617 count = yearlyMonthCalc(func, enddate); 01618 break; 01619 case rYearlyPos: 01620 count = yearlyPosCalc(func, enddate); 01621 break; 01622 case rYearlyDay: 01623 count = yearlyDayCalc(func, enddate); 01624 break; 01625 default: 01626 break; 01627 } 01628 01629 switch (func) { 01630 case END_DATE_AND_COUNT: 01631 case NEXT_AFTER_DATE: 01632 if (count == 0) 01633 endtime = QDate(); 01634 else if (timed) 01635 enddate = endtime.date(); 01636 break; 01637 case COUNT_TO_DATE: 01638 break; 01639 } 01640 return count; 01641 } 01642 01643 /* Find count and, depending on 'func', the end date/time of a secondly recurrence. 01644 * Reply = total number of occurrences up to 'endtime', or 0 if error. 01645 * If 'func' = END_DATE_AND_COUNT or NEXT_AFTER_DATE, 'endtime' is updated to the 01646 * recurrence end date/time. 01647 */ 01648 int Recurrence::secondlyCalc(PeriodFunc func, QDateTime &endtime, int freq) const 01649 { 01650 switch (func) { 01651 case END_DATE_AND_COUNT: 01652 endtime = mRecurStart.addSecs((rDuration - 1) * freq); 01653 return rDuration; 01654 case COUNT_TO_DATE: { 01655 int n = mRecurStart.secsTo(endtime)/freq + 1; 01656 if (rDuration > 0 && n > rDuration) 01657 return rDuration; 01658 return n; 01659 } 01660 case NEXT_AFTER_DATE: { 01661 int count = mRecurStart.secsTo(endtime) / freq + 2; 01662 if (rDuration > 0 && count > rDuration) 01663 return 0; 01664 endtime = mRecurStart.addSecs((count - 1)*freq); 01665 return count; 01666 } 01667 } 01668 return 0; 01669 } 01670 01671 /* Find count and, depending on 'func', the end date of a daily recurrence. 01672 * Reply = total number of occurrences up to 'enddate', or 0 if error. 01673 * If 'func' = END_DATE_AND_COUNT or NEXT_AFTER_DATE, 'enddate' is updated to the 01674 * recurrence end date. 01675 */ 01676 int Recurrence::dailyCalc(PeriodFunc func, QDate &enddate) const 01677 { 01678 QDate dStart = mRecurStart.date(); 01679 switch (func) { 01680 case END_DATE_AND_COUNT: 01681 enddate = dStart.addDays((rDuration - 1) * rFreq); 01682 return rDuration; 01683 case COUNT_TO_DATE: { 01684 int n = dStart.daysTo(enddate)/rFreq + 1; 01685 if (rDuration > 0 && n > rDuration) 01686 return rDuration; 01687 return n; 01688 } 01689 case NEXT_AFTER_DATE: { 01690 int count = dStart.daysTo(enddate) / rFreq + 2; 01691 if (rDuration > 0 && count > rDuration) 01692 return 0; 01693 enddate = dStart.addDays((count - 1)*rFreq); 01694 return count; 01695 } 01696 } 01697 return 0; 01698 } 01699 01700 /* Find count and, depending on 'func', the end date of a weekly recurrence. 01701 * Reply = total number of occurrences up to 'enddate', or 0 if error. 01702 * If 'func' = END_DATE_AND_COUNT or NEXT_AFTER_DATE, 'enddate' is updated to the 01703 * recurrence end date. 01704 */ 01705 int Recurrence::weeklyCalc(PeriodFunc func, QDate &enddate) const 01706 { 01707 int daysPerWeek = 0; 01708 for (int i = 0; i < 7; ++i) { 01709 if (rDays.testBit((uint)i)) 01710 ++daysPerWeek; 01711 } 01712 if (!daysPerWeek) 01713 return 0; // there are no days to recur on 01714 01715 switch (func) { 01716 case END_DATE_AND_COUNT: 01717 return weeklyCalcEndDate(enddate, daysPerWeek); 01718 case COUNT_TO_DATE: 01719 return weeklyCalcToDate(enddate, daysPerWeek); 01720 case NEXT_AFTER_DATE: 01721 return weeklyCalcNextAfter(enddate, daysPerWeek); 01722 } 01723 return 0; 01724 } 01725 01726 int Recurrence::weeklyCalcEndDate(QDate &enddate, int daysPerWeek) const 01727 { 01728 int startDayOfWeek = mRecurStart.date().dayOfWeek(); // 1..7 01729 int countGone = 0; 01730 int daysGone = 0; 01731 uint countTogo = rDuration; 01732 if (startDayOfWeek != rWeekStart) { 01733 // Check what remains of the start week 01734 for (int i = startDayOfWeek - 1; i != rWeekStart - 1; i = (i + 1) % 7) { 01735 ++daysGone; 01736 if (rDays.testBit((uint)i)) { 01737 ++countGone; 01738 if (--countTogo == 0) 01739 break; 01740 } 01741 } 01742 daysGone += 7 * (rFreq - 1); 01743 } 01744 if (countTogo) { 01745 // Skip the remaining whole weeks 01746 // Leave at least 1 recurrence remaining, in order to get its date 01747 int wholeWeeks = (countTogo - 1) / daysPerWeek; 01748 daysGone += wholeWeeks * 7 * rFreq; 01749 countGone += wholeWeeks * daysPerWeek; 01750 countTogo -= wholeWeeks * daysPerWeek; 01751 // Check the last week in the recurrence 01752 for (int i = rWeekStart - 1; ; i = (i + 1) % 7) { 01753 ++daysGone; 01754 if (rDays.testBit((uint)i)) { 01755 ++countGone; 01756 if (--countTogo == 0) 01757 break; 01758 } 01759 } 01760 } 01761 enddate = mRecurStart.date().addDays(daysGone); 01762 return countGone; 01763 } 01764 01765 int Recurrence::weeklyCalcToDate(const QDate &enddate, int daysPerWeek) const 01766 { 01767 QDate dStart = mRecurStart.date(); 01768 int startDayOfWeek = dStart.dayOfWeek(); // 1..7 01769 int countGone = 0; 01770 int daysGone = 0; 01771 int totalDays = dStart.daysTo(enddate) + 1; 01772 int countMax = (rDuration > 0) ? rDuration : INT_MAX; 01773 01774 if (startDayOfWeek != rWeekStart) { 01775 // Check what remains of the start week 01776 for (int i = startDayOfWeek - 1; i != rWeekStart - 1; i = (i + 1) % 7) { 01777 if (rDays.testBit((uint)i)) { 01778 if (++countGone >= countMax) 01779 return countMax; 01780 } 01781 if (++daysGone == totalDays) 01782 return countGone; 01783 } 01784 daysGone += 7 * (rFreq - 1); 01785 if (daysGone >= totalDays) 01786 return countGone; 01787 } 01788 // Skip the remaining whole weeks 01789 int wholeWeeks = (totalDays - daysGone) / 7; 01790 countGone += (wholeWeeks / rFreq) * daysPerWeek; 01791 if (countGone >= countMax) 01792 return countMax; 01793 daysGone += wholeWeeks * 7; 01794 if (daysGone >= totalDays // have we reached the end date? 01795 || wholeWeeks % rFreq) // is end week a recurrence week? 01796 return countGone; 01797 01798 // Check the last week in the recurrence 01799 for (int i = rWeekStart - 1; ; i = (i + 1) % 7) { 01800 if (rDays.testBit((uint)i)) { 01801 if (++countGone >= countMax) 01802 return countMax; 01803 } 01804 if (++daysGone == totalDays) 01805 return countGone; 01806 } 01807 return countGone; 01808 } 01809 01810 int Recurrence::weeklyCalcNextAfter(QDate &enddate, int daysPerWeek) const 01811 { 01812 QDate dStart = mRecurStart.date(); 01813 int startDayOfWeek = dStart.dayOfWeek(); // 1..7 01814 int totalDays = dStart.daysTo(enddate) + 1; 01815 uint countTogo = (rDuration > 0) ? rDuration : UINT_MAX; 01816 int countGone = 0; 01817 int daysGone = 0; 01818 int recurWeeks; 01819 01820 if (startDayOfWeek != rWeekStart) { 01821 // Check what remains of the start week 01822 for (int i = startDayOfWeek - 1; i != rWeekStart - 1; i = (i + 1) % 7) { 01823 ++daysGone; 01824 if (rDays.testBit((uint)i)) { 01825 ++countGone; 01826 if (daysGone > totalDays) 01827 goto ex; 01828 if (--countTogo == 0) 01829 return 0; 01830 } 01831 } 01832 daysGone += 7 * (rFreq - 1); 01833 } 01834 01835 // Skip the remaining whole weeks 01836 recurWeeks = (totalDays - daysGone) / (7 * rFreq); 01837 if (recurWeeks) { 01838 int n = recurWeeks * daysPerWeek; 01839 if (static_cast<uint>(n) > countTogo) 01840 return 0; // reached end of recurrence 01841 countGone += n; 01842 countTogo -= n; 01843 daysGone += recurWeeks * 7 * rFreq; 01844 } 01845 01846 // Check the last week or two in the recurrence 01847 for ( ; ; ) { 01848 for (int i = rWeekStart - 1; ; i = (i + 1) % 7) { 01849 ++daysGone; 01850 if (rDays.testBit((uint)i)) { 01851 ++countGone; 01852 if (daysGone > totalDays) 01853 goto ex; 01854 if (--countTogo == 0) 01855 return 0; 01856 } 01857 } 01858 daysGone += 7 * (rFreq - 1); 01859 } 01860 ex: 01861 enddate = dStart.addDays(daysGone); 01862 return countGone; 01863 } 01864 01865 /* Find count and, depending on 'func', the end date of a monthly recurrence. 01866 * Reply = total number of occurrences up to 'enddate', or 0 if error. 01867 * If 'func' = END_DATE_AND_COUNT or NEXT_AFTER_DATE, 'enddate' is updated to the 01868 * recurrence end date. 01869 */ 01870 class Recurrence::MonthlyData 01871 { 01872 public: 01873 const Recurrence *recurrence; 01874 int year; // current year 01875 int month; // current month 0..11 01876 int day; // current day of month 1..31 01877 bool varies; // true if recurring days vary between different months 01878 01879 private: 01880 QValueList<int> days28, days29, days30, days31; // recurring days in months of each length 01881 QValueList<int> *recurDays[4]; 01882 01883 public: 01884 MonthlyData(const Recurrence* r, const QDate &date) 01885 : recurrence(r), year(date.year()), month(date.month()-1), day(date.day()) 01886 { recurDays[0] = &days28; 01887 recurDays[1] = &days29; 01888 recurDays[2] = &days30; 01889 recurDays[3] = &days31; 01890 varies = (recurrence->doesRecur() == rMonthlyPos) 01891 ? true : recurrence->getMonthlyDayDays(days31, 31); 01892 } 01893 const QValueList<int>* dayList() const { 01894 if (!varies) 01895 return &days31; 01896 QDate startOfMonth(year, month + 1, 1); 01897 int daysInMonth = startOfMonth.daysInMonth(); 01898 QValueList<int>* days = recurDays[daysInMonth - 28]; 01899 if (recurrence->doesRecur() == rMonthlyPos) 01900 recurrence->getMonthlyPosDays(*days, daysInMonth, startOfMonth.dayOfWeek()); 01901 else if (days->isEmpty()) 01902 recurrence->getMonthlyDayDays(*days, daysInMonth); 01903 return days; 01904 } 01905 int yearMonth() const { return year*12 + month; } 01906 void addMonths(int diff) { month += diff; year += month / 12; month %= 12; } 01907 QDate date() const { return QDate(year, month + 1, day); } 01908 }; 01909 01910 int Recurrence::monthlyCalc(PeriodFunc func, QDate &enddate) const 01911 { 01912 if ( (recurs == rMonthlyPos && rMonthPositions.isEmpty() ) 01913 || ( recurs == rMonthlyDay && rMonthDays.isEmpty() ) ) 01914 return 0; 01915 01916 MonthlyData data(this, mRecurStart.date()); 01917 switch (func) { 01918 case END_DATE_AND_COUNT: 01919 return monthlyCalcEndDate(enddate, data); 01920 case COUNT_TO_DATE: 01921 return monthlyCalcToDate(enddate, data); 01922 case NEXT_AFTER_DATE: 01923 return monthlyCalcNextAfter(enddate, data); 01924 } 01925 return 0; 01926 } 01927 01928 int Recurrence::monthlyCalcEndDate(QDate &enddate, MonthlyData &data) const 01929 { 01930 uint countTogo = rDuration; 01931 int countGone = 0; 01932 QValueList<int>::ConstIterator it; 01933 const QValueList<int>* days = data.dayList(); 01934 01935 if (data.day > 1) { 01936 // Check what remains of the start month 01937 for (it = days->begin(); it != days->end(); ++it) { 01938 if (*it >= data.day) { 01939 ++countGone; 01940 if (--countTogo == 0) { 01941 data.day = *it; 01942 break; 01943 } 01944 } 01945 } 01946 if (countTogo) { 01947 data.day = 1; 01948 data.addMonths(rFreq); 01949 } 01950 } 01951 if (countTogo) { 01952 if (data.varies) { 01953 // The number of recurrence days varies from month to month, 01954 // so we need to check month by month. 01955 for ( ; ; ) { 01956 days = data.dayList(); 01957 uint n = days->count(); // number of recurrence days in this month 01958 if (n >= countTogo) 01959 break; 01960 countTogo -= n; 01961 countGone += n; 01962 data.addMonths(rFreq); 01963 } 01964 } else { 01965 // The number of recurrences is the same every month, 01966 // so skip the month-by-month check. 01967 // Skip the remaining whole months, but leave at least 01968 // 1 recurrence remaining, in order to get its date. 01969 int daysPerMonth = days->count(); 01970 int wholeMonths = (countTogo - 1) / daysPerMonth; 01971 data.addMonths(wholeMonths * rFreq); 01972 countGone += wholeMonths * daysPerMonth; 01973 countTogo -= wholeMonths * daysPerMonth; 01974 } 01975 if (countTogo) { 01976 // Check the last month in the recurrence 01977 for (it = days->begin(); it != days->end(); ++it) { 01978 ++countGone; 01979 if (--countTogo == 0) { 01980 data.day = *it; 01981 break; 01982 } 01983 } 01984 } 01985 } 01986 enddate = data.date(); 01987 return countGone; 01988 } 01989 01990 int Recurrence::monthlyCalcToDate(const QDate &enddate, MonthlyData &data) const 01991 { 01992 int countGone = 0; 01993 int countMax = (rDuration > 0) ? rDuration : INT_MAX; 01994 int endYear = enddate.year(); 01995 int endMonth = enddate.month() - 1; // zero-based 01996 int endDay = enddate.day(); 01997 int endYearMonth = endYear*12 + endMonth; 01998 QValueList<int>::ConstIterator it; 01999 const QValueList<int>* days = data.dayList(); 02000 02001 if (data.day > 1) { 02002 // Check what remains of the start month 02003 for (it = days->begin(); it != days->end(); ++it) { 02004 if (*it >= data.day) { 02005 if (data.yearMonth() == endYearMonth && *it > endDay) 02006 return countGone; 02007 if (++countGone >= countMax) 02008 return countMax; 02009 } 02010 } 02011 data.day = 1; 02012 data.addMonths(rFreq); 02013 } 02014 02015 if (data.varies) { 02016 // The number of recurrence days varies from month to month, 02017 // so we need to check month by month. 02018 while (data.yearMonth() < endYearMonth) { 02019 countGone += data.dayList()->count(); 02020 if (countGone >= countMax) 02021 return countMax; 02022 data.addMonths(rFreq); 02023 } 02024 days = data.dayList(); 02025 } else { 02026 // The number of recurrences is the same every month, 02027 // so skip the month-by-month check. 02028 // Skip the remaining whole months. 02029 int daysPerMonth = days->count(); 02030 int wholeMonths = endYearMonth - data.yearMonth(); 02031 countGone += (wholeMonths / rFreq) * daysPerMonth; 02032 if (countGone >= countMax) 02033 return countMax; 02034 if (wholeMonths % rFreq) 02035 return countGone; // end year isn't a recurrence year 02036 data.year = endYear; 02037 data.month = endMonth; 02038 } 02039 02040 // Check the last month in the recurrence 02041 for (it = days->begin(); it != days->end(); ++it) { 02042 if (*it > endDay) 02043 return countGone; 02044 if (++countGone >= countMax) 02045 return countMax; 02046 } 02047 return countGone; 02048 } 02049 02050 int Recurrence::monthlyCalcNextAfter(QDate &enddate, MonthlyData &data) const 02051 { 02052 uint countTogo = (rDuration > 0) ? rDuration : UINT_MAX; 02053 int countGone = 0; 02054 int endYear = enddate.year(); 02055 int endDay = enddate.day(); 02056 int endYearMonth = endYear*12 + enddate.month() - 1; 02057 QValueList<int>::ConstIterator it; 02058 const QValueList<int>* days = data.dayList(); 02059 02060 if (data.day > 1) { 02061 // Check what remains of the start month 02062 for (it = days->begin(); it != days->end(); ++it) { 02063 if (*it >= data.day) { 02064 ++countGone; 02065 if (data.yearMonth() == endYearMonth && *it > endDay) { 02066 data.day = *it; 02067 goto ex; 02068 } 02069 if (--countTogo == 0) 02070 return 0; 02071 } 02072 } 02073 data.day = 1; 02074 data.addMonths(rFreq); 02075 } 02076 02077 if (data.varies) { 02078 // The number of recurrence days varies from month to month, 02079 // so we need to check month by month. 02080 while (data.yearMonth() <= endYearMonth) { 02081 days = data.dayList(); 02082 uint n = days->count(); // number of recurrence days in this month 02083 if (data.yearMonth() == endYearMonth && days->last() > endDay) 02084 break; 02085 if (n >= countTogo) 02086 return 0; 02087 countGone += n; 02088 countTogo -= n; 02089 data.addMonths(rFreq); 02090 } 02091 days = data.dayList(); 02092 } else { 02093 // The number of recurrences is the same every month, 02094 // so skip the month-by-month check. 02095 // Skip the remaining whole months to at least end year/month. 02096 int daysPerMonth = days->count(); 02097 int elapsed = endYearMonth - data.yearMonth(); 02098 int recurMonths = (elapsed + rFreq - 1) / rFreq; 02099 if (elapsed % rFreq == 0 && days->last() <= endDay) 02100 ++recurMonths; // required month is after endYearMonth 02101 if (recurMonths) { 02102 int n = recurMonths * daysPerMonth; 02103 if (static_cast<uint>(n) > countTogo) 02104 return 0; // reached end of recurrence 02105 countTogo -= n; 02106 countGone += n; 02107 data.addMonths(recurMonths * rFreq); 02108 } 02109 } 02110 02111 // Check the last month in the recurrence 02112 for (it = days->begin(); it != days->end(); ++it) { 02113 ++countGone; 02114 if (data.yearMonth() > endYearMonth || *it > endDay) { 02115 data.day = *it; 02116 break; 02117 } 02118 if (--countTogo == 0) 02119 return 0; 02120 } 02121 ex: 02122 enddate = data.date(); 02123 return countGone; 02124 } 02125 02126 02127 /* Find count and, depending on 'func', the end date of an annual recurrence by date. 02128 * Reply = total number of occurrences up to 'enddate', or 0 if error. 02129 * If 'func' = END_DATE_AND_COUNT or NEXT_AFTER_DATE, 'enddate' is updated to the 02130 * recurrence end date. 02131 * 02132 * WARNING: These methods currently do not cater for day of month < -28 02133 * (which would need different months to be treated differently). 02134 */ 02135 class Recurrence::YearlyMonthData 02136 { 02137 public: 02138 const Recurrence *recurrence; 02139 int year; // current year 02140 int month; // current month 1..12 02141 int day; // current day of month 1..31 02142 bool leapyear; // true if February 29th recurs and current year is a leap year 02143 bool feb29; // true if February 29th recurs 02144 02145 private: 02146 QValueList<int> months; // recurring months in non-leap years 1..12 02147 QValueList<int> leapMonths; // recurring months in leap years 1..12 02148 02149 public: 02150 YearlyMonthData(const Recurrence* r, const QDate &date, int d) 02151 : recurrence(r), year(date.year()), month(date.month()), day(d ? d : date.day()) 02152 { feb29 = recurrence->getYearlyMonthMonths(day, months, leapMonths); 02153 leapyear = feb29 && QDate::leapYear(year); 02154 } 02155 const QValueList<int>* monthList() const 02156 { return leapyear ? &leapMonths : &months; } 02157 const QValueList<int>* leapMonthList() const { return &leapMonths; } 02158 QDate date() const { if (day > 0) return QDate(year, month, day); 02159 return QDate(year, month, QDate(year, month, 1).daysInMonth() + day + 1); 02160 } 02161 }; 02162 02163 int Recurrence::yearlyMonthCalc(PeriodFunc func, QDate &enddate) const 02164 { 02165 if (rYearNums.isEmpty()) 02166 return 0; 02167 YearlyMonthData data(this, mRecurStart.date(), (rMonthDays.count() ? *rMonthDays.getFirst() : 0)); 02168 switch (func) { 02169 case END_DATE_AND_COUNT: 02170 return yearlyMonthCalcEndDate(enddate, data); 02171 case COUNT_TO_DATE: 02172 return yearlyMonthCalcToDate(enddate, data); 02173 case NEXT_AFTER_DATE: 02174 return yearlyMonthCalcNextAfter(enddate, data); 02175 } 02176 return 0; 02177 } 02178 02179 // Find total count and end date of an annual recurrence by date. 02180 // Reply = total number of occurrences. 02181 int Recurrence::yearlyMonthCalcEndDate(QDate &enddate, YearlyMonthData &data) const 02182 { 02183 uint countTogo = rDuration; 02184 int countGone = 0; 02185 QValueList<int>::ConstIterator it; 02186 const QValueList<int>* mons = data.monthList(); // get recurring months for this year 02187 02188 if (data.month > 1) { 02189 // Check what remains of the start year 02190 for (it = mons->begin(); it != mons->end(); ++it) { 02191 if (*it >= data.month) { 02192 ++countGone; 02193 if (--countTogo == 0) { 02194 data.month = *it; 02195 if (data.month == 2 && data.feb29 && !data.leapyear) { 02196 // The recurrence should end on February 29th, but it's a non-leap year 02197 switch (mFeb29YearlyType) { 02198 case rFeb28: 02199 data.day = 28; 02200 break; 02201 case rMar1: 02202 data.month = 3; 02203 data.day = 1; 02204 break; 02205 case rFeb29: 02206 break; 02207 } 02208 } 02209 break; 02210 } 02211 } 02212 } 02213 if (countTogo) { 02214 data.month = 1; 02215 data.year += rFreq; 02216 } 02217 } 02218 if (countTogo) { 02219 if (data.feb29 && mFeb29YearlyType == rFeb29) { 02220 // The number of recurrences is different on leap years, 02221 // so check year-by-year. 02222 for ( ; ; ) { 02223 mons = data.monthList(); 02224 uint n = mons->count(); 02225 if (n >= countTogo) 02226 break; 02227 countTogo -= n; 02228 countGone += n; 02229 data.year += rFreq; 02230 } 02231 } else { 02232 // The number of recurrences is the same every year, 02233 // so skip the year-by-year check. 02234 // Skip the remaining whole years, but leave at least 02235 // 1 recurrence remaining, in order to get its date. 02236 int monthsPerYear = mons->count(); 02237 int wholeYears = (countTogo - 1) / monthsPerYear; 02238 data.year += wholeYears * rFreq; 02239 countGone += wholeYears * monthsPerYear; 02240 countTogo -= wholeYears * monthsPerYear; 02241 } 02242 if (countTogo) { 02243 // Check the last year in the recurrence 02244 for (it = mons->begin(); it != mons->end(); ++it) { 02245 ++countGone; 02246 if (--countTogo == 0) { 02247 data.month = *it; 02248 if (data.month == 2 && data.feb29 && !QDate::leapYear(data.year)) { 02249 // The recurrence should end on February 29th, but it's a non-leap year 02250 switch (mFeb29YearlyType) { 02251 case rFeb28: 02252 data.day = 28; 02253 break; 02254 case rMar1: 02255 data.month = 3; 02256 data.day = 1; 02257 break; 02258 case rFeb29: 02259 break; 02260 } 02261 } 02262 break; 02263 } 02264 } 02265 } 02266 } 02267 enddate = data.date(); 02268 return countGone; 02269 } 02270 02271 // Find count of an annual recurrence by date. 02272 // Reply = total number of occurrences up to 'enddate'. 02273 int Recurrence::yearlyMonthCalcToDate(const QDate &enddate, YearlyMonthData &data) const 02274 { 02275 int countGone = 0; 02276 int countMax = (rDuration > 0) ? rDuration : INT_MAX; 02277 int endYear = enddate.year(); 02278 int endMonth = enddate.month(); 02279 int endDay = enddate.day(); 02280 if (data.day < 0) { 02281 // The end day of the month is relative to the end of the month. 02282 if (endDay < enddate.daysInMonth() + data.day + 1) { 02283 if (--endMonth == 0) { 02284 endMonth = 12; 02285 --endYear; 02286 } 02287 } 02288 } 02289 else if (endDay < data.day) { 02290 /* The end day of the month is earlier than the recurrence day of the month. 02291 * If Feb 29th recurs and: 02292 * 1) it recurs on Feb 28th in non-leap years, don't adjust the end month 02293 * if enddate is Feb 28th on a non-leap year. 02294 * 2) it recurs on Mar 1st in non-leap years, allow the end month to be 02295 * adjusted to February, to simplify calculations. 02296 */ 02297 if (data.feb29 && !QDate::leapYear(endYear) 02298 && mFeb29YearlyType == rFeb28 && endDay == 28 && endMonth == 2) { 02299 } 02300 else if (--endMonth == 0) { 02301 endMonth = 12; 02302 --endYear; 02303 } 02304 } 02305 QValueList<int>::ConstIterator it; 02306 const QValueList<int>* mons = data.monthList(); 02307 02308 if (data.month > 1) { 02309 // Check what remains of the start year 02310 for (it = mons->begin(); it != mons->end(); ++it) { 02311 if (*it >= data.month) { 02312 if (data.year == endYear && *it > endMonth) 02313 return countGone; 02314 if (++countGone >= countMax) 02315 return countMax; 02316 } 02317 } 02318 data.month = 1; 02319 data.year += rFreq; 02320 } 02321 if (data.feb29 && mFeb29YearlyType == rFeb29) { 02322 // The number of recurrences is different on leap years, 02323 // so check year-by-year. 02324 while (data.year < endYear) { 02325 countGone += data.monthList()->count(); 02326 if (countGone >= countMax) 02327 return countMax; 02328 data.year += rFreq; 02329 } 02330 mons = data.monthList(); 02331 } else { 02332 // The number of recurrences is the same every year, 02333 // so skip the year-by-year check. 02334 // Skip the remaining whole years. 02335 int monthsPerYear = mons->count(); 02336 int wholeYears = endYear - data.year; 02337 countGone += (wholeYears / rFreq) * monthsPerYear; 02338 if (countGone >= countMax) 02339 return countMax; 02340 if (wholeYears % rFreq) 02341 return countGone; // end year isn't a recurrence year 02342 data.year = endYear; 02343 } 02344 02345 // Check the last year in the recurrence 02346 for (it = mons->begin(); it != mons->end(); ++it) { 02347 if (*it > endMonth) 02348 return countGone; 02349 if (++countGone >= countMax) 02350 return countMax; 02351 } 02352 return countGone; 02353 } 02354 02355 // Find count and date of first recurrence after 'enddate' of an annual recurrence by date. 02356 // Reply = total number of occurrences up to 'enddate'. 02357 int Recurrence::yearlyMonthCalcNextAfter(QDate &enddate, YearlyMonthData &data) const 02358 { 02359 uint countTogo = (rDuration > 0) ? rDuration : UINT_MAX; 02360 int countGone = 0; 02361 int endYear = enddate.year(); 02362 int endMonth = enddate.month(); 02363 int endDay = enddate.day(); 02364 bool mar1TooEarly = false; 02365 bool feb28ok = false; 02366 if (data.day < 0) { 02367 // The end day of the month is relative to the end of the month. 02368 if (endDay < enddate.daysInMonth() + data.day + 1) { 02369 if (--endMonth == 0) { 02370 endMonth = 12; 02371 --endYear; 02372 } 02373 } 02374 } 02375 else if (endDay < data.day) { 02376 if (data.feb29 && mFeb29YearlyType == rMar1 && endMonth == 3) 02377 mar1TooEarly = true; 02378 if (data.feb29 && mFeb29YearlyType == rFeb28 && endMonth == 2 && endDay == 28) 02379 feb28ok = true; 02380 else if (--endMonth == 0) { 02381 endMonth = 12; 02382 --endYear; 02383 } 02384 } 02385 QValueList<int>::ConstIterator it; 02386 const QValueList<int>* mons = data.monthList(); 02387 02388 if (data.month > 1) { 02389 // Check what remains of the start year 02390 for (it = mons->begin(); it != mons->end(); ++it) { 02391 if (*it >= data.month) { 02392 ++countGone; 02393 if (data.year == endYear 02394 && ( *it > endMonth && (*it > 3 || !mar1TooEarly) 02395 || *it == 2 && feb28ok && data.leapyear)) { 02396 if (*it == 2 && data.feb29 && !data.leapyear) { 02397 // The next recurrence should be on February 29th, but it's a non-leap year 02398 switch (mFeb29YearlyType) { 02399 case rFeb28: 02400 data.month = 2; 02401 data.day = 28; 02402 break; 02403 case rMar1: 02404 data.month = 3; 02405 data.day = 1; 02406 break; 02407 case rFeb29: // impossible in this context! 02408 break; 02409 } 02410 } 02411 else 02412 data.month = *it; 02413 goto ex; 02414 } 02415 if (--countTogo == 0) 02416 return 0; 02417 } 02418 } 02419 data.month = 1; 02420 data.year += rFreq; 02421 } 02422 02423 if (data.feb29 && mFeb29YearlyType == rFeb29) { 02424 // The number of recurrences is different on leap years, 02425 // so check year-by-year. 02426 while (data.year <= endYear) { 02427 mons = data.monthList(); 02428 if (data.year == endYear && mons->last() > endMonth) 02429 break; 02430 uint n = mons->count(); 02431 if (n >= countTogo) 02432 break; 02433 countTogo -= n; 02434 countGone += n; 02435 data.year += rFreq; 02436 } 02437 mons = data.monthList(); 02438 } else { 02439 // The number of recurrences is the same every year, 02440 // so skip the year-by-year check. 02441 // Skip the remaining whole years to at least endYear. 02442 int monthsPerYear = mons->count(); 02443 int recurYears = (endYear - data.year + rFreq - 1) / rFreq; 02444 if ((endYear - data.year)%rFreq == 0 02445 && mons->last() <= endMonth) 02446 ++recurYears; // required year is after endYear 02447 if (recurYears) { 02448 int n = recurYears * monthsPerYear; 02449 if (static_cast<uint>(n) > countTogo) 02450 return 0; // reached end of recurrence 02451 countTogo -= n; 02452 countGone += n; 02453 data.year += recurYears * rFreq; 02454 } 02455 } 02456 02457 // Check the last year in the recurrence 02458 for (it = mons->begin(); it != mons->end(); ++it) { 02459 ++countGone; 02460 if (data.year > endYear 02461 || ( *it > endMonth && (*it > 3 || !mar1TooEarly) 02462 || *it == 2 && feb28ok && QDate::leapYear(data.year))) { 02463 if (*it == 2 && data.feb29 && !QDate::leapYear(data.year)) { 02464 // The next recurrence should be on February 29th, but it's a non-leap year 02465 switch (mFeb29YearlyType) { 02466 case rFeb28: 02467 data.month = 2; 02468 data.day = 28; 02469 break; 02470 case rMar1: 02471 data.month = 3; 02472 data.day = 1; 02473 break; 02474 case rFeb29: // impossible in this context! 02475 break; 02476 } 02477 } 02478 else 02479 data.month = *it; 02480 break; 02481 } 02482 if (--countTogo == 0) 02483 return 0; 02484 } 02485 ex: 02486 enddate = data.date(); 02487 return countGone; 02488 } 02489 02490 02491 /* Find count and, depending on 'func', the end date of an annual recurrence by date. 02492 * Reply = total number of occurrences up to 'enddate', or 0 if error. 02493 * If 'func' = END_DATE_AND_COUNT or NEXT_AFTER_DATE, 'enddate' is updated to the 02494 * recurrence end date. 02495 */ 02496 class Recurrence::YearlyPosData 02497 { 02498 public: 02499 const Recurrence *recurrence; 02500 int year; // current year 02501 int month; // current month 1..12 02502 int day; // current day of month 1..31 02503 int daysPerMonth; // number of days which recur each month, or -1 if variable 02504 int count; // number of days which recur each year, or -1 if variable 02505 bool varies; // true if number of days varies from year to year 02506 02507 private: 02508 mutable QValueList<int> days; 02509 02510 public: 02511 YearlyPosData(const Recurrence* r, const QDate &date) 02512 : recurrence(r), year(date.year()), month(date.month()), day(date.day()), count(-1) 02513 { if ((daysPerMonth = r->countMonthlyPosDays()) > 0) 02514 count = daysPerMonth * r->yearNums().count(); 02515 varies = (daysPerMonth < 0); 02516 } 02517 const QValueList<int>* dayList() const { 02518 QDate startOfMonth(year, month, 1); 02519 recurrence->getMonthlyPosDays(days, startOfMonth.daysInMonth(), startOfMonth.dayOfWeek()); 02520 return &days; 02521 } 02522 int yearMonth() const { return year*12 + month - 1; } 02523 void addMonths(int diff) { month += diff - 1; year += month / 12; month = month % 12 + 1; } 02524 QDate date() const { return QDate(year, month, day); } 02525 }; 02526 02527 int Recurrence::yearlyPosCalc(PeriodFunc func, QDate &enddate) const 02528 { 02529 if (rYearNums.isEmpty() || rMonthPositions.isEmpty()) 02530 return 0; 02531 YearlyPosData data(this, mRecurStart.date()); 02532 switch (func) { 02533 case END_DATE_AND_COUNT: 02534 return yearlyPosCalcEndDate(enddate, data); 02535 case COUNT_TO_DATE: 02536 return yearlyPosCalcToDate(enddate, data); 02537 case NEXT_AFTER_DATE: 02538 return yearlyPosCalcNextAfter(enddate, data); 02539 } 02540 return 0; 02541 } 02542 02543 int Recurrence::yearlyPosCalcEndDate(QDate &enddate, YearlyPosData &data) const 02544 { 02545 uint countTogo = rDuration; 02546 int countGone = 0; 02547 QValueList<int>::ConstIterator id; 02548 const QValueList<int>* days; 02549 02550 if (data.month > 1 || data.day > 1) { 02551 // Check what remains of the start year 02552 for (QPtrListIterator<int> im(rYearNums); im.current(); ++im) { 02553 if (*im.current() >= data.month) { 02554 // Check what remains of the start month 02555 if (data.day > 1 || data.varies 02556 || static_cast<uint>(data.daysPerMonth) >= countTogo) { 02557 data.month = *im.current(); 02558 days = data.dayList(); 02559 for (id = days->begin(); id != days->end(); ++id) { 02560 if (*id >= data.day) { 02561 ++countGone; 02562 if (--countTogo == 0) { 02563 data.month = *im.current(); 02564 data.day = *id; 02565 goto ex; 02566 } 02567 } 02568 } 02569 data.day = 1; 02570 } else { 02571 // The number of days per month is constant, so skip 02572 // the whole month. 02573 countTogo -= data.daysPerMonth; 02574 countGone += data.daysPerMonth; 02575 } 02576 } 02577 } 02578 data.month = 1; 02579 data.year += rFreq; 02580 } 02581 02582 if (data.varies) { 02583 // The number of recurrences varies from year to year. 02584 for ( ; ; ) { 02585 for (QPtrListIterator<int> im(rYearNums); im.current(); ++im) { 02586 data.month = *im.current(); 02587 days = data.dayList(); 02588 int n = days->count(); 02589 if (static_cast<uint>(n) >= countTogo) { 02590 // Check the last month in the recurrence 02591 for (id = days->begin(); id != days->end(); ++id) { 02592 ++countGone; 02593 if (--countTogo == 0) { 02594 data.day = *id; 02595 goto ex; 02596 } 02597 } 02598 } 02599 countTogo -= n; 02600 countGone += n; 02601 } 02602 data.year += rFreq; 02603 } 02604 } else { 02605 // The number of recurrences is the same every year, 02606 // so skip the year-by-year check. 02607 // Skip the remaining whole years, but leave at least 02608 // 1 recurrence remaining, in order to get its date. 02609 int wholeYears = (countTogo - 1) / data.count; 02610 data.year += wholeYears * rFreq; 02611 countGone += wholeYears * data.count; 02612 countTogo -= wholeYears * data.count; 02613 02614 // Check the last year in the recurrence. 02615 for (QPtrListIterator<int> im(rYearNums); im.current(); ++im) { 02616 if (static_cast<uint>(data.daysPerMonth) >= countTogo) { 02617 // Check the last month in the recurrence 02618 data.month = *im.current(); 02619 days = data.dayList(); 02620 for (id = days->begin(); id != days->end(); ++id) { 02621 ++countGone; 02622 if (--countTogo == 0) { 02623 data.day = *id; 02624 goto ex; 02625 } 02626 } 02627 } 02628 countTogo -= data.daysPerMonth; 02629 countGone += data.daysPerMonth; 02630 } 02631 data.year += rFreq; 02632 } 02633 ex: 02634 enddate = data.date(); 02635 return countGone; 02636 } 02637 02638 int Recurrence::yearlyPosCalcToDate(const QDate &enddate, YearlyPosData &data) const 02639 { 02640 int countGone = 0; 02641 int countMax = (rDuration > 0) ? rDuration : INT_MAX; 02642 int endYear = enddate.year(); 02643 int endMonth = enddate.month(); 02644 int endDay = enddate.day(); 02645 if (endDay < data.day && --endMonth == 0) { 02646 endMonth = 12; 02647 --endYear; 02648 } 02649 int endYearMonth = endYear*12 + endMonth; 02650 QValueList<int>::ConstIterator id; 02651 const QValueList<int>* days; 02652 02653 if (data.month > 1 || data.day > 1) { 02654 // Check what remains of the start year 02655 for (QPtrListIterator<int> im(rYearNums); im.current(); ++im) { 02656 if (*im.current() >= data.month) { 02657 data.month = *im.current(); 02658 if (data.yearMonth() > endYearMonth) 02659 return countGone; 02660 // Check what remains of the start month 02661 bool lastMonth = (data.yearMonth() == endYearMonth); 02662 if (lastMonth || data.day > 1 || data.varies) { 02663 days = data.dayList(); 02664 if (lastMonth || data.day > 1) { 02665 for (id = days->begin(); id != days->end(); ++id) { 02666 if (*id >= data.day) { 02667 if (lastMonth && *id > endDay) 02668 return countGone; 02669 if (++countGone >= countMax) 02670 return countMax; 02671 } 02672 } 02673 } else { 02674 countGone += days->count(); 02675 if (countGone >= countMax) 02676 return countMax; 02677 } 02678 data.day = 1; 02679 } else { 02680 // The number of days per month is constant, so skip 02681 // the whole month. 02682 countGone += data.daysPerMonth; 02683 if (countGone >= countMax) 02684 return countMax; 02685 } 02686 } 02687 } 02688 data.month = 1; 02689 data.year += rFreq; 02690 } 02691 02692 if (data.varies) { 02693 // The number of recurrences varies from year to year. 02694 for ( ; ; ) { 02695 for (QPtrListIterator<int> im(rYearNums); im.current(); ++im) { 02696 data.month = *im.current(); 02697 days = data.dayList(); 02698 if (data.yearMonth() >= endYearMonth) { 02699 if (data.yearMonth() > endYearMonth) 02700 return countGone; 02701 // Check the last month in the recurrence 02702 for (id = days->begin(); id != days->end(); ++id) { 02703 if (*id > endDay) 02704 return countGone; 02705 if (++countGone >= countMax) 02706 return countMax; 02707 } 02708 } else { 02709 countGone += days->count(); 02710 if (countGone >= countMax) 02711 return countMax; 02712 } 02713 } 02714 data.year += rFreq; 02715 } 02716 } else { 02717 // The number of recurrences is the same every year, 02718 // so skip the year-by-year check. 02719 // Skip the remaining whole years, but leave at least 02720 // 1 recurrence remaining, in order to get its date. 02721 int wholeYears = endYear - data.year; 02722 countGone += (wholeYears / rFreq) * data.count; 02723 if (countGone >= countMax) 02724 return countMax; 02725 if (wholeYears % rFreq) 02726 return countGone; // end year isn't a recurrence year 02727 data.year = endYear; 02728 02729 // Check the last year in the recurrence. 02730 for (QPtrListIterator<int> im(rYearNums); im.current(); ++im) { 02731 data.month = *im.current(); 02732 if (data.month >= endMonth) { 02733 if (data.month > endMonth) 02734 return countGone; 02735 // Check the last month in the recurrence 02736 days = data.dayList(); 02737 for (id = days->begin(); id != days->end(); ++id) { 02738 if (*id > endDay) 02739 return countGone; 02740 if (++countGone >= countMax) 02741 return countMax; 02742 } 02743 } else { 02744 countGone += data.daysPerMonth; 02745 if (countGone >= countMax) 02746 return countMax; 02747 } 02748 } 02749 } 02750 return countGone; 02751 } 02752 02753 int Recurrence::yearlyPosCalcNextAfter(QDate &enddate, YearlyPosData &data) const 02754 { 02755 uint countTogo = (rDuration > 0) ? rDuration : UINT_MAX; 02756 int countGone = 0; 02757 int endYear = enddate.year(); 02758 int endMonth = enddate.month(); 02759 int endDay = enddate.day(); 02760 if (endDay < data.day && --endMonth == 0) { 02761 endMonth = 12; 02762 --endYear; 02763 } 02764 int endYearMonth = endYear*12 + endMonth; 02765 QValueList<int>::ConstIterator id; 02766 const QValueList<int>* days; 02767 02768 if (data.varies) { 02769 // The number of recurrences varies from year to year. 02770 for ( ; ; ) { 02771 // Check the next year 02772 for (QPtrListIterator<int> im(rYearNums); im.current(); ++im) { 02773 if (*im.current() >= data.month) { 02774 // Check the next month 02775 data.month = *im.current(); 02776 int ended = data.yearMonth() - endYearMonth; 02777 days = data.dayList(); 02778 if (ended >= 0 || data.day > 1) { 02779 // This is the start or end month, so check each day 02780 for (id = days->begin(); id != days->end(); ++id) { 02781 if (*id >= data.day) { 02782 ++countGone; 02783 if (ended > 0 || (ended == 0 && *id > endDay)) { 02784 data.day = *id; 02785 goto ex; 02786 } 02787 if (--countTogo == 0) 02788 return 0; 02789 } 02790 } 02791 } else { 02792 // Skip the whole month 02793 uint n = days->count(); 02794 if (n >= countTogo) 02795 return 0; 02796 countGone += n; 02797 } 02798 data.day = 1; // we've checked the start month now 02799 } 02800 } 02801 data.month = 1; // we've checked the start year now 02802 data.year += rFreq; 02803 } 02804 } else { 02805 // The number of recurrences is the same every year. 02806 if (data.month > 1 || data.day > 1) { 02807 // Check what remains of the start year 02808 for (QPtrListIterator<int> im(rYearNums); im.current(); ++im) { 02809 if (*im.current() >= data.month) { 02810 // Check what remains of the start month 02811 data.month = *im.current(); 02812 int ended = data.yearMonth() - endYearMonth; 02813 if (ended >= 0 || data.day > 1) { 02814 // This is the start or end month, so check each day 02815 days = data.dayList(); 02816 for (id = days->begin(); id != days->end(); ++id) { 02817 if (*id >= data.day) { 02818 ++countGone; 02819 if (ended > 0 || (ended == 0 && *id > endDay)) { 02820 data.day = *id; 02821 goto ex; 02822 } 02823 if (--countTogo == 0) 02824 return 0; 02825 } 02826 } 02827 data.day = 1; // we've checked the start month now 02828 } else { 02829 // Skip the whole month. 02830 if (static_cast<uint>(data.daysPerMonth) >= countTogo) 02831 return 0; 02832 countGone += data.daysPerMonth; 02833 } 02834 } 02835 } 02836 data.year += rFreq; 02837 } 02838 // Skip the remaining whole years to at least endYear. 02839 int recurYears = (endYear - data.year + rFreq - 1) / rFreq; 02840 if ((endYear - data.year)%rFreq == 0 02841 && *rYearNums.getLast() <= endMonth) 02842 ++recurYears; // required year is after endYear 02843 if (recurYears) { 02844 int n = recurYears * data.count; 02845 if (static_cast<uint>(n) > countTogo) 02846 return 0; // reached end of recurrence 02847 countTogo -= n; 02848 countGone += n; 02849 data.year += recurYears * rFreq; 02850 } 02851 02852 // Check the last year in the recurrence 02853 for (QPtrListIterator<int> im(rYearNums); im.current(); ++im) { 02854 data.month = *im.current(); 02855 int ended = data.yearMonth() - endYearMonth; 02856 if (ended >= 0) { 02857 // This is the end month, so check each day 02858 days = data.dayList(); 02859 for (id = days->begin(); id != days->end(); ++id) { 02860 ++countGone; 02861 if (ended > 0 || (ended == 0 && *id > endDay)) { 02862 data.day = *id; 02863 goto ex; 02864 } 02865 if (--countTogo == 0) 02866 return 0; 02867 } 02868 } else { 02869 // Skip the whole month. 02870 if (static_cast<uint>(data.daysPerMonth) >= countTogo) 02871 return 0; 02872 countGone += data.daysPerMonth; 02873 } 02874 } 02875 } 02876 ex: 02877 enddate = data.date(); 02878 return countGone; 02879 } 02880 02881 02882 /* Find count and, depending on 'func', the end date of an annual recurrence by day. 02883 * Reply = total number of occurrences up to 'enddate', or 0 if error. 02884 * If 'func' = END_DATE_AND_COUNT or NEXT_AFTER_DATE, 'enddate' is updated to the 02885 * recurrence end date. 02886 */ 02887 class Recurrence::YearlyDayData 02888 { 02889 public: 02890 int year; // current year 02891 int day; // current day of year 1..366 02892 bool varies; // true if day 366 recurs 02893 02894 private: 02895 int daycount; 02896 02897 public: 02898 YearlyDayData(const Recurrence* r, const QDate &date) 02899 : year( date.year() ), day( date.dayOfYear() ), 02900 varies( *r->yearNums().getLast() == 366 ), 02901 daycount( r->yearNums().count() ) { } 02902 bool leapYear() const { return QDate::leapYear(year); } 02903 int dayCount() const { return daycount - (varies && !QDate::leapYear(year) ? 1 : 0); } 02904 bool isMaxDayCount() const { return !varies || QDate::leapYear(year); } 02905 QDate date() const { return QDate(year, 1, 1).addDays(day - 1); } 02906 }; 02907 02908 int Recurrence::yearlyDayCalc(PeriodFunc func, QDate &enddate) const 02909 { 02910 if (rYearNums.isEmpty()) 02911 return 0; 02912 YearlyDayData data(this, mRecurStart.date()); 02913 switch (func) { 02914 case END_DATE_AND_COUNT: 02915 return yearlyDayCalcEndDate(enddate, data); 02916 case COUNT_TO_DATE: 02917 return yearlyDayCalcToDate(enddate, data); 02918 case NEXT_AFTER_DATE: 02919 return yearlyDayCalcNextAfter(enddate, data); 02920 } 02921 return 0; 02922 } 02923 02924 int Recurrence::yearlyDayCalcEndDate(QDate &enddate, YearlyDayData &data) const 02925 { 02926 uint countTogo = rDuration; 02927 int countGone = 0; 02928 02929 if (data.day > 1) { 02930 // Check what remains of the start year 02931 bool leapOK = data.isMaxDayCount(); 02932 for (QPtrListIterator<int> it(rYearNums); it.current(); ++it) { 02933 int d = *it.current(); 02934 if (d >= data.day && (leapOK || d < 366)) { 02935 ++countGone; 02936 if (--countTogo == 0) { 02937 data.day = d; 02938 goto ex; 02939 } 02940 } 02941 } 02942 data.day = 1; 02943 data.year += rFreq; 02944 } 02945 02946 if (data.varies) { 02947 // The number of recurrences is different in leap years, 02948 // so check year-by-year. 02949 for ( ; ; ) { 02950 uint n = data.dayCount(); 02951 if (n >= countTogo) 02952 break; 02953 countTogo -= n; 02954 countGone += n; 02955 data.year += rFreq; 02956 } 02957 } else { 02958 // The number of recurrences is the same every year, 02959 // so skip the year-by-year check. 02960 // Skip the remaining whole years, but leave at least 02961 // 1 recurrence remaining, in order to get its date. 02962 int daysPerYear = rYearNums.count(); 02963 int wholeYears = (countTogo - 1) / daysPerYear; 02964 data.year += wholeYears * rFreq; 02965 countGone += wholeYears * daysPerYear; 02966 countTogo -= wholeYears * daysPerYear; 02967 } 02968 if (countTogo) { 02969 // Check the last year in the recurrence 02970 for (QPtrListIterator<int> it(rYearNums); it.current(); ++it) { 02971 ++countGone; 02972 if (--countTogo == 0) { 02973 data.day = *it.current(); 02974 break; 02975 } 02976 } 02977 } 02978 ex: 02979 enddate = data.date(); 02980 return countGone; 02981 } 02982 02983 int Recurrence::yearlyDayCalcToDate(const QDate &enddate, YearlyDayData &data) const 02984 { 02985 int countGone = 0; 02986 int countMax = (rDuration > 0) ? rDuration : INT_MAX; 02987 int endYear = enddate.year(); 02988 int endDay = enddate.dayOfYear(); 02989 02990 if (data.day > 1) { 02991 // Check what remains of the start year 02992 bool leapOK = data.isMaxDayCount(); 02993 for (QPtrListIterator<int> it(rYearNums); it.current(); ++it) { 02994 int d = *it.current(); 02995 if (d >= data.day && (leapOK || d < 366)) { 02996 if (data.year == endYear && d > endDay) 02997 return countGone; 02998 if (++countGone >= countMax) 02999 return countMax; 03000 } 03001 } 03002 data.day = 1; 03003 data.year += rFreq; 03004 } 03005 03006 if (data.varies) { 03007 // The number of recurrences is different in leap years, 03008 // so check year-by-year. 03009 while (data.year < endYear) { 03010 uint n = data.dayCount(); 03011 countGone += n; 03012 if (countGone >= countMax) 03013 return countMax; 03014 data.year += rFreq; 03015 } 03016 if (data.year > endYear) 03017 return countGone; 03018 } else { 03019 // The number of recurrences is the same every year. 03020 // Skip the remaining whole years. 03021 int wholeYears = endYear - data.year; 03022 countGone += (wholeYears / rFreq) * rYearNums.count(); 03023 if (countGone >= countMax) 03024 return countMax; 03025 if (wholeYears % rFreq) 03026 return countGone; // end year isn't a recurrence year 03027 data.year = endYear; 03028 } 03029 03030 if (data.year <= endYear) { 03031 // Check the last year in the recurrence 03032 for (QPtrListIterator<int> it(rYearNums); it.current(); ++it) { 03033 if (*it.current() > endDay) 03034 return countGone; 03035 if (++countGone >= countMax) 03036 return countMax; 03037 } 03038 } 03039 return countGone; 03040 } 03041 03042 int Recurrence::yearlyDayCalcNextAfter(QDate &enddate, YearlyDayData &data) const 03043 { 03044 uint countTogo = (rDuration > 0) ? rDuration : UINT_MAX; 03045 int countGone = 0; 03046 int endYear = enddate.year(); 03047 int endDay = enddate.dayOfYear(); 03048 03049 if (data.day > 1) { 03050 // Check what remains of the start year 03051 bool leapOK = data.isMaxDayCount(); 03052 for (QPtrListIterator<int> it(rYearNums); it.current(); ++it) { 03053 int d = *it.current(); 03054 if (d >= data.day && (leapOK || d < 366)) { 03055 ++countGone; 03056 if (data.year == endYear && d > endDay) { 03057 data.day = d; 03058 goto ex; 03059 } 03060 if (--countTogo == 0) 03061 return 0; 03062 } 03063 } 03064 data.day = 1; 03065 data.year += rFreq; 03066 } 03067 03068 if (data.varies) { 03069 // The number of recurrences is different in leap years, 03070 // so check year-by-year. 03071 while (data.year <= endYear) { 03072 uint n = data.dayCount(); 03073 if (data.year == endYear && *rYearNums.getLast() > endDay) 03074 break; 03075 if (n >= countTogo) 03076 break; 03077 countTogo -= n; 03078 countGone += n; 03079 data.year += rFreq; 03080 } 03081 } else { 03082 // The number of recurrences is the same every year, 03083 // so skip the year-by-year check. 03084 // Skip the remaining whole years to at least endYear. 03085 int daysPerYear = rYearNums.count(); 03086 int recurYears = (endYear - data.year + rFreq - 1) / rFreq; 03087 if ((endYear - data.year)%rFreq == 0 03088 && *rYearNums.getLast() <= endDay) 03089 ++recurYears; // required year is after endYear 03090 if (recurYears) { 03091 int n = recurYears * daysPerYear; 03092 if (static_cast<uint>(n) > countTogo) 03093 return 0; // reached end of recurrence 03094 countTogo -= n; 03095 countGone += n; 03096 data.year += recurYears * rFreq; 03097 } 03098 } 03099 03100 // Check the last year in the recurrence 03101 for (QPtrListIterator<int> it(rYearNums); it.current(); ++it) { 03102 ++countGone; 03103 int d = *it.current(); 03104 if (data.year > endYear || d > endDay) { 03105 data.day = d; 03106 break; 03107 } 03108 if (--countTogo == 0) 03109 return 0; 03110 } 03111 ex: 03112 enddate = data.date(); 03113 return countGone; 03114 } 03115 03116 // Get the days in this month which recur, in numerical order. 03117 // Parameters: daysInMonth = number of days in this month 03118 // startDayOfWeek = day of week for first day of month. 03119 void Recurrence::getMonthlyPosDays(QValueList<int> &list, int daysInMonth, int startDayOfWeek) const 03120 { 03121 list.clear(); 03122 int endDayOfWeek = (startDayOfWeek + daysInMonth - 2) % 7 + 1; 03123 // Go through the list, compiling a bit list of actual day numbers 03124 Q_UINT32 days = 0; 03125 for (QPtrListIterator<rMonthPos> pos(rMonthPositions); pos.current(); ++pos) { 03126 int weeknum = pos.current()->rPos - 1; // get 0-based week number 03127 QBitArray &rdays = pos.current()->rDays; 03128 if (pos.current()->negative) { 03129 // nth days before the end of the month 03130 for (uint i = 1; i <= 7; ++i) { 03131 if (rdays.testBit(i - 1)) { 03132 int day = daysInMonth - weeknum*7 - (endDayOfWeek - i + 7) % 7; 03133 if (day > 0) 03134 days |= 1 << (day - 1); 03135 } 03136 } 03137 } else { 03138 // nth days after the start of the month 03139 for (uint i = 1; i <= 7; ++i) { 03140 if (rdays.testBit(i - 1)) { 03141 int day = 1 + weeknum*7 + (i - startDayOfWeek + 7) % 7; 03142 if (day <= daysInMonth) 03143 days |= 1 << (day - 1); 03144 } 03145 } 03146 } 03147 } 03148 // Compile the ordered list 03149 Q_UINT32 mask = 1; 03150 for (int i = 0; i < daysInMonth; mask <<= 1, ++i) { 03151 if (days & mask) 03152 list.append(i + 1); 03153 } 03154 } 03155 03156 // Get the number of days in the month which recur. 03157 // Reply = -1 if the number varies from month to month. 03158 int Recurrence::countMonthlyPosDays() const 03159 { 03160 int count = 0; 03161 Q_UINT8 positive[5] = { 0, 0, 0, 0, 0 }; 03162 Q_UINT8 negative[4] = { 0, 0, 0, 0 }; 03163 for (QPtrListIterator<rMonthPos> pos(rMonthPositions); pos.current(); ++pos) { 03164 int weeknum = pos.current()->rPos; 03165 Q_UINT8* wk; 03166 if (pos.current()->negative) { 03167 // nth days before the end of the month 03168 if (weeknum > 4) 03169 return -1; // days in 5th week are often missing 03170 wk = &negative[4 - weeknum]; 03171 } else { 03172 // nth days after the start of the month 03173 if (weeknum > 4) 03174 return -1; // days in 5th week are often missing 03175 wk = &positive[weeknum - 1]; 03176 } 03177 QBitArray &rdays = pos.current()->rDays; 03178 for (uint i = 0; i < 7; ++i) { 03179 if (rdays.testBit(i)) { 03180 ++count; 03181 *wk |= (1 << i); 03182 } 03183 } 03184 } 03185 // Check for any possible days which could be duplicated by 03186 // a positive and a negative position. 03187 for (int i = 0; i < 4; ++i) { 03188 if (negative[i] & (positive[i] | positive[i+1])) 03189 return -1; 03190 } 03191 return count; 03192 } 03193 03194 // Get the days in this month which recur, in numerical order. 03195 // Reply = true if day numbers varies from month to month. 03196 bool Recurrence::getMonthlyDayDays(QValueList<int> &list, int daysInMonth) const 03197 { 03198 list.clear(); 03199 bool variable = false; 03200 Q_UINT32 days = 0; 03201 for (QPtrListIterator<int> it(rMonthDays); it.current(); ++it) { 03202 int day = *it.current(); 03203 if (day > 0) { 03204 // date in the month 03205 if (day <= daysInMonth) 03206 days |= 1 << (day - 1); 03207 if (day > 28 && day <= 31) 03208 variable = true; // this date does not appear in some months 03209 } else if (day < 0) { 03210 // days before the end of the month 03211 variable = true; // this date varies depending on the month length 03212 day = daysInMonth + day; // zero-based day of month 03213 if (day >= 0) 03214 days |= 1 << day; 03215 } 03216 } 03217 // Compile the ordered list 03218 Q_UINT32 mask = 1; 03219 for (int i = 0; i < daysInMonth; mask <<= 1, ++i) { 03220 if (days & mask) 03221 list.append(i + 1); 03222 } 03223 return variable; 03224 } 03225 03226 // Get the months which recur, in numerical order, for both leap years and non-leap years. 03227 // N.B. If February 29th recurs on March 1st in non-leap years, February (not March) is 03228 // included in the non-leap year month list. 03229 // Reply = true if February 29th also recurs. 03230 bool Recurrence::getYearlyMonthMonths(int day, QValueList<int> &list, QValueList<int> &leaplist) const 03231 { 03232 list.clear(); 03233 leaplist.clear(); 03234 bool feb29 = false; 03235 for (QPtrListIterator<int> it(rYearNums); it.current(); ++it) { 03236 int month = *it.current(); 03237 if (month == 2) { 03238 if (day <= 28) { 03239 list.append(month); // date appears in February 03240 leaplist.append(month); 03241 } 03242 else if (day == 29) { 03243 // February 29th 03244 leaplist.append(month); 03245 switch (mFeb29YearlyType) { 03246 case rFeb28: 03247 case rMar1: 03248 list.append(2); 03249 break; 03250 case rFeb29: 03251 break; 03252 } 03253 feb29 = true; 03254 } 03255 } 03256 else if (day <= 30 || QDate(2000, month, 1).daysInMonth() == 31) { 03257 list.append(month); // date appears in every month 03258 leaplist.append(month); 03259 } 03260 } 03261 return feb29; 03262 } 03263 03264 /* From the recurrence day of the week list, get the earliest day in the 03265 * specified week which is >= the startDay. 03266 * Parameters: startDay = 1..7 (Monday..Sunday) 03267 * useWeekStart = true to end search at day before next rWeekStart 03268 * = false to search for a full 7 days 03269 * Reply = day of the week (1..7), or 0 if none found. 03270 */ 03271 int Recurrence::getFirstDayInWeek(int startDay, bool useWeekStart) const 03272 { 03273 int last = ((useWeekStart ? rWeekStart : startDay) + 5)%7; 03274 for (int i = startDay - 1; ; i = (i + 1)%7) { 03275 if (rDays.testBit(i)) 03276 return i + 1; 03277 if (i == last) 03278 return 0; 03279 } 03280 } 03281 03282 /* From the recurrence day of the week list, get the latest day in the 03283 * specified week which is <= the endDay. 03284 * Parameters: endDay = 1..7 (Monday..Sunday) 03285 * useWeekStart = true to end search at rWeekStart 03286 * = false to search for a full 7 days 03287 * Reply = day of the week (1..7), or 0 if none found. 03288 */ 03289 int Recurrence::getLastDayInWeek(int endDay, bool useWeekStart) const 03290 { 03291 int last = useWeekStart ? rWeekStart - 1 : endDay%7; 03292 for (int i = endDay - 1; ; i = (i + 6)%7) { 03293 if (rDays.testBit(i)) 03294 return i + 1; 03295 if (i == last) 03296 return 0; 03297 } 03298 } 03299 03300 /* From the recurrence monthly day number list or monthly day of week/week of 03301 * month list, get the earliest day in the specified month which is >= the 03302 * earliestDate. 03303 */ 03304 QDate Recurrence::getFirstDateInMonth(const QDate &earliestDate) const 03305 { 03306 int earliestDay = earliestDate.day(); 03307 int daysInMonth = earliestDate.daysInMonth(); 03308 switch (recurs) { 03309 case rMonthlyDay: { 03310 int minday = daysInMonth + 1; 03311 for (QPtrListIterator<int> it(rMonthDays); it.current(); ++it) { 03312 int day = *it.current(); 03313 if (day < 0) 03314 day = daysInMonth + day + 1; 03315 if (day >= earliestDay && day < minday) 03316 minday = day; 03317 } 03318 if (minday <= daysInMonth) 03319 return earliestDate.addDays(minday - earliestDay); 03320 break; 03321 } 03322 case rMonthlyPos: 03323 case rYearlyPos: { 03324 QDate monthBegin(earliestDate.addDays(1 - earliestDay)); 03325 QValueList<int> dayList; 03326 getMonthlyPosDays(dayList, daysInMonth, monthBegin.dayOfWeek()); 03327 for (QValueList<int>::ConstIterator id = dayList.begin(); id != dayList.end(); ++id) { 03328 if (*id >= earliestDay) 03329 return monthBegin.addDays(*id - 1); 03330 } 03331 break; 03332 } 03333 } 03334 return QDate(); 03335 } 03336 03337 /* From the recurrence monthly day number list or monthly day of week/week of 03338 * month list, get the latest day in the specified month which is <= the 03339 * latestDate. 03340 */ 03341 QDate Recurrence::getLastDateInMonth(const QDate &latestDate) const 03342 { 03343 int latestDay = latestDate.day(); 03344 int daysInMonth = latestDate.daysInMonth(); 03345 switch (recurs) { 03346 case rMonthlyDay: { 03347 int maxday = -1; 03348 for (QPtrListIterator<int> it(rMonthDays); it.current(); ++it) { 03349 int day = *it.current(); 03350 if (day < 0) 03351 day = daysInMonth + day + 1; 03352 if (day <= latestDay && day > maxday) 03353 maxday = day; 03354 } 03355 if (maxday > 0) 03356 return QDate(latestDate.year(), latestDate.month(), maxday); 03357 break; 03358 } 03359 case rMonthlyPos: 03360 case rYearlyPos: { 03361 QDate monthBegin(latestDate.addDays(1 - latestDay)); 03362 QValueList<int> dayList; 03363 getMonthlyPosDays(dayList, daysInMonth, monthBegin.dayOfWeek()); 03364 for (QValueList<int>::ConstIterator id = dayList.fromLast(); id != dayList.end(); --id) { 03365 if (*id <= latestDay) 03366 return monthBegin.addDays(*id - 1); 03367 } 03368 break; 03369 } 03370 } 03371 return QDate(); 03372 } 03373 03374 /* From the recurrence yearly month list or yearly day list, get the earliest 03375 * month or day in the specified year which is >= the earliestDate. 03376 * Note that rYearNums is sorted in numerical order. 03377 */ 03378 QDate Recurrence::getFirstDateInYear(const QDate &earliestDate) const 03379 { 03380 QPtrListIterator<int> it(rYearNums); 03381 switch (recurs) { 03382 case rYearlyMonth: { 03383 int earliestYear = earliestDate.year(); 03384 int earliestMonth = earliestDate.month(); 03385 int earliestDay = earliestDate.day(); 03386 int day = rMonthDays.count() ? *rMonthDays.getFirst() : recurStart().date().day(); 03387 int dayThisMonth = (day > 0) ? day : earliestDate.daysInMonth() + 1 + day; 03388 if (earliestDay > dayThisMonth) { 03389 // The earliest date is later in the month than the recurrence date, 03390 // so skip to the next month before starting to check 03391 if (++earliestMonth > 12) 03392 return QDate(); 03393 } 03394 for ( ; it.current(); ++it) { 03395 int month = *it.current(); 03396 if (month >= earliestMonth) { 03397 dayThisMonth = (day > 0) ? day : QDate(earliestYear, month, 1).daysInMonth() + 1 + day; 03398 if (dayThisMonth <= 28 || QDate::isValid(earliestYear, month, dayThisMonth)) 03399 return QDate(earliestYear, month, dayThisMonth); 03400 if (day == 29 && month == 2) { 03401 // It's a recurrence on February 29th, in a non-leap year 03402 switch (mFeb29YearlyType) { 03403 case rMar1: 03404 return QDate(earliestYear, 3, 1); 03405 case rFeb28: 03406 if (earliestDay <= 28) 03407 return QDate(earliestYear, 2, 28); 03408 break; 03409 case rFeb29: 03410 break; 03411 } 03412 } 03413 } 03414 } 03415 break; 03416 } 03417 case rYearlyPos: { 03418 QValueList<int> dayList; 03419 int earliestYear = earliestDate.year(); 03420 int earliestMonth = earliestDate.month(); 03421 int earliestDay = earliestDate.day(); 03422 for ( ; it.current(); ++it) { 03423 int month = *it.current(); 03424 if (month >= earliestMonth) { 03425 QDate monthBegin(earliestYear, month, 1); 03426 getMonthlyPosDays(dayList, monthBegin.daysInMonth(), monthBegin.dayOfWeek()); 03427 for (QValueList<int>::ConstIterator id = dayList.begin(); id != dayList.end(); ++id) { 03428 if (*id >= earliestDay) 03429 return monthBegin.addDays(*id - 1); 03430 } 03431 earliestDay = 1; 03432 } 03433 } 03434 break; 03435 } 03436 case rYearlyDay: { 03437 int earliestDay = earliestDate.dayOfYear(); 03438 for ( ; it.current(); ++it) { 03439 int day = *it.current(); 03440 if (day >= earliestDay && (day <= 365 || day <= earliestDate.daysInYear())) 03441 return earliestDate.addDays(day - earliestDay); 03442 } 03443 break; 03444 } 03445 } 03446 return QDate(); 03447 } 03448 03449 /* From the recurrence yearly month list or yearly day list, get the latest 03450 * month or day in the specified year which is <= the latestDate. 03451 * Note that rYearNums is sorted in numerical order. 03452 */ 03453 QDate Recurrence::getLastDateInYear(const QDate &latestDate) const 03454 { 03455 QPtrListIterator<int> it(rYearNums); 03456 switch (recurs) { 03457 case rYearlyMonth: { 03458 int latestYear = latestDate.year(); 03459 int latestMonth = latestDate.month(); 03460 int day = rMonthDays.count() ? *rMonthDays.getFirst() : recurStart().date().day(); 03461 int dayThisMonth = (day > 0) ? day : latestDate.daysInMonth() + 1 + day; 03462 if (latestDate.day() < dayThisMonth) { 03463 // The latest date is earlier in the month than the recurrence date, 03464 // so skip to the previous month before starting to check 03465 if (--latestMonth <= 0) 03466 return QDate(); 03467 } 03468 for (it.toLast(); it.current(); --it) { 03469 int month = *it.current(); 03470 if (month <= latestMonth) { 03471 dayThisMonth = (day > 0) ? day : QDate(latestYear, month, 1).daysInMonth() + 1 + day; 03472 if (dayThisMonth <= 28 || QDate::isValid(latestYear, month, dayThisMonth)) 03473 return QDate(latestYear, month, dayThisMonth); 03474 if (day == 29 && month == 2) { 03475 // It's a recurrence on February 29th, in a non-leap year 03476 switch (mFeb29YearlyType) { 03477 case rMar1: 03478 if (latestMonth >= 3) 03479 return QDate(latestYear, 3, 1); 03480 break; 03481 case rFeb28: 03482 return QDate(latestYear, 2, 28); 03483 case rFeb29: 03484 break; 03485 } 03486 } 03487 } 03488 } 03489 break; 03490 } 03491 case rYearlyPos: { 03492 QValueList<int> dayList; 03493 int latestYear = latestDate.year(); 03494 int latestMonth = latestDate.month(); 03495 int latestDay = latestDate.day(); 03496 for (it.toLast(); it.current(); --it) { 03497 int month = *it.current(); 03498 if (month <= latestMonth) { 03499 QDate monthBegin(latestYear, month, 1); 03500 getMonthlyPosDays(dayList, monthBegin.daysInMonth(), monthBegin.dayOfWeek()); 03501 for (QValueList<int>::ConstIterator id = dayList.fromLast(); id != dayList.end(); --id) { 03502 if (*id <= latestDay) 03503 return monthBegin.addDays(*id - 1); 03504 } 03505 latestDay = 31; 03506 } 03507 } 03508 break; 03509 } 03510 case rYearlyDay: { 03511 int latestDay = latestDate.dayOfYear(); 03512 for (it.toLast(); it.current(); --it) { 03513 int day = *it.current(); 03514 if (day <= latestDay) 03515 return latestDate.addDays(day - latestDay); 03516 } 03517 break; 03518 } 03519 } 03520 return QDate(); 03521 } 03522 03523 void Recurrence::dump() const 03524 { 03525 kdDebug(5800) << "Recurrence::dump():" << endl; 03526 03527 kdDebug(5800) << " type: " << recurs << endl; 03528 03529 kdDebug(5800) << " rDays: " << endl; 03530 int i; 03531 for( i = 0; i < 7; ++i ) { 03532 kdDebug(5800) << " " << i << ": " 03533 << ( rDays.testBit( i ) ? "true" : "false" ) << endl; 03534 } 03535 }
KDE Logo
This file is part of the documentation for libkcal Library Version 3.3.0.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Fri Aug 27 12:49:11 2004 by doxygen 1.3.8 written by Dimitri van Heesch, © 1997-2003