File indexing completed on 2024-04-28 15:19:09

0001 /*
0002   This file is part of kcalcore library.
0003 
0004   SPDX-FileCopyrightText: 1998 Preston Brown <pbrown@kde.org>
0005   SPDX-FileCopyrightText: 2001 Cornelius Schumacher <schumacher@kde.org>
0006   SPDX-FileCopyrightText: 2002, 2006 David Jarvie <djarvie@kde.org>
0007   SPDX-FileCopyrightText: 2005 Reinhold Kainhofer <kainhofer@kde.org>
0008 
0009   SPDX-License-Identifier: LGPL-2.0-or-later
0010 */
0011 
0012 #include "incidencebase.h"
0013 #include "recurrence.h"
0014 #include "recurrencehelper_p.h"
0015 #include "utils_p.h"
0016 
0017 #include "kcalendarcore_debug.h"
0018 
0019 #include <QBitArray>
0020 #include <QDataStream>
0021 #include <QTime>
0022 #include <QTimeZone>
0023 #include <QHash>
0024 
0025 using namespace KCalendarCore;
0026 
0027 //@cond PRIVATE
0028 class Q_DECL_HIDDEN KCalendarCore::Recurrence::Private
0029 {
0030 public:
0031     Private()
0032         : mCachedType(rMax)
0033         , mAllDay(false)
0034         , mRecurReadOnly(false)
0035     {
0036     }
0037 
0038     Private(const Private &p)
0039         : mRDateTimes(p.mRDateTimes)
0040         , mRDateTimePeriods(p.mRDateTimePeriods)
0041         , mRDates(p.mRDates)
0042         , mExDateTimes(p.mExDateTimes)
0043         , mExDates(p.mExDates)
0044         , mStartDateTime(p.mStartDateTime)
0045         , mCachedType(p.mCachedType)
0046         , mAllDay(p.mAllDay)
0047         , mRecurReadOnly(p.mRecurReadOnly)
0048     {
0049     }
0050 
0051     bool operator==(const Private &p) const;
0052 
0053     RecurrenceRule::List mExRules;
0054     RecurrenceRule::List mRRules;
0055     QList<QDateTime> mRDateTimes;
0056     QHash<QDateTime, Period> mRDateTimePeriods; // Map RDate starts with periods if any
0057     DateList mRDates;
0058     QList<QDateTime> mExDateTimes;
0059     DateList mExDates;
0060     QDateTime mStartDateTime; // date/time of first recurrence
0061     QList<RecurrenceObserver *> mObservers;
0062 
0063     // Cache the type of the recurrence with the old system (e.g. MonthlyPos)
0064     mutable ushort mCachedType;
0065 
0066     bool mAllDay = false; // the recurrence has no time, just a date
0067     bool mRecurReadOnly = false;
0068 };
0069 
0070 bool Recurrence::Private::operator==(const Recurrence::Private &p) const
0071 {
0072     //   qCDebug(KCALCORE_LOG) << mStartDateTime << p.mStartDateTime;
0073     if (!identical(mStartDateTime, p.mStartDateTime) || mAllDay != p.mAllDay
0074         || mRecurReadOnly != p.mRecurReadOnly || mExDates != p.mExDates || mExDateTimes != p.mExDateTimes || mRDates != p.mRDates
0075         || mRDateTimes != p.mRDateTimes || mRDateTimePeriods != p.mRDateTimePeriods) {
0076         return false;
0077     }
0078 
0079     // Compare the rrules, exrules! Assume they have the same order... This only
0080     // matters if we have more than one rule (which shouldn't be the default anyway)
0081     int i;
0082     int end = mRRules.count();
0083     if (end != p.mRRules.count()) {
0084         return false;
0085     }
0086     for (i = 0; i < end; ++i) {
0087         if (*mRRules[i] != *p.mRRules[i]) {
0088             return false;
0089         }
0090     }
0091     end = mExRules.count();
0092     if (end != p.mExRules.count()) {
0093         return false;
0094     }
0095     for (i = 0; i < end; ++i) {
0096         if (*mExRules[i] != *p.mExRules[i]) {
0097             return false;
0098         }
0099     }
0100     return true;
0101 }
0102 //@endcond
0103 
0104 Recurrence::Recurrence()
0105     : d(new KCalendarCore::Recurrence::Private())
0106 {
0107 }
0108 
0109 Recurrence::Recurrence(const Recurrence &r)
0110     : RecurrenceRule::RuleObserver()
0111     , d(new KCalendarCore::Recurrence::Private(*r.d))
0112 {
0113     int i;
0114     int end;
0115     d->mRRules.reserve(r.d->mRRules.count());
0116     for (i = 0, end = r.d->mRRules.count(); i < end; ++i) {
0117         RecurrenceRule *rule = new RecurrenceRule(*r.d->mRRules[i]);
0118         d->mRRules.append(rule);
0119         rule->addObserver(this);
0120     }
0121     d->mExRules.reserve(r.d->mExRules.count());
0122     for (i = 0, end = r.d->mExRules.count(); i < end; ++i) {
0123         RecurrenceRule *rule = new RecurrenceRule(*r.d->mExRules[i]);
0124         d->mExRules.append(rule);
0125         rule->addObserver(this);
0126     }
0127 }
0128 
0129 Recurrence::~Recurrence()
0130 {
0131     qDeleteAll(d->mExRules);
0132     qDeleteAll(d->mRRules);
0133     delete d;
0134 }
0135 
0136 bool Recurrence::operator==(const Recurrence &recurrence) const
0137 {
0138     return *d == *recurrence.d;
0139 }
0140 
0141 #if KCALENDARCORE_BUILD_DEPRECATED_SINCE(5, 64)
0142 Recurrence &Recurrence::operator=(const Recurrence &recurrence)
0143 {
0144     // check for self assignment
0145     if (&recurrence == this) {
0146         return *this;
0147     }
0148 
0149     // ### this copies the pointers in mExRules and mRRules eventually resulting in a double free!
0150     // fortunately however this function is unused, we just can't remove it just yet, due to ABI guarantees
0151     QT_WARNING_PUSH
0152     QT_WARNING_DISABLE_GCC("-Wdeprecated-copy")
0153     *d = *recurrence.d;
0154     QT_WARNING_POP
0155     return *this;
0156 }
0157 #endif
0158 
0159 void Recurrence::addObserver(RecurrenceObserver *observer)
0160 {
0161     if (!d->mObservers.contains(observer)) {
0162         d->mObservers.append(observer);
0163     }
0164 }
0165 
0166 void Recurrence::removeObserver(RecurrenceObserver *observer)
0167 {
0168     d->mObservers.removeAll(observer);
0169 }
0170 
0171 QDateTime Recurrence::startDateTime() const
0172 {
0173     return d->mStartDateTime;
0174 }
0175 
0176 bool Recurrence::allDay() const
0177 {
0178     return d->mAllDay;
0179 }
0180 
0181 void Recurrence::setAllDay(bool allDay)
0182 {
0183     if (d->mRecurReadOnly || allDay == d->mAllDay) {
0184         return;
0185     }
0186 
0187     d->mAllDay = allDay;
0188     for (int i = 0, end = d->mRRules.count(); i < end; ++i) {
0189         d->mRRules[i]->setAllDay(allDay);
0190     }
0191     for (int i = 0, end = d->mExRules.count(); i < end; ++i) {
0192         d->mExRules[i]->setAllDay(allDay);
0193     }
0194     updated();
0195 }
0196 
0197 RecurrenceRule *Recurrence::defaultRRule(bool create) const
0198 {
0199     if (d->mRRules.isEmpty()) {
0200         if (!create || d->mRecurReadOnly) {
0201             return nullptr;
0202         }
0203         RecurrenceRule *rrule = new RecurrenceRule();
0204         rrule->setStartDt(startDateTime());
0205         const_cast<KCalendarCore::Recurrence *>(this)->addRRule(rrule);
0206         return rrule;
0207     } else {
0208         return d->mRRules[0];
0209     }
0210 }
0211 
0212 RecurrenceRule *Recurrence::defaultRRuleConst() const
0213 {
0214     return d->mRRules.isEmpty() ? nullptr : d->mRRules[0];
0215 }
0216 
0217 void Recurrence::updated()
0218 {
0219     // recurrenceType() re-calculates the type if it's rMax
0220     d->mCachedType = rMax;
0221     for (int i = 0, end = d->mObservers.count(); i < end; ++i) {
0222         if (d->mObservers[i]) {
0223             d->mObservers[i]->recurrenceUpdated(this);
0224         }
0225     }
0226 }
0227 
0228 bool Recurrence::recurs() const
0229 {
0230     return !d->mRRules.isEmpty() || !d->mRDates.isEmpty() || !d->mRDateTimes.isEmpty();
0231 }
0232 
0233 ushort Recurrence::recurrenceType() const
0234 {
0235     if (d->mCachedType == rMax) {
0236         d->mCachedType = recurrenceType(defaultRRuleConst());
0237     }
0238     return d->mCachedType;
0239 }
0240 
0241 ushort Recurrence::recurrenceType(const RecurrenceRule *rrule)
0242 {
0243     if (!rrule) {
0244         return rNone;
0245     }
0246     RecurrenceRule::PeriodType type = rrule->recurrenceType();
0247 
0248     // BYSETPOS, BYWEEKNUMBER and BYSECOND were not supported in old versions
0249     if (!rrule->bySetPos().isEmpty() || !rrule->bySeconds().isEmpty() || !rrule->byWeekNumbers().isEmpty()) {
0250         return rOther;
0251     }
0252 
0253     // It wasn't possible to set BYMINUTES, BYHOUR etc. by the old code. So if
0254     // it's set, it's none of the old types
0255     if (!rrule->byMinutes().isEmpty() || !rrule->byHours().isEmpty()) {
0256         return rOther;
0257     }
0258 
0259     // Possible combinations were:
0260     // BYDAY: with WEEKLY, MONTHLY, YEARLY
0261     // BYMONTHDAY: with MONTHLY, YEARLY
0262     // BYMONTH: with YEARLY
0263     // BYYEARDAY: with YEARLY
0264     if ((!rrule->byYearDays().isEmpty() && type != RecurrenceRule::rYearly) || (!rrule->byMonths().isEmpty() && type != RecurrenceRule::rYearly)) {
0265         return rOther;
0266     }
0267     if (!rrule->byDays().isEmpty()) {
0268         if (type != RecurrenceRule::rYearly && type != RecurrenceRule::rMonthly && type != RecurrenceRule::rWeekly) {
0269             return rOther;
0270         }
0271     }
0272 
0273     switch (type) {
0274     case RecurrenceRule::rNone:
0275         return rNone;
0276     case RecurrenceRule::rMinutely:
0277         return rMinutely;
0278     case RecurrenceRule::rHourly:
0279         return rHourly;
0280     case RecurrenceRule::rDaily:
0281         return rDaily;
0282     case RecurrenceRule::rWeekly:
0283         return rWeekly;
0284     case RecurrenceRule::rMonthly: {
0285         if (rrule->byDays().isEmpty()) {
0286             return rMonthlyDay;
0287         } else if (rrule->byMonthDays().isEmpty()) {
0288             return rMonthlyPos;
0289         } else {
0290             return rOther; // both position and date specified
0291         }
0292     }
0293     case RecurrenceRule::rYearly: {
0294         // Possible combinations:
0295         //   rYearlyMonth: [BYMONTH &] BYMONTHDAY
0296         //   rYearlyDay: BYYEARDAY
0297         //   rYearlyPos: [BYMONTH &] BYDAY
0298         if (!rrule->byDays().isEmpty()) {
0299             // can only by rYearlyPos
0300             if (rrule->byMonthDays().isEmpty() && rrule->byYearDays().isEmpty()) {
0301                 return rYearlyPos;
0302             } else {
0303                 return rOther;
0304             }
0305         } else if (!rrule->byYearDays().isEmpty()) {
0306             // Can only be rYearlyDay
0307             if (rrule->byMonths().isEmpty() && rrule->byMonthDays().isEmpty()) {
0308                 return rYearlyDay;
0309             } else {
0310                 return rOther;
0311             }
0312         } else {
0313             return rYearlyMonth;
0314         }
0315     }
0316     default:
0317         return rOther;
0318     }
0319 }
0320 
0321 bool Recurrence::recursOn(const QDate &qd, const QTimeZone &timeZone) const
0322 {
0323     // Don't waste time if date is before the start of the recurrence
0324     if (QDateTime(qd, QTime(23, 59, 59), timeZone) < d->mStartDateTime) {
0325         return false;
0326     }
0327 
0328     // First handle dates. Exrules override
0329     if (std::binary_search(d->mExDates.constBegin(), d->mExDates.constEnd(), qd)) {
0330         return false;
0331     }
0332 
0333     int i;
0334     int end;
0335     // For all-day events a matching exrule excludes the whole day
0336     // since exclusions take precedence over inclusions, we know it can't occur on that day.
0337     if (allDay()) {
0338         for (i = 0, end = d->mExRules.count(); i < end; ++i) {
0339             if (d->mExRules[i]->recursOn(qd, timeZone)) {
0340                 return false;
0341             }
0342         }
0343     }
0344 
0345     if (std::binary_search(d->mRDates.constBegin(), d->mRDates.constEnd(), qd)) {
0346         return true;
0347     }
0348 
0349     // Check if it might recur today at all.
0350     bool recurs = (startDate() == qd);
0351     for (i = 0, end = d->mRDateTimes.count(); i < end && !recurs; ++i) {
0352         recurs = (d->mRDateTimes[i].toTimeZone(timeZone).date() == qd);
0353     }
0354     for (i = 0, end = d->mRRules.count(); i < end && !recurs; ++i) {
0355         recurs = d->mRRules[i]->recursOn(qd, timeZone);
0356     }
0357     // If the event wouldn't recur at all, simply return false, don't check ex*
0358     if (!recurs) {
0359         return false;
0360     }
0361 
0362     // Check if there are any times for this day excluded, either by exdate or exrule:
0363     bool exon = false;
0364     for (i = 0, end = d->mExDateTimes.count(); i < end && !exon; ++i) {
0365         exon = (d->mExDateTimes[i].toTimeZone(timeZone).date() == qd);
0366     }
0367     if (!allDay()) { // we have already checked all-day times above
0368         for (i = 0, end = d->mExRules.count(); i < end && !exon; ++i) {
0369             exon = d->mExRules[i]->recursOn(qd, timeZone);
0370         }
0371     }
0372 
0373     if (!exon) {
0374         // Simple case, nothing on that day excluded, return the value from before
0375         return recurs;
0376     } else {
0377         // Harder part: I don't think there is any way other than to calculate the
0378         // whole list of items for that day.
0379         // TODO: consider whether it would be more efficient to call
0380         //      Rule::recurTimesOn() instead of Rule::recursOn() from the start
0381         TimeList timesForDay(recurTimesOn(qd, timeZone));
0382         return !timesForDay.isEmpty();
0383     }
0384 }
0385 
0386 bool Recurrence::recursAt(const QDateTime &dt) const
0387 {
0388     // Convert to recurrence's time zone for date comparisons, and for more efficient time comparisons
0389     const auto dtrecur = dt.toTimeZone(d->mStartDateTime.timeZone());
0390 
0391     // if it's excluded anyway, don't bother to check if it recurs at all.
0392     if (std::binary_search(d->mExDateTimes.constBegin(), d->mExDateTimes.constEnd(), dtrecur)
0393         || std::binary_search(d->mExDates.constBegin(), d->mExDates.constEnd(), dtrecur.date())) {
0394         return false;
0395     }
0396     int i;
0397     int end;
0398     for (i = 0, end = d->mExRules.count(); i < end; ++i) {
0399         if (d->mExRules[i]->recursAt(dtrecur)) {
0400             return false;
0401         }
0402     }
0403 
0404     // Check explicit recurrences, then rrules.
0405     if (startDateTime() == dtrecur || std::binary_search(d->mRDateTimes.constBegin(), d->mRDateTimes.constEnd(), dtrecur)) {
0406         return true;
0407     }
0408     for (i = 0, end = d->mRRules.count(); i < end; ++i) {
0409         if (d->mRRules[i]->recursAt(dtrecur)) {
0410             return true;
0411         }
0412     }
0413 
0414     return false;
0415 }
0416 
0417 /** Calculates the cumulative end of the whole recurrence (rdates and rrules).
0418     If any rrule is infinite, or the recurrence doesn't have any rrules or
0419     rdates, an invalid date is returned. */
0420 QDateTime Recurrence::endDateTime() const
0421 {
0422     QList<QDateTime> dts;
0423     dts << startDateTime();
0424     if (!d->mRDates.isEmpty()) {
0425         dts << QDateTime(d->mRDates.last(), QTime(0, 0, 0), d->mStartDateTime.timeZone());
0426     }
0427     if (!d->mRDateTimes.isEmpty()) {
0428         dts << d->mRDateTimes.last();
0429     }
0430     for (int i = 0, end = d->mRRules.count(); i < end; ++i) {
0431         auto rl = d->mRRules[i]->endDt();
0432         // if any of the rules is infinite, the whole recurrence is
0433         if (!rl.isValid()) {
0434             return QDateTime();
0435         }
0436         dts << rl;
0437     }
0438     sortAndRemoveDuplicates(dts);
0439     return dts.isEmpty() ? QDateTime() : dts.last();
0440 }
0441 
0442 /** Calculates the cumulative end of the whole recurrence (rdates and rrules).
0443     If any rrule is infinite, or the recurrence doesn't have any rrules or
0444     rdates, an invalid date is returned. */
0445 QDate Recurrence::endDate() const
0446 {
0447     QDateTime end(endDateTime());
0448     return end.isValid() ? end.date() : QDate();
0449 }
0450 
0451 void Recurrence::setEndDate(const QDate &date)
0452 {
0453     QDateTime dt(date, d->mStartDateTime.time(), d->mStartDateTime.timeZone());
0454     if (allDay()) {
0455         dt.setTime(QTime(23, 59, 59));
0456     }
0457     setEndDateTime(dt);
0458 }
0459 
0460 void Recurrence::setEndDateTime(const QDateTime &dateTime)
0461 {
0462     if (d->mRecurReadOnly) {
0463         return;
0464     }
0465     RecurrenceRule *rrule = defaultRRule(true);
0466     if (!rrule) {
0467         return;
0468     }
0469 
0470     // If the recurrence rule has a duration, and we're trying to set an invalid end date,
0471     // we have to skip setting it to avoid setting the field dirty.
0472     // The end date is already invalid since the duration is set and end date/duration
0473     // are mutually exclusive.
0474     // We can't use inequality check below, because endDt() also returns a valid date
0475     // for a duration (it is calculated from the duration).
0476     if (rrule->duration() > 0 && !dateTime.isValid()) {
0477         return;
0478     }
0479 
0480     if (!identical(dateTime, rrule->endDt())) {
0481         rrule->setEndDt(dateTime);
0482         updated();
0483     }
0484 }
0485 
0486 int Recurrence::duration() const
0487 {
0488     RecurrenceRule *rrule = defaultRRuleConst();
0489     return rrule ? rrule->duration() : 0;
0490 }
0491 
0492 int Recurrence::durationTo(const QDateTime &datetime) const
0493 {
0494     // Emulate old behavior: This is just an interface to the first rule!
0495     RecurrenceRule *rrule = defaultRRuleConst();
0496     return rrule ? rrule->durationTo(datetime) : 0;
0497 }
0498 
0499 int Recurrence::durationTo(const QDate &date) const
0500 {
0501     return durationTo(QDateTime(date, QTime(23, 59, 59), d->mStartDateTime.timeZone()));
0502 }
0503 
0504 void Recurrence::setDuration(int duration)
0505 {
0506     if (d->mRecurReadOnly) {
0507         return;
0508     }
0509 
0510     RecurrenceRule *rrule = defaultRRule(true);
0511     if (!rrule) {
0512         return;
0513     }
0514 
0515     if (duration != rrule->duration()) {
0516         rrule->setDuration(duration);
0517         updated();
0518     }
0519 }
0520 
0521 void Recurrence::shiftTimes(const QTimeZone &oldTz, const QTimeZone &newTz)
0522 {
0523     if (d->mRecurReadOnly) {
0524         return;
0525     }
0526 
0527     d->mStartDateTime = d->mStartDateTime.toTimeZone(oldTz);
0528     d->mStartDateTime.setTimeZone(newTz);
0529 
0530     QHash<QDateTime, Period> oldPeriods = d->mRDateTimePeriods;
0531 
0532     for (auto &rDt : d->mRDateTimes) {
0533         auto periodIt = oldPeriods.find(rDt);
0534         periodIt->shiftTimes(oldTz, newTz);
0535         rDt = rDt.toTimeZone(oldTz);
0536         rDt.setTimeZone(newTz);
0537         // Now there are QDateTime objects in the hash? is this shifting times?
0538         d->mRDateTimePeriods.insert(rDt, *periodIt);
0539     }
0540 
0541     for (auto &exDt : d->mExDateTimes) {
0542         exDt = exDt.toTimeZone(oldTz);
0543         exDt.setTimeZone(newTz);
0544     }
0545 
0546     for (auto &rr : d->mRRules) {
0547         rr->shiftTimes(oldTz, newTz);
0548     }
0549 
0550     for (auto exR : d->mExRules) {
0551         exR->shiftTimes(oldTz, newTz);
0552     }
0553 }
0554 
0555 void Recurrence::unsetRecurs()
0556 {
0557     if (d->mRecurReadOnly) {
0558         return;
0559     }
0560     qDeleteAll(d->mRRules);
0561     d->mRRules.clear();
0562     updated();
0563 }
0564 
0565 void Recurrence::clear()
0566 {
0567     if (d->mRecurReadOnly) {
0568         return;
0569     }
0570     qDeleteAll(d->mRRules);
0571     d->mRRules.clear();
0572     qDeleteAll(d->mExRules);
0573     d->mExRules.clear();
0574     d->mRDates.clear();
0575     d->mRDateTimes.clear();
0576     d->mRDateTimePeriods.clear();
0577     d->mExDates.clear();
0578     d->mExDateTimes.clear();
0579     d->mCachedType = rMax;
0580     updated();
0581 }
0582 
0583 void Recurrence::setRecurReadOnly(bool readOnly)
0584 {
0585     d->mRecurReadOnly = readOnly;
0586 }
0587 
0588 bool Recurrence::recurReadOnly() const
0589 {
0590     return d->mRecurReadOnly;
0591 }
0592 
0593 QDate Recurrence::startDate() const
0594 {
0595     return d->mStartDateTime.date();
0596 }
0597 
0598 void Recurrence::setStartDateTime(const QDateTime &start, bool isAllDay)
0599 {
0600     if (d->mRecurReadOnly) {
0601         return;
0602     }
0603     d->mStartDateTime = start;
0604     setAllDay(isAllDay); // set all RRULEs and EXRULEs
0605 
0606     int i;
0607     int end;
0608     for (i = 0, end = d->mRRules.count(); i < end; ++i) {
0609         d->mRRules[i]->setStartDt(start);
0610     }
0611     for (i = 0, end = d->mExRules.count(); i < end; ++i) {
0612         d->mExRules[i]->setStartDt(start);
0613     }
0614     updated();
0615 }
0616 
0617 int Recurrence::frequency() const
0618 {
0619     RecurrenceRule *rrule = defaultRRuleConst();
0620     return rrule ? rrule->frequency() : 0;
0621 }
0622 
0623 // Emulate the old behaviour. Make this methods just an interface to the
0624 // first rrule
0625 void Recurrence::setFrequency(int freq)
0626 {
0627     if (d->mRecurReadOnly || freq <= 0) {
0628         return;
0629     }
0630 
0631     RecurrenceRule *rrule = defaultRRule(true);
0632     if (rrule) {
0633         rrule->setFrequency(freq);
0634     }
0635     updated();
0636 }
0637 
0638 // WEEKLY
0639 
0640 int Recurrence::weekStart() const
0641 {
0642     RecurrenceRule *rrule = defaultRRuleConst();
0643     return rrule ? rrule->weekStart() : 1;
0644 }
0645 
0646 // Emulate the old behavior
0647 QBitArray Recurrence::days() const
0648 {
0649     QBitArray days(7);
0650     days.fill(0);
0651     RecurrenceRule *rrule = defaultRRuleConst();
0652     if (rrule) {
0653         const QList<RecurrenceRule::WDayPos> &bydays = rrule->byDays();
0654         for (int i = 0; i < bydays.size(); ++i) {
0655             if (bydays.at(i).pos() == 0) {
0656                 days.setBit(bydays.at(i).day() - 1);
0657             }
0658         }
0659     }
0660     return days;
0661 }
0662 
0663 // MONTHLY
0664 
0665 // Emulate the old behavior
0666 QList<int> Recurrence::monthDays() const
0667 {
0668     RecurrenceRule *rrule = defaultRRuleConst();
0669     if (rrule) {
0670         return rrule->byMonthDays();
0671     } else {
0672         return QList<int>();
0673     }
0674 }
0675 
0676 // Emulate the old behavior
0677 QList<RecurrenceRule::WDayPos> Recurrence::monthPositions() const
0678 {
0679     RecurrenceRule *rrule = defaultRRuleConst();
0680     return rrule ? rrule->byDays() : QList<RecurrenceRule::WDayPos>();
0681 }
0682 
0683 // YEARLY
0684 
0685 QList<int> Recurrence::yearDays() const
0686 {
0687     RecurrenceRule *rrule = defaultRRuleConst();
0688     return rrule ? rrule->byYearDays() : QList<int>();
0689 }
0690 
0691 QList<int> Recurrence::yearDates() const
0692 {
0693     return monthDays();
0694 }
0695 
0696 QList<int> Recurrence::yearMonths() const
0697 {
0698     RecurrenceRule *rrule = defaultRRuleConst();
0699     return rrule ? rrule->byMonths() : QList<int>();
0700 }
0701 
0702 QList<RecurrenceRule::WDayPos> Recurrence::yearPositions() const
0703 {
0704     return monthPositions();
0705 }
0706 
0707 RecurrenceRule *Recurrence::setNewRecurrenceType(RecurrenceRule::PeriodType type, int freq)
0708 {
0709     if (d->mRecurReadOnly || freq <= 0) {
0710         return nullptr;
0711     }
0712 
0713     // Ignore the call if nothing has change
0714     if (defaultRRuleConst() && defaultRRuleConst()->recurrenceType() == type && frequency() == freq) {
0715         return nullptr;
0716     }
0717 
0718     qDeleteAll(d->mRRules);
0719     d->mRRules.clear();
0720     updated();
0721     RecurrenceRule *rrule = defaultRRule(true);
0722     if (!rrule) {
0723         return nullptr;
0724     }
0725     rrule->setRecurrenceType(type);
0726     rrule->setFrequency(freq);
0727     rrule->setDuration(-1);
0728     return rrule;
0729 }
0730 
0731 void Recurrence::setMinutely(int _rFreq)
0732 {
0733     if (setNewRecurrenceType(RecurrenceRule::rMinutely, _rFreq)) {
0734         updated();
0735     }
0736 }
0737 
0738 void Recurrence::setHourly(int _rFreq)
0739 {
0740     if (setNewRecurrenceType(RecurrenceRule::rHourly, _rFreq)) {
0741         updated();
0742     }
0743 }
0744 
0745 void Recurrence::setDaily(int _rFreq)
0746 {
0747     if (setNewRecurrenceType(RecurrenceRule::rDaily, _rFreq)) {
0748         updated();
0749     }
0750 }
0751 
0752 void Recurrence::setWeekly(int freq, int weekStart)
0753 {
0754     RecurrenceRule *rrule = setNewRecurrenceType(RecurrenceRule::rWeekly, freq);
0755     if (!rrule) {
0756         return;
0757     }
0758     rrule->setWeekStart(weekStart);
0759     updated();
0760 }
0761 
0762 void Recurrence::setWeekly(int freq, const QBitArray &days, int weekStart)
0763 {
0764     setWeekly(freq, weekStart);
0765     addMonthlyPos(0, days);
0766 }
0767 
0768 void Recurrence::addWeeklyDays(const QBitArray &days)
0769 {
0770     addMonthlyPos(0, days);
0771 }
0772 
0773 void Recurrence::setMonthly(int freq)
0774 {
0775     if (setNewRecurrenceType(RecurrenceRule::rMonthly, freq)) {
0776         updated();
0777     }
0778 }
0779 
0780 void Recurrence::addMonthlyPos(short pos, const QBitArray &days)
0781 {
0782     // Allow 53 for yearly!
0783     if (d->mRecurReadOnly || pos > 53 || pos < -53) {
0784         return;
0785     }
0786 
0787     RecurrenceRule *rrule = defaultRRule(false);
0788     if (!rrule) {
0789         return;
0790     }
0791     bool changed = false;
0792     QList<RecurrenceRule::WDayPos> positions = rrule->byDays();
0793 
0794     for (int i = 0; i < 7; ++i) {
0795         if (days.testBit(i)) {
0796             RecurrenceRule::WDayPos p(pos, i + 1);
0797             if (!positions.contains(p)) {
0798                 changed = true;
0799                 positions.append(p);
0800             }
0801         }
0802     }
0803     if (changed) {
0804         rrule->setByDays(positions);
0805         updated();
0806     }
0807 }
0808 
0809 void Recurrence::addMonthlyPos(short pos, ushort day)
0810 {
0811     // Allow 53 for yearly!
0812     if (d->mRecurReadOnly || pos > 53 || pos < -53) {
0813         return;
0814     }
0815 
0816     RecurrenceRule *rrule = defaultRRule(false);
0817     if (!rrule) {
0818         return;
0819     }
0820     QList<RecurrenceRule::WDayPos> positions = rrule->byDays();
0821 
0822     RecurrenceRule::WDayPos p(pos, day);
0823     if (!positions.contains(p)) {
0824         positions.append(p);
0825         setMonthlyPos(positions);
0826     }
0827 }
0828 
0829 void Recurrence::setMonthlyPos(const QList<RecurrenceRule::WDayPos> &monthlyDays)
0830 {
0831     if (d->mRecurReadOnly) {
0832         return;
0833     }
0834 
0835     RecurrenceRule *rrule = defaultRRule(true);
0836     if (!rrule) {
0837         return;
0838     }
0839 
0840     // TODO: sort lists
0841     // the position inside the list has no meaning, so sort the list before testing if it changed
0842 
0843     if (monthlyDays != rrule->byDays()) {
0844         rrule->setByDays(monthlyDays);
0845         updated();
0846     }
0847 }
0848 
0849 void Recurrence::addMonthlyDate(short day)
0850 {
0851     if (d->mRecurReadOnly || day > 31 || day < -31) {
0852         return;
0853     }
0854 
0855     RecurrenceRule *rrule = defaultRRule(true);
0856     if (!rrule) {
0857         return;
0858     }
0859 
0860     QList<int> monthDays = rrule->byMonthDays();
0861     if (!monthDays.contains(day)) {
0862         monthDays.append(day);
0863         setMonthlyDate(monthDays);
0864     }
0865 }
0866 
0867 void Recurrence::setMonthlyDate(const QList<int> &monthlyDays)
0868 {
0869     if (d->mRecurReadOnly) {
0870         return;
0871     }
0872 
0873     RecurrenceRule *rrule = defaultRRule(true);
0874     if (!rrule) {
0875         return;
0876     }
0877 
0878     QList<int> mD(monthlyDays);
0879     QList<int> rbD(rrule->byMonthDays());
0880 
0881     sortAndRemoveDuplicates(mD);
0882     sortAndRemoveDuplicates(rbD);
0883 
0884     if (mD != rbD) {
0885         rrule->setByMonthDays(monthlyDays);
0886         updated();
0887     }
0888 }
0889 
0890 void Recurrence::setYearly(int freq)
0891 {
0892     if (setNewRecurrenceType(RecurrenceRule::rYearly, freq)) {
0893         updated();
0894     }
0895 }
0896 
0897 // Daynumber within year
0898 void Recurrence::addYearlyDay(int day)
0899 {
0900     RecurrenceRule *rrule = defaultRRule(false); // It must already exist!
0901     if (!rrule) {
0902         return;
0903     }
0904 
0905     QList<int> days = rrule->byYearDays();
0906     if (!days.contains(day)) {
0907         days << day;
0908         setYearlyDay(days);
0909     }
0910 }
0911 
0912 void Recurrence::setYearlyDay(const QList<int> &days)
0913 {
0914     RecurrenceRule *rrule = defaultRRule(false); // It must already exist!
0915     if (!rrule) {
0916         return;
0917     }
0918 
0919     QList<int> d(days);
0920     QList<int> bYD(rrule->byYearDays());
0921 
0922     sortAndRemoveDuplicates(d);
0923     sortAndRemoveDuplicates(bYD);
0924 
0925     if (d != bYD) {
0926         rrule->setByYearDays(days);
0927         updated();
0928     }
0929 }
0930 
0931 // day part of date within year
0932 void Recurrence::addYearlyDate(int day)
0933 {
0934     addMonthlyDate(day);
0935 }
0936 
0937 void Recurrence::setYearlyDate(const QList<int> &dates)
0938 {
0939     setMonthlyDate(dates);
0940 }
0941 
0942 // day part of date within year, given as position (n-th weekday)
0943 void Recurrence::addYearlyPos(short pos, const QBitArray &days)
0944 {
0945     addMonthlyPos(pos, days);
0946 }
0947 
0948 void Recurrence::setYearlyPos(const QList<RecurrenceRule::WDayPos> &days)
0949 {
0950     setMonthlyPos(days);
0951 }
0952 
0953 // month part of date within year
0954 void Recurrence::addYearlyMonth(short month)
0955 {
0956     if (d->mRecurReadOnly || month < 1 || month > 12) {
0957         return;
0958     }
0959 
0960     RecurrenceRule *rrule = defaultRRule(false);
0961     if (!rrule) {
0962         return;
0963     }
0964 
0965     QList<int> months = rrule->byMonths();
0966     if (!months.contains(month)) {
0967         months << month;
0968         setYearlyMonth(months);
0969     }
0970 }
0971 
0972 void Recurrence::setYearlyMonth(const QList<int> &months)
0973 {
0974     if (d->mRecurReadOnly) {
0975         return;
0976     }
0977 
0978     RecurrenceRule *rrule = defaultRRule(false);
0979     if (!rrule) {
0980         return;
0981     }
0982 
0983     QList<int> m(months);
0984     QList<int> bM(rrule->byMonths());
0985 
0986     sortAndRemoveDuplicates(m);
0987     sortAndRemoveDuplicates(bM);
0988 
0989     if (m != bM) {
0990         rrule->setByMonths(months);
0991         updated();
0992     }
0993 }
0994 
0995 TimeList Recurrence::recurTimesOn(const QDate &date, const QTimeZone &timeZone) const
0996 {
0997     // qCDebug(KCALCORE_LOG) << "recurTimesOn(" << date << ")";
0998     int i;
0999     int end;
1000     TimeList times;
1001 
1002     // The whole day is excepted
1003     if (std::binary_search(d->mExDates.constBegin(), d->mExDates.constEnd(), date)) {
1004         return times;
1005     }
1006 
1007     // EXRULE takes precedence over RDATE entries, so for all-day events,
1008     // a matching excule also excludes the whole day automatically
1009     if (allDay()) {
1010         for (i = 0, end = d->mExRules.count(); i < end; ++i) {
1011             if (d->mExRules[i]->recursOn(date, timeZone)) {
1012                 return times;
1013             }
1014         }
1015     }
1016 
1017     QDateTime dt = startDateTime().toTimeZone(timeZone);
1018     if (dt.date() == date) {
1019         times << dt.time();
1020     }
1021 
1022     bool foundDate = false;
1023     for (i = 0, end = d->mRDateTimes.count(); i < end; ++i) {
1024         dt = d->mRDateTimes[i].toTimeZone(timeZone);
1025         if (dt.date() == date) {
1026             times << dt.time();
1027             foundDate = true;
1028         } else if (foundDate) {
1029             break; // <= Assume that the rdatetime list is sorted
1030         }
1031     }
1032     for (i = 0, end = d->mRRules.count(); i < end; ++i) {
1033         times += d->mRRules[i]->recurTimesOn(date, timeZone);
1034     }
1035     sortAndRemoveDuplicates(times);
1036 
1037     foundDate = false;
1038     TimeList extimes;
1039     for (i = 0, end = d->mExDateTimes.count(); i < end; ++i) {
1040         dt = d->mExDateTimes[i].toTimeZone(timeZone);
1041         if (dt.date() == date) {
1042             extimes << dt.time();
1043             foundDate = true;
1044         } else if (foundDate) {
1045             break;
1046         }
1047     }
1048     if (!allDay()) { // we have already checked all-day times above
1049         for (i = 0, end = d->mExRules.count(); i < end; ++i) {
1050             extimes += d->mExRules[i]->recurTimesOn(date, timeZone);
1051         }
1052     }
1053     sortAndRemoveDuplicates(extimes);
1054     inplaceSetDifference(times, extimes);
1055     return times;
1056 }
1057 
1058 QList<QDateTime> Recurrence::timesInInterval(const QDateTime &start, const QDateTime &end) const
1059 {
1060     int i;
1061     int count;
1062     QList<QDateTime> times;
1063     for (i = 0, count = d->mRRules.count(); i < count; ++i) {
1064         times += d->mRRules[i]->timesInInterval(start, end);
1065     }
1066 
1067     // add rdatetimes that fit in the interval
1068     for (i = 0, count = d->mRDateTimes.count(); i < count; ++i) {
1069         if (d->mRDateTimes[i] >= start && d->mRDateTimes[i] <= end) {
1070             times += d->mRDateTimes[i];
1071         }
1072     }
1073 
1074     // add rdates that fit in the interval
1075     QDateTime kdt = d->mStartDateTime;
1076     for (i = 0, count = d->mRDates.count(); i < count; ++i) {
1077         kdt.setDate(d->mRDates[i]);
1078         if (kdt >= start && kdt <= end) {
1079             times += kdt;
1080         }
1081     }
1082 
1083     // Recurrence::timesInInterval(...) doesn't explicitly add mStartDateTime to the list
1084     // of times to be returned. It calls mRRules[i]->timesInInterval(...) which include
1085     // mStartDateTime.
1086     // So, If we have rdates/rdatetimes but don't have any rrule we must explicitly
1087     // add mStartDateTime to the list, otherwise we won't see the first occurrence.
1088     if ((!d->mRDates.isEmpty() || !d->mRDateTimes.isEmpty()) && d->mRRules.isEmpty() && start <= d->mStartDateTime && end >= d->mStartDateTime) {
1089         times += d->mStartDateTime;
1090     }
1091 
1092     sortAndRemoveDuplicates(times);
1093 
1094     // Remove excluded times
1095     int idt = 0;
1096     int enddt = times.count();
1097     for (i = 0, count = d->mExDates.count(); i < count && idt < enddt; ++i) {
1098         while (idt < enddt && times[idt].date() < d->mExDates[i]) {
1099             ++idt;
1100         }
1101         while (idt < enddt && times[idt].date() == d->mExDates[i]) {
1102             times.removeAt(idt);
1103             --enddt;
1104         }
1105     }
1106     QList<QDateTime> extimes;
1107     for (i = 0, count = d->mExRules.count(); i < count; ++i) {
1108         extimes += d->mExRules[i]->timesInInterval(start, end);
1109     }
1110     extimes += d->mExDateTimes;
1111     sortAndRemoveDuplicates(extimes);
1112     inplaceSetDifference(times, extimes);
1113     return times;
1114 }
1115 
1116 QDateTime Recurrence::getNextDateTime(const QDateTime &preDateTime) const
1117 {
1118     QDateTime nextDT = preDateTime;
1119     // prevent infinite loops, e.g. when an exrule extinguishes an rrule (e.g.
1120     // the exrule is identical to the rrule). If an occurrence is found, break
1121     // out of the loop by returning that QDateTime
1122     // TODO_Recurrence: Is a loop counter of 1000 really okay? I mean for secondly
1123     // recurrence, an exdate might exclude more than 1000 intervals!
1124     int loop = 0;
1125     while (loop < 1000) {
1126         // Outline of the algo:
1127         //   1) Find the next date/time after preDateTime when the event could recur
1128         //     1.0) Add the start date if it's after preDateTime
1129         //     1.1) Use the next occurrence from the explicit RDATE lists
1130         //     1.2) Add the next recurrence for each of the RRULEs
1131         //   2) Take the earliest recurrence of these = QDateTime nextDT
1132         //   3) If that date/time is not excluded, either explicitly by an EXDATE or
1133         //      by an EXRULE, return nextDT as the next date/time of the recurrence
1134         //   4) If it's excluded, start all at 1), but starting at nextDT (instead
1135         //      of preDateTime). Loop at most 1000 times.
1136         ++loop;
1137         // First, get the next recurrence from the RDate lists
1138         QList<QDateTime> dates;
1139         if (nextDT < startDateTime()) {
1140             dates << startDateTime();
1141         }
1142 
1143         // Assume that the rdatetime list is sorted
1144         const auto it = std::upper_bound(d->mRDateTimes.constBegin(), d->mRDateTimes.constEnd(), nextDT);
1145         if (it != d->mRDateTimes.constEnd()) {
1146             dates << *it;
1147         }
1148 
1149         QDateTime kdt(startDateTime());
1150         for (const auto &date : std::as_const(d->mRDates)) {
1151             kdt.setDate(date);
1152             if (kdt > nextDT) {
1153                 dates << kdt;
1154                 break;
1155             }
1156         }
1157 
1158         // Add the next occurrences from all RRULEs.
1159         for (const auto &rule : std::as_const(d->mRRules)) {
1160             QDateTime dt = rule->getNextDate(nextDT);
1161             if (dt.isValid()) {
1162                 dates << dt;
1163             }
1164         }
1165 
1166         // Take the first of these (all others can't be used later on)
1167         sortAndRemoveDuplicates(dates);
1168         if (dates.isEmpty()) {
1169             return QDateTime();
1170         }
1171         nextDT = dates.first();
1172 
1173         // Check if that date/time is excluded explicitly or by an exrule:
1174         if (!std::binary_search(d->mExDates.constBegin(), d->mExDates.constEnd(), nextDT.date())
1175             && !std::binary_search(d->mExDateTimes.constBegin(), d->mExDateTimes.constEnd(), nextDT)) {
1176             bool allowed = true;
1177             for (const auto &rule : std::as_const(d->mExRules)) {
1178                 allowed = allowed && !rule->recursAt(nextDT);
1179             }
1180             if (allowed) {
1181                 return nextDT;
1182             }
1183         }
1184     }
1185 
1186     // Couldn't find a valid occurrences in 1000 loops, something is wrong!
1187     return QDateTime();
1188 }
1189 
1190 QDateTime Recurrence::getPreviousDateTime(const QDateTime &afterDateTime) const
1191 {
1192     QDateTime prevDT = afterDateTime;
1193     // prevent infinite loops, e.g. when an exrule extinguishes an rrule (e.g.
1194     // the exrule is identical to the rrule). If an occurrence is found, break
1195     // out of the loop by returning that QDateTime
1196     int loop = 0;
1197     while (loop < 1000) {
1198         // Outline of the algo:
1199         //   1) Find the next date/time after preDateTime when the event could recur
1200         //     1.1) Use the next occurrence from the explicit RDATE lists
1201         //     1.2) Add the next recurrence for each of the RRULEs
1202         //   2) Take the earliest recurrence of these = QDateTime nextDT
1203         //   3) If that date/time is not excluded, either explicitly by an EXDATE or
1204         //      by an EXRULE, return nextDT as the next date/time of the recurrence
1205         //   4) If it's excluded, start all at 1), but starting at nextDT (instead
1206         //      of preDateTime). Loop at most 1000 times.
1207         ++loop;
1208         // First, get the next recurrence from the RDate lists
1209         QList<QDateTime> dates;
1210         if (prevDT > startDateTime()) {
1211             dates << startDateTime();
1212         }
1213 
1214         const auto it = strictLowerBound(d->mRDateTimes.constBegin(), d->mRDateTimes.constEnd(), prevDT);
1215         if (it != d->mRDateTimes.constEnd()) {
1216             dates << *it;
1217         }
1218 
1219         QDateTime kdt(startDateTime());
1220         for (const auto &date : std::as_const(d->mRDates)) {
1221             kdt.setDate(date);
1222             if (kdt < prevDT) {
1223                 dates << kdt;
1224                 break;
1225             }
1226         }
1227 
1228         // Add the previous occurrences from all RRULEs.
1229         for (const auto &rule : std::as_const(d->mRRules)) {
1230             QDateTime dt = rule->getPreviousDate(prevDT);
1231             if (dt.isValid()) {
1232                 dates << dt;
1233             }
1234         }
1235 
1236         // Take the last of these (all others can't be used later on)
1237         sortAndRemoveDuplicates(dates);
1238         if (dates.isEmpty()) {
1239             return QDateTime();
1240         }
1241         prevDT = dates.last();
1242 
1243         // Check if that date/time is excluded explicitly or by an exrule:
1244         if (!std::binary_search(d->mExDates.constBegin(), d->mExDates.constEnd(), prevDT.date())
1245             && !std::binary_search(d->mExDateTimes.constBegin(), d->mExDateTimes.constEnd(), prevDT)) {
1246             bool allowed = true;
1247             for (const auto &rule : std::as_const(d->mExRules)) {
1248                 allowed = allowed && !rule->recursAt(prevDT);
1249             }
1250             if (allowed) {
1251                 return prevDT;
1252             }
1253         }
1254     }
1255 
1256     // Couldn't find a valid occurrences in 1000 loops, something is wrong!
1257     return QDateTime();
1258 }
1259 
1260 /***************************** PROTECTED FUNCTIONS ***************************/
1261 
1262 RecurrenceRule::List Recurrence::rRules() const
1263 {
1264     return d->mRRules;
1265 }
1266 
1267 void Recurrence::addRRule(RecurrenceRule *rrule)
1268 {
1269     if (d->mRecurReadOnly || !rrule) {
1270         return;
1271     }
1272 
1273     rrule->setAllDay(d->mAllDay);
1274     d->mRRules.append(rrule);
1275     rrule->addObserver(this);
1276     updated();
1277 }
1278 
1279 void Recurrence::removeRRule(RecurrenceRule *rrule)
1280 {
1281     if (d->mRecurReadOnly) {
1282         return;
1283     }
1284 
1285     d->mRRules.removeAll(rrule);
1286     rrule->removeObserver(this);
1287     updated();
1288 }
1289 
1290 void Recurrence::deleteRRule(RecurrenceRule *rrule)
1291 {
1292     if (d->mRecurReadOnly) {
1293         return;
1294     }
1295 
1296     d->mRRules.removeAll(rrule);
1297     delete rrule;
1298     updated();
1299 }
1300 
1301 RecurrenceRule::List Recurrence::exRules() const
1302 {
1303     return d->mExRules;
1304 }
1305 
1306 void Recurrence::addExRule(RecurrenceRule *exrule)
1307 {
1308     if (d->mRecurReadOnly || !exrule) {
1309         return;
1310     }
1311 
1312     exrule->setAllDay(d->mAllDay);
1313     d->mExRules.append(exrule);
1314     exrule->addObserver(this);
1315     updated();
1316 }
1317 
1318 void Recurrence::removeExRule(RecurrenceRule *exrule)
1319 {
1320     if (d->mRecurReadOnly) {
1321         return;
1322     }
1323 
1324     d->mExRules.removeAll(exrule);
1325     exrule->removeObserver(this);
1326     updated();
1327 }
1328 
1329 void Recurrence::deleteExRule(RecurrenceRule *exrule)
1330 {
1331     if (d->mRecurReadOnly) {
1332         return;
1333     }
1334 
1335     d->mExRules.removeAll(exrule);
1336     delete exrule;
1337     updated();
1338 }
1339 
1340 QList<QDateTime> Recurrence::rDateTimes() const
1341 {
1342     return d->mRDateTimes;
1343 }
1344 
1345 void Recurrence::setRDateTimes(const QList<QDateTime> &rdates)
1346 {
1347     if (d->mRecurReadOnly) {
1348         return;
1349     }
1350 
1351     d->mRDateTimes = rdates;
1352     sortAndRemoveDuplicates(d->mRDateTimes);
1353     d->mRDateTimePeriods.clear();
1354     updated();
1355 }
1356 
1357 void Recurrence::addRDateTime(const QDateTime &rdate)
1358 {
1359     if (d->mRecurReadOnly) {
1360         return;
1361     }
1362 
1363     setInsert(d->mRDateTimes, rdate);
1364     updated();
1365 }
1366 
1367 void Recurrence::addRDateTimePeriod(const Period &period)
1368 {
1369     if (d->mRecurReadOnly) {
1370         return;
1371     }
1372 
1373     setInsert(d->mRDateTimes, period.start());
1374     d->mRDateTimePeriods.insert(period.start(), period);
1375     updated();
1376 }
1377 
1378 Period Recurrence::rDateTimePeriod(const QDateTime &rdate) const
1379 {
1380     return d->mRDateTimePeriods.value(rdate);
1381 }
1382 
1383 DateList Recurrence::rDates() const
1384 {
1385     return d->mRDates;
1386 }
1387 
1388 void Recurrence::setRDates(const DateList &rdates)
1389 {
1390     if (d->mRecurReadOnly) {
1391         return;
1392     }
1393 
1394     d->mRDates = rdates;
1395     sortAndRemoveDuplicates(d->mRDates);
1396     updated();
1397 }
1398 
1399 void Recurrence::addRDate(const QDate &rdate)
1400 {
1401     if (d->mRecurReadOnly) {
1402         return;
1403     }
1404 
1405     setInsert(d->mRDates, rdate);
1406     updated();
1407 }
1408 
1409 QList<QDateTime> Recurrence::exDateTimes() const
1410 {
1411     return d->mExDateTimes;
1412 }
1413 
1414 void Recurrence::setExDateTimes(const QList<QDateTime> &exdates)
1415 {
1416     if (d->mRecurReadOnly) {
1417         return;
1418     }
1419 
1420     d->mExDateTimes = exdates;
1421     sortAndRemoveDuplicates(d->mExDateTimes);
1422 }
1423 
1424 void Recurrence::addExDateTime(const QDateTime &exdate)
1425 {
1426     if (d->mRecurReadOnly) {
1427         return;
1428     }
1429 
1430     setInsert(d->mExDateTimes, exdate);
1431     updated();
1432 }
1433 
1434 DateList Recurrence::exDates() const
1435 {
1436     return d->mExDates;
1437 }
1438 
1439 void Recurrence::setExDates(const DateList &exdates)
1440 {
1441     if (d->mRecurReadOnly) {
1442         return;
1443     }
1444 
1445     DateList l = exdates;
1446     sortAndRemoveDuplicates(l);
1447 
1448     if (d->mExDates != l) {
1449         d->mExDates = l;
1450         updated();
1451     }
1452 }
1453 
1454 void Recurrence::addExDate(const QDate &exdate)
1455 {
1456     if (d->mRecurReadOnly) {
1457         return;
1458     }
1459 
1460     setInsert(d->mExDates, exdate);
1461     updated();
1462 }
1463 
1464 void Recurrence::recurrenceChanged(RecurrenceRule *)
1465 {
1466     updated();
1467 }
1468 
1469 // %%%%%%%%%%%%%%%%%% end:Recurrencerule %%%%%%%%%%%%%%%%%%
1470 
1471 void Recurrence::dump() const
1472 {
1473     int i;
1474     int count = d->mRRules.count();
1475     qCDebug(KCALCORE_LOG) << "  -)" << count << "RRULEs:";
1476     for (i = 0; i < count; ++i) {
1477         qCDebug(KCALCORE_LOG) << "    -) RecurrenceRule: ";
1478         d->mRRules[i]->dump();
1479     }
1480     count = d->mExRules.count();
1481     qCDebug(KCALCORE_LOG) << "  -)" << count << "EXRULEs:";
1482     for (i = 0; i < count; ++i) {
1483         qCDebug(KCALCORE_LOG) << "    -) ExceptionRule :";
1484         d->mExRules[i]->dump();
1485     }
1486 
1487     count = d->mRDates.count();
1488     qCDebug(KCALCORE_LOG) << "  -)" << count << "Recurrence Dates:";
1489     for (i = 0; i < count; ++i) {
1490         qCDebug(KCALCORE_LOG) << "    " << d->mRDates[i];
1491     }
1492     count = d->mRDateTimes.count();
1493     qCDebug(KCALCORE_LOG) << "  -)" << count << "Recurrence Date/Times:";
1494     for (i = 0; i < count; ++i) {
1495         qCDebug(KCALCORE_LOG) << "    " << d->mRDateTimes[i];
1496     }
1497     count = d->mExDates.count();
1498     qCDebug(KCALCORE_LOG) << "  -)" << count << "Exceptions Dates:";
1499     for (i = 0; i < count; ++i) {
1500         qCDebug(KCALCORE_LOG) << "    " << d->mExDates[i];
1501     }
1502     count = d->mExDateTimes.count();
1503     qCDebug(KCALCORE_LOG) << "  -)" << count << "Exception Date/Times:";
1504     for (i = 0; i < count; ++i) {
1505         qCDebug(KCALCORE_LOG) << "    " << d->mExDateTimes[i];
1506     }
1507 }
1508 
1509 Recurrence::RecurrenceObserver::~RecurrenceObserver()
1510 {
1511 }
1512 
1513 KCALENDARCORE_EXPORT QDataStream &KCalendarCore::operator<<(QDataStream &out, KCalendarCore::Recurrence *r)
1514 {
1515     if (!r) {
1516         return out;
1517     }
1518 
1519     serializeQDateTimeList(out, r->d->mRDateTimes);
1520     out << (qint32)r->d->mRDateTimePeriods.size();
1521     for (auto it = r->d->mRDateTimePeriods.cbegin(); it != r->d->mRDateTimePeriods.cend(); ++it) {
1522         out << it.key() << it.value();
1523     }
1524     serializeQDateTimeList(out, r->d->mExDateTimes);
1525     out << r->d->mRDates;
1526     serializeQDateTimeAsKDateTime(out, r->d->mStartDateTime);
1527     out << r->d->mCachedType << r->d->mAllDay << r->d->mRecurReadOnly << r->d->mExDates << (qint32)r->d->mExRules.count() << (qint32)r->d->mRRules.count();
1528 
1529     for (RecurrenceRule *rule : std::as_const(r->d->mExRules)) {
1530         out << rule;
1531     }
1532 
1533     for (RecurrenceRule *rule : std::as_const(r->d->mRRules)) {
1534         out << rule;
1535     }
1536 
1537     return out;
1538 }
1539 
1540 KCALENDARCORE_EXPORT QDataStream &KCalendarCore::operator>>(QDataStream &in, KCalendarCore::Recurrence *r)
1541 {
1542     if (!r) {
1543         return in;
1544     }
1545 
1546     int rruleCount;
1547     int exruleCount;
1548     int size;
1549 
1550     deserializeQDateTimeList(in, r->d->mRDateTimes);
1551     in >> size;
1552     r->d->mRDateTimePeriods.clear();
1553     r->d->mRDateTimePeriods.reserve(size);
1554     for (int i = 0; i < size; ++i) {
1555         QDateTime start;
1556         Period period;
1557         in >> start >> period;
1558         r->d->mRDateTimes << start;
1559         r->d->mRDateTimePeriods.insert(start, period);
1560     }
1561     deserializeQDateTimeList(in, r->d->mExDateTimes);
1562     in >> r->d->mRDates;
1563     deserializeKDateTimeAsQDateTime(in, r->d->mStartDateTime);
1564     in >> r->d->mCachedType >> r->d->mAllDay >> r->d->mRecurReadOnly >> r->d->mExDates >> exruleCount >> rruleCount;
1565 
1566     r->d->mExRules.clear();
1567     r->d->mRRules.clear();
1568 
1569     for (int i = 0; i < exruleCount; ++i) {
1570         RecurrenceRule *rule = new RecurrenceRule();
1571         rule->addObserver(r);
1572         in >> rule;
1573         r->d->mExRules.append(rule);
1574     }
1575 
1576     for (int i = 0; i < rruleCount; ++i) {
1577         RecurrenceRule *rule = new RecurrenceRule();
1578         rule->addObserver(r);
1579         in >> rule;
1580         r->d->mRRules.append(rule);
1581     }
1582 
1583     return in;
1584 }