KCalendarCore

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

KDE's Doxygen guidelines are available online.