• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.11.5 API Reference
  • KDE Home
  • Contact Us
 

KCalCore Library

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

KDE's Doxygen guidelines are available online.

KCalCore Library

Skip menu "KCalCore Library"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Related Pages

kdepimlibs-4.11.5 API Reference

Skip menu "kdepimlibs-4.11.5 API Reference"
  • akonadi
  •   contact
  •   kmime
  •   socialutils
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal