/* This file is part of libkcal. Copyright (c) 1998 Preston Brown Copyright (c) 2001 Cornelius Schumacher Copyright (c) 2002 David Jarvie This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include "incidence.h" #include "recurrence.h" //Added by qt3to4: #include #include using namespace KCal; Recurrence::Feb29Type Recurrence::mFeb29YearlyDefaultType = Recurrence::rMar1; Recurrence::Recurrence(Incidence *parent, int compatVersion) : recurs(rNone), // by default, it's not a recurring event rWeekStart(1), // default is Monday rDays(7), mFloats(parent ? parent->doesFloat() : false), mRecurReadOnly(false), mRecurExDatesCount(0), mFeb29YearlyType(mFeb29YearlyDefaultType), mCompatVersion(compatVersion ? compatVersion : INT_MAX), mCompatRecurs(rNone), mCompatDuration(0), mParent(parent) { rMonthDays.setAutoDelete( true ); rMonthPositions.setAutoDelete( true ); rYearNums.setAutoDelete( true ); } Recurrence::Recurrence(const Recurrence &r, Incidence *parent) : recurs(r.recurs), rWeekStart(r.rWeekStart), rDays(r.rDays), rFreq(r.rFreq), rDuration(r.rDuration), rEndDateTime(r.rEndDateTime), mRecurStart(r.mRecurStart), mFloats(r.mFloats), mRecurReadOnly(r.mRecurReadOnly), mRecurExDatesCount(r.mRecurExDatesCount), mFeb29YearlyType(r.mFeb29YearlyType), mCompatVersion(r.mCompatVersion), mCompatRecurs(r.mCompatRecurs), mCompatDuration(r.mCompatDuration), mParent(parent) { for (Q3PtrListIterator mp(r.rMonthPositions); mp.current(); ++mp) { rMonthPos *tmp = new rMonthPos; tmp->rPos = mp.current()->rPos; tmp->negative = mp.current()->negative; tmp->rDays = mp.current()->rDays; rMonthPositions.append(tmp); } for (Q3PtrListIterator md(r.rMonthDays); md.current(); ++md) { int *tmp = new int; *tmp = *md.current(); rMonthDays.append(tmp); } for (Q3PtrListIterator yn(r.rYearNums); yn.current(); ++yn) { int *tmp = new int; *tmp = *yn.current(); rYearNums.append(tmp); } rMonthDays.setAutoDelete( true ); rMonthPositions.setAutoDelete( true ); rYearNums.setAutoDelete( true ); } Recurrence::~Recurrence() { } bool Recurrence::operator==( const Recurrence& r2 ) const { // the following line is obvious if ( recurs == rNone && r2.recurs == rNone ) return true; // we need the above line, because two non recurring events may // differ in the other settings, because one (or both) // may be not initialized properly if ( recurs != r2.recurs || rFreq != r2.rFreq || rDuration != r2.rDuration || !rDuration && rEndDateTime != r2.rEndDateTime || mRecurStart != r2.mRecurStart || mFloats != r2.mFloats || mRecurReadOnly != r2.mRecurReadOnly || mRecurExDatesCount != r2.mRecurExDatesCount ) return false; // no need to compare mCompat* and mParent // OK to compare the pointers switch ( recurs ) { case rWeekly: return rDays == r2.rDays && rWeekStart == r2.rWeekStart; case rMonthlyPos: { Q3PtrList MonthPositions = rMonthPositions; Q3PtrList MonthPositions2 = r2.rMonthPositions; if ( !MonthPositions.count() ) return false; if ( !MonthPositions2.count() ) return false; return MonthPositions.first()->rPos == MonthPositions2.first()->rPos; } case rMonthlyDay: { Q3PtrList MonthDays = rMonthDays ; Q3PtrList MonthDays2 = r2.rMonthDays ; if ( !MonthDays.count() ) return false; if ( !MonthDays2.count() ) return false; return *MonthDays.first() == *MonthDays2.first() ; } case rYearlyPos: { Q3PtrList YearNums = rYearNums; Q3PtrList YearNums2 = r2.rYearNums; if ( *YearNums.first() != *YearNums2.first() ) return false; Q3PtrList MonthPositions = rMonthPositions; Q3PtrList MonthPositions2 = r2.rMonthPositions; if ( !MonthPositions.count() ) return false; if ( !MonthPositions2.count() ) return false; return MonthPositions.first()->rPos == MonthPositions2.first()->rPos; } case rYearlyMonth: { Q3PtrList YearNums = rYearNums; Q3PtrList YearNums2 = r2.rYearNums; return ( *YearNums.first() == *YearNums2.first() && mFeb29YearlyType == r2.mFeb29YearlyType); } case rYearlyDay: { Q3PtrList YearNums = rYearNums; Q3PtrList YearNums2 = r2.rYearNums; return ( *YearNums.first() == *YearNums2.first() ); } case rNone: case rMinutely: case rHourly: case rDaily: default: return true; } } /* bool Recurrence::compareLists( const QPtrList &l1 ,const QPtrList &l2) { if ( l1.count() != l2.count() ) return false; int count = l1.count(); int i; for ( i = 0; i < count ; ++i ) { // if ( l1.at(i) != l2.at(i) ) return false; qDebug("compüare "); } return true; } */ QString Recurrence::recurrenceText() const { QString recurText; if ( recurs == Recurrence::rMinutely ) recurText = i18n("minutely"); else if ( recurs == Recurrence::rHourly ) recurText = i18n("hourly"); else if ( recurs == Recurrence::rDaily ) recurText = i18n("daily"); else if ( recurs == Recurrence::rWeekly ) recurText = i18n("weekly"); else if ( recurs == Recurrence::rMonthlyPos ) recurText = i18n("monthly"); else if ( recurs == Recurrence::rMonthlyDay ) recurText = i18n("monthly"); else if ( recurs == Recurrence::rYearlyMonth ) recurText = i18n("yearly"); else if ( recurs == Recurrence::rYearlyDay ) recurText = i18n("day-yearly"); else if ( recurs == Recurrence::rYearlyPos ) recurText = i18n("position-yearly"); if ( !recurText.isEmpty() ) { if ( rFreq > 1 ){ recurText = i18n("(%1) ").arg(rFreq ) + recurText; } } else recurText = i18n("No"); return recurText; } void Recurrence::setCompatVersion(int version) { mCompatVersion = version ? version : INT_MAX; } ushort Recurrence::doesRecur() const { return recurs; } bool Recurrence::recursOnPure(const QDate &qd) const { switch(recurs) { case rMinutely: return recursSecondly(qd, rFreq*60); case rHourly: return recursSecondly(qd, rFreq*3600); case rDaily: return recursDaily(qd); case rWeekly: return recursWeekly(qd); case rMonthlyPos: case rMonthlyDay: return recursMonthly(qd); case rYearlyMonth: return recursYearlyByMonth(qd); case rYearlyDay: return recursYearlyByDay(qd); case rYearlyPos: return recursYearlyByPos(qd); default: return false; case rNone: return false; } // case return false; } bool Recurrence::recursAtPure(const QDateTime &dt) const { switch(recurs) { case rMinutely: return recursMinutelyAt(dt, rFreq); case rHourly: return recursMinutelyAt(dt, rFreq*60); default: if (dt.time() != mRecurStart.time()) return false; switch(recurs) { case rDaily: return recursDaily(dt.date()); case rWeekly: return recursWeekly(dt.date()); case rMonthlyPos: case rMonthlyDay: return recursMonthly(dt.date()); case rYearlyMonth: return recursYearlyByMonth(dt.date()); case rYearlyDay: return recursYearlyByDay(dt.date()); case rYearlyPos: return recursYearlyByPos(dt.date()); default: return false; case rNone: return false; } } // case return false; } QDate Recurrence::endDate() const { int count = 0; QDate end; if (recurs != rNone) { if (rDuration < 0) return QDate(); // infinite recurrence if (rDuration == 0) return rEndDateTime.date(); // The end date is determined by the recurrence count QDate dStart = mRecurStart.date(); switch (recurs) { case rMinutely: return mRecurStart.addSecs((rDuration-1+mRecurExDatesCount)*rFreq*60).date(); case rHourly: return mRecurStart.addSecs((rDuration-1+mRecurExDatesCount)*rFreq*3600).date(); case rDaily: return dStart.addDays((rDuration-1+mRecurExDatesCount)*rFreq); case rWeekly: count = weeklyCalc(END_DATE_AND_COUNT, end); break; case rMonthlyPos: case rMonthlyDay: count = monthlyCalc(END_DATE_AND_COUNT, end); break; case rYearlyMonth: count = yearlyMonthCalc(END_DATE_AND_COUNT, end); break; case rYearlyDay: count = yearlyDayCalc(END_DATE_AND_COUNT, end); break; case rYearlyPos: count = yearlyPosCalc(END_DATE_AND_COUNT, end); break; default: // catch-all. Should never get here. kdDebug(5800) << "Control should never reach here in endDate()!" << endl; break; } } if (!count) return QDate(); // error - there is no recurrence return end; } QDateTime Recurrence::endDateTime() const { int count = 0; QDate end; if (recurs != rNone) { if (rDuration < 0) return QDateTime(); // infinite recurrence if (rDuration == 0) return rEndDateTime; // The end date is determined by the recurrence count QDate dStart = mRecurStart.date(); switch (recurs) { case rMinutely: return mRecurStart.addSecs((rDuration-1+mRecurExDatesCount)*rFreq*60); case rHourly: return mRecurStart.addSecs((rDuration-1+mRecurExDatesCount)*rFreq*3600); case rDaily: return (QDateTime)dStart.addDays((rDuration-1+mRecurExDatesCount)*rFreq); case rWeekly: count = weeklyCalc(END_DATE_AND_COUNT, end); break; case rMonthlyPos: case rMonthlyDay: count = monthlyCalc(END_DATE_AND_COUNT, end); break; case rYearlyMonth: count = yearlyMonthCalc(END_DATE_AND_COUNT, end); break; case rYearlyDay: count = yearlyDayCalc(END_DATE_AND_COUNT, end); break; case rYearlyPos: count = yearlyPosCalc(END_DATE_AND_COUNT, end); break; default: // catch-all. Should never get here. kdDebug(5800) << "Control should never reach here in endDate()!" << endl; break; } } if (!count) return QDateTime(); // error - there is no recurrence return QDateTime(end, mRecurStart.time()); } int Recurrence::durationTo(const QDate &date) const { QDate d = date; return recurCalc(COUNT_TO_DATE, d); } int Recurrence::durationTo(const QDateTime &datetime) const { QDateTime dt = datetime; return recurCalc(COUNT_TO_DATE, dt); } void Recurrence::unsetRecurs() { if (mRecurReadOnly) return; recurs = rNone; rMonthPositions.clear(); rMonthDays.clear(); rYearNums.clear(); } void Recurrence::setRecurStart(const QDateTime &start) { mRecurStart = start; mFloats = false; switch (recurs) { case rMinutely: case rHourly: break; case rDaily: case rWeekly: case rMonthlyPos: case rMonthlyDay: case rYearlyMonth: case rYearlyDay: case rYearlyPos: default: rEndDateTime.setTime(start.time()); break; } } void Recurrence::setRecurStart(const QDate &start) { mRecurStart.setDate(start); mRecurStart.setTime(QTime(0,0,0)); switch (recurs) { case rMinutely: case rHourly: break; case rDaily: case rWeekly: case rMonthlyPos: case rMonthlyDay: case rYearlyMonth: case rYearlyDay: case rYearlyPos: default: mFloats = true; break; } } void Recurrence::setFloats(bool f) { switch (recurs) { case rDaily: case rWeekly: case rMonthlyPos: case rMonthlyDay: case rYearlyMonth: case rYearlyDay: case rYearlyPos: break; case rMinutely: case rHourly: default: return; // can't set sub-daily to floating } mFloats = f; if (f) { mRecurStart.setTime(QTime(0,0,0)); rEndDateTime.setTime(QTime(0,0,0)); } } int Recurrence::frequency() const { return rFreq; } int Recurrence::duration() const { return rDuration; } void Recurrence::setDuration(int _rDuration) { if (mRecurReadOnly) return; if (_rDuration > 0) { rDuration = _rDuration; // Compatibility mode is only needed when reading the calendar in ICalFormatImpl, // so explicitly setting the duration means no backwards compatibility is needed. mCompatDuration = 0; } } QString Recurrence::endDateStr(bool shortfmt) const { return KGlobal::locale()->formatDate(rEndDateTime.date(),shortfmt); } const QBitArray &Recurrence::days() const { return rDays; } const Q3PtrList &Recurrence::monthPositions() const { return rMonthPositions; } const Q3PtrList &Recurrence::yearMonthPositions() const { return rMonthPositions; } const Q3PtrList &Recurrence::monthDays() const { return rMonthDays; } void Recurrence::setMinutely(int _rFreq, int _rDuration) { if (mRecurReadOnly || _rDuration == 0 || _rDuration < -1) return; setDailySub(rMinutely, _rFreq, _rDuration); } void Recurrence::setMinutely(int _rFreq, const QDateTime &_rEndDateTime) { if (mRecurReadOnly) return; rEndDateTime = _rEndDateTime; setDailySub(rMinutely, _rFreq, 0); } void Recurrence::setHourly(int _rFreq, int _rDuration) { if (mRecurReadOnly || _rDuration == 0 || _rDuration < -1) return; setDailySub(rHourly, _rFreq, _rDuration); } void Recurrence::setHourly(int _rFreq, const QDateTime &_rEndDateTime) { if (mRecurReadOnly) return; rEndDateTime = _rEndDateTime; setDailySub(rHourly, _rFreq, 0); } void Recurrence::setDaily(int _rFreq, int _rDuration) { if (mRecurReadOnly || _rDuration == 0 || _rDuration < -1) return; setDailySub(rDaily, _rFreq, _rDuration); } void Recurrence::setDaily(int _rFreq, const QDate &_rEndDate) { if (mRecurReadOnly) return; rEndDateTime.setDate(_rEndDate); rEndDateTime.setTime(mRecurStart.time()); setDailySub(rDaily, _rFreq, 0); } void Recurrence::setWeekly(int _rFreq, const QBitArray &_rDays, int _rDuration, int _rWeekStart) { if (mRecurReadOnly || _rDuration == 0 || _rDuration < -1) return; recurs = rWeekly; rFreq = _rFreq; rDays = _rDays; rWeekStart = _rWeekStart; rDuration = _rDuration; if (mCompatVersion < 310 && _rDuration > 0) { // Backwards compatibility for KDE < 3.1. // rDuration was set to the number of time periods to recur, // with week start always on a Monday. // Convert this to the number of occurrences. mCompatDuration = _rDuration; int weeks = ((mCompatDuration-1+mRecurExDatesCount)*7) + (7 - mRecurStart.date().dayOfWeek()); QDate end(mRecurStart.date().addDays(weeks * rFreq)); rDuration = INT_MAX; // ensure that weeklyCalc() does its job correctly rDuration = weeklyCalc(COUNT_TO_DATE, end); } else { mCompatDuration = 0; } rMonthPositions.clear(); rMonthDays.clear(); if (mParent) mParent->updated(); } void Recurrence::setWeekly(int _rFreq, const QBitArray &_rDays, const QDate &_rEndDate, int _rWeekStart) { if (mRecurReadOnly) return; recurs = rWeekly; rFreq = _rFreq; rDays = _rDays; rWeekStart = _rWeekStart; rEndDateTime.setDate(_rEndDate); rEndDateTime.setTime(mRecurStart.time()); rDuration = 0; // set to 0 because there is an end date mCompatDuration = 0; rMonthPositions.clear(); rMonthDays.clear(); rYearNums.clear(); if (mParent) mParent->updated(); } void Recurrence::setMonthly(short type, int _rFreq, int _rDuration) { if (mRecurReadOnly || _rDuration == 0 || _rDuration < -1) return; recurs = type; rFreq = _rFreq; rDuration = _rDuration; if (mCompatVersion < 310) mCompatDuration = (_rDuration > 0) ? _rDuration : 0; rYearNums.clear(); if (mParent) mParent->updated(); } void Recurrence::setMonthly(short type, int _rFreq, const QDate &_rEndDate) { if (mRecurReadOnly) return; recurs = type; rFreq = _rFreq; rEndDateTime.setDate(_rEndDate); rEndDateTime.setTime(mRecurStart.time()); rDuration = 0; // set to 0 because there is an end date mCompatDuration = 0; rYearNums.clear(); if (mParent) mParent->updated(); } void Recurrence::addMonthlyPos(short _rPos, const QBitArray &_rDays) { if (recurs == rMonthlyPos) addMonthlyPos_(_rPos, _rDays); } void Recurrence::addMonthlyPos_(short _rPos, const QBitArray &_rDays) { if (mRecurReadOnly || _rPos == 0 || _rPos > 5 || _rPos < -5) // invalid week number return; for (rMonthPos* it = rMonthPositions.first(); it; it = rMonthPositions.next()) { int itPos = it->negative ? -it->rPos : it->rPos; if (_rPos == itPos) { // This week is already in the list. // Combine the specified days with those in the list. it->rDays |= _rDays; if (mParent) mParent->updated(); return; } } // Add the new position to the list rMonthPos *tmpPos = new rMonthPos; if (_rPos > 0) { tmpPos->rPos = _rPos; tmpPos->negative = false; } else { tmpPos->rPos = -_rPos; // take abs() tmpPos->negative = true; } tmpPos->rDays = _rDays; tmpPos->rDays.detach(); rMonthPositions.append(tmpPos); if (mCompatVersion < 310 && mCompatDuration > 0) { // Backwards compatibility for KDE < 3.1. // rDuration was set to the number of time periods to recur. // Convert this to the number of occurrences. int monthsAhead = (mCompatDuration-1+mRecurExDatesCount) * rFreq; int month = mRecurStart.date().month() - 1 + monthsAhead; QDate end(mRecurStart.date().year() + month/12, month%12 + 1, 31); rDuration = INT_MAX; // ensure that recurCalc() does its job correctly rDuration = recurCalc(COUNT_TO_DATE, end); } if (mParent) mParent->updated(); } void Recurrence::addMonthlyDay(short _rDay) { if (mRecurReadOnly || (recurs != rMonthlyDay && recurs != rYearlyMonth) || _rDay == 0 || _rDay > 31 || _rDay < -31) // invalid day number return; for (int* it = rMonthDays.first(); it; it = rMonthDays.next()) { if (_rDay == *it) return; // this day is already in the list - avoid duplication } int *tmpDay = new int; *tmpDay = _rDay; rMonthDays.append(tmpDay); if (mCompatVersion < 310 && mCompatDuration > 0) { // Backwards compatibility for KDE < 3.1. // rDuration was set to the number of time periods to recur. // Convert this to the number of occurrences. int monthsAhead = (mCompatDuration-1+mRecurExDatesCount) * rFreq; int month = mRecurStart.date().month() - 1 + monthsAhead; QDate end(mRecurStart.date().year() + month/12, month%12 + 1, 31); rDuration = INT_MAX; // ensure that recurCalc() does its job correctly rDuration = recurCalc(COUNT_TO_DATE, end); } if (mParent) mParent->updated(); } void Recurrence::setYearly(int type, int _rFreq, int _rDuration) { if (mRecurReadOnly || _rDuration == 0 || _rDuration < -1) return; if (mCompatVersion < 310) mCompatDuration = (_rDuration > 0) ? _rDuration : 0; setYearly_(type, mFeb29YearlyDefaultType, _rFreq, _rDuration); } void Recurrence::setYearly(int type, int _rFreq, const QDate &_rEndDate) { if (mRecurReadOnly) return; rEndDateTime.setDate(_rEndDate); rEndDateTime.setTime(mRecurStart.time()); mCompatDuration = 0; setYearly_(type, mFeb29YearlyDefaultType, _rFreq, 0); } void Recurrence::setYearlyByDate(Feb29Type type, int _rFreq, int _rDuration) { if (mRecurReadOnly || _rDuration == 0 || _rDuration < -1) return; if (mCompatVersion < 310) mCompatDuration = (_rDuration > 0) ? _rDuration : 0; setYearly_(rYearlyMonth, type, _rFreq, _rDuration); } void Recurrence::setYearlyByDate(Feb29Type type, int _rFreq, const QDate &_rEndDate) { if (mRecurReadOnly) return; rEndDateTime.setDate(_rEndDate); rEndDateTime.setTime(mRecurStart.time()); mCompatDuration = 0; setYearly_(rYearlyMonth, type, _rFreq, 0); } void Recurrence::addYearlyMonthPos(short _rPos, const QBitArray &_rDays) { if (recurs == rYearlyPos) addMonthlyPos_(_rPos, _rDays); } const Q3PtrList &Recurrence::yearNums() const { return rYearNums; } void Recurrence::addYearlyMonth(short _rPos ) { if (mRecurReadOnly || recurs != rYearlyMonth) // invalid day/month number return; rMonthPos *tmpPos = new rMonthPos; if ( _rPos > 0) { tmpPos->rPos = _rPos; tmpPos->negative = false; } else { tmpPos->rPos = -_rPos; // take abs() tmpPos->negative = true; } rMonthPositions.append(tmpPos); } void Recurrence::addYearlyNum(short _rNum) { if (mRecurReadOnly || (recurs != rYearlyMonth && recurs != rYearlyDay && recurs != rYearlyPos) || _rNum <= 0) // invalid day/month number return; if (mCompatVersion < 310 && mCompatRecurs == rYearlyDay) { // Backwards compatibility for KDE < 3.1. // Dates were stored as day numbers, with a fiddle to take account of leap years. // Convert the day number to a month. if (_rNum <= 0 || _rNum > 366 || (_rNum == 366 && mRecurStart.date().daysInYear() < 366)) return; // invalid day number _rNum = QDate(mRecurStart.date().year(), 1, 1).addDays(_rNum - 1).month(); } else if ((recurs == rYearlyMonth || recurs == rYearlyPos) && _rNum > 12 || recurs == rYearlyDay && _rNum > 366) return; // invalid day number uint i = 0; for (int* it = rYearNums.first(); it && _rNum >= *it; it = rYearNums.next()) { if (_rNum == *it) return; // this day/month is already in the list - avoid duplication ++i; } int *tmpNum = new int; *tmpNum = _rNum; rYearNums.insert(i, tmpNum); // insert the day/month in a sorted position if (mCompatVersion < 310 && mCompatDuration > 0) { // Backwards compatibility for KDE < 3.1. // rDuration was set to the number of time periods to recur. // Convert this to the number of occurrences. QDate end(mRecurStart.date().year() + (mCompatDuration-1+mRecurExDatesCount)*rFreq, 12, 31); rDuration = INT_MAX; // ensure that recurCalc() does its job correctly rDuration = recurCalc(COUNT_TO_DATE, end); } if (mParent) mParent->updated(); } QDateTime Recurrence::getNextDateTime(const QDateTime &preDateTime, bool *last) const { if (last) *last = false; int freq; switch (recurs) { case rMinutely: freq = rFreq * 60; break; case rHourly: freq = rFreq * 3600; break; case rDaily: case rWeekly: case rMonthlyPos: case rMonthlyDay: case rYearlyMonth: case rYearlyDay: case rYearlyPos: { QDate preDate = preDateTime.date(); if (!mFloats && mRecurStart.time() > preDateTime.time()) preDate = preDate.addDays(-1); return QDateTime(getNextDateNoTime(preDate, last), mRecurStart.time()); } default: return QDateTime(); } // It's a sub-daily recurrence if (preDateTime < mRecurStart) return mRecurStart; int count = mRecurStart.secsTo(preDateTime) / freq + 2; if (rDuration > 0) { if (count > rDuration) return QDateTime(); if (last && count == rDuration) *last = true; } QDateTime endtime = mRecurStart.addSecs((count - 1)*freq); if (rDuration == 0) { if (endtime > rEndDateTime) return QDateTime(); if (last && endtime == rEndDateTime) *last = true; } return endtime; } QDate Recurrence::getNextDate(const QDate &preDate, bool *last) const { if (last) *last = false; switch (recurs) { case rMinutely: case rHourly: return getNextDateTime(QDateTime(preDate, QTime(23,59,59)), last).date(); case rDaily: case rWeekly: case rMonthlyPos: case rMonthlyDay: case rYearlyMonth: case rYearlyDay: case rYearlyPos: qDebug("Recurrence::getNextDate: MAY BE BROKEN "); return getNextDateNoTime(preDate, last); default: return QDate(); } } QDateTime Recurrence::getPreviousDateTime(const QDateTime &afterDateTime, bool *last) const { if (last) *last = false; int freq; switch (recurs) { case rMinutely: freq = rFreq * 60; break; case rHourly: freq = rFreq * 3600; break; case rDaily: case rWeekly: case rMonthlyPos: case rMonthlyDay: case rYearlyMonth: case rYearlyDay: case rYearlyPos: { QDate afterDate = afterDateTime.date(); if (!mFloats && mRecurStart.time() < afterDateTime.time()) afterDate = afterDate.addDays(1); return QDateTime(getPreviousDateNoTime(afterDate, last), mRecurStart.time()); } default: return QDateTime(); } // It's a sub-daily recurrence if (afterDateTime <= mRecurStart) return QDateTime(); int count = (mRecurStart.secsTo(afterDateTime) - 1) / freq + 1; if (rDuration > 0) { if (count > rDuration) count = rDuration; if (last && count == rDuration) *last = true; } QDateTime endtime = mRecurStart.addSecs((count - 1)*freq); if (rDuration == 0) { if (endtime > rEndDateTime) endtime = rEndDateTime; if (last && endtime == rEndDateTime) *last = true; } return endtime; } QDate Recurrence::getPreviousDate(const QDate &afterDate, bool *last) const { if (last) *last = false; switch (recurs) { case rMinutely: case rHourly: return getPreviousDateTime(QDateTime(afterDate, QTime(0,0,0)), last).date(); case rDaily: case rWeekly: case rMonthlyPos: case rMonthlyDay: case rYearlyMonth: case rYearlyDay: case rYearlyPos: return getPreviousDateNoTime(afterDate, last); default: return QDate(); } } /***************************** PROTECTED FUNCTIONS ***************************/ bool Recurrence::recursSecondly(const QDate &qd, int secondFreq) const { if ((qd >= mRecurStart.date()) && ((rDuration > 0) && (qd <= endDate()) || ((rDuration == 0) && (qd <= rEndDateTime.date())) || (rDuration == -1))) { // The date queried falls within the range of the event. if (secondFreq < 24*3600) return true; // the event recurs at least once each day int after = mRecurStart.secsTo(QDateTime(qd)) - 1; if (after / secondFreq != (after + 24*3600) / secondFreq) return true; } return false; } bool Recurrence::recursMinutelyAt(const QDateTime &dt, int minuteFreq) const { if ((dt >= mRecurStart) && ((rDuration > 0) && (dt <= endDateTime()) || ((rDuration == 0) && (dt <= rEndDateTime)) || (rDuration == -1))) { // The time queried falls within the range of the event. if (((mRecurStart.secsTo(dt) / 60) % minuteFreq) == 0) return true; } return false; } bool Recurrence::recursDaily(const QDate &qd) const { QDate dStart = mRecurStart.date(); if ((dStart.daysTo(qd) % rFreq) == 0) { // The date is a day which recurs if (qd >= dStart && ((rDuration > 0 && qd <= endDate()) || (rDuration == 0 && qd <= rEndDateTime.date()) || rDuration == -1)) { // The date queried falls within the range of the event. return true; } } return false; } bool Recurrence::recursWeekly(const QDate &qd) const { QDate dStart = mRecurStart.date(); if ((dStart.daysTo(qd)/7) % rFreq == 0) { // The date is in a week which recurs if (qd >= dStart && ((rDuration > 0 && qd <= endDate()) || (rDuration == 0 && qd <= rEndDateTime.date()) || rDuration == -1)) { // The date queried falls within the range of the event. // check if the bits set match today. int i = qd.dayOfWeek()-1; if (rDays.testBit((uint) i)) return true; } } return false; } bool Recurrence::recursMonthly(const QDate &qd) const { QDate dStart = mRecurStart.date(); int year = qd.year(); int month = qd.month(); int day = qd.day(); // calculate how many months ahead this date is from the original // event's date int monthsAhead = (year - dStart.year()) * 12 + (month - dStart.month()); if ((monthsAhead % rFreq) == 0) { // The date is in a month which recurs if (qd >= dStart && ((rDuration > 0 && qd <= endDate()) || (rDuration == 0 && qd <= rEndDateTime.date()) || rDuration == -1)) { // The date queried falls within the range of the event. Q3ValueList days; int daysInMonth = qd.daysInMonth(); if (recurs == rMonthlyDay) getMonthlyDayDays(days, daysInMonth); else if (recurs == rMonthlyPos) getMonthlyPosDays(days, daysInMonth, QDate(year, month, 1).dayOfWeek()); for (Q3ValueList::Iterator it = days.begin(); it != days.end(); ++it) { if (*it == day) return true; } // no dates matched } } return false; } bool Recurrence::recursYearlyByMonth(const QDate &qd) const { QDate dStart = mRecurStart.date(); int startDay = dStart.day(); int qday = qd.day(); int qmonth = qd.month(); int qyear = qd.year(); bool match = (qday == startDay); if (!match && startDay == 29 && dStart.month() == 2) { // It's a recurrence on February 29th switch (mFeb29YearlyType) { case rFeb28: if (qday == 28 && qmonth == 2 && !QDate::leapYear(qyear)) match = true; break; case rMar1: if (qday == 1 && qmonth == 3 && !QDate::leapYear(qyear)) { qmonth = 2; match = true; } break; case rFeb29: break; } } if (match) { // The day of the month matches. Calculate how many years ahead // this date is from the original event's date. int yearsAhead = (qyear - dStart.year()); if (yearsAhead % rFreq == 0) { // The date is in a year which recurs if (qd >= dStart && ((rDuration > 0 && qd <= endDate()) || (rDuration == 0 && qd <= rEndDateTime.date()) || rDuration == -1)) { // The date queried falls within the range of the event. int i = qmonth; for (Q3PtrListIterator qlin(rYearNums); qlin.current(); ++qlin) { if (i == *qlin.current()) return true; } } } } return false; } bool Recurrence::recursYearlyByPos(const QDate &qd) const { QDate dStart = mRecurStart.date(); int year = qd.year(); int month = qd.month(); int day = qd.day(); // calculate how many years ahead this date is from the original // event's date int yearsAhead = (year - dStart.year()); if (yearsAhead % rFreq == 0) { // The date is in a year which recurs if (qd >= dStart && ((rDuration > 0 && qd <= endDate()) || (rDuration == 0 && qd <= rEndDateTime.date()) || rDuration == -1)) { // The date queried falls within the range of the event. for (Q3PtrListIterator qlin(rYearNums); qlin.current(); ++qlin) { if (month == *qlin.current()) { // The month recurs Q3ValueList days; getMonthlyPosDays(days, qd.daysInMonth(), QDate(year, month, 1).dayOfWeek()); for (Q3ValueList::Iterator it = days.begin(); it != days.end(); ++it) { if (*it == day) return true; } } } } } return false; } bool Recurrence::recursYearlyByDay(const QDate &qd) const { QDate dStart = mRecurStart.date(); // calculate how many years ahead this date is from the original // event's date int yearsAhead = (qd.year() - dStart.year()); if (yearsAhead % rFreq == 0) { // The date is in a year which recurs if (qd >= dStart && ((rDuration > 0 && qd <= endDate()) || (rDuration == 0 && qd <= rEndDateTime.date()) || rDuration == -1)) { // The date queried falls within the range of the event. int i = qd.dayOfYear(); for (Q3PtrListIterator qlin(rYearNums); qlin.current(); ++qlin) { if (i == *qlin.current()) return true; } } } return false; } /* Get the date of the next recurrence, after the specified date. * If 'last' is non-null, '*last' is set to true if the next recurrence is the * last recurrence, else false. * Reply = date of next recurrence, or invalid date if none. */ QDate Recurrence::getNextDateNoTime(const QDate &preDate, bool *last) const { if (last) *last = false; QDate dStart = mRecurStart.date(); if (preDate < dStart) return dStart; QDate earliestDate = preDate.addDays(1); QDate nextDate; switch (recurs) { case rDaily: nextDate = dStart.addDays((dStart.daysTo(preDate)/rFreq + 1) * rFreq); break; case rWeekly: { QDate start = dStart.addDays(-((dStart.dayOfWeek() - rWeekStart + 7)%7)); // start of week for dStart int earliestDayOfWeek = earliestDate.dayOfWeek(); int weeksAhead = start.daysTo(earliestDate) / 7; int notThisWeek = weeksAhead % rFreq; // zero if this week is a recurring week weeksAhead -= notThisWeek; // latest week which recurred int weekday = 0; // First check for any remaining day this week, if this week is a recurring week if (!notThisWeek) weekday = getFirstDayInWeek(earliestDayOfWeek); // Check for a day in the next scheduled week if (!weekday ) weekday = getFirstDayInWeek(rWeekStart) + rFreq*7; nextDate = start.addDays(weeksAhead*7 + weekday - 1); break; } case rMonthlyDay: case rMonthlyPos: { int startYear = dStart.year(); int startMonth = dStart.month(); // 1..12 int earliestYear = earliestDate.year(); int monthsAhead = (earliestYear - startYear)*12 + earliestDate.month() - startMonth; int notThisMonth = monthsAhead % rFreq; // zero if this month is a recurring month monthsAhead -= notThisMonth; // latest month which recurred // Check for the first later day in the current month if (!notThisMonth) nextDate = getFirstDateInMonth(earliestDate); if (!nextDate.isValid() ) { // Check for a day in the next scheduled month int months = startMonth - 1 + monthsAhead + rFreq; nextDate = getFirstDateInMonth(QDate(startYear + months/12, months%12 + 1, 1)); } break; } case rYearlyMonth: case rYearlyPos: case rYearlyDay: { int startYear = dStart.year(); int yearsAhead = earliestDate.year() - startYear; int notThisYear = yearsAhead % rFreq; // zero if this year is a recurring year yearsAhead -= notThisYear; // latest year which recurred // Check for the first later date in the current year if (!notThisYear) nextDate = getFirstDateInYear(earliestDate); // Check for a date in the next scheduled year if (!nextDate.isValid() && earliestDate.dayOfYear() > 1) nextDate = getFirstDateInYear(QDate(startYear + yearsAhead + rFreq, 1, 1)); break; } case rNone: default: return QDate(); } if (rDuration >= 0 && nextDate.isValid()) { // Check that the date found is within the range of the recurrence QDate end = endDate(); if (nextDate > end) return QDate(); if (last && nextDate == end) *last = true; } return nextDate; } /* Get the date of the last previous recurrence, before the specified date. * Reply = date of previous recurrence, or invalid date if none. */ QDate Recurrence::getPreviousDateNoTime(const QDate &afterDate, bool *last) const { if (last) *last = false; QDate dStart = mRecurStart.date(); QDate latestDate = afterDate.addDays(-1); if (latestDate < dStart) return QDate(); QDate prevDate; switch (recurs) { case rDaily: prevDate = dStart.addDays((dStart.daysTo(latestDate) / rFreq) * rFreq); break; case rWeekly: { QDate start = dStart.addDays(-((dStart.dayOfWeek() - rWeekStart + 7)%7)); // start of week for dStart int latestDayOfWeek = latestDate.dayOfWeek(); int weeksAhead = start.daysTo(latestDate) / 7; int notThisWeek = weeksAhead % rFreq; // zero if this week is a recurring week weeksAhead -= notThisWeek; // latest week which recurred int weekday = 0; // First check for any previous day this week, if this week is a recurring week if (!notThisWeek) weekday = getLastDayInWeek(latestDayOfWeek); // Check for a day in the previous scheduled week if (!weekday) { if (!notThisWeek) weeksAhead -= rFreq; int weekEnd = (rWeekStart + 5)%7 + 1; weekday = getLastDayInWeek(weekEnd); } if (weekday) prevDate = start.addDays(weeksAhead*7 + weekday - 1); break; } case rMonthlyDay: case rMonthlyPos: { int startYear = dStart.year(); int startMonth = dStart.month(); // 1..12 int latestYear = latestDate.year(); int monthsAhead = (latestYear - startYear)*12 + latestDate.month() - startMonth; int notThisMonth = monthsAhead % rFreq; // zero if this month is a recurring month monthsAhead -= notThisMonth; // latest month which recurred // Check for the last earlier day in the current month if (!notThisMonth) prevDate = getLastDateInMonth(latestDate); if (!prevDate.isValid() && latestDate.day() < latestDate.daysInMonth()) { // Check for a day in the previous scheduled month if (!notThisMonth) monthsAhead -= rFreq; int months = startMonth + monthsAhead; // get the month after the one that recurs prevDate = getLastDateInMonth(QDate(startYear + months/12, months%12 + 1, 1).addDays(-1)); } break; } case rYearlyMonth: case rYearlyPos: case rYearlyDay: { int startYear = dStart.year(); int yearsAhead = latestDate.year() - startYear; int notThisYear = yearsAhead % rFreq; // zero if this year is a recurring year yearsAhead -= notThisYear; // latest year which recurred // Check for the first later date in the current year if (!notThisYear) prevDate = getLastDateInYear(latestDate); if (!prevDate.isValid() && latestDate.dayOfYear() < latestDate.daysInYear()) { // Check for a date in the next scheduled year if (!notThisYear) yearsAhead -= rFreq; prevDate = getLastDateInYear(QDate(startYear + yearsAhead, 12, 31)); } break; } case rNone: default: return QDate(); } if (prevDate.isValid()) { // Check that the date found is within the range of the recurrence if (prevDate < dStart) return QDate(); if (rDuration >= 0) { QDate end = endDate(); if (prevDate >= end) { if (last) *last = true; return end; } } } return prevDate; } void Recurrence::setDailySub(short type, int freq, int duration) { recurs = type; rFreq = freq; rDuration = duration; rMonthPositions.clear(); rMonthDays.clear(); rYearNums.clear(); if (type != rDaily) mFloats = false; // sub-daily types can't be floating if (mParent) mParent->updated(); } void Recurrence::setYearly_(short type, Feb29Type feb29type, int freq, int duration) { recurs = type; if (mCompatVersion < 310 && type == rYearlyDay) { mCompatRecurs = rYearlyDay; recurs = rYearlyMonth; // convert old yearly-by-day to yearly-by-month feb29type = rMar1; // retain the same day number in the year } mFeb29YearlyType = feb29type; rFreq = freq; rDuration = duration; if (type != rYearlyPos) rMonthPositions.clear(); rMonthDays.clear(); if (mParent) mParent->updated(); } int Recurrence::recurCalc(PeriodFunc func, QDateTime &endtime) const { QDate enddate = endtime.date(); switch (func) { case END_DATE_AND_COUNT: if (rDuration < 0) { endtime = QDateTime(); return 0; // infinite recurrence } if (rDuration == 0) { endtime = rEndDateTime; func = COUNT_TO_DATE; } break; case COUNT_TO_DATE: // Count recurrences up to and including the specified date/time. if (endtime < mRecurStart) return 0; if (rDuration == 0 && endtime > rEndDateTime) enddate = rEndDateTime.date(); else if (!mFloats && mRecurStart.time() > endtime.time()) enddate = enddate.addDays(-1); break; case NEXT_AFTER_DATE: // Find next recurrence AFTER endtime if (endtime < mRecurStart) { endtime = mRecurStart; return 1; } if (rDuration == 0 && endtime >= rEndDateTime) { endtime = QDateTime(); return 0; } if (!mFloats && mRecurStart.time() > endtime.time()) enddate = enddate.addDays(-1); break; default: endtime = QDateTime(); return 0; } int count = 0; // default = error bool timed = false; switch (recurs) { case rMinutely: timed = true; count = secondlyCalc(func, endtime, rFreq*60); break; case rHourly: timed = true; count = secondlyCalc(func, endtime, rFreq*3600); break; case rDaily: count = dailyCalc(func, enddate); break; case rWeekly: count = weeklyCalc(func, enddate); break; case rMonthlyPos: case rMonthlyDay: count = monthlyCalc(func, enddate); break; case rYearlyMonth: count = yearlyMonthCalc(func, enddate); break; case rYearlyPos: count = yearlyPosCalc(func, enddate); break; case rYearlyDay: count = yearlyDayCalc(func, enddate); break; default: break; } switch (func) { case END_DATE_AND_COUNT: case NEXT_AFTER_DATE: if (count == 0) endtime = QDateTime(); else if (!timed) { endtime.setDate(enddate); endtime.setTime(mRecurStart.time()); } break; case COUNT_TO_DATE: break; } return count; } int Recurrence::recurCalc(PeriodFunc func, QDate &enddate) const { QDateTime endtime(enddate, QTime(23,59,59)); switch (func) { case END_DATE_AND_COUNT: if (rDuration < 0) { enddate = QDate(); return 0; // infinite recurrence } if (rDuration == 0) { enddate = rEndDateTime.date(); func = COUNT_TO_DATE; } break; case COUNT_TO_DATE: // Count recurrences up to and including the specified date. if (enddate < mRecurStart.date()) return 0; if (rDuration == 0 && enddate > rEndDateTime.date()) { enddate = rEndDateTime.date(); endtime.setDate(enddate); } break; case NEXT_AFTER_DATE: if (enddate < mRecurStart.date()) { enddate = mRecurStart.date(); return 1; } if (rDuration == 0 && enddate >= rEndDateTime.date()) { enddate = QDate(); return 0; } break; default: enddate = QDate(); return 0; } int count = 0; // default = error bool timed = false; switch (recurs) { case rMinutely: timed = true; count = secondlyCalc(func, endtime, rFreq*60); break; case rHourly: timed = true; count = secondlyCalc(func, endtime, rFreq*3600); break; case rDaily: count = dailyCalc(func, enddate); break; case rWeekly: count = weeklyCalc(func, enddate); break; case rMonthlyPos: case rMonthlyDay: count = monthlyCalc(func, enddate); break; case rYearlyMonth: count = yearlyMonthCalc(func, enddate); break; case rYearlyPos: count = yearlyPosCalc(func, enddate); break; case rYearlyDay: count = yearlyDayCalc(func, enddate); break; default: break; } switch (func) { case END_DATE_AND_COUNT: case NEXT_AFTER_DATE: if (count == 0) endtime = QDateTime(); else if (timed) enddate = endtime.date(); break; case COUNT_TO_DATE: break; } return count; } /* Find count and, depending on 'func', the end date/time of a secondly recurrence. * Reply = total number of occurrences up to 'endtime', or 0 if error. * If 'func' = END_DATE_AND_COUNT or NEXT_AFTER_DATE, 'endtime' is updated to the * recurrence end date/time. */ int Recurrence::secondlyCalc(PeriodFunc func, QDateTime &endtime, int freq) const { switch (func) { case END_DATE_AND_COUNT: endtime = mRecurStart.addSecs((rDuration + mRecurExDatesCount - 1) * freq); return rDuration + mRecurExDatesCount; case COUNT_TO_DATE: { int n = mRecurStart.secsTo(endtime)/freq + 1; if (rDuration > 0 && n > rDuration + mRecurExDatesCount) return rDuration + mRecurExDatesCount; return n; } case NEXT_AFTER_DATE: { int count = mRecurStart.secsTo(endtime) / freq + 2; if (rDuration > 0 && count > rDuration) return 0; endtime = mRecurStart.addSecs((count - 1)*freq); return count; } } return 0; } /* Find count and, depending on 'func', the end date of a daily recurrence. * Reply = total number of occurrences up to 'enddate', or 0 if error. * If 'func' = END_DATE_AND_COUNT or NEXT_AFTER_DATE, 'enddate' is updated to the * recurrence end date. */ int Recurrence::dailyCalc(PeriodFunc func, QDate &enddate) const { QDate dStart = mRecurStart.date(); switch (func) { case END_DATE_AND_COUNT: enddate = dStart.addDays((rDuration + mRecurExDatesCount - 1) * rFreq); return rDuration + mRecurExDatesCount; case COUNT_TO_DATE: { int n = dStart.daysTo(enddate)/rFreq + 1; if (rDuration > 0 && n > rDuration + mRecurExDatesCount) return rDuration + mRecurExDatesCount; return n; } case NEXT_AFTER_DATE: { int count = dStart.daysTo(enddate) / rFreq + 2; if (rDuration > 0 && count > rDuration) return 0; enddate = dStart.addDays((count - 1)*rFreq); return count; } } return 0; } /* Find count and, depending on 'func', the end date of a weekly recurrence. * Reply = total number of occurrences up to 'enddate', or 0 if error. * If 'func' = END_DATE_AND_COUNT or NEXT_AFTER_DATE, 'enddate' is updated to the * recurrence end date. */ int Recurrence::weeklyCalc(PeriodFunc func, QDate &enddate) const { int daysPerWeek = 0; for (int i = 0; i < 7; ++i) { if (rDays.testBit((uint)i)) ++daysPerWeek; } if (!daysPerWeek) return 0; // there are no days to recur on switch (func) { case END_DATE_AND_COUNT: return weeklyCalcEndDate(enddate, daysPerWeek); case COUNT_TO_DATE: return weeklyCalcToDate(enddate, daysPerWeek); case NEXT_AFTER_DATE: return weeklyCalcNextAfter(enddate, daysPerWeek); } return 0; } int Recurrence::weeklyCalcEndDate(QDate &enddate, int daysPerWeek) const { int startDayOfWeek = mRecurStart.date().dayOfWeek(); // 1..7 int countGone = 0; int daysGone = 0; uint countTogo = rDuration + mRecurExDatesCount; if (startDayOfWeek != rWeekStart) { // Check what remains of the start week for (int i = startDayOfWeek - 1; i != rWeekStart - 1; i = (i + 1) % 7) { ++daysGone; if (rDays.testBit((uint)i)) { ++countGone; if (--countTogo == 0) break; } } daysGone += 7 * (rFreq - 1); } if (countTogo) { // Skip the remaining whole weeks // Leave at least 1 recurrence remaining, in order to get its date int wholeWeeks = (countTogo - 1) / daysPerWeek; daysGone += wholeWeeks * 7 * rFreq; countGone += wholeWeeks * daysPerWeek; countTogo -= wholeWeeks * daysPerWeek; // Check the last week in the recurrence for (int i = rWeekStart - 1; ; i = (i + 1) % 7) { ++daysGone; if (rDays.testBit((uint)i)) { ++countGone; if (--countTogo == 0) break; } } } enddate = mRecurStart.date().addDays(daysGone); return countGone; } int Recurrence::weeklyCalcToDate(const QDate &enddate, int daysPerWeek) const { QDate dStart = mRecurStart.date(); int startDayOfWeek = dStart.dayOfWeek(); // 1..7 int countGone = 0; int daysGone = 0; int totalDays = dStart.daysTo(enddate) + 1; int countMax = (rDuration > 0) ? rDuration + mRecurExDatesCount : INT_MAX; if (startDayOfWeek != rWeekStart) { // Check what remains of the start week for (int i = startDayOfWeek - 1; i != rWeekStart - 1; i = (i + 1) % 7) { if (rDays.testBit((uint)i)) { if (++countGone >= countMax) return countMax; } if (++daysGone == totalDays) return countGone; } daysGone += 7 * (rFreq - 1); if (daysGone >= totalDays) return countGone; } // Skip the remaining whole weeks int wholeWeeks = (totalDays - daysGone) / 7; countGone += (wholeWeeks / rFreq) * daysPerWeek; if (countGone >= countMax) return countMax; daysGone += wholeWeeks * 7; if (daysGone >= totalDays // have we reached the end date? || wholeWeeks % rFreq) // is end week a recurrence week? return countGone; // Check the last week in the recurrence for (int i = rWeekStart - 1; ; i = (i + 1) % 7) { if (rDays.testBit((uint)i)) { if (++countGone >= countMax) return countMax; } if (++daysGone == totalDays) return countGone; } return countGone; } int Recurrence::weeklyCalcNextAfter(QDate &enddate, int daysPerWeek) const { QDate dStart = mRecurStart.date(); int startDayOfWeek = dStart.dayOfWeek(); // 1..7 int totalDays = dStart.daysTo(enddate) + 1; uint countTogo = (rDuration > 0) ? rDuration + mRecurExDatesCount : UINT_MAX; int countGone = 0; int daysGone = 0; int recurWeeks; if (startDayOfWeek != rWeekStart) { // Check what remains of the start week for (int i = startDayOfWeek - 1; i != rWeekStart - 1; i = (i + 1) % 7) { ++daysGone; if (rDays.testBit((uint)i)) { ++countGone; if (daysGone > totalDays) goto ex; if (--countTogo == 0) return 0; } } daysGone += 7 * (rFreq - 1); } // Skip the remaining whole weeks recurWeeks = (totalDays - daysGone) / (7 * rFreq); if (recurWeeks) { int n = recurWeeks * daysPerWeek; if (static_cast(n) > countTogo) return 0; // reached end of recurrence countGone += n; countTogo -= n; daysGone += recurWeeks * 7 * rFreq; } // Check the last week or two in the recurrence for ( ; ; ) { for (int i = rWeekStart - 1; ; i = (i + 1) % 7) { ++daysGone; if (rDays.testBit((uint)i)) { ++countGone; if (daysGone > totalDays) goto ex; if (--countTogo == 0) return 0; } } daysGone += 7 * (rFreq - 1); } ex: enddate = dStart.addDays(daysGone); return countGone; } /* Find count and, depending on 'func', the end date of a monthly recurrence. * Reply = total number of occurrences up to 'enddate', or 0 if error. * If 'func' = END_DATE_AND_COUNT or NEXT_AFTER_DATE, 'enddate' is updated to the * recurrence end date. */ struct Recurrence::MonthlyData { const Recurrence *recurrence; int year; // current year int month; // current month 0..11 int day; // current day of month 1..31 bool varies; // true if recurring days vary between different months private: Q3ValueList days28, days29, days30, days31; // recurring days in months of each length Q3ValueList *recurDays[4]; public: MonthlyData(const Recurrence* r, const QDate &date) : recurrence(r), year(date.year()), month(date.month()-1), day(date.day()) { recurDays[0] = &days28; recurDays[1] = &days29; recurDays[2] = &days30; recurDays[3] = &days31; varies = (recurrence->recurs == rMonthlyPos) ? true : recurrence->getMonthlyDayDays(days31, 31); } const Q3ValueList* dayList() const { if (!varies) return &days31; QDate startOfMonth(year, month + 1, 1); int daysInMonth = startOfMonth.daysInMonth(); Q3ValueList* days = recurDays[daysInMonth - 28]; if (recurrence->recurs == rMonthlyPos) recurrence->getMonthlyPosDays(*days, daysInMonth, startOfMonth.dayOfWeek()); else if (days->isEmpty()) recurrence->getMonthlyDayDays(*days, daysInMonth); return days; } int yearMonth() const { return year*12 + month; } void addMonths(int diff) { month += diff; year += month / 12; month %= 12; } QDate date() const { return QDate(year, month + 1, day); } }; int Recurrence::monthlyCalc(PeriodFunc func, QDate &enddate) const { if (recurs == rMonthlyPos && rMonthPositions.isEmpty() || recurs == rMonthlyDay && rMonthDays.isEmpty()) return 0; MonthlyData data(this, mRecurStart.date()); switch (func) { case END_DATE_AND_COUNT: return monthlyCalcEndDate(enddate, data); case COUNT_TO_DATE: return monthlyCalcToDate(enddate, data); case NEXT_AFTER_DATE: return monthlyCalcNextAfter(enddate, data); } return 0; } int Recurrence::monthlyCalcEndDate(QDate &enddate, MonthlyData &data) const { uint countTogo = rDuration + mRecurExDatesCount; int countGone = 0; Q3ValueList::ConstIterator it; const Q3ValueList* days = data.dayList(); if (data.day > 1) { // Check what remains of the start month for (it = days->begin(); it != days->end(); ++it) { if (*it >= data.day) { ++countGone; if (--countTogo == 0) { data.day = *it; break; } } } if (countTogo) { data.day = 1; data.addMonths(rFreq); } } if (countTogo) { if (data.varies) { // The number of recurrence days varies from month to month, // so we need to check month by month. for ( ; ; ) { days = data.dayList(); uint n = days->count(); // number of recurrence days in this month if (n >= countTogo) break; countTogo -= n; countGone += n; data.addMonths(rFreq); } } else { // The number of recurrences is the same every month, // so skip the month-by-month check. // Skip the remaining whole months, but leave at least // 1 recurrence remaining, in order to get its date. int daysPerMonth = days->count(); int wholeMonths = (countTogo - 1) / daysPerMonth; data.addMonths(wholeMonths * rFreq); countGone += wholeMonths * daysPerMonth; countTogo -= wholeMonths * daysPerMonth; } if (countTogo) { // Check the last month in the recurrence for (it = days->begin(); it != days->end(); ++it) { ++countGone; if (--countTogo == 0) { data.day = *it; break; } } } } enddate = data.date(); return countGone; } int Recurrence::monthlyCalcToDate(const QDate &enddate, MonthlyData &data) const { int countGone = 0; int countMax = (rDuration > 0) ? rDuration + mRecurExDatesCount : INT_MAX; int endYear = enddate.year(); int endMonth = enddate.month() - 1; // zero-based int endDay = enddate.day(); int endYearMonth = endYear*12 + endMonth; Q3ValueList::ConstIterator it; const Q3ValueList* days = data.dayList(); if (data.day > 1) { // Check what remains of the start month for (it = days->begin(); it != days->end(); ++it) { if (*it >= data.day) { if (data.yearMonth() == endYearMonth && *it > endDay) return countGone; if (++countGone >= countMax) return countMax; } } data.day = 1; data.addMonths(rFreq); } if (data.varies) { // The number of recurrence days varies from month to month, // so we need to check month by month. while (data.yearMonth() < endYearMonth) { countGone += data.dayList()->count(); if (countGone >= countMax) return countMax; data.addMonths(rFreq); } days = data.dayList(); } else { // The number of recurrences is the same every month, // so skip the month-by-month check. // Skip the remaining whole months. int daysPerMonth = days->count(); int wholeMonths = endYearMonth - data.yearMonth(); countGone += (wholeMonths / rFreq) * daysPerMonth; if (countGone >= countMax) return countMax; if (wholeMonths % rFreq) return countGone; // end year isn't a recurrence year data.year = endYear; data.month = endMonth; } // Check the last month in the recurrence for (it = days->begin(); it != days->end(); ++it) { if (*it > endDay) return countGone; if (++countGone >= countMax) return countMax; } return countGone; } int Recurrence::monthlyCalcNextAfter(QDate &enddate, MonthlyData &data) const { uint countTogo = (rDuration > 0) ? rDuration + mRecurExDatesCount : UINT_MAX; int countGone = 0; int endYear = enddate.year(); int endDay = enddate.day(); int endYearMonth = endYear*12 + enddate.month() - 1; Q3ValueList::ConstIterator it; const Q3ValueList* days = data.dayList(); if (data.day > 1) { // Check what remains of the start month for (it = days->begin(); it != days->end(); ++it) { if (*it >= data.day) { ++countGone; if (data.yearMonth() == endYearMonth && *it > endDay) { data.day = *it; goto ex; } if (--countTogo == 0) return 0; } } data.day = 1; data.addMonths(rFreq); } if (data.varies) { // The number of recurrence days varies from month to month, // so we need to check month by month. while (data.yearMonth() <= endYearMonth) { days = data.dayList(); uint n = days->count(); // number of recurrence days in this month if (data.yearMonth() == endYearMonth && days->last() > endDay) break; if (n >= countTogo) return 0; countGone += n; countTogo -= n; data.addMonths(rFreq); } days = data.dayList(); } else { // The number of recurrences is the same every month, // so skip the month-by-month check. // Skip the remaining whole months to at least end year/month. int daysPerMonth = days->count(); int elapsed = endYearMonth - data.yearMonth(); int recurMonths = (elapsed + rFreq - 1) / rFreq; if (elapsed % rFreq == 0 && days->last() <= endDay) ++recurMonths; // required month is after endYearMonth if (recurMonths) { int n = recurMonths * daysPerMonth; if (static_cast(n) > countTogo) return 0; // reached end of recurrence countTogo -= n; countGone += n; data.addMonths(recurMonths * rFreq); } } // Check the last month in the recurrence for (it = days->begin(); it != days->end(); ++it) { ++countGone; if (data.yearMonth() > endYearMonth || *it > endDay) { data.day = *it; break; } if (--countTogo == 0) return 0; } ex: enddate = data.date(); return countGone; } /* Find count and, depending on 'func', the end date of an annual recurrence by date. * Reply = total number of occurrences up to 'enddate', or 0 if error. * If 'func' = END_DATE_AND_COUNT or NEXT_AFTER_DATE, 'enddate' is updated to the * recurrence end date. */ struct Recurrence::YearlyMonthData { const Recurrence *recurrence; int year; // current year int month; // current month 1..12 int day; // current day of month 1..31 bool leapyear; // true if February 29th recurs and current year is a leap year bool feb29; // true if February 29th recurs private: Q3ValueList months; // recurring months in non-leap years 1..12 Q3ValueList leapMonths; // recurring months in leap years 1..12 public: YearlyMonthData(const Recurrence* r, const QDate &date) : recurrence(r), year(date.year()), month(date.month()), day(date.day()) { feb29 = recurrence->getYearlyMonthMonths(day, months, leapMonths); leapyear = feb29 && QDate::leapYear(year); } const Q3ValueList* monthList() const { return leapyear ? &leapMonths : &months; } const Q3ValueList* leapMonthList() const { return &leapMonths; } QDate date() const { return QDate(year, month, day); } }; int Recurrence::yearlyMonthCalc(PeriodFunc func, QDate &enddate) const { if (rYearNums.isEmpty()) return 0; YearlyMonthData data(this, mRecurStart.date()); switch (func) { case END_DATE_AND_COUNT: return yearlyMonthCalcEndDate(enddate, data); case COUNT_TO_DATE: return yearlyMonthCalcToDate(enddate, data); case NEXT_AFTER_DATE: return yearlyMonthCalcNextAfter(enddate, data); } return 0; } // Find total count and end date of an annual recurrence by date. // Reply = total number of occurrences. int Recurrence::yearlyMonthCalcEndDate(QDate &enddate, YearlyMonthData &data) const { uint countTogo = rDuration + mRecurExDatesCount; int countGone = 0; Q3ValueList::ConstIterator it; const Q3ValueList* mons = data.monthList(); // get recurring months for this year if (data.month > 1) { // Check what remains of the start year for (it = mons->begin(); it != mons->end(); ++it) { if (*it >= data.month) { ++countGone; if (--countTogo == 0) { data.month = *it; if (data.month == 2 && data.feb29 && !data.leapyear) { // The recurrence should end on February 29th, but it's a non-leap year switch (mFeb29YearlyType) { case rFeb28: data.day = 28; break; case rMar1: data.month = 3; data.day = 1; break; case rFeb29: break; } } break; } } } if (countTogo) { data.month = 1; data.year += rFreq; } } if (countTogo) { if (data.feb29 && mFeb29YearlyType == rFeb29) { // The number of recurrences is different on leap years, // so check year-by-year. for ( ; ; ) { mons = data.monthList(); uint n = mons->count(); if (n >= countTogo) break; countTogo -= n; countGone += n; data.year += rFreq; } } else { // The number of recurrences is the same every year, // so skip the year-by-year check. // Skip the remaining whole years, but leave at least // 1 recurrence remaining, in order to get its date. int monthsPerYear = mons->count(); int wholeYears = (countTogo - 1) / monthsPerYear; data.year += wholeYears * rFreq; countGone += wholeYears * monthsPerYear; countTogo -= wholeYears * monthsPerYear; } if (countTogo) { // Check the last year in the recurrence for (it = mons->begin(); it != mons->end(); ++it) { ++countGone; if (--countTogo == 0) { data.month = *it; if (data.month == 2 && data.feb29 && !QDate::leapYear(data.year)) { // The recurrence should end on February 29th, but it's a non-leap year switch (mFeb29YearlyType) { case rFeb28: data.day = 28; break; case rMar1: data.month = 3; data.day = 1; break; case rFeb29: break; } } break; } } } } enddate = data.date(); return countGone; } // Find count of an annual recurrence by date. // Reply = total number of occurrences up to 'enddate'. int Recurrence::yearlyMonthCalcToDate(const QDate &enddate, YearlyMonthData &data) const { int countGone = 0; int countMax = (rDuration > 0) ? rDuration + mRecurExDatesCount : INT_MAX; int endYear = enddate.year(); int endMonth = enddate.month(); int endDay = enddate.day(); if (endDay < data.day) { /* The end day of the month is earlier than the recurrence day of the month. * If Feb 29th recurs and: * 1) it recurs on Feb 28th in non-leap years, don't adjust the end month * if enddate is Feb 28th on a non-leap year. * 2) it recurs on Mar 1st in non-leap years, allow the end month to be * adjusted to February, to simplify calculations. */ if (data.feb29 && !QDate::leapYear(endYear) && mFeb29YearlyType == rFeb28 && endDay == 28 && endMonth == 2) { } else if (--endMonth == 0) { endMonth = 12; --endYear; } } Q3ValueList::ConstIterator it; const Q3ValueList* mons = data.monthList(); if (data.month > 1) { // Check what remains of the start year for (it = mons->begin(); it != mons->end(); ++it) { if (*it >= data.month) { if (data.year == endYear && *it > endMonth) return countGone; if (++countGone >= countMax) return countMax; } } data.month = 1; data.year += rFreq; } if (data.feb29 && mFeb29YearlyType == rFeb29) { // The number of recurrences is different on leap years, // so check year-by-year. while (data.year < endYear) { countGone += data.monthList()->count(); if (countGone >= countMax) return countMax; data.year += rFreq; } mons = data.monthList(); } else { // The number of recurrences is the same every year, // so skip the year-by-year check. // Skip the remaining whole years. int monthsPerYear = mons->count(); int wholeYears = endYear - data.year; countGone += (wholeYears / rFreq) * monthsPerYear; if (countGone >= countMax) return countMax; if (wholeYears % rFreq) return countGone; // end year isn't a recurrence year data.year = endYear; } // Check the last year in the recurrence for (it = mons->begin(); it != mons->end(); ++it) { if (*it > endMonth) return countGone; if (++countGone >= countMax) return countMax; } return countGone; } // Find count and date of first recurrence after 'enddate' of an annual recurrence by date. // Reply = total number of occurrences up to 'enddate'. int Recurrence::yearlyMonthCalcNextAfter(QDate &enddate, YearlyMonthData &data) const { uint countTogo = (rDuration > 0) ? rDuration + mRecurExDatesCount : UINT_MAX; int countGone = 0; int endYear = enddate.year(); int endMonth = enddate.month(); int endDay = enddate.day(); bool mar1TooEarly = false; bool feb28ok = false; if (endDay < data.day) { if (data.feb29 && mFeb29YearlyType == rMar1 && endMonth == 3) mar1TooEarly = true; if (data.feb29 && mFeb29YearlyType == rFeb28 && endMonth == 2 && endDay == 28) feb28ok = true; else if (--endMonth == 0) { endMonth = 12; --endYear; } } Q3ValueList::ConstIterator it; const Q3ValueList* mons = data.monthList(); if (data.month > 1) { // Check what remains of the start year for (it = mons->begin(); it != mons->end(); ++it) { if (*it >= data.month) { ++countGone; if (data.year == endYear && ( *it > endMonth && (*it > 3 || !mar1TooEarly) || *it == 2 && feb28ok && data.leapyear)) { if (*it == 2 && data.feb29 && !data.leapyear) { // The next recurrence should be on February 29th, but it's a non-leap year switch (mFeb29YearlyType) { case rFeb28: data.month = 2; data.day = 28; break; case rMar1: data.month = 3; data.day = 1; break; case rFeb29: // impossible in this context! break; } } else data.month = *it; goto ex; } if (--countTogo == 0) return 0; } } data.month = 1; data.year += rFreq; } if (data.feb29 && mFeb29YearlyType == rFeb29) { // The number of recurrences is different on leap years, // so check year-by-year. while (data.year <= endYear) { mons = data.monthList(); if (data.year == endYear && mons->last() > endMonth) break; uint n = mons->count(); if (n >= countTogo) break; countTogo -= n; countGone += n; data.year += rFreq; } mons = data.monthList(); } else { // The number of recurrences is the same every year, // so skip the year-by-year check. // Skip the remaining whole years to at least endYear. int monthsPerYear = mons->count(); int recurYears = (endYear - data.year + rFreq - 1) / rFreq; if ((endYear - data.year)%rFreq == 0 && mons->last() <= endMonth) ++recurYears; // required year is after endYear if (recurYears) { int n = recurYears * monthsPerYear; if (static_cast(n) > countTogo) return 0; // reached end of recurrence countTogo -= n; countGone += n; data.year += recurYears * rFreq; } } // Check the last year in the recurrence for (it = mons->begin(); it != mons->end(); ++it) { ++countGone; if (data.year > endYear || ( *it > endMonth && (*it > 3 || !mar1TooEarly) || *it == 2 && feb28ok && QDate::leapYear(data.year))) { if (*it == 2 && data.feb29 && !QDate::leapYear(data.year)) { // The next recurrence should be on February 29th, but it's a non-leap year switch (mFeb29YearlyType) { case rFeb28: data.month = 2; data.day = 28; break; case rMar1: data.month = 3; data.day = 1; break; case rFeb29: // impossible in this context! break; } } else data.month = *it; break; } if (--countTogo == 0) return 0; } ex: enddate = data.date(); return countGone; } /* Find count and, depending on 'func', the end date of an annual recurrence by date. * Reply = total number of occurrences up to 'enddate', or 0 if error. * If 'func' = END_DATE_AND_COUNT or NEXT_AFTER_DATE, 'enddate' is updated to the * recurrence end date. */ struct Recurrence::YearlyPosData { const Recurrence *recurrence; int year; // current year int month; // current month 1..12 int day; // current day of month 1..31 int daysPerMonth; // number of days which recur each month, or -1 if variable int count; // number of days which recur each year, or -1 if variable bool varies; // true if number of days varies from year to year private: mutable Q3ValueList days; public: YearlyPosData(const Recurrence* r, const QDate &date) : recurrence(r), year(date.year()), month(date.month()), day(date.day()), count(-1) { if ((daysPerMonth = r->countMonthlyPosDays()) > 0) count = daysPerMonth * r->rYearNums.count(); varies = (daysPerMonth < 0); } const Q3ValueList* dayList() const { QDate startOfMonth(year, month, 1); recurrence->getMonthlyPosDays(days, startOfMonth.daysInMonth(), startOfMonth.dayOfWeek()); return &days; } int yearMonth() const { return year*12 + month - 1; } void addMonths(int diff) { month += diff - 1; year += month / 12; month = month % 12 + 1; } QDate date() const { return QDate(year, month, day); } }; int Recurrence::yearlyPosCalc(PeriodFunc func, QDate &enddate) const { if (rYearNums.isEmpty() || rMonthPositions.isEmpty()) return 0; YearlyPosData data(this, mRecurStart.date()); switch (func) { case END_DATE_AND_COUNT: return yearlyPosCalcEndDate(enddate, data); case COUNT_TO_DATE: return yearlyPosCalcToDate(enddate, data); case NEXT_AFTER_DATE: return yearlyPosCalcNextAfter(enddate, data); } return 0; } int Recurrence::yearlyPosCalcEndDate(QDate &enddate, YearlyPosData &data) const { uint countTogo = rDuration + mRecurExDatesCount; int countGone = 0; Q3ValueList::ConstIterator id; const Q3ValueList* days; if (data.month > 1 || data.day > 1) { // Check what remains of the start year for (Q3PtrListIterator im(rYearNums); im.current(); ++im) { if (*im.current() >= data.month) { // Check what remains of the start month if (data.day > 1 || data.varies || static_cast(data.daysPerMonth) >= countTogo) { data.month = *im.current(); days = data.dayList(); for (id = days->begin(); id != days->end(); ++id) { if (*id >= data.day) { ++countGone; if (--countTogo == 0) { data.month = *im.current(); data.day = *id; goto ex; } } } data.day = 1; } else { // The number of days per month is constant, so skip // the whole month. countTogo -= data.daysPerMonth; countGone += data.daysPerMonth; } } } data.month = 1; data.year += rFreq; } if (data.varies) { // The number of recurrences varies from year to year. for ( ; ; ) { for (Q3PtrListIterator im(rYearNums); im.current(); ++im) { data.month = *im.current(); days = data.dayList(); int n = days->count(); if (static_cast(n) >= countTogo) { // Check the last month in the recurrence for (id = days->begin(); id != days->end(); ++id) { ++countGone; if (--countTogo == 0) { data.day = *id; goto ex; } } } countTogo -= n; countGone += n; } data.year += rFreq; } } else { // The number of recurrences is the same every year, // so skip the year-by-year check. // Skip the remaining whole years, but leave at least // 1 recurrence remaining, in order to get its date. int wholeYears = (countTogo - 1) / data.count; data.year += wholeYears * rFreq; countGone += wholeYears * data.count; countTogo -= wholeYears * data.count; // Check the last year in the recurrence. for (Q3PtrListIterator im(rYearNums); im.current(); ++im) { if (static_cast(data.daysPerMonth) >= countTogo) { // Check the last month in the recurrence data.month = *im.current(); days = data.dayList(); for (id = days->begin(); id != days->end(); ++id) { ++countGone; if (--countTogo == 0) { data.day = *id; goto ex; } } } countTogo -= data.daysPerMonth; countGone += data.daysPerMonth; } data.year += rFreq; } ex: enddate = data.date(); return countGone; } int Recurrence::yearlyPosCalcToDate(const QDate &enddate, YearlyPosData &data) const { int countGone = 0; int countMax = (rDuration > 0) ? rDuration + mRecurExDatesCount : INT_MAX; int endYear = enddate.year(); int endMonth = enddate.month(); int endDay = enddate.day(); if (endDay < data.day && --endMonth == 0) { endMonth = 12; --endYear; } int endYearMonth = endYear*12 + endMonth; Q3ValueList::ConstIterator id; const Q3ValueList* days; if (data.month > 1 || data.day > 1) { // Check what remains of the start year for (Q3PtrListIterator im(rYearNums); im.current(); ++im) { if (*im.current() >= data.month) { data.month = *im.current(); if (data.yearMonth() > endYearMonth) return countGone; // Check what remains of the start month bool lastMonth = (data.yearMonth() == endYearMonth); if (lastMonth || data.day > 1 || data.varies) { days = data.dayList(); if (lastMonth || data.day > 1) { for (id = days->begin(); id != days->end(); ++id) { if (*id >= data.day) { if (lastMonth && *id > endDay) return countGone; if (++countGone >= countMax) return countMax; } } } else { countGone += days->count(); if (countGone >= countMax) return countMax; } data.day = 1; } else { // The number of days per month is constant, so skip // the whole month. countGone += data.daysPerMonth; if (countGone >= countMax) return countMax; } } } data.month = 1; data.year += rFreq; } if (data.varies) { // The number of recurrences varies from year to year. for ( ; ; ) { for (Q3PtrListIterator im(rYearNums); im.current(); ++im) { data.month = *im.current(); days = data.dayList(); if (data.yearMonth() >= endYearMonth) { if (data.yearMonth() > endYearMonth) return countGone; // Check the last month in the recurrence for (id = days->begin(); id != days->end(); ++id) { if (*id > endDay) return countGone; if (++countGone >= countMax) return countMax; } } else { countGone += days->count(); if (countGone >= countMax) return countMax; } } data.year += rFreq; } } else { // The number of recurrences is the same every year, // so skip the year-by-year check. // Skip the remaining whole years, but leave at least // 1 recurrence remaining, in order to get its date. int wholeYears = endYear - data.year; countGone += (wholeYears / rFreq) * data.count; if (countGone >= countMax) return countMax; if (wholeYears % rFreq) return countGone; // end year isn't a recurrence year data.year = endYear; // Check the last year in the recurrence. for (Q3PtrListIterator im(rYearNums); im.current(); ++im) { data.month = *im.current(); if (data.month >= endMonth) { if (data.month > endMonth) return countGone; // Check the last month in the recurrence days = data.dayList(); for (id = days->begin(); id != days->end(); ++id) { if (*id > endDay) return countGone; if (++countGone >= countMax) return countMax; } } else { countGone += data.daysPerMonth; if (countGone >= countMax) return countMax; } } } return countGone; } int Recurrence::yearlyPosCalcNextAfter(QDate &enddate, YearlyPosData &data) const { uint countTogo = (rDuration > 0) ? rDuration + mRecurExDatesCount : UINT_MAX; int countGone = 0; int endYear = enddate.year(); int endMonth = enddate.month(); int endDay = enddate.day(); if (endDay < data.day && --endMonth == 0) { endMonth = 12; --endYear; } int endYearMonth = endYear*12 + endMonth; Q3ValueList::ConstIterator id; const Q3ValueList* days; if (data.varies) { // The number of recurrences varies from year to year. for ( ; ; ) { // Check the next year for (Q3PtrListIterator im(rYearNums); im.current(); ++im) { if (*im.current() >= data.month) { // Check the next month data.month = *im.current(); int ended = data.yearMonth() - endYearMonth; days = data.dayList(); if (ended >= 0 || data.day > 1) { // This is the start or end month, so check each day for (id = days->begin(); id != days->end(); ++id) { if (*id >= data.day) { ++countGone; if (ended > 0 || (ended == 0 && *id > endDay)) { data.day = *id; goto ex; } if (--countTogo == 0) return 0; } } } else { // Skip the whole month uint n = days->count(); if (n >= countTogo) return 0; countGone += n; } data.day = 1; // we've checked the start month now } } data.month = 1; // we've checked the start year now data.year += rFreq; } } else { // The number of recurrences is the same every year. if (data.month > 1 || data.day > 1) { // Check what remains of the start year for (Q3PtrListIterator im(rYearNums); im.current(); ++im) { if (*im.current() >= data.month) { // Check what remains of the start month data.month = *im.current(); int ended = data.yearMonth() - endYearMonth; if (ended >= 0 || data.day > 1) { // This is the start or end month, so check each day days = data.dayList(); for (id = days->begin(); id != days->end(); ++id) { if (*id >= data.day) { ++countGone; if (ended > 0 || (ended == 0 && *id > endDay)) { data.day = *id; goto ex; } if (--countTogo == 0) return 0; } } data.day = 1; // we've checked the start month now } else { // Skip the whole month. if (static_cast(data.daysPerMonth) >= countTogo) return 0; countGone += data.daysPerMonth; } } } data.year += rFreq; } // Skip the remaining whole years to at least endYear. int recurYears = (endYear - data.year + rFreq - 1) / rFreq; if ((endYear - data.year)%rFreq == 0 && *rYearNums.getLast() <= endMonth) ++recurYears; // required year is after endYear if (recurYears) { int n = recurYears * data.count; if (static_cast(n) > countTogo) return 0; // reached end of recurrence countTogo -= n; countGone += n; data.year += recurYears * rFreq; } // Check the last year in the recurrence for (Q3PtrListIterator im(rYearNums); im.current(); ++im) { data.month = *im.current(); int ended = data.yearMonth() - endYearMonth; if (ended >= 0) { // This is the end month, so check each day days = data.dayList(); for (id = days->begin(); id != days->end(); ++id) { ++countGone; if (ended > 0 || (ended == 0 && *id > endDay)) { data.day = *id; goto ex; } if (--countTogo == 0) return 0; } } else { // Skip the whole month. if (static_cast(data.daysPerMonth) >= countTogo) return 0; countGone += data.daysPerMonth; } } } ex: enddate = data.date(); return countGone; } /* Find count and, depending on 'func', the end date of an annual recurrence by day. * Reply = total number of occurrences up to 'enddate', or 0 if error. * If 'func' = END_DATE_AND_COUNT or NEXT_AFTER_DATE, 'enddate' is updated to the * recurrence end date. */ struct Recurrence::YearlyDayData { int year; // current year int day; // current day of year 1..366 bool varies; // true if day 366 recurs private: int daycount; public: YearlyDayData(const Recurrence* r, const QDate &date) : year(date.year()), day(date.dayOfYear()), varies(*r->rYearNums.getLast() == 366), daycount(r->rYearNums.count()) { } bool leapYear() const { return QDate::leapYear(year); } int dayCount() const { return daycount - (varies && !QDate::leapYear(year) ? 1 : 0); } bool isMaxDayCount() const { return !varies || QDate::leapYear(year); } QDate date() const { return QDate(year, 1, 1).addDays(day - 1); } }; int Recurrence::yearlyDayCalc(PeriodFunc func, QDate &enddate) const { if (rYearNums.isEmpty()) return 0; YearlyDayData data(this, mRecurStart.date()); switch (func) { case END_DATE_AND_COUNT: return yearlyDayCalcEndDate(enddate, data); case COUNT_TO_DATE: return yearlyDayCalcToDate(enddate, data); case NEXT_AFTER_DATE: return yearlyDayCalcNextAfter(enddate, data); } return 0; } int Recurrence::yearlyDayCalcEndDate(QDate &enddate, YearlyDayData &data) const { uint countTogo = rDuration + mRecurExDatesCount; int countGone = 0; if (data.day > 1) { // Check what remains of the start year bool leapOK = data.isMaxDayCount(); for (Q3PtrListIterator it(rYearNums); it.current(); ++it) { int d = *it.current(); if (d >= data.day && (leapOK || d < 366)) { ++countGone; if (--countTogo == 0) { data.day = d; goto ex; } } } data.day = 1; data.year += rFreq; } if (data.varies) { // The number of recurrences is different in leap years, // so check year-by-year. for ( ; ; ) { uint n = data.dayCount(); if (n >= countTogo) break; countTogo -= n; countGone += n; data.year += rFreq; } } else { // The number of recurrences is the same every year, // so skip the year-by-year check. // Skip the remaining whole years, but leave at least // 1 recurrence remaining, in order to get its date. int daysPerYear = rYearNums.count(); int wholeYears = (countTogo - 1) / daysPerYear; data.year += wholeYears * rFreq; countGone += wholeYears * daysPerYear; countTogo -= wholeYears * daysPerYear; } if (countTogo) { // Check the last year in the recurrence for (Q3PtrListIterator it(rYearNums); it.current(); ++it) { ++countGone; if (--countTogo == 0) { data.day = *it.current(); break; } } } ex: enddate = data.date(); return countGone; } int Recurrence::yearlyDayCalcToDate(const QDate &enddate, YearlyDayData &data) const { int countGone = 0; int countMax = (rDuration > 0) ? rDuration + mRecurExDatesCount : INT_MAX; int endYear = enddate.year(); int endDay = enddate.dayOfYear(); if (data.day > 1) { // Check what remains of the start year bool leapOK = data.isMaxDayCount(); for (Q3PtrListIterator it(rYearNums); it.current(); ++it) { int d = *it.current(); if (d >= data.day && (leapOK || d < 366)) { if (data.year == endYear && d > endDay) return countGone; if (++countGone >= countMax) return countMax; } } data.day = 1; data.year += rFreq; } if (data.varies) { // The number of recurrences is different in leap years, // so check year-by-year. while (data.year < endYear) { uint n = data.dayCount(); countGone += n; if (countGone >= countMax) return countMax; data.year += rFreq; } if (data.year > endYear) return countGone; } else { // The number of recurrences is the same every year. // Skip the remaining whole years. int wholeYears = endYear - data.year; countGone += (wholeYears / rFreq) * rYearNums.count(); if (countGone >= countMax) return countMax; if (wholeYears % rFreq) return countGone; // end year isn't a recurrence year data.year = endYear; } if (data.year <= endYear) { // Check the last year in the recurrence for (Q3PtrListIterator it(rYearNums); it.current(); ++it) { if (*it.current() > endDay) return countGone; if (++countGone >= countMax) return countMax; } } return countGone; } int Recurrence::yearlyDayCalcNextAfter(QDate &enddate, YearlyDayData &data) const { uint countTogo = (rDuration > 0) ? rDuration + mRecurExDatesCount : UINT_MAX; int countGone = 0; int endYear = enddate.year(); int endDay = enddate.dayOfYear(); if (data.day > 1) { // Check what remains of the start year bool leapOK = data.isMaxDayCount(); for (Q3PtrListIterator it(rYearNums); it.current(); ++it) { int d = *it.current(); if (d >= data.day && (leapOK || d < 366)) { ++countGone; if (data.year == endYear && d > endDay) { data.day = d; goto ex; } if (--countTogo == 0) return 0; } } data.day = 1; data.year += rFreq; } if (data.varies) { // The number of recurrences is different in leap years, // so check year-by-year. while (data.year <= endYear) { uint n = data.dayCount(); if (data.year == endYear && *rYearNums.getLast() > endDay) break; if (n >= countTogo) break; countTogo -= n; countGone += n; data.year += rFreq; } } else { // The number of recurrences is the same every year, // so skip the year-by-year check. // Skip the remaining whole years to at least endYear. int daysPerYear = rYearNums.count(); int recurYears = (endYear - data.year + rFreq - 1) / rFreq; if ((endYear - data.year)%rFreq == 0 && *rYearNums.getLast() <= endDay) ++recurYears; // required year is after endYear if (recurYears) { int n = recurYears * daysPerYear; if (static_cast(n) > countTogo) return 0; // reached end of recurrence countTogo -= n; countGone += n; data.year += recurYears * rFreq; } } // Check the last year in the recurrence for (Q3PtrListIterator it(rYearNums); it.current(); ++it) { ++countGone; int d = *it.current(); if (data.year > endYear || d > endDay) { data.day = d; break; } if (--countTogo == 0) return 0; } ex: enddate = data.date(); return countGone; } // Get the days in this month which recur, in numerical order. // Parameters: daysInMonth = number of days in this month // startDayOfWeek = day of week for first day of month. void Recurrence::getMonthlyPosDays(Q3ValueList &list, int daysInMonth, int startDayOfWeek) const { list.clear(); int endDayOfWeek = (startDayOfWeek + daysInMonth - 2) % 7 + 1; // Go through the list, compiling a bit list of actual day numbers Q_UINT32 days = 0; for (Q3PtrListIterator pos(rMonthPositions); pos.current(); ++pos) { int weeknum = pos.current()->rPos - 1; // get 0-based week number QBitArray &rdays = pos.current()->rDays; if (pos.current()->negative) { // nth days before the end of the month for (uint i = 1; i <= 7; ++i) { if (rdays.testBit(i - 1)) { int day = daysInMonth - weeknum*7 - (endDayOfWeek - i + 7) % 7; if (day > 0) days |= 1 << (day - 1); } } } else { // nth days after the start of the month for (uint i = 1; i <= 7; ++i) { if (rdays.testBit(i - 1)) { int day = 1 + weeknum*7 + (i - startDayOfWeek + 7) % 7; if (day <= daysInMonth) days |= 1 << (day - 1); } } } } // Compile the ordered list Q_UINT32 mask = 1; for (int i = 0; i < daysInMonth; mask <<= 1, ++i) { if (days & mask) list.append(i + 1); } } // Get the number of days in the month which recur. // Reply = -1 if the number varies from month to month. int Recurrence::countMonthlyPosDays() const { int count = 0; Q_UINT8 positive[5] = { 0, 0, 0, 0, 0 }; Q_UINT8 negative[4] = { 0, 0, 0, 0 }; for (Q3PtrListIterator pos(rMonthPositions); pos.current(); ++pos) { int weeknum = pos.current()->rPos; Q_UINT8* wk; if (pos.current()->negative) { // nth days before the end of the month if (weeknum > 4) return -1; // days in 5th week are often missing wk = &negative[4 - weeknum]; } else { // nth days after the start of the month if (weeknum > 4) return -1; // days in 5th week are often missing wk = &positive[weeknum - 1]; } QBitArray &rdays = pos.current()->rDays; for (uint i = 0; i < 7; ++i) { if (rdays.testBit(i)) { ++count; *wk |= (1 << i); } } } // Check for any possible days which could be duplicated by // a positive and a negative position. for (int i = 0; i < 4; ++i) { if (negative[i] & (positive[i] | positive[i+1])) return -1; } return count; } // Get the days in this month which recur, in numerical order. // Reply = true if day numbers varies from month to month. bool Recurrence::getMonthlyDayDays(Q3ValueList &list, int daysInMonth) const { list.clear(); bool variable = false; Q_UINT32 days = 0; for (Q3PtrListIterator it(rMonthDays); it.current(); ++it) { int day = *it.current(); if (day > 0) { // date in the month if (day <= daysInMonth) days |= 1 << (day - 1); if (day > 28 && day <= 31) variable = true; // this date does not appear in some months } else if (day < 0) { // days before the end of the month variable = true; // this date varies depending on the month length day = daysInMonth + day; // zero-based day of month if (day >= 0) days |= 1 << day; } } // Compile the ordered list Q_UINT32 mask = 1; for (int i = 0; i < daysInMonth; mask <<= 1, ++i) { if (days & mask) list.append(i + 1); } return variable; } // Get the months which recur, in numerical order, for both leap years and non-leap years. // N.B. If February 29th recurs on March 1st in non-leap years, February (not March) is // included in the non-leap year month list. // Reply = true if February 29th also recurs. bool Recurrence::getYearlyMonthMonths(int day, Q3ValueList &list, Q3ValueList &leaplist) const { list.clear(); leaplist.clear(); bool feb29 = false; for (Q3PtrListIterator it(rYearNums); it.current(); ++it) { int month = *it.current(); if (month == 2) { if (day <= 28) { list.append(month); // date appears in February leaplist.append(month); } else if (day == 29) { // February 29th leaplist.append(month); switch (mFeb29YearlyType) { case rFeb28: case rMar1: list.append(2); break; case rFeb29: break; } feb29 = true; } } else if (day <= 30 || QDate(2000, month, 1).daysInMonth() == 31) { list.append(month); // date appears in every month leaplist.append(month); } } return feb29; } /* From the recurrence day of the week list, get the earliest day in the * specified week which is >= the startDay. * Parameters: startDay = 1..7 (Monday..Sunday) * useWeekStart = true to end search at day before next rWeekStart * = false to search for a full 7 days * Reply = day of the week (1..7), or 0 if none found. */ int Recurrence::getFirstDayInWeek(int startDay, bool useWeekStart) const { int last = ((useWeekStart ? rWeekStart : startDay) + 5)%7; for (int i = startDay - 1; ; i = (i + 1)%7) { if (rDays.testBit(i)) return i + 1; if (i == last) return 0; } } /* From the recurrence day of the week list, get the latest day in the * specified week which is <= the endDay. * Parameters: endDay = 1..7 (Monday..Sunday) * useWeekStart = true to end search at rWeekStart * = false to search for a full 7 days * Reply = day of the week (1..7), or 0 if none found. */ int Recurrence::getLastDayInWeek(int endDay, bool useWeekStart) const { int last = useWeekStart ? rWeekStart - 1 : endDay%7; for (int i = endDay - 1; ; i = (i + 6)%7) { if (rDays.testBit(i)) return i + 1; if (i == last) return 0; } } /* From the recurrence monthly day number list or monthly day of week/week of * month list, get the earliest day in the specified month which is >= the * earliestDate. */ QDate Recurrence::getFirstDateInMonth(const QDate &earliestDate) const { int earliestDay = earliestDate.day(); int daysInMonth = earliestDate.daysInMonth(); switch (recurs) { case rMonthlyDay: { int minday = daysInMonth + 1; for (Q3PtrListIterator it(rMonthDays); it.current(); ++it) { int day = *it.current(); if (day < 0) day = daysInMonth + day + 1; if (day >= earliestDay && day < minday) minday = day; } if (minday <= daysInMonth) return earliestDate.addDays(minday - earliestDay); break; } case rMonthlyPos: case rYearlyPos: { QDate monthBegin(earliestDate.addDays(1 - earliestDay)); Q3ValueList dayList; getMonthlyPosDays(dayList, daysInMonth, monthBegin.dayOfWeek()); for (Q3ValueList::ConstIterator id = dayList.begin(); id != dayList.end(); ++id) { if (*id >= earliestDay) return monthBegin.addDays(*id - 1); } break; } } return QDate(); } /* From the recurrence monthly day number list or monthly day of week/week of * month list, get the latest day in the specified month which is <= the * latestDate. */ QDate Recurrence::getLastDateInMonth(const QDate &latestDate) const { int latestDay = latestDate.day(); int daysInMonth = latestDate.daysInMonth(); switch (recurs) { case rMonthlyDay: { int maxday = -1; for (Q3PtrListIterator it(rMonthDays); it.current(); ++it) { int day = *it.current(); if (day < 0) day = daysInMonth + day + 1; if (day <= latestDay && day > maxday) maxday = day; } if (maxday > 0) return QDate(latestDate.year(), latestDate.month(), maxday); break; } case rMonthlyPos: case rYearlyPos: { QDate monthBegin(latestDate.addDays(1 - latestDay)); Q3ValueList dayList; getMonthlyPosDays(dayList, daysInMonth, monthBegin.dayOfWeek()); for (Q3ValueList::ConstIterator id = dayList.fromLast(); id != dayList.end(); --id) { if (*id <= latestDay) return monthBegin.addDays(*id - 1); } break; } } return QDate(); } /* From the recurrence yearly month list or yearly day list, get the earliest * month or day in the specified year which is >= the earliestDate. * Note that rYearNums is sorted in numerical order. */ QDate Recurrence::getFirstDateInYear(const QDate &earliestDate) const { Q3PtrListIterator it(rYearNums); switch (recurs) { case rYearlyMonth: { int day = recurStart().date().day(); int earliestYear = earliestDate.year(); int earliestMonth = earliestDate.month(); int earliestDay = earliestDate.day(); if (earliestDay > day) { // The earliest date is later in the month than the recurrence date, // so skip to the next month before starting to check if (++earliestMonth > 12) return QDate(); } for ( ; it.current(); ++it) { int month = *it.current(); if (month >= earliestMonth) { if (day <= 28 || QDate::isValid(earliestYear, month, day)) return QDate(earliestYear, month, day); if (day == 29 && month == 2) { // It's a recurrence on February 29th, in a non-leap year switch (mFeb29YearlyType) { case rMar1: return QDate(earliestYear, 3, 1); case rFeb28: if (earliestDay <= 28) return QDate(earliestYear, 2, 28); break; case rFeb29: break; } } } } break; } case rYearlyPos: { Q3ValueList dayList; int earliestYear = earliestDate.year(); int earliestMonth = earliestDate.month(); int earliestDay = earliestDate.day(); for ( ; it.current(); ++it) { int month = *it.current(); if (month >= earliestMonth) { QDate monthBegin(earliestYear, month, 1); getMonthlyPosDays(dayList, monthBegin.daysInMonth(), monthBegin.dayOfWeek()); for (Q3ValueList::ConstIterator id = dayList.begin(); id != dayList.end(); ++id) { if (*id >= earliestDay) return monthBegin.addDays(*id - 1); } earliestDay = 1; } } break; } case rYearlyDay: { int earliestDay = earliestDate.dayOfYear(); for ( ; it.current(); ++it) { int day = *it.current(); if (day >= earliestDay && (day <= 365 || day <= earliestDate.daysInYear())) return earliestDate.addDays(day - earliestDay); } break; } } return QDate(); } /* From the recurrence yearly month list or yearly day list, get the latest * month or day in the specified year which is <= the latestDate. * Note that rYearNums is sorted in numerical order. */ QDate Recurrence::getLastDateInYear(const QDate &latestDate) const { Q3PtrListIterator it(rYearNums); switch (recurs) { case rYearlyMonth: { int day = recurStart().date().day(); int latestYear = latestDate.year(); int latestMonth = latestDate.month(); if (latestDate.day() > day) { // The latest date is earlier in the month than the recurrence date, // so skip to the previous month before starting to check if (--latestMonth <= 0) return QDate(); } for (it.toLast(); it.current(); --it) { int month = *it.current(); if (month <= latestMonth) { if (day <= 28 || QDate::isValid(latestYear, month, day)) return QDate(latestYear, month, day); if (day == 29 && month == 2) { // It's a recurrence on February 29th, in a non-leap year switch (mFeb29YearlyType) { case rMar1: if (latestMonth >= 3) return QDate(latestYear, 3, 1); break; case rFeb28: return QDate(latestYear, 2, 28); case rFeb29: break; } } } } break; } case rYearlyPos: { Q3ValueList dayList; int latestYear = latestDate.year(); int latestMonth = latestDate.month(); int latestDay = latestDate.day(); for (it.toLast(); it.current(); --it) { int month = *it.current(); if (month <= latestMonth) { QDate monthBegin(latestYear, month, 1); getMonthlyPosDays(dayList, monthBegin.daysInMonth(), monthBegin.dayOfWeek()); for (Q3ValueList::ConstIterator id = dayList.fromLast(); id != dayList.end(); --id) { if (*id <= latestDay) return monthBegin.addDays(*id - 1); } latestDay = 31; } } break; } case rYearlyDay: { int latestDay = latestDate.dayOfYear(); for (it.toLast(); it.current(); --it) { int day = *it.current(); if (day <= latestDay) return latestDate.addDays(day - latestDay); } break; } } return QDate(); } void Recurrence::dump() const { ; }