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 || recurs == rYearlyPos )
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   kdDebug(5800) << "  duration: " << rDuration << endl;
03536 
03537   for (QPtrListIterator<int> it(rMonthDays);  it.current();  ++it) {
03538     kdDebug(5800) << "  monthday: " << *(it.current()) << endl;
03539   }
03540 }
KDE Logo
This file is part of the documentation for libkcal Library Version 3.3.2.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Wed Jan 31 15:52:19 2007 by doxygen 1.4.2 written by Dimitri van Heesch, © 1997-2003