summaryrefslogtreecommitdiffabout
path: root/libkcal/recurrence.cpp
Unidiff
Diffstat (limited to 'libkcal/recurrence.cpp') (more/less context) (ignore whitespace changes)
-rw-r--r--libkcal/recurrence.cpp3360
1 files changed, 3360 insertions, 0 deletions
diff --git a/libkcal/recurrence.cpp b/libkcal/recurrence.cpp
new file mode 100644
index 0000000..5fc5d1f
--- a/dev/null
+++ b/libkcal/recurrence.cpp
@@ -0,0 +1,3360 @@
1/*
2 This file is part of libkcal.
3 Copyright (c) 1998 Preston Brown
4 Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
5 Copyright (c) 2002 David Jarvie <software@astrojar.org.uk>
6
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Library General Public
9 License as published by the Free Software Foundation; either
10 version 2 of the License, or (at your option) any later version.
11
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Library General Public License for more details.
16
17 You should have received a copy of the GNU Library General Public License
18 along with this library; see the file COPYING.LIB. If not, write to
19 the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 Boston, MA 02111-1307, USA.
21*/
22
23#include <limits.h>
24
25#include <kdebug.h>
26#include <kglobal.h>
27#include <klocale.h>
28
29#include "incidence.h"
30
31#include "recurrence.h"
32
33using namespace KCal;
34
35Recurrence::Feb29Type Recurrence::mFeb29YearlyDefaultType = Recurrence::rMar1;
36
37
38Recurrence::Recurrence(Incidence *parent, int compatVersion)
39: recurs(rNone), // by default, it's not a recurring event
40 rWeekStart(1), // default is Monday
41 rDays(7),
42 mFloats(parent ? parent->doesFloat() : false),
43 mRecurReadOnly(false),
44 mRecurExDatesCount(0),
45 mFeb29YearlyType(mFeb29YearlyDefaultType),
46 mCompatVersion(compatVersion ? compatVersion : INT_MAX),
47 mCompatRecurs(rNone),
48 mCompatDuration(0),
49 mParent(parent)
50{
51 rMonthDays.setAutoDelete( true );
52 rMonthPositions.setAutoDelete( true );
53 rYearNums.setAutoDelete( true );
54}
55
56Recurrence::Recurrence(const Recurrence &r, Incidence *parent)
57: recurs(r.recurs),
58 rWeekStart(r.rWeekStart),
59 rDays(r.rDays.copy()),
60 rFreq(r.rFreq),
61 rDuration(r.rDuration),
62 rEndDateTime(r.rEndDateTime),
63 mRecurStart(r.mRecurStart),
64 mFloats(r.mFloats),
65 mRecurReadOnly(r.mRecurReadOnly),
66 mRecurExDatesCount(r.mRecurExDatesCount),
67 mFeb29YearlyType(r.mFeb29YearlyType),
68 mCompatVersion(r.mCompatVersion),
69 mCompatRecurs(r.mCompatRecurs),
70 mCompatDuration(r.mCompatDuration),
71 mParent(parent)
72{
73 for (QPtrListIterator<rMonthPos> mp(r.rMonthPositions); mp.current(); ++mp) {
74 rMonthPos *tmp = new rMonthPos;
75 tmp->rPos = mp.current()->rPos;
76 tmp->negative = mp.current()->negative;
77 tmp->rDays = mp.current()->rDays.copy();
78 rMonthPositions.append(tmp);
79 }
80 for (QPtrListIterator<int> md(r.rMonthDays); md.current(); ++md) {
81 int *tmp = new int;
82 *tmp = *md.current();
83 rMonthDays.append(tmp);
84 }
85 for (QPtrListIterator<int> yn(r.rYearNums); yn.current(); ++yn) {
86 int *tmp = new int;
87 *tmp = *yn.current();
88 rYearNums.append(tmp);
89 }
90 rMonthDays.setAutoDelete( true );
91 rMonthPositions.setAutoDelete( true );
92 rYearNums.setAutoDelete( true );
93}
94
95Recurrence::~Recurrence()
96{
97}
98
99
100bool Recurrence::operator==( const Recurrence& r2 ) const
101{
102
103 // the following line is obvious
104 if ( recurs == rNone && r2.recurs == rNone )
105 return true;
106 // we need the above line, because two non recurring events may
107 // differ in the other settings, because one (or both)
108 // may be not initialized properly
109 if ( recurs != r2.recurs
110 || rFreq != r2.rFreq
111 || rDuration != r2.rDuration
112 || !rDuration && rEndDateTime != r2.rEndDateTime
113 || mRecurStart != r2.mRecurStart
114 || mFloats != r2.mFloats
115 || mRecurReadOnly != r2.mRecurReadOnly
116 || mRecurExDatesCount != r2.mRecurExDatesCount )
117 return false;
118 // no need to compare mCompat* and mParent
119 // OK to compare the pointers
120 switch ( recurs )
121 {
122 case rWeekly:
123 return rDays == r2.rDays
124 && rWeekStart == r2.rWeekStart;
125 case rMonthlyPos:
126 return rMonthPositions.count() == r2.rMonthPositions.count();
127 case rMonthlyDay:
128 return rMonthDays.count() == r2.rMonthDays.count();
129 case rYearlyPos:
130 return rYearNums.count() == r2.rYearNums.count()
131 && rMonthPositions.count() == r2.rMonthPositions.count();
132 case rYearlyMonth:
133 return rYearNums.count() == r2.rYearNums.count()
134 && mFeb29YearlyType == r2.mFeb29YearlyType;
135 case rYearlyDay:
136 return rYearNums == r2.rYearNums;
137 case rNone:
138 case rMinutely:
139 case rHourly:
140 case rDaily:
141 default:
142 return true;
143 }
144}
145/*
146bool Recurrence::compareLists( const QPtrList<int> &l1 ,const QPtrList<int> &l2)
147{
148 if ( l1.count() != l2.count() )
149 return false;
150 int count = l1.count();
151 int i;
152 for ( i = 0; i < count ; ++i ) {
153 // if ( l1.at(i) != l2.at(i) )
154 return false;
155 qDebug("compüare ");
156 }
157 return true;
158}
159*/
160QString Recurrence::recurrenceText() const
161{
162 QString recurText = i18n("No");
163 if ( recurs == Recurrence::rMinutely )
164 recurText = i18n("minutely");
165 else if ( recurs == Recurrence::rHourly )
166 recurText = i18n("hourly");
167 else if ( recurs == Recurrence::rDaily )
168 recurText = i18n("daily");
169 else if ( recurs == Recurrence::rWeekly )
170 recurText = i18n("weekly");
171 else if ( recurs == Recurrence::rMonthlyPos )
172 recurText = i18n("monthly");
173 else if ( recurs == Recurrence::rMonthlyDay )
174 recurText = i18n("day-monthly");
175 else if ( recurs == Recurrence::rYearlyMonth )
176 recurText = i18n("month-yearly");
177 else if ( recurs == Recurrence::rYearlyDay )
178 recurText = i18n("day-yearly");
179 else if ( recurs == Recurrence::rYearlyPos )
180 recurText = i18n("position-yearly");
181 return recurText;
182}
183
184void Recurrence::setCompatVersion(int version)
185{
186 mCompatVersion = version ? version : INT_MAX;
187}
188
189ushort Recurrence::doesRecur() const
190{
191 return recurs;
192}
193
194bool Recurrence::recursOnPure(const QDate &qd) const
195{
196 switch(recurs) {
197 case rMinutely:
198 return recursSecondly(qd, rFreq*60);
199 case rHourly:
200 return recursSecondly(qd, rFreq*3600);
201 case rDaily:
202 return recursDaily(qd);
203 case rWeekly:
204 return recursWeekly(qd);
205 case rMonthlyPos:
206 case rMonthlyDay:
207 return recursMonthly(qd);
208 case rYearlyMonth:
209 return recursYearlyByMonth(qd);
210 case rYearlyDay:
211 return recursYearlyByDay(qd);
212 case rYearlyPos:
213 return recursYearlyByPos(qd);
214 default:
215 return false;
216 case rNone:
217 return false;
218 } // case
219 return false;
220}
221
222bool Recurrence::recursAtPure(const QDateTime &dt) const
223{
224 switch(recurs) {
225 case rMinutely:
226 return recursMinutelyAt(dt, rFreq);
227 case rHourly:
228 return recursMinutelyAt(dt, rFreq*60);
229 default:
230 if (dt.time() != mRecurStart.time())
231 return false;
232 switch(recurs) {
233 case rDaily:
234 return recursDaily(dt.date());
235 case rWeekly:
236 return recursWeekly(dt.date());
237 case rMonthlyPos:
238 case rMonthlyDay:
239 return recursMonthly(dt.date());
240 case rYearlyMonth:
241 return recursYearlyByMonth(dt.date());
242 case rYearlyDay:
243 return recursYearlyByDay(dt.date());
244 case rYearlyPos:
245 return recursYearlyByPos(dt.date());
246 default:
247 return false;
248 case rNone:
249 return false;
250 }
251 } // case
252 return false;
253}
254
255QDate Recurrence::endDate() const
256{
257 int count = 0;
258 QDate end;
259 if (recurs != rNone) {
260 if (rDuration < 0)
261 return QDate(); // infinite recurrence
262 if (rDuration == 0)
263 return rEndDateTime.date();
264
265 // The end date is determined by the recurrence count
266 QDate dStart = mRecurStart.date();
267 switch (recurs)
268 {
269 case rMinutely:
270 return mRecurStart.addSecs((rDuration-1+mRecurExDatesCount)*rFreq*60).date();
271 case rHourly:
272 return mRecurStart.addSecs((rDuration-1+mRecurExDatesCount)*rFreq*3600).date();
273 case rDaily:
274 return dStart.addDays((rDuration-1+mRecurExDatesCount)*rFreq);
275
276 case rWeekly:
277 count = weeklyCalc(END_DATE_AND_COUNT, end);
278 break;
279 case rMonthlyPos:
280 case rMonthlyDay:
281 count = monthlyCalc(END_DATE_AND_COUNT, end);
282 break;
283 case rYearlyMonth:
284 count = yearlyMonthCalc(END_DATE_AND_COUNT, end);
285 break;
286 case rYearlyDay:
287 count = yearlyDayCalc(END_DATE_AND_COUNT, end);
288 break;
289 case rYearlyPos:
290 count = yearlyPosCalc(END_DATE_AND_COUNT, end);
291 break;
292 default:
293 // catch-all. Should never get here.
294 kdDebug(5800) << "Control should never reach here in endDate()!" << endl;
295 break;
296 }
297 }
298 if (!count)
299 return QDate(); // error - there is no recurrence
300 return end;
301}
302
303QDateTime Recurrence::endDateTime() const
304{
305 int count = 0;
306 QDate end;
307 if (recurs != rNone) {
308 if (rDuration < 0)
309 return QDateTime(); // infinite recurrence
310 if (rDuration == 0)
311 return rEndDateTime;
312
313 // The end date is determined by the recurrence count
314 QDate dStart = mRecurStart.date();
315 switch (recurs)
316 {
317 case rMinutely:
318 return mRecurStart.addSecs((rDuration-1+mRecurExDatesCount)*rFreq*60);
319 case rHourly:
320 return mRecurStart.addSecs((rDuration-1+mRecurExDatesCount)*rFreq*3600);
321 case rDaily:
322 return dStart.addDays((rDuration-1+mRecurExDatesCount)*rFreq);
323
324 case rWeekly:
325 count = weeklyCalc(END_DATE_AND_COUNT, end);
326 break;
327 case rMonthlyPos:
328 case rMonthlyDay:
329 count = monthlyCalc(END_DATE_AND_COUNT, end);
330 break;
331 case rYearlyMonth:
332 count = yearlyMonthCalc(END_DATE_AND_COUNT, end);
333 break;
334 case rYearlyDay:
335 count = yearlyDayCalc(END_DATE_AND_COUNT, end);
336 break;
337 case rYearlyPos:
338 count = yearlyPosCalc(END_DATE_AND_COUNT, end);
339 break;
340 default:
341 // catch-all. Should never get here.
342 kdDebug(5800) << "Control should never reach here in endDate()!" << endl;
343 break;
344 }
345 }
346 if (!count)
347 return QDateTime(); // error - there is no recurrence
348 return QDateTime(end, mRecurStart.time());
349}
350
351int Recurrence::durationTo(const QDate &date) const
352{
353 QDate d = date;
354 return recurCalc(COUNT_TO_DATE, d);
355}
356
357int Recurrence::durationTo(const QDateTime &datetime) const
358{
359 QDateTime dt = datetime;
360 return recurCalc(COUNT_TO_DATE, dt);
361}
362
363void Recurrence::unsetRecurs()
364{
365 if (mRecurReadOnly) return;
366 recurs = rNone;
367 rMonthPositions.clear();
368 rMonthDays.clear();
369 rYearNums.clear();
370}
371
372void Recurrence::setRecurStart(const QDateTime &start)
373{
374 mRecurStart = start;
375 mFloats = false;
376 switch (recurs)
377 {
378 case rMinutely:
379 case rHourly:
380 break;
381 case rDaily:
382 case rWeekly:
383 case rMonthlyPos:
384 case rMonthlyDay:
385 case rYearlyMonth:
386 case rYearlyDay:
387 case rYearlyPos:
388 default:
389 rEndDateTime.setTime(start.time());
390 break;
391 }
392}
393
394void Recurrence::setRecurStart(const QDate &start)
395{
396 mRecurStart.setDate(start);
397 mRecurStart.setTime(QTime(0,0,0));
398 switch (recurs)
399 {
400 case rMinutely:
401 case rHourly:
402 break;
403 case rDaily:
404 case rWeekly:
405 case rMonthlyPos:
406 case rMonthlyDay:
407 case rYearlyMonth:
408 case rYearlyDay:
409 case rYearlyPos:
410 default:
411 mFloats = true;
412 break;
413 }
414}
415
416void Recurrence::setFloats(bool f)
417{
418 switch (recurs)
419 {
420 case rDaily:
421 case rWeekly:
422 case rMonthlyPos:
423 case rMonthlyDay:
424 case rYearlyMonth:
425 case rYearlyDay:
426 case rYearlyPos:
427 break;
428 case rMinutely:
429 case rHourly:
430 default:
431 return; // can't set sub-daily to floating
432 }
433 mFloats = f;
434 if (f) {
435 mRecurStart.setTime(QTime(0,0,0));
436 rEndDateTime.setTime(QTime(0,0,0));
437 }
438}
439
440int Recurrence::frequency() const
441{
442 return rFreq;
443}
444
445int Recurrence::duration() const
446{
447 return rDuration;
448}
449
450void Recurrence::setDuration(int _rDuration)
451{
452 if (mRecurReadOnly) return;
453 if (_rDuration > 0) {
454 rDuration = _rDuration;
455 // Compatibility mode is only needed when reading the calendar in ICalFormatImpl,
456 // so explicitly setting the duration means no backwards compatibility is needed.
457 mCompatDuration = 0;
458 }
459}
460
461QString Recurrence::endDateStr(bool shortfmt) const
462{
463 return KGlobal::locale()->formatDate(rEndDateTime.date(),shortfmt);
464}
465
466const QBitArray &Recurrence::days() const
467{
468 return rDays;
469}
470
471const QPtrList<Recurrence::rMonthPos> &Recurrence::monthPositions() const
472{
473 return rMonthPositions;
474}
475
476const QPtrList<Recurrence::rMonthPos> &Recurrence::yearMonthPositions() const
477{
478 return rMonthPositions;
479}
480
481const QPtrList<int> &Recurrence::monthDays() const
482{
483 return rMonthDays;
484}
485
486void Recurrence::setMinutely(int _rFreq, int _rDuration)
487{
488 if (mRecurReadOnly || _rDuration == 0 || _rDuration < -1)
489 return;
490 setDailySub(rMinutely, _rFreq, _rDuration);
491}
492
493void Recurrence::setMinutely(int _rFreq, const QDateTime &_rEndDateTime)
494{
495 if (mRecurReadOnly) return;
496 rEndDateTime = _rEndDateTime;
497 setDailySub(rMinutely, _rFreq, 0);
498}
499
500void Recurrence::setHourly(int _rFreq, int _rDuration)
501{
502 if (mRecurReadOnly || _rDuration == 0 || _rDuration < -1)
503 return;
504 setDailySub(rHourly, _rFreq, _rDuration);
505}
506
507void Recurrence::setHourly(int _rFreq, const QDateTime &_rEndDateTime)
508{
509 if (mRecurReadOnly) return;
510 rEndDateTime = _rEndDateTime;
511 setDailySub(rHourly, _rFreq, 0);
512}
513
514void Recurrence::setDaily(int _rFreq, int _rDuration)
515{
516 if (mRecurReadOnly || _rDuration == 0 || _rDuration < -1)
517 return;
518 setDailySub(rDaily, _rFreq, _rDuration);
519}
520
521void Recurrence::setDaily(int _rFreq, const QDate &_rEndDate)
522{
523 if (mRecurReadOnly) return;
524 rEndDateTime.setDate(_rEndDate);
525 rEndDateTime.setTime(mRecurStart.time());
526 setDailySub(rDaily, _rFreq, 0);
527}
528
529void Recurrence::setWeekly(int _rFreq, const QBitArray &_rDays,
530 int _rDuration, int _rWeekStart)
531{
532 if (mRecurReadOnly || _rDuration == 0 || _rDuration < -1)
533 return;
534 recurs = rWeekly;
535
536 rFreq = _rFreq;
537 rDays = _rDays;
538 rWeekStart = _rWeekStart;
539 rDuration = _rDuration;
540 if (mCompatVersion < 310 && _rDuration > 0) {
541 // Backwards compatibility for KDE < 3.1.
542 // rDuration was set to the number of time periods to recur,
543 // with week start always on a Monday.
544 // Convert this to the number of occurrences.
545 mCompatDuration = _rDuration;
546 int weeks = ((mCompatDuration-1+mRecurExDatesCount)*7) + (7 - mRecurStart.date().dayOfWeek());
547 QDate end(mRecurStart.date().addDays(weeks * rFreq));
548 rDuration = INT_MAX; // ensure that weeklyCalc() does its job correctly
549 rDuration = weeklyCalc(COUNT_TO_DATE, end);
550 } else {
551 mCompatDuration = 0;
552 }
553 rMonthPositions.clear();
554 rMonthDays.clear();
555 if (mParent) mParent->updated();
556}
557
558void Recurrence::setWeekly(int _rFreq, const QBitArray &_rDays,
559 const QDate &_rEndDate, int _rWeekStart)
560{
561 if (mRecurReadOnly) return;
562 recurs = rWeekly;
563
564 rFreq = _rFreq;
565 rDays = _rDays;
566 rWeekStart = _rWeekStart;
567 rEndDateTime.setDate(_rEndDate);
568 rEndDateTime.setTime(mRecurStart.time());
569 rDuration = 0; // set to 0 because there is an end date
570 mCompatDuration = 0;
571 rMonthPositions.clear();
572 rMonthDays.clear();
573 rYearNums.clear();
574 if (mParent) mParent->updated();
575}
576
577void Recurrence::setMonthly(short type, int _rFreq, int _rDuration)
578{
579 if (mRecurReadOnly || _rDuration == 0 || _rDuration < -1)
580 return;
581 recurs = type;
582
583 rFreq = _rFreq;
584 rDuration = _rDuration;
585 if (mCompatVersion < 310)
586 mCompatDuration = (_rDuration > 0) ? _rDuration : 0;
587 rYearNums.clear();
588 if (mParent) mParent->updated();
589}
590
591void Recurrence::setMonthly(short type, int _rFreq,
592 const QDate &_rEndDate)
593{
594 if (mRecurReadOnly) return;
595 recurs = type;
596
597 rFreq = _rFreq;
598 rEndDateTime.setDate(_rEndDate);
599 rEndDateTime.setTime(mRecurStart.time());
600 rDuration = 0; // set to 0 because there is an end date
601 mCompatDuration = 0;
602 rYearNums.clear();
603 if (mParent) mParent->updated();
604}
605
606void Recurrence::addMonthlyPos(short _rPos, const QBitArray &_rDays)
607{
608 if (recurs == rMonthlyPos)
609 addMonthlyPos_(_rPos, _rDays);
610}
611
612void Recurrence::addMonthlyPos_(short _rPos, const QBitArray &_rDays)
613{
614 if (mRecurReadOnly
615 || _rPos == 0 || _rPos > 5 || _rPos < -5) // invalid week number
616 return;
617
618 for (rMonthPos* it = rMonthPositions.first(); it; it = rMonthPositions.next()) {
619 int itPos = it->negative ? -it->rPos : it->rPos;
620 if (_rPos == itPos) {
621 // This week is already in the list.
622 // Combine the specified days with those in the list.
623 it->rDays |= _rDays;
624 if (mParent) mParent->updated();
625 return;
626 }
627 }
628 // Add the new position to the list
629 rMonthPos *tmpPos = new rMonthPos;
630 if (_rPos > 0) {
631 tmpPos->rPos = _rPos;
632 tmpPos->negative = false;
633 } else {
634 tmpPos->rPos = -_rPos; // take abs()
635 tmpPos->negative = true;
636 }
637 tmpPos->rDays = _rDays;
638 tmpPos->rDays.detach();
639 rMonthPositions.append(tmpPos);
640
641 if (mCompatVersion < 310 && mCompatDuration > 0) {
642 // Backwards compatibility for KDE < 3.1.
643 // rDuration was set to the number of time periods to recur.
644 // Convert this to the number of occurrences.
645 int monthsAhead = (mCompatDuration-1+mRecurExDatesCount) * rFreq;
646 int month = mRecurStart.date().month() - 1 + monthsAhead;
647 QDate end(mRecurStart.date().year() + month/12, month%12 + 1, 31);
648 rDuration = INT_MAX; // ensure that recurCalc() does its job correctly
649 rDuration = recurCalc(COUNT_TO_DATE, end);
650 }
651
652 if (mParent) mParent->updated();
653}
654
655void Recurrence::addMonthlyDay(short _rDay)
656{
657 if (mRecurReadOnly || recurs != rMonthlyDay
658 || _rDay == 0 || _rDay > 31 || _rDay < -31) // invalid day number
659 return;
660 for (int* it = rMonthDays.first(); it; it = rMonthDays.next()) {
661 if (_rDay == *it)
662 return; // this day is already in the list - avoid duplication
663 }
664 int *tmpDay = new int;
665 *tmpDay = _rDay;
666 rMonthDays.append(tmpDay);
667
668 if (mCompatVersion < 310 && mCompatDuration > 0) {
669 // Backwards compatibility for KDE < 3.1.
670 // rDuration was set to the number of time periods to recur.
671 // Convert this to the number of occurrences.
672 int monthsAhead = (mCompatDuration-1+mRecurExDatesCount) * rFreq;
673 int month = mRecurStart.date().month() - 1 + monthsAhead;
674 QDate end(mRecurStart.date().year() + month/12, month%12 + 1, 31);
675 rDuration = INT_MAX; // ensure that recurCalc() does its job correctly
676 rDuration = recurCalc(COUNT_TO_DATE, end);
677 }
678
679 if (mParent) mParent->updated();
680}
681
682void Recurrence::setYearly(int type, int _rFreq, int _rDuration)
683{
684 if (mRecurReadOnly || _rDuration == 0 || _rDuration < -1)
685 return;
686 if (mCompatVersion < 310)
687 mCompatDuration = (_rDuration > 0) ? _rDuration : 0;
688 setYearly_(type, mFeb29YearlyDefaultType, _rFreq, _rDuration);
689}
690
691void Recurrence::setYearly(int type, int _rFreq, const QDate &_rEndDate)
692{
693 if (mRecurReadOnly) return;
694 rEndDateTime.setDate(_rEndDate);
695 rEndDateTime.setTime(mRecurStart.time());
696 mCompatDuration = 0;
697 setYearly_(type, mFeb29YearlyDefaultType, _rFreq, 0);
698}
699
700void Recurrence::setYearlyByDate(Feb29Type type, int _rFreq, int _rDuration)
701{
702 if (mRecurReadOnly || _rDuration == 0 || _rDuration < -1)
703 return;
704 if (mCompatVersion < 310)
705 mCompatDuration = (_rDuration > 0) ? _rDuration : 0;
706 setYearly_(rYearlyMonth, type, _rFreq, _rDuration);
707}
708
709void Recurrence::setYearlyByDate(Feb29Type type, int _rFreq, const QDate &_rEndDate)
710{
711 if (mRecurReadOnly) return;
712 rEndDateTime.setDate(_rEndDate);
713 rEndDateTime.setTime(mRecurStart.time());
714 mCompatDuration = 0;
715 setYearly_(rYearlyMonth, type, _rFreq, 0);
716}
717
718void Recurrence::addYearlyMonthPos(short _rPos, const QBitArray &_rDays)
719{
720 if (recurs == rYearlyPos)
721 addMonthlyPos_(_rPos, _rDays);
722}
723
724const QPtrList<int> &Recurrence::yearNums() const
725{
726 return rYearNums;
727}
728
729void Recurrence::addYearlyNum(short _rNum)
730{
731 if (mRecurReadOnly
732 || (recurs != rYearlyMonth && recurs != rYearlyDay && recurs != rYearlyPos)
733 || _rNum <= 0) // invalid day/month number
734 return;
735
736 if (mCompatVersion < 310 && mCompatRecurs == rYearlyDay) {
737 // Backwards compatibility for KDE < 3.1.
738 // Dates were stored as day numbers, with a fiddle to take account of leap years.
739 // Convert the day number to a month.
740 if (_rNum <= 0 || _rNum > 366 || (_rNum == 366 && mRecurStart.date().daysInYear() < 366))
741 return; // invalid day number
742 _rNum = QDate(mRecurStart.date().year(), 1, 1).addDays(_rNum - 1).month();
743 } else
744 if ((recurs == rYearlyMonth || recurs == rYearlyPos) && _rNum > 12
745 || recurs == rYearlyDay && _rNum > 366)
746 return; // invalid day number
747
748 uint i = 0;
749 for (int* it = rYearNums.first(); it && _rNum >= *it; it = rYearNums.next()) {
750 if (_rNum == *it)
751 return; // this day/month is already in the list - avoid duplication
752 ++i;
753 }
754
755 int *tmpNum = new int;
756 *tmpNum = _rNum;
757 rYearNums.insert(i, tmpNum); // insert the day/month in a sorted position
758
759 if (mCompatVersion < 310 && mCompatDuration > 0) {
760 // Backwards compatibility for KDE < 3.1.
761 // rDuration was set to the number of time periods to recur.
762 // Convert this to the number of occurrences.
763 QDate end(mRecurStart.date().year() + (mCompatDuration-1+mRecurExDatesCount)*rFreq, 12, 31);
764 rDuration = INT_MAX; // ensure that recurCalc() does its job correctly
765 rDuration = recurCalc(COUNT_TO_DATE, end);
766 }
767
768 if (mParent) mParent->updated();
769}
770
771
772QDateTime Recurrence::getNextDateTime(const QDateTime &preDateTime, bool *last) const
773{
774 if (last)
775 *last = false;
776 int freq;
777 switch (recurs)
778 {
779 case rMinutely:
780 freq = rFreq * 60;
781 break;
782 case rHourly:
783 freq = rFreq * 3600;
784 break;
785 case rDaily:
786 case rWeekly:
787 case rMonthlyPos:
788 case rMonthlyDay:
789 case rYearlyMonth:
790 case rYearlyDay:
791 case rYearlyPos: {
792 QDate preDate = preDateTime.date();
793 if (!mFloats && mRecurStart.time() > preDateTime.time())
794 preDate = preDate.addDays(-1);
795 return QDateTime(getNextDateNoTime(preDate, last), mRecurStart.time());
796 }
797 default:
798 return QDateTime();
799 }
800
801 // It's a sub-daily recurrence
802 if (preDateTime < mRecurStart)
803 return mRecurStart;
804 int count = mRecurStart.secsTo(preDateTime) / freq + 2;
805 if (rDuration > 0) {
806 if (count > rDuration)
807 return QDateTime();
808 if (last && count == rDuration)
809 *last = true;
810 }
811 QDateTime endtime = mRecurStart.addSecs((count - 1)*freq);
812 if (rDuration == 0) {
813 if (endtime > rEndDateTime)
814 return QDateTime();
815 if (last && endtime == rEndDateTime)
816 *last = true;
817 }
818 return endtime;
819}
820
821QDate Recurrence::getNextDate(const QDate &preDate, bool *last) const
822{
823 if (last)
824 *last = false;
825 switch (recurs)
826 {
827 case rMinutely:
828 case rHourly:
829 return getNextDateTime(QDateTime(preDate, QTime(23,59,59)), last).date();
830 case rDaily:
831 case rWeekly:
832 case rMonthlyPos:
833 case rMonthlyDay:
834 case rYearlyMonth:
835 case rYearlyDay:
836 case rYearlyPos:
837 return getNextDateNoTime(preDate, last);
838 default:
839 return QDate();
840 }
841}
842
843
844QDateTime Recurrence::getPreviousDateTime(const QDateTime &afterDateTime, bool *last) const
845{
846 if (last)
847 *last = false;
848 int freq;
849 switch (recurs)
850 {
851 case rMinutely:
852 freq = rFreq * 60;
853 break;
854 case rHourly:
855 freq = rFreq * 3600;
856 break;
857 case rDaily:
858 case rWeekly:
859 case rMonthlyPos:
860 case rMonthlyDay:
861 case rYearlyMonth:
862 case rYearlyDay:
863 case rYearlyPos: {
864 QDate afterDate = afterDateTime.date();
865 if (!mFloats && mRecurStart.time() < afterDateTime.time())
866 afterDate = afterDate.addDays(1);
867 return QDateTime(getPreviousDateNoTime(afterDate, last), mRecurStart.time());
868 }
869 default:
870 return QDateTime();
871 }
872
873 // It's a sub-daily recurrence
874 if (afterDateTime <= mRecurStart)
875 return QDateTime();
876 int count = (mRecurStart.secsTo(afterDateTime) - 1) / freq + 1;
877 if (rDuration > 0) {
878 if (count > rDuration)
879 count = rDuration;
880 if (last && count == rDuration)
881 *last = true;
882 }
883 QDateTime endtime = mRecurStart.addSecs((count - 1)*freq);
884 if (rDuration == 0) {
885 if (endtime > rEndDateTime)
886 endtime = rEndDateTime;
887 if (last && endtime == rEndDateTime)
888 *last = true;
889 }
890 return endtime;
891}
892
893QDate Recurrence::getPreviousDate(const QDate &afterDate, bool *last) const
894{
895 if (last)
896 *last = false;
897 switch (recurs)
898 {
899 case rMinutely:
900 case rHourly:
901 return getPreviousDateTime(QDateTime(afterDate, QTime(0,0,0)), last).date();
902 case rDaily:
903 case rWeekly:
904 case rMonthlyPos:
905 case rMonthlyDay:
906 case rYearlyMonth:
907 case rYearlyDay:
908 case rYearlyPos:
909 return getPreviousDateNoTime(afterDate, last);
910 default:
911 return QDate();
912 }
913}
914
915
916/***************************** PROTECTED FUNCTIONS ***************************/
917
918bool Recurrence::recursSecondly(const QDate &qd, int secondFreq) const
919{
920 if ((qd >= mRecurStart.date()) &&
921 ((rDuration > 0) && (qd <= endDate()) ||
922 ((rDuration == 0) && (qd <= rEndDateTime.date())) ||
923 (rDuration == -1))) {
924 // The date queried falls within the range of the event.
925 if (secondFreq < 24*3600)
926 return true; // the event recurs at least once each day
927 int after = mRecurStart.secsTo(QDateTime(qd));
928 if (after / secondFreq != (after + 24*3600) / secondFreq)
929 return true;
930 }
931 return false;
932}
933
934bool Recurrence::recursMinutelyAt(const QDateTime &dt, int minuteFreq) const
935{
936 if ((dt >= mRecurStart) &&
937 ((rDuration > 0) && (dt <= endDateTime()) ||
938 ((rDuration == 0) && (dt <= rEndDateTime)) ||
939 (rDuration == -1))) {
940 // The time queried falls within the range of the event.
941 if (((mRecurStart.secsTo(dt) / 60) % minuteFreq) == 0)
942 return true;
943 }
944 return false;
945}
946
947bool Recurrence::recursDaily(const QDate &qd) const
948{
949 QDate dStart = mRecurStart.date();
950 if ((dStart.daysTo(qd) % rFreq) == 0) {
951 // The date is a day which recurs
952 if (qd >= dStart
953 && ((rDuration > 0 && qd <= endDate()) ||
954 (rDuration == 0 && qd <= rEndDateTime.date()) ||
955 rDuration == -1)) {
956 // The date queried falls within the range of the event.
957 return true;
958 }
959 }
960 return false;
961}
962
963bool Recurrence::recursWeekly(const QDate &qd) const
964{
965 QDate dStart = mRecurStart.date();
966 if ((dStart.daysTo(qd)/7) % rFreq == 0) {
967 // The date is in a week which recurs
968 if (qd >= dStart
969 && ((rDuration > 0 && qd <= endDate()) ||
970 (rDuration == 0 && qd <= rEndDateTime.date()) ||
971 rDuration == -1)) {
972 // The date queried falls within the range of the event.
973 // check if the bits set match today.
974 int i = qd.dayOfWeek()-1;
975 if (rDays.testBit((uint) i))
976 return true;
977 }
978 }
979 return false;
980}
981
982bool Recurrence::recursMonthly(const QDate &qd) const
983{
984 QDate dStart = mRecurStart.date();
985 int year = qd.year();
986 int month = qd.month();
987 int day = qd.day();
988 // calculate how many months ahead this date is from the original
989 // event's date
990 int monthsAhead = (year - dStart.year()) * 12 + (month - dStart.month());
991 if ((monthsAhead % rFreq) == 0) {
992 // The date is in a month which recurs
993 if (qd >= dStart
994 && ((rDuration > 0 && qd <= endDate()) ||
995 (rDuration == 0 && qd <= rEndDateTime.date()) ||
996 rDuration == -1)) {
997 // The date queried falls within the range of the event.
998 QValueList<int> days;
999 int daysInMonth = qd.daysInMonth();
1000 if (recurs == rMonthlyDay)
1001 getMonthlyDayDays(days, daysInMonth);
1002 else if (recurs == rMonthlyPos)
1003 getMonthlyPosDays(days, daysInMonth, QDate(year, month, 1).dayOfWeek());
1004 for (QValueList<int>::Iterator it = days.begin(); it != days.end(); ++it) {
1005 if (*it == day)
1006 return true;
1007 }
1008 // no dates matched
1009 }
1010 }
1011 return false;
1012}
1013
1014bool Recurrence::recursYearlyByMonth(const QDate &qd) const
1015{
1016 QDate dStart = mRecurStart.date();
1017 int startDay = dStart.day();
1018 int qday = qd.day();
1019 int qmonth = qd.month();
1020 int qyear = qd.year();
1021 bool match = (qday == startDay);
1022 if (!match && startDay == 29 && dStart.month() == 2) {
1023 // It's a recurrence on February 29th
1024 switch (mFeb29YearlyType) {
1025 case rFeb28:
1026 if (qday == 28 && qmonth == 2 && !QDate::leapYear(qyear))
1027 match = true;
1028 break;
1029 case rMar1:
1030 if (qday == 1 && qmonth == 3 && !QDate::leapYear(qyear)) {
1031 qmonth = 2;
1032 match = true;
1033 }
1034 break;
1035 case rFeb29:
1036 break;
1037 }
1038 }
1039
1040 if (match) {
1041 // The day of the month matches. Calculate how many years ahead
1042 // this date is from the original event's date.
1043 int yearsAhead = (qyear - dStart.year());
1044 if (yearsAhead % rFreq == 0) {
1045 // The date is in a year which recurs
1046 if (qd >= dStart
1047 && ((rDuration > 0 && qd <= endDate()) ||
1048 (rDuration == 0 && qd <= rEndDateTime.date()) ||
1049 rDuration == -1)) {
1050 // The date queried falls within the range of the event.
1051 int i = qmonth;
1052 for (QPtrListIterator<int> qlin(rYearNums); qlin.current(); ++qlin) {
1053 if (i == *qlin.current())
1054 return true;
1055 }
1056 }
1057 }
1058 }
1059 return false;
1060}
1061
1062bool Recurrence::recursYearlyByPos(const QDate &qd) const
1063{
1064 QDate dStart = mRecurStart.date();
1065 int year = qd.year();
1066 int month = qd.month();
1067 int day = qd.day();
1068 // calculate how many years ahead this date is from the original
1069 // event's date
1070 int yearsAhead = (year - dStart.year());
1071 if (yearsAhead % rFreq == 0) {
1072 // The date is in a year which recurs
1073 if (qd >= dStart
1074 && ((rDuration > 0 && qd <= endDate()) ||
1075 (rDuration == 0 && qd <= rEndDateTime.date()) ||
1076 rDuration == -1)) {
1077 // The date queried falls within the range of the event.
1078 for (QPtrListIterator<int> qlin(rYearNums); qlin.current(); ++qlin) {
1079 if (month == *qlin.current()) {
1080 // The month recurs
1081 QValueList<int> days;
1082 getMonthlyPosDays(days, qd.daysInMonth(), QDate(year, month, 1).dayOfWeek());
1083 for (QValueList<int>::Iterator it = days.begin(); it != days.end(); ++it) {
1084 if (*it == day)
1085 return true;
1086 }
1087 }
1088 }
1089 }
1090 }
1091 return false;
1092}
1093
1094bool Recurrence::recursYearlyByDay(const QDate &qd) const
1095{
1096 QDate dStart = mRecurStart.date();
1097 // calculate how many years ahead this date is from the original
1098 // event's date
1099 int yearsAhead = (qd.year() - dStart.year());
1100 if (yearsAhead % rFreq == 0) {
1101 // The date is in a year which recurs
1102 if (qd >= dStart
1103 && ((rDuration > 0 && qd <= endDate()) ||
1104 (rDuration == 0 && qd <= rEndDateTime.date()) ||
1105 rDuration == -1)) {
1106 // The date queried falls within the range of the event.
1107 int i = qd.dayOfYear();
1108 for (QPtrListIterator<int> qlin(rYearNums); qlin.current(); ++qlin) {
1109 if (i == *qlin.current())
1110 return true;
1111 }
1112 }
1113 }
1114 return false;
1115}
1116
1117/* Get the date of the next recurrence, after the specified date.
1118 * If 'last' is non-null, '*last' is set to true if the next recurrence is the
1119 * last recurrence, else false.
1120 * Reply = date of next recurrence, or invalid date if none.
1121 */
1122QDate Recurrence::getNextDateNoTime(const QDate &preDate, bool *last) const
1123{
1124 if (last)
1125 *last = false;
1126 QDate dStart = mRecurStart.date();
1127 if (preDate < dStart)
1128 return dStart;
1129 QDate earliestDate = preDate.addDays(1);
1130 QDate nextDate;
1131
1132 switch (recurs) {
1133 case rDaily:
1134 nextDate = dStart.addDays((dStart.daysTo(preDate)/rFreq + 1) * rFreq);
1135 break;
1136
1137 case rWeekly: {
1138 QDate start = dStart.addDays(1 - dStart.dayOfWeek()); // start of week for dStart
1139 int earliestDayOfWeek = earliestDate.dayOfWeek();
1140 int weeksAhead = start.daysTo(earliestDate) / 7;
1141 int notThisWeek = weeksAhead % rFreq; // zero if this week is a recurring week
1142 weeksAhead -= notThisWeek; // latest week which recurred
1143 int weekday = 0;
1144 // First check for any remaining day this week, if this week is a recurring week
1145 if (!notThisWeek)
1146 weekday = getFirstDayInWeek(earliestDayOfWeek);
1147 // Check for a day in the next scheduled week
1148 if (!weekday && earliestDayOfWeek > 1)
1149 weekday = getFirstDayInWeek(rWeekStart) + rFreq*7;
1150 if (weekday)
1151 nextDate = start.addDays(weeksAhead*7 + weekday - 1);
1152 break;
1153 }
1154 case rMonthlyDay:
1155 case rMonthlyPos: {
1156 int startYear = dStart.year();
1157 int startMonth = dStart.month(); // 1..12
1158 int earliestYear = earliestDate.year();
1159 int monthsAhead = (earliestYear - startYear)*12 + earliestDate.month() - startMonth;
1160 int notThisMonth = monthsAhead % rFreq; // zero if this month is a recurring month
1161 monthsAhead -= notThisMonth; // latest month which recurred
1162 // Check for the first later day in the current month
1163 if (!notThisMonth)
1164 nextDate = getFirstDateInMonth(earliestDate);
1165 if (!nextDate.isValid() && earliestDate.day() > 1) {
1166 // Check for a day in the next scheduled month
1167 int months = startMonth - 1 + monthsAhead + rFreq;
1168 nextDate = getFirstDateInMonth(QDate(startYear + months/12, months%12 + 1, 1));
1169 }
1170 break;
1171 }
1172 case rYearlyMonth:
1173 case rYearlyPos:
1174 case rYearlyDay: {
1175 int startYear = dStart.year();
1176 int yearsAhead = earliestDate.year() - startYear;
1177 int notThisYear = yearsAhead % rFreq; // zero if this year is a recurring year
1178 yearsAhead -= notThisYear; // latest year which recurred
1179 // Check for the first later date in the current year
1180 if (!notThisYear)
1181 nextDate = getFirstDateInYear(earliestDate);
1182 // Check for a date in the next scheduled year
1183 if (!nextDate.isValid() && earliestDate.dayOfYear() > 1)
1184 nextDate = getFirstDateInYear(QDate(startYear + yearsAhead + rFreq, 1, 1));
1185 break;
1186 }
1187 case rNone:
1188 default:
1189 return QDate();
1190 }
1191
1192 if (rDuration >= 0 && nextDate.isValid()) {
1193 // Check that the date found is within the range of the recurrence
1194 QDate end = endDate();
1195 if (nextDate > end)
1196 return QDate();
1197 if (last && nextDate == end)
1198 *last = true;
1199 }
1200 return nextDate;
1201}
1202
1203/* Get the date of the last previous recurrence, before the specified date.
1204 * Reply = date of previous recurrence, or invalid date if none.
1205 */
1206QDate Recurrence::getPreviousDateNoTime(const QDate &afterDate, bool *last) const
1207{
1208 if (last)
1209 *last = false;
1210 QDate dStart = mRecurStart.date();
1211 QDate latestDate = afterDate.addDays(-1);
1212 if (latestDate < dStart)
1213 return QDate();
1214 QDate prevDate;
1215
1216 switch (recurs) {
1217 case rDaily:
1218 prevDate = dStart.addDays((dStart.daysTo(latestDate) / rFreq) * rFreq);
1219 break;
1220
1221 case rWeekly: {
1222 QDate start = dStart.addDays(1 - dStart.dayOfWeek()); // start of week for dStart
1223 int latestDayOfWeek = latestDate.dayOfWeek();
1224 int weeksAhead = start.daysTo(latestDate) / 7;
1225 int notThisWeek = weeksAhead % rFreq; // zero if this week is a recurring week
1226 weeksAhead -= notThisWeek; // latest week which recurred
1227 int weekday = 0;
1228 // First check for any previous day this week, if this week is a recurring week
1229 if (!notThisWeek)
1230 weekday = getLastDayInWeek(latestDayOfWeek);
1231 // Check for a day in the previous scheduled week
1232 if (!weekday) {
1233 int weekEnd = (rWeekStart + 5)%7 + 1;
1234 if (latestDayOfWeek < weekEnd) {
1235 if (!notThisWeek)
1236 weeksAhead -= rFreq;
1237 weekday = getLastDayInWeek(weekEnd);
1238 }
1239 }
1240 if (weekday)
1241 prevDate = start.addDays(weeksAhead*7 + weekday - 1);
1242 break;
1243 }
1244 case rMonthlyDay:
1245 case rMonthlyPos: {
1246 int startYear = dStart.year();
1247 int startMonth = dStart.month(); // 1..12
1248 int latestYear = latestDate.year();
1249 int monthsAhead = (latestYear - startYear)*12 + latestDate.month() - startMonth;
1250 int notThisMonth = monthsAhead % rFreq; // zero if this month is a recurring month
1251 monthsAhead -= notThisMonth; // latest month which recurred
1252 // Check for the last earlier day in the current month
1253 if (!notThisMonth)
1254 prevDate = getLastDateInMonth(latestDate);
1255 if (!prevDate.isValid() && latestDate.day() < latestDate.daysInMonth()) {
1256 // Check for a day in the previous scheduled month
1257 if (!notThisMonth)
1258 monthsAhead -= rFreq;
1259 int months = startMonth + monthsAhead; // get the month after the one that recurs
1260 prevDate = getLastDateInMonth(QDate(startYear + months/12, months%12 + 1, 1).addDays(-1));
1261 }
1262 break;
1263 }
1264 case rYearlyMonth:
1265 case rYearlyPos:
1266 case rYearlyDay: {
1267 int startYear = dStart.year();
1268 int yearsAhead = latestDate.year() - startYear;
1269 int notThisYear = yearsAhead % rFreq; // zero if this year is a recurring year
1270 yearsAhead -= notThisYear; // latest year which recurred
1271 // Check for the first later date in the current year
1272 if (!notThisYear)
1273 prevDate = getLastDateInYear(latestDate);
1274 if (!prevDate.isValid() && latestDate.dayOfYear() < latestDate.daysInYear()) {
1275 // Check for a date in the next scheduled year
1276 if (!notThisYear)
1277 yearsAhead -= rFreq;
1278 prevDate = getLastDateInYear(QDate(startYear + yearsAhead, 12, 31));
1279 }
1280 break;
1281 }
1282 case rNone:
1283 default:
1284 return QDate();
1285 }
1286
1287 if (prevDate.isValid()) {
1288 // Check that the date found is within the range of the recurrence
1289 if (prevDate < dStart)
1290 return QDate();
1291 if (rDuration >= 0) {
1292 QDate end = endDate();
1293 if (prevDate >= end) {
1294 if (last)
1295 *last = true;
1296 return end;
1297 }
1298 }
1299 }
1300 return prevDate;
1301}
1302
1303void Recurrence::setDailySub(short type, int freq, int duration)
1304{
1305 recurs = type;
1306 rFreq = freq;
1307 rDuration = duration;
1308 rMonthPositions.clear();
1309 rMonthDays.clear();
1310 rYearNums.clear();
1311 if (type != rDaily)
1312 mFloats = false; // sub-daily types can't be floating
1313
1314 if (mParent) mParent->updated();
1315}
1316
1317void Recurrence::setYearly_(short type, Feb29Type feb29type, int freq, int duration)
1318{
1319 recurs = type;
1320 if (mCompatVersion < 310 && type == rYearlyDay) {
1321 mCompatRecurs = rYearlyDay;
1322 recurs = rYearlyMonth; // convert old yearly-by-day to yearly-by-month
1323 feb29type = rMar1; // retain the same day number in the year
1324 }
1325
1326 mFeb29YearlyType = feb29type;
1327 rFreq = freq;
1328 rDuration = duration;
1329 if (type != rYearlyPos)
1330 rMonthPositions.clear();
1331 rMonthDays.clear();
1332 if (mParent) mParent->updated();
1333}
1334
1335int Recurrence::recurCalc(PeriodFunc func, QDateTime &endtime) const
1336{
1337 QDate enddate = endtime.date();
1338 switch (func) {
1339 case END_DATE_AND_COUNT:
1340 if (rDuration < 0) {
1341 endtime = QDateTime();
1342 return 0; // infinite recurrence
1343 }
1344 if (rDuration == 0) {
1345 endtime = rEndDateTime;
1346 func = COUNT_TO_DATE;
1347 }
1348 break;
1349 case COUNT_TO_DATE:
1350 // Count recurrences up to and including the specified date/time.
1351 if (endtime < mRecurStart)
1352 return 0;
1353 if (rDuration == 0 && endtime > rEndDateTime)
1354 enddate = rEndDateTime.date();
1355 else if (!mFloats && mRecurStart.time() > endtime.time())
1356 enddate = enddate.addDays(-1);
1357 break;
1358 case NEXT_AFTER_DATE:
1359 // Find next recurrence AFTER endtime
1360 if (endtime < mRecurStart) {
1361 endtime = mRecurStart;
1362 return 1;
1363 }
1364 if (rDuration == 0 && endtime >= rEndDateTime) {
1365 endtime = QDateTime();
1366 return 0;
1367 }
1368 if (!mFloats && mRecurStart.time() > endtime.time())
1369 enddate = enddate.addDays(-1);
1370 break;
1371 default:
1372 endtime = QDateTime();
1373 return 0;
1374 }
1375
1376 int count = 0; // default = error
1377 bool timed = false;
1378 switch (recurs) {
1379 case rMinutely:
1380 timed = true;
1381 count = secondlyCalc(func, endtime, rFreq*60);
1382 break;
1383 case rHourly:
1384 timed = true;
1385 count = secondlyCalc(func, endtime, rFreq*3600);
1386 break;
1387 case rDaily:
1388 count = dailyCalc(func, enddate);
1389 break;
1390 case rWeekly:
1391 count = weeklyCalc(func, enddate);
1392 break;
1393 case rMonthlyPos:
1394 case rMonthlyDay:
1395 count = monthlyCalc(func, enddate);
1396 break;
1397 case rYearlyMonth:
1398 count = yearlyMonthCalc(func, enddate);
1399 break;
1400 case rYearlyPos:
1401 count = yearlyPosCalc(func, enddate);
1402 break;
1403 case rYearlyDay:
1404 count = yearlyDayCalc(func, enddate);
1405 break;
1406 default:
1407 break;
1408 }
1409
1410 switch (func) {
1411 case END_DATE_AND_COUNT:
1412 case NEXT_AFTER_DATE:
1413 if (count == 0)
1414 endtime = QDateTime();
1415 else if (!timed) {
1416 endtime.setDate(enddate);
1417 endtime.setTime(mRecurStart.time());
1418 }
1419 break;
1420 case COUNT_TO_DATE:
1421 break;
1422 }
1423 return count;
1424}
1425
1426int Recurrence::recurCalc(PeriodFunc func, QDate &enddate) const
1427{
1428 QDateTime endtime(enddate, QTime(23,59,59));
1429 switch (func) {
1430 case END_DATE_AND_COUNT:
1431 if (rDuration < 0) {
1432 enddate = QDate();
1433 return 0; // infinite recurrence
1434 }
1435 if (rDuration == 0) {
1436 enddate = rEndDateTime.date();
1437 func = COUNT_TO_DATE;
1438 }
1439 break;
1440 case COUNT_TO_DATE:
1441 // Count recurrences up to and including the specified date.
1442 if (enddate < mRecurStart.date())
1443 return 0;
1444 if (rDuration == 0 && enddate > rEndDateTime.date()) {
1445 enddate = rEndDateTime.date();
1446 endtime.setDate(enddate);
1447 }
1448 break;
1449 case NEXT_AFTER_DATE:
1450 if (enddate < mRecurStart.date()) {
1451 enddate = mRecurStart.date();
1452 return 1;
1453 }
1454 if (rDuration == 0 && enddate >= rEndDateTime.date()) {
1455 enddate = QDate();
1456 return 0;
1457 }
1458 break;
1459 default:
1460 enddate = QDate();
1461 return 0;
1462 }
1463
1464 int count = 0; // default = error
1465 bool timed = false;
1466 switch (recurs) {
1467 case rMinutely:
1468 timed = true;
1469 count = secondlyCalc(func, endtime, rFreq*60);
1470 break;
1471 case rHourly:
1472 timed = true;
1473 count = secondlyCalc(func, endtime, rFreq*3600);
1474 break;
1475 case rDaily:
1476 count = dailyCalc(func, enddate);
1477 break;
1478 case rWeekly:
1479 count = weeklyCalc(func, enddate);
1480 break;
1481 case rMonthlyPos:
1482 case rMonthlyDay:
1483 count = monthlyCalc(func, enddate);
1484 break;
1485 case rYearlyMonth:
1486 count = yearlyMonthCalc(func, enddate);
1487 break;
1488 case rYearlyPos:
1489 count = yearlyPosCalc(func, enddate);
1490 break;
1491 case rYearlyDay:
1492 count = yearlyDayCalc(func, enddate);
1493 break;
1494 default:
1495 break;
1496 }
1497
1498 switch (func) {
1499 case END_DATE_AND_COUNT:
1500 case NEXT_AFTER_DATE:
1501 if (count == 0)
1502 endtime = QDate();
1503 else if (timed)
1504 enddate = endtime.date();
1505 break;
1506 case COUNT_TO_DATE:
1507 break;
1508 }
1509 return count;
1510}
1511
1512/* Find count and, depending on 'func', the end date/time of a secondly recurrence.
1513 * Reply = total number of occurrences up to 'endtime', or 0 if error.
1514 * If 'func' = END_DATE_AND_COUNT or NEXT_AFTER_DATE, 'endtime' is updated to the
1515 * recurrence end date/time.
1516 */
1517int Recurrence::secondlyCalc(PeriodFunc func, QDateTime &endtime, int freq) const
1518{
1519 switch (func) {
1520 case END_DATE_AND_COUNT:
1521 endtime = mRecurStart.addSecs((rDuration + mRecurExDatesCount - 1) * freq);
1522 return rDuration + mRecurExDatesCount;
1523 case COUNT_TO_DATE: {
1524 int n = mRecurStart.secsTo(endtime)/freq + 1;
1525 if (rDuration > 0 && n > rDuration + mRecurExDatesCount)
1526 return rDuration + mRecurExDatesCount;
1527 return n;
1528 }
1529 case NEXT_AFTER_DATE: {
1530 int count = mRecurStart.secsTo(endtime) / freq + 2;
1531 if (rDuration > 0 && count > rDuration)
1532 return 0;
1533 endtime = mRecurStart.addSecs((count - 1)*freq);
1534 return count;
1535 }
1536 }
1537 return 0;
1538}
1539
1540/* Find count and, depending on 'func', the end date of a daily recurrence.
1541 * Reply = total number of occurrences up to 'enddate', or 0 if error.
1542 * If 'func' = END_DATE_AND_COUNT or NEXT_AFTER_DATE, 'enddate' is updated to the
1543 * recurrence end date.
1544 */
1545int Recurrence::dailyCalc(PeriodFunc func, QDate &enddate) const
1546{
1547 QDate dStart = mRecurStart.date();
1548 switch (func) {
1549 case END_DATE_AND_COUNT:
1550 enddate = dStart.addDays((rDuration + mRecurExDatesCount - 1) * rFreq);
1551 return rDuration + mRecurExDatesCount;
1552 case COUNT_TO_DATE: {
1553 int n = dStart.daysTo(enddate)/rFreq + 1;
1554 if (rDuration > 0 && n > rDuration + mRecurExDatesCount)
1555 return rDuration + mRecurExDatesCount;
1556 return n;
1557 }
1558 case NEXT_AFTER_DATE: {
1559 int count = dStart.daysTo(enddate) / rFreq + 2;
1560 if (rDuration > 0 && count > rDuration)
1561 return 0;
1562 enddate = dStart.addDays((count - 1)*rFreq);
1563 return count;
1564 }
1565 }
1566 return 0;
1567}
1568
1569/* Find count and, depending on 'func', the end date of a weekly recurrence.
1570 * Reply = total number of occurrences up to 'enddate', or 0 if error.
1571 * If 'func' = END_DATE_AND_COUNT or NEXT_AFTER_DATE, 'enddate' is updated to the
1572 * recurrence end date.
1573 */
1574int Recurrence::weeklyCalc(PeriodFunc func, QDate &enddate) const
1575{
1576 int daysPerWeek = 0;
1577 for (int i = 0; i < 7; ++i) {
1578 if (rDays.testBit((uint)i))
1579 ++daysPerWeek;
1580 }
1581 if (!daysPerWeek)
1582 return 0; // there are no days to recur on
1583
1584 switch (func) {
1585 case END_DATE_AND_COUNT:
1586 return weeklyCalcEndDate(enddate, daysPerWeek);
1587 case COUNT_TO_DATE:
1588 return weeklyCalcToDate(enddate, daysPerWeek);
1589 case NEXT_AFTER_DATE:
1590 return weeklyCalcNextAfter(enddate, daysPerWeek);
1591 }
1592 return 0;
1593}
1594
1595int Recurrence::weeklyCalcEndDate(QDate &enddate, int daysPerWeek) const
1596{
1597 int startDayOfWeek = mRecurStart.date().dayOfWeek(); // 1..7
1598 int countGone = 0;
1599 int daysGone = 0;
1600 uint countTogo = rDuration + mRecurExDatesCount;
1601 if (startDayOfWeek != rWeekStart) {
1602 // Check what remains of the start week
1603 for (int i = startDayOfWeek - 1; i != rWeekStart - 1; i = (i + 1) % 7) {
1604 ++daysGone;
1605 if (rDays.testBit((uint)i)) {
1606 ++countGone;
1607 if (--countTogo == 0)
1608 break;
1609 }
1610 }
1611 daysGone += 7 * (rFreq - 1);
1612 }
1613 if (countTogo) {
1614 // Skip the remaining whole weeks
1615 // Leave at least 1 recurrence remaining, in order to get its date
1616 int wholeWeeks = (countTogo - 1) / daysPerWeek;
1617 daysGone += wholeWeeks * 7 * rFreq;
1618 countGone += wholeWeeks * daysPerWeek;
1619 countTogo -= wholeWeeks * daysPerWeek;
1620 // Check the last week in the recurrence
1621 for (int i = rWeekStart - 1; ; i = (i + 1) % 7) {
1622 ++daysGone;
1623 if (rDays.testBit((uint)i)) {
1624 ++countGone;
1625 if (--countTogo == 0)
1626 break;
1627 }
1628 }
1629 }
1630 enddate = mRecurStart.date().addDays(daysGone);
1631 return countGone;
1632}
1633
1634int Recurrence::weeklyCalcToDate(const QDate &enddate, int daysPerWeek) const
1635{
1636 QDate dStart = mRecurStart.date();
1637 int startDayOfWeek = dStart.dayOfWeek(); // 1..7
1638 int countGone = 0;
1639 int daysGone = 0;
1640 int totalDays = dStart.daysTo(enddate) + 1;
1641 int countMax = (rDuration > 0) ? rDuration + mRecurExDatesCount : INT_MAX;
1642
1643 if (startDayOfWeek != rWeekStart) {
1644 // Check what remains of the start week
1645 for (int i = startDayOfWeek - 1; i != rWeekStart - 1; i = (i + 1) % 7) {
1646 if (rDays.testBit((uint)i)) {
1647 if (++countGone >= countMax)
1648 return countMax;
1649 }
1650 if (++daysGone == totalDays)
1651 return countGone;
1652 }
1653 daysGone += 7 * (rFreq - 1);
1654 if (daysGone >= totalDays)
1655 return countGone;
1656 }
1657 // Skip the remaining whole weeks
1658 int wholeWeeks = (totalDays - daysGone) / 7;
1659 countGone += (wholeWeeks / rFreq) * daysPerWeek;
1660 if (countGone >= countMax)
1661 return countMax;
1662 daysGone += wholeWeeks * 7;
1663 if (daysGone >= totalDays // have we reached the end date?
1664 || wholeWeeks % rFreq) // is end week a recurrence week?
1665 return countGone;
1666
1667 // Check the last week in the recurrence
1668 for (int i = rWeekStart - 1; ; i = (i + 1) % 7) {
1669 if (rDays.testBit((uint)i)) {
1670 if (++countGone >= countMax)
1671 return countMax;
1672 }
1673 if (++daysGone == totalDays)
1674 return countGone;
1675 }
1676 return countGone;
1677}
1678
1679int Recurrence::weeklyCalcNextAfter(QDate &enddate, int daysPerWeek) const
1680{
1681 QDate dStart = mRecurStart.date();
1682 int startDayOfWeek = dStart.dayOfWeek(); // 1..7
1683 int totalDays = dStart.daysTo(enddate) + 1;
1684 uint countTogo = (rDuration > 0) ? rDuration + mRecurExDatesCount : UINT_MAX;
1685 int countGone = 0;
1686 int daysGone = 0;
1687 int recurWeeks;
1688
1689 if (startDayOfWeek != rWeekStart) {
1690 // Check what remains of the start week
1691 for (int i = startDayOfWeek - 1; i != rWeekStart - 1; i = (i + 1) % 7) {
1692 ++daysGone;
1693 if (rDays.testBit((uint)i)) {
1694 ++countGone;
1695 if (daysGone > totalDays)
1696 goto ex;
1697 if (--countTogo == 0)
1698 return 0;
1699 }
1700 }
1701 daysGone += 7 * (rFreq - 1);
1702 }
1703
1704 // Skip the remaining whole weeks
1705 recurWeeks = (totalDays - daysGone) / (7 * rFreq);
1706 if (recurWeeks) {
1707 int n = recurWeeks * daysPerWeek;
1708 if (static_cast<uint>(n) > countTogo)
1709 return 0; // reached end of recurrence
1710 countGone += n;
1711 countTogo -= n;
1712 daysGone += recurWeeks * 7 * rFreq;
1713 }
1714
1715 // Check the last week or two in the recurrence
1716 for ( ; ; ) {
1717 for (int i = rWeekStart - 1; ; i = (i + 1) % 7) {
1718 ++daysGone;
1719 if (rDays.testBit((uint)i)) {
1720 ++countGone;
1721 if (daysGone > totalDays)
1722 goto ex;
1723 if (--countTogo == 0)
1724 return 0;
1725 }
1726 }
1727 daysGone += 7 * (rFreq - 1);
1728 }
1729ex:
1730 enddate = dStart.addDays(daysGone);
1731 return countGone;
1732}
1733
1734/* Find count and, depending on 'func', the end date of a monthly recurrence.
1735 * Reply = total number of occurrences up to 'enddate', or 0 if error.
1736 * If 'func' = END_DATE_AND_COUNT or NEXT_AFTER_DATE, 'enddate' is updated to the
1737 * recurrence end date.
1738 */
1739struct Recurrence::MonthlyData {
1740 const Recurrence *recurrence;
1741 int year; // current year
1742 int month; // current month 0..11
1743 int day; // current day of month 1..31
1744 bool varies; // true if recurring days vary between different months
1745 private:
1746 QValueList<int> days28, days29, days30, days31; // recurring days in months of each length
1747 QValueList<int> *recurDays[4];
1748 public:
1749 MonthlyData(const Recurrence* r, const QDate &date)
1750 : recurrence(r), year(date.year()), month(date.month()-1), day(date.day())
1751 { recurDays[0] = &days28;
1752 recurDays[1] = &days29;
1753 recurDays[2] = &days30;
1754 recurDays[3] = &days31;
1755 varies = (recurrence->recurs == rMonthlyPos)
1756 ? true : recurrence->getMonthlyDayDays(days31, 31);
1757 }
1758 const QValueList<int>* dayList() const {
1759 if (!varies)
1760 return &days31;
1761 QDate startOfMonth(year, month + 1, 1);
1762 int daysInMonth = startOfMonth.daysInMonth();
1763 QValueList<int>* days = recurDays[daysInMonth - 28];
1764 if (recurrence->recurs == rMonthlyPos)
1765 recurrence->getMonthlyPosDays(*days, daysInMonth, startOfMonth.dayOfWeek());
1766 else if (days->isEmpty())
1767 recurrence->getMonthlyDayDays(*days, daysInMonth);
1768 return days;
1769 }
1770 int yearMonth() const { return year*12 + month; }
1771 void addMonths(int diff) { month += diff; year += month / 12; month %= 12; }
1772 QDate date() const { return QDate(year, month + 1, day); }
1773};
1774
1775int Recurrence::monthlyCalc(PeriodFunc func, QDate &enddate) const
1776{
1777 if (recurs == rMonthlyPos && rMonthPositions.isEmpty()
1778 || recurs == rMonthlyDay && rMonthDays.isEmpty())
1779 return 0;
1780
1781 MonthlyData data(this, mRecurStart.date());
1782 switch (func) {
1783 case END_DATE_AND_COUNT:
1784 return monthlyCalcEndDate(enddate, data);
1785 case COUNT_TO_DATE:
1786 return monthlyCalcToDate(enddate, data);
1787 case NEXT_AFTER_DATE:
1788 return monthlyCalcNextAfter(enddate, data);
1789 }
1790 return 0;
1791}
1792
1793int Recurrence::monthlyCalcEndDate(QDate &enddate, MonthlyData &data) const
1794{
1795 uint countTogo = rDuration + mRecurExDatesCount;
1796 int countGone = 0;
1797 QValueList<int>::ConstIterator it;
1798 const QValueList<int>* days = data.dayList();
1799
1800 if (data.day > 1) {
1801 // Check what remains of the start month
1802 for (it = days->begin(); it != days->end(); ++it) {
1803 if (*it >= data.day) {
1804 ++countGone;
1805 if (--countTogo == 0) {
1806 data.day = *it;
1807 break;
1808 }
1809 }
1810 }
1811 if (countTogo) {
1812 data.day = 1;
1813 data.addMonths(rFreq);
1814 }
1815 }
1816 if (countTogo) {
1817 if (data.varies) {
1818 // The number of recurrence days varies from month to month,
1819 // so we need to check month by month.
1820 for ( ; ; ) {
1821 days = data.dayList();
1822 uint n = days->count(); // number of recurrence days in this month
1823 if (n >= countTogo)
1824 break;
1825 countTogo -= n;
1826 countGone += n;
1827 data.addMonths(rFreq);
1828 }
1829 } else {
1830 // The number of recurrences is the same every month,
1831 // so skip the month-by-month check.
1832 // Skip the remaining whole months, but leave at least
1833 // 1 recurrence remaining, in order to get its date.
1834 int daysPerMonth = days->count();
1835 int wholeMonths = (countTogo - 1) / daysPerMonth;
1836 data.addMonths(wholeMonths * rFreq);
1837 countGone += wholeMonths * daysPerMonth;
1838 countTogo -= wholeMonths * daysPerMonth;
1839 }
1840 if (countTogo) {
1841 // Check the last month in the recurrence
1842 for (it = days->begin(); it != days->end(); ++it) {
1843 ++countGone;
1844 if (--countTogo == 0) {
1845 data.day = *it;
1846 break;
1847 }
1848 }
1849 }
1850 }
1851 enddate = data.date();
1852 return countGone;
1853}
1854
1855int Recurrence::monthlyCalcToDate(const QDate &enddate, MonthlyData &data) const
1856{
1857 int countGone = 0;
1858 int countMax = (rDuration > 0) ? rDuration + mRecurExDatesCount : INT_MAX;
1859 int endYear = enddate.year();
1860 int endMonth = enddate.month() - 1; // zero-based
1861 int endDay = enddate.day();
1862 int endYearMonth = endYear*12 + endMonth;
1863 QValueList<int>::ConstIterator it;
1864 const QValueList<int>* days = data.dayList();
1865
1866 if (data.day > 1) {
1867 // Check what remains of the start month
1868 for (it = days->begin(); it != days->end(); ++it) {
1869 if (*it >= data.day) {
1870 if (data.yearMonth() == endYearMonth && *it > endDay)
1871 return countGone;
1872 if (++countGone >= countMax)
1873 return countMax;
1874 }
1875 }
1876 data.day = 1;
1877 data.addMonths(rFreq);
1878 }
1879
1880 if (data.varies) {
1881 // The number of recurrence days varies from month to month,
1882 // so we need to check month by month.
1883 while (data.yearMonth() < endYearMonth) {
1884 countGone += data.dayList()->count();
1885 if (countGone >= countMax)
1886 return countMax;
1887 data.addMonths(rFreq);
1888 }
1889 days = data.dayList();
1890 } else {
1891 // The number of recurrences is the same every month,
1892 // so skip the month-by-month check.
1893 // Skip the remaining whole months.
1894 int daysPerMonth = days->count();
1895 int wholeMonths = endYearMonth - data.yearMonth();
1896 countGone += (wholeMonths / rFreq) * daysPerMonth;
1897 if (countGone >= countMax)
1898 return countMax;
1899 if (wholeMonths % rFreq)
1900 return countGone; // end year isn't a recurrence year
1901 data.year = endYear;
1902 data.month = endMonth;
1903 }
1904
1905 // Check the last month in the recurrence
1906 for (it = days->begin(); it != days->end(); ++it) {
1907 if (*it > endDay)
1908 return countGone;
1909 if (++countGone >= countMax)
1910 return countMax;
1911 }
1912 return countGone;
1913}
1914
1915int Recurrence::monthlyCalcNextAfter(QDate &enddate, MonthlyData &data) const
1916{
1917 uint countTogo = (rDuration > 0) ? rDuration + mRecurExDatesCount : UINT_MAX;
1918 int countGone = 0;
1919 int endYear = enddate.year();
1920 int endDay = enddate.day();
1921 int endYearMonth = endYear*12 + enddate.month() - 1;
1922 QValueList<int>::ConstIterator it;
1923 const QValueList<int>* days = data.dayList();
1924
1925 if (data.day > 1) {
1926 // Check what remains of the start month
1927 for (it = days->begin(); it != days->end(); ++it) {
1928 if (*it >= data.day) {
1929 ++countGone;
1930 if (data.yearMonth() == endYearMonth && *it > endDay) {
1931 data.day = *it;
1932 goto ex;
1933 }
1934 if (--countTogo == 0)
1935 return 0;
1936 }
1937 }
1938 data.day = 1;
1939 data.addMonths(rFreq);
1940 }
1941
1942 if (data.varies) {
1943 // The number of recurrence days varies from month to month,
1944 // so we need to check month by month.
1945 while (data.yearMonth() <= endYearMonth) {
1946 days = data.dayList();
1947 uint n = days->count(); // number of recurrence days in this month
1948 if (data.yearMonth() == endYearMonth && days->last() > endDay)
1949 break;
1950 if (n >= countTogo)
1951 return 0;
1952 countGone += n;
1953 countTogo -= n;
1954 data.addMonths(rFreq);
1955 }
1956 days = data.dayList();
1957 } else {
1958 // The number of recurrences is the same every month,
1959 // so skip the month-by-month check.
1960 // Skip the remaining whole months to at least end year/month.
1961 int daysPerMonth = days->count();
1962 int elapsed = endYearMonth - data.yearMonth();
1963 int recurMonths = (elapsed + rFreq - 1) / rFreq;
1964 if (elapsed % rFreq == 0 && days->last() <= endDay)
1965 ++recurMonths; // required month is after endYearMonth
1966 if (recurMonths) {
1967 int n = recurMonths * daysPerMonth;
1968 if (static_cast<uint>(n) > countTogo)
1969 return 0; // reached end of recurrence
1970 countTogo -= n;
1971 countGone += n;
1972 data.addMonths(recurMonths * rFreq);
1973 }
1974 }
1975
1976 // Check the last month in the recurrence
1977 for (it = days->begin(); it != days->end(); ++it) {
1978 ++countGone;
1979 if (data.yearMonth() > endYearMonth || *it > endDay) {
1980 data.day = *it;
1981 break;
1982 }
1983 if (--countTogo == 0)
1984 return 0;
1985 }
1986ex:
1987 enddate = data.date();
1988 return countGone;
1989}
1990
1991
1992/* Find count and, depending on 'func', the end date of an annual recurrence by date.
1993 * Reply = total number of occurrences up to 'enddate', or 0 if error.
1994 * If 'func' = END_DATE_AND_COUNT or NEXT_AFTER_DATE, 'enddate' is updated to the
1995 * recurrence end date.
1996 */
1997struct Recurrence::YearlyMonthData {
1998 const Recurrence *recurrence;
1999 int year; // current year
2000 int month; // current month 1..12
2001 int day; // current day of month 1..31
2002 bool leapyear; // true if February 29th recurs and current year is a leap year
2003 bool feb29; // true if February 29th recurs
2004 private:
2005 QValueList<int> months; // recurring months in non-leap years 1..12
2006 QValueList<int> leapMonths; // recurring months in leap years 1..12
2007 public:
2008 YearlyMonthData(const Recurrence* r, const QDate &date)
2009 : recurrence(r), year(date.year()), month(date.month()), day(date.day())
2010 { feb29 = recurrence->getYearlyMonthMonths(day, months, leapMonths);
2011 leapyear = feb29 && QDate::leapYear(year);
2012 }
2013 const QValueList<int>* monthList() const
2014 { return leapyear ? &leapMonths : &months; }
2015 const QValueList<int>* leapMonthList() const { return &leapMonths; }
2016 QDate date() const { return QDate(year, month, day); }
2017};
2018
2019int Recurrence::yearlyMonthCalc(PeriodFunc func, QDate &enddate) const
2020{
2021 if (rYearNums.isEmpty())
2022 return 0;
2023 YearlyMonthData data(this, mRecurStart.date());
2024 switch (func) {
2025 case END_DATE_AND_COUNT:
2026 return yearlyMonthCalcEndDate(enddate, data);
2027 case COUNT_TO_DATE:
2028 return yearlyMonthCalcToDate(enddate, data);
2029 case NEXT_AFTER_DATE:
2030 return yearlyMonthCalcNextAfter(enddate, data);
2031 }
2032 return 0;
2033}
2034
2035// Find total count and end date of an annual recurrence by date.
2036// Reply = total number of occurrences.
2037int Recurrence::yearlyMonthCalcEndDate(QDate &enddate, YearlyMonthData &data) const
2038{
2039 uint countTogo = rDuration + mRecurExDatesCount;
2040 int countGone = 0;
2041 QValueList<int>::ConstIterator it;
2042 const QValueList<int>* mons = data.monthList(); // get recurring months for this year
2043
2044 if (data.month > 1) {
2045 // Check what remains of the start year
2046 for (it = mons->begin(); it != mons->end(); ++it) {
2047 if (*it >= data.month) {
2048 ++countGone;
2049 if (--countTogo == 0) {
2050 data.month = *it;
2051 if (data.month == 2 && data.feb29 && !data.leapyear) {
2052 // The recurrence should end on February 29th, but it's a non-leap year
2053 switch (mFeb29YearlyType) {
2054 case rFeb28:
2055 data.day = 28;
2056 break;
2057 case rMar1:
2058 data.month = 3;
2059 data.day = 1;
2060 break;
2061 case rFeb29:
2062 break;
2063 }
2064 }
2065 break;
2066 }
2067 }
2068 }
2069 if (countTogo) {
2070 data.month = 1;
2071 data.year += rFreq;
2072 }
2073 }
2074 if (countTogo) {
2075 if (data.feb29 && mFeb29YearlyType == rFeb29) {
2076 // The number of recurrences is different on leap years,
2077 // so check year-by-year.
2078 for ( ; ; ) {
2079 mons = data.monthList();
2080 uint n = mons->count();
2081 if (n >= countTogo)
2082 break;
2083 countTogo -= n;
2084 countGone += n;
2085 data.year += rFreq;
2086 }
2087 } else {
2088 // The number of recurrences is the same every year,
2089 // so skip the year-by-year check.
2090 // Skip the remaining whole years, but leave at least
2091 // 1 recurrence remaining, in order to get its date.
2092 int monthsPerYear = mons->count();
2093 int wholeYears = (countTogo - 1) / monthsPerYear;
2094 data.year += wholeYears * rFreq;
2095 countGone += wholeYears * monthsPerYear;
2096 countTogo -= wholeYears * monthsPerYear;
2097 }
2098 if (countTogo) {
2099 // Check the last year in the recurrence
2100 for (it = mons->begin(); it != mons->end(); ++it) {
2101 ++countGone;
2102 if (--countTogo == 0) {
2103 data.month = *it;
2104 if (data.month == 2 && data.feb29 && !QDate::leapYear(data.year)) {
2105 // The recurrence should end on February 29th, but it's a non-leap year
2106 switch (mFeb29YearlyType) {
2107 case rFeb28:
2108 data.day = 28;
2109 break;
2110 case rMar1:
2111 data.month = 3;
2112 data.day = 1;
2113 break;
2114 case rFeb29:
2115 break;
2116 }
2117 }
2118 break;
2119 }
2120 }
2121 }
2122 }
2123 enddate = data.date();
2124 return countGone;
2125}
2126
2127// Find count of an annual recurrence by date.
2128// Reply = total number of occurrences up to 'enddate'.
2129int Recurrence::yearlyMonthCalcToDate(const QDate &enddate, YearlyMonthData &data) const
2130{
2131 int countGone = 0;
2132 int countMax = (rDuration > 0) ? rDuration + mRecurExDatesCount : INT_MAX;
2133 int endYear = enddate.year();
2134 int endMonth = enddate.month();
2135 int endDay = enddate.day();
2136 if (endDay < data.day) {
2137 /* The end day of the month is earlier than the recurrence day of the month.
2138 * If Feb 29th recurs and:
2139 * 1) it recurs on Feb 28th in non-leap years, don't adjust the end month
2140 * if enddate is Feb 28th on a non-leap year.
2141 * 2) it recurs on Mar 1st in non-leap years, allow the end month to be
2142 * adjusted to February, to simplify calculations.
2143 */
2144 if (data.feb29 && !QDate::leapYear(endYear)
2145 && mFeb29YearlyType == rFeb28 && endDay == 28 && endMonth == 2) {
2146 }
2147 else if (--endMonth == 0) {
2148 endMonth = 12;
2149 --endYear;
2150 }
2151 }
2152 QValueList<int>::ConstIterator it;
2153 const QValueList<int>* mons = data.monthList();
2154
2155 if (data.month > 1) {
2156 // Check what remains of the start year
2157 for (it = mons->begin(); it != mons->end(); ++it) {
2158 if (*it >= data.month) {
2159 if (data.year == endYear && *it > endMonth)
2160 return countGone;
2161 if (++countGone >= countMax)
2162 return countMax;
2163 }
2164 }
2165 data.month = 1;
2166 data.year += rFreq;
2167 }
2168 if (data.feb29 && mFeb29YearlyType == rFeb29) {
2169 // The number of recurrences is different on leap years,
2170 // so check year-by-year.
2171 while (data.year < endYear) {
2172 countGone += data.monthList()->count();
2173 if (countGone >= countMax)
2174 return countMax;
2175 data.year += rFreq;
2176 }
2177 mons = data.monthList();
2178 } else {
2179 // The number of recurrences is the same every year,
2180 // so skip the year-by-year check.
2181 // Skip the remaining whole years.
2182 int monthsPerYear = mons->count();
2183 int wholeYears = endYear - data.year;
2184 countGone += (wholeYears / rFreq) * monthsPerYear;
2185 if (countGone >= countMax)
2186 return countMax;
2187 if (wholeYears % rFreq)
2188 return countGone; // end year isn't a recurrence year
2189 data.year = endYear;
2190 }
2191
2192 // Check the last year in the recurrence
2193 for (it = mons->begin(); it != mons->end(); ++it) {
2194 if (*it > endMonth)
2195 return countGone;
2196 if (++countGone >= countMax)
2197 return countMax;
2198 }
2199 return countGone;
2200}
2201
2202// Find count and date of first recurrence after 'enddate' of an annual recurrence by date.
2203// Reply = total number of occurrences up to 'enddate'.
2204int Recurrence::yearlyMonthCalcNextAfter(QDate &enddate, YearlyMonthData &data) const
2205{
2206 uint countTogo = (rDuration > 0) ? rDuration + mRecurExDatesCount : UINT_MAX;
2207 int countGone = 0;
2208 int endYear = enddate.year();
2209 int endMonth = enddate.month();
2210 int endDay = enddate.day();
2211 bool mar1TooEarly = false;
2212 bool feb28ok = false;
2213 if (endDay < data.day) {
2214 if (data.feb29 && mFeb29YearlyType == rMar1 && endMonth == 3)
2215 mar1TooEarly = true;
2216 if (data.feb29 && mFeb29YearlyType == rFeb28 && endMonth == 2 && endDay == 28)
2217 feb28ok = true;
2218 else if (--endMonth == 0) {
2219 endMonth = 12;
2220 --endYear;
2221 }
2222 }
2223 QValueList<int>::ConstIterator it;
2224 const QValueList<int>* mons = data.monthList();
2225
2226 if (data.month > 1) {
2227 // Check what remains of the start year
2228 for (it = mons->begin(); it != mons->end(); ++it) {
2229 if (*it >= data.month) {
2230 ++countGone;
2231 if (data.year == endYear
2232 && ( *it > endMonth && (*it > 3 || !mar1TooEarly)
2233 || *it == 2 && feb28ok && data.leapyear)) {
2234 if (*it == 2 && data.feb29 && !data.leapyear) {
2235 // The next recurrence should be on February 29th, but it's a non-leap year
2236 switch (mFeb29YearlyType) {
2237 case rFeb28:
2238 data.month = 2;
2239 data.day = 28;
2240 break;
2241 case rMar1:
2242 data.month = 3;
2243 data.day = 1;
2244 break;
2245 case rFeb29: // impossible in this context!
2246 break;
2247 }
2248 }
2249 else
2250 data.month = *it;
2251 goto ex;
2252 }
2253 if (--countTogo == 0)
2254 return 0;
2255 }
2256 }
2257 data.month = 1;
2258 data.year += rFreq;
2259 }
2260
2261 if (data.feb29 && mFeb29YearlyType == rFeb29) {
2262 // The number of recurrences is different on leap years,
2263 // so check year-by-year.
2264 while (data.year <= endYear) {
2265 mons = data.monthList();
2266 if (data.year == endYear && mons->last() > endMonth)
2267 break;
2268 uint n = mons->count();
2269 if (n >= countTogo)
2270 break;
2271 countTogo -= n;
2272 countGone += n;
2273 data.year += rFreq;
2274 }
2275 mons = data.monthList();
2276 } else {
2277 // The number of recurrences is the same every year,
2278 // so skip the year-by-year check.
2279 // Skip the remaining whole years to at least endYear.
2280 int monthsPerYear = mons->count();
2281 int recurYears = (endYear - data.year + rFreq - 1) / rFreq;
2282 if ((endYear - data.year)%rFreq == 0
2283 && mons->last() <= endMonth)
2284 ++recurYears; // required year is after endYear
2285 if (recurYears) {
2286 int n = recurYears * monthsPerYear;
2287 if (static_cast<uint>(n) > countTogo)
2288 return 0; // reached end of recurrence
2289 countTogo -= n;
2290 countGone += n;
2291 data.year += recurYears * rFreq;
2292 }
2293 }
2294
2295 // Check the last year in the recurrence
2296 for (it = mons->begin(); it != mons->end(); ++it) {
2297 ++countGone;
2298 if (data.year > endYear
2299 || ( *it > endMonth && (*it > 3 || !mar1TooEarly)
2300 || *it == 2 && feb28ok && QDate::leapYear(data.year))) {
2301 if (*it == 2 && data.feb29 && !QDate::leapYear(data.year)) {
2302 // The next recurrence should be on February 29th, but it's a non-leap year
2303 switch (mFeb29YearlyType) {
2304 case rFeb28:
2305 data.month = 2;
2306 data.day = 28;
2307 break;
2308 case rMar1:
2309 data.month = 3;
2310 data.day = 1;
2311 break;
2312 case rFeb29: // impossible in this context!
2313 break;
2314 }
2315 }
2316 else
2317 data.month = *it;
2318 break;
2319 }
2320 if (--countTogo == 0)
2321 return 0;
2322 }
2323ex:
2324 enddate = data.date();
2325 return countGone;
2326}
2327
2328
2329/* Find count and, depending on 'func', the end date of an annual recurrence by date.
2330 * Reply = total number of occurrences up to 'enddate', or 0 if error.
2331 * If 'func' = END_DATE_AND_COUNT or NEXT_AFTER_DATE, 'enddate' is updated to the
2332 * recurrence end date.
2333 */
2334struct Recurrence::YearlyPosData {
2335 const Recurrence *recurrence;
2336 int year; // current year
2337 int month; // current month 1..12
2338 int day; // current day of month 1..31
2339 int daysPerMonth; // number of days which recur each month, or -1 if variable
2340 int count; // number of days which recur each year, or -1 if variable
2341 bool varies; // true if number of days varies from year to year
2342 private:
2343 mutable QValueList<int> days;
2344 public:
2345 YearlyPosData(const Recurrence* r, const QDate &date)
2346 : recurrence(r), year(date.year()), month(date.month()), day(date.day()), count(-1)
2347 { if ((daysPerMonth = r->countMonthlyPosDays()) > 0)
2348 count = daysPerMonth * r->rYearNums.count();
2349 varies = (daysPerMonth < 0);
2350 }
2351 const QValueList<int>* dayList() const {
2352 QDate startOfMonth(year, month, 1);
2353 recurrence->getMonthlyPosDays(days, startOfMonth.daysInMonth(), startOfMonth.dayOfWeek());
2354 return &days;
2355 }
2356 int yearMonth() const { return year*12 + month - 1; }
2357 void addMonths(int diff) { month += diff - 1; year += month / 12; month = month % 12 + 1; }
2358 QDate date() const { return QDate(year, month, day); }
2359};
2360
2361int Recurrence::yearlyPosCalc(PeriodFunc func, QDate &enddate) const
2362{
2363 if (rYearNums.isEmpty() || rMonthPositions.isEmpty())
2364 return 0;
2365 YearlyPosData data(this, mRecurStart.date());
2366 switch (func) {
2367 case END_DATE_AND_COUNT:
2368 return yearlyPosCalcEndDate(enddate, data);
2369 case COUNT_TO_DATE:
2370 return yearlyPosCalcToDate(enddate, data);
2371 case NEXT_AFTER_DATE:
2372 return yearlyPosCalcNextAfter(enddate, data);
2373 }
2374 return 0;
2375}
2376
2377int Recurrence::yearlyPosCalcEndDate(QDate &enddate, YearlyPosData &data) const
2378{
2379 uint countTogo = rDuration + mRecurExDatesCount;
2380 int countGone = 0;
2381 QValueList<int>::ConstIterator id;
2382 const QValueList<int>* days;
2383
2384 if (data.month > 1 || data.day > 1) {
2385 // Check what remains of the start year
2386 for (QPtrListIterator<int> im(rYearNums); im.current(); ++im) {
2387 if (*im.current() >= data.month) {
2388 // Check what remains of the start month
2389 if (data.day > 1 || data.varies
2390 || static_cast<uint>(data.daysPerMonth) >= countTogo) {
2391 data.month = *im.current();
2392 days = data.dayList();
2393 for (id = days->begin(); id != days->end(); ++id) {
2394 if (*id >= data.day) {
2395 ++countGone;
2396 if (--countTogo == 0) {
2397 data.month = *im.current();
2398 data.day = *id;
2399 goto ex;
2400 }
2401 }
2402 }
2403 data.day = 1;
2404 } else {
2405 // The number of days per month is constant, so skip
2406 // the whole month.
2407 countTogo -= data.daysPerMonth;
2408 countGone += data.daysPerMonth;
2409 }
2410 }
2411 }
2412 data.month = 1;
2413 data.year += rFreq;
2414 }
2415
2416 if (data.varies) {
2417 // The number of recurrences varies from year to year.
2418 for ( ; ; ) {
2419 for (QPtrListIterator<int> im(rYearNums); im.current(); ++im) {
2420 data.month = *im.current();
2421 days = data.dayList();
2422 int n = days->count();
2423 if (static_cast<uint>(n) >= countTogo) {
2424 // Check the last month in the recurrence
2425 for (id = days->begin(); id != days->end(); ++id) {
2426 ++countGone;
2427 if (--countTogo == 0) {
2428 data.day = *id;
2429 goto ex;
2430 }
2431 }
2432 }
2433 countTogo -= n;
2434 countGone += n;
2435 }
2436 data.year += rFreq;
2437 }
2438 } else {
2439 // The number of recurrences is the same every year,
2440 // so skip the year-by-year check.
2441 // Skip the remaining whole years, but leave at least
2442 // 1 recurrence remaining, in order to get its date.
2443 int wholeYears = (countTogo - 1) / data.count;
2444 data.year += wholeYears * rFreq;
2445 countGone += wholeYears * data.count;
2446 countTogo -= wholeYears * data.count;
2447
2448 // Check the last year in the recurrence.
2449 for (QPtrListIterator<int> im(rYearNums); im.current(); ++im) {
2450 if (static_cast<uint>(data.daysPerMonth) >= countTogo) {
2451 // Check the last month in the recurrence
2452 data.month = *im.current();
2453 days = data.dayList();
2454 for (id = days->begin(); id != days->end(); ++id) {
2455 ++countGone;
2456 if (--countTogo == 0) {
2457 data.day = *id;
2458 goto ex;
2459 }
2460 }
2461 }
2462 countTogo -= data.daysPerMonth;
2463 countGone += data.daysPerMonth;
2464 }
2465 data.year += rFreq;
2466 }
2467ex:
2468 enddate = data.date();
2469 return countGone;
2470}
2471
2472int Recurrence::yearlyPosCalcToDate(const QDate &enddate, YearlyPosData &data) const
2473{
2474 int countGone = 0;
2475 int countMax = (rDuration > 0) ? rDuration + mRecurExDatesCount : INT_MAX;
2476 int endYear = enddate.year();
2477 int endMonth = enddate.month();
2478 int endDay = enddate.day();
2479 if (endDay < data.day && --endMonth == 0) {
2480 endMonth = 12;
2481 --endYear;
2482 }
2483 int endYearMonth = endYear*12 + endMonth;
2484 QValueList<int>::ConstIterator id;
2485 const QValueList<int>* days;
2486
2487 if (data.month > 1 || data.day > 1) {
2488 // Check what remains of the start year
2489 for (QPtrListIterator<int> im(rYearNums); im.current(); ++im) {
2490 if (*im.current() >= data.month) {
2491 data.month = *im.current();
2492 if (data.yearMonth() > endYearMonth)
2493 return countGone;
2494 // Check what remains of the start month
2495 bool lastMonth = (data.yearMonth() == endYearMonth);
2496 if (lastMonth || data.day > 1 || data.varies) {
2497 days = data.dayList();
2498 if (lastMonth || data.day > 1) {
2499 for (id = days->begin(); id != days->end(); ++id) {
2500 if (*id >= data.day) {
2501 if (lastMonth && *id > endDay)
2502 return countGone;
2503 if (++countGone >= countMax)
2504 return countMax;
2505 }
2506 }
2507 } else {
2508 countGone += days->count();
2509 if (countGone >= countMax)
2510 return countMax;
2511 }
2512 data.day = 1;
2513 } else {
2514 // The number of days per month is constant, so skip
2515 // the whole month.
2516 countGone += data.daysPerMonth;
2517 if (countGone >= countMax)
2518 return countMax;
2519 }
2520 }
2521 }
2522 data.month = 1;
2523 data.year += rFreq;
2524 }
2525
2526 if (data.varies) {
2527 // The number of recurrences varies from year to year.
2528 for ( ; ; ) {
2529 for (QPtrListIterator<int> im(rYearNums); im.current(); ++im) {
2530 data.month = *im.current();
2531 days = data.dayList();
2532 if (data.yearMonth() >= endYearMonth) {
2533 if (data.yearMonth() > endYearMonth)
2534 return countGone;
2535 // Check the last month in the recurrence
2536 for (id = days->begin(); id != days->end(); ++id) {
2537 if (*id > endDay)
2538 return countGone;
2539 if (++countGone >= countMax)
2540 return countMax;
2541 }
2542 } else {
2543 countGone += days->count();
2544 if (countGone >= countMax)
2545 return countMax;
2546 }
2547 }
2548 data.year += rFreq;
2549 }
2550 } else {
2551 // The number of recurrences is the same every year,
2552 // so skip the year-by-year check.
2553 // Skip the remaining whole years, but leave at least
2554 // 1 recurrence remaining, in order to get its date.
2555 int wholeYears = endYear - data.year;
2556 countGone += (wholeYears / rFreq) * data.count;
2557 if (countGone >= countMax)
2558 return countMax;
2559 if (wholeYears % rFreq)
2560 return countGone; // end year isn't a recurrence year
2561 data.year = endYear;
2562
2563 // Check the last year in the recurrence.
2564 for (QPtrListIterator<int> im(rYearNums); im.current(); ++im) {
2565 data.month = *im.current();
2566 if (data.month >= endMonth) {
2567 if (data.month > endMonth)
2568 return countGone;
2569 // Check the last month in the recurrence
2570 days = data.dayList();
2571 for (id = days->begin(); id != days->end(); ++id) {
2572 if (*id > endDay)
2573 return countGone;
2574 if (++countGone >= countMax)
2575 return countMax;
2576 }
2577 } else {
2578 countGone += data.daysPerMonth;
2579 if (countGone >= countMax)
2580 return countMax;
2581 }
2582 }
2583 }
2584 return countGone;
2585}
2586
2587int Recurrence::yearlyPosCalcNextAfter(QDate &enddate, YearlyPosData &data) const
2588{
2589 uint countTogo = (rDuration > 0) ? rDuration + mRecurExDatesCount : UINT_MAX;
2590 int countGone = 0;
2591 int endYear = enddate.year();
2592 int endMonth = enddate.month();
2593 int endDay = enddate.day();
2594 if (endDay < data.day && --endMonth == 0) {
2595 endMonth = 12;
2596 --endYear;
2597 }
2598 int endYearMonth = endYear*12 + endMonth;
2599 QValueList<int>::ConstIterator id;
2600 const QValueList<int>* days;
2601
2602 if (data.varies) {
2603 // The number of recurrences varies from year to year.
2604 for ( ; ; ) {
2605 // Check the next year
2606 for (QPtrListIterator<int> im(rYearNums); im.current(); ++im) {
2607 if (*im.current() >= data.month) {
2608 // Check the next month
2609 data.month = *im.current();
2610 int ended = data.yearMonth() - endYearMonth;
2611 days = data.dayList();
2612 if (ended >= 0 || data.day > 1) {
2613 // This is the start or end month, so check each day
2614 for (id = days->begin(); id != days->end(); ++id) {
2615 if (*id >= data.day) {
2616 ++countGone;
2617 if (ended > 0 || (ended == 0 && *id > endDay)) {
2618 data.day = *id;
2619 goto ex;
2620 }
2621 if (--countTogo == 0)
2622 return 0;
2623 }
2624 }
2625 } else {
2626 // Skip the whole month
2627 uint n = days->count();
2628 if (n >= countTogo)
2629 return 0;
2630 countGone += n;
2631 }
2632 data.day = 1; // we've checked the start month now
2633 }
2634 }
2635 data.month = 1; // we've checked the start year now
2636 data.year += rFreq;
2637 }
2638 } else {
2639 // The number of recurrences is the same every year.
2640 if (data.month > 1 || data.day > 1) {
2641 // Check what remains of the start year
2642 for (QPtrListIterator<int> im(rYearNums); im.current(); ++im) {
2643 if (*im.current() >= data.month) {
2644 // Check what remains of the start month
2645 data.month = *im.current();
2646 int ended = data.yearMonth() - endYearMonth;
2647 if (ended >= 0 || data.day > 1) {
2648 // This is the start or end month, so check each day
2649 days = data.dayList();
2650 for (id = days->begin(); id != days->end(); ++id) {
2651 if (*id >= data.day) {
2652 ++countGone;
2653 if (ended > 0 || (ended == 0 && *id > endDay)) {
2654 data.day = *id;
2655 goto ex;
2656 }
2657 if (--countTogo == 0)
2658 return 0;
2659 }
2660 }
2661 data.day = 1; // we've checked the start month now
2662 } else {
2663 // Skip the whole month.
2664 if (static_cast<uint>(data.daysPerMonth) >= countTogo)
2665 return 0;
2666 countGone += data.daysPerMonth;
2667 }
2668 }
2669 }
2670 data.year += rFreq;
2671 }
2672 // Skip the remaining whole years to at least endYear.
2673 int recurYears = (endYear - data.year + rFreq - 1) / rFreq;
2674 if ((endYear - data.year)%rFreq == 0
2675 && *rYearNums.getLast() <= endMonth)
2676 ++recurYears; // required year is after endYear
2677 if (recurYears) {
2678 int n = recurYears * data.count;
2679 if (static_cast<uint>(n) > countTogo)
2680 return 0; // reached end of recurrence
2681 countTogo -= n;
2682 countGone += n;
2683 data.year += recurYears * rFreq;
2684 }
2685
2686 // Check the last year in the recurrence
2687 for (QPtrListIterator<int> im(rYearNums); im.current(); ++im) {
2688 data.month = *im.current();
2689 int ended = data.yearMonth() - endYearMonth;
2690 if (ended >= 0) {
2691 // This is the end month, so check each day
2692 days = data.dayList();
2693 for (id = days->begin(); id != days->end(); ++id) {
2694 ++countGone;
2695 if (ended > 0 || (ended == 0 && *id > endDay)) {
2696 data.day = *id;
2697 goto ex;
2698 }
2699 if (--countTogo == 0)
2700 return 0;
2701 }
2702 } else {
2703 // Skip the whole month.
2704 if (static_cast<uint>(data.daysPerMonth) >= countTogo)
2705 return 0;
2706 countGone += data.daysPerMonth;
2707 }
2708 }
2709 }
2710ex:
2711 enddate = data.date();
2712 return countGone;
2713}
2714
2715
2716/* Find count and, depending on 'func', the end date of an annual recurrence by day.
2717 * Reply = total number of occurrences up to 'enddate', or 0 if error.
2718 * If 'func' = END_DATE_AND_COUNT or NEXT_AFTER_DATE, 'enddate' is updated to the
2719 * recurrence end date.
2720 */
2721struct Recurrence::YearlyDayData {
2722 int year; // current year
2723 int day; // current day of year 1..366
2724 bool varies; // true if day 366 recurs
2725 private:
2726 int daycount;
2727 public:
2728 YearlyDayData(const Recurrence* r, const QDate &date)
2729 : year(date.year()), day(date.dayOfYear()), varies(*r->rYearNums.getLast() == 366),
2730 daycount(r->rYearNums.count()) { }
2731 bool leapYear() const { return QDate::leapYear(year); }
2732 int dayCount() const { return daycount - (varies && !QDate::leapYear(year) ? 1 : 0); }
2733 bool isMaxDayCount() const { return !varies || QDate::leapYear(year); }
2734 QDate date() const { return QDate(year, 1, 1).addDays(day - 1); }
2735};
2736
2737int Recurrence::yearlyDayCalc(PeriodFunc func, QDate &enddate) const
2738{
2739 if (rYearNums.isEmpty())
2740 return 0;
2741 YearlyDayData data(this, mRecurStart.date());
2742 switch (func) {
2743 case END_DATE_AND_COUNT:
2744 return yearlyDayCalcEndDate(enddate, data);
2745 case COUNT_TO_DATE:
2746 return yearlyDayCalcToDate(enddate, data);
2747 case NEXT_AFTER_DATE:
2748 return yearlyDayCalcNextAfter(enddate, data);
2749 }
2750 return 0;
2751}
2752
2753int Recurrence::yearlyDayCalcEndDate(QDate &enddate, YearlyDayData &data) const
2754{
2755 uint countTogo = rDuration + mRecurExDatesCount;
2756 int countGone = 0;
2757
2758 if (data.day > 1) {
2759 // Check what remains of the start year
2760 bool leapOK = data.isMaxDayCount();
2761 for (QPtrListIterator<int> it(rYearNums); it.current(); ++it) {
2762 int d = *it.current();
2763 if (d >= data.day && (leapOK || d < 366)) {
2764 ++countGone;
2765 if (--countTogo == 0) {
2766 data.day = d;
2767 goto ex;
2768 }
2769 }
2770 }
2771 data.day = 1;
2772 data.year += rFreq;
2773 }
2774
2775 if (data.varies) {
2776 // The number of recurrences is different in leap years,
2777 // so check year-by-year.
2778 for ( ; ; ) {
2779 uint n = data.dayCount();
2780 if (n >= countTogo)
2781 break;
2782 countTogo -= n;
2783 countGone += n;
2784 data.year += rFreq;
2785 }
2786 } else {
2787 // The number of recurrences is the same every year,
2788 // so skip the year-by-year check.
2789 // Skip the remaining whole years, but leave at least
2790 // 1 recurrence remaining, in order to get its date.
2791 int daysPerYear = rYearNums.count();
2792 int wholeYears = (countTogo - 1) / daysPerYear;
2793 data.year += wholeYears * rFreq;
2794 countGone += wholeYears * daysPerYear;
2795 countTogo -= wholeYears * daysPerYear;
2796 }
2797 if (countTogo) {
2798 // Check the last year in the recurrence
2799 for (QPtrListIterator<int> it(rYearNums); it.current(); ++it) {
2800 ++countGone;
2801 if (--countTogo == 0) {
2802 data.day = *it.current();
2803 break;
2804 }
2805 }
2806 }
2807ex:
2808 enddate = data.date();
2809 return countGone;
2810}
2811
2812int Recurrence::yearlyDayCalcToDate(const QDate &enddate, YearlyDayData &data) const
2813{
2814 int countGone = 0;
2815 int countMax = (rDuration > 0) ? rDuration + mRecurExDatesCount : INT_MAX;
2816 int endYear = enddate.year();
2817 int endDay = enddate.dayOfYear();
2818
2819 if (data.day > 1) {
2820 // Check what remains of the start year
2821 bool leapOK = data.isMaxDayCount();
2822 for (QPtrListIterator<int> it(rYearNums); it.current(); ++it) {
2823 int d = *it.current();
2824 if (d >= data.day && (leapOK || d < 366)) {
2825 if (data.year == endYear && d > endDay)
2826 return countGone;
2827 if (++countGone >= countMax)
2828 return countMax;
2829 }
2830 }
2831 data.day = 1;
2832 data.year += rFreq;
2833 }
2834
2835 if (data.varies) {
2836 // The number of recurrences is different in leap years,
2837 // so check year-by-year.
2838 while (data.year < endYear) {
2839 uint n = data.dayCount();
2840 countGone += n;
2841 if (countGone >= countMax)
2842 return countMax;
2843 data.year += rFreq;
2844 }
2845 if (data.year > endYear)
2846 return countGone;
2847 } else {
2848 // The number of recurrences is the same every year.
2849 // Skip the remaining whole years.
2850 int wholeYears = endYear - data.year;
2851 countGone += (wholeYears / rFreq) * rYearNums.count();
2852 if (countGone >= countMax)
2853 return countMax;
2854 if (wholeYears % rFreq)
2855 return countGone; // end year isn't a recurrence year
2856 data.year = endYear;
2857 }
2858
2859 if (data.year <= endYear) {
2860 // Check the last year in the recurrence
2861 for (QPtrListIterator<int> it(rYearNums); it.current(); ++it) {
2862 if (*it.current() > endDay)
2863 return countGone;
2864 if (++countGone >= countMax)
2865 return countMax;
2866 }
2867 }
2868 return countGone;
2869}
2870
2871int Recurrence::yearlyDayCalcNextAfter(QDate &enddate, YearlyDayData &data) const
2872{
2873 uint countTogo = (rDuration > 0) ? rDuration + mRecurExDatesCount : UINT_MAX;
2874 int countGone = 0;
2875 int endYear = enddate.year();
2876 int endDay = enddate.dayOfYear();
2877
2878 if (data.day > 1) {
2879 // Check what remains of the start year
2880 bool leapOK = data.isMaxDayCount();
2881 for (QPtrListIterator<int> it(rYearNums); it.current(); ++it) {
2882 int d = *it.current();
2883 if (d >= data.day && (leapOK || d < 366)) {
2884 ++countGone;
2885 if (data.year == endYear && d > endDay) {
2886 data.day = d;
2887 goto ex;
2888 }
2889 if (--countTogo == 0)
2890 return 0;
2891 }
2892 }
2893 data.day = 1;
2894 data.year += rFreq;
2895 }
2896
2897 if (data.varies) {
2898 // The number of recurrences is different in leap years,
2899 // so check year-by-year.
2900 while (data.year <= endYear) {
2901 uint n = data.dayCount();
2902 if (data.year == endYear && *rYearNums.getLast() > endDay)
2903 break;
2904 if (n >= countTogo)
2905 break;
2906 countTogo -= n;
2907 countGone += n;
2908 data.year += rFreq;
2909 }
2910 } else {
2911 // The number of recurrences is the same every year,
2912 // so skip the year-by-year check.
2913 // Skip the remaining whole years to at least endYear.
2914 int daysPerYear = rYearNums.count();
2915 int recurYears = (endYear - data.year + rFreq - 1) / rFreq;
2916 if ((endYear - data.year)%rFreq == 0
2917 && *rYearNums.getLast() <= endDay)
2918 ++recurYears; // required year is after endYear
2919 if (recurYears) {
2920 int n = recurYears * daysPerYear;
2921 if (static_cast<uint>(n) > countTogo)
2922 return 0; // reached end of recurrence
2923 countTogo -= n;
2924 countGone += n;
2925 data.year += recurYears * rFreq;
2926 }
2927 }
2928
2929 // Check the last year in the recurrence
2930 for (QPtrListIterator<int> it(rYearNums); it.current(); ++it) {
2931 ++countGone;
2932 int d = *it.current();
2933 if (data.year > endYear || d > endDay) {
2934 data.day = d;
2935 break;
2936 }
2937 if (--countTogo == 0)
2938 return 0;
2939 }
2940ex:
2941 enddate = data.date();
2942 return countGone;
2943}
2944
2945// Get the days in this month which recur, in numerical order.
2946// Parameters: daysInMonth = number of days in this month
2947// startDayOfWeek = day of week for first day of month.
2948void Recurrence::getMonthlyPosDays(QValueList<int> &list, int daysInMonth, int startDayOfWeek) const
2949{
2950 list.clear();
2951 int endDayOfWeek = (startDayOfWeek + daysInMonth - 2) % 7 + 1;
2952 // Go through the list, compiling a bit list of actual day numbers
2953 Q_UINT32 days = 0;
2954 for (QPtrListIterator<rMonthPos> pos(rMonthPositions); pos.current(); ++pos) {
2955 int weeknum = pos.current()->rPos - 1; // get 0-based week number
2956 QBitArray &rdays = pos.current()->rDays;
2957 if (pos.current()->negative) {
2958 // nth days before the end of the month
2959 for (uint i = 1; i <= 7; ++i) {
2960 if (rdays.testBit(i - 1)) {
2961 int day = daysInMonth - weeknum*7 - (endDayOfWeek - i + 7) % 7;
2962 if (day > 0)
2963 days |= 1 << (day - 1);
2964 }
2965 }
2966 } else {
2967 // nth days after the start of the month
2968 for (uint i = 1; i <= 7; ++i) {
2969 if (rdays.testBit(i - 1)) {
2970 int day = 1 + weeknum*7 + (i - startDayOfWeek + 7) % 7;
2971 if (day <= daysInMonth)
2972 days |= 1 << (day - 1);
2973 }
2974 }
2975 }
2976 }
2977 // Compile the ordered list
2978 Q_UINT32 mask = 1;
2979 for (int i = 0; i < daysInMonth; mask <<= 1, ++i) {
2980 if (days & mask)
2981 list.append(i + 1);
2982 }
2983}
2984
2985// Get the number of days in the month which recur.
2986// Reply = -1 if the number varies from month to month.
2987int Recurrence::countMonthlyPosDays() const
2988{
2989 int count = 0;
2990 Q_UINT8 positive[5] = { 0, 0, 0, 0, 0 };
2991 Q_UINT8 negative[4] = { 0, 0, 0, 0 };
2992 for (QPtrListIterator<rMonthPos> pos(rMonthPositions); pos.current(); ++pos) {
2993 int weeknum = pos.current()->rPos;
2994 Q_UINT8* wk;
2995 if (pos.current()->negative) {
2996 // nth days before the end of the month
2997 if (weeknum > 4)
2998 return -1; // days in 5th week are often missing
2999 wk = &negative[4 - weeknum];
3000 } else {
3001 // nth days after the start of the month
3002 if (weeknum > 4)
3003 return -1; // days in 5th week are often missing
3004 wk = &positive[weeknum - 1];
3005 }
3006 QBitArray &rdays = pos.current()->rDays;
3007 for (uint i = 0; i < 7; ++i) {
3008 if (rdays.testBit(i)) {
3009 ++count;
3010 *wk |= (1 << i);
3011 }
3012 }
3013 }
3014 // Check for any possible days which could be duplicated by
3015 // a positive and a negative position.
3016 for (int i = 0; i < 4; ++i) {
3017 if (negative[i] & (positive[i] | positive[i+1]))
3018 return -1;
3019 }
3020 return count;
3021}
3022
3023// Get the days in this month which recur, in numerical order.
3024// Reply = true if day numbers varies from month to month.
3025bool Recurrence::getMonthlyDayDays(QValueList<int> &list, int daysInMonth) const
3026{
3027 list.clear();
3028 bool variable = false;
3029 Q_UINT32 days = 0;
3030 for (QPtrListIterator<int> it(rMonthDays); it.current(); ++it) {
3031 int day = *it.current();
3032 if (day > 0) {
3033 // date in the month
3034 if (day <= daysInMonth)
3035 days |= 1 << (day - 1);
3036 if (day > 28 && day <= 31)
3037 variable = true; // this date does not appear in some months
3038 } else if (day < 0) {
3039 // days before the end of the month
3040 variable = true; // this date varies depending on the month length
3041 day = daysInMonth + day; // zero-based day of month
3042 if (day >= 0)
3043 days |= 1 << day;
3044 }
3045 }
3046 // Compile the ordered list
3047 Q_UINT32 mask = 1;
3048 for (int i = 0; i < daysInMonth; mask <<= 1, ++i) {
3049 if (days & mask)
3050 list.append(i + 1);
3051 }
3052 return variable;
3053}
3054
3055// Get the months which recur, in numerical order, for both leap years and non-leap years.
3056// N.B. If February 29th recurs on March 1st in non-leap years, February (not March) is
3057// included in the non-leap year month list.
3058// Reply = true if February 29th also recurs.
3059bool Recurrence::getYearlyMonthMonths(int day, QValueList<int> &list, QValueList<int> &leaplist) const
3060{
3061 list.clear();
3062 leaplist.clear();
3063 bool feb29 = false;
3064 for (QPtrListIterator<int> it(rYearNums); it.current(); ++it) {
3065 int month = *it.current();
3066 if (month == 2) {
3067 if (day <= 28) {
3068 list.append(month); // date appears in February
3069 leaplist.append(month);
3070 }
3071 else if (day == 29) {
3072 // February 29th
3073 leaplist.append(month);
3074 switch (mFeb29YearlyType) {
3075 case rFeb28:
3076 case rMar1:
3077 list.append(2);
3078 break;
3079 case rFeb29:
3080 break;
3081 }
3082 feb29 = true;
3083 }
3084 }
3085 else if (day <= 30 || QDate(2000, month, 1).daysInMonth() == 31) {
3086 list.append(month); // date appears in every month
3087 leaplist.append(month);
3088 }
3089 }
3090 return feb29;
3091}
3092
3093/* From the recurrence day of the week list, get the earliest day in the
3094 * specified week which is >= the startDay.
3095 * Parameters: startDay = 1..7 (Monday..Sunday)
3096 * useWeekStart = true to end search at day before next rWeekStart
3097 * = false to search for a full 7 days
3098 * Reply = day of the week (1..7), or 0 if none found.
3099 */
3100int Recurrence::getFirstDayInWeek(int startDay, bool useWeekStart) const
3101{
3102 int last = ((useWeekStart ? rWeekStart : startDay) + 5)%7;
3103 for (int i = startDay - 1; ; i = (i + 1)%7) {
3104 if (rDays.testBit(i))
3105 return i + 1;
3106 if (i == last)
3107 return 0;
3108 }
3109}
3110
3111/* From the recurrence day of the week list, get the latest day in the
3112 * specified week which is <= the endDay.
3113 * Parameters: endDay = 1..7 (Monday..Sunday)
3114 * useWeekStart = true to end search at rWeekStart
3115 * = false to search for a full 7 days
3116 * Reply = day of the week (1..7), or 0 if none found.
3117 */
3118int Recurrence::getLastDayInWeek(int endDay, bool useWeekStart) const
3119{
3120 int last = useWeekStart ? rWeekStart - 1 : endDay%7;
3121 for (int i = endDay - 1; ; i = (i + 6)%7) {
3122 if (rDays.testBit(i))
3123 return i + 1;
3124 if (i == last)
3125 return 0;
3126 }
3127}
3128
3129/* From the recurrence monthly day number list or monthly day of week/week of
3130 * month list, get the earliest day in the specified month which is >= the
3131 * earliestDate.
3132 */
3133QDate Recurrence::getFirstDateInMonth(const QDate &earliestDate) const
3134{
3135 int earliestDay = earliestDate.day();
3136 int daysInMonth = earliestDate.daysInMonth();
3137 switch (recurs) {
3138 case rMonthlyDay: {
3139 int minday = daysInMonth + 1;
3140 for (QPtrListIterator<int> it(rMonthDays); it.current(); ++it) {
3141 int day = *it.current();
3142 if (day < 0)
3143 day = daysInMonth + day + 1;
3144 if (day >= earliestDay && day < minday)
3145 minday = day;
3146 }
3147 if (minday <= daysInMonth)
3148 return earliestDate.addDays(minday - earliestDay);
3149 break;
3150 }
3151 case rMonthlyPos:
3152 case rYearlyPos: {
3153 QDate monthBegin(earliestDate.addDays(1 - earliestDay));
3154 QValueList<int> dayList;
3155 getMonthlyPosDays(dayList, daysInMonth, monthBegin.dayOfWeek());
3156 for (QValueList<int>::ConstIterator id = dayList.begin(); id != dayList.end(); ++id) {
3157 if (*id >= earliestDay)
3158 return monthBegin.addDays(*id - 1);
3159 }
3160 break;
3161 }
3162 }
3163 return QDate();
3164}
3165
3166/* From the recurrence monthly day number list or monthly day of week/week of
3167 * month list, get the latest day in the specified month which is <= the
3168 * latestDate.
3169 */
3170QDate Recurrence::getLastDateInMonth(const QDate &latestDate) const
3171{
3172 int latestDay = latestDate.day();
3173 int daysInMonth = latestDate.daysInMonth();
3174 switch (recurs) {
3175 case rMonthlyDay: {
3176 int maxday = -1;
3177 for (QPtrListIterator<int> it(rMonthDays); it.current(); ++it) {
3178 int day = *it.current();
3179 if (day < 0)
3180 day = daysInMonth + day + 1;
3181 if (day <= latestDay && day > maxday)
3182 maxday = day;
3183 }
3184 if (maxday > 0)
3185 return QDate(latestDate.year(), latestDate.month(), maxday);
3186 break;
3187 }
3188 case rMonthlyPos:
3189 case rYearlyPos: {
3190 QDate monthBegin(latestDate.addDays(1 - latestDay));
3191 QValueList<int> dayList;
3192 getMonthlyPosDays(dayList, daysInMonth, monthBegin.dayOfWeek());
3193 for (QValueList<int>::ConstIterator id = dayList.fromLast(); id != dayList.end(); --id) {
3194 if (*id <= latestDay)
3195 return monthBegin.addDays(*id - 1);
3196 }
3197 break;
3198 }
3199 }
3200 return QDate();
3201}
3202
3203/* From the recurrence yearly month list or yearly day list, get the earliest
3204 * month or day in the specified year which is >= the earliestDate.
3205 * Note that rYearNums is sorted in numerical order.
3206 */
3207QDate Recurrence::getFirstDateInYear(const QDate &earliestDate) const
3208{
3209 QPtrListIterator<int> it(rYearNums);
3210 switch (recurs) {
3211 case rYearlyMonth: {
3212 int day = recurStart().date().day();
3213 int earliestYear = earliestDate.year();
3214 int earliestMonth = earliestDate.month();
3215 int earliestDay = earliestDate.day();
3216 if (earliestDay > day) {
3217 // The earliest date is later in the month than the recurrence date,
3218 // so skip to the next month before starting to check
3219 if (++earliestMonth > 12)
3220 return QDate();
3221 }
3222 for ( ; it.current(); ++it) {
3223 int month = *it.current();
3224 if (month >= earliestMonth) {
3225 if (day <= 28 || QDate::isValid(earliestYear, month, day))
3226 return QDate(earliestYear, month, day);
3227 if (day == 29 && month == 2) {
3228 // It's a recurrence on February 29th, in a non-leap year
3229 switch (mFeb29YearlyType) {
3230 case rMar1:
3231 return QDate(earliestYear, 3, 1);
3232 case rFeb28:
3233 if (earliestDay <= 28)
3234 return QDate(earliestYear, 2, 28);
3235 break;
3236 case rFeb29:
3237 break;
3238 }
3239 }
3240 }
3241 }
3242 break;
3243 }
3244 case rYearlyPos: {
3245 QValueList<int> dayList;
3246 int earliestYear = earliestDate.year();
3247 int earliestMonth = earliestDate.month();
3248 int earliestDay = earliestDate.day();
3249 for ( ; it.current(); ++it) {
3250 int month = *it.current();
3251 if (month >= earliestMonth) {
3252 QDate monthBegin(earliestYear, month, 1);
3253 getMonthlyPosDays(dayList, monthBegin.daysInMonth(), monthBegin.dayOfWeek());
3254 for (QValueList<int>::ConstIterator id = dayList.begin(); id != dayList.end(); ++id) {
3255 if (*id >= earliestDay)
3256 return monthBegin.addDays(*id - 1);
3257 }
3258 earliestDay = 1;
3259 }
3260 }
3261 break;
3262 }
3263 case rYearlyDay: {
3264 int earliestDay = earliestDate.dayOfYear();
3265 for ( ; it.current(); ++it) {
3266 int day = *it.current();
3267 if (day >= earliestDay && (day <= 365 || day <= earliestDate.daysInYear()))
3268 return earliestDate.addDays(day - earliestDay);
3269 }
3270 break;
3271 }
3272 }
3273 return QDate();
3274}
3275
3276/* From the recurrence yearly month list or yearly day list, get the latest
3277 * month or day in the specified year which is <= the latestDate.
3278 * Note that rYearNums is sorted in numerical order.
3279 */
3280QDate Recurrence::getLastDateInYear(const QDate &latestDate) const
3281{
3282 QPtrListIterator<int> it(rYearNums);
3283 switch (recurs) {
3284 case rYearlyMonth: {
3285 int day = recurStart().date().day();
3286 int latestYear = latestDate.year();
3287 int latestMonth = latestDate.month();
3288 if (latestDate.day() > day) {
3289 // The latest date is earlier in the month than the recurrence date,
3290 // so skip to the previous month before starting to check
3291 if (--latestMonth <= 0)
3292 return QDate();
3293 }
3294 for (it.toLast(); it.current(); --it) {
3295 int month = *it.current();
3296 if (month <= latestMonth) {
3297 if (day <= 28 || QDate::isValid(latestYear, month, day))
3298 return QDate(latestYear, month, day);
3299 if (day == 29 && month == 2) {
3300 // It's a recurrence on February 29th, in a non-leap year
3301 switch (mFeb29YearlyType) {
3302 case rMar1:
3303 if (latestMonth >= 3)
3304 return QDate(latestYear, 3, 1);
3305 break;
3306 case rFeb28:
3307 return QDate(latestYear, 2, 28);
3308 case rFeb29:
3309 break;
3310 }
3311 }
3312 }
3313 }
3314 break;
3315 }
3316 case rYearlyPos: {
3317 QValueList<int> dayList;
3318 int latestYear = latestDate.year();
3319 int latestMonth = latestDate.month();
3320 int latestDay = latestDate.day();
3321 for (it.toLast(); it.current(); --it) {
3322 int month = *it.current();
3323 if (month <= latestMonth) {
3324 QDate monthBegin(latestYear, month, 1);
3325 getMonthlyPosDays(dayList, monthBegin.daysInMonth(), monthBegin.dayOfWeek());
3326 for (QValueList<int>::ConstIterator id = dayList.fromLast(); id != dayList.end(); --id) {
3327 if (*id <= latestDay)
3328 return monthBegin.addDays(*id - 1);
3329 }
3330 latestDay = 31;
3331 }
3332 }
3333 break;
3334 }
3335 case rYearlyDay: {
3336 int latestDay = latestDate.dayOfYear();
3337 for (it.toLast(); it.current(); --it) {
3338 int day = *it.current();
3339 if (day <= latestDay)
3340 return latestDate.addDays(day - latestDay);
3341 }
3342 break;
3343 }
3344 }
3345 return QDate();
3346}
3347
3348void Recurrence::dump() const
3349{
3350 kdDebug() << "Recurrence::dump():" << endl;
3351
3352 kdDebug() << " type: " << recurs << endl;
3353
3354 kdDebug() << " rDays: " << endl;
3355 int i;
3356 for( i = 0; i < 7; ++i ) {
3357 kdDebug() << " " << i << ": "
3358 << ( rDays.testBit( i ) ? "true" : "false" ) << endl;
3359 }
3360}