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

KAlarm Library

  • kalarmcal
karecurrence.cpp
1 /*
2  * karecurrence.cpp - recurrence with special yearly February 29th handling
3  * This file is part of kalarmcal library, which provides access to KAlarm
4  * calendar data.
5  * Copyright © 2005-2012 by David Jarvie <djarvie@kde.org>
6  *
7  * This library is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU Library General Public License as published
9  * by the Free Software Foundation; either version 2 of the License, or (at
10  * your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
15  * 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 the
19  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20  * MA 02110-1301, USA.
21  */
22 
23 #include "karecurrence.h"
24 
25 #ifndef KALARMCAL_USE_KRESOURCES
26 #include <kcalcore/recurrence.h>
27 #include <kcalcore/icalformat.h>
28 #else
29 #include <kcal/recurrence.h>
30 #include <kcal/icalformat.h>
31 #endif
32 #include <kglobal.h>
33 #include <klocale.h>
34 #include <kdebug.h>
35 
36 #include <QBitArray>
37 
38 
39 #ifndef KALARMCAL_USE_KRESOURCES
40 using namespace KCalCore;
41 #else
42 using namespace KCal;
43 #endif
44 
45 namespace KAlarmCal
46 {
47 
48 class Recurrence_p : public Recurrence
49 {
50  public:
51  using Recurrence::setNewRecurrenceType;
52  Recurrence_p() : Recurrence() {}
53  Recurrence_p(const Recurrence& r) : Recurrence(r) {}
54  Recurrence_p(const Recurrence_p& r) : Recurrence(r) {}
55 };
56 
57 class KARecurrence::Private
58 {
59  public:
60  Private()
61  : mFeb29Type(Feb29_None), mCachedType(-1) {}
62  explicit Private(const Recurrence& r)
63  : mRecurrence(r), mFeb29Type(Feb29_None), mCachedType(-1) {}
64  void clear()
65  {
66  mRecurrence.clear();
67  mFeb29Type = Feb29_None;
68  mCachedType = -1;
69  }
70  bool set(Type, int freq, int count, int f29, const KDateTime& start, const KDateTime& end);
71  bool init(RecurrenceRule::PeriodType, int freq, int count, int feb29Type, const KDateTime& start, const KDateTime& end);
72  void fix();
73  void writeRecurrence(const KARecurrence* q, Recurrence& recur) const;
74  KDateTime endDateTime() const;
75  int combineDurations(const RecurrenceRule*, const RecurrenceRule*, QDate& end) const;
76 
77  static Feb29Type mDefaultFeb29;
78  Recurrence_p mRecurrence;
79  Feb29Type mFeb29Type; // yearly recurrence on Feb 29th (leap years) / Mar 1st (non-leap years)
80  mutable int mCachedType;
81 };
82 
83 
84 /*=============================================================================
85 = Class KARecurrence
86 = The purpose of this class is to represent the restricted range of recurrence
87 = types which are handled by KAlarm, and to translate between these and the
88 = libkcal Recurrence class. In particular, it handles yearly recurrences on
89 = 29th February specially:
90 =
91 = KARecurrence allows annual 29th February recurrences to fall on 28th
92 = February or 1st March, or not at all, in non-leap years. It allows such
93 = 29th February recurrences to be combined with the 29th of other months in
94 = a simple way, represented simply as the 29th of multiple months including
95 = February. For storage in the libkcal calendar, the 29th day of the month
96 = recurrence for other months is combined with a last-day-of-February or a
97 = 60th-day-of-the-year recurrence rule, thereby conforming to RFC2445.
98 =============================================================================*/
99 
100 
101 KARecurrence::Feb29Type KARecurrence::Private::mDefaultFeb29 = KARecurrence::Feb29_None;
102 
103 
104 KARecurrence::KARecurrence()
105  : d(new Private)
106 { }
107 
108 KARecurrence::KARecurrence(const Recurrence& r)
109  : d(new Private(r))
110 {
111  fix();
112 }
113 
114 KARecurrence::KARecurrence(const KARecurrence& r)
115  : d(new Private(*r.d))
116 { }
117 
118 KARecurrence::~KARecurrence()
119 {
120  delete d;
121 }
122 
123 KARecurrence& KARecurrence::operator=(const KARecurrence& r)
124 {
125  if (&r != this)
126  *d = *r.d;
127  return *this;
128 }
129 
130 bool KARecurrence::operator==(const KARecurrence& r) const
131 {
132  return d->mRecurrence == r.d->mRecurrence
133  && d->mFeb29Type == r.d->mFeb29Type;
134 }
135 
136 KARecurrence::Feb29Type KARecurrence::feb29Type() const
137 {
138  return d->mFeb29Type;
139 }
140 
141 KARecurrence::Feb29Type KARecurrence::defaultFeb29Type()
142 {
143  return Private::mDefaultFeb29;
144 }
145 
146 void KARecurrence::setDefaultFeb29Type(Feb29Type t)
147 {
148  Private::mDefaultFeb29 = t;
149 }
150 
151 /******************************************************************************
152 * Set up a KARecurrence from recurrence parameters, using the start date to
153 * determine the recurrence day/month as appropriate.
154 * Only a restricted subset of recurrence types is allowed.
155 * Reply = true if successful.
156 */
157 bool KARecurrence::set(Type t, int freq, int count, const KDateTime& start, const KDateTime& end)
158 {
159  return d->set(t, freq, count, -1, start, end);
160 }
161 
162 bool KARecurrence::set(Type t, int freq, int count, const KDateTime& start, const KDateTime& end, Feb29Type f29)
163 {
164  return d->set(t, freq, count, f29, start, end);
165 }
166 
167 bool KARecurrence::Private::set(Type recurType, int freq, int count, int f29, const KDateTime& start, const KDateTime& end)
168 {
169  mCachedType = -1;
170  RecurrenceRule::PeriodType rrtype;
171  switch (recurType)
172  {
173  case MINUTELY: rrtype = RecurrenceRule::rMinutely; break;
174  case DAILY: rrtype = RecurrenceRule::rDaily; break;
175  case WEEKLY: rrtype = RecurrenceRule::rWeekly; break;
176  case MONTHLY_DAY: rrtype = RecurrenceRule::rMonthly; break;
177  case ANNUAL_DATE: rrtype = RecurrenceRule::rYearly; break;
178  case NO_RECUR: rrtype = RecurrenceRule::rNone; break;
179  default:
180  return false;
181  }
182  if (!init(rrtype, freq, count, f29, start, end))
183  return false;
184  switch (recurType)
185  {
186  case WEEKLY:
187  {
188  QBitArray days(7);
189  days.setBit(start.date().dayOfWeek() - 1);
190  mRecurrence.addWeeklyDays(days);
191  break;
192  }
193  case MONTHLY_DAY:
194  mRecurrence.addMonthlyDate(start.date().day());
195  break;
196  case ANNUAL_DATE:
197  mRecurrence.addYearlyDate(start.date().day());
198  mRecurrence.addYearlyMonth(start.date().month());
199  break;
200  default:
201  break;
202  }
203  return true;
204 }
205 
206 /******************************************************************************
207 * Initialise a KARecurrence from recurrence parameters.
208 * Reply = true if successful.
209 */
210 bool KARecurrence::init(RecurrenceRule::PeriodType t, int freq, int count, const KDateTime& start, const KDateTime& end)
211 {
212  return d->init(t, freq, count, -1, start, end);
213 }
214 
215 bool KARecurrence::init(RecurrenceRule::PeriodType t, int freq, int count, const KDateTime& start, const KDateTime& end, Feb29Type f29)
216 {
217  return d->init(t, freq, count, f29, start, end);
218 }
219 
220 bool KARecurrence::Private::init(RecurrenceRule::PeriodType recurType, int freq, int count, int f29, const KDateTime& start,
221  const KDateTime& end)
222 {
223  clear();
224  Feb29Type feb29Type = (f29 == -1) ? mDefaultFeb29 : static_cast<Feb29Type>(f29);
225  if (count < -1)
226  return false;
227  bool dateOnly = start.isDateOnly();
228  if (!count && ((!dateOnly && !end.isValid())
229  || (dateOnly && !end.date().isValid())))
230  return false;
231  switch (recurType)
232  {
233  case RecurrenceRule::rMinutely:
234  case RecurrenceRule::rDaily:
235  case RecurrenceRule::rWeekly:
236  case RecurrenceRule::rMonthly:
237  case RecurrenceRule::rYearly:
238  break;
239  case RecurrenceRule::rNone:
240  return true;
241  default:
242  return false;
243  }
244  mRecurrence.setNewRecurrenceType(recurType, freq);
245  if (count)
246  mRecurrence.setDuration(count);
247  else if (dateOnly)
248  mRecurrence.setEndDate(end.date());
249  else
250  mRecurrence.setEndDateTime(end);
251  KDateTime startdt = start;
252  if (recurType == RecurrenceRule::rYearly
253  && (feb29Type == Feb29_Feb28 || feb29Type == Feb29_Mar1))
254  {
255  int year = startdt.date().year();
256  if (!QDate::isLeapYear(year)
257  && startdt.date().dayOfYear() == (feb29Type == Feb29_Mar1 ? 60 : 59))
258  {
259  /* The event start date is February 28th or March 1st, but it
260  * is a recurrence on February 29th (recurring on February 28th
261  * or March 1st in non-leap years). Adjust the start date to
262  * be on February 29th in the last previous leap year.
263  * This is necessary because KARecurrence represents all types
264  * of 29th February recurrences by a simple 29th February.
265  */
266  while (!QDate::isLeapYear(--year)) ;
267  startdt.setDate(QDate(year, 2, 29));
268  }
269  mFeb29Type = feb29Type;
270  }
271  mRecurrence.setStartDateTime(startdt); // sets recurrence all-day if date-only
272  return true;
273 }
274 
275 /******************************************************************************
276 * Initialise the recurrence from an iCalendar RRULE string.
277 */
278 bool KARecurrence::set(const QString& icalRRULE)
279 {
280  static QString RRULE = QLatin1String("RRULE:");
281  d->clear();
282  if (icalRRULE.isEmpty())
283  return true;
284  ICalFormat format;
285  if (!format.fromString(d->mRecurrence.defaultRRule(true),
286  (icalRRULE.startsWith(RRULE) ? icalRRULE.mid(RRULE.length()) : icalRRULE)))
287  return false;
288  fix();
289  return true;
290 }
291 
292 void KARecurrence::clear()
293 {
294  d->clear();
295 }
296 
297 /******************************************************************************
298 * Must be called after presetting with a KCal::Recurrence, to convert the
299 * recurrence to KARecurrence types:
300 * - Convert hourly recurrences to minutely.
301 * - Remove all but the first day in yearly date recurrences.
302 * - Check for yearly recurrences falling on February 29th and adjust them as
303 * necessary. A 29th of the month rule can be combined with either a 60th day
304 * of the year rule or a last day of February rule.
305 */
306 void KARecurrence::fix()
307 {
308  d->fix();
309 }
310 
311 void KARecurrence::Private::fix()
312 {
313  mCachedType = -1;
314  mFeb29Type = Feb29_None;
315  int convert = 0;
316  int days[2] = { 0, 0 };
317  RecurrenceRule* rrules[2];
318  RecurrenceRule::List rrulelist = mRecurrence.rRules();
319  int rri = 0;
320  int rrend = rrulelist.count();
321  for (int i = 0; i < 2 && rri < rrend; ++i, ++rri)
322  {
323  RecurrenceRule* rrule = rrulelist[rri];
324  rrules[i] = rrule;
325  bool stop = true;
326  int rtype = mRecurrence.recurrenceType(rrule);
327  switch (rtype)
328  {
329  case Recurrence::rHourly:
330  // Convert an hourly recurrence to a minutely one
331  rrule->setRecurrenceType(RecurrenceRule::rMinutely);
332  rrule->setFrequency(rrule->frequency() * 60);
333  // fall through to rMinutely
334  case Recurrence::rMinutely:
335  case Recurrence::rDaily:
336  case Recurrence::rWeekly:
337  case Recurrence::rMonthlyDay:
338  case Recurrence::rMonthlyPos:
339  case Recurrence::rYearlyPos:
340  if (!convert)
341  ++rri; // remove all rules except the first
342  break;
343  case Recurrence::rOther:
344  if (dailyType(rrule))
345  { // it's a daily rule with BYDAYS
346  if (!convert)
347  ++rri; // remove all rules except the first
348  }
349  break;
350  case Recurrence::rYearlyDay:
351  {
352  // Ensure that the yearly day number is 60 (i.e. Feb 29th/Mar 1st)
353  if (convert)
354  {
355  // This is the second rule.
356  // Ensure that it can be combined with the first one.
357  if (days[0] != 29
358  || rrule->frequency() != rrules[0]->frequency()
359  || rrule->startDt() != rrules[0]->startDt())
360  break;
361  }
362  QList<int> ds = rrule->byYearDays();
363  if (!ds.isEmpty() && ds.first() == 60)
364  {
365  ++convert; // this rule needs to be converted
366  days[i] = 60;
367  stop = false;
368  break;
369  }
370  break; // not day 60, so remove this rule
371  }
372  case Recurrence::rYearlyMonth:
373  {
374  QList<int> ds = rrule->byMonthDays();
375  if (!ds.isEmpty())
376  {
377  int day = ds.first();
378  if (convert)
379  {
380  // This is the second rule.
381  // Ensure that it can be combined with the first one.
382  if (day == days[0] || (day == -1 && days[0] == 60)
383  || rrule->frequency() != rrules[0]->frequency()
384  || rrule->startDt() != rrules[0]->startDt())
385  break;
386  }
387  if (ds.count() > 1)
388  {
389  ds.clear(); // remove all but the first day
390  ds.append(day);
391  rrule->setByMonthDays(ds);
392  }
393  if (day == -1)
394  {
395  // Last day of the month - only combine if it's February
396  QList<int> months = rrule->byMonths();
397  if (months.count() != 1 || months.first() != 2)
398  day = 0;
399  }
400  if (day == 29 || day == -1)
401  {
402  ++convert; // this rule may need to be converted
403  days[i] = day;
404  stop = false;
405  break;
406  }
407  }
408  if (!convert)
409  ++rri;
410  break;
411  }
412  default:
413  break;
414  }
415  if (stop)
416  break;
417  }
418 
419  // Remove surplus rules
420  for ( ; rri < rrend; ++rri)
421  mRecurrence.deleteRRule(rrulelist[rri]);
422 
423  QDate end;
424  int count;
425  QList<int> months;
426  if (convert == 2)
427  {
428  // There are two yearly recurrence rules to combine into a February 29th recurrence.
429  // Combine the two recurrence rules into a single rYearlyMonth rule falling on Feb 29th.
430  // Find the duration of the two RRULEs combined, using the shorter of the two if they differ.
431  if (days[0] != 29)
432  {
433  // Swap the two rules so that the 29th rule is the first
434  RecurrenceRule* rr = rrules[0];
435  rrules[0] = rrules[1]; // the 29th rule
436  rrules[1] = rr;
437  int d = days[0];
438  days[0] = days[1];
439  days[1] = d; // the non-29th day
440  }
441  // If February is included in the 29th rule, remove it to avoid duplication
442  months = rrules[0]->byMonths();
443  if (months.removeAll(2))
444  rrules[0]->setByMonths(months);
445 
446  count = combineDurations(rrules[0], rrules[1], end);
447  mFeb29Type = (days[1] == 60) ? Feb29_Mar1 : Feb29_Feb28;
448  }
449  else if (convert == 1 && days[0] == 60)
450  {
451  // There is a single 60th day of the year rule.
452  // Convert it to a February 29th recurrence.
453  count = mRecurrence.duration();
454  if (!count)
455  end = mRecurrence.endDate();
456  mFeb29Type = Feb29_Mar1;
457  }
458  else
459  return;
460 
461  // Create the new February 29th recurrence
462  mRecurrence.setNewRecurrenceType(RecurrenceRule::rYearly, mRecurrence.frequency());
463  RecurrenceRule* rrule = mRecurrence.defaultRRule();
464  months.append(2);
465  rrule->setByMonths(months);
466  QList<int> ds;
467  ds.append(29);
468  rrule->setByMonthDays(ds);
469  if (count)
470  mRecurrence.setDuration(count);
471  else
472  mRecurrence.setEndDate(end);
473 }
474 
475 /******************************************************************************
476 * Initialise a KCal::Recurrence to be the same as this instance.
477 * Additional recurrence rules are created as necessary if it recurs on Feb 29th.
478 */
479 void KARecurrence::writeRecurrence(Recurrence& recur) const
480 {
481  d->writeRecurrence(this, recur);
482 }
483 
484 void KARecurrence::Private::writeRecurrence(const KARecurrence* q, Recurrence& recur) const
485 {
486  recur.clear();
487  recur.setStartDateTime(mRecurrence.startDateTime());
488  recur.setExDates(mRecurrence.exDates());
489  recur.setExDateTimes(mRecurrence.exDateTimes());
490  const RecurrenceRule* rrule = mRecurrence.defaultRRuleConst();
491  if (!rrule)
492  return;
493  int freq = mRecurrence.frequency();
494  int count = mRecurrence.duration();
495  static_cast<Recurrence_p*>(&recur)->setNewRecurrenceType(rrule->recurrenceType(), freq);
496  if (count)
497  recur.setDuration(count);
498  else
499  recur.setEndDateTime(endDateTime());
500  switch (q->type())
501  {
502  case DAILY:
503  if (rrule->byDays().isEmpty())
504  break;
505  // fall through to rWeekly
506  case WEEKLY:
507  case MONTHLY_POS:
508  recur.defaultRRule(true)->setByDays(rrule->byDays());
509  break;
510  case MONTHLY_DAY:
511  recur.defaultRRule(true)->setByMonthDays(rrule->byMonthDays());
512  break;
513  case ANNUAL_POS:
514  recur.defaultRRule(true)->setByMonths(rrule->byMonths());
515  recur.defaultRRule()->setByDays(rrule->byDays());
516  break;
517  case ANNUAL_DATE:
518  {
519  QList<int> months = rrule->byMonths();
520  QList<int> days = mRecurrence.monthDays();
521  bool special = (mFeb29Type != Feb29_None && !days.isEmpty()
522  && days.first() == 29 && months.removeAll(2));
523  RecurrenceRule* rrule1 = recur.defaultRRule();
524  rrule1->setByMonths(months);
525  rrule1->setByMonthDays(days);
526  if (!special)
527  break;
528 
529  // It recurs on the 29th February.
530  // Create an additional 60th day of the year, or last day of February, rule.
531  RecurrenceRule* rrule2 = new RecurrenceRule();
532  rrule2->setRecurrenceType(RecurrenceRule::rYearly);
533  rrule2->setFrequency(freq);
534  rrule2->setStartDt(mRecurrence.startDateTime());
535  rrule2->setAllDay(mRecurrence.allDay());
536  if (!count)
537  rrule2->setEndDt(endDateTime());
538  if (mFeb29Type == Feb29_Mar1)
539  {
540  QList<int> ds;
541  ds.append(60);
542  rrule2->setByYearDays(ds);
543  }
544  else
545  {
546  QList<int> ds;
547  ds.append(-1);
548  rrule2->setByMonthDays(ds);
549  QList<int> ms;
550  ms.append(2);
551  rrule2->setByMonths(ms);
552  }
553 
554  if (months.isEmpty())
555  {
556  // Only February recurs.
557  // Replace the RRULE and keep the recurrence count the same.
558  if (count)
559  rrule2->setDuration(count);
560  recur.unsetRecurs();
561  }
562  else
563  {
564  // Months other than February also recur on the 29th.
565  // Remove February from the list and add a separate RRULE for February.
566  if (count)
567  {
568  rrule1->setDuration(-1);
569  rrule2->setDuration(-1);
570  if (count > 0)
571  {
572  /* Adjust counts in the two rules to keep the correct occurrence total.
573  * Note that durationTo() always includes the start date. Since for an
574  * individual RRULE the start date may not actually be included, we need
575  * to decrement the count if the start date doesn't actually recur in
576  * this RRULE.
577  * Note that if the count is small, one of the rules may not recur at
578  * all. In that case, retain it so that the February 29th characteristic
579  * is not lost should the user later change the recurrence count.
580  */
581  KDateTime end = endDateTime();
582  int count1 = rrule1->durationTo(end)
583  - (rrule1->recursOn(mRecurrence.startDate(), mRecurrence.startDateTime().timeSpec()) ? 0 : 1);
584  if (count1 > 0)
585  rrule1->setDuration(count1);
586  else
587  rrule1->setEndDt(mRecurrence.startDateTime());
588  int count2 = rrule2->durationTo(end)
589  - (rrule2->recursOn(mRecurrence.startDate(), mRecurrence.startDateTime().timeSpec()) ? 0 : 1);
590  if (count2 > 0)
591  rrule2->setDuration(count2);
592  else
593  rrule2->setEndDt(mRecurrence.startDateTime());
594  }
595  }
596  }
597  recur.addRRule(rrule2);
598  break;
599  }
600  default:
601  break;
602  }
603 }
604 
605 KDateTime KARecurrence::startDateTime() const
606 {
607  return d->mRecurrence.startDateTime();
608 }
609 
610 QDate KARecurrence::startDate() const
611 {
612  return d->mRecurrence.startDate();
613 }
614 
615 void KARecurrence::setStartDateTime(const KDateTime& dt, bool dateOnly)
616 {
617  d->mRecurrence.setStartDateTime(dt);
618  if (dateOnly)
619  d->mRecurrence.setAllDay(true);
620 }
621 
622 /******************************************************************************
623 * Return the date/time of the last recurrence.
624 */
625 KDateTime KARecurrence::endDateTime() const
626 {
627  return d->endDateTime();
628 }
629 
630 KDateTime KARecurrence::Private::endDateTime() const
631 {
632  if (mFeb29Type == Feb29_None || mRecurrence.duration() <= 1)
633  {
634  /* Either it doesn't have any special February 29th treatment,
635  * it's infinite (count = -1), the end date is specified
636  * (count = 0), or it ends on the start date (count = 1).
637  * So just use the normal KCal end date calculation.
638  */
639  return mRecurrence.endDateTime();
640  }
641 
642  /* Create a temporary recurrence rule to find the end date.
643  * In a standard KCal recurrence, the 29th February only occurs once every
644  * 4 years. So shift the temporary recurrence date to the 28th to ensure
645  * that it occurs every year, thus giving the correct occurrence count.
646  */
647  RecurrenceRule* rrule = new RecurrenceRule();
648  rrule->setRecurrenceType(RecurrenceRule::rYearly);
649  KDateTime dt = mRecurrence.startDateTime();
650  QDate da = dt.date();
651  switch (da.day())
652  {
653  case 29:
654  // The start date is definitely a recurrence date, so shift
655  // start date to the temporary recurrence date of the 28th
656  da.setYMD(da.year(), da.month(), 28);
657  break;
658  case 28:
659  if (da.month() != 2 || mFeb29Type != Feb29_Feb28 || QDate::isLeapYear(da.year()))
660  {
661  // Start date is not a recurrence date, so shift it to 27th
662  da.setYMD(da.year(), da.month(), 27);
663  }
664  break;
665  case 1:
666  if (da.month() == 3 && mFeb29Type == Feb29_Mar1 && !QDate::isLeapYear(da.year()))
667  {
668  // Start date is a March 1st recurrence date, so shift
669  // start date to the temporary recurrence date of the 28th
670  da.setYMD(da.year(), 2, 28);
671  }
672  break;
673  default:
674  break;
675  }
676  dt.setDate(da);
677  rrule->setStartDt(dt);
678  rrule->setAllDay(mRecurrence.allDay());
679  rrule->setFrequency(mRecurrence.frequency());
680  rrule->setDuration(mRecurrence.duration());
681  QList<int> ds;
682  ds.append(28);
683  rrule->setByMonthDays(ds);
684  rrule->setByMonths(mRecurrence.defaultRRuleConst()->byMonths());
685  dt = rrule->endDt();
686  delete rrule;
687 
688  // We've found the end date for a recurrence on the 28th. Unless that date
689  // is a real February 28th recurrence, adjust to the actual recurrence date.
690  if (mFeb29Type == Feb29_Feb28 && dt.date().month() == 2 && !QDate::isLeapYear(dt.date().year()))
691  return dt;
692  return dt.addDays(1);
693 }
694 
695 /******************************************************************************
696 * Return the date of the last recurrence.
697 */
698 QDate KARecurrence::endDate() const
699 {
700  KDateTime end = endDateTime();
701  return end.isValid() ? end.date() : QDate();
702 }
703 
704 void KARecurrence::setEndDate(const QDate& endDate)
705 {
706  d->mRecurrence.setEndDate(endDate);
707 }
708 
709 void KARecurrence::setEndDateTime(const KDateTime& endDateTime)
710 {
711  d->mRecurrence.setEndDateTime(endDateTime);
712 }
713 
714 bool KARecurrence::allDay() const
715 {
716  return d->mRecurrence.allDay();
717 }
718 
719 void KARecurrence::setRecurReadOnly(bool readOnly)
720 {
721  d->mRecurrence.setRecurReadOnly(readOnly);
722 }
723 
724 bool KARecurrence::recurReadOnly() const
725 {
726  return d->mRecurrence.recurReadOnly();
727 }
728 
729 bool KARecurrence::recurs() const
730 {
731  return d->mRecurrence.recurs();
732 }
733 
734 QBitArray KARecurrence::days() const
735 {
736  return d->mRecurrence.days();
737 }
738 
739 QList<RecurrenceRule::WDayPos> KARecurrence::monthPositions() const
740 {
741  return d->mRecurrence.monthPositions();
742 }
743 
744 QList<int> KARecurrence::monthDays() const
745 {
746  return d->mRecurrence.monthDays();
747 }
748 
749 QList<int> KARecurrence::yearDays() const
750 {
751  return d->mRecurrence.yearDays();
752 }
753 
754 QList<int> KARecurrence::yearDates() const
755 {
756  return d->mRecurrence.yearDates();
757 }
758 
759 QList<int> KARecurrence::yearMonths() const
760 {
761  return d->mRecurrence.yearMonths();
762 }
763 
764 QList<RecurrenceRule::WDayPos> KARecurrence::yearPositions() const
765 {
766  return d->mRecurrence.yearPositions();
767 }
768 
769 void KARecurrence::addWeeklyDays(const QBitArray& days)
770 {
771  d->mRecurrence.addWeeklyDays(days);
772 }
773 
774 void KARecurrence::addYearlyDay(int day)
775 {
776  d->mRecurrence.addYearlyDay(day);
777 }
778 
779 void KARecurrence::addYearlyDate(int date)
780 {
781  d->mRecurrence.addYearlyDate(date);
782 }
783 
784 void KARecurrence::addYearlyMonth(short month)
785 {
786  d->mRecurrence.addYearlyMonth(month);
787 }
788 
789 void KARecurrence::addYearlyPos(short pos, const QBitArray& days)
790 {
791  d->mRecurrence.addYearlyPos(pos, days);
792 }
793 
794 void KARecurrence::addMonthlyPos(short pos, const QBitArray& days)
795 {
796  d->mRecurrence.addMonthlyPos(pos, days);
797 }
798 
799 void KARecurrence::addMonthlyPos(short pos, ushort day)
800 {
801  d->mRecurrence.addMonthlyPos(pos, day);
802 }
803 
804 void KARecurrence::addMonthlyDate(short day)
805 {
806  d->mRecurrence.addMonthlyDate(day);
807 }
808 
809 /******************************************************************************
810 * Get the next time the recurrence occurs, strictly after a specified time.
811 */
812 KDateTime KARecurrence::getNextDateTime(const KDateTime& preDateTime) const
813 {
814  switch (type())
815  {
816  case ANNUAL_DATE:
817  case ANNUAL_POS:
818  {
819  Recurrence recur;
820  writeRecurrence(recur);
821  return recur.getNextDateTime(preDateTime);
822  }
823  default:
824  return d->mRecurrence.getNextDateTime(preDateTime);
825  }
826 }
827 
828 /******************************************************************************
829 * Get the previous time the recurrence occurred, strictly before a specified time.
830 */
831 KDateTime KARecurrence::getPreviousDateTime(const KDateTime& afterDateTime) const
832 {
833  switch (type())
834  {
835  case ANNUAL_DATE:
836  case ANNUAL_POS:
837  {
838  Recurrence recur;
839  writeRecurrence(recur);
840  return recur.getPreviousDateTime(afterDateTime);
841  }
842  default:
843  return d->mRecurrence.getPreviousDateTime(afterDateTime);
844  }
845 }
846 
847 /******************************************************************************
848 * Return whether the event will recur on the specified date.
849 * The start date only returns true if it matches the recurrence rules.
850 */
851 bool KARecurrence::recursOn(const QDate& dt, const KDateTime::Spec& timeSpec) const
852 {
853  if (!d->mRecurrence.recursOn(dt, timeSpec))
854  return false;
855  if (dt != d->mRecurrence.startDate())
856  return true;
857  // We know now that it isn't in EXDATES or EXRULES,
858  // so we just need to check if it's in RDATES or RRULES
859  if (d->mRecurrence.rDates().contains(dt))
860  return true;
861  RecurrenceRule::List rulelist = d->mRecurrence.rRules();
862  for (int rri = 0, rrend = rulelist.count(); rri < rrend; ++rri)
863  if (rulelist[rri]->recursOn(dt, timeSpec))
864  return true;
865  DateTimeList dtlist = d->mRecurrence.rDateTimes();
866  for (int dti = 0, dtend = dtlist.count(); dti < dtend; ++dti)
867  if (dtlist[dti].date() == dt)
868  return true;
869  return false;
870 }
871 
872 bool KARecurrence::recursAt(const KDateTime& dt) const
873 {
874  return d->mRecurrence.recursAt(dt);
875 }
876 
877 TimeList KARecurrence::recurTimesOn(const QDate& date, const KDateTime::Spec& timeSpec) const
878 {
879  return d->mRecurrence.recurTimesOn(date, timeSpec);
880 }
881 
882 DateTimeList KARecurrence::timesInInterval(const KDateTime& start, const KDateTime& end) const
883 {
884  return d->mRecurrence.timesInInterval(start, end);
885 }
886 
887 int KARecurrence::frequency() const
888 {
889  return d->mRecurrence.frequency();
890 }
891 
892 void KARecurrence::setFrequency(int freq)
893 {
894  d->mRecurrence.setFrequency(freq);
895 }
896 
897 int KARecurrence::duration() const
898 {
899  return d->mRecurrence.duration();
900 }
901 
902 void KARecurrence::setDuration(int duration)
903 {
904  d->mRecurrence.setDuration(duration);
905 }
906 
907 int KARecurrence::durationTo(const KDateTime& dt) const
908 {
909  return d->mRecurrence.durationTo(dt);
910 }
911 
912 int KARecurrence::durationTo(const QDate& date) const
913 {
914  return d->mRecurrence.durationTo(date);
915 }
916 
917 /******************************************************************************
918 * Find the duration of two RRULEs combined.
919 * Use the shorter of the two if they differ.
920 */
921 int KARecurrence::Private::combineDurations(const RecurrenceRule* rrule1, const RecurrenceRule* rrule2, QDate& end) const
922 {
923  int count1 = rrule1->duration();
924  int count2 = rrule2->duration();
925  if (count1 == -1 && count2 == -1)
926  return -1;
927 
928  // One of the RRULEs may not recur at all if the recurrence count is small.
929  // In this case, its end date will have been set to the start date.
930  if (count1 && !count2 && rrule2->endDt().date() == mRecurrence.startDateTime().date())
931  return count1;
932  if (count2 && !count1 && rrule1->endDt().date() == mRecurrence.startDateTime().date())
933  return count2;
934 
935  /* The duration counts will be different even for RRULEs of the same length,
936  * because the first RRULE only actually occurs every 4 years. So we need to
937  * compare the end dates.
938  */
939  if (!count1 || !count2)
940  count1 = count2 = 0;
941  // Get the two rules sorted by end date.
942  KDateTime end1 = rrule1->endDt();
943  KDateTime end2 = rrule2->endDt();
944  if (end1.date() == end2.date())
945  {
946  end = end1.date();
947  return count1 + count2;
948  }
949  const RecurrenceRule* rr1; // earlier end date
950  const RecurrenceRule* rr2; // later end date
951  if (end2.isValid()
952  && (!end1.isValid() || end1.date() > end2.date()))
953  {
954  // Swap the two rules to make rr1 have the earlier end date
955  rr1 = rrule2;
956  rr2 = rrule1;
957  KDateTime e = end1;
958  end1 = end2;
959  end2 = e;
960  }
961  else
962  {
963  rr1 = rrule1;
964  rr2 = rrule2;
965  }
966 
967  // Get the date of the next occurrence after the end of the earlier ending rule
968  RecurrenceRule rr(*rr1);
969  rr.setDuration(-1);
970  KDateTime next1(rr.getNextDate(end1));
971  next1.setDateOnly(true);
972  if (!next1.isValid())
973  end = end1.date();
974  else
975  {
976  if (end2.isValid() && next1 > end2)
977  {
978  // The next occurrence after the end of the earlier ending rule
979  // is later than the end of the later ending rule. So simply use
980  // the end date of the later rule.
981  end = end2.date();
982  return count1 + count2;
983  }
984  QDate prev2 = rr2->getPreviousDate(next1).date();
985  end = (prev2 > end1.date()) ? prev2 : end1.date();
986  }
987  if (count2)
988  count2 = rr2->durationTo(end);
989  return count1 + count2;
990 }
991 
992 /******************************************************************************
993 * Return the longest interval between recurrences.
994 * Reply = 0 if it never recurs.
995 */
996 Duration KARecurrence::longestInterval() const
997 {
998  int freq = d->mRecurrence.frequency();
999  switch (type())
1000  {
1001  case MINUTELY:
1002  return Duration(freq * 60, Duration::Seconds);
1003 
1004  case DAILY:
1005  {
1006  QList<RecurrenceRule::WDayPos> days = d->mRecurrence.defaultRRuleConst()->byDays();
1007  if (days.isEmpty())
1008  return Duration(freq, Duration::Days);
1009 
1010  // After applying the frequency, the specified days of the week
1011  // further restrict when the recurrence occurs.
1012  // So the maximum interval may be greater than the frequency.
1013  bool ds[7] = { false, false, false, false, false, false, false };
1014  for (int i = 0, end = days.count(); i < end; ++i)
1015  if (days[i].pos() == 0)
1016  ds[days[i].day() - 1] = true;
1017  if (freq % 7)
1018  {
1019  // It will recur on every day of the week in some week or other
1020  // (except for those days which are excluded).
1021  int first = -1;
1022  int last = -1;
1023  int maxgap = 1;
1024  for (int i = 0; i < freq*7; i += freq)
1025  {
1026  if (ds[i % 7])
1027  {
1028  if (first < 0)
1029  first = i;
1030  else if (i - last > maxgap)
1031  maxgap = i - last;
1032  last = i;
1033  }
1034  }
1035  int wrap = freq*7 - last + first;
1036  if (wrap > maxgap)
1037  maxgap = wrap;
1038  return Duration(maxgap, Duration::Days);
1039  }
1040  else
1041  {
1042  // It will recur on the same day of the week every time.
1043  // Ensure that the day is a day which is not excluded.
1044  if (ds[d->mRecurrence.startDate().dayOfWeek() - 1])
1045  return Duration(freq, Duration::Days);
1046  break;
1047  }
1048  }
1049  case WEEKLY:
1050  {
1051  // Find which days of the week it recurs on, and if on more than
1052  // one, reduce the maximum interval accordingly.
1053  QBitArray ds = d->mRecurrence.days();
1054  int first = -1;
1055  int last = -1;
1056  int maxgap = 1;
1057  // Use the user's definition of the week, starting at the
1058  // day of the week specified by the user's locale.
1059  int weekStart = KGlobal::locale()->weekStartDay() - 1; // zero-based
1060  for (int i = 0; i < 7; ++i)
1061  {
1062  // Get the standard KDE day-of-week number (zero-based)
1063  // for the day-of-week number in the user's locale.
1064  if (ds.testBit((i + weekStart) % 7))
1065  {
1066  if (first < 0)
1067  first = i;
1068  else if (i - last > maxgap)
1069  maxgap = i - last;
1070  last = i;
1071  }
1072  }
1073  if (first < 0)
1074  break; // no days recur
1075  int span = last - first;
1076  if (freq > 1)
1077  return Duration(freq*7 - span, Duration::Days);
1078  if (7 - span > maxgap)
1079  return Duration(7 - span, Duration::Days);
1080  return Duration(maxgap, Duration::Days);
1081  }
1082  case MONTHLY_DAY:
1083  case MONTHLY_POS:
1084  return Duration(freq * 31, Duration::Days);
1085 
1086  case ANNUAL_DATE:
1087  case ANNUAL_POS:
1088  {
1089  // Find which months of the year it recurs on, and if on more than
1090  // one, reduce the maximum interval accordingly.
1091  const QList<int> months = d->mRecurrence.yearMonths(); // month list is sorted
1092  if (months.isEmpty())
1093  break; // no months recur
1094  if (months.count() == 1)
1095  return Duration(freq * 365, Duration::Days);
1096  int first = -1;
1097  int last = -1;
1098  int maxgap = 0;
1099  for (int i = 0, end = months.count(); i < end; ++i)
1100  {
1101  if (first < 0)
1102  first = months[i];
1103  else
1104  {
1105  int span = QDate(2001, last, 1).daysTo(QDate(2001, months[i], 1));
1106  if (span > maxgap)
1107  maxgap = span;
1108  }
1109  last = months[i];
1110  }
1111  int span = QDate(2001, first, 1).daysTo(QDate(2001, last, 1));
1112  if (freq > 1)
1113  return Duration(freq*365 - span, Duration::Days);
1114  if (365 - span > maxgap)
1115  return Duration(365 - span, Duration::Days);
1116  return Duration(maxgap, Duration::Days);
1117  }
1118  default:
1119  break;
1120  }
1121  return 0;
1122 }
1123 
1124 /******************************************************************************
1125 * Return the interval between recurrences, if the interval between successive
1126 * occurrences does not vary.
1127 * Reply = 0 if recurrence does not occur at fixed intervals.
1128 */
1129 Duration KARecurrence::regularInterval() const
1130 {
1131  int freq = d->mRecurrence.frequency();
1132  switch (type())
1133  {
1134  case MINUTELY:
1135  return Duration(freq * 60, Duration::Seconds);
1136  case DAILY:
1137  {
1138  QList<RecurrenceRule::WDayPos> days = d->mRecurrence.defaultRRuleConst()->byDays();
1139  if (days.isEmpty())
1140  return Duration(freq, Duration::Days);
1141  // After applying the frequency, the specified days of the week
1142  // further restrict when the recurrence occurs.
1143  // Find which days occur, and count the number of days which occur.
1144  bool ds[7] = { false, false, false, false, false, false, false };
1145  for (int i = 0, end = days.count(); i < end; ++i)
1146  if (days[i].pos() == 0)
1147  ds[days[i].day() - 1] = true;
1148  if (!(freq % 7))
1149  {
1150  // It will recur on the same day of the week every time.
1151  // Check whether that day is in the list of included days.
1152  if (ds[d->mRecurrence.startDate().dayOfWeek() - 1])
1153  return Duration(freq, Duration::Days);
1154  break;
1155  }
1156  int n = 0; // number of days which occur
1157  for (int i = 0; i < 7; ++i)
1158  if (ds[i])
1159  ++n;
1160  if (n == 7)
1161  return Duration(freq, Duration::Days); // every day is included
1162  if (n == 1)
1163  return Duration(freq * 7, Duration::Days); // only one day of the week is included
1164  break;
1165  }
1166  case WEEKLY:
1167  {
1168  QList<RecurrenceRule::WDayPos> days = d->mRecurrence.defaultRRuleConst()->byDays();
1169  if (days.isEmpty())
1170  return Duration(freq * 7, Duration::Days);
1171  // The specified days of the week occur every week in which the
1172  // recurrence occurs.
1173  // Find which days occur, and count the number of days which occur.
1174  bool ds[7] = { false, false, false, false, false, false, false };
1175  for (int i = 0, end = days.count(); i < end; ++i)
1176  if (days[i].pos() == 0)
1177  ds[days[i].day() - 1] = true;
1178  int n = 0; // number of days which occur
1179  for (int i = 0; i < 7; ++i)
1180  if (ds[i])
1181  ++n;
1182  if (n == 7)
1183  {
1184  if (freq == 1)
1185  return Duration(freq, Duration::Days); // every day is included
1186  break;
1187  }
1188  if (n == 1)
1189  return Duration(freq * 7, Duration::Days); // only one day of the week is included
1190  break;
1191  }
1192  default:
1193  break;
1194  }
1195  return 0;
1196 }
1197 
1198 DateTimeList KARecurrence::exDateTimes() const
1199 {
1200  return d->mRecurrence.exDateTimes();
1201 }
1202 
1203 DateList KARecurrence::exDates() const
1204 {
1205  return d->mRecurrence.exDates();
1206 }
1207 
1208 void KARecurrence::setExDateTimes(const DateTimeList& exdates)
1209 {
1210  d->mRecurrence.setExDateTimes(exdates);
1211 }
1212 
1213 void KARecurrence::setExDates(const DateList& exdates)
1214 {
1215  d->mRecurrence.setExDates(exdates);
1216 }
1217 
1218 void KARecurrence::addExDateTime(const KDateTime& exdate)
1219 {
1220  d->mRecurrence.addExDateTime(exdate);
1221 }
1222 
1223 void KARecurrence::addExDate(const QDate& exdate)
1224 {
1225  d->mRecurrence.addExDate(exdate);
1226 }
1227 
1228 void KARecurrence::shiftTimes(const KDateTime::Spec& oldSpec, const KDateTime::Spec& newSpec)
1229 {
1230  d->mRecurrence.shiftTimes(oldSpec, newSpec);
1231 }
1232 
1233 RecurrenceRule* KARecurrence::defaultRRuleConst() const
1234 {
1235  return d->mRecurrence.defaultRRuleConst();
1236 }
1237 
1238 /******************************************************************************
1239 * Return the recurrence's period type.
1240 */
1241 KARecurrence::Type KARecurrence::type() const
1242 {
1243  if (d->mCachedType == -1)
1244  d->mCachedType = type(d->mRecurrence.defaultRRuleConst());
1245  return static_cast<Type>(d->mCachedType);
1246 }
1247 
1248 /******************************************************************************
1249 * Return the recurrence rule type.
1250 */
1251 KARecurrence::Type KARecurrence::type(const RecurrenceRule* rrule)
1252 {
1253  switch (Recurrence::recurrenceType(rrule))
1254  {
1255  case Recurrence::rMinutely: return MINUTELY;
1256  case Recurrence::rDaily: return DAILY;
1257  case Recurrence::rWeekly: return WEEKLY;
1258  case Recurrence::rMonthlyDay: return MONTHLY_DAY;
1259  case Recurrence::rMonthlyPos: return MONTHLY_POS;
1260  case Recurrence::rYearlyMonth: return ANNUAL_DATE;
1261  case Recurrence::rYearlyPos: return ANNUAL_POS;
1262  default:
1263  if (dailyType(rrule))
1264  return DAILY;
1265  return NO_RECUR;
1266  }
1267 }
1268 
1269 /******************************************************************************
1270 * Check if the rule is a daily rule with or without BYDAYS specified.
1271 */
1272 bool KARecurrence::dailyType(const RecurrenceRule* rrule)
1273 {
1274  if (rrule->recurrenceType() != RecurrenceRule::rDaily
1275  || !rrule->bySeconds().isEmpty()
1276  || !rrule->byMinutes().isEmpty()
1277  || !rrule->byHours().isEmpty()
1278  || !rrule->byWeekNumbers().isEmpty()
1279  || !rrule->byMonthDays().isEmpty()
1280  || !rrule->byMonths().isEmpty()
1281  || !rrule->bySetPos().isEmpty()
1282  || !rrule->byYearDays().isEmpty())
1283  return false;
1284  QList<RecurrenceRule::WDayPos> days = rrule->byDays();
1285  if (days.isEmpty())
1286  return true;
1287  // Check that all the positions are zero (i.e. every time)
1288  bool found = false;
1289  for (int i = 0, end = days.count(); i < end; ++i)
1290  {
1291  if (days[i].pos() != 0)
1292  return false;
1293  found = true;
1294  }
1295  return found;
1296 }
1297 
1298 } // namespace KAlarmCal
1299 
1300 // vim: et sw=4:
This file is part of the KDE documentation.
Documentation copyright © 1996-2013 The KDE developers.
Generated on Sat Jul 13 2013 01:30:15 by doxygen 1.8.3.1 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KAlarm Library

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

kdepimlibs-4.10.5 API Reference

Skip menu "kdepimlibs-4.10.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