diff --git a/kcalcore/occurrenceiterator.cpp b/kcalcore/occurrenceiterator.cpp index 4fbfe87ad..03dbaf450 100644 --- a/kcalcore/occurrenceiterator.cpp +++ b/kcalcore/occurrenceiterator.cpp @@ -1,248 +1,258 @@ /* This file is part of the kcalcore library. Copyright (C) 2013 Christian Mollekopf This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** @file This file is part of the API for handling calendar data and defines the OccurrenceIterator class. @brief This class provides an iterator to iterate over all occurrences of incidences. @author Christian Mollekopf \ */ #include "occurrenceiterator.h" #include "calendar.h" #include "calfilter.h" #include #include using namespace KCalCore; /** Private class that helps to provide binary compatibility between releases. @internal */ //@cond PRIVATE class KCalCore::OccurrenceIterator::Private { public: Private(OccurrenceIterator *qq) : q(qq), occurrenceIt(occurrenceList) { } OccurrenceIterator *q; KDateTime start; KDateTime end; struct Occurrence { Occurrence() { } - Occurrence(const Incidence::Ptr &i, const KDateTime &d) - : incidence(i), date(d) + Occurrence(const Incidence::Ptr &i, const KDateTime &recurrenceId, const KDateTime &startDate) + : incidence(i), recurrenceId(recurrenceId), startDate(startDate) { } Incidence::Ptr incidence; - KDateTime date; + KDateTime recurrenceId; + KDateTime startDate; }; QList occurrenceList; QListIterator occurrenceIt; Occurrence current; /* * KCalCore::CalFilter can't handle individual occurrences. * When filtering completed to-dos, the CalFilter doesn't hide * them if it's a recurring to-do. */ bool occurrenceIsHidden(const Calendar &calendar, const Incidence::Ptr &inc, const KDateTime &occurrenceDate) { if ((inc->type() == Incidence::TypeTodo) && calendar.filter() && (calendar.filter()->criteria() & KCalCore::CalFilter::HideCompletedTodos)) { if (inc->recurs()) { const Todo::Ptr todo = inc.staticCast(); if (todo && (occurrenceDate < todo->dtDue())) { return true; } } else if (inc->hasRecurrenceId()) { const Todo::Ptr mainTodo = calendar.todo(inc->uid()); if (mainTodo && mainTodo->isCompleted()) { return true; } } } return false; } void setupIterator(const Calendar &calendar, const Incidence::List &incidences) { foreach(const Incidence::Ptr &inc, incidences) { if (inc->hasRecurrenceId()) { continue; } if (inc->recurs()) { QHash recurrenceIds; KDateTime incidenceRecStart = inc->dateTime(Incidence::RoleRecurrenceStart); foreach(const Incidence::Ptr &exception, calendar.instances(inc)) { if (incidenceRecStart.isValid()) recurrenceIds.insert(exception->recurrenceId().toTimeSpec(incidenceRecStart.timeSpec()), exception); } const bool isAllDay = inc->allDay(); const DateTimeList occurrences = inc->recurrence()->timesInInterval(start, end); - foreach(KDateTime occurrenceDate, occurrences) { //krazy:exclude=foreach Incidence::Ptr incidence(inc), lastInc(inc); qint64 offset(0), lastOffset(0); + KDateTime occurrenceStartDate; + foreach(KDateTime recurrenceId, occurrences) { //krazy:exclude=foreach //timesInInterval generates always date-times, //which is not what we want for all-day events - occurrenceDate.setDateOnly(isAllDay); + recurrenceId.setDateOnly(isAllDay); + occurrenceStartDate = recurrenceId; bool resetIncidence = false; - if (recurrenceIds.contains(occurrenceDate)) { + if (recurrenceIds.contains(recurrenceId)) { // TODO: exclude exceptions where the start/end is not within // (so the occurrence of the recurrence is omitted, but no exception is added) - if (recurrenceIds.value(occurrenceDate)->status() == Incidence::StatusCanceled) + if (recurrenceIds.value(recurrenceId)->status() == Incidence::StatusCanceled) continue; - incidence = recurrenceIds.value(occurrenceDate); - occurrenceDate = incidence->dtStart(); + incidence = recurrenceIds.value(recurrenceId); + occurrenceStartDate = incidence->dtStart(); resetIncidence = !incidence->thisAndFuture(); offset = incidence->recurrenceId().secsTo_long(incidence->dtStart()); if (incidence->thisAndFuture()) { lastInc = incidence; lastOffset = offset; } } else if (inc != incidence) { //thisAndFuture exception is active - occurrenceDate = occurrenceDate.addSecs(offset); + occurrenceStartDate = occurrenceStartDate.addSecs(offset); } - if (!occurrenceIsHidden(calendar, incidence, occurrenceDate)) { - occurrenceList << Private::Occurrence(incidence, occurrenceDate); + + if (!occurrenceIsHidden(calendar, incidence, occurrenceStartDate)) { + occurrenceList << Private::Occurrence(incidence, recurrenceId , occurrenceStartDate); } + if (resetIncidence) { incidence = lastInc; offset = lastOffset; } } } else { - occurrenceList << Private::Occurrence(inc, inc->dtStart()); + occurrenceList << Private::Occurrence(inc, KDateTime(), inc->dtStart()); } } occurrenceIt = QListIterator(occurrenceList); } }; //@endcond static uint qHash(const KDateTime &dt) { return qHash(dt.toString()); } /** * Right now there is little point in the iterator, but: * With an iterator it should be possible to solve this more memory efficiently * and with immediate results at the beginning of the selected timeframe. * Either all events are iterated simoulatneously, resulting in occurrences * of all events in parallel in the correct time-order, or incidence after * incidence, which would be even more efficient. * * By making this class a friend of calendar, we could also use the internally * available data structures. */ OccurrenceIterator::OccurrenceIterator(const Calendar &calendar, const KDateTime &start, const KDateTime &end) : d(new KCalCore::OccurrenceIterator::Private(this)) { d->start = start; d->end = end; Event::List events = calendar.rawEvents(start.date(), end.date(), start.timeSpec()); if (calendar.filter()) { calendar.filter()->apply(&events); } Todo::List todos = calendar.rawTodos(start.date(), end.date(), start.timeSpec()); if (calendar.filter()) { calendar.filter()->apply(&todos); } Journal::List journals; const Journal::List allJournals = calendar.rawJournals(); foreach(const KCalCore::Journal::Ptr &journal, allJournals) { const QDate journalStart = journal->dtStart().toTimeSpec(start.timeSpec()).date(); if (journal->dtStart().isValid() && journalStart >= start.date() && journalStart <= end.date()) journals << journal; } if (calendar.filter()) { calendar.filter()->apply(&journals); } const Incidence::List incidences = KCalCore::Calendar::mergeIncidenceList(events, todos, journals); d->setupIterator(calendar, incidences); } OccurrenceIterator::OccurrenceIterator(const Calendar &calendar, const Incidence::Ptr &incidence, const KDateTime &start, const KDateTime &end) : d(new KCalCore::OccurrenceIterator::Private(this)) { Q_ASSERT(incidence); d->start = start; d->end = end; d->setupIterator(calendar, Incidence::List() << incidence); } OccurrenceIterator::~OccurrenceIterator() { } bool OccurrenceIterator::hasNext() const { return d->occurrenceIt.hasNext(); } void OccurrenceIterator::next() { d->current = d->occurrenceIt.next(); } Incidence::Ptr OccurrenceIterator::incidence() const { return d->current.incidence; } KDateTime OccurrenceIterator::occurrenceStartDate() const { - return d->current.date; + return d->current.startDate; } + +KDateTime OccurrenceIterator::recurrenceId() const +{ + return d->current.recurrenceId; +} \ No newline at end of file diff --git a/kcalcore/occurrenceiterator.h b/kcalcore/occurrenceiterator.h index f972f6ac1..ce51acee4 100644 --- a/kcalcore/occurrenceiterator.h +++ b/kcalcore/occurrenceiterator.h @@ -1,97 +1,105 @@ /* This file is part of the kcalcore library. Copyright (c) 2013 Christian Mollekopf This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** @file This file is part of the API for handling calendar data and defines the OccurrenceIterator class. @author Christian Mollekopf \ */ #ifndef KCALCORE_OCCURRENCEITERATOR_H #define KCALCORE_OCCURRENCEITERATOR_H #include "kcalcore_export.h" #include "incidence.h" namespace KCalCore { class Calendar; /** * Iterate over calendar items in a calendar. * * The iterator takes recurrences and exceptions to recurrences into account * * The iterator does not iterate the occurrences of all incidences chronologically. * @since 4.11 */ class KCALCORE_EXPORT OccurrenceIterator { public: /** * Creates iterator that iterates over all occurrences of all incidences * between @param start and @param end (inclusive) */ explicit OccurrenceIterator(const Calendar &calendar, const KDateTime &start = KDateTime(), const KDateTime &end = KDateTime()); /** * Creates iterator that iterates over all occurrences * of @param incidence between @param start and @param end (inclusive) */ OccurrenceIterator(const Calendar &calendar, const KCalCore::Incidence::Ptr &incidence, const KDateTime &start = KDateTime(), const KDateTime &end = KDateTime()); ~OccurrenceIterator(); bool hasNext() const; /** * Advance iterator to the next occurrence. */ void next(); /** * Returns either main incidence or exception, depending on occurrence. */ Incidence::Ptr incidence() const; /** * Returns the start date of the occurrence * * This is either the occurrence date, or the start date of an exception * which overrides that occurrence. */ KDateTime occurrenceStartDate() const; + /** + * Returns the recurrence Id. + * + * This is the date where the occurrence starts without exceptions, + * this id is used to identify one excat occurence. + */ + KDateTime recurrenceId() const; + private: Q_DISABLE_COPY(OccurrenceIterator) //@cond PRIVATE class Private; QScopedPointer d; //@endcond }; } //namespace #endif diff --git a/kcalcore/tests/testoccurrenceiterator.cpp b/kcalcore/tests/testoccurrenceiterator.cpp index 2fdf6143a..379b18e7d 100644 --- a/kcalcore/tests/testoccurrenceiterator.cpp +++ b/kcalcore/tests/testoccurrenceiterator.cpp @@ -1,298 +1,299 @@ /* * Copyright (C) 2012 Christian Mollekopf * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "testoccurrenceiterator.h" #include "occurrenceiterator.h" #include "memorycalendar.h" #include "calfilter.h" #include #include QTEST_KDEMAIN(TestOccurrenceIterator, NoGUI) void TestOccurrenceIterator::testIterationWithExceptions() { KCalCore::MemoryCalendar calendar(KDateTime::UTC); KDateTime start(QDate(2013, 03, 10), QTime(10, 0, 0), KDateTime::UTC); KDateTime end(QDate(2013, 03, 10), QTime(11, 0, 0), KDateTime::UTC); KDateTime recurrenceId(QDate(2013, 03, 11), QTime(10, 0, 0), KDateTime::UTC); KDateTime exceptionStart(QDate(2013, 03, 11), QTime(12, 0, 0), KDateTime::UTC); KDateTime exceptionEnd(QDate(2013, 03, 11), QTime(13, 0, 0), KDateTime::UTC); KDateTime actualEnd(QDate(2013, 03, 12), QTime(11, 0, 0), KDateTime::UTC); KCalCore::Event::Ptr event1(new KCalCore::Event()); event1->setUid("event1"); event1->setSummary("event1"); event1->setDtStart(start); event1->setDtEnd(end); event1->recurrence()->setDaily(1); calendar.addEvent(event1); KCalCore::Event::Ptr exception(new KCalCore::Event()); exception->setUid(event1->uid()); exception->setSummary("exception"); exception->setRecurrenceId(recurrenceId); exception->setDtStart(exceptionStart); exception->setDtEnd(exceptionEnd); calendar.addEvent(exception); int occurrence = 0; KCalCore::OccurrenceIterator rIt(calendar, start, actualEnd); while (rIt.hasNext()) { rIt.next(); occurrence++; if (occurrence == 1) { QCOMPARE(rIt.occurrenceStartDate(), start); QCOMPARE(rIt.incidence()->summary(), event1->summary()); } if (occurrence == 2) { QCOMPARE(rIt.occurrenceStartDate(), exceptionStart); QCOMPARE(rIt.incidence()->summary(), exception->summary()); } if (occurrence == 3) { QCOMPARE(rIt.occurrenceStartDate(), start.addDays(2)); QCOMPARE(rIt.incidence()->summary(), event1->summary()); } // qDebug() << occurrence; // qDebug() << "occurrence: " << rIt.occurrenceStartDate().toString(); // qDebug() << "uid: " << rIt.incidence()->uid(); // qDebug() << "summary: " << rIt.incidence()->summary(); // qDebug() << "start: " << rIt.incidence()->dtStart().toString(); // qDebug(); } QCOMPARE(occurrence, 3); } void TestOccurrenceIterator::testEventsAndTodos() { KCalCore::MemoryCalendar calendar(KDateTime::UTC); KDateTime start(QDate(2013, 03, 10), QTime(10, 0, 0), KDateTime::UTC); KDateTime end(QDate(2013, 03, 10), QTime(11, 0, 0), KDateTime::UTC); KDateTime actualEnd(QDate(2013, 03, 13), QTime(11, 0, 0), KDateTime::UTC); KCalCore::Event::Ptr event(new KCalCore::Event()); event->setUid("event"); event->setDtStart(start); event->recurrence()->setDaily(1); event->recurrence()->setDuration(2); calendar.addEvent(event); KCalCore::Todo::Ptr todo(new KCalCore::Todo()); todo->setUid("todo"); todo->setDtStart(start); todo->recurrence()->setDaily(1); todo->recurrence()->setDuration(2); calendar.addTodo(todo); KCalCore::OccurrenceIterator rIt(calendar, start, actualEnd); QList expectedTodoOccurrences; expectedTodoOccurrences << start << start.addDays(1); QList expectedEventOccurrences; expectedEventOccurrences << start << start.addDays(1); while (rIt.hasNext()) { rIt.next(); kDebug() << rIt.occurrenceStartDate(); if (rIt.incidence()->type() == KCalCore::Incidence::TypeTodo) { QCOMPARE(expectedTodoOccurrences.removeAll(rIt.occurrenceStartDate()), 1); } else { QCOMPARE(expectedEventOccurrences.removeAll(rIt.occurrenceStartDate()), 1); } } QCOMPARE(expectedTodoOccurrences.size(), 0); QCOMPARE(expectedEventOccurrences.size(), 0); } void TestOccurrenceIterator::testFilterCompletedTodos() { KCalCore::MemoryCalendar calendar(KDateTime::UTC); calendar.filter()->setCriteria(KCalCore::CalFilter::HideCompletedTodos); KDateTime start(QDate(2013, 03, 10), QTime(10, 0, 0), KDateTime::UTC); KDateTime end(QDate(2013, 03, 10), QTime(11, 0, 0), KDateTime::UTC); KDateTime actualEnd(QDate(2013, 03, 13), QTime(11, 0, 0), KDateTime::UTC); KCalCore::Todo::Ptr todo(new KCalCore::Todo()); todo->setUid("todo"); todo->setDtDue(start); todo->setDtStart(start); todo->recurrence()->setDaily(1); todo->recurrence()->setDuration(2); //Yes, recurring todos are weird... setting this says that all occurrences until this one have been completed, and thus should be skipped. //that's what kontact did, so it's what we test now. todo->setDtRecurrence(start.addDays(2)); calendar.addTodo(todo); KCalCore::OccurrenceIterator rIt(calendar, start, actualEnd); QVERIFY(!rIt.hasNext()); } void TestOccurrenceIterator::testAllDayEvents() { KCalCore::MemoryCalendar calendar(KDateTime::UTC); KDateTime start(QDate(2013, 03, 10), KDateTime::UTC); KDateTime actualEnd(QDate(2013, 03, 13), QTime(11, 0, 0), KDateTime::UTC); KCalCore::Event::Ptr event(new KCalCore::Event()); event->setUid("event"); event->setDtStart(start); event->recurrence()->setDaily(1); event->recurrence()->setDuration(2); calendar.addEvent(event); KCalCore::OccurrenceIterator rIt(calendar, start, actualEnd); QList expectedEventOccurrences; expectedEventOccurrences << start << start.addDays(1); while (rIt.hasNext()) { rIt.next(); kDebug() << rIt.occurrenceStartDate(); QCOMPARE(expectedEventOccurrences.removeAll(rIt.occurrenceStartDate()), 1); } QCOMPARE(expectedEventOccurrences.size(), 0); } void TestOccurrenceIterator::testWithExceptionThisAndFuture() { KCalCore::MemoryCalendar calendar(KDateTime::UTC); KDateTime start(QDate(2013, 03, 10), QTime(10, 0, 0), KDateTime::UTC); KDateTime end(QDate(2013, 03, 10), QTime(11, 0, 0), KDateTime::UTC); KDateTime recurrenceId1(QDate(2013, 03, 11), QTime(10, 0, 0), KDateTime::UTC); KDateTime exceptionStart1(QDate(2013, 03, 11), QTime(12, 0, 0), KDateTime::UTC); KDateTime exceptionEnd1(QDate(2013, 03, 11), QTime(13, 0, 0), KDateTime::UTC); KDateTime recurrenceId2(QDate(2013, 03, 13), QTime(10, 0, 0), KDateTime::UTC); KDateTime exceptionStart2(QDate(2013, 03, 13), QTime(14, 0, 0), KDateTime::UTC); KDateTime exceptionEnd2(QDate(2013, 03, 13), QTime(15, 0, 0), KDateTime::UTC); KDateTime actualEnd(QDate(2013, 03, 14), QTime(11, 0, 0), KDateTime::UTC); KCalCore::Event::Ptr event1(new KCalCore::Event()); event1->setUid("event1"); event1->setSummary("event1"); event1->setDtStart(start); event1->setDtEnd(end); event1->recurrence()->setDaily(1); calendar.addEvent(event1); KCalCore::Event::Ptr exception1(new KCalCore::Event()); exception1->setUid(event1->uid()); exception1->setSummary("exception1"); exception1->setRecurrenceId(recurrenceId1); exception1->setThisAndFuture(true); exception1->setDtStart(exceptionStart1); exception1->setDtEnd(exceptionEnd1); calendar.addEvent(exception1); KCalCore::Event::Ptr exception2(new KCalCore::Event()); exception2->setUid(event1->uid()); exception2->setSummary("exception2"); exception2->setRecurrenceId(recurrenceId2); exception2->setDtStart(exceptionStart2); exception2->setDtEnd(exceptionEnd2); calendar.addEvent(exception2); int occurrence = 0; KCalCore::OccurrenceIterator rIt(calendar, start, actualEnd); while (rIt.hasNext()) { rIt.next(); occurrence++; // qDebug() << occurrence; // qDebug() << "occurrence: " << rIt.occurrenceStartDate().toString(); // qDebug() << "uid: " << rIt.incidence()->uid(); // qDebug() << "summary: " << rIt.incidence()->summary(); // qDebug() << "start: " << rIt.incidence()->dtStart().toString(); // qDebug(); + QCOMPARE(rIt.recurrenceId(), start.addDays(occurrence - 1)); if (occurrence == 1) { QCOMPARE(rIt.occurrenceStartDate(), start); QCOMPARE(rIt.incidence()->summary(), event1->summary()); } if (occurrence == 2) { QCOMPARE(rIt.occurrenceStartDate(), exceptionStart1); QCOMPARE(rIt.incidence()->summary(), exception1->summary()); } if (occurrence == 3) { QCOMPARE(rIt.occurrenceStartDate(), exceptionStart1.addDays(1)); QCOMPARE(rIt.incidence()->summary(), exception1->summary()); } if (occurrence == 4) { QCOMPARE(rIt.occurrenceStartDate(), exceptionStart2); QCOMPARE(rIt.incidence()->summary(), exception2->summary()); } if (occurrence == 5) { QCOMPARE(rIt.occurrenceStartDate(), exceptionStart1.addDays(3)); QCOMPARE(rIt.incidence()->summary(), exception1->summary()); } } QCOMPARE(occurrence, 5); } void TestOccurrenceIterator::testSubDailyRecurrences() { KCalCore::MemoryCalendar calendar(KDateTime::UTC); KDateTime start(QDate(2013, 03, 10), QTime(10, 0, 0), KDateTime::UTC); KDateTime actualEnd(QDate(2013, 03, 10), QTime(13, 0, 0), KDateTime::UTC); KCalCore::Event::Ptr event(new KCalCore::Event()); event->setUid("event"); event->setDtStart(start); event->recurrence()->setHourly(1); event->recurrence()->setDuration(2); calendar.addEvent(event); KCalCore::OccurrenceIterator rIt(calendar, start, actualEnd); QList expectedEventOccurrences; expectedEventOccurrences << start << start.addSecs(60*60); while (rIt.hasNext()) { rIt.next(); kDebug() << rIt.occurrenceStartDate(); QCOMPARE(expectedEventOccurrences.removeAll(rIt.occurrenceStartDate()), 1); } QCOMPARE(expectedEventOccurrences.size(), 0); } void TestOccurrenceIterator::testJournals() { KCalCore::MemoryCalendar calendar(KDateTime::UTC); const KDateTime today = KDateTime::currentDateTime(KDateTime::UTC); const KDateTime yesterday = today.addDays(-1); const KDateTime tomorrow = today.addDays(1); KCalCore::Journal::Ptr journal(new KCalCore::Journal()); journal->setUid("journal"); journal->setDtStart(today); calendar.addJournal(journal); KCalCore::OccurrenceIterator rIt(calendar, yesterday, tomorrow); QVERIFY(rIt.hasNext()); rIt.next(); QCOMPARE(rIt.occurrenceStartDate(), today); QVERIFY(!rIt.hasNext()); KCalCore::OccurrenceIterator rIt2(calendar, tomorrow, tomorrow.addDays(1)); QVERIFY(!rIt2.hasNext()); }