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