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