1/*
2 This file is part of kcalcore library.
3
4 Copyright (c) 1998 Preston Brown <pbrown@kde.org>
5 Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
6 Copyright (c) 2002,2006 David Jarvie <software@astrojar.org.uk>
7 Copyright (C) 2005 Reinhold Kainhofer <kainhofer@kde.org>
8
9 This library is free software; you can redistribute it and/or
10 modify it under the terms of the GNU Library General Public
11 License as published by the Free Software Foundation; either
12 version 2 of the License, or (at your option) any later version.
13
14 This library is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Library General Public License for more details.
18
19 You should have received a copy of the GNU Library General Public License
20 along with this library; see the file COPYING.LIB. If not, write to
21 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 Boston, MA 02110-1301, USA.
23*/
24#include "recurrence.h"
25
26#include <KDebug>
27
28#include <QtCore/QBitArray>
29#include <QtCore/QTime>
30
31using namespace KCalCore;
32
33//@cond PRIVATE
34class KCalCore::Recurrence::Private
35{
36public:
37 Private()
38 : mCachedType(rMax),
39 mAllDay(false),
40 mRecurReadOnly(false)
41 {
42 }
43
44 Private(const Private &p)
45 : mRDateTimes(p.mRDateTimes),
46 mRDates(p.mRDates),
47 mExDateTimes(p.mExDateTimes),
48 mExDates(p.mExDates),
49 mStartDateTime(p.mStartDateTime),
50 mCachedType(p.mCachedType),
51 mAllDay(p.mAllDay),
52 mRecurReadOnly(p.mRecurReadOnly)
53 {
54 }
55
56 bool operator==(const Private &p) const;
57
58 RecurrenceRule::List mExRules;
59 RecurrenceRule::List mRRules;
60 DateTimeList mRDateTimes;
61 DateList mRDates;
62 DateTimeList mExDateTimes;
63 DateList mExDates;
64 KDateTime mStartDateTime; // date/time of first recurrence
65 QList<RecurrenceObserver*> mObservers;
66
67 // Cache the type of the recurrence with the old system (e.g. MonthlyPos)
68 mutable ushort mCachedType;
69
70 bool mAllDay; // the recurrence has no time, just a date
71 bool mRecurReadOnly;
72};
73
74bool Recurrence::Private::operator==(const Recurrence::Private &p) const
75{
76 if ((mStartDateTime != p.mStartDateTime &&
77 (mStartDateTime.isValid() || p.mStartDateTime.isValid())) ||
78 mAllDay != p.mAllDay ||
79 mRecurReadOnly != p.mRecurReadOnly ||
80 mExDates != p.mExDates ||
81 mExDateTimes != p.mExDateTimes ||
82 mRDates != p.mRDates ||
83 mRDateTimes != p.mRDateTimes) {
84 return false;
85 }
86
87// Compare the rrules, exrules! Assume they have the same order... This only
88// matters if we have more than one rule (which shouldn't be the default anyway)
89 int i;
90 int end = mRRules.count();
91 if (end != p.mRRules.count()) {
92 return false;
93 }
94 for (i = 0; i < end; ++i) {
95 if (*mRRules[i] != *p.mRRules[i]) {
96 return false;
97 }
98 }
99 end = mExRules.count();
100 if (end != p.mExRules.count()) {
101 return false;
102 }
103 for (i = 0; i < end; ++i) {
104 if (*mExRules[i] != *p.mExRules[i]) {
105 return false;
106 }
107 }
108 return true;
109}
110//@endcond
111
112Recurrence::Recurrence()
113 : d(new KCalCore::Recurrence::Private())
114{
115}
116
117Recurrence::Recurrence(const Recurrence &r)
118 : RecurrenceRule::RuleObserver(),
119 d(new KCalCore::Recurrence::Private(*r.d))
120{
121 int i, end;
122 for (i = 0, end = r.d->mRRules.count(); i < end; ++i) {
123 RecurrenceRule *rule = new RecurrenceRule(*r.d->mRRules[i]);
124 d->mRRules.append(rule);
125 rule->addObserver(this);
126 }
127 for (i = 0, end = r.d->mExRules.count(); i < end; ++i) {
128 RecurrenceRule *rule = new RecurrenceRule(*r.d->mExRules[i]);
129 d->mExRules.append(rule);
130 rule->addObserver(this);
131 }
132}
133
134Recurrence::~Recurrence()
135{
136 qDeleteAll(d->mExRules);
137 qDeleteAll(d->mRRules);
138 delete d;
139}
140
141bool Recurrence::operator==(const Recurrence &recurrence) const
142{
143 return *d == *recurrence.d;
144}
145
146Recurrence &Recurrence::operator=(const Recurrence &recurrence)
147{
148 // check for self assignment
149 if (&recurrence == this) {
150 return *this;
151 }
152
153 *d = *recurrence.d;
154 return *this;
155}
156
157void Recurrence::addObserver(RecurrenceObserver *observer)
158{
159 if (!d->mObservers.contains(observer)) {
160 d->mObservers.append(observer);
161 }
162}
163
164void Recurrence::removeObserver(RecurrenceObserver *observer)
165{
166 if (d->mObservers.contains(observer)) {
167 d->mObservers.removeAll(observer);
168 }
169}
170
171KDateTime Recurrence::startDateTime() const
172{
173 return d->mStartDateTime;
174}
175
176bool Recurrence::allDay() const
177{
178 return d->mAllDay;
179}
180
181void Recurrence::setAllDay(bool allDay)
182{
183 if (d->mRecurReadOnly || allDay == d->mAllDay) {
184 return;
185 }
186
187 d->mAllDay = allDay;
188 for (int i = 0, end = d->mRRules.count(); i < end; ++i) {
189 d->mRRules[i]->setAllDay(allDay);
190 }
191 for (int i = 0, end = d->mExRules.count(); i < end; ++i) {
192 d->mExRules[i]->setAllDay(allDay);
193 }
194 updated();
195}
196
197RecurrenceRule *Recurrence::defaultRRule(bool create) const
198{
199 if (d->mRRules.isEmpty()) {
200 if (!create || d->mRecurReadOnly) {
201 return 0;
202 }
203 RecurrenceRule *rrule = new RecurrenceRule();
204 rrule->setStartDt(startDateTime());
205 const_cast<KCalCore::Recurrence*>(this)->addRRule(rrule);
206 return rrule;
207 } else {
208 return d->mRRules[0];
209 }
210}
211
212RecurrenceRule *Recurrence::defaultRRuleConst() const
213{
214 return d->mRRules.isEmpty() ? 0 : d->mRRules[0];
215}
216
217void Recurrence::updated()
218{
219 // recurrenceType() re-calculates the type if it's rMax
220 d->mCachedType = rMax;
221 for (int i = 0, end = d->mObservers.count(); i < end; ++i) {
222 if (d->mObservers[i]) {
223 d->mObservers[i]->recurrenceUpdated(this);
224 }
225 }
226}
227
228bool Recurrence::recurs() const
229{
230 return !d->mRRules.isEmpty() || !d->mRDates.isEmpty() || !d->mRDateTimes.isEmpty();
231}
232
233ushort Recurrence::recurrenceType() const
234{
235 if (d->mCachedType == rMax) {
236 d->mCachedType = recurrenceType(defaultRRuleConst());
237 }
238 return d->mCachedType;
239}
240
241ushort Recurrence::recurrenceType(const RecurrenceRule *rrule)
242{
243 if (!rrule) {
244 return rNone;
245 }
246 RecurrenceRule::PeriodType type = rrule->recurrenceType();
247
248 // BYSETPOS, BYWEEKNUMBER and BYSECOND were not supported in old versions
249 if (!rrule->bySetPos().isEmpty() ||
250 !rrule->bySeconds().isEmpty() ||
251 !rrule->byWeekNumbers().isEmpty()) {
252 return rOther;
253 }
254
255 // It wasn't possible to set BYMINUTES, BYHOUR etc. by the old code. So if
256 // it's set, it's none of the old types
257 if (!rrule->byMinutes().isEmpty() || !rrule->byHours().isEmpty()) {
258 return rOther;
259 }
260
261 // Possible combinations were:
262 // BYDAY: with WEEKLY, MONTHLY, YEARLY
263 // BYMONTHDAY: with MONTHLY, YEARLY
264 // BYMONTH: with YEARLY
265 // BYYEARDAY: with YEARLY
266 if ((!rrule->byYearDays().isEmpty() && type != RecurrenceRule::rYearly) ||
267 (!rrule->byMonths().isEmpty() && type != RecurrenceRule::rYearly)) {
268 return rOther;
269 }
270 if (!rrule->byDays().isEmpty()) {
271 if (type != RecurrenceRule::rYearly &&
272 type != RecurrenceRule::rMonthly &&
273 type != RecurrenceRule::rWeekly) {
274 return rOther;
275 }
276 }
277
278 switch (type) {
279 case RecurrenceRule::rNone:
280 return rNone;
281 case RecurrenceRule::rMinutely:
282 return rMinutely;
283 case RecurrenceRule::rHourly:
284 return rHourly;
285 case RecurrenceRule::rDaily:
286 return rDaily;
287 case RecurrenceRule::rWeekly:
288 return rWeekly;
289 case RecurrenceRule::rMonthly:
290 {
291 if (rrule->byDays().isEmpty()) {
292 return rMonthlyDay;
293 } else if (rrule->byMonthDays().isEmpty()) {
294 return rMonthlyPos;
295 } else {
296 return rOther; // both position and date specified
297 }
298 }
299 case RecurrenceRule::rYearly:
300 {
301 // Possible combinations:
302 // rYearlyMonth: [BYMONTH &] BYMONTHDAY
303 // rYearlyDay: BYYEARDAY
304 // rYearlyPos: [BYMONTH &] BYDAY
305 if (!rrule->byDays().isEmpty()) {
306 // can only by rYearlyPos
307 if (rrule->byMonthDays().isEmpty() && rrule->byYearDays().isEmpty()) {
308 return rYearlyPos;
309 } else {
310 return rOther;
311 }
312 } else if (!rrule->byYearDays().isEmpty()) {
313 // Can only be rYearlyDay
314 if (rrule->byMonths().isEmpty() && rrule->byMonthDays().isEmpty()) {
315 return rYearlyDay;
316 } else {
317 return rOther;
318 }
319 } else {
320 return rYearlyMonth;
321 }
322 break;
323 }
324 default:
325 return rOther;
326 }
327 return rOther;
328}
329
330bool Recurrence::recursOn(const QDate &qd, const KDateTime::Spec &timeSpec) const
331{
332 // Don't waste time if date is before the start of the recurrence
333 if (KDateTime(qd, QTime(23, 59, 59), timeSpec) < d->mStartDateTime) {
334 return false;
335 }
336
337 // First handle dates. Exrules override
338 if (d->mExDates.containsSorted(qd)) {
339 return false;
340 }
341
342 int i, end;
343 TimeList tms;
344 // For all-day events a matching exrule excludes the whole day
345 // since exclusions take precedence over inclusions, we know it can't occur on that day.
346 if (allDay()) {
347 for (i = 0, end = d->mExRules.count(); i < end; ++i) {
348 if (d->mExRules[i]->recursOn(qd, timeSpec)) {
349 return false;
350 }
351 }
352 }
353
354 if (d->mRDates.containsSorted(qd)) {
355 return true;
356 }
357
358 // Check if it might recur today at all.
359 bool recurs = (startDate() == qd);
360 for (i = 0, end = d->mRDateTimes.count(); i < end && !recurs; ++i) {
361 recurs = (d->mRDateTimes[i].toTimeSpec(timeSpec).date() == qd);
362 }
363 for (i = 0, end = d->mRRules.count(); i < end && !recurs; ++i) {
364 recurs = d->mRRules[i]->recursOn(qd, timeSpec);
365 }
366 // If the event wouldn't recur at all, simply return false, don't check ex*
367 if (!recurs) {
368 return false;
369 }
370
371 // Check if there are any times for this day excluded, either by exdate or exrule:
372 bool exon = false;
373 for (i = 0, end = d->mExDateTimes.count(); i < end && !exon; ++i) {
374 exon = (d->mExDateTimes[i].toTimeSpec(timeSpec).date() == qd);
375 }
376 if (!allDay()) { // we have already checked all-day times above
377 for (i = 0, end = d->mExRules.count(); i < end && !exon; ++i) {
378 exon = d->mExRules[i]->recursOn(qd, timeSpec);
379 }
380 }
381
382 if (!exon) {
383 // Simple case, nothing on that day excluded, return the value from before
384 return recurs;
385 } else {
386 // Harder part: I don't think there is any way other than to calculate the
387 // whole list of items for that day.
388//TODO: consider whether it would be more efficient to call
389// Rule::recurTimesOn() instead of Rule::recursOn() from the start
390 TimeList timesForDay(recurTimesOn(qd, timeSpec));
391 return !timesForDay.isEmpty();
392 }
393}
394
395bool Recurrence::recursAt(const KDateTime &dt) const
396{
397 // Convert to recurrence's time zone for date comparisons, and for more efficient time comparisons
398 KDateTime dtrecur = dt.toTimeSpec(d->mStartDateTime.timeSpec());
399
400 // if it's excluded anyway, don't bother to check if it recurs at all.
401 if (d->mExDateTimes.containsSorted(dtrecur) ||
402 d->mExDates.containsSorted(dtrecur.date())) {
403 return false;
404 }
405 int i, end;
406 for (i = 0, end = d->mExRules.count(); i < end; ++i) {
407 if (d->mExRules[i]->recursAt(dtrecur)) {
408 return false;
409 }
410 }
411
412 // Check explicit recurrences, then rrules.
413 if (startDateTime() == dtrecur || d->mRDateTimes.containsSorted(dtrecur)) {
414 return true;
415 }
416 for (i = 0, end = d->mRRules.count(); i < end; ++i) {
417 if (d->mRRules[i]->recursAt(dtrecur)) {
418 return true;
419 }
420 }
421
422 return false;
423}
424
425/** Calculates the cumulative end of the whole recurrence (rdates and rrules).
426 If any rrule is infinite, or the recurrence doesn't have any rrules or
427 rdates, an invalid date is returned. */
428KDateTime Recurrence::endDateTime() const
429{
430 DateTimeList dts;
431 dts << startDateTime();
432 if (!d->mRDates.isEmpty()) {
433 dts << KDateTime(d->mRDates.last(), QTime(0, 0, 0), d->mStartDateTime.timeSpec());
434 }
435 if (!d->mRDateTimes.isEmpty()) {
436 dts << d->mRDateTimes.last();
437 }
438 for (int i = 0, end = d->mRRules.count(); i < end; ++i) {
439 KDateTime rl(d->mRRules[i]->endDt());
440 // if any of the rules is infinite, the whole recurrence is
441 if (!rl.isValid()) {
442 return KDateTime();
443 }
444 dts << rl;
445 }
446 dts.sortUnique();
447 return dts.isEmpty() ? KDateTime() : dts.last();
448}
449
450/** Calculates the cumulative end of the whole recurrence (rdates and rrules).
451 If any rrule is infinite, or the recurrence doesn't have any rrules or
452 rdates, an invalid date is returned. */
453QDate Recurrence::endDate() const
454{
455 KDateTime end(endDateTime());
456 return end.isValid() ? end.date() : QDate();
457}
458
459void Recurrence::setEndDate(const QDate &date)
460{
461 KDateTime dt(date, d->mStartDateTime.time(), d->mStartDateTime.timeSpec());
462 if (allDay()) {
463 dt.setTime(QTime(23, 59, 59));
464 }
465 setEndDateTime(dt);
466}
467
468void Recurrence::setEndDateTime(const KDateTime &dateTime)
469{
470 if (d->mRecurReadOnly) {
471 return;
472 }
473 RecurrenceRule *rrule = defaultRRule(true);
474 if (!rrule) {
475 return;
476 }
477 rrule->setEndDt(dateTime);
478 updated();
479}
480
481int Recurrence::duration() const
482{
483 RecurrenceRule *rrule = defaultRRuleConst();
484 return rrule ? rrule->duration() : 0;
485}
486
487int Recurrence::durationTo(const KDateTime &datetime) const
488{
489 // Emulate old behavior: This is just an interface to the first rule!
490 RecurrenceRule *rrule = defaultRRuleConst();
491 return rrule ? rrule->durationTo(datetime) : 0;
492}
493
494int Recurrence::durationTo(const QDate &date) const
495{
496 return durationTo(KDateTime(date, QTime(23, 59, 59), d->mStartDateTime.timeSpec()));
497}
498
499void Recurrence::setDuration(int duration)
500{
501 if (d->mRecurReadOnly) {
502 return;
503 }
504
505 RecurrenceRule *rrule = defaultRRule(true);
506 if (!rrule) {
507 return;
508 }
509 rrule->setDuration(duration);
510 updated();
511}
512
513void Recurrence::shiftTimes(const KDateTime::Spec &oldSpec, const KDateTime::Spec &newSpec)
514{
515 if (d->mRecurReadOnly) {
516 return;
517 }
518
519 d->mStartDateTime = d->mStartDateTime.toTimeSpec(oldSpec);
520 d->mStartDateTime.setTimeSpec(newSpec);
521
522 int i, end;
523 for (i = 0, end = d->mRDateTimes.count(); i < end; ++i) {
524 d->mRDateTimes[i] = d->mRDateTimes[i].toTimeSpec(oldSpec);
525 d->mRDateTimes[i].setTimeSpec(newSpec);
526 }
527 for (i = 0, end = d->mExDateTimes.count(); i < end; ++i) {
528 d->mExDateTimes[i] = d->mExDateTimes[i].toTimeSpec(oldSpec);
529 d->mExDateTimes[i].setTimeSpec(newSpec);
530 }
531 for (i = 0, end = d->mRRules.count(); i < end; ++i) {
532 d->mRRules[i]->shiftTimes(oldSpec, newSpec);
533 }
534 for (i = 0, end = d->mExRules.count(); i < end; ++i) {
535 d->mExRules[i]->shiftTimes(oldSpec, newSpec);
536 }
537}
538
539void Recurrence::unsetRecurs()
540{
541 if (d->mRecurReadOnly) {
542 return;
543 }
544 qDeleteAll(d->mRRules);
545 d->mRRules.clear();
546 updated();
547}
548
549void Recurrence::clear()
550{
551 if (d->mRecurReadOnly) {
552 return;
553 }
554 qDeleteAll(d->mRRules);
555 d->mRRules.clear();
556 qDeleteAll(d->mExRules);
557 d->mExRules.clear();
558 d->mRDates.clear();
559 d->mRDateTimes.clear();
560 d->mExDates.clear();
561 d->mExDateTimes.clear();
562 d->mCachedType = rMax;
563 updated();
564}
565
566void Recurrence::setRecurReadOnly(bool readOnly)
567{
568 d->mRecurReadOnly = readOnly;
569}
570
571bool Recurrence::recurReadOnly() const
572{
573 return d->mRecurReadOnly;
574}
575
576QDate Recurrence::startDate() const
577{
578 return d->mStartDateTime.date();
579}
580
581void Recurrence::setStartDateTime(const KDateTime &start)
582{
583 if (d->mRecurReadOnly) {
584 return;
585 }
586 d->mStartDateTime = start;
587 setAllDay(start.isDateOnly()); // set all RRULEs and EXRULEs
588
589 int i, end;
590 for (i = 0, end = d->mRRules.count(); i < end; ++i) {
591 d->mRRules[i]->setStartDt(start);
592 }
593 for (i = 0, end = d->mExRules.count(); i < end; ++i) {
594 d->mExRules[i]->setStartDt(start);
595 }
596 updated();
597}
598
599int Recurrence::frequency() const
600{
601 RecurrenceRule *rrule = defaultRRuleConst();
602 return rrule ? rrule->frequency() : 0;
603}
604
605// Emulate the old behaviour. Make this methods just an interface to the
606// first rrule
607void Recurrence::setFrequency(int freq)
608{
609 if (d->mRecurReadOnly || freq <= 0) {
610 return;
611 }
612
613 RecurrenceRule *rrule = defaultRRule(true);
614 if (rrule) {
615 rrule->setFrequency(freq);
616 }
617 updated();
618}
619
620// WEEKLY
621
622int Recurrence::weekStart() const
623{
624 RecurrenceRule *rrule = defaultRRuleConst();
625 return rrule ? rrule->weekStart() : 1;
626}
627
628// Emulate the old behavior
629QBitArray Recurrence::days() const
630{
631 QBitArray days(7);
632 days.fill(0);
633 RecurrenceRule *rrule = defaultRRuleConst();
634 if (rrule) {
635 QList<RecurrenceRule::WDayPos> bydays = rrule->byDays();
636 for (int i = 0; i < bydays.size(); ++i) {
637 if (bydays.at(i).pos() == 0) {
638 days.setBit(bydays.at(i).day() - 1);
639 }
640 }
641 }
642 return days;
643}
644
645// MONTHLY
646
647// Emulate the old behavior
648QList<int> Recurrence::monthDays() const
649{
650 RecurrenceRule *rrule = defaultRRuleConst();
651 if (rrule) {
652 return rrule->byMonthDays();
653 } else {
654 return QList<int>();
655 }
656}
657
658// Emulate the old behavior
659QList<RecurrenceRule::WDayPos> Recurrence::monthPositions() const
660{
661 RecurrenceRule *rrule = defaultRRuleConst();
662 return rrule ? rrule->byDays() : QList<RecurrenceRule::WDayPos>();
663}
664
665// YEARLY
666
667QList<int> Recurrence::yearDays() const
668{
669 RecurrenceRule *rrule = defaultRRuleConst();
670 return rrule ? rrule->byYearDays() : QList<int>();
671}
672
673QList<int> Recurrence::yearDates() const
674{
675 return monthDays();
676}
677
678QList<int> Recurrence::yearMonths() const
679{
680 RecurrenceRule *rrule = defaultRRuleConst();
681 return rrule ? rrule->byMonths() : QList<int>();
682}
683
684QList<RecurrenceRule::WDayPos> Recurrence::yearPositions() const
685{
686 return monthPositions();
687}
688
689RecurrenceRule *Recurrence::setNewRecurrenceType(RecurrenceRule::PeriodType type, int freq)
690{
691 if (d->mRecurReadOnly || freq <= 0) {
692 return 0;
693 }
694
695 qDeleteAll(d->mRRules);
696 d->mRRules.clear();
697 updated();
698 RecurrenceRule *rrule = defaultRRule(true);
699 if (!rrule) {
700 return 0;
701 }
702 rrule->setRecurrenceType(type);
703 rrule->setFrequency(freq);
704 rrule->setDuration(-1);
705 return rrule;
706}
707
708void Recurrence::setMinutely(int _rFreq)
709{
710 if (setNewRecurrenceType(RecurrenceRule::rMinutely, _rFreq)) {
711 updated();
712 }
713}
714
715void Recurrence::setHourly(int _rFreq)
716{
717 if (setNewRecurrenceType(RecurrenceRule::rHourly, _rFreq)) {
718 updated();
719 }
720}
721
722void Recurrence::setDaily(int _rFreq)
723{
724 if (setNewRecurrenceType(RecurrenceRule::rDaily, _rFreq)) {
725 updated();
726 }
727}
728
729void Recurrence::setWeekly(int freq, int weekStart)
730{
731 RecurrenceRule *rrule = setNewRecurrenceType(RecurrenceRule::rWeekly, freq);
732 if (!rrule) {
733 return;
734 }
735 rrule->setWeekStart(weekStart);
736 updated();
737}
738
739void Recurrence::setWeekly(int freq, const QBitArray &days, int weekStart)
740{
741 setWeekly(freq, weekStart);
742 addMonthlyPos(0, days);
743}
744
745void Recurrence::addWeeklyDays(const QBitArray &days)
746{
747 addMonthlyPos(0, days);
748}
749
750void Recurrence::setMonthly(int freq)
751{
752 if (setNewRecurrenceType(RecurrenceRule::rMonthly, freq)) {
753 updated();
754 }
755}
756
757void Recurrence::addMonthlyPos(short pos, const QBitArray &days)
758{
759 // Allow 53 for yearly!
760 if (d->mRecurReadOnly || pos > 53 || pos < -53) {
761 return;
762 }
763
764 RecurrenceRule *rrule = defaultRRule(false);
765 if (!rrule) {
766 return;
767 }
768 bool changed = false;
769 QList<RecurrenceRule::WDayPos> positions = rrule->byDays();
770
771 for (int i = 0; i < 7; ++i) {
772 if (days.testBit(i)) {
773 RecurrenceRule::WDayPos p(pos, i + 1);
774 if (!positions.contains(p)) {
775 changed = true;
776 positions.append(p);
777 }
778 }
779 }
780 if (changed) {
781 rrule->setByDays(positions);
782 updated();
783 }
784}
785
786void Recurrence::addMonthlyPos(short pos, ushort day)
787{
788 // Allow 53 for yearly!
789 if (d->mRecurReadOnly || pos > 53 || pos < -53) {
790 return;
791 }
792
793 RecurrenceRule *rrule = defaultRRule(false);
794 if (!rrule) {
795 return;
796 }
797 QList<RecurrenceRule::WDayPos> positions = rrule->byDays();
798
799 RecurrenceRule::WDayPos p(pos, day);
800 if (!positions.contains(p)) {
801 positions.append(p);
802 rrule->setByDays(positions);
803 updated();
804 }
805}
806
807void Recurrence::addMonthlyDate(short day)
808{
809 if (d->mRecurReadOnly || day > 31 || day < -31) {
810 return;
811 }
812
813 RecurrenceRule *rrule = defaultRRule(true);
814 if (!rrule) {
815 return;
816 }
817
818 QList<int> monthDays = rrule->byMonthDays();
819 if (!monthDays.contains(day)) {
820 monthDays.append(day);
821 rrule->setByMonthDays(monthDays);
822 updated();
823 }
824}
825
826void Recurrence::setYearly(int freq)
827{
828 if (setNewRecurrenceType(RecurrenceRule::rYearly, freq)) {
829 updated();
830 }
831}
832
833// Daynumber within year
834void Recurrence::addYearlyDay(int day)
835{
836 RecurrenceRule *rrule = defaultRRule(false); // It must already exist!
837 if (!rrule) {
838 return;
839 }
840
841 QList<int> days = rrule->byYearDays();
842 if (!days.contains(day)) {
843 days << day;
844 rrule->setByYearDays(days);
845 updated();
846 }
847}
848
849// day part of date within year
850void Recurrence::addYearlyDate(int day)
851{
852 addMonthlyDate(day);
853}
854
855// day part of date within year, given as position (n-th weekday)
856void Recurrence::addYearlyPos(short pos, const QBitArray &days)
857{
858 addMonthlyPos(pos, days);
859}
860
861// month part of date within year
862void Recurrence::addYearlyMonth(short month)
863{
864 if (d->mRecurReadOnly || month < 1 || month > 12) {
865 return;
866 }
867
868 RecurrenceRule *rrule = defaultRRule(false);
869 if (!rrule) {
870 return;
871 }
872
873 QList<int> months = rrule->byMonths();
874 if (!months.contains(month)) {
875 months << month;
876 rrule->setByMonths(months);
877 updated();
878 }
879}
880
881TimeList Recurrence::recurTimesOn(const QDate &date, const KDateTime::Spec &timeSpec) const
882{
883// kDebug() << "recurTimesOn(" << date << ")";
884 int i, end;
885 TimeList times;
886
887 // The whole day is excepted
888 if (d->mExDates.containsSorted(date)) {
889 return times;
890 }
891
892 // EXRULE takes precedence over RDATE entries, so for all-day events,
893 // a matching excule also excludes the whole day automatically
894 if (allDay()) {
895 for (i = 0, end = d->mExRules.count(); i < end; ++i) {
896 if (d->mExRules[i]->recursOn(date, timeSpec)) {
897 return times;
898 }
899 }
900 }
901
902 KDateTime dt = startDateTime().toTimeSpec(timeSpec);
903 if (dt.date() == date) {
904 times << dt.time();
905 }
906
907 bool foundDate = false;
908 for (i = 0, end = d->mRDateTimes.count(); i < end; ++i) {
909 dt = d->mRDateTimes[i].toTimeSpec(timeSpec);
910 if (dt.date() == date) {
911 times << dt.time();
912 foundDate = true;
913 } else if (foundDate) {
914 break; // <= Assume that the rdatetime list is sorted
915 }
916 }
917 for (i = 0, end = d->mRRules.count(); i < end; ++i) {
918 times += d->mRRules[i]->recurTimesOn(date, timeSpec);
919 }
920 times.sortUnique();
921
922 foundDate = false;
923 TimeList extimes;
924 for (i = 0, end = d->mExDateTimes.count(); i < end; ++i) {
925 dt = d->mExDateTimes[i].toTimeSpec(timeSpec);
926 if (dt.date() == date) {
927 extimes << dt.time();
928 foundDate = true;
929 } else if (foundDate) {
930 break;
931 }
932 }
933 if (!allDay()) { // we have already checked all-day times above
934 for (i = 0, end = d->mExRules.count(); i < end; ++i) {
935 extimes += d->mExRules[i]->recurTimesOn(date, timeSpec);
936 }
937 }
938 extimes.sortUnique();
939
940 int st = 0;
941 for (i = 0, end = extimes.count(); i < end; ++i) {
942 int j = times.removeSorted(extimes[i], st);
943 if (j >= 0) {
944 st = j;
945 }
946 }
947 return times;
948}
949
950DateTimeList Recurrence::timesInInterval(const KDateTime &start, const KDateTime &end) const
951{
952 int i, count;
953 DateTimeList times;
954 for (i = 0, count = d->mRRules.count(); i < count; ++i) {
955 times += d->mRRules[i]->timesInInterval(start, end);
956 }
957
958 // add rdatetimes that fit in the interval
959 for (i = 0, count = d->mRDateTimes.count(); i < count; ++i) {
960 if (d->mRDateTimes[i] >= start && d->mRDateTimes[i] <= end) {
961 times += d->mRDateTimes[i];
962 }
963 }
964
965 // add rdates that fit in the interval
966 KDateTime kdt(d->mStartDateTime);
967 for (i = 0, count = d->mRDates.count(); i < count; ++i) {
968 kdt.setDate(d->mRDates[i]);
969 if (kdt >= start && kdt <= end) {
970 times += kdt;
971 }
972 }
973
974 // Recurrence::timesInInterval(...) doesn't explicitly add mStartDateTime to the list
975 // of times to be returned. It calls mRRules[i]->timesInInterval(...) which include
976 // mStartDateTime.
977 // So, If we have rdates/rdatetimes but don't have any rrule we must explicitly
978 // add mStartDateTime to the list, otherwise we won't see the first occurrence.
979 if ((!d->mRDates.isEmpty() || !d->mRDateTimes.isEmpty()) &&
980 d->mRRules.isEmpty() &&
981 start <= d->mStartDateTime &&
982 end >= d->mStartDateTime) {
983 times += d->mStartDateTime;
984 }
985
986 times.sortUnique();
987
988 // Remove excluded times
989 int idt = 0;
990 int enddt = times.count();
991 for (i = 0, count = d->mExDates.count(); i < count && idt < enddt; ++i) {
992 while (idt < enddt && times[idt].date() < d->mExDates[i]) {
993 ++idt;
994 }
995 while (idt < enddt && times[idt].date() == d->mExDates[i]) {
996 times.removeAt(idt);
997 --enddt;
998 }
999 }
1000 DateTimeList extimes;
1001 for (i = 0, count = d->mExRules.count(); i < count; ++i) {
1002 extimes += d->mExRules[i]->timesInInterval(start, end);
1003 }
1004 extimes += d->mExDateTimes;
1005 extimes.sortUnique();
1006
1007 int st = 0;
1008 for (i = 0, count = extimes.count(); i < count; ++i) {
1009 int j = times.removeSorted(extimes[i], st);
1010 if (j >= 0) {
1011 st = j;
1012 }
1013 }
1014
1015 return times;
1016}
1017
1018KDateTime Recurrence::getNextDateTime(const KDateTime &preDateTime) const
1019{
1020 KDateTime nextDT = preDateTime;
1021 // prevent infinite loops, e.g. when an exrule extinguishes an rrule (e.g.
1022 // the exrule is identical to the rrule). If an occurrence is found, break
1023 // out of the loop by returning that KDateTime
1024// TODO_Recurrence: Is a loop counter of 1000 really okay? I mean for secondly
1025// recurrence, an exdate might exclude more than 1000 intervals!
1026 int loop = 0;
1027 while (loop < 1000) {
1028 // Outline of the algo:
1029 // 1) Find the next date/time after preDateTime when the event could recur
1030 // 1.0) Add the start date if it's after preDateTime
1031 // 1.1) Use the next occurrence from the explicit RDATE lists
1032 // 1.2) Add the next recurrence for each of the RRULEs
1033 // 2) Take the earliest recurrence of these = KDateTime nextDT
1034 // 3) If that date/time is not excluded, either explicitly by an EXDATE or
1035 // by an EXRULE, return nextDT as the next date/time of the recurrence
1036 // 4) If it's excluded, start all at 1), but starting at nextDT (instead
1037 // of preDateTime). Loop at most 1000 times.
1038 ++loop;
1039 // First, get the next recurrence from the RDate lists
1040 DateTimeList dates;
1041 if (nextDT < startDateTime()) {
1042 dates << startDateTime();
1043 }
1044
1045 int end;
1046 // Assume that the rdatetime list is sorted
1047 int i = d->mRDateTimes.findGT(nextDT);
1048 if (i >= 0) {
1049 dates << d->mRDateTimes[i];
1050 }
1051
1052 KDateTime kdt(startDateTime());
1053 for (i = 0, end = d->mRDates.count(); i < end; ++i) {
1054 kdt.setDate(d->mRDates[i]);
1055 if (kdt > nextDT) {
1056 dates << kdt;
1057 break;
1058 }
1059 }
1060
1061 // Add the next occurrences from all RRULEs.
1062 for (i = 0, end = d->mRRules.count(); i < end; ++i) {
1063 KDateTime dt = d->mRRules[i]->getNextDate(nextDT);
1064 if (dt.isValid()) {
1065 dates << dt;
1066 }
1067 }
1068
1069 // Take the first of these (all others can't be used later on)
1070 dates.sortUnique();
1071 if (dates.isEmpty()) {
1072 return KDateTime();
1073 }
1074 nextDT = dates.first();
1075
1076 // Check if that date/time is excluded explicitly or by an exrule:
1077 if (!d->mExDates.containsSorted(nextDT.date()) &&
1078 !d->mExDateTimes.containsSorted(nextDT)) {
1079 bool allowed = true;
1080 for (i = 0, end = d->mExRules.count(); i < end; ++i) {
1081 allowed = allowed && !(d->mExRules[i]->recursAt(nextDT));
1082 }
1083 if (allowed) {
1084 return nextDT;
1085 }
1086 }
1087 }
1088
1089 // Couldn't find a valid occurrences in 1000 loops, something is wrong!
1090 return KDateTime();
1091}
1092
1093KDateTime Recurrence::getPreviousDateTime(const KDateTime &afterDateTime) const
1094{
1095 KDateTime prevDT = afterDateTime;
1096 // prevent infinite loops, e.g. when an exrule extinguishes an rrule (e.g.
1097 // the exrule is identical to the rrule). If an occurrence is found, break
1098 // out of the loop by returning that KDateTime
1099 int loop = 0;
1100 while (loop < 1000) {
1101 // Outline of the algo:
1102 // 1) Find the next date/time after preDateTime when the event could recur
1103 // 1.1) Use the next occurrence from the explicit RDATE lists
1104 // 1.2) Add the next recurrence for each of the RRULEs
1105 // 2) Take the earliest recurrence of these = KDateTime nextDT
1106 // 3) If that date/time is not excluded, either explicitly by an EXDATE or
1107 // by an EXRULE, return nextDT as the next date/time of the recurrence
1108 // 4) If it's excluded, start all at 1), but starting at nextDT (instead
1109 // of preDateTime). Loop at most 1000 times.
1110 ++loop;
1111 // First, get the next recurrence from the RDate lists
1112 DateTimeList dates;
1113 if (prevDT > startDateTime()) {
1114 dates << startDateTime();
1115 }
1116
1117 int i = d->mRDateTimes.findLT(prevDT);
1118 if (i >= 0) {
1119 dates << d->mRDateTimes[i];
1120 }
1121
1122 KDateTime kdt(startDateTime());
1123 for (i = d->mRDates.count(); --i >= 0;) {
1124 kdt.setDate(d->mRDates[i]);
1125 if (kdt < prevDT) {
1126 dates << kdt;
1127 break;
1128 }
1129 }
1130
1131 // Add the previous occurrences from all RRULEs.
1132 int end;
1133 for (i = 0, end = d->mRRules.count(); i < end; ++i) {
1134 KDateTime dt = d->mRRules[i]->getPreviousDate(prevDT);
1135 if (dt.isValid()) {
1136 dates << dt;
1137 }
1138 }
1139
1140 // Take the last of these (all others can't be used later on)
1141 dates.sortUnique();
1142 if (dates.isEmpty()) {
1143 return KDateTime();
1144 }
1145 prevDT = dates.last();
1146
1147 // Check if that date/time is excluded explicitly or by an exrule:
1148 if (!d->mExDates.containsSorted(prevDT.date()) &&
1149 !d->mExDateTimes.containsSorted(prevDT)) {
1150 bool allowed = true;
1151 for (i = 0, end = d->mExRules.count(); i < end; ++i) {
1152 allowed = allowed && !(d->mExRules[i]->recursAt(prevDT));
1153 }
1154 if (allowed) {
1155 return prevDT;
1156 }
1157 }
1158 }
1159
1160 // Couldn't find a valid occurrences in 1000 loops, something is wrong!
1161 return KDateTime();
1162}
1163
1164/***************************** PROTECTED FUNCTIONS ***************************/
1165
1166RecurrenceRule::List Recurrence::rRules() const
1167{
1168 return d->mRRules;
1169}
1170
1171void Recurrence::addRRule(RecurrenceRule *rrule)
1172{
1173 if (d->mRecurReadOnly || !rrule) {
1174 return;
1175 }
1176
1177 rrule->setAllDay(d->mAllDay);
1178 d->mRRules.append(rrule);
1179 rrule->addObserver(this);
1180 updated();
1181}
1182
1183void Recurrence::removeRRule(RecurrenceRule *rrule)
1184{
1185 if (d->mRecurReadOnly) {
1186 return;
1187 }
1188
1189 d->mRRules.removeAll(rrule);
1190 rrule->removeObserver(this);
1191 updated();
1192}
1193
1194void Recurrence::deleteRRule(RecurrenceRule *rrule)
1195{
1196 if (d->mRecurReadOnly) {
1197 return;
1198 }
1199
1200 d->mRRules.removeAll(rrule);
1201 delete rrule;
1202 updated();
1203}
1204
1205RecurrenceRule::List Recurrence::exRules() const
1206{
1207 return d->mExRules;
1208}
1209
1210void Recurrence::addExRule(RecurrenceRule *exrule)
1211{
1212 if (d->mRecurReadOnly || !exrule) {
1213 return;
1214 }
1215
1216 exrule->setAllDay(d->mAllDay);
1217 d->mExRules.append(exrule);
1218 exrule->addObserver(this);
1219 updated();
1220}
1221
1222void Recurrence::removeExRule(RecurrenceRule *exrule)
1223{
1224 if (d->mRecurReadOnly) {
1225 return;
1226 }
1227
1228 d->mExRules.removeAll(exrule);
1229 exrule->removeObserver(this);
1230 updated();
1231}
1232
1233void Recurrence::deleteExRule(RecurrenceRule *exrule)
1234{
1235 if (d->mRecurReadOnly) {
1236 return;
1237 }
1238
1239 d->mExRules.removeAll(exrule);
1240 delete exrule;
1241 updated();
1242}
1243
1244DateTimeList Recurrence::rDateTimes() const
1245{
1246 return d->mRDateTimes;
1247}
1248
1249void Recurrence::setRDateTimes(const DateTimeList &rdates)
1250{
1251 if (d->mRecurReadOnly) {
1252 return;
1253 }
1254
1255 d->mRDateTimes = rdates;
1256 d->mRDateTimes.sortUnique();
1257 updated();
1258}
1259
1260void Recurrence::addRDateTime(const KDateTime &rdate)
1261{
1262 if (d->mRecurReadOnly) {
1263 return;
1264 }
1265
1266 d->mRDateTimes.insertSorted(rdate);
1267 updated();
1268}
1269
1270DateList Recurrence::rDates() const
1271{
1272 return d->mRDates;
1273}
1274
1275void Recurrence::setRDates(const DateList &rdates)
1276{
1277 if (d->mRecurReadOnly) {
1278 return;
1279 }
1280
1281 d->mRDates = rdates;
1282 d->mRDates.sortUnique();
1283 updated();
1284}
1285
1286void Recurrence::addRDate(const QDate &rdate)
1287{
1288 if (d->mRecurReadOnly) {
1289 return;
1290 }
1291
1292 d->mRDates.insertSorted(rdate);
1293 updated();
1294}
1295
1296DateTimeList Recurrence::exDateTimes() const
1297{
1298 return d->mExDateTimes;
1299}
1300
1301void Recurrence::setExDateTimes(const DateTimeList &exdates)
1302{
1303 if (d->mRecurReadOnly) {
1304 return;
1305 }
1306
1307 d->mExDateTimes = exdates;
1308 d->mExDateTimes.sortUnique();
1309}
1310
1311void Recurrence::addExDateTime(const KDateTime &exdate)
1312{
1313 if (d->mRecurReadOnly) {
1314 return;
1315 }
1316
1317 d->mExDateTimes.insertSorted(exdate);
1318 updated();
1319}
1320
1321DateList Recurrence::exDates() const
1322{
1323 return d->mExDates;
1324}
1325
1326void Recurrence::setExDates(const DateList &exdates)
1327{
1328 if (d->mRecurReadOnly) {
1329 return;
1330 }
1331
1332 d->mExDates = exdates;
1333 d->mExDates.sortUnique();
1334 updated();
1335}
1336
1337void Recurrence::addExDate(const QDate &exdate)
1338{
1339 if (d->mRecurReadOnly) {
1340 return;
1341 }
1342
1343 d->mExDates.insertSorted(exdate);
1344 updated();
1345}
1346
1347void Recurrence::recurrenceChanged(RecurrenceRule *)
1348{
1349 updated();
1350}
1351
1352// %%%%%%%%%%%%%%%%%% end:Recurrencerule %%%%%%%%%%%%%%%%%%
1353
1354void Recurrence::dump() const
1355{
1356 kDebug();
1357
1358 int i;
1359 int count = d->mRRules.count();
1360 kDebug() << " -)" << count << "RRULEs:";
1361 for (i = 0; i < count; ++i) {
1362 kDebug() << " -) RecurrenceRule: ";
1363 d->mRRules[i]->dump();
1364 }
1365 count = d->mExRules.count();
1366 kDebug() << " -)" << count << "EXRULEs:";
1367 for (i = 0; i < count; ++i) {
1368 kDebug() << " -) ExceptionRule :";
1369 d->mExRules[i]->dump();
1370 }
1371
1372 count = d->mRDates.count();
1373 kDebug() << endl << " -)" << count << "Recurrence Dates:";
1374 for (i = 0; i < count; ++i) {
1375 kDebug() << " " << d->mRDates[i];
1376 }
1377 count = d->mRDateTimes.count();
1378 kDebug() << endl << " -)" << count << "Recurrence Date/Times:";
1379 for (i = 0; i < count; ++i) {
1380 kDebug() << " " << d->mRDateTimes[i].dateTime();
1381 }
1382 count = d->mExDates.count();
1383 kDebug() << endl << " -)" << count << "Exceptions Dates:";
1384 for (i = 0; i < count; ++i) {
1385 kDebug() << " " << d->mExDates[i];
1386 }
1387 count = d->mExDateTimes.count();
1388 kDebug() << endl << " -)" << count << "Exception Date/Times:";
1389 for (i = 0; i < count; ++i) {
1390 kDebug() << " " << d->mExDateTimes[i].dateTime();
1391 }
1392}
1393
1394Recurrence::RecurrenceObserver::~RecurrenceObserver()
1395{
1396}
1397
1398KCALCORE_EXPORT QDataStream& KCalCore::operator<<(QDataStream &out, KCalCore::Recurrence *r)
1399{
1400 if (!r)
1401 return out;
1402
1403 out << r->d->mRDateTimes << r->d->mExDateTimes
1404 << r->d->mRDates << r->d->mStartDateTime << r->d->mCachedType
1405 << r->d->mAllDay << r->d->mRecurReadOnly << r->d->mExDates
1406 << r->d->mExRules.count() << r->d->mRRules.count();
1407
1408 foreach(RecurrenceRule *rule, r->d->mExRules) {
1409 out << rule;
1410 }
1411
1412 foreach(RecurrenceRule *rule, r->d->mRRules) {
1413 out << rule;
1414 }
1415
1416 return out;
1417}
1418
1419
1420KCALCORE_EXPORT QDataStream& KCalCore::operator>>(QDataStream &in, KCalCore::Recurrence *r)
1421{
1422 if (!r)
1423 return in;
1424
1425 int rruleCount, exruleCount;
1426
1427 in >> r->d->mRDateTimes >> r->d->mExDateTimes
1428 >> r->d->mRDates >> r->d->mStartDateTime >> r->d->mCachedType
1429 >> r->d->mAllDay >> r->d->mRecurReadOnly >> r->d->mExDates
1430 >> exruleCount >> rruleCount;
1431
1432 r->d->mExRules.clear();
1433 r->d->mRRules.clear();
1434
1435 for (int i=0; i<exruleCount; ++i) {
1436 RecurrenceRule *rule = new RecurrenceRule();
1437 rule->addObserver(r);
1438 in >> rule;
1439 r->d->mExRules.append(rule);
1440 }
1441
1442 for (int i=0; i<rruleCount; ++i) {
1443 RecurrenceRule *rule = new RecurrenceRule();
1444 rule->addObserver(r);
1445 in >> rule;
1446 r->d->mRRules.append(rule);
1447 }
1448
1449 return in;
1450}
1451