File indexing completed on 2024-04-28 07:41:19
0001 /* 0002 This file is part of the kcalcore library. 0003 0004 SPDX-FileCopyrightText: 2013 Christian Mollekopf <mollekopf@kolabsys.com> 0005 0006 SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 /** 0009 @file 0010 This file is part of the API for handling calendar data and 0011 defines the OccurrenceIterator class. 0012 0013 @brief 0014 This class provides an iterator to iterate over all occurrences of incidences. 0015 0016 @author Christian Mollekopf \<mollekopf@kolabsys.com\> 0017 */ 0018 0019 #include "occurrenceiterator.h" 0020 #include "calendar.h" 0021 #include "calfilter.h" 0022 0023 #include <QDate> 0024 0025 using namespace KCalendarCore; 0026 0027 /** 0028 Private class that helps to provide binary compatibility between releases. 0029 @internal 0030 */ 0031 //@cond PRIVATE 0032 class Q_DECL_HIDDEN KCalendarCore::OccurrenceIterator::Private 0033 { 0034 public: 0035 Private(OccurrenceIterator *qq) 0036 : q(qq) 0037 , occurrenceIt(occurrenceList) 0038 { 0039 } 0040 0041 OccurrenceIterator *q; 0042 QDateTime start; 0043 QDateTime end; 0044 0045 struct Occurrence { 0046 Occurrence() 0047 { 0048 } 0049 0050 Occurrence(const Incidence::Ptr &i, const QDateTime &recurrenceId, 0051 const QDateTime &startDate, const QDateTime &endDate) 0052 : incidence(i) 0053 , recurrenceId(recurrenceId) 0054 , startDate(startDate) 0055 , endDate(endDate) 0056 { 0057 } 0058 0059 Incidence::Ptr incidence; 0060 QDateTime recurrenceId; 0061 QDateTime startDate; 0062 QDateTime endDate; 0063 }; 0064 QList<Occurrence> occurrenceList; 0065 QListIterator<Occurrence> occurrenceIt; 0066 Occurrence current; 0067 0068 /* 0069 * KCalendarCore::CalFilter can't handle individual occurrences. 0070 * When filtering completed to-dos, the CalFilter doesn't hide 0071 * them if it's a recurring to-do. 0072 */ 0073 bool occurrenceIsHidden(const Calendar &calendar, const Incidence::Ptr &inc, const QDateTime &occurrenceDate) 0074 { 0075 if ((inc->type() == Incidence::TypeTodo) && calendar.filter() && (calendar.filter()->criteria() & KCalendarCore::CalFilter::HideCompletedTodos)) { 0076 if (inc->recurs()) { 0077 const Todo::Ptr todo = inc.staticCast<Todo>(); 0078 if (todo && (occurrenceDate < todo->dtDue())) { 0079 return true; 0080 } 0081 } else if (inc->hasRecurrenceId()) { 0082 const Todo::Ptr mainTodo = calendar.todo(inc->uid()); 0083 if (mainTodo && mainTodo->isCompleted()) { 0084 return true; 0085 } 0086 } 0087 } 0088 return false; 0089 } 0090 0091 QDateTime occurrenceEnd(const Incidence::Ptr &inc, const QDateTime &start) 0092 { 0093 if (inc->hasDuration()) { 0094 return inc->duration().end(start); 0095 } else { 0096 const QDateTime end = inc->dateTime(Incidence::RoleEnd); 0097 if (end.isValid()) { 0098 const Duration elapsed(inc->dtStart(), end, Duration::Seconds); 0099 return elapsed.end(start); 0100 } 0101 } 0102 return QDateTime(); 0103 } 0104 0105 void setupIterator(const Calendar &calendar, const Incidence::List &incidences) 0106 { 0107 for (const Incidence::Ptr &inc : std::as_const(incidences)) { 0108 if (inc->hasRecurrenceId()) { 0109 continue; 0110 } 0111 if (inc->recurs()) { 0112 QHash<QDateTime, Incidence::Ptr> recurrenceIds; 0113 QDateTime incidenceRecStart = inc->dateTime(Incidence::RoleRecurrenceStart); 0114 // const bool isAllDay = inc->allDay(); 0115 const auto lstInstances = calendar.instances(inc); 0116 for (const Incidence::Ptr &exception : lstInstances) { 0117 if (incidenceRecStart.isValid()) { 0118 recurrenceIds.insert(exception->recurrenceId().toTimeZone(incidenceRecStart.timeZone()), exception); 0119 } 0120 } 0121 const auto occurrences = inc->recurrence()->timesInInterval(start, end); 0122 Incidence::Ptr incidence(inc); 0123 Incidence::Ptr lastInc(inc); 0124 qint64 offset(0); 0125 qint64 lastOffset(0); 0126 QDateTime occurrenceStartDate; 0127 for (const auto &recurrenceId : std::as_const(occurrences)) { 0128 occurrenceStartDate = recurrenceId; 0129 0130 bool resetIncidence = false; 0131 if (recurrenceIds.contains(recurrenceId)) { 0132 // TODO: exclude exceptions where the start/end is not within 0133 // (so the occurrence of the recurrence is omitted, but no exception is added) 0134 incidence = recurrenceIds.value(recurrenceId); 0135 occurrenceStartDate = incidence->dtStart(); 0136 resetIncidence = !incidence->thisAndFuture(); 0137 offset = incidence->recurrenceId().secsTo(incidence->dtStart()); 0138 if (incidence->thisAndFuture()) { 0139 lastInc = incidence; 0140 lastOffset = offset; 0141 } 0142 } else if (inc != incidence) { // thisAndFuture exception is active 0143 occurrenceStartDate = occurrenceStartDate.addSecs(offset); 0144 } 0145 0146 if (!occurrenceIsHidden(calendar, incidence, occurrenceStartDate)) { 0147 const Period period = inc->recurrence()->rDateTimePeriod(occurrenceStartDate); 0148 if (period.isValid()) { 0149 occurrenceList << Private::Occurrence(incidence, recurrenceId, occurrenceStartDate, period.end()); 0150 } else { 0151 occurrenceList << Private::Occurrence(incidence, recurrenceId, occurrenceStartDate, occurrenceEnd(incidence, occurrenceStartDate)); 0152 } 0153 } 0154 0155 if (resetIncidence) { 0156 incidence = lastInc; 0157 offset = lastOffset; 0158 } 0159 } 0160 } else { 0161 occurrenceList << Private::Occurrence(inc, {}, inc->dtStart(), 0162 inc->dateTime(Incidence::RoleEnd)); 0163 } 0164 } 0165 occurrenceIt = QListIterator<Private::Occurrence>(occurrenceList); 0166 } 0167 }; 0168 //@endcond 0169 0170 /** 0171 * Right now there is little point in the iterator, but: 0172 * With an iterator it should be possible to solve this more memory efficiently 0173 * and with immediate results at the beginning of the selected timeframe. 0174 * Either all events are iterated simoulatneously, resulting in occurrences 0175 * of all events in parallel in the correct time-order, or incidence after 0176 * incidence, which would be even more efficient. 0177 * 0178 * By making this class a friend of calendar, we could also use the internally 0179 * available data structures. 0180 */ 0181 OccurrenceIterator::OccurrenceIterator(const Calendar &calendar, const QDateTime &start, const QDateTime &end) 0182 : d(new KCalendarCore::OccurrenceIterator::Private(this)) 0183 { 0184 d->start = start; 0185 d->end = end; 0186 0187 Event::List events = calendar.rawEvents(start.date(), end.date(), start.timeZone()); 0188 if (calendar.filter()) { 0189 calendar.filter()->apply(&events); 0190 } 0191 0192 Todo::List todos = calendar.rawTodos(start.date(), end.date(), start.timeZone()); 0193 if (calendar.filter()) { 0194 calendar.filter()->apply(&todos); 0195 } 0196 0197 Journal::List journals; 0198 const Journal::List allJournals = calendar.rawJournals(); 0199 for (const KCalendarCore::Journal::Ptr &journal : allJournals) { 0200 const QDate journalStart = journal->dtStart().toTimeZone(start.timeZone()).date(); 0201 if (journal->dtStart().isValid() && journalStart >= start.date() && journalStart <= end.date()) { 0202 journals << journal; 0203 } 0204 } 0205 0206 if (calendar.filter()) { 0207 calendar.filter()->apply(&journals); 0208 } 0209 0210 const Incidence::List incidences = KCalendarCore::Calendar::mergeIncidenceList(events, todos, journals); 0211 d->setupIterator(calendar, incidences); 0212 } 0213 0214 OccurrenceIterator::OccurrenceIterator(const Calendar &calendar, const Incidence::Ptr &incidence, const QDateTime &start, const QDateTime &end) 0215 : d(new KCalendarCore::OccurrenceIterator::Private(this)) 0216 { 0217 Q_ASSERT(incidence); 0218 d->start = start; 0219 d->end = end; 0220 d->setupIterator(calendar, Incidence::List() << incidence); 0221 } 0222 0223 OccurrenceIterator::~OccurrenceIterator() 0224 { 0225 } 0226 0227 bool OccurrenceIterator::hasNext() const 0228 { 0229 return d->occurrenceIt.hasNext(); 0230 } 0231 0232 void OccurrenceIterator::next() 0233 { 0234 d->current = d->occurrenceIt.next(); 0235 } 0236 0237 Incidence::Ptr OccurrenceIterator::incidence() const 0238 { 0239 return d->current.incidence; 0240 } 0241 0242 QDateTime OccurrenceIterator::occurrenceStartDate() const 0243 { 0244 return d->current.startDate; 0245 } 0246 0247 QDateTime OccurrenceIterator::occurrenceEndDate() const 0248 { 0249 return d->current.endDate; 0250 } 0251 0252 QDateTime OccurrenceIterator::recurrenceId() const 0253 { 0254 return d->current.recurrenceId; 0255 }