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

KDE's Doxygen guidelines are available online.