1/*
2 This file is part of the kcalcore library.
3
4 Copyright (c) 2005 Reinhold Kainhofer <reinhold@kainhofe.com>
5 Copyright (c) 2006-2008 David Jarvie <djarvie@kde.org>
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., 51 Franklin Street, Fifth Floor,
20 Boston, MA 02110-1301, USA.
21*/
22#include "recurrencerule.h"
23
24#include <KDebug>
25
26#include <QtCore/QStringList>
27#include <QtCore/QTime>
28
29using namespace KCalCore;
30
31// Maximum number of intervals to process
32const int LOOP_LIMIT = 10000;
33
34static QString dumpTime(const KDateTime &dt); // for debugging
35
36/*=========================================================================
37= =
38= IMPORTANT CODING NOTE: =
39= =
40= Recurrence handling code is time critical, especially for sub-daily =
41= recurrences. For example, if getNextDate() is called repeatedly to =
42= check all consecutive occurrences over a few years, on a slow machine =
43= this could take many seconds to complete in the worst case. Simple =
44= sub-daily recurrences are optimised by use of mTimedRepetition. =
45= =
46==========================================================================*/
47
48/**************************************************************************
49 * DateHelper *
50 **************************************************************************/
51//@cond PRIVATE
52class DateHelper
53{
54public:
55#ifndef NDEBUG
56 static QString dayName(short day);
57#endif
58 static QDate getNthWeek(int year, int weeknumber, short weekstart = 1);
59 static int weekNumbersInYear(int year, short weekstart = 1);
60 static int getWeekNumber(const QDate &date, short weekstart, int *year = 0);
61 static int getWeekNumberNeg(const QDate &date, short weekstart, int *year = 0);
62 // Convert to QDate, allowing for day < 0.
63 // month and day must be non-zero.
64 static QDate getDate(int year, int month, int day)
65 {
66 if (day >= 0) {
67 return QDate(year, month, day);
68 } else {
69 if (++month > 12) {
70 month = 1;
71 ++year;
72 }
73 return QDate(year, month, 1).addDays(day);
74 }
75 }
76};
77
78#ifndef NDEBUG
79// TODO: Move to a general library / class, as we need the same in the iCal
80// generator and in the xcal format
81QString DateHelper::dayName(short day)
82{
83 switch (day) {
84 case 1:
85 return QLatin1String("MO");
86 case 2:
87 return QLatin1String("TU");
88 case 3:
89 return QLatin1String("WE");
90 case 4:
91 return QLatin1String("TH");
92 case 5:
93 return QLatin1String("FR");
94 case 6:
95 return QLatin1String("SA");
96 case 7:
97 return QLatin1String("SU");
98 default:
99 return QLatin1String("??");
100 }
101}
102#endif
103
104QDate DateHelper::getNthWeek(int year, int weeknumber, short weekstart)
105{
106 if (weeknumber == 0) {
107 return QDate();
108 }
109
110 // Adjust this to the first day of week #1 of the year and add 7*weekno days.
111 QDate dt(year, 1, 4); // Week #1 is the week that contains Jan 4
112 int adjust = -(7 + dt.dayOfWeek() - weekstart) % 7;
113 if (weeknumber > 0) {
114 dt = dt.addDays(7 * (weeknumber-1) + adjust);
115 } else if (weeknumber < 0) {
116 dt = dt.addYears(1);
117 dt = dt.addDays(7 * weeknumber + adjust);
118 }
119 return dt;
120}
121
122int DateHelper::getWeekNumber(const QDate &date, short weekstart, int *year)
123{
124 int y = date.year();
125 QDate dt(y, 1, 4); // <= definitely in week #1
126 dt = dt.addDays(-(7 + dt.dayOfWeek() - weekstart) % 7); // begin of week #1
127
128 int daysto = dt.daysTo(date);
129 if (daysto < 0) {
130 // in first week of year
131 --y;
132 dt = QDate(y, 1, 4);
133 dt = dt.addDays(-(7 + dt.dayOfWeek() - weekstart) % 7); // begin of week #1
134 daysto = dt.daysTo(date);
135 } else if (daysto > 355) {
136 // near the end of the year - check if it's next year
137 QDate dtn(y+1, 1, 4); // <= definitely first week of next year
138 dtn = dtn.addDays(-(7 + dtn.dayOfWeek() - weekstart) % 7);
139 int dayston = dtn.daysTo(date);
140 if (dayston >= 0) {
141 // in first week of next year;
142 ++y;
143 daysto = dayston;
144 }
145 }
146 if (year) {
147 *year = y;
148 }
149 return daysto / 7 + 1;
150}
151
152int DateHelper::weekNumbersInYear(int year, short weekstart)
153{
154 QDate dt(year, 1, weekstart);
155 QDate dt1(year + 1, 1, weekstart);
156 return dt.daysTo(dt1) / 7;
157}
158
159// Week number from the end of the year
160int DateHelper::getWeekNumberNeg(const QDate &date, short weekstart, int *year)
161{
162 int weekpos = getWeekNumber(date, weekstart, year);
163 return weekNumbersInYear(*year, weekstart) - weekpos - 1;
164}
165//@endcond
166
167/**************************************************************************
168 * WDayPos *
169 **************************************************************************/
170
171bool RecurrenceRule::WDayPos::operator==(const RecurrenceRule::WDayPos &pos2) const
172{
173 return mDay == pos2.mDay && mPos == pos2.mPos;
174}
175
176bool RecurrenceRule::WDayPos::operator!=(const RecurrenceRule::WDayPos &pos2) const
177{
178 return !operator==(pos2);
179}
180
181/**************************************************************************
182 * Constraint *
183 **************************************************************************/
184//@cond PRIVATE
185class Constraint
186{
187public:
188 typedef QList<Constraint> List;
189
190 Constraint() {}
191 explicit Constraint(KDateTime::Spec, int wkst = 1);
192 Constraint(const KDateTime &dt, RecurrenceRule::PeriodType type, int wkst);
193 void clear();
194 void setYear(int n)
195 {
196 year = n;
197 useCachedDt = false;
198 }
199 void setMonth(int n)
200 {
201 month = n;
202 useCachedDt = false;
203 }
204 void setDay(int n)
205 {
206 day = n;
207 useCachedDt = false;
208 }
209 void setHour(int n)
210 {
211 hour = n;
212 useCachedDt = false;
213 }
214 void setMinute(int n)
215 {
216 minute = n;
217 useCachedDt = false;
218 }
219 void setSecond(int n)
220 {
221 second = n;
222 useCachedDt = false;
223 }
224 void setWeekday(int n)
225 {
226 weekday = n;
227 useCachedDt = false;
228 }
229 void setWeekdaynr(int n)
230 {
231 weekdaynr = n;
232 useCachedDt = false;
233 }
234 void setWeeknumber(int n)
235 {
236 weeknumber = n;
237 useCachedDt = false;
238 }
239 void setYearday(int n)
240 {
241 yearday = n;
242 useCachedDt = false;
243 }
244 void setWeekstart(int n)
245 {
246 weekstart = n;
247 useCachedDt = false;
248 }
249 void setSecondOccurrence(int n)
250 {
251 secondOccurrence = n;
252 useCachedDt = false;
253 }
254
255 int year; // 0 means unspecified
256 int month; // 0 means unspecified
257 int day; // 0 means unspecified
258 int hour; // -1 means unspecified
259 int minute; // -1 means unspecified
260 int second; // -1 means unspecified
261 int weekday; // 0 means unspecified
262 int weekdaynr; // index of weekday in month/year (0=unspecified)
263 int weeknumber; // 0 means unspecified
264 int yearday; // 0 means unspecified
265 int weekstart; // first day of week (1=monday, 7=sunday, 0=unspec.)
266 KDateTime::Spec timespec; // time zone etc. to use
267 bool secondOccurrence; // the time is the second occurrence during daylight savings shift
268
269 bool readDateTime(const KDateTime &dt, RecurrenceRule::PeriodType type);
270 bool matches(const QDate &dt, RecurrenceRule::PeriodType type) const;
271 bool matches(const KDateTime &dt, RecurrenceRule::PeriodType type) const;
272 bool merge(const Constraint &interval);
273 bool isConsistent() const;
274 bool isConsistent(RecurrenceRule::PeriodType period) const;
275 bool increase(RecurrenceRule::PeriodType type, int freq);
276 KDateTime intervalDateTime(RecurrenceRule::PeriodType type) const;
277 QList<KDateTime> dateTimes(RecurrenceRule::PeriodType type) const;
278 void appendDateTime(const QDate &date, const QTime &time, QList<KDateTime> &list) const;
279 void dump() const;
280
281private:
282 mutable bool useCachedDt;
283 mutable KDateTime cachedDt;
284};
285
286Constraint::Constraint(KDateTime::Spec spec, int wkst)
287 : weekstart(wkst),
288 timespec(spec)
289{
290 clear();
291}
292
293Constraint::Constraint(const KDateTime &dt, RecurrenceRule::PeriodType type, int wkst)
294 : weekstart(wkst),
295 timespec(dt.timeSpec())
296{
297 clear();
298 readDateTime(dt, type);
299}
300
301void Constraint::clear()
302{
303 year = 0;
304 month = 0;
305 day = 0;
306 hour = -1;
307 minute = -1;
308 second = -1;
309 weekday = 0;
310 weekdaynr = 0;
311 weeknumber = 0;
312 yearday = 0;
313 secondOccurrence = false;
314 useCachedDt = false;
315}
316
317bool Constraint::matches(const QDate &dt, RecurrenceRule::PeriodType type) const
318{
319 // If the event recurs in week 53 or 1, the day might not belong to the same
320 // year as the week it is in. E.g. Jan 1, 2005 is in week 53 of year 2004.
321 // So we can't simply check the year in that case!
322 if (weeknumber == 0) {
323 if (year > 0 && year != dt.year()) {
324 return false;
325 }
326 } else {
327 int y;
328 if (weeknumber > 0 &&
329 weeknumber != DateHelper::getWeekNumber(dt, weekstart, &y)) {
330 return false;
331 }
332 if (weeknumber < 0 &&
333 weeknumber != DateHelper::getWeekNumberNeg(dt, weekstart, &y)) {
334 return false;
335 }
336 if (year > 0 && year != y) {
337 return false;
338 }
339 }
340
341 if (month > 0 && month != dt.month()) {
342 return false;
343 }
344 if (day > 0 && day != dt.day()) {
345 return false;
346 }
347 if (day < 0 && dt.day() != (dt.daysInMonth() + day + 1)) {
348 return false;
349 }
350 if (weekday > 0) {
351 if (weekday != dt.dayOfWeek()) {
352 return false;
353 }
354 if (weekdaynr != 0) {
355 // If it's a yearly recurrence and a month is given, the position is
356 // still in the month, not in the year.
357 if ((type == RecurrenceRule::rMonthly) ||
358 (type == RecurrenceRule::rYearly && month > 0)) {
359 // Monthly
360 if (weekdaynr > 0 &&
361 weekdaynr != (dt.day() - 1) / 7 + 1) {
362 return false;
363 }
364 if (weekdaynr < 0 &&
365 weekdaynr != -((dt.daysInMonth() - dt.day()) / 7 + 1)) {
366 return false;
367 }
368 } else {
369 // Yearly
370 if (weekdaynr > 0 &&
371 weekdaynr != (dt.dayOfYear() - 1) / 7 + 1) {
372 return false;
373 }
374 if (weekdaynr < 0 &&
375 weekdaynr != -((dt.daysInYear() - dt.dayOfYear()) / 7 + 1)) {
376 return false;
377 }
378 }
379 }
380 }
381 if (yearday > 0 && yearday != dt.dayOfYear()) {
382 return false;
383 }
384 if (yearday < 0 && yearday != dt.daysInYear() - dt.dayOfYear() + 1) {
385 return false;
386 }
387 return true;
388}
389
390/* Check for a match with the specified date/time.
391 * The date/time's time specification must correspond with that of the start date/time.
392 */
393bool Constraint::matches(const KDateTime &dt, RecurrenceRule::PeriodType type) const
394{
395 if ((hour >= 0 && (hour != dt.time().hour() ||
396 secondOccurrence != dt.isSecondOccurrence())) ||
397 (minute >= 0 && minute != dt.time().minute()) ||
398 (second >= 0 && second != dt.time().second()) ||
399 !matches(dt.date(), type)) {
400 return false;
401 }
402 return true;
403}
404
405bool Constraint::isConsistent(RecurrenceRule::PeriodType /*period*/) const
406{
407 // TODO: Check for consistency, e.g. byyearday=3 and bymonth=10
408 return true;
409}
410
411// Return a date/time set to the constraint values, but with those parts less
412// significant than the given period type set to 1 (for dates) or 0 (for times).
413KDateTime Constraint::intervalDateTime(RecurrenceRule::PeriodType type) const
414{
415 if (useCachedDt) {
416 return cachedDt;
417 }
418 QDate d;
419 QTime t(0, 0, 0);
420 bool subdaily = true;
421 switch (type) {
422 case RecurrenceRule::rSecondly:
423 t.setHMS(hour, minute, second);
424 break;
425 case RecurrenceRule::rMinutely:
426 t.setHMS(hour, minute, 0);
427 break;
428 case RecurrenceRule::rHourly:
429 t.setHMS(hour, 0, 0);
430 break;
431 case RecurrenceRule::rDaily:
432 break;
433 case RecurrenceRule::rWeekly:
434 d = DateHelper::getNthWeek(year, weeknumber, weekstart);
435 subdaily = false;
436 break;
437 case RecurrenceRule::rMonthly:
438 d.setYMD(year, month, 1);
439 subdaily = false;
440 break;
441 case RecurrenceRule::rYearly:
442 d.setYMD(year, 1, 1);
443 subdaily = false;
444 break;
445 default:
446 break;
447 }
448 if (subdaily) {
449 d = DateHelper::getDate(year, (month>0)?month:1, day?day:1);
450 }
451 cachedDt = KDateTime(d, t, timespec);
452 if (secondOccurrence) {
453 cachedDt.setSecondOccurrence(true);
454 }
455 useCachedDt = true;
456 return cachedDt;
457}
458
459bool Constraint::merge(const Constraint &interval)
460{
461#define mergeConstraint( name, cmparison ) \
462 if ( interval.name cmparison ) { \
463 if ( !( name cmparison ) ) { \
464 name = interval.name; \
465 } else if ( name != interval.name ) { \
466 return false;\
467 } \
468 }
469
470 useCachedDt = false;
471
472 mergeConstraint(year, > 0);
473 mergeConstraint(month, > 0);
474 mergeConstraint(day, != 0);
475 mergeConstraint(hour, >= 0);
476 mergeConstraint(minute, >= 0);
477 mergeConstraint(second, >= 0);
478
479 mergeConstraint(weekday, != 0);
480 mergeConstraint(weekdaynr, != 0);
481 mergeConstraint(weeknumber, != 0);
482 mergeConstraint(yearday, != 0);
483
484#undef mergeConstraint
485 return true;
486}
487
488// Y M D | H Mn S | WD #WD | WN | YD
489// required:
490// x | x x x | | |
491// 0) Trivial: Exact date given, maybe other restrictions
492// x x x | x x x | | |
493// 1) Easy case: no weekly restrictions -> at most a loop through possible dates
494// x + + | x x x | - - | - | -
495// 2) Year day is given -> date known
496// x | x x x | | | +
497// 3) week number is given -> loop through all days of that week. Further
498// restrictions will be applied in the end, when we check all dates for
499// consistency with the constraints
500// x | x x x | | + | (-)
501// 4) week day is specified ->
502// x | x x x | x ? | (-)| (-)
503// 5) All possiblecases have already been treated, so this must be an error!
504
505QList<KDateTime> Constraint::dateTimes(RecurrenceRule::PeriodType type) const
506{
507 QList<KDateTime> result;
508 bool done = false;
509 if (!isConsistent(type)) {
510 return result;
511 }
512
513 // TODO_Recurrence: Handle all-day
514 QTime tm(hour, minute, second);
515
516 if (!done && day && month > 0) {
517 appendDateTime(DateHelper::getDate(year, month, day), tm, result);
518 done = true;
519 }
520
521 if (!done && weekday == 0 && weeknumber == 0 && yearday == 0) {
522 // Easy case: date is given, not restrictions by week or yearday
523 uint mstart = (month > 0) ? month : 1;
524 uint mend = (month <= 0) ? 12 : month;
525 for (uint m = mstart; m <= mend; ++m) {
526 uint dstart, dend;
527 if (day > 0) {
528 dstart = dend = day;
529 } else if (day < 0) {
530 QDate date(year, month, 1);
531 dstart = dend = date.daysInMonth() + day + 1;
532 } else {
533 QDate date(year, month, 1);
534 dstart = 1;
535 dend = date.daysInMonth();
536 }
537 uint d = dstart;
538 for (QDate dt(year, m, dstart); ; dt = dt.addDays(1)) {
539 appendDateTime(dt, tm, result);
540 if (++d > dend) {
541 break;
542 }
543 }
544 }
545 done = true;
546 }
547
548 // Else: At least one of the week / yearday restrictions was given...
549 // If we have a yearday (and of course a year), we know the exact date
550 if (!done && yearday != 0) {
551 // yearday < 0 means from end of year, so we'll need Jan 1 of the next year
552 QDate d(year + ((yearday > 0) ? 0 : 1), 1, 1);
553 d = d.addDays(yearday - ((yearday > 0) ? 1 : 0));
554 appendDateTime(d, tm, result);
555 done = true;
556 }
557
558 // Else: If we have a weeknumber, we have at most 7 possible dates, loop through them
559 if (!done && weeknumber != 0) {
560 QDate wst(DateHelper::getNthWeek(year, weeknumber, weekstart));
561 if (weekday != 0) {
562 wst = wst.addDays((7 + weekday - weekstart) % 7);
563 appendDateTime(wst, tm, result);
564 } else {
565 for (int i = 0; i < 7; ++i) {
566 appendDateTime(wst, tm, result);
567 wst = wst.addDays(1);
568 }
569 }
570 done = true;
571 }
572
573 // weekday is given
574 if (!done && weekday != 0) {
575 QDate dt(year, 1, 1);
576 // If type == yearly and month is given, pos is still in month not year!
577 // TODO_Recurrence: Correct handling of n-th BYDAY...
578 int maxloop = 53;
579 bool inMonth = (type == RecurrenceRule::rMonthly) ||
580 (type == RecurrenceRule::rYearly && month > 0);
581 if (inMonth && month > 0) {
582 dt = QDate(year, month, 1);
583 maxloop = 5;
584 }
585 if (weekdaynr < 0) {
586 // From end of period (month, year) => relative to begin of next period
587 if (inMonth) {
588 dt = dt.addMonths(1);
589 } else {
590 dt = dt.addYears(1);
591 }
592 }
593 int adj = (7 + weekday - dt.dayOfWeek()) % 7;
594 dt = dt.addDays(adj); // correct first weekday of the period
595
596 if (weekdaynr > 0) {
597 dt = dt.addDays((weekdaynr - 1) * 7);
598 appendDateTime(dt, tm, result);
599 } else if (weekdaynr < 0) {
600 dt = dt.addDays(weekdaynr * 7);
601 appendDateTime(dt, tm, result);
602 } else {
603 // loop through all possible weeks, non-matching will be filtered later
604 for (int i = 0; i < maxloop; ++i) {
605 appendDateTime(dt, tm, result);
606 dt = dt.addDays(7);
607 }
608 }
609 } // weekday != 0
610
611 // Only use those times that really match all other constraints, too
612 QList<KDateTime> valid;
613 for (int i = 0, iend = result.count(); i < iend; ++i) {
614 if (matches(result[i], type)) {
615 valid.append(result[i]);
616 }
617 }
618 // Don't sort it here, would be unnecessary work. The results from all
619 // constraints will be merged to one big list of the interval. Sort that one!
620 return valid;
621}
622
623void Constraint::appendDateTime(const QDate &date, const QTime &time,
624 QList<KDateTime> &list) const
625{
626 KDateTime dt(date, time, timespec);
627 if (dt.isValid()) {
628 if (secondOccurrence) {
629 dt.setSecondOccurrence(true);
630 }
631 list.append(dt);
632 }
633}
634
635bool Constraint::increase(RecurrenceRule::PeriodType type, int freq)
636{
637 // convert the first day of the interval to KDateTime
638 intervalDateTime(type);
639
640 // Now add the intervals
641 switch (type) {
642 case RecurrenceRule::rSecondly:
643 cachedDt = cachedDt.addSecs(freq);
644 break;
645 case RecurrenceRule::rMinutely:
646 cachedDt = cachedDt.addSecs(60 * freq);
647 break;
648 case RecurrenceRule::rHourly:
649 cachedDt = cachedDt.addSecs(3600 * freq);
650 break;
651 case RecurrenceRule::rDaily:
652 cachedDt = cachedDt.addDays(freq);
653 break;
654 case RecurrenceRule::rWeekly:
655 cachedDt = cachedDt.addDays(7 * freq);
656 break;
657 case RecurrenceRule::rMonthly:
658 cachedDt = cachedDt.addMonths(freq);
659 break;
660 case RecurrenceRule::rYearly:
661 cachedDt = cachedDt.addYears(freq);
662 break;
663 default:
664 break;
665 }
666 // Convert back from KDateTime to the Constraint class
667 readDateTime(cachedDt, type);
668 useCachedDt = true; // readDateTime() resets this
669
670 return true;
671}
672
673// Set the constraint's value appropriate to 'type', to the value contained in a date/time.
674bool Constraint::readDateTime(const KDateTime &dt, RecurrenceRule::PeriodType type)
675{
676 switch (type) {
677 // Really fall through! Only weekly needs to be treated differently!
678 case RecurrenceRule::rSecondly:
679 second = dt.time().second();
680 case RecurrenceRule::rMinutely:
681 minute = dt.time().minute();
682 case RecurrenceRule::rHourly:
683 hour = dt.time().hour();
684 secondOccurrence = dt.isSecondOccurrence();
685 case RecurrenceRule::rDaily:
686 day = dt.date().day();
687 case RecurrenceRule::rMonthly:
688 month = dt.date().month();
689 case RecurrenceRule::rYearly:
690 year = dt.date().year();
691 break;
692 case RecurrenceRule::rWeekly:
693 // Determine start day of the current week, calculate the week number from that
694 weeknumber = DateHelper::getWeekNumber(dt.date(), weekstart, &year);
695 break;
696 default:
697 break;
698 }
699 useCachedDt = false;
700 return true;
701}
702//@endcond
703
704/**************************************************************************
705 * RecurrenceRule::Private *
706 **************************************************************************/
707
708//@cond PRIVATE
709class KCalCore::RecurrenceRule::Private
710{
711public:
712 Private(RecurrenceRule *parent)
713 : mParent(parent),
714 mPeriod(rNone),
715 mFrequency(0),
716 mDuration(-1),
717 mWeekStart(1),
718 mIsReadOnly(false),
719 mAllDay(false)
720 {
721 setDirty();
722 }
723
724 Private(RecurrenceRule *parent, const Private &p);
725
726 Private &operator=(const Private &other);
727 bool operator==(const Private &other) const;
728 void clear();
729 void setDirty();
730 void buildConstraints();
731 bool buildCache() const;
732 Constraint getNextValidDateInterval(const KDateTime &preDate, PeriodType type) const;
733 Constraint getPreviousValidDateInterval(const KDateTime &afterDate, PeriodType type) const;
734 DateTimeList datesForInterval(const Constraint &interval, PeriodType type) const;
735
736 RecurrenceRule *mParent;
737 QString mRRule; // RRULE string
738 PeriodType mPeriod;
739 KDateTime mDateStart; // start of recurrence (but mDateStart is not an occurrence
740 // unless it matches the rule)
741 uint mFrequency;
742 /** how often it recurs:
743 < 0 means no end date,
744 0 means an explicit end date,
745 positive values give the number of occurrences */
746 int mDuration;
747 KDateTime mDateEnd;
748
749 QList<int> mBySeconds; // values: second 0-59
750 QList<int> mByMinutes; // values: minute 0-59
751 QList<int> mByHours; // values: hour 0-23
752
753 QList<WDayPos> mByDays; // n-th weekday of the month or year
754 QList<int> mByMonthDays; // values: day -31 to -1 and 1-31
755 QList<int> mByYearDays; // values: day -366 to -1 and 1-366
756 QList<int> mByWeekNumbers; // values: week -53 to -1 and 1-53
757 QList<int> mByMonths; // values: month 1-12
758 QList<int> mBySetPos; // values: position -366 to -1 and 1-366
759 short mWeekStart; // first day of the week (1=Monday, 7=Sunday)
760
761 Constraint::List mConstraints;
762 QList<RuleObserver*> mObservers;
763
764 // Cache for duration
765 mutable DateTimeList mCachedDates;
766 mutable KDateTime mCachedDateEnd;
767 mutable KDateTime mCachedLastDate; // when mCachedDateEnd invalid, last date checked
768 mutable bool mCached;
769
770 bool mIsReadOnly;
771 bool mAllDay;
772 bool mNoByRules; // no BySeconds, ByMinutes, ... rules exist
773 uint mTimedRepetition; // repeats at a regular number of seconds interval, or 0
774};
775
776RecurrenceRule::Private::Private(RecurrenceRule *parent, const Private &p)
777 : mParent(parent),
778 mRRule(p.mRRule),
779 mPeriod(p.mPeriod),
780 mDateStart(p.mDateStart),
781 mFrequency(p.mFrequency),
782 mDuration(p.mDuration),
783 mDateEnd(p.mDateEnd),
784
785 mBySeconds(p.mBySeconds),
786 mByMinutes(p.mByMinutes),
787 mByHours(p.mByHours),
788 mByDays(p.mByDays),
789 mByMonthDays(p.mByMonthDays),
790 mByYearDays(p.mByYearDays),
791 mByWeekNumbers(p.mByWeekNumbers),
792 mByMonths(p.mByMonths),
793 mBySetPos(p.mBySetPos),
794 mWeekStart(p.mWeekStart),
795
796 mIsReadOnly(p.mIsReadOnly),
797 mAllDay(p.mAllDay),
798 mNoByRules(p.mNoByRules)
799{
800 setDirty();
801}
802
803RecurrenceRule::Private &RecurrenceRule::Private::operator=(const Private &p)
804{
805 // check for self assignment
806 if (&p == this) {
807 return *this;
808 }
809
810 mRRule = p.mRRule;
811 mPeriod = p.mPeriod;
812 mDateStart = p.mDateStart;
813 mFrequency = p.mFrequency;
814 mDuration = p.mDuration;
815 mDateEnd = p.mDateEnd;
816
817 mBySeconds = p.mBySeconds;
818 mByMinutes = p.mByMinutes;
819 mByHours = p.mByHours;
820 mByDays = p.mByDays;
821 mByMonthDays = p.mByMonthDays;
822 mByYearDays = p.mByYearDays;
823 mByWeekNumbers = p.mByWeekNumbers;
824 mByMonths = p.mByMonths;
825 mBySetPos = p.mBySetPos;
826 mWeekStart = p.mWeekStart;
827
828 mIsReadOnly = p.mIsReadOnly;
829 mAllDay = p.mAllDay;
830 mNoByRules = p.mNoByRules;
831
832 setDirty();
833
834 return *this;
835}
836
837bool RecurrenceRule::Private::operator==(const Private &r) const
838{
839 return
840 mPeriod == r.mPeriod &&
841 ((mDateStart == r.mDateStart) ||
842 (!mDateStart.isValid() && !r.mDateStart.isValid())) &&
843 mDuration == r.mDuration &&
844 ((mDateEnd == r.mDateEnd) ||
845 (!mDateEnd.isValid() && !r.mDateEnd.isValid())) &&
846 mFrequency == r.mFrequency &&
847 mIsReadOnly == r.mIsReadOnly &&
848 mAllDay == r.mAllDay &&
849 mBySeconds == r.mBySeconds &&
850 mByMinutes == r.mByMinutes &&
851 mByHours == r.mByHours &&
852 mByDays == r.mByDays &&
853 mByMonthDays == r.mByMonthDays &&
854 mByYearDays == r.mByYearDays &&
855 mByWeekNumbers == r.mByWeekNumbers &&
856 mByMonths == r.mByMonths &&
857 mBySetPos == r.mBySetPos &&
858 mWeekStart == r.mWeekStart &&
859 mNoByRules == r.mNoByRules;
860}
861
862void RecurrenceRule::Private::clear()
863{
864 if (mIsReadOnly) {
865 return;
866 }
867 mPeriod = rNone;
868 mBySeconds.clear();
869 mByMinutes.clear();
870 mByHours.clear();
871 mByDays.clear();
872 mByMonthDays.clear();
873 mByYearDays.clear();
874 mByWeekNumbers.clear();
875 mByMonths.clear();
876 mBySetPos.clear();
877 mWeekStart = 1;
878 mNoByRules = false;
879
880 setDirty();
881}
882
883void RecurrenceRule::Private::setDirty()
884{
885 buildConstraints();
886 mCached = false;
887 mCachedDates.clear();
888 for (int i = 0, iend = mObservers.count(); i < iend; ++i) {
889 if (mObservers[i]) {
890 mObservers[i]->recurrenceChanged(mParent);
891 }
892 }
893}
894//@endcond
895
896/**************************************************************************
897 * RecurrenceRule *
898 **************************************************************************/
899
900RecurrenceRule::RecurrenceRule()
901 : d(new Private(this))
902{
903}
904
905RecurrenceRule::RecurrenceRule(const RecurrenceRule &r)
906 : d(new Private(this, *r.d))
907{
908}
909
910RecurrenceRule::~RecurrenceRule()
911{
912 delete d;
913}
914
915bool RecurrenceRule::operator==(const RecurrenceRule &r) const
916{
917 return *d == *r.d;
918}
919
920RecurrenceRule &RecurrenceRule::operator=(const RecurrenceRule &r)
921{
922 // check for self assignment
923 if (&r == this) {
924 return *this;
925 }
926
927 *d = *r.d;
928
929 return *this;
930}
931
932void RecurrenceRule::addObserver(RuleObserver *observer)
933{
934 if (!d->mObservers.contains(observer)) {
935 d->mObservers.append(observer);
936 }
937}
938
939void RecurrenceRule::removeObserver(RuleObserver *observer)
940{
941 if (d->mObservers.contains(observer)) {
942 d->mObservers.removeAll(observer);
943 }
944}
945
946void RecurrenceRule::setRecurrenceType(PeriodType period)
947{
948 if (isReadOnly()) {
949 return;
950 }
951 d->mPeriod = period;
952 d->setDirty();
953}
954
955KDateTime RecurrenceRule::endDt(bool *result) const
956{
957 if (result) {
958 *result = false;
959 }
960 if (d->mPeriod == rNone) {
961 return KDateTime();
962 }
963 if (d->mDuration < 0) {
964 return KDateTime();
965 }
966 if (d->mDuration == 0) {
967 if (result) {
968 *result = true;
969 }
970 return d->mDateEnd;
971 }
972
973 // N occurrences. Check if we have a full cache. If so, return the cached end date.
974 if (!d->mCached) {
975 // If not enough occurrences can be found (i.e. inconsistent constraints)
976 if (!d->buildCache()) {
977 return KDateTime();
978 }
979 }
980 if (result) {
981 *result = true;
982 }
983 return d->mCachedDateEnd;
984}
985
986void RecurrenceRule::setEndDt(const KDateTime &dateTime)
987{
988 if (isReadOnly()) {
989 return;
990 }
991 d->mDateEnd = dateTime;
992 d->mDuration = 0; // set to 0 because there is an end date/time
993 d->setDirty();
994}
995
996void RecurrenceRule::setDuration(int duration)
997{
998 if (isReadOnly()) {
999 return;
1000 }
1001 d->mDuration = duration;
1002 d->setDirty();
1003}
1004
1005void RecurrenceRule::setAllDay(bool allDay)
1006{
1007 if (isReadOnly()) {
1008 return;
1009 }
1010 d->mAllDay = allDay;
1011 d->setDirty();
1012}
1013
1014void RecurrenceRule::clear()
1015{
1016 d->clear();
1017}
1018
1019void RecurrenceRule::setDirty()
1020{
1021 d->setDirty();
1022}
1023
1024void RecurrenceRule::setStartDt(const KDateTime &start)
1025{
1026 if (isReadOnly()) {
1027 return;
1028 }
1029 d->mDateStart = start;
1030 d->setDirty();
1031}
1032
1033void RecurrenceRule::setFrequency(int freq)
1034{
1035 if (isReadOnly() || freq <= 0) {
1036 return;
1037 }
1038 d->mFrequency = freq;
1039 d->setDirty();
1040}
1041
1042void RecurrenceRule::setBySeconds(const QList<int> &bySeconds)
1043{
1044 if (isReadOnly()) {
1045 return;
1046 }
1047 d->mBySeconds = bySeconds;
1048 d->setDirty();
1049}
1050
1051void RecurrenceRule::setByMinutes(const QList<int> &byMinutes)
1052{
1053 if (isReadOnly()) {
1054 return;
1055 }
1056 d->mByMinutes = byMinutes;
1057 d->setDirty();
1058}
1059
1060void RecurrenceRule::setByHours(const QList<int> &byHours)
1061{
1062 if (isReadOnly()) {
1063 return;
1064 }
1065 d->mByHours = byHours;
1066 d->setDirty();
1067}
1068
1069void RecurrenceRule::setByDays(const QList<WDayPos> &byDays)
1070{
1071 if (isReadOnly()) {
1072 return;
1073 }
1074 d->mByDays = byDays;
1075 d->setDirty();
1076}
1077
1078void RecurrenceRule::setByMonthDays(const QList<int> &byMonthDays)
1079{
1080 if (isReadOnly()) {
1081 return;
1082 }
1083 d->mByMonthDays = byMonthDays;
1084 d->setDirty();
1085}
1086
1087void RecurrenceRule::setByYearDays(const QList<int> &byYearDays)
1088{
1089 if (isReadOnly()) {
1090 return;
1091 }
1092 d->mByYearDays = byYearDays;
1093 d->setDirty();
1094}
1095
1096void RecurrenceRule::setByWeekNumbers(const QList<int> &byWeekNumbers)
1097{
1098 if (isReadOnly()) {
1099 return;
1100 }
1101 d->mByWeekNumbers = byWeekNumbers;
1102 d->setDirty();
1103}
1104
1105void RecurrenceRule::setByMonths(const QList<int> &byMonths)
1106{
1107 if (isReadOnly()) {
1108 return;
1109 }
1110 d->mByMonths = byMonths;
1111 d->setDirty();
1112}
1113
1114void RecurrenceRule::setBySetPos(const QList<int> &bySetPos)
1115{
1116 if (isReadOnly()) {
1117 return;
1118 }
1119 d->mBySetPos = bySetPos;
1120 d->setDirty();
1121}
1122
1123void RecurrenceRule::setWeekStart(short weekStart)
1124{
1125 if (isReadOnly()) {
1126 return;
1127 }
1128 d->mWeekStart = weekStart;
1129 d->setDirty();
1130}
1131
1132void RecurrenceRule::shiftTimes(const KDateTime::Spec &oldSpec, const KDateTime::Spec &newSpec)
1133{
1134 d->mDateStart = d->mDateStart.toTimeSpec(oldSpec);
1135 d->mDateStart.setTimeSpec(newSpec);
1136 if (d->mDuration == 0) {
1137 d->mDateEnd = d->mDateEnd.toTimeSpec(oldSpec);
1138 d->mDateEnd.setTimeSpec(newSpec);
1139 }
1140 d->setDirty();
1141}
1142
1143// Taken from recurrence.cpp
1144// int RecurrenceRule::maxIterations() const
1145// {
1146// /* Find the maximum number of iterations which may be needed to reach the
1147// * next actual occurrence of a monthly or yearly recurrence.
1148// * More than one iteration may be needed if, for example, it's the 29th February,
1149// * the 31st day of the month or the 5th Monday, and the month being checked is
1150// * February or a 30-day month.
1151// * The following recurrences may never occur:
1152// * - For rMonthlyDay: if the frequency is a whole number of years.
1153// * - For rMonthlyPos: if the frequency is an even whole number of years.
1154// * - For rYearlyDay, rYearlyMonth: if the frequeny is a multiple of 4 years.
1155// * - For rYearlyPos: if the frequency is an even number of years.
1156// * The maximum number of iterations needed, assuming that it does actually occur,
1157// * was found empirically.
1158// */
1159// switch (recurs) {
1160// case rMonthlyDay:
1161// return (rFreq % 12) ? 6 : 8;
1162//
1163// case rMonthlyPos:
1164// if (rFreq % 12 == 0) {
1165// // Some of these frequencies may never occur
1166// return (rFreq % 84 == 0) ? 364 // frequency = multiple of 7 years
1167// : (rFreq % 48 == 0) ? 7 // frequency = multiple of 4 years
1168// : (rFreq % 24 == 0) ? 14 : 28; // frequency = multiple of 2 or 1 year
1169// }
1170// // All other frequencies will occur sometime
1171// if (rFreq > 120)
1172// return 364; // frequencies of > 10 years will hit the date limit first
1173// switch (rFreq) {
1174// case 23: return 50;
1175// case 46: return 38;
1176// case 56: return 138;
1177// case 66: return 36;
1178// case 89: return 54;
1179// case 112: return 253;
1180// default: return 25; // most frequencies will need < 25 iterations
1181// }
1182//
1183// case rYearlyMonth:
1184// case rYearlyDay:
1185// return 8; // only 29th Feb or day 366 will need more than one iteration
1186//
1187// case rYearlyPos:
1188// if (rFreq % 7 == 0)
1189// return 364; // frequencies of a multiple of 7 years will hit the date limit first
1190// if (rFreq % 2 == 0) {
1191// // Some of these frequencies may never occur
1192// return (rFreq % 4 == 0) ? 7 : 14; // frequency = even number of years
1193// }
1194// return 28;
1195// }
1196// return 1;
1197// }
1198
1199//@cond PRIVATE
1200void RecurrenceRule::Private::buildConstraints()
1201{
1202 mTimedRepetition = 0;
1203 mNoByRules = mBySetPos.isEmpty();
1204 mConstraints.clear();
1205 Constraint con(mDateStart.timeSpec());
1206 if (mWeekStart > 0) {
1207 con.setWeekstart(mWeekStart);
1208 }
1209 mConstraints.append(con);
1210
1211 int c, cend;
1212 int i, iend;
1213 Constraint::List tmp;
1214
1215#define intConstraint( list, setElement ) \
1216 if ( !list.isEmpty() ) { \
1217 mNoByRules = false; \
1218 iend = list.count(); \
1219 if ( iend == 1 ) { \
1220 for ( c = 0, cend = mConstraints.count(); c < cend; ++c ) { \
1221 mConstraints[c].setElement( list[0] ); \
1222 } \
1223 } else { \
1224 for ( c = 0, cend = mConstraints.count(); c < cend; ++c ) { \
1225 for ( i = 0; i < iend; ++i ) { \
1226 con = mConstraints[c]; \
1227 con.setElement( list[i] ); \
1228 tmp.append( con ); \
1229 } \
1230 } \
1231 mConstraints = tmp; \
1232 tmp.clear(); \
1233 } \
1234 }
1235
1236 intConstraint(mBySeconds, setSecond);
1237 intConstraint(mByMinutes, setMinute);
1238 intConstraint(mByHours, setHour);
1239 intConstraint(mByMonthDays, setDay);
1240 intConstraint(mByMonths, setMonth);
1241 intConstraint(mByYearDays, setYearday);
1242 intConstraint(mByWeekNumbers, setWeeknumber);
1243#undef intConstraint
1244
1245 if (!mByDays.isEmpty()) {
1246 mNoByRules = false;
1247 for (c = 0, cend = mConstraints.count(); c < cend; ++c) {
1248 for (i = 0, iend = mByDays.count(); i < iend; ++i) {
1249 con = mConstraints[c];
1250 con.setWeekday(mByDays[i].day());
1251 con.setWeekdaynr(mByDays[i].pos());
1252 tmp.append(con);
1253 }
1254 }
1255 mConstraints = tmp;
1256 tmp.clear();
1257 }
1258
1259#define fixConstraint( setElement, value ) \
1260 { \
1261 for ( c = 0, cend = mConstraints.count(); c < cend; ++c ) { \
1262 mConstraints[c].setElement( value ); \
1263 } \
1264 }
1265 // Now determine missing values from DTSTART. This can speed up things,
1266 // because we have more restrictions and save some loops.
1267
1268 // TODO: Does RFC 2445 intend to restrict the weekday in all cases of weekly?
1269 if (mPeriod == rWeekly && mByDays.isEmpty()) {
1270 fixConstraint(setWeekday, mDateStart.date().dayOfWeek());
1271 }
1272
1273 // Really fall through in the cases, because all smaller time intervals are
1274 // constrained from dtstart
1275 switch (mPeriod) {
1276 case rYearly:
1277 if (mByDays.isEmpty() && mByWeekNumbers.isEmpty() &&
1278 mByYearDays.isEmpty() && mByMonths.isEmpty()) {
1279 fixConstraint(setMonth, mDateStart.date().month());
1280 }
1281 case rMonthly:
1282 if (mByDays.isEmpty() && mByWeekNumbers.isEmpty() &&
1283 mByYearDays.isEmpty() && mByMonthDays.isEmpty()) {
1284 fixConstraint(setDay, mDateStart.date().day());
1285 }
1286 case rWeekly:
1287 case rDaily:
1288 if (mByHours.isEmpty()) {
1289 fixConstraint(setHour, mDateStart.time().hour());
1290 }
1291 case rHourly:
1292 if (mByMinutes.isEmpty()) {
1293 fixConstraint(setMinute, mDateStart.time().minute());
1294 }
1295 case rMinutely:
1296 if (mBySeconds.isEmpty()) {
1297 fixConstraint(setSecond, mDateStart.time().second());
1298 }
1299 case rSecondly:
1300 default:
1301 break;
1302 }
1303#undef fixConstraint
1304
1305 if (mNoByRules) {
1306 switch (mPeriod) {
1307 case rHourly:
1308 mTimedRepetition = mFrequency * 3600;
1309 break;
1310 case rMinutely:
1311 mTimedRepetition = mFrequency * 60;
1312 break;
1313 case rSecondly:
1314 mTimedRepetition = mFrequency;
1315 break;
1316 default:
1317 break;
1318 }
1319 } else {
1320 for (c = 0, cend = mConstraints.count(); c < cend;) {
1321 if (mConstraints[c].isConsistent(mPeriod)) {
1322 ++c;
1323 } else {
1324 mConstraints.removeAt(c);
1325 --cend;
1326 }
1327 }
1328 }
1329}
1330
1331// Build and cache a list of all occurrences.
1332// Only call buildCache() if mDuration > 0.
1333bool RecurrenceRule::Private::buildCache() const
1334{
1335 Q_ASSERT(mDuration > 0);
1336 // Build the list of all occurrences of this event (we need that to determine
1337 // the end date!)
1338 Constraint interval(getNextValidDateInterval(mDateStart, mPeriod));
1339 QDateTime next;
1340
1341 DateTimeList dts = datesForInterval(interval, mPeriod);
1342 // Only use dates after the event has started (start date is only included
1343 // if it matches)
1344 int i = dts.findLT(mDateStart);
1345 if (i >= 0) {
1346 dts.erase(dts.begin(), dts.begin() + i + 1);
1347 }
1348
1349 // some validity checks to avoid infinite loops (i.e. if we have
1350 // done this loop already 10000 times, bail out )
1351 for (int loopnr = 0; loopnr < LOOP_LIMIT && dts.count() < mDuration; ++loopnr) {
1352 interval.increase(mPeriod, mFrequency);
1353 // The returned date list is already sorted!
1354 dts += datesForInterval(interval, mPeriod);
1355 }
1356 if (dts.count() > mDuration) {
1357 // we have picked up more occurrences than necessary, remove them
1358 dts.erase(dts.begin() + mDuration, dts.end());
1359 }
1360 mCached = true;
1361 mCachedDates = dts;
1362
1363// it = dts.begin();
1364// while ( it != dts.end() ) {
1365// kDebug() << " -=>" << dumpTime(*it);
1366// ++it;
1367// }
1368 if (int(dts.count()) == mDuration) {
1369 mCachedDateEnd = dts.last();
1370 return true;
1371 } else {
1372 // The cached date list is incomplete
1373 mCachedDateEnd = KDateTime();
1374 mCachedLastDate = interval.intervalDateTime(mPeriod);
1375 return false;
1376 }
1377}
1378//@endcond
1379
1380bool RecurrenceRule::dateMatchesRules(const KDateTime &kdt) const
1381{
1382 KDateTime dt = kdt.toTimeSpec(d->mDateStart.timeSpec());
1383 for (int i = 0, iend = d->mConstraints.count(); i < iend; ++i) {
1384 if (d->mConstraints[i].matches(dt, recurrenceType())) {
1385 return true;
1386 }
1387 }
1388 return false;
1389}
1390
1391bool RecurrenceRule::recursOn(const QDate &qd, const KDateTime::Spec &timeSpec) const
1392{
1393 int i, iend;
1394
1395 if (!qd.isValid() || !d->mDateStart.isValid()) {
1396 // There can't be recurrences on invalid dates
1397 return false;
1398 }
1399
1400 if (allDay()) {
1401 // It's a date-only rule, so it has no time specification.
1402 // Therefore ignore 'timeSpec'.
1403 if (qd < d->mDateStart.date()) {
1404 return false;
1405 }
1406 // Start date is only included if it really matches
1407 QDate endDate;
1408 if (d->mDuration >= 0) {
1409 endDate = endDt().date();
1410 if (qd > endDate) {
1411 return false;
1412 }
1413 }
1414
1415 // The date must be in an appropriate interval (getNextValidDateInterval),
1416 // Plus it must match at least one of the constraints
1417 bool match = false;
1418 for (i = 0, iend = d->mConstraints.count(); i < iend && !match; ++i) {
1419 match = d->mConstraints[i].matches(qd, recurrenceType());
1420 }
1421 if (!match) {
1422 return false;
1423 }
1424
1425 KDateTime start(qd, QTime(0, 0, 0), d->mDateStart.timeSpec());
1426 Constraint interval(d->getNextValidDateInterval(start, recurrenceType()));
1427 // Constraint::matches is quite efficient, so first check if it can occur at
1428 // all before we calculate all actual dates.
1429 if (!interval.matches(qd, recurrenceType())) {
1430 return false;
1431 }
1432 // We really need to obtain the list of dates in this interval, since
1433 // otherwise BYSETPOS will not work (i.e. the date will match the interval,
1434 // but BYSETPOS selects only one of these matching dates!
1435 KDateTime end = start.addDays(1);
1436 do {
1437 DateTimeList dts = d->datesForInterval(interval, recurrenceType());
1438 for (i = 0, iend = dts.count(); i < iend; ++i) {
1439 if (dts[i].date() >= qd) {
1440 return dts[i].date() == qd;
1441 }
1442 }
1443 interval.increase(recurrenceType(), frequency());
1444 } while (interval.intervalDateTime(recurrenceType()) < end);
1445 return false;
1446 }
1447
1448 // It's a date-time rule, so we need to take the time specification into account.
1449 KDateTime start(qd, QTime(0, 0, 0), timeSpec);
1450 KDateTime end = start.addDays(1).toTimeSpec(d->mDateStart.timeSpec());
1451 start = start.toTimeSpec(d->mDateStart.timeSpec());
1452 if (end < d->mDateStart) {
1453 return false;
1454 }
1455 if (start < d->mDateStart) {
1456 start = d->mDateStart;
1457 }
1458
1459 // Start date is only included if it really matches
1460 if (d->mDuration >= 0) {
1461 KDateTime endRecur = endDt();
1462 if (endRecur.isValid()) {
1463 if (start > endRecur) {
1464 return false;
1465 }
1466 if (end > endRecur) {
1467 end = endRecur; // limit end-of-day time to end of recurrence rule
1468 }
1469 }
1470 }
1471
1472 if (d->mTimedRepetition) {
1473 // It's a simple sub-daily recurrence with no constraints
1474 int n = static_cast<int>((d->mDateStart.secsTo_long(start) - 1) % d->mTimedRepetition);
1475 return start.addSecs(d->mTimedRepetition - n) < end;
1476 }
1477
1478 // Find the start and end dates in the time spec for the rule
1479 QDate startDay = start.date();
1480 QDate endDay = end.addSecs(-1).date();
1481 int dayCount = startDay.daysTo(endDay) + 1;
1482
1483 // The date must be in an appropriate interval (getNextValidDateInterval),
1484 // Plus it must match at least one of the constraints
1485 bool match = false;
1486 for (i = 0, iend = d->mConstraints.count(); i < iend && !match; ++i) {
1487 match = d->mConstraints[i].matches(startDay, recurrenceType());
1488 for (int day = 1; day < dayCount && !match; ++day) {
1489 match = d->mConstraints[i].matches(startDay.addDays(day), recurrenceType());
1490 }
1491 }
1492 if (!match) {
1493 return false;
1494 }
1495
1496 Constraint interval(d->getNextValidDateInterval(start, recurrenceType()));
1497 // Constraint::matches is quite efficient, so first check if it can occur at
1498 // all before we calculate all actual dates.
1499 match = false;
1500 Constraint intervalm = interval;
1501 do {
1502 match = intervalm.matches(startDay, recurrenceType());
1503 for (int day = 1; day < dayCount && !match; ++day) {
1504 match = intervalm.matches(startDay.addDays(day), recurrenceType());
1505 }
1506 if (match) {
1507 break;
1508 }
1509 intervalm.increase(recurrenceType(), frequency());
1510 } while (intervalm.intervalDateTime(recurrenceType()) < end);
1511 if (!match) {
1512 return false;
1513 }
1514
1515 // We really need to obtain the list of dates in this interval, since
1516 // otherwise BYSETPOS will not work (i.e. the date will match the interval,
1517 // but BYSETPOS selects only one of these matching dates!
1518 do {
1519 DateTimeList dts = d->datesForInterval(interval, recurrenceType());
1520 int i = dts.findGE(start);
1521 if (i >= 0) {
1522 return dts[i] <= end;
1523 }
1524 interval.increase(recurrenceType(), frequency());
1525 } while (interval.intervalDateTime(recurrenceType()) < end);
1526
1527 return false;
1528}
1529
1530bool RecurrenceRule::recursAt(const KDateTime &kdt) const
1531{
1532 // Convert to the time spec used by this recurrence rule
1533 KDateTime dt(kdt.toTimeSpec(d->mDateStart.timeSpec()));
1534
1535 if (allDay()) {
1536 return recursOn(dt.date(), dt.timeSpec());
1537 }
1538 if (dt < d->mDateStart) {
1539 return false;
1540 }
1541 // Start date is only included if it really matches
1542 if (d->mDuration >= 0 && dt > endDt()) {
1543 return false;
1544 }
1545
1546 if (d->mTimedRepetition) {
1547 // It's a simple sub-daily recurrence with no constraints
1548 return !(d->mDateStart.secsTo_long(dt) % d->mTimedRepetition);
1549 }
1550
1551 // The date must be in an appropriate interval (getNextValidDateInterval),
1552 // Plus it must match at least one of the constraints
1553 if (!dateMatchesRules(dt)) {
1554 return false;
1555 }
1556 // if it recurs every interval, speed things up...
1557// if ( d->mFrequency == 1 && d->mBySetPos.isEmpty() && d->mByDays.isEmpty() ) return true;
1558 Constraint interval(d->getNextValidDateInterval(dt, recurrenceType()));
1559 // TODO_Recurrence: Does this work with BySetPos???
1560 if (interval.matches(dt, recurrenceType())) {
1561 return true;
1562 }
1563 return false;
1564}
1565
1566TimeList RecurrenceRule::recurTimesOn(const QDate &date, const KDateTime::Spec &timeSpec) const
1567{
1568 TimeList lst;
1569 if (allDay()) {
1570 return lst;
1571 }
1572 KDateTime start(date, QTime(0, 0, 0), timeSpec);
1573 KDateTime end = start.addDays(1).addSecs(-1);
1574 DateTimeList dts = timesInInterval(start, end); // returns between start and end inclusive
1575 for (int i = 0, iend = dts.count(); i < iend; ++i) {
1576 lst += dts[i].toTimeSpec(timeSpec).time();
1577 }
1578 return lst;
1579}
1580
1581/** Returns the number of recurrences up to and including the date/time specified. */
1582int RecurrenceRule::durationTo(const KDateTime &dt) const
1583{
1584 // Convert to the time spec used by this recurrence rule
1585 KDateTime toDate(dt.toTimeSpec(d->mDateStart.timeSpec()));
1586 // Easy cases:
1587 // either before start, or after all recurrences and we know their number
1588 if (toDate < d->mDateStart) {
1589 return 0;
1590 }
1591 // Start date is only included if it really matches
1592 if (d->mDuration > 0 && toDate >= endDt()) {
1593 return d->mDuration;
1594 }
1595
1596 if (d->mTimedRepetition) {
1597 // It's a simple sub-daily recurrence with no constraints
1598 return static_cast<int>(d->mDateStart.secsTo_long(toDate) / d->mTimedRepetition);
1599 }
1600
1601 return timesInInterval(d->mDateStart, toDate).count();
1602}
1603
1604int RecurrenceRule::durationTo(const QDate &date) const
1605{
1606 return durationTo(KDateTime(date, QTime(23, 59, 59), d->mDateStart.timeSpec()));
1607}
1608
1609KDateTime RecurrenceRule::getPreviousDate(const KDateTime &afterDate) const
1610{
1611 // Convert to the time spec used by this recurrence rule
1612 KDateTime toDate(afterDate.toTimeSpec(d->mDateStart.timeSpec()));
1613
1614 // Invalid starting point, or beyond end of recurrence
1615 if (!toDate.isValid() || toDate < d->mDateStart) {
1616 return KDateTime();
1617 }
1618
1619 if (d->mTimedRepetition) {
1620 // It's a simple sub-daily recurrence with no constraints
1621 KDateTime prev = toDate;
1622 if (d->mDuration >= 0 && endDt().isValid() && toDate > endDt()) {
1623 prev = endDt().addSecs(1).toTimeSpec(d->mDateStart.timeSpec());
1624 }
1625 int n = static_cast<int>((d->mDateStart.secsTo_long(prev) - 1) % d->mTimedRepetition);
1626 if (n < 0) {
1627 return KDateTime(); // before recurrence start
1628 }
1629 prev = prev.addSecs(-n - 1);
1630 return prev >= d->mDateStart ? prev : KDateTime();
1631 }
1632
1633 // If we have a cache (duration given), use that
1634 if (d->mDuration > 0) {
1635 if (!d->mCached) {
1636 d->buildCache();
1637 }
1638 int i = d->mCachedDates.findLT(toDate);
1639 if (i >= 0) {
1640 return d->mCachedDates[i];
1641 }
1642 return KDateTime();
1643 }
1644
1645 KDateTime prev = toDate;
1646 if (d->mDuration >= 0 && endDt().isValid() && toDate > endDt()) {
1647 prev = endDt().addSecs(1).toTimeSpec(d->mDateStart.timeSpec());
1648 }
1649
1650 Constraint interval(d->getPreviousValidDateInterval(prev, recurrenceType()));
1651 DateTimeList dts = d->datesForInterval(interval, recurrenceType());
1652 int i = dts.findLT(prev);
1653 if (i >= 0) {
1654 return (dts[i] >= d->mDateStart) ? dts[i] : KDateTime();
1655 }
1656
1657 // Previous interval. As soon as we find an occurrence, we're done.
1658 while (interval.intervalDateTime(recurrenceType()) > d->mDateStart) {
1659 interval.increase(recurrenceType(), -int(frequency()));
1660 // The returned date list is sorted
1661 DateTimeList dts = d->datesForInterval(interval, recurrenceType());
1662 // The list is sorted, so take the last one.
1663 if (!dts.isEmpty()) {
1664 prev = dts.last();
1665 if (prev.isValid() && prev >= d->mDateStart) {
1666 return prev;
1667 } else {
1668 return KDateTime();
1669 }
1670 }
1671 }
1672 return KDateTime();
1673}
1674
1675KDateTime RecurrenceRule::getNextDate(const KDateTime &preDate) const
1676{
1677 // Convert to the time spec used by this recurrence rule
1678 KDateTime fromDate(preDate.toTimeSpec(d->mDateStart.timeSpec()));
1679 // Beyond end of recurrence
1680 if (d->mDuration >= 0 && endDt().isValid() && fromDate >= endDt()) {
1681 return KDateTime();
1682 }
1683
1684 // Start date is only included if it really matches
1685 if (fromDate < d->mDateStart) {
1686 fromDate = d->mDateStart.addSecs(-1);
1687 }
1688
1689 if (d->mTimedRepetition) {
1690 // It's a simple sub-daily recurrence with no constraints
1691 int n = static_cast<int>((d->mDateStart.secsTo_long(fromDate) + 1) % d->mTimedRepetition);
1692 KDateTime next = fromDate.addSecs(d->mTimedRepetition - n + 1);
1693 return d->mDuration < 0 || !endDt().isValid() || next <= endDt() ? next : KDateTime();
1694 }
1695
1696 if (d->mDuration > 0) {
1697 if (!d->mCached) {
1698 d->buildCache();
1699 }
1700 int i = d->mCachedDates.findGT(fromDate);
1701 if (i >= 0) {
1702 return d->mCachedDates[i];
1703 }
1704 }
1705
1706 KDateTime end = endDt();
1707 Constraint interval(d->getNextValidDateInterval(fromDate, recurrenceType()));
1708 DateTimeList dts = d->datesForInterval(interval, recurrenceType());
1709 int i = dts.findGT(fromDate);
1710 if (i >= 0) {
1711 return (d->mDuration < 0 || dts[i] <= end) ? dts[i] : KDateTime();
1712 }
1713 interval.increase(recurrenceType(), frequency());
1714 if (d->mDuration >= 0 && interval.intervalDateTime(recurrenceType()) > end) {
1715 return KDateTime();
1716 }
1717
1718 // Increase the interval. The first occurrence that we find is the result (if
1719 // if's before the end date).
1720 // TODO: some validity checks to avoid infinite loops for contradictory constraints
1721 int loop = 0;
1722 do {
1723 DateTimeList dts = d->datesForInterval(interval, recurrenceType());
1724 if (dts.count() > 0) {
1725 KDateTime ret(dts[0]);
1726 if (d->mDuration >= 0 && ret > end) {
1727 return KDateTime();
1728 } else {
1729 return ret;
1730 }
1731 }
1732 interval.increase(recurrenceType(), frequency());
1733 } while (++loop < LOOP_LIMIT &&
1734 (d->mDuration < 0 || interval.intervalDateTime(recurrenceType()) < end));
1735 return KDateTime();
1736}
1737
1738DateTimeList RecurrenceRule::timesInInterval(const KDateTime &dtStart,
1739 const KDateTime &dtEnd) const
1740{
1741 const KDateTime start = dtStart.toTimeSpec(d->mDateStart.timeSpec());
1742 const KDateTime end = dtEnd.toTimeSpec(d->mDateStart.timeSpec());
1743 DateTimeList result;
1744 if (end < d->mDateStart) {
1745 return result; // before start of recurrence
1746 }
1747 KDateTime enddt = end;
1748 if (d->mDuration >= 0) {
1749 const KDateTime endRecur = endDt();
1750 if (endRecur.isValid()) {
1751 if (start > endRecur) {
1752 return result; // beyond end of recurrence
1753 }
1754 if (end >= endRecur) {
1755 enddt = endRecur; // limit end time to end of recurrence rule
1756 }
1757 }
1758 }
1759
1760 if (d->mTimedRepetition) {
1761 // It's a simple sub-daily recurrence with no constraints
1762
1763 //Seconds to add to interval start, to get first occurrence which is within interval
1764 qint64 offsetFromNextOccurrence;
1765 if (d->mDateStart < start) {
1766 offsetFromNextOccurrence =
1767 d->mTimedRepetition - (d->mDateStart.secsTo_long(start) % d->mTimedRepetition);
1768 } else {
1769 offsetFromNextOccurrence = -(d->mDateStart.secsTo_long(start) % d->mTimedRepetition);
1770 }
1771 KDateTime dt = start.addSecs(offsetFromNextOccurrence);
1772 if (dt <= enddt) {
1773 int numberOfOccurrencesWithinInterval =
1774 static_cast<int>(dt.secsTo_long(enddt) / d->mTimedRepetition) + 1;
1775 // limit n by a sane value else we can "explode".
1776 numberOfOccurrencesWithinInterval = qMin(numberOfOccurrencesWithinInterval, LOOP_LIMIT);
1777 for (int i = 0;
1778 i < numberOfOccurrencesWithinInterval;
1779 dt = dt.addSecs(d->mTimedRepetition), ++i) {
1780 result += dt;
1781 }
1782 }
1783 return result;
1784 }
1785
1786 KDateTime st = start;
1787 bool done = false;
1788 if (d->mDuration > 0) {
1789 if (!d->mCached) {
1790 d->buildCache();
1791 }
1792 if (d->mCachedDateEnd.isValid() && start > d->mCachedDateEnd) {
1793 return result; // beyond end of recurrence
1794 }
1795 int i = d->mCachedDates.findGE(start);
1796 if (i >= 0) {
1797 int iend = d->mCachedDates.findGT(enddt, i);
1798 if (iend < 0) {
1799 iend = d->mCachedDates.count();
1800 } else {
1801 done = true;
1802 }
1803 while (i < iend) {
1804 result += d->mCachedDates[i++];
1805 }
1806 }
1807 if (d->mCachedDateEnd.isValid()) {
1808 done = true;
1809 } else if (!result.isEmpty()) {
1810 result += KDateTime(); // indicate that the returned list is incomplete
1811 done = true;
1812 }
1813 if (done) {
1814 return result;
1815 }
1816 // We don't have any result yet, but we reached the end of the incomplete cache
1817 st = d->mCachedLastDate.addSecs(1);
1818 }
1819
1820 Constraint interval(d->getNextValidDateInterval(st, recurrenceType()));
1821 int loop = 0;
1822 do {
1823 DateTimeList dts = d->datesForInterval(interval, recurrenceType());
1824 int i = 0;
1825 int iend = dts.count();
1826 if (loop == 0) {
1827 i = dts.findGE(st);
1828 if (i < 0) {
1829 i = iend;
1830 }
1831 }
1832 int j = dts.findGT(enddt, i);
1833 if (j >= 0) {
1834 iend = j;
1835 loop = LOOP_LIMIT;
1836 }
1837 while (i < iend) {
1838 result += dts[i++];
1839 }
1840 // Increase the interval.
1841 interval.increase(recurrenceType(), frequency());
1842 } while (++loop < LOOP_LIMIT &&
1843 interval.intervalDateTime(recurrenceType()) < end);
1844 return result;
1845}
1846
1847//@cond PRIVATE
1848// Find the date/time of the occurrence at or before a date/time,
1849// for a given period type.
1850// Return a constraint whose value appropriate to 'type', is set to
1851// the value contained in the date/time.
1852Constraint RecurrenceRule::Private::getPreviousValidDateInterval(const KDateTime &dt,
1853 PeriodType type) const
1854{
1855 long periods = 0;
1856 KDateTime start = mDateStart;
1857 KDateTime nextValid(start);
1858 int modifier = 1;
1859 KDateTime toDate(dt.toTimeSpec(start.timeSpec()));
1860 // for super-daily recurrences, don't care about the time part
1861
1862 // Find the #intervals since the dtstart and round to the next multiple of
1863 // the frequency
1864 switch (type) {
1865 // Really fall through for sub-daily, since the calculations only differ
1866 // by the factor 60 and 60*60! Same for weekly and daily (factor 7)
1867 case rHourly:
1868 modifier *= 60;
1869 case rMinutely:
1870 modifier *= 60;
1871 case rSecondly:
1872 periods = static_cast<int>(start.secsTo_long(toDate) / modifier);
1873 // round it down to the next lower multiple of frequency:
1874 if (mFrequency > 0) {
1875 periods = (periods / mFrequency) * mFrequency;
1876 }
1877 nextValid = start.addSecs(modifier * periods);
1878 break;
1879 case rWeekly:
1880 toDate = toDate.addDays(-(7 + toDate.date().dayOfWeek() - mWeekStart) % 7);
1881 start = start.addDays(-(7 + start.date().dayOfWeek() - mWeekStart) % 7);
1882 modifier *= 7;
1883 case rDaily:
1884 periods = start.daysTo(toDate) / modifier;
1885 // round it down to the next lower multiple of frequency:
1886 if (mFrequency > 0) {
1887 periods = (periods / mFrequency) * mFrequency;
1888 }
1889 nextValid = start.addDays(modifier * periods);
1890 break;
1891 case rMonthly:
1892 {
1893 periods = 12 * (toDate.date().year() - start.date().year()) +
1894 (toDate.date().month() - start.date().month());
1895 // round it down to the next lower multiple of frequency:
1896 if (mFrequency > 0) {
1897 periods = (periods / mFrequency) * mFrequency;
1898 }
1899 // set the day to the first day of the month, so we don't have problems
1900 // with non-existent days like Feb 30 or April 31
1901 start.setDate(QDate(start.date().year(), start.date().month(), 1));
1902 nextValid.setDate(start.date().addMonths(periods));
1903 break;
1904 }
1905 case rYearly:
1906 periods = (toDate.date().year() - start.date().year());
1907 // round it down to the next lower multiple of frequency:
1908 if (mFrequency > 0) {
1909 periods = (periods / mFrequency) * mFrequency;
1910 }
1911 nextValid.setDate(start.date().addYears(periods));
1912 break;
1913 default:
1914 break;
1915 }
1916
1917 return Constraint(nextValid, type, mWeekStart);
1918}
1919
1920// Find the date/time of the next occurrence at or after a date/time,
1921// for a given period type.
1922// Return a constraint whose value appropriate to 'type', is set to the
1923// value contained in the date/time.
1924Constraint RecurrenceRule::Private::getNextValidDateInterval(const KDateTime &dt,
1925 PeriodType type) const
1926{
1927 // TODO: Simplify this!
1928 long periods = 0;
1929 KDateTime start = mDateStart;
1930 KDateTime nextValid(start);
1931 int modifier = 1;
1932 KDateTime toDate(dt.toTimeSpec(start.timeSpec()));
1933 // for super-daily recurrences, don't care about the time part
1934
1935 // Find the #intervals since the dtstart and round to the next multiple of
1936 // the frequency
1937 switch (type) {
1938 // Really fall through for sub-daily, since the calculations only differ
1939 // by the factor 60 and 60*60! Same for weekly and daily (factor 7)
1940 case rHourly:
1941 modifier *= 60;
1942 case rMinutely:
1943 modifier *= 60;
1944 case rSecondly:
1945 periods = static_cast<int>(start.secsTo_long(toDate) / modifier);
1946 periods = qMax(0L, periods);
1947 if (periods > 0 && mFrequency > 0) {
1948 periods += (mFrequency - 1 - ((periods - 1) % mFrequency));
1949 }
1950 nextValid = start.addSecs(modifier * periods);
1951 break;
1952 case rWeekly:
1953 // correct both start date and current date to start of week
1954 toDate = toDate.addDays(-(7 + toDate.date().dayOfWeek() - mWeekStart) % 7);
1955 start = start.addDays(-(7 + start.date().dayOfWeek() - mWeekStart) % 7);
1956 modifier *= 7;
1957 case rDaily:
1958 periods = start.daysTo(toDate) / modifier;
1959 periods = qMax(0L, periods);
1960 if (periods > 0 && mFrequency > 0) {
1961 periods += (mFrequency - 1 - ((periods - 1) % mFrequency));
1962 }
1963 nextValid = start.addDays(modifier * periods);
1964 break;
1965 case rMonthly:
1966 {
1967 periods = 12 * (toDate.date().year() - start.date().year()) +
1968 (toDate.date().month() - start.date().month());
1969 periods = qMax(0L, periods);
1970 if (periods > 0 && mFrequency > 0) {
1971 periods += (mFrequency - 1 - ((periods - 1) % mFrequency));
1972 }
1973 // set the day to the first day of the month, so we don't have problems
1974 // with non-existent days like Feb 30 or April 31
1975 start.setDate(QDate(start.date().year(), start.date().month(), 1));
1976 nextValid.setDate(start.date().addMonths(periods));
1977 break;
1978 }
1979 case rYearly:
1980 periods = (toDate.date().year() - start.date().year());
1981 periods = qMax(0L, periods);
1982 if (periods > 0 && mFrequency > 0) {
1983 periods += (mFrequency - 1 - ((periods - 1) % mFrequency));
1984 }
1985 nextValid.setDate(start.date().addYears(periods));
1986 break;
1987 default:
1988 break;
1989 }
1990
1991 return Constraint(nextValid, type, mWeekStart);
1992}
1993
1994DateTimeList RecurrenceRule::Private::datesForInterval(const Constraint &interval,
1995 PeriodType type) const
1996{
1997 /* -) Loop through constraints,
1998 -) merge interval with each constraint
1999 -) if merged constraint is not consistent => ignore that constraint
2000 -) if complete => add that one date to the date list
2001 -) Loop through all missing fields => For each add the resulting
2002 */
2003 DateTimeList lst;
2004 for (int i = 0, iend = mConstraints.count(); i < iend; ++i) {
2005 Constraint merged(interval);
2006 if (merged.merge(mConstraints[i])) {
2007 // If the information is incomplete, we can't use this constraint
2008 if (merged.year > 0 && merged.hour >= 0 && merged.minute >= 0 && merged.second >= 0) {
2009 // We have a valid constraint, so get all datetimes that match it andd
2010 // append it to all date/times of this interval
2011 QList<KDateTime> lstnew = merged.dateTimes(type);
2012 lst += lstnew;
2013 }
2014 }
2015 }
2016 // Sort it so we can apply the BySetPos. Also some logic relies on this being sorted
2017 lst.sortUnique();
2018
2019 /*if ( lst.isEmpty() ) {
2020 kDebug() << " No Dates in Interval";
2021 } else {
2022 kDebug() << " Dates:";
2023 for ( int i = 0, iend = lst.count(); i < iend; ++i ) {
2024 kDebug()<< " -)" << dumpTime(lst[i]);
2025 }
2026 kDebug() << " ---------------------";
2027 }*/
2028 if (!mBySetPos.isEmpty()) {
2029 DateTimeList tmplst = lst;
2030 lst.clear();
2031 for (int i = 0, iend = mBySetPos.count(); i < iend; ++i) {
2032 int pos = mBySetPos[i];
2033 if (pos > 0) {
2034 --pos;
2035 }
2036 if (pos < 0) {
2037 pos += tmplst.count();
2038 }
2039 if (pos >= 0 && pos < tmplst.count()) {
2040 lst.append(tmplst[pos]);
2041 }
2042 }
2043 lst.sortUnique();
2044 }
2045
2046 return lst;
2047}
2048//@endcond
2049
2050void RecurrenceRule::dump() const
2051{
2052#ifndef NDEBUG
2053 kDebug();
2054 if (!d->mRRule.isEmpty()) {
2055 kDebug() << " RRULE=" << d->mRRule;
2056 }
2057 kDebug() << " Read-Only:" << isReadOnly();
2058
2059 kDebug() << " Period type:" << int(recurrenceType()) << ", frequency:" << frequency();
2060 kDebug() << " #occurrences:" << duration();
2061 kDebug() << " start date:" << dumpTime(startDt())
2062 << ", end date:" << dumpTime(endDt());
2063
2064#define dumpByIntList(list,label) \
2065 if ( !list.isEmpty() ) {\
2066 QStringList lst;\
2067 for ( int i = 0, iend = list.count(); i < iend; ++i ) {\
2068 lst.append( QString::number( list[i] ) );\
2069 }\
2070 kDebug() << " " << label << lst.join( QLatin1String(", ") );\
2071 }
2072 dumpByIntList(d->mBySeconds, QLatin1String("BySeconds: "));
2073 dumpByIntList(d->mByMinutes, QLatin1String("ByMinutes: "));
2074 dumpByIntList(d->mByHours, QLatin1String("ByHours: "));
2075 if (!d->mByDays.isEmpty()) {
2076 QStringList lst;
2077 for (int i = 0, iend = d->mByDays.count(); i < iend; ++i) {
2078 \
2079 lst.append((d->mByDays[i].pos() ? QString::number(d->mByDays[i].pos()) : QLatin1String("")) +
2080 DateHelper::dayName(d->mByDays[i].day()));
2081 }
2082 kDebug() << " ByDays: " << lst.join(QLatin1String(", "));
2083 }
2084 dumpByIntList(d->mByMonthDays, QLatin1String("ByMonthDays:"));
2085 dumpByIntList(d->mByYearDays, QLatin1String("ByYearDays: "));
2086 dumpByIntList(d->mByWeekNumbers, QLatin1String("ByWeekNr: "));
2087 dumpByIntList(d->mByMonths, QLatin1String("ByMonths: "));
2088 dumpByIntList(d->mBySetPos, QLatin1String("BySetPos: "));
2089#undef dumpByIntList
2090
2091 kDebug() << " Week start:" << DateHelper::dayName(d->mWeekStart); //krazy:exclude=kdebug
2092
2093 kDebug() << " Constraints:";
2094 // dump constraints
2095 for (int i = 0, iend = d->mConstraints.count(); i < iend; ++i) {
2096 d->mConstraints[i].dump();
2097 }
2098#endif
2099}
2100
2101//@cond PRIVATE
2102void Constraint::dump() const
2103{
2104 kDebug() << " ~> Y=" << year
2105 << ", M=" << month
2106 << ", D=" << day
2107 << ", H=" << hour
2108 << ", m=" << minute
2109 << ", S=" << second
2110 << ", wd=" << weekday
2111 << ",#wd=" << weekdaynr
2112 << ", #w=" << weeknumber
2113 << ", yd=" << yearday;
2114}
2115//@endcond
2116
2117QString dumpTime(const KDateTime &dt)
2118{
2119#ifndef NDEBUG
2120 if (!dt.isValid()) {
2121 return QString();
2122 }
2123 QString result;
2124 if (dt.isDateOnly()) {
2125 result = dt.toString(QLatin1String("%a %Y-%m-%d %:Z"));
2126 } else {
2127 result = dt.toString(QLatin1String("%a %Y-%m-%d %H:%M:%S %:Z"));
2128 if (dt.isSecondOccurrence()) {
2129 result += QLatin1String(" (2nd)");
2130 }
2131 }
2132 if (dt.timeSpec() == KDateTime::Spec::ClockTime()) {
2133 result += QLatin1String("Clock");
2134 }
2135 return result;
2136#else
2137 Q_UNUSED(dt);
2138 return QString();
2139#endif
2140}
2141
2142KDateTime RecurrenceRule::startDt() const
2143{
2144 return d->mDateStart;
2145}
2146
2147RecurrenceRule::PeriodType RecurrenceRule::recurrenceType() const
2148{
2149 return d->mPeriod;
2150}
2151
2152uint RecurrenceRule::frequency() const
2153{
2154 return d->mFrequency;
2155}
2156
2157int RecurrenceRule::duration() const
2158{
2159 return d->mDuration;
2160}
2161
2162QString RecurrenceRule::rrule() const
2163{
2164 return d->mRRule;
2165}
2166
2167void RecurrenceRule::setRRule(const QString &rrule)
2168{
2169 d->mRRule = rrule;
2170}
2171
2172bool RecurrenceRule::isReadOnly() const
2173{
2174 return d->mIsReadOnly;
2175}
2176
2177void RecurrenceRule::setReadOnly(bool readOnly)
2178{
2179 d->mIsReadOnly = readOnly;
2180}
2181
2182bool RecurrenceRule::recurs() const
2183{
2184 return d->mPeriod != rNone;
2185}
2186
2187bool RecurrenceRule::allDay() const
2188{
2189 return d->mAllDay;
2190}
2191
2192const QList<int> &RecurrenceRule::bySeconds() const
2193{
2194 return d->mBySeconds;
2195}
2196
2197const QList<int> &RecurrenceRule::byMinutes() const
2198{
2199 return d->mByMinutes;
2200}
2201
2202const QList<int> &RecurrenceRule::byHours() const
2203{
2204 return d->mByHours;
2205}
2206
2207const QList<RecurrenceRule::WDayPos> &RecurrenceRule::byDays() const
2208{
2209 return d->mByDays;
2210}
2211
2212const QList<int> &RecurrenceRule::byMonthDays() const
2213{
2214 return d->mByMonthDays;
2215}
2216
2217const QList<int> &RecurrenceRule::byYearDays() const
2218{
2219 return d->mByYearDays;
2220}
2221
2222const QList<int> &RecurrenceRule::byWeekNumbers() const
2223{
2224 return d->mByWeekNumbers;
2225}
2226
2227const QList<int> &RecurrenceRule::byMonths() const
2228{
2229 return d->mByMonths;
2230}
2231
2232const QList<int> &RecurrenceRule::bySetPos() const
2233{
2234 return d->mBySetPos;
2235}
2236
2237short RecurrenceRule::weekStart() const
2238{
2239 return d->mWeekStart;
2240}
2241
2242RecurrenceRule::RuleObserver::~RuleObserver()
2243{
2244}
2245
2246RecurrenceRule::WDayPos::WDayPos(int ps, short dy)
2247 : mDay(dy), mPos(ps)
2248{
2249}
2250
2251void RecurrenceRule::WDayPos::setDay(short dy)
2252{
2253 mDay = dy;
2254}
2255
2256short RecurrenceRule::WDayPos::day() const
2257{
2258 return mDay;
2259}
2260
2261void RecurrenceRule::WDayPos::setPos(int ps)
2262{
2263 mPos = ps;
2264}
2265
2266int RecurrenceRule::WDayPos::pos() const
2267{
2268 return mPos;
2269}
2270
2271
2272QDataStream& operator<<(QDataStream &out, const Constraint &c)
2273{
2274 out << c.year << c.month << c.day << c.hour << c.minute << c.second
2275 << c.weekday << c.weekdaynr << c.weeknumber << c.yearday << c.weekstart
2276 << c.timespec << c.secondOccurrence;
2277
2278 return out;
2279}
2280
2281QDataStream& operator>>(QDataStream &in, Constraint &c)
2282{
2283 in >> c.year >> c.month >> c.day >> c.hour >> c.minute >> c.second
2284 >> c.weekday >> c.weekdaynr >> c.weeknumber >> c.yearday >> c.weekstart
2285 >> c.timespec >> c.secondOccurrence;
2286 return in;
2287}
2288
2289KCALCORE_EXPORT QDataStream& KCalCore::operator<<(QDataStream &out, const KCalCore::RecurrenceRule::WDayPos &w)
2290{
2291 out << w.mDay << w.mPos;
2292 return out;
2293}
2294
2295KCALCORE_EXPORT QDataStream& KCalCore::operator>>(QDataStream &in, KCalCore::RecurrenceRule::WDayPos &w)
2296{
2297 in >> w.mDay >> w.mPos;
2298 return in;
2299}
2300
2301KCALCORE_EXPORT QDataStream& KCalCore::operator<<(QDataStream &out, const KCalCore::RecurrenceRule *r)
2302{
2303 if (!r)
2304 return out;
2305
2306 RecurrenceRule::Private *d = r->d;
2307 out << d->mRRule << static_cast<quint32>(d->mPeriod) << d->mDateStart << d->mFrequency << d->mDuration << d->mDateEnd
2308 << d->mBySeconds << d->mByMinutes << d->mByHours << d->mByDays << d->mByMonthDays
2309 << d->mByYearDays << d->mByWeekNumbers << d->mByMonths << d->mBySetPos
2310 << d->mWeekStart << d->mConstraints << d->mAllDay << d->mNoByRules << d->mTimedRepetition
2311 << d->mIsReadOnly;
2312
2313 return out;
2314}
2315
2316
2317KCALCORE_EXPORT QDataStream& KCalCore::operator>>(QDataStream &in, const KCalCore::RecurrenceRule *r)
2318{
2319 if (!r)
2320 return in;
2321
2322 RecurrenceRule::Private *d = r->d;
2323 quint32 period;
2324 in >> d->mRRule >> period >> d->mDateStart >> d->mFrequency >> d->mDuration >> d->mDateEnd
2325 >> d->mBySeconds >> d->mByMinutes >> d->mByHours >> d->mByDays >> d->mByMonthDays
2326 >> d->mByYearDays >> d->mByWeekNumbers >> d->mByMonths >> d->mBySetPos
2327 >> d->mWeekStart >> d->mConstraints >> d->mAllDay >> d->mNoByRules >> d->mTimedRepetition
2328 >> d->mIsReadOnly;
2329
2330 d->mPeriod = static_cast<RecurrenceRule::PeriodType>(period);
2331
2332 return in;
2333}
2334