1/****************************************************************************
2**
3** Copyright (C) 2015 The Qt Company Ltd.
4** Contact: http://www.qt.io/licensing/
5**
6** This file is part of the QtOrganizer module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL21$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see http://www.qt.io/terms-conditions. For further
15** information use the contact form at http://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 2.1 or version 3 as published by the Free
20** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22** following information to ensure the GNU Lesser General Public License
23** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25**
26** As a special exception, The Qt Company gives you certain additional
27** rights. These rights are described in The Qt Company LGPL Exception
28** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29**
30** $QT_END_LICENSE$
31**
32****************************************************************************/
33
34#include "qorganizerrecurrencerule.h"
35#include "qorganizerrecurrencerule_p.h"
36
37#ifndef QT_NO_DEBUG_STREAM
38#include <QtCore/qdebug.h>
39#endif
40
41QT_BEGIN_NAMESPACE_ORGANIZER
42
43/*!
44 \class QOrganizerRecurrenceRule
45 \brief The QOrganizerRecurrenceRule class describes the a rule by which a QOrganizerItem repeats.
46 \inmodule QtOrganizer
47 \ingroup organizer-main
48
49 This class is a mapping of a subset of the iCalendar RRULE property value, and each field in this
50 class corresponds to a fragment of iCalendar's RRULE. This class supports the same fragments as
51 those supported by RRULE, except for describing recurrences on a higher frequency than Daily.
52 That is, this class doesn't support hourly, minutely or secondly recurrences, nor does it support
53 specifying which hour, minute or second of a day to recur on. These types of rules are
54 unsupported because most calendaring backends don't support them, and it simplifies recurrences
55 by enforcing that there can be at most one occurrence of an item per day.
56
57 The general rules for interaction between the fields when generating the occurence dates is as
58 follows:
59 \list
60 \li When a criterion takes a list, the items in the list are unioned together.
61 \list
62 \li e.g. with [dayOfWeek == Tuesday,Thursday], the event occurs if it is Tuesday or Thursday.
63 \endlist
64
65 \li Frequency and specific criteria interact in a more complicated fashion. For each criterion on a
66 larger timespan than the frequency, the dates matching the criterion are intersected with the
67 dates resulting from the frequency.
68 \list
69 \li e.g. [frequency = Daily, month = January] means every day in January. For each criterion
70 on a shorter timespan than the frequency, the criterion is unioned.
71 \li e.g. [frequency = Weekly, dayOfWeek = Wednesday,Friday] means every Wednesday and Friday of
72 every week.
73 \endlist
74 This makes the frequency field superfluous in many cases when other criteria are present.
75 e.g. all of the below mean the same thing:
76 \list
77 \li [frequency = Daily, dayOfWeek = Monday,Tuesday]
78 \li [frequency = Weekly, dayOfWeek = Monday,Tuesday]
79 \li [frequency = Monthly, dayOfWeek = Monday,Tuesday]
80 \li [frequency = Yearly, dayOfWeek = Monday,Tuesday]
81 \endlist
82 However, the frequency field may start affecting the result differently when other fields are
83 added like interval and positions.
84
85 \li For the purpose of calculating occurrence dates, information not contained in the rule is in some
86 cases derived from the startDateTime field of the event that the detail is associated with.
87 There are three cases where such derivation is necessary.
88 \list
89 \li Case 1: frequency == Weekly. If dayOfWeek is not specified, derive it from the week day that
90 the startDateTime occurs on.
91 \li Case 2: frequency == Monthly. If neither dayOfWeek or dayOfMonth is specified, dayOfMonth should
92 be derived from the startDateTime
93 \li Case 3: frequency == Yearly. If none of monthOfYear, weekOfYear, dayOfYear, dayOfMonth or dayOfWeek
94 are specified, derive monthOfYear and dayOfMonth. If monthOfYear is specified but not weekOfYear, dayOfYear,
95 dayOfMonth or dayOfWeek, then derive dayOfMonth. If weekOfYear is specified but not dayOfYear,
96 dayOfWeek or dayOfMonth, derive dayOfWeek from the startDateTime.
97 For any cases not covered here, do not derive any of the fields.
98 \endlist
99 \endlist
100
101 A recurrence rule may be limited by either count or date, or it may be unlimited.
102 If limited by count, the series generated by the rule will have at most \c count
103 occurrences. If limited by date, the series generated by the rule may have occurrences
104 up to (and including) the limit \c date. See \l setLimit() for more information on
105 this topic.
106 */
107
108/*!
109 \enum QOrganizerRecurrenceRule::Frequency
110
111 This enumeration describes how often an item recurs.
112 \value Invalid The entire recurrence rule is invalid.
113 \value Daily The item recurs every day.
114 \value Weekly The item recurs every week.
115 \value Monthly The item recurs every month.
116 \value Yearly The item recurs every year.
117 */
118
119/*!
120 \enum QOrganizerRecurrenceRule::Month
121
122 This enumeration describes which month an item recurs on.
123 \value January
124 \value February
125 \value March
126 \value April
127 \value May
128 \value June
129 \value July
130 \value August
131 \value September
132 \value October
133 \value November
134 \value December
135 */
136
137/*!
138 \enum QOrganizerRecurrenceRule::LimitType
139
140 This enumeration describes the limitation of this recurrence rule.
141 \value NoLimit The recurrence rule has no limit specified.
142 \value CountLimit The recurrence rule specifies a certain count of repetitions in the series.
143 \value DateLimit The recurrence rule specifies that the series ends after a particular date.
144 */
145
146/*!
147 Constructs a QOrganizerRecurrenceRule object describing a weekly recurrence.
148 */
149QOrganizerRecurrenceRule::QOrganizerRecurrenceRule()
150 : d(new QOrganizerRecurrenceRulePrivate)
151{
152}
153
154/*!
155 Destroys the QOrganizerRecurrenceRule object.
156 */
157QOrganizerRecurrenceRule::~QOrganizerRecurrenceRule()
158{
159}
160
161/*!
162 Constructs a QOrganizerRecurrenceRule object as a copy of \a other.
163 */
164QOrganizerRecurrenceRule::QOrganizerRecurrenceRule(const QOrganizerRecurrenceRule &other)
165 : d(other.d)
166{
167}
168
169/*!
170 Assigns this detail to be equal to \a other.
171 */
172QOrganizerRecurrenceRule &QOrganizerRecurrenceRule::operator=(const QOrganizerRecurrenceRule &other)
173{
174 d = other.d;
175 return *this;
176}
177
178/*!
179 Returns true if this recurrence rule is equal to the \a other; otherwise returns false.
180
181 \sa operator!=()
182 */
183bool QOrganizerRecurrenceRule::operator==(const QOrganizerRecurrenceRule &other) const
184{
185 if (d == other.d)
186 return true;
187
188 return d->firstDayOfWeek == other.d->firstDayOfWeek
189 && d->frequency == other.d->frequency
190 && d->interval == other.d->interval
191 && d->limitCount == other.d->limitCount
192 && d->limitDate == other.d->limitDate
193 && d->limitType == other.d->limitType
194 && d->positions == other.d->positions
195 && d->daysOfMonth == other.d->daysOfMonth
196 && d->daysOfWeek == other.d->daysOfWeek
197 && d->daysOfYear == other.d->daysOfYear
198 && d->monthsOfYear == other.d->monthsOfYear
199 && d->weeksOfYear == other.d->weeksOfYear;
200}
201
202/*!
203 \fn bool QOrganizerRecurrenceRule::operator!=(const QOrganizerRecurrenceRule &other) const
204
205 Returns true if this recurrence rule is not equal to the \a other; otherwise returns false.
206
207 \sa operator==()
208*/
209
210/*!
211 Sets the frequency with which the item recurs to \a freq.
212
213 This corresponds to the FREQ fragment in iCalendar's RRULE.
214 */
215void QOrganizerRecurrenceRule::setFrequency(Frequency freq)
216{
217 d->frequency = freq;
218}
219
220/*!
221 Returns the frequency with which the item recurs. The default frequency is Invalid.
222 */
223QOrganizerRecurrenceRule::Frequency QOrganizerRecurrenceRule::frequency() const
224{
225 return d->frequency;
226}
227
228/*!
229 Sets the "count" condition of the recurrence rule to \a count. If an end-date was previously
230 set, it is removed as count and endDate are mutually exclusive.
231
232 The "count" condition is the maximum number of times the item should recur. Calling clearLimit()
233 or setting this to a negative value removes the count condition.
234
235 This corresponds to the COUNT fragment in iCalendar's RRULE.
236 */
237void QOrganizerRecurrenceRule::setLimit(int count)
238{
239 if (count < 0) {
240 clearLimit();
241 } else {
242 d->limitType = QOrganizerRecurrenceRule::CountLimit;
243 d->limitCount = count;
244 d->limitDate = QDate();
245 }
246}
247
248/*!
249 Sets the end-date condition of the recurrence rule to \a date. If a "count" condition was
250 previously set, it is removed as count and endDate are mutually exclusive.
251
252 The end-date condition is the date after which the item should not recur. Calling clearLimit()
253 or setting this to an invalid date removes the end-date condition.
254
255 This corresponds to the UNTIL fragment in iCalendar's RRULE.
256 */
257void QOrganizerRecurrenceRule::setLimit(const QDate &date)
258{
259 if (!date.isValid()) {
260 clearLimit();
261 } else {
262 d->limitType = QOrganizerRecurrenceRule::DateLimit;
263 d->limitDate = date;
264 d->limitCount = -1;
265 }
266}
267
268/*!
269 Clear any recurrence rule limitation conditions.
270 */
271void QOrganizerRecurrenceRule::clearLimit()
272{
273 d->limitType = QOrganizerRecurrenceRule::NoLimit;
274 d->limitCount = -1;
275 d->limitDate = QDate();
276}
277
278/*!
279 Returns the type of limitation specified by the recurrence rule. The default limit type is NoLimit
280 (i.e. unlimited).
281 */
282QOrganizerRecurrenceRule::LimitType QOrganizerRecurrenceRule::limitType() const
283{
284 return d->limitType;
285}
286
287/*!
288 Returns the "count" condition specified by the recurrence rule. -1 is returned if the "count"
289 condition is not set or an end-date condition is currently set.
290 */
291int QOrganizerRecurrenceRule::limitCount() const
292{
293 if (d->limitType == QOrganizerRecurrenceRule::CountLimit)
294 return d->limitCount;
295 return -1;
296}
297
298/*!
299 Returns the end-date condition specified by the recurrence rule. An invalid date is returned if
300 the end-date condition is not set or a "count" condition is currently set.
301 */
302QDate QOrganizerRecurrenceRule::limitDate() const
303{
304 if (d->limitType == QOrganizerRecurrenceRule::DateLimit)
305 return d->limitDate;
306 return QDate();
307}
308
309/*!
310 Sets the interval, between cycles of length given by frequency(), in which the item should recur
311 to \a interval.
312
313 For example, if the frequency() is QOrganizerRecurrenceRule::Daily and the interval is set to 2,
314 the item should recur every second day.
315
316 This corresponds to the INTERVAL fragment in iCalendar's RRULE.
317 */
318void QOrganizerRecurrenceRule::setInterval(int interval)
319{
320 if (interval > 0)
321 d->interval = interval;
322}
323
324/*!
325 Returns the interval of recurrence. The default interval is 1.
326 */
327int QOrganizerRecurrenceRule::interval() const
328{
329 return d->interval;
330}
331
332/*!
333 Sets the days of week on which the item should recur to \a days.
334
335 This corresponds to the BYDAY fragment in iCalendar's RRULE.
336 */
337void QOrganizerRecurrenceRule::setDaysOfWeek(const QSet<Qt::DayOfWeek> &days)
338{
339 d->daysOfWeek = days;
340}
341
342/*!
343 Returns a set of the days of week that the item should recur on, or an empty set if not specified.
344 */
345QSet<Qt::DayOfWeek> QOrganizerRecurrenceRule::daysOfWeek() const
346{
347 return d->daysOfWeek;
348}
349
350/*!
351 Sets the days of the month on which the item should recur to \a days. Negative values in the set
352 represent the number of days from the end of the month. e.g. 1 represents the first day of the
353 month and -1 represents the last day of the month.
354
355 This corresponds to the BYMONTHDAY fragment in iCalendar's RRULE.
356 */
357void QOrganizerRecurrenceRule::setDaysOfMonth(const QSet<int> &days)
358{
359 d->daysOfMonth = days;
360}
361
362/*!
363 Returns a set of the days of the month that the item should recur on, or an empty set if not specified.
364 */
365QSet<int> QOrganizerRecurrenceRule::daysOfMonth() const
366{
367 return d->daysOfMonth;
368}
369
370/*!
371 Sets the days of the year on which the item should recur to \a days. Negative values in the set
372 represent the number of days from the end of the year. e.g. 1 represents the first day of the
373 year and -1 represents the last day of the year.
374
375 This corresponds to the BYYEARDAY fragment in iCalendar's RRULE.
376 */
377void QOrganizerRecurrenceRule::setDaysOfYear(const QSet<int> &days)
378{
379 d->daysOfYear = days;
380}
381
382/*!
383 Returns a set of the days of the year that the item should recur on, or an empty set if not specified.
384 */
385QSet<int> QOrganizerRecurrenceRule::daysOfYear() const
386{
387 return d->daysOfYear;
388}
389
390/*!
391 Sets the months on which the item should recur to \a months.
392
393 This corresponds to the BYMONTHDAY fragment in iCalendar's RRULE.
394 */
395void QOrganizerRecurrenceRule::setMonthsOfYear(const QSet<Month> &months)
396{
397 d->monthsOfYear = months;
398}
399
400/*!
401 Returns a set of the months of the year that the item should recur on, or an empty set if not
402 specified.
403 */
404QSet<QOrganizerRecurrenceRule::Month> QOrganizerRecurrenceRule::monthsOfYear() const
405{
406 return d->monthsOfYear;
407}
408
409/*!
410 Sets the weeks of the year on which the item should recur to \a weeks. Negative values in the
411 set represent the number of weeks from the end of the year. e.g. 1 represents the first week of
412 the year and -1 represents the last week of the year.
413
414 This corresponds to the BYWEEK fragment in iCalendar's RRULE.
415 */
416void QOrganizerRecurrenceRule::setWeeksOfYear(const QSet<int> &weeks)
417{
418 d->weeksOfYear = weeks;
419}
420
421/*!
422 Returns a set of the weeks of the year that the item should recur on, or an empty set if not
423 specified.
424 */
425QSet<int> QOrganizerRecurrenceRule::weeksOfYear() const
426{
427 return d->weeksOfYear;
428}
429
430/*!
431 Sets the set of positions that the item should recur on to \a pos. This specifies that the item
432 should only recur on the nth occurrence within the set of events otherwise specified by the rule,
433 for the values of n in \a pos.
434
435 Negative values in the list represnet a position counting from the end of the set.
436
437 For example, if frequency() == Monthly and months() is the list Monday, Tuesday, Wednesday,
438 Thursday, Friday, and positions() == -1, this specifies that the item should recur on the last
439 weekday of each month.
440
441 This corresponds to the BYSETPOS fragment in iCalendar's RRULE.
442 */
443void QOrganizerRecurrenceRule::setPositions(const QSet<int> &pos)
444{
445 d->positions = pos;
446}
447
448/*!
449 Returns the position-set of the recurrence rule, or an empty set if not specified.
450 */
451QSet<int> QOrganizerRecurrenceRule::positions() const
452{
453 return d->positions;
454}
455
456/*!
457 Sets the day that the week starts on to \a day, for the purposes of calculating recurrences.
458 This is significant when the frequency is Weekly and the interval is greater than 1, or when
459 weekOfYear is set. See the iCalendar spec for examples of its significance.
460
461 If not set, Monday is the first day of a week.
462
463 This corresponds to the BYWKST fragment in iCalendar's RRULE.
464 */
465void QOrganizerRecurrenceRule::setFirstDayOfWeek(Qt::DayOfWeek day)
466{
467 d->firstDayOfWeek = day;
468}
469
470/*!
471 Returns the day that the week starts on.
472 */
473Qt::DayOfWeek QOrganizerRecurrenceRule::firstDayOfWeek() const
474{
475 return d->firstDayOfWeek;
476}
477
478/*!
479 \relates QOrganizerRecurrenceRule
480 Returns the hash value for \a key.
481 */
482uint qHash(const QOrganizerRecurrenceRule &key)
483{
484 uint hash(0);
485 static const unsigned int prime1 = 11;
486 static const unsigned int prime2 = 31;
487 static const unsigned int prime3 = 47;
488
489 foreach (int day, key.daysOfMonth())
490 hash += day;
491 hash *= prime1;
492
493 foreach (Qt::DayOfWeek day, key.daysOfWeek())
494 hash += day;
495 hash *= prime2;
496
497 foreach (int day, key.daysOfYear())
498 hash += day;
499 hash *= prime3;
500
501 foreach (QOrganizerRecurrenceRule::Month month, key.monthsOfYear())
502 hash += month;
503 hash *= prime1;
504
505 foreach (int week, key.weeksOfYear())
506 hash += week;
507 hash *= prime2;
508
509 foreach (int pos, key.positions())
510 hash += pos;
511 hash *= prime3;
512
513 hash += static_cast<uint>(key.firstDayOfWeek())
514 + static_cast<uint>(key.frequency())
515 + key.interval()
516 + key.limitCount()
517 + qHash(key: key.limitDate())
518 + static_cast<uint>(key.limitType());
519
520 return hash * prime1;
521}
522
523#ifndef QT_NO_DEBUG_STREAM
524/*!
525 \relates QOrganizerRecurrenceRule
526 Outputs \a rule to the debug stream \a dbg.
527 */
528QDebug operator<<(QDebug dbg, const QOrganizerRecurrenceRule &rule)
529{
530 dbg.nospace() << "QOrganizerRecurrenceRule(frequency=";
531 dbg.nospace() << rule.frequency();
532 dbg.nospace() << ",";
533 dbg.nospace() << "interval=";
534 dbg.nospace() << rule.interval();
535 dbg.nospace() << ",";
536 switch (rule.limitType()) {
537 case QOrganizerRecurrenceRule::CountLimit:
538 dbg.nospace() << "limitCount=";
539 dbg.nospace() << rule.limitCount();
540 break;
541 case QOrganizerRecurrenceRule::DateLimit:
542 dbg.nospace() << "limitDate=";
543 dbg.nospace() << rule.limitDate().toString();
544 break;
545 case QOrganizerRecurrenceRule::NoLimit:
546 dbg.nospace() << "no limit";
547 break;
548 default:
549 break;
550 }
551 dbg.nospace() << ",daysOfWeek=\"";
552 QList<Qt::DayOfWeek> wdays(rule.daysOfWeek().toList());
553 std::sort(first: wdays.begin(), last: wdays.end());
554 foreach (Qt::DayOfWeek day, wdays) {
555 dbg.nospace() << static_cast<quint32>(day);
556 dbg.space();
557 }
558 dbg.nospace() << "\"";
559
560 dbg.nospace() << ",daysOfMonth=\"";
561 QList<int> mdays(rule.daysOfMonth().toList());
562 std::sort(first: mdays.begin(), last: mdays.end());
563 foreach (int day, mdays) {
564 dbg.nospace() << day;
565 dbg.space();
566 }
567 dbg.nospace() << "\"";
568
569 dbg.nospace() << ",daysOfYear=\"";
570 QList<int> ydays(rule.daysOfYear().toList());
571 std::sort(first: ydays.begin(), last: ydays.end());
572 foreach (int day, ydays) {
573 dbg.nospace() << day;
574 dbg.space();
575 }
576 dbg.nospace() << "\"";
577
578 dbg.nospace() << ",monthsOfYear=\"";
579 QList<QOrganizerRecurrenceRule::Month> months(rule.monthsOfYear().toList());
580 std::sort(first: months.begin(), last: months.end());
581 foreach (QOrganizerRecurrenceRule::Month month, months) {
582 dbg.nospace() << static_cast<quint32>(month);
583 dbg.space();
584 }
585 dbg.nospace() << "\"";
586
587 dbg.nospace() << ",positions=\"";
588 QList<int> positions(rule.positions().toList());
589 std::sort(first: positions.begin(), last: positions.end());
590 foreach (int position, positions) {
591 dbg.nospace() << position;
592 dbg.space();
593 }
594 dbg.nospace() << "\",";
595
596 dbg.nospace() << "firstDayOfWeek=";
597 dbg.nospace() << static_cast<quint32>(rule.firstDayOfWeek());
598 dbg.nospace() << ')';
599 return dbg.maybeSpace();
600}
601#endif // QT_NO_DEBUG_STREAM
602
603QT_END_NAMESPACE_ORGANIZER
604

source code of qtpim/src/organizer/qorganizerrecurrencerule.cpp