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 }