diff --git a/autotests/testmemorycalendar.cpp b/autotests/testmemorycalendar.cpp index 27b1e6207..95ba8df7b 100644 --- a/autotests/testmemorycalendar.cpp +++ b/autotests/testmemorycalendar.cpp @@ -1,274 +1,315 @@ /* This file is part of the kcalcore library. Copyright (C) 2006-2007 Allen Winter 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. */ #include "testmemorycalendar.h" #include "filestorage.h" #include "memorycalendar.h" #include #include #include QTEST_MAIN(MemoryCalendarTest) using namespace KCalendarCore; void MemoryCalendarTest::testValidity() { MemoryCalendar::Ptr cal(new MemoryCalendar(QTimeZone::utc())); cal->setProductId(QStringLiteral("fredware calendar")); QVERIFY(cal->productId() == QLatin1String("fredware calendar")); QVERIFY(cal->timeZoneId() == QByteArrayLiteral("UTC")); QVERIFY(cal->timeZone() == QTimeZone::utc()); cal->close(); } void MemoryCalendarTest::testEvents() { MemoryCalendar::Ptr cal(new MemoryCalendar(QTimeZone::utc())); cal->setProductId(QStringLiteral("fredware calendar")); QDate dt = QDate::currentDate(); Event::Ptr event1 = Event::Ptr(new Event()); event1->setUid(QStringLiteral("1")); event1->setDtStart(QDateTime(dt, {})); event1->setDtEnd(QDateTime(dt, {}).addDays(1)); event1->setAllDay(true); event1->setSummary(QStringLiteral("Event1 Summary")); event1->setDescription(QStringLiteral("This is a description of the first event")); event1->setLocation(QStringLiteral("the place")); Event::Ptr event2 = Event::Ptr(new Event()); event2->setUid(QStringLiteral("2")); event2->setDtStart(QDateTime(dt, {}).addDays(1)); event2->setDtEnd(QDateTime(dt, {}).addDays(2)); event2->setAllDay(true); event2->setSummary(QStringLiteral("Event2 Summary")); event2->setDescription(QStringLiteral("This is a description of the second event")); event2->setLocation(QStringLiteral("the other place")); QVERIFY(cal->addEvent(event1)); QVERIFY(cal->addEvent(event2)); FileStorage store(cal, QStringLiteral("foo.ics")); QVERIFY(store.save()); cal->close(); QFile::remove(QStringLiteral("foo.ics")); } void MemoryCalendarTest::testIncidences() { MemoryCalendar::Ptr cal(new MemoryCalendar(QTimeZone::utc())); cal->setProductId(QStringLiteral("fredware calendar")); QDate dt = QDate::currentDate(); Event::Ptr event1 = Event::Ptr(new Event()); event1->setUid(QStringLiteral("1")); event1->setDtStart(QDateTime(dt, {})); event1->setDtEnd(QDateTime(dt, {}).addDays(1)); event1->setAllDay(true); event1->setSummary(QStringLiteral("Event1 Summary")); event1->setDescription(QStringLiteral("This is a description of the first event")); event1->setLocation(QStringLiteral("the place")); Event::Ptr event2 = Event::Ptr(new Event()); event2->setUid(QStringLiteral("2")); event2->setDtStart(QDateTime(dt, {}).addDays(1)); event2->setDtEnd(QDateTime(dt, {}).addDays(2)); event2->setAllDay(true); event2->setSummary(QStringLiteral("Event2 Summary")); event2->setDescription(QStringLiteral("This is a description of the second event")); event2->setLocation(QStringLiteral("the other place")); QVERIFY(cal->addEvent(event1)); QVERIFY(cal->addEvent(event2)); Todo::Ptr todo1 = Todo::Ptr(new Todo()); todo1->setUid(QStringLiteral("3")); todo1->setDtStart(QDateTime(dt, {}).addDays(1)); todo1->setDtDue(QDateTime(dt, {}).addDays(2)); todo1->setAllDay(true); todo1->setSummary(QStringLiteral("Todo1 Summary")); todo1->setDescription(QStringLiteral("This is a description of a todo")); todo1->setLocation(QStringLiteral("this place")); Todo::Ptr todo2 = Todo::Ptr(new Todo()); todo2->setUid(QStringLiteral("4")); todo2->setDtStart(QDateTime(dt, {}).addDays(1)); todo2->setAllDay(true); todo2->setSummary(QStringLiteral("

Todo2 Summary

"), true); todo2->setDescription(QStringLiteral("This is a description of a todo")); todo2->setLocation(QStringLiteral("this place"), true); QVERIFY(cal->addTodo(todo1)); QVERIFY(cal->addTodo(todo2)); FileStorage store(cal, QStringLiteral("foo.ics")); QVERIFY(store.save()); cal->close(); QVERIFY(store.load()); Todo::Ptr todo = cal->incidence(QStringLiteral("4")).staticCast(); QVERIFY(todo->uid() == QLatin1Char('4')); QVERIFY(todo->summaryIsRich()); QVERIFY(todo->locationIsRich()); cal->close(); QFile::remove(QStringLiteral("foo.ics")); } void MemoryCalendarTest::testRelationsCrash() { // Before, there was a crash that occurred only when reloading a calendar in which // the incidences had special relations. // This test tests that scenario, and will crash if it fails. MemoryCalendar::Ptr cal(new MemoryCalendar(QTimeZone::utc())); FileStorage store1(cal, QLatin1String(ICALTESTDATADIR) + QLatin1String("test_relations.ics")); QVERIFY(store1.load()); const Todo::List oldTodos = cal->todos(); qDebug() << "Loaded " << oldTodos.count() << " todos into oldTodos."; FileStorage store2(cal, QLatin1String(ICALTESTDATADIR) + QLatin1String("test_relations.ics")); QVERIFY(store2.load()); const Todo::List newTodos = cal->todos(); qDebug() << "Loaded " << newTodos.count() << " into newTodos."; // We can saftely access the old deleted todos here, since they are not really deleted // and are still kept in a map of deleted items somewhere. // // Here we make sure that non of the old items have connections to the new items, and // the other way around. // This doesn't makes sense so i commented it. when you load a calendar the second time // it reuses what it can, so oldTodo == newTodo /* foreach (const Todo::Ptr &oldTodo, oldTodos ) { foreach (const Todo::Ptr &newTodo, newTodos ) { QVERIFY( oldTodo != newTodo ); // Make sure that none of the new todos point to an old, deleted todo QVERIFY( newTodo->relatedTo() != oldTodo ); QVERIFY( !newTodo->relations().contains( oldTodo ) ); // Make sure that none of the old todos point to a new todo QVERIFY( oldTodo->relatedTo() != newTodo ); QVERIFY( !oldTodo->relations().contains( newTodo ) ); } } */ cal->close(); } void MemoryCalendarTest::testRecurrenceExceptions() { MemoryCalendar::Ptr cal(new MemoryCalendar(QTimeZone::utc())); cal->setProductId(QStringLiteral("fredware calendar")); QDate dt = QDate::currentDate(); QDateTime start(dt, {}); Event::Ptr event1 = Event::Ptr(new Event()); event1->setUid(QStringLiteral("1")); event1->setDtStart(start); event1->setDtEnd(start.addDays(1)); event1->setSummary(QStringLiteral("Event1 Summary")); event1->recurrence()->setDaily(1); event1->recurrence()->setDuration(3); QVERIFY(cal->addEvent(event1)); const QDateTime recurrenceId = event1->dtStart().addDays(1); Event::Ptr exception1 = cal->createException(event1, recurrenceId).staticCast(); QCOMPARE(exception1->recurrenceId(), recurrenceId); QCOMPARE(exception1->uid(), event1->uid()); exception1->setSummary(QStringLiteral("exception")); QVERIFY(exception1); QVERIFY(cal->addEvent(exception1)); QCOMPARE(cal->event(event1->uid()), event1); QCOMPARE(cal->event(event1->uid(), recurrenceId), exception1); const Event::List incidences = cal->rawEvents(start.date(), start.addDays(3).date(), start.timeZone()); //Contains incidence and exception QCOMPARE(incidences.size(), 2); //Returns only exceptions for an event const Event::List exceptions = cal->eventInstances(event1); QCOMPARE(exceptions.size(), 1); QCOMPARE(exceptions.first()->uid(), event1->uid()); QCOMPARE(exceptions.first()->summary(), exception1->summary()); } void MemoryCalendarTest::testChangeRecurId() { // When we change the recurring id, internal hashtables should be updated. MemoryCalendar::Ptr cal(new MemoryCalendar(QTimeZone::utc())); QDateTime start(QDate::currentDate(), {}); // Add main event Event::Ptr event1 = Event::Ptr(new Event()); const QString uid = QStringLiteral("1"); event1->setUid(uid); event1->setDtStart(start); event1->setDtEnd(start.addDays(1)); event1->setAllDay(true); event1->setSummary(QStringLiteral("Event1 Summary")); event1->recurrence()->setDaily(1); event1->recurrence()->setDuration(3); QVERIFY(cal->addEvent(event1)); // Add exception event: const QDateTime recurrenceId = event1->dtStart().addDays(1); Event::Ptr exception1 = cal->createException(event1, recurrenceId).staticCast(); QCOMPARE(exception1->recurrenceId(), recurrenceId); QCOMPARE(exception1->uid(), event1->uid()); exception1->setSummary(QStringLiteral("exception")); QVERIFY(exception1); QVERIFY(cal->addEvent(exception1)); const QString oldIdentifier = exception1->instanceIdentifier(); Incidence::Ptr foo = cal->instance(oldIdentifier); QVERIFY(foo && foo->hasRecurrenceId()); // Now change the recurring id! exception1->setRecurrenceId(start.addDays(2)); const QString newIdentifier = exception1->instanceIdentifier(); QVERIFY(oldIdentifier != newIdentifier); foo = cal->instance(oldIdentifier); QVERIFY(!foo); foo = cal->instance(newIdentifier); QVERIFY(foo); // Test hashing Incidence::List incidences = cal->incidences(); QVERIFY(incidences.count() == 2); QDateTime newRecId = start.addDays(2); Incidence::Ptr main = cal->incidence(uid); Incidence::Ptr exception = cal->incidence(uid, newRecId); Incidence::Ptr noException = cal->incidence(uid, recurrenceId); QVERIFY(!noException); QVERIFY(main); QVERIFY(exception); QVERIFY(exception->recurrenceId() == newRecId); QVERIFY(exception->summary() == QLatin1String("exception")); QVERIFY(main->summary() == event1->summary()); } + +void MemoryCalendarTest::testRawEventsForDate() +{ + // We're checking that events at a date in a given time zone + // are properly returned for the day after / before if + // the calendar is for another time zone. + MemoryCalendar::Ptr cal(new MemoryCalendar(QTimeZone::utc())); + + Event::Ptr event = Event::Ptr(new Event()); + event->setDtStart(QDateTime(QDate(2019, 10, 29), QTime(1, 30), + QTimeZone("Asia/Ho_Chi_Minh"))); + + QVERIFY(cal->addEvent(event)); + + QCOMPARE(cal->rawEventsForDate(QDate(2019, 10, 28)).count(), 1); + QCOMPARE(cal->rawEventsForDate(QDate(2019, 10, 29), + QTimeZone("Asia/Ho_Chi_Minh")).count(), 1); + + cal->setTimeZoneId("Asia/Ho_Chi_Minh"); + QCOMPARE(cal->rawEventsForDate(QDate(2019, 10, 29)).count(), 1); + QCOMPARE(cal->rawEventsForDate(QDate(2019, 10, 28), + QTimeZone::utc()).count(), 1); + + event->setDtStart(QDateTime(QDate(2019, 10, 30), QTime(23, 00), + QTimeZone::utc())); + QCOMPARE(cal->rawEventsForDate(QDate(2019, 10, 31)).count(), 1); + QCOMPARE(cal->rawEventsForDate(QDate(2019, 10, 30), + QTimeZone::utc()).count(), 1); + + QVERIFY(cal->deleteIncidence(event)); + QCOMPARE(cal->rawEventsForDate(QDate(2019, 10, 31)).count(), 0); + + // Multi-days events are treated differently. + event->setDtEnd(QDateTime(QDate(2019, 10, 31), QTime(23, 00), + QTimeZone::utc())); + QVERIFY(cal->addEvent(event)); + QCOMPARE(cal->rawEventsForDate(QDate(2019, 10, 31)).count(), 1); + QCOMPARE(cal->rawEventsForDate(QDate(2019, 11, 1)).count(), 1); + + cal->close(); +} diff --git a/autotests/testmemorycalendar.h b/autotests/testmemorycalendar.h index 73cad5075..ad303df76 100644 --- a/autotests/testmemorycalendar.h +++ b/autotests/testmemorycalendar.h @@ -1,39 +1,40 @@ /* This file is part of the kcalcore library. Copyright (c) 2006-2007 Allen Winter 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. */ #ifndef TESTMEMORYCALENDAR_H #define TESTMEMORYCALENDAR_H #include class MemoryCalendarTest : public QObject { Q_OBJECT private Q_SLOTS: void testValidity(); void testEvents(); void testIncidences(); void testRelationsCrash(); void testRecurrenceExceptions(); void testChangeRecurId(); + void testRawEventsForDate(); }; #endif diff --git a/src/memorycalendar.cpp b/src/memorycalendar.cpp index 3e4cbc5ec..e0cd85629 100644 --- a/src/memorycalendar.cpp +++ b/src/memorycalendar.cpp @@ -1,871 +1,884 @@ /* This file is part of the kcalcore library. Copyright (c) 1998 Preston Brown Copyright (c) 2001,2003,2004 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer 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 MemoryCalendar class. @brief This class provides a calendar stored as a local file. @author Preston Brown \ @author Cornelius Schumacher \ */ #include "memorycalendar.h" #include "kcalendarcore_debug.h" #include "calformat.h" #include template static QVector values(const QMultiHash &c) { QVector v; v.reserve(c.size()); for (typename QMultiHash::const_iterator it = c.begin(), end = c.end(); it != end; ++it) { v.push_back(it.value()); } return v; } template static QVector values(const QMultiHash &c, const K &x) { QVector v; typename QMultiHash::const_iterator it = c.find(x); while (it != c.end() && it.key() == x) { v.push_back(it.value()); ++it; } return v; } using namespace KCalendarCore; /** Private class that helps to provide binary compatibility between releases. @internal */ //@cond PRIVATE class Q_DECL_HIDDEN KCalendarCore::MemoryCalendar::Private { public: Private(MemoryCalendar *qq) : q(qq), mFormat(nullptr) { } ~Private() { } MemoryCalendar *q; CalFormat *mFormat; // calendar format QString mIncidenceBeingUpdated; // Instance identifier of Incidence currently being updated /** * List of all incidences. * First indexed by incidence->type(), then by incidence->uid(); */ QMap > mIncidences; /** * Has all incidences, indexed by identifier. */ QHash mIncidencesByIdentifier; /** * List of all deleted incidences. * First indexed by incidence->type(), then by incidence->uid(); */ QMap > mDeletedIncidences; /** * Contains incidences ( to-dos; non-recurring, non-multiday events; journals; ) * indexed by start/due date. * * The QMap key is the incidence->type(). * The QMultiHash key is the dtStart/dtDue().toString() * * Note: We had 3 variables, mJournalsForDate, mTodosForDate and mEventsForDate * but i merged them into one (indexed by type) because it simplifies code using * it. No need to if else based on type. */ QMap > mIncidencesForDate; void insertIncidence(const Incidence::Ptr &incidence); Incidence::Ptr incidence(const QString &uid, IncidenceBase::IncidenceType type, const QDateTime &recurrenceId = {}) const; Incidence::Ptr deletedIncidence(const QString &uid, const QDateTime &recurrenceId, IncidenceBase::IncidenceType type) const; void deleteAllIncidences(IncidenceBase::IncidenceType type); }; //@endcond MemoryCalendar::MemoryCalendar(const QTimeZone &timeZone) : Calendar(timeZone), d(new KCalendarCore::MemoryCalendar::Private(this)) { } MemoryCalendar::MemoryCalendar(const QByteArray &timeZoneId) : Calendar(timeZoneId), d(new KCalendarCore::MemoryCalendar::Private(this)) { } MemoryCalendar::~MemoryCalendar() { close(); //NOLINT false clang-analyzer-optin.cplusplus.VirtualCall delete d; } +void MemoryCalendar::doSetTimeZone(const QTimeZone &timeZone) +{ + // Reset date based hashes before storing for the new zone. + d->mIncidencesForDate.clear(); + + for (auto it = d->mIncidences.constBegin(); it != d->mIncidences.constEnd(); ++it) { + for (auto incidence = it->constBegin(); incidence != it->constEnd(); ++incidence) { + const QDateTime dt = incidence.value()->dateTime(Incidence::RoleCalendarHashing); + if (dt.isValid()) { + d->mIncidencesForDate[incidence.value()->type()].insert(dt.toTimeZone(timeZone).date().toString(), incidence.value()); + } + } + } +} + void MemoryCalendar::close() { setObserversEnabled(false); // Don't call the virtual function deleteEvents() etc, the base class might have // other ways of deleting the data. d->deleteAllIncidences(Incidence::TypeEvent); d->deleteAllIncidences(Incidence::TypeTodo); d->deleteAllIncidences(Incidence::TypeJournal); d->mIncidencesByIdentifier.clear(); d->mDeletedIncidences.clear(); setModified(false); setObserversEnabled(true); } bool MemoryCalendar::deleteIncidence(const Incidence::Ptr &incidence) { // Handle orphaned children // relations is an Incidence's property, not a Todo's, so // we remove relations in deleteIncidence, not in deleteTodo. removeRelations(incidence); const Incidence::IncidenceType type = incidence->type(); const QString uid = incidence->uid(); if (d->mIncidences[type].contains(uid, incidence)) { // Notify while the incidence is still available, // this is necessary so korganizer still has time to query for exceptions notifyIncidenceAboutToBeDeleted(incidence); d->mIncidences[type].remove(uid, incidence); d->mIncidencesByIdentifier.remove(incidence->instanceIdentifier()); setModified(true); if (deletionTracking()) { d->mDeletedIncidences[type].insert(uid, incidence); } const QDateTime dt = incidence->dateTime(Incidence::RoleCalendarHashing); if (dt.isValid()) { - d->mIncidencesForDate[type].remove(dt.date().toString(), incidence); + d->mIncidencesForDate[type].remove(dt.toTimeZone(timeZone()).date().toString(), incidence); } // Delete child-incidences. if (!incidence->hasRecurrenceId()) { deleteIncidenceInstances(incidence); } notifyIncidenceDeleted(incidence); return true; } else { qCWarning(KCALCORE_LOG) << incidence->typeStr() << " not found. uid=" << uid; return false; } } bool MemoryCalendar::deleteIncidenceInstances(const Incidence::Ptr &incidence) { const Incidence::IncidenceType type = incidence->type(); Incidence::List values = ::values(d->mIncidences[type], incidence->uid()); for (auto it = values.constBegin(); it != values.constEnd(); ++it) { Incidence::Ptr i = *it; if (i->hasRecurrenceId()) { qCDebug(KCALCORE_LOG) << "deleting child" << ", type=" << int(type) << ", uid=" << i->uid() // << ", start=" << i->dtStart() << " from calendar"; deleteIncidence(i); } } return true; } //@cond PRIVATE void MemoryCalendar::Private::deleteAllIncidences(Incidence::IncidenceType incidenceType) { QHashIteratori(mIncidences[incidenceType]); while (i.hasNext()) { i.next(); q->notifyIncidenceAboutToBeDeleted(i.value()); i.value()->unRegisterObserver(q); } mIncidences[incidenceType].clear(); mIncidencesForDate[incidenceType].clear(); } Incidence::Ptr MemoryCalendar::Private::incidence(const QString &uid, Incidence::IncidenceType type, const QDateTime &recurrenceId) const { Incidence::List values = ::values(mIncidences[type], uid); for (auto it = values.constBegin(); it != values.constEnd(); ++it) { Incidence::Ptr i = *it; if (recurrenceId.isNull()) { if (!i->hasRecurrenceId()) { return i; } } else { if (i->hasRecurrenceId() && i->recurrenceId() == recurrenceId) { return i; } } } return Incidence::Ptr(); } Incidence::Ptr MemoryCalendar::Private::deletedIncidence(const QString &uid, const QDateTime &recurrenceId, IncidenceBase::IncidenceType type) const { if (!q->deletionTracking()) { return Incidence::Ptr(); } Incidence::List values = ::values(mDeletedIncidences[type], uid); for (auto it = values.constBegin(); it != values.constEnd(); ++it) { Incidence::Ptr i = *it; if (recurrenceId.isNull()) { if (!i->hasRecurrenceId()) { return i; } } else { if (i->hasRecurrenceId() && i->recurrenceId() == recurrenceId) { return i; } } } return Incidence::Ptr(); } void MemoryCalendar::Private::insertIncidence(const Incidence::Ptr &incidence) { const QString uid = incidence->uid(); const Incidence::IncidenceType type = incidence->type(); if (!mIncidences[type].contains(uid, incidence)) { mIncidences[type].insert(uid, incidence); mIncidencesByIdentifier.insert(incidence->instanceIdentifier(), incidence); const QDateTime dt = incidence->dateTime(Incidence::RoleCalendarHashing); if (dt.isValid()) { - mIncidencesForDate[type].insert(dt.date().toString(), incidence); + mIncidencesForDate[type].insert(dt.toTimeZone(q->timeZone()).date().toString(), incidence); } } else { #ifndef NDEBUG // if we already have an to-do with this UID, it must be the same incidence, // otherwise something's really broken Q_ASSERT(mIncidences[type].value(uid) == incidence); #endif } } //@endcond bool MemoryCalendar::addIncidence(const Incidence::Ptr &incidence) { d->insertIncidence(incidence); notifyIncidenceAdded(incidence); incidence->registerObserver(this); setupRelations(incidence); setModified(true); return true; } bool MemoryCalendar::addEvent(const Event::Ptr &event) { return addIncidence(event); } bool MemoryCalendar::deleteEvent(const Event::Ptr &event) { return deleteIncidence(event); } bool MemoryCalendar::deleteEventInstances(const Event::Ptr &event) { return deleteIncidenceInstances(event); } Event::Ptr MemoryCalendar::event(const QString &uid, const QDateTime &recurrenceId) const { return d->incidence(uid, Incidence::TypeEvent, recurrenceId).staticCast(); } Event::Ptr MemoryCalendar::deletedEvent(const QString &uid, const QDateTime &recurrenceId) const { return d->deletedIncidence(uid, recurrenceId, Incidence::TypeEvent).staticCast(); } bool MemoryCalendar::addTodo(const Todo::Ptr &todo) { return addIncidence(todo); } bool MemoryCalendar::deleteTodo(const Todo::Ptr &todo) { return deleteIncidence(todo); } bool MemoryCalendar::deleteTodoInstances(const Todo::Ptr &todo) { return deleteIncidenceInstances(todo); } Todo::Ptr MemoryCalendar::todo(const QString &uid, const QDateTime &recurrenceId) const { return d->incidence(uid, Incidence::TypeTodo, recurrenceId).staticCast(); } Todo::Ptr MemoryCalendar::deletedTodo(const QString &uid, const QDateTime &recurrenceId) const { return d->deletedIncidence(uid, recurrenceId, Incidence::TypeTodo).staticCast(); } Todo::List MemoryCalendar::rawTodos(TodoSortField sortField, SortDirection sortDirection) const { Todo::List todoList; todoList.reserve(d->mIncidences[Incidence::TypeTodo].count()); QHashIteratori(d->mIncidences[Incidence::TypeTodo]); while (i.hasNext()) { i.next(); todoList.append(i.value().staticCast()); } return Calendar::sortTodos(todoList, sortField, sortDirection); } Todo::List MemoryCalendar::deletedTodos(TodoSortField sortField, SortDirection sortDirection) const { if (!deletionTracking()) { return Todo::List(); } Todo::List todoList; todoList.reserve(d->mDeletedIncidences[Incidence::TypeTodo].count()); QHashIteratori(d->mDeletedIncidences[Incidence::TypeTodo]); while (i.hasNext()) { i.next(); todoList.append(i.value().staticCast()); } return Calendar::sortTodos(todoList, sortField, sortDirection); } Todo::List MemoryCalendar::todoInstances(const Incidence::Ptr &todo, TodoSortField sortField, SortDirection sortDirection) const { Todo::List list; Incidence::List values = ::values(d->mIncidences[Incidence::TypeTodo], todo->uid()); for (auto it = values.constBegin(); it != values.constEnd(); ++it) { Todo::Ptr t = (*it).staticCast(); if (t->hasRecurrenceId()) { list.append(t); } } return Calendar::sortTodos(list, sortField, sortDirection); } Todo::List MemoryCalendar::rawTodosForDate(const QDate &date) const { Todo::List todoList; Todo::Ptr t; const QString dateStr = date.toString(); QMultiHash::const_iterator it = d->mIncidencesForDate[Incidence::TypeTodo].constFind(dateStr); while (it != d->mIncidencesForDate[Incidence::TypeTodo].constEnd() && it.key() == dateStr) { t = it.value().staticCast(); todoList.append(t); ++it; } // Iterate over all todos. Look for recurring todoss that occur on this date QHashIteratori(d->mIncidences[Incidence::TypeTodo]); while (i.hasNext()) { i.next(); t = i.value().staticCast(); if (t->recurs()) { if (t->recursOn(date, timeZone())) { todoList.append(t); } } } return todoList; } Todo::List MemoryCalendar::rawTodos(const QDate &start, const QDate &end, const QTimeZone &timeZone, bool inclusive) const { Q_UNUSED(inclusive); // use only exact dtDue/dtStart, not dtStart and dtEnd Todo::List todoList; const auto ts = timeZone.isValid() ? timeZone : this->timeZone(); QDateTime st(start, QTime(0, 0, 0), ts); QDateTime nd(end, QTime(23, 59, 59, 999), ts); // Get todos QHashIteratori(d->mIncidences[Incidence::TypeTodo]); Todo::Ptr todo; while (i.hasNext()) { i.next(); todo = i.value().staticCast(); if (!isVisible(todo)) { continue; } QDateTime rStart = todo->hasDueDate() ? todo->dtDue() : todo->hasStartDate() ? todo->dtStart() : QDateTime(); if (!rStart.isValid()) { continue; } if (!todo->recurs()) { // non-recurring todos if (nd.isValid() && nd < rStart) { continue; } if (st.isValid() && rStart < st) { continue; } } else { // recurring events switch (todo->recurrence()->duration()) { case -1: // infinite break; case 0: // end date given default: // count given QDateTime rEnd(todo->recurrence()->endDate(), QTime(23, 59, 59, 999), ts); if (!rEnd.isValid()) { continue; } if (st.isValid() && rEnd < st) { continue; } break; } // switch(duration) } //if(recurs) todoList.append(todo); } return todoList; } Alarm::List MemoryCalendar::alarmsTo(const QDateTime &to) const { return alarms(QDateTime(QDate(1900, 1, 1), QTime(0, 0, 0)), to); } Alarm::List MemoryCalendar::alarms(const QDateTime &from, const QDateTime &to, bool excludeBlockedAlarms) const { Q_UNUSED(excludeBlockedAlarms); Alarm::List alarmList; QHashIteratorie(d->mIncidences[Incidence::TypeEvent]); Event::Ptr e; while (ie.hasNext()) { ie.next(); e = ie.value().staticCast(); if (e->recurs()) { appendRecurringAlarms(alarmList, e, from, to); } else { appendAlarms(alarmList, e, from, to); } } QHashIteratorit(d->mIncidences[Incidence::TypeTodo]); Todo::Ptr t; while (it.hasNext()) { it.next(); t = it.value().staticCast(); if (!t->isCompleted()) { appendAlarms(alarmList, t, from, to); if (t->recurs()) { appendRecurringAlarms(alarmList, t, from, to); } else { appendAlarms(alarmList, t, from, to); } } } return alarmList; } void MemoryCalendar::incidenceUpdate(const QString &uid, const QDateTime &recurrenceId) { Incidence::Ptr inc = incidence(uid, recurrenceId); if (inc) { if (!d->mIncidenceBeingUpdated.isEmpty()) { qCWarning(KCALCORE_LOG) << "Incidence::update() called twice without an updated() call in between."; } // Save it so we can detect changes to uid or recurringId. d->mIncidenceBeingUpdated = inc->instanceIdentifier(); const QDateTime dt = inc->dateTime(Incidence::RoleCalendarHashing); if (dt.isValid()) { const Incidence::IncidenceType type = inc->type(); - d->mIncidencesForDate[type].remove(dt.date().toString(), inc); + d->mIncidencesForDate[type].remove(dt.toTimeZone(timeZone()).date().toString(), inc); } } } void MemoryCalendar::incidenceUpdated(const QString &uid, const QDateTime &recurrenceId) { Incidence::Ptr inc = incidence(uid, recurrenceId); if (inc) { if (d->mIncidenceBeingUpdated.isEmpty()) { qCWarning(KCALCORE_LOG) << "Incidence::updated() called twice without an update() call in between."; } else if (inc->instanceIdentifier() != d->mIncidenceBeingUpdated) { // Instance identifier changed, update our hash table d->mIncidencesByIdentifier.remove(d->mIncidenceBeingUpdated); d->mIncidencesByIdentifier.insert(inc->instanceIdentifier(), inc); } d->mIncidenceBeingUpdated = QString(); inc->setLastModified(QDateTime::currentDateTimeUtc()); // we should probably update the revision number here, // or internally in the Event itself when certain things change. // need to verify with ical documentation. const QDateTime dt = inc->dateTime(Incidence::RoleCalendarHashing); if (dt.isValid()) { const Incidence::IncidenceType type = inc->type(); - d->mIncidencesForDate[type].insert(dt.date().toString(), inc); + d->mIncidencesForDate[type].insert(dt.toTimeZone(timeZone()).date().toString(), inc); } notifyIncidenceChanged(inc); setModified(true); } } Event::List MemoryCalendar::rawEventsForDate(const QDate &date, const QTimeZone &timeZone, EventSortField sortField, SortDirection sortDirection) const { Event::List eventList; if (!date.isValid()) { // There can't be events on invalid dates return eventList; } + if (timeZone.isValid() && timeZone != this->timeZone()) { + // We cannot use the hash table on date, since time zone is different. + eventList = rawEvents(date, date, timeZone, false); + return Calendar::sortEvents(eventList, sortField, sortDirection); + } + Event::Ptr ev; // Find the hash for the specified date const QString dateStr = date.toString(); QMultiHash::const_iterator it = d->mIncidencesForDate[Incidence::TypeEvent].constFind(dateStr); // Iterate over all non-recurring, single-day events that start on this date const auto ts = timeZone.isValid() ? timeZone : this->timeZone(); while (it != d->mIncidencesForDate[Incidence::TypeEvent].constEnd() && it.key() == dateStr) { ev = it.value().staticCast(); - QDateTime end(ev->dtEnd().toTimeZone(ev->dtStart().timeZone())); - if (ev->allDay()) { - end.setTime(QTime()); - } else { - end = end.addSecs(-1); - } - if (end.date() >= date) { - eventList.append(ev); - } + eventList.append(ev); ++it; } // Iterate over all events. Look for recurring events that occur on this date QHashIteratori(d->mIncidences[Incidence::TypeEvent]); while (i.hasNext()) { i.next(); ev = i.value().staticCast(); if (ev->recurs()) { if (ev->isMultiDay()) { int extraDays = ev->dtStart().date().daysTo(ev->dtEnd().date()); for (int i = 0; i <= extraDays; ++i) { if (ev->recursOn(date.addDays(-i), ts)) { eventList.append(ev); break; } } } else { if (ev->recursOn(date, ts)) { eventList.append(ev); } } } else { if (ev->isMultiDay()) { - if (ev->dtStart().date() <= date && ev->dtEnd().date() >= date) { + if (ev->dtStart().toTimeZone(ts).date() <= date && ev->dtEnd().toTimeZone(ts).date() >= date) { eventList.append(ev); } } } } return Calendar::sortEvents(eventList, sortField, sortDirection); } Event::List MemoryCalendar::rawEvents(const QDate &start, const QDate &end, const QTimeZone &timeZone, bool inclusive) const { Event::List eventList; const auto ts = timeZone.isValid() ? timeZone : this->timeZone(); QDateTime st(start, QTime(0, 0, 0), ts); QDateTime nd(end, QTime(23, 59, 59, 999), ts); // Get non-recurring events QHashIteratori(d->mIncidences[Incidence::TypeEvent]); Event::Ptr event; while (i.hasNext()) { i.next(); event = i.value().staticCast(); QDateTime rStart = event->dtStart(); if (nd < rStart) { continue; } if (inclusive && rStart < st) { continue; } if (!event->recurs()) { // non-recurring events QDateTime rEnd = event->dtEnd(); if (rEnd < st) { continue; } if (inclusive && nd < rEnd) { continue; } } else { // recurring events switch (event->recurrence()->duration()) { case -1: // infinite if (inclusive) { continue; } break; case 0: // end date given default: // count given QDateTime rEnd(event->recurrence()->endDate(), QTime(23, 59, 59, 999), ts); if (!rEnd.isValid()) { continue; } if (rEnd < st) { continue; } if (inclusive && nd < rEnd) { continue; } break; } // switch(duration) } //if(recurs) eventList.append(event); } return eventList; } Event::List MemoryCalendar::rawEventsForDate(const QDateTime &kdt) const { return rawEventsForDate(kdt.date(), kdt.timeZone()); } Event::List MemoryCalendar::rawEvents(EventSortField sortField, SortDirection sortDirection) const { Event::List eventList; eventList.reserve(d->mIncidences[Incidence::TypeEvent].count()); QHashIterator i(d->mIncidences[Incidence::TypeEvent]); while (i.hasNext()) { i.next(); eventList.append(i.value().staticCast()); } return Calendar::sortEvents(eventList, sortField, sortDirection); } Event::List MemoryCalendar::deletedEvents(EventSortField sortField, SortDirection sortDirection) const { if (!deletionTracking()) { return Event::List(); } Event::List eventList; eventList.reserve(d->mDeletedIncidences[Incidence::TypeEvent].count()); QHashIteratori(d->mDeletedIncidences[Incidence::TypeEvent]); while (i.hasNext()) { i.next(); eventList.append(i.value().staticCast()); } return Calendar::sortEvents(eventList, sortField, sortDirection); } Event::List MemoryCalendar::eventInstances(const Incidence::Ptr &event, EventSortField sortField, SortDirection sortDirection) const { Event::List list; Incidence::List values = ::values(d->mIncidences[Incidence::TypeEvent], event->uid()); for (auto it = values.constBegin(); it != values.constEnd(); ++it) { Event::Ptr ev = (*it).staticCast(); if (ev->hasRecurrenceId()) { list.append(ev); } } return Calendar::sortEvents(list, sortField, sortDirection); } bool MemoryCalendar::addJournal(const Journal::Ptr &journal) { return addIncidence(journal); } bool MemoryCalendar::deleteJournal(const Journal::Ptr &journal) { return deleteIncidence(journal); } bool MemoryCalendar::deleteJournalInstances(const Journal::Ptr &journal) { return deleteIncidenceInstances(journal); } Journal::Ptr MemoryCalendar::journal(const QString &uid, const QDateTime &recurrenceId) const { return d->incidence(uid, Incidence::TypeJournal, recurrenceId).staticCast(); } Journal::Ptr MemoryCalendar::deletedJournal(const QString &uid, const QDateTime &recurrenceId) const { return d->deletedIncidence(uid, recurrenceId, Incidence::TypeJournal).staticCast(); } Journal::List MemoryCalendar::rawJournals(JournalSortField sortField, SortDirection sortDirection) const { Journal::List journalList; QHashIteratori(d->mIncidences[Incidence::TypeJournal]); while (i.hasNext()) { i.next(); journalList.append(i.value().staticCast()); } return Calendar::sortJournals(journalList, sortField, sortDirection); } Journal::List MemoryCalendar::deletedJournals(JournalSortField sortField, SortDirection sortDirection) const { if (!deletionTracking()) { return Journal::List(); } Journal::List journalList; journalList.reserve(d->mDeletedIncidences[Incidence::TypeJournal].count()); QHashIteratori(d->mDeletedIncidences[Incidence::TypeJournal]); while (i.hasNext()) { i.next(); journalList.append(i.value().staticCast()); } return Calendar::sortJournals(journalList, sortField, sortDirection); } Journal::List MemoryCalendar::journalInstances(const Incidence::Ptr &journal, JournalSortField sortField, SortDirection sortDirection) const { Journal::List list; Incidence::List values = ::values(d->mIncidences[Incidence::TypeJournal], journal->uid()); for (auto it = values.constBegin(); it != values.constEnd(); ++it) { Journal::Ptr j = (*it).staticCast(); if (j->hasRecurrenceId()) { list.append(j); } } return Calendar::sortJournals(list, sortField, sortDirection); } Journal::List MemoryCalendar::rawJournalsForDate(const QDate &date) const { Journal::List journalList; Journal::Ptr j; QString dateStr = date.toString(); QMultiHash::const_iterator it = d->mIncidencesForDate[Incidence::TypeJournal].constFind(dateStr); while (it != d->mIncidencesForDate[Incidence::TypeJournal].constEnd() && it.key() == dateStr) { j = it.value().staticCast(); journalList.append(j); ++it; } return journalList; } Incidence::Ptr MemoryCalendar::instance(const QString &identifier) const { return d->mIncidencesByIdentifier.value(identifier); } void MemoryCalendar::virtual_hook(int id, void *data) { Q_UNUSED(id); Q_UNUSED(data); Q_ASSERT(false); } diff --git a/src/memorycalendar.h b/src/memorycalendar.h index ea8472fc4..2aff53021 100644 --- a/src/memorycalendar.h +++ b/src/memorycalendar.h @@ -1,333 +1,338 @@ /* This file is part of the kcalcore library. Copyright (c) 1998 Preston Brown Copyright (c) 2001,2003 Cornelius Schumacher 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 MemoryCalendar class. Very simple implementation of a Calendar that is only in memory @author Preston Brown \ @author Cornelius Schumacher \ */ #ifndef KCALCORE_MEMORYCALENDAR_H #define KCALCORE_MEMORYCALENDAR_H #include "kcalendarcore_export.h" #include "calendar.h" namespace KCalendarCore { /** @brief This class provides a calendar stored in memory. */ class KCALENDARCORE_EXPORT MemoryCalendar : public Calendar { Q_OBJECT public: /** A shared pointer to a MemoryCalendar */ typedef QSharedPointer Ptr; /** @copydoc Calendar::Calendar(const QTimeZone &) */ explicit MemoryCalendar(const QTimeZone &timeZone); /** @copydoc Calendar::Calendar(const QString &) */ explicit MemoryCalendar(const QByteArray &timeZoneId); /** @copydoc Calendar::~Calendar() */ ~MemoryCalendar() override; /** Clears out the current calendar, freeing all used memory etc. etc. */ void close() override; + /** + @copydoc Calendar::doSetTimeZone() + */ + void doSetTimeZone(const QTimeZone &timeZone) override; + /** @copydoc Calendar::deleteIncidence() */ bool deleteIncidence(const Incidence::Ptr &incidence) override; /** @copydoc Calendar::deleteIncidenceInstances */ bool deleteIncidenceInstances(const Incidence::Ptr &incidence) override; /** @copydoc Calendar::addIncidence() */ bool addIncidence(const Incidence::Ptr &incidence) override; // Event Specific Methods // /** @copydoc Calendar::addEvent() */ bool addEvent(const Event::Ptr &event) override; /** @copydoc Calendar::deleteEvent() */ bool deleteEvent(const Event::Ptr &event) override; /** @copydoc Calendar::deleteEventInstances() */ bool deleteEventInstances(const Event::Ptr &event) override; /** @copydoc Calendar::rawEvents(EventSortField, SortDirection)const */ Q_REQUIRED_RESULT Event::List rawEvents( EventSortField sortField = EventSortUnsorted, SortDirection sortDirection = SortDirectionAscending) const override; /** @copydoc Calendar::rawEvents(const QDate &, const QDate &, const QTimeZone &, bool)const */ Q_REQUIRED_RESULT Event::List rawEvents(const QDate &start, const QDate &end, const QTimeZone &timeZone = {}, bool inclusive = false) const override; /** Returns an unfiltered list of all Events which occur on the given date. @param date request unfiltered Event list for this QDate only. @param timeZone time zone to interpret @p date, or the calendar's default time zone if none is specified @param sortField specifies the EventSortField. @param sortDirection specifies the SortDirection. @return the list of unfiltered Events occurring on the specified QDate. */ Q_REQUIRED_RESULT Event::List rawEventsForDate( const QDate &date, const QTimeZone &timeZone = {}, EventSortField sortField = EventSortUnsorted, SortDirection sortDirection = SortDirectionAscending) const override; /** @copydoc Calendar::rawEventsForDate(const QDateTime &)const */ Q_REQUIRED_RESULT Event::List rawEventsForDate(const QDateTime &dt) const override; /** * Returns an incidence by identifier. * @see Incidence::instanceIdentifier() * @since 4.11 */ Incidence::Ptr instance(const QString &identifier) const; /** @copydoc Calendar::event() */ Q_REQUIRED_RESULT Event::Ptr event(const QString &uid, const QDateTime &recurrenceId = {}) const override; /** @copydoc Calendar::deletedEvent() */ Q_REQUIRED_RESULT Event::Ptr deletedEvent(const QString &uid, const QDateTime &recurrenceId = {}) const override; /** @copydoc Calendar::deletedEvents(EventSortField, SortDirection)const */ Q_REQUIRED_RESULT Event::List deletedEvents( EventSortField sortField = EventSortUnsorted, SortDirection sortDirection = SortDirectionAscending) const override; /** @copydoc Calendar::eventInstances(const Incidence::Ptr &, EventSortField, SortDirection)const */ Q_REQUIRED_RESULT Event::List eventInstances( const Incidence::Ptr &event, EventSortField sortField = EventSortUnsorted, SortDirection sortDirection = SortDirectionAscending) const override; // To-do Specific Methods // /** @copydoc Calendar::addTodo() */ bool addTodo(const Todo::Ptr &todo) override; /** @copydoc Calendar::deleteTodo() */ bool deleteTodo(const Todo::Ptr &todo) override; /** @copydoc Calendar::deleteTodoInstances() */ bool deleteTodoInstances(const Todo::Ptr &todo) override; /** @copydoc Calendar::rawTodos(TodoSortField, SortDirection)const */ Q_REQUIRED_RESULT Todo::List rawTodos( TodoSortField sortField = TodoSortUnsorted, SortDirection sortDirection = SortDirectionAscending) const override; /** @copydoc Calendar::rawTodos(const QDate &, const QDate &, const QTimeZone &, bool)const */ Q_REQUIRED_RESULT Todo::List rawTodos( const QDate &start, const QDate &end, const QTimeZone &timeZone = {}, bool inclusive = false) const override; /** @copydoc Calendar::rawTodosForDate() */ Q_REQUIRED_RESULT Todo::List rawTodosForDate(const QDate &date) const override; /** @copydoc Calendar::todo() */ Q_REQUIRED_RESULT Todo::Ptr todo(const QString &uid, const QDateTime &recurrenceId = {}) const override; /** @copydoc Calendar::deletedTodo() */ Q_REQUIRED_RESULT Todo::Ptr deletedTodo(const QString &uid, const QDateTime &recurrenceId = {}) const override; /** @copydoc Calendar::deletedTodos(TodoSortField, SortDirection)const */ Q_REQUIRED_RESULT Todo::List deletedTodos( TodoSortField sortField = TodoSortUnsorted, SortDirection sortDirection = SortDirectionAscending) const override; /** @copydoc Calendar::todoInstances(const Incidence::Ptr &, TodoSortField, SortDirection)const */ Q_REQUIRED_RESULT Todo::List todoInstances(const Incidence::Ptr &todo, TodoSortField sortField = TodoSortUnsorted, SortDirection sortDirection = SortDirectionAscending) const override; // Journal Specific Methods // /** @copydoc Calendar::addJournal() */ bool addJournal(const Journal::Ptr &journal) override; /** @copydoc Calendar::deleteJournal() */ bool deleteJournal(const Journal::Ptr &journal) override; /** @copydoc Calendar::deleteJournalInstances() */ bool deleteJournalInstances(const Journal::Ptr &journal) override; /** @copydoc Calendar::rawJournals() */ Q_REQUIRED_RESULT Journal::List rawJournals( JournalSortField sortField = JournalSortUnsorted, SortDirection sortDirection = SortDirectionAscending) const override; /** @copydoc Calendar::rawJournalsForDate() */ Q_REQUIRED_RESULT Journal::List rawJournalsForDate(const QDate &date) const override; /** @copydoc Calendar::journal() */ Journal::Ptr journal(const QString &uid, const QDateTime &recurrenceId = {}) const override; /** @copydoc Calendar::deletedJournal() */ Journal::Ptr deletedJournal(const QString &uid, const QDateTime &recurrenceId = {}) const override; /** @copydoc Calendar::deletedJournals(JournalSortField, SortDirection)const */ Q_REQUIRED_RESULT Journal::List deletedJournals( JournalSortField sortField = JournalSortUnsorted, SortDirection sortDirection = SortDirectionAscending) const override; /** @copydoc Calendar::journalInstances(const Incidence::Ptr &, JournalSortField, SortDirection)const */ Q_REQUIRED_RESULT Journal::List journalInstances(const Incidence::Ptr &journal, JournalSortField sortField = JournalSortUnsorted, SortDirection sortDirection = SortDirectionAscending) const override; // Alarm Specific Methods // /** @copydoc Calendar::alarms() */ Q_REQUIRED_RESULT Alarm::List alarms(const QDateTime &from, const QDateTime &to, bool excludeBlockedAlarms = false) const override; /** Return a list of Alarms that occur before the specified timestamp. @param to is the ending timestamp. @return the list of Alarms occurring before the specified QDateTime. */ Q_REQUIRED_RESULT Alarm::List alarmsTo(const QDateTime &to) const; /** @copydoc Calendar::incidenceUpdate(const QString &,const QDateTime &) */ void incidenceUpdate(const QString &uid, const QDateTime &recurrenceId) override; /** @copydoc Calendar::incidenceUpdated(const QString &,const QDateTime &) */ void incidenceUpdated(const QString &uid, const QDateTime &recurrenceId) override; using QObject::event; // prevent warning about hidden virtual method protected: /** @copydoc IncidenceBase::virtual_hook() */ void virtual_hook(int id, void *data) override; private: //@cond PRIVATE class Private; Private *const d; //@endcond Q_DISABLE_COPY(MemoryCalendar) }; } #endif