File indexing completed on 2024-04-21 03:52:52

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