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 }