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

KCalCore Library

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

Skip menu "kdepimlibs API Reference"
  • akonadi
  •   contact
  •   kmime
  •   socialutils
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kldap
  • kmbox
  • kmime
  • kpimidentities
  • kpimtextedit
  • kresources
  • ktnef
  • kxmlrpcclient
  • microblog

Search



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