diff --git a/kcalcore/occurrenceiterator.cpp b/kcalcore/occurrenceiterator.cpp index 50d0e21d6..4fbfe87ad 100644 --- a/kcalcore/occurrenceiterator.cpp +++ b/kcalcore/occurrenceiterator.cpp @@ -1,244 +1,248 @@ /* 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) { } Incidence::Ptr incidence; KDateTime date; }; 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); - Incidence::Ptr incidence(inc); - qint64 offset(0); foreach(KDateTime occurrenceDate, occurrences) { //krazy:exclude=foreach + Incidence::Ptr incidence(inc), lastInc(inc); + qint64 offset(0), lastOffset(0); //timesInInterval generates always date-times, //which is not what we want for all-day events occurrenceDate.setDateOnly(isAllDay); bool resetIncidence = false; if (recurrenceIds.contains(occurrenceDate)) { // 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) continue; incidence = recurrenceIds.value(occurrenceDate); occurrenceDate = 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); } if (!occurrenceIsHidden(calendar, incidence, occurrenceDate)) { occurrenceList << Private::Occurrence(incidence, occurrenceDate); } if (resetIncidence) { - incidence = inc; - offset = 0; + incidence = lastInc; + offset = lastOffset; } } } else { occurrenceList << Private::Occurrence(inc, 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; } diff --git a/kcalcore/tests/testoccurrenceiterator.cpp b/kcalcore/tests/testoccurrenceiterator.cpp index 89c9f0921..2fdf6143a 100644 --- a/kcalcore/tests/testoccurrenceiterator.cpp +++ b/kcalcore/tests/testoccurrenceiterator.cpp @@ -1,278 +1,298 @@ /* * 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 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 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 actualEnd(QDate(2013, 03, 12), QTime(11, 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 exception(new KCalCore::Event()); - exception->setUid(event1->uid()); - exception->setSummary("exception"); - exception->setRecurrenceId(recurrenceId); - exception->setThisAndFuture(true); - exception->setDtStart(exceptionStart); - exception->setDtEnd(exceptionEnd); - calendar.addEvent(exception); + 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(); 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()); + QCOMPARE(rIt.occurrenceStartDate(), exceptionStart1); + QCOMPARE(rIt.incidence()->summary(), exception1->summary()); } if (occurrence == 3) { - QCOMPARE(rIt.occurrenceStartDate(), exceptionStart.addDays(1)); - QCOMPARE(rIt.incidence()->summary(), exception->summary()); + 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, 3); + 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()); }