diff options
author | Kevin Wu Won <kevin.wu-won@nokia.com> | 2010-08-27 10:31:56 +1000 |
---|---|---|
committer | Kevin Wu Won <kevin.wu-won@nokia.com> | 2010-08-27 11:11:44 +1000 |
commit | 0276c2a8836026fc0bff763f6f08281fac2997d1 (patch) | |
tree | 49531ca7b9e77f2be3bcee65410afd27e916fdb3 | |
parent | a5cf107614bafb50e29234ae11051707d2014e5a (diff) |
Add support for VTIMEZONE in QVersitOrganizerImporter
Added auto tests for it
Added extra value to enum QOrganizerItemRecurrenceRule::Frequency =
Invalid
-rw-r--r-- | src/organizer/engines/qorganizeritemmemorybackend.cpp | 59 | ||||
-rw-r--r-- | src/organizer/qorganizeritemrecurrencerule.cpp | 5 | ||||
-rw-r--r-- | src/organizer/qorganizeritemrecurrencerule.h | 1 | ||||
-rw-r--r-- | src/versitorganizer/qversitorganizerimporter.cpp | 7 | ||||
-rw-r--r-- | src/versitorganizer/qversitorganizerimporter_p.cpp | 73 | ||||
-rw-r--r-- | src/versitorganizer/qversitorganizerimporter_p.h | 17 | ||||
-rw-r--r-- | src/versitorganizer/timezones_p.cpp | 85 | ||||
-rw-r--r-- | src/versitorganizer/timezones_p.h | 116 | ||||
-rw-r--r-- | src/versitorganizer/versitorganizer.pro | 6 | ||||
-rw-r--r-- | tests/auto/qversitorganizerimporter/tst_qversitorganizerimporter.cpp | 156 | ||||
-rw-r--r-- | tests/auto/qversitorganizerimporter/tst_qversitorganizerimporter.h | 3 |
11 files changed, 481 insertions, 47 deletions
diff --git a/src/organizer/engines/qorganizeritemmemorybackend.cpp b/src/organizer/engines/qorganizeritemmemorybackend.cpp index 10587bcc92..5332943bb3 100644 --- a/src/organizer/engines/qorganizeritemmemorybackend.cpp +++ b/src/organizer/engines/qorganizeritemmemorybackend.cpp @@ -200,11 +200,6 @@ QList<QDateTime> QOrganizerItemMemoryEngine::generateDateTimes(const QDateTime& maxCount = qMin(maxCount, rrule.count()); QDateTime realPeriodEnd(periodEnd); - if (!periodEnd.isValid()) { - // If no endDateTime is given, we'll only generate items that occur within the next 4 years of periodStart. - realPeriodEnd.setDate(periodStart.date().addDays(1461)); - realPeriodEnd.setTime(periodStart.time()); - } if (rrule.endDate().isValid() && rrule.endDate() < realPeriodEnd.date()) realPeriodEnd.setDate(rrule.endDate()); @@ -308,6 +303,8 @@ void QOrganizerItemMemoryEngine::inferMissingCriteria(QOrganizerItemRecurrenceRu break; case QOrganizerItemRecurrenceRule::Daily: break; + case QOrganizerItemRecurrenceRule::Invalid: + Q_ASSERT(false); } } @@ -348,7 +345,7 @@ bool QOrganizerItemMemoryEngine::inIntervaledPeriod(const QDate& date, const QDa int daysDelta = initialDate.daysTo(date); return (daysDelta % interval == 0); } - default: + case QOrganizerItemRecurrenceRule::Invalid: Q_ASSERT(false); return true; } @@ -410,7 +407,7 @@ QDate QOrganizerItemMemoryEngine::firstDateInNextPeriod(const QDate& date, QOrga case QOrganizerItemRecurrenceRule::Daily: retn = retn.addDays(1); return retn; - default: + case QOrganizerItemRecurrenceRule::Invalid: Q_ASSERT(false); return retn; } @@ -478,7 +475,28 @@ QList<QOrganizerItem> QOrganizerItemMemoryEngine::itemInstances(const QOrganizer // Also, should this also return the exception instances (ie, return any persistent instances with parent information == generator?) // XXX TODO: in detail validation, ensure that the referenced parent Id exists... - if (periodStart > periodEnd) { + QDateTime realPeriodStart(periodStart); + QDateTime realPeriodEnd(periodEnd); + QDateTime initialDateTime; + if (generator.type() == QOrganizerItemType::TypeEvent) { + QOrganizerEvent evt = generator; + initialDateTime = evt.startDateTime(); + } else if (generator.type() == QOrganizerItemType::TypeTodo) { + QOrganizerTodo todo = generator; + initialDateTime = todo.startDateTime(); + } else { + // erm... not a recurring item in our schema... + return QList<QOrganizerItem>() << generator; + } + + if (initialDateTime > realPeriodStart) + realPeriodStart = initialDateTime; + if (!periodEnd.isValid()) { + // If no endDateTime is given, we'll only generate items that occur within the next 4 years of realPeriodStart. + realPeriodEnd.setDate(realPeriodStart.date().addDays(1461)); + realPeriodEnd.setTime(realPeriodStart.time()); + } + if (realPeriodStart > realPeriodEnd) { *error = QOrganizerItemManager::BadArgumentError; return QList<QOrganizerItem>(); } @@ -504,24 +522,12 @@ QList<QOrganizerItem> QOrganizerItemMemoryEngine::itemInstances(const QOrganizer upperBound = instance.dueDateTime(); } - if ((lowerBound.isNull() || lowerBound > periodStart) && (upperBound.isNull() || upperBound < periodEnd)) { + if ((lowerBound.isNull() || lowerBound > realPeriodStart) && (upperBound.isNull() || upperBound < realPeriodEnd)) { // this occurrence fulfils the criteria. retn.append(currException); } } - QDateTime initialDateTime; - if (generator.type() == QOrganizerItemType::TypeEvent) { - QOrganizerEvent evt = generator; - initialDateTime = evt.startDateTime(); - } else if (generator.type() == QOrganizerItemType::TypeTodo) { - QOrganizerTodo todo = generator; - initialDateTime = todo.startDateTime(); - } else { - // erm... not a recurring item in our schema... - return QList<QOrganizerItem>() << generator; - } - // then, generate the required (unchanged) instances from the generator. // before doing that, we have to find out all of the exception dates. QList<QDate> xdates; @@ -530,9 +536,10 @@ QList<QOrganizerItem> QOrganizerItemMemoryEngine::itemInstances(const QOrganizer } QList<QOrganizerItemRecurrenceRule> xrules = recur.exceptionRules(); foreach (const QOrganizerItemRecurrenceRule& xrule, xrules) { - if ((xrule.endDate().isNull()) || (xrule.endDate() >= periodStart.date())) { + if (xrule.frequency() != QOrganizerItemRecurrenceRule::Invalid + && ((xrule.endDate().isNull()) || (xrule.endDate() >= realPeriodStart.date()))) { // we cannot skip it, since it applies in the given time period. - QList<QDateTime> xdatetimes = generateDateTimes(initialDateTime, xrule, periodStart, periodEnd, 50); // max count of 50 is arbitrary... + QList<QDateTime> xdatetimes = generateDateTimes(initialDateTime, xrule, realPeriodStart, realPeriodEnd, 50); // max count of 50 is arbitrary... foreach (const QDateTime& xdatetime, xdatetimes) { xdates += xdatetime.date(); } @@ -541,14 +548,16 @@ QList<QOrganizerItem> QOrganizerItemMemoryEngine::itemInstances(const QOrganizer // now generate a list of rdates (from the recurrenceDates and recurrenceRules) QList<QDateTime> rdates; + rdates += initialDateTime; foreach (const QDate& rdate, recur.recurrenceDates()) { rdates += QDateTime(rdate, initialDateTime.time()); } QList<QOrganizerItemRecurrenceRule> rrules = recur.recurrenceRules(); foreach (const QOrganizerItemRecurrenceRule& rrule, rrules) { - if ((rrule.endDate().isNull()) || (rrule.endDate() >= periodStart.date())) { + if (rrule.frequency() != QOrganizerItemRecurrenceRule::Invalid + && ((rrule.endDate().isNull()) || (rrule.endDate() >= realPeriodStart.date()))) { // we cannot skip it, since it applies in the given time period. - rdates += generateDateTimes(initialDateTime, rrule, periodStart, periodEnd, 50); // max count of 50 is arbitrary... + rdates += generateDateTimes(initialDateTime, rrule, realPeriodStart, realPeriodEnd, 50); // max count of 50 is arbitrary... } } diff --git a/src/organizer/qorganizeritemrecurrencerule.cpp b/src/organizer/qorganizeritemrecurrencerule.cpp index 2dc9abf41c..ad156ef5a2 100644 --- a/src/organizer/qorganizeritemrecurrencerule.cpp +++ b/src/organizer/qorganizeritemrecurrencerule.cpp @@ -80,6 +80,7 @@ Q_DEFINE_LATIN1_CONSTANT(QOrganizerItemRecurrenceRule::FieldWeekStart, "WeekStar /*! * \enum QOrganizerItemRecurrenceRule::Frequency + * \value Invalid Signifies that the entire rrule is invalid. * \value Daily * \value Weekly * \value Monthly @@ -145,14 +146,14 @@ void QOrganizerItemRecurrenceRule::setFrequency(Frequency freq) } /*! - * Returns the frequency with which the item recurs. The default frequency is 1. + * Returns the frequency with which the item recurs. The default frequency is Invalid. */ QOrganizerItemRecurrenceRule::Frequency QOrganizerItemRecurrenceRule::frequency() const { if (d->m_variantValues.contains(FieldFrequency)) return static_cast<Frequency>(d->m_variantValues.value(FieldFrequency).toInt()); else - return Weekly; + return Invalid; } /*! Sets the "count" condition of the recurrence rule to \a count. If an end-date was previously diff --git a/src/organizer/qorganizeritemrecurrencerule.h b/src/organizer/qorganizeritemrecurrencerule.h index 700aa14942..32b94a04e8 100644 --- a/src/organizer/qorganizeritemrecurrencerule.h +++ b/src/organizer/qorganizeritemrecurrencerule.h @@ -134,6 +134,7 @@ public: // enums enum Frequency { + Invalid, Daily, Weekly, Monthly, diff --git a/src/versitorganizer/qversitorganizerimporter.cpp b/src/versitorganizer/qversitorganizerimporter.cpp index 8984a6431f..6036cbb4c2 100644 --- a/src/versitorganizer/qversitorganizerimporter.cpp +++ b/src/versitorganizer/qversitorganizerimporter.cpp @@ -161,8 +161,11 @@ bool QVersitOrganizerImporter::importDocument(const QVersitDocument& document) if (d->importDocument(subDocument, &item, &error)) { d->mItems.append(item); } else { - d->mErrors.insert(documentIndex, error); - ok = false; + // importDocument can return false with no error if it's a non-document component + if (error != QVersitOrganizerImporter::NoError) { + d->mErrors.insert(documentIndex, error); + ok = false; + } } documentIndex++; } diff --git a/src/versitorganizer/qversitorganizerimporter_p.cpp b/src/versitorganizer/qversitorganizerimporter_p.cpp index 1eb744a9f2..9946a6212c 100644 --- a/src/versitorganizer/qversitorganizerimporter_p.cpp +++ b/src/versitorganizer/qversitorganizerimporter_p.cpp @@ -88,7 +88,9 @@ bool QVersitOrganizerImporterPrivate::importDocument( } else if (document.componentType() == QLatin1String("VJOURNAL")) { item->setType(QOrganizerItemType::TypeJournal); } else if (document.componentType() == QLatin1String("VTIMEZONE")) { - // TODO + mTimeZones.addTimeZone(importTimeZone(document)); + *error = QVersitOrganizerImporter::NoError; + return false; } else { *error = QVersitOrganizerImporter::InvalidDocumentError; return false; @@ -411,14 +413,17 @@ bool QVersitOrganizerImporterPrivate::createJournalEntryDateTime( * time zone (returned as a UTC datetime). Returns an invalid QDateTime if the string cannot be * parsed. */ -QDateTime QVersitOrganizerImporterPrivate::parseDateTime(const QVersitProperty& property) +QDateTime QVersitOrganizerImporterPrivate::parseDateTime(const QVersitProperty& property) const { - QDateTime datetime = parseDateTime(property.value()); - if (datetime.isValid() && datetime.timeSpec() == Qt::LocalTime && mTimeZoneHandler) { + QDateTime datetime(parseDateTime(property.value())); + if (datetime.isValid() && datetime.timeSpec() == Qt::LocalTime) { QMultiHash<QString, QString> params = property.parameters(); QString tzid = params.value(QLatin1String("TZID")); if (!tzid.isEmpty()) { - datetime = mTimeZoneHandler->convertTimeZoneToUtc(datetime, tzid); + if (tzid.at(0) == QLatin1Char('/') && mTimeZoneHandler) + datetime = mTimeZoneHandler->convertTimeZoneToUtc(datetime, tzid); + else + datetime = mTimeZones.convert(datetime, tzid); } } return datetime; @@ -427,7 +432,7 @@ QDateTime QVersitOrganizerImporterPrivate::parseDateTime(const QVersitProperty& /*! Parses \a str as an ISO 8601 datetime in basic format, either in UTC timezone or floating * timezone. Returns an invalid QDateTime if the string cannot be parsed. */ -QDateTime QVersitOrganizerImporterPrivate::parseDateTime(QString str) +QDateTime QVersitOrganizerImporterPrivate::parseDateTime(QString str) const { bool utc = str.endsWith(QLatin1Char('Z'), Qt::CaseInsensitive); if (utc) @@ -464,7 +469,7 @@ bool QVersitOrganizerImporterPrivate::createRecurrenceRule( * Parses an iCalendar recurrence rule string \a str and puts the result in \a rule. * Return true on success, false on failure. */ -bool QVersitOrganizerImporterPrivate::parseRecurRule(const QString& str, QOrganizerItemRecurrenceRule* rule) +bool QVersitOrganizerImporterPrivate::parseRecurRule(const QString& str, QOrganizerItemRecurrenceRule* rule) const { QStringList parts = str.split(QLatin1Char(';')); if (parts.size() == 0) @@ -503,7 +508,7 @@ bool QVersitOrganizerImporterPrivate::parseRecurRule(const QString& str, QOrgani * \a key is the part of the fragment before the equals sign and \a value is the part after. */ void QVersitOrganizerImporterPrivate::parseRecurFragment(const QString& key, const QString& value, - QOrganizerItemRecurrenceRule* rule) + QOrganizerItemRecurrenceRule* rule) const { if (key == QLatin1String("INTERVAL")) { bool ok; @@ -597,7 +602,7 @@ void QVersitOrganizerImporterPrivate::parseRecurFragment(const QString& key, con * Parses and returns a comma-separated list of integers. Only non-zero values between \a min and * \a max (inclusive) are added */ -QList<int> QVersitOrganizerImporterPrivate::parseIntList(const QString& str, int min, int max) +QList<int> QVersitOrganizerImporterPrivate::parseIntList(const QString& str, int min, int max) const { QList<int> values; QStringList parts = str.split(QLatin1Char(',')); @@ -615,7 +620,7 @@ QList<int> QVersitOrganizerImporterPrivate::parseIntList(const QString& str, int * Parses an iCalendar two-character string representing a day of week and returns an int * corresponding to Qt::DayOfWeek. Returns -1 on parse failure. */ -int QVersitOrganizerImporterPrivate::parseDayOfWeek(const QString& str) +int QVersitOrganizerImporterPrivate::parseDayOfWeek(const QString& str) const { if (str == QLatin1String("MO")) { return Qt::Monday; @@ -892,3 +897,51 @@ QString Duration::nextToken(QString* str) return QString(); // null QString } } + +TimeZone QVersitOrganizerImporterPrivate::importTimeZone(const QVersitDocument& document) const +{ + TimeZone timeZone; + foreach (const QVersitProperty& property, document.properties()) { + if (property.name() == QLatin1String("TZID") && !property.value().isEmpty()) { + timeZone.setTzid(property.value()); + } + } + foreach (const QVersitDocument& subDocument, document.subDocuments()) { + timeZone.addPhase(importTimeZonePhase(subDocument)); + } + return timeZone; +} + +TimeZonePhase QVersitOrganizerImporterPrivate::importTimeZonePhase(const QVersitDocument& document) const +{ + TimeZonePhase phase; + phase.setStandard(document.componentType() == QLatin1String("STANDARD")); + + foreach (const QVersitProperty& property, document.properties()) { + if (property.name() == QLatin1String("TZOFFSETTO")) { + QString value(property.value()); + if (value.size() == 5 + && (value.at(0) == QLatin1Char('+') || value.at(0) == QLatin1Char('-')) + && value.at(1).isDigit() + && value.at(2).isDigit() + && value.at(3).isDigit() + && value.at(4).isDigit()) { + phase.setUtcOffset((value.at(0) == QLatin1Char('+') ? 1 : -1) * ( // deal with sign + value.mid(1, 2).toInt() * 3600 // hours part + + value.mid(3, 2).toInt() * 60 // minutes part + )); + } + } else if (property.name() == QLatin1String("DTSTART")) { + QDateTime dt(parseDateTime(property.value())); + if (dt.isValid() && dt.timeSpec() == Qt::LocalTime) { + phase.setStartDateTime(dt); + } + } else if (property.name() == QLatin1String("RRULE")) { + QOrganizerItemRecurrenceRule rrule; + if (parseRecurRule(property.value(), &rrule)) { + phase.setRecurrenceRule(rrule); + } + } + } + return phase; +} diff --git a/src/versitorganizer/qversitorganizerimporter_p.h b/src/versitorganizer/qversitorganizerimporter_p.h index c4911dbb94..0b4a85fb45 100644 --- a/src/versitorganizer/qversitorganizerimporter_p.h +++ b/src/versitorganizer/qversitorganizerimporter_p.h @@ -57,6 +57,7 @@ #include "qorganizeritemrecurrence.h" #include "qversitorganizerhandler.h" #include "qversittimezonehandler.h" +#include "timezones_p.h" QTM_BEGIN_NAMESPACE class QOrganizerItem; @@ -169,18 +170,18 @@ private: const QVersitProperty& property, QOrganizerItem* item, QList<QOrganizerItemDetail>* updatedDetails); - QDateTime parseDateTime(const QVersitProperty& property); - QDateTime parseDateTime(QString str); + QDateTime parseDateTime(const QVersitProperty& property) const; + QDateTime parseDateTime(QString str) const; bool createRecurrenceRule( const QVersitProperty& property, QOrganizerItem* item, QList<QOrganizerItemDetail>* updatedDetails); - bool parseRecurRule(const QString& str, QOrganizerItemRecurrenceRule* rule); + bool parseRecurRule(const QString& str, QOrganizerItemRecurrenceRule* rule) const; void parseRecurFragment(const QString& key, const QString& value, - QOrganizerItemRecurrenceRule* rule); - QList<int> parseIntList(const QString& str, int min, int max); - int parseDayOfWeek(const QString& str); + QOrganizerItemRecurrenceRule* rule) const; + QList<int> parseIntList(const QString& str, int min, int max) const; + int parseDayOfWeek(const QString& str) const; bool createRecurrenceDates( const QVersitProperty& property, @@ -202,8 +203,12 @@ private: QOrganizerItem* item, QList<QOrganizerItemDetail>* updatedDetails); + TimeZone importTimeZone(const QVersitDocument& document) const; + TimeZonePhase importTimeZonePhase(const QVersitDocument& document) const; + // versit property name -> <definition name, field name>: QMap<QString, QPair<QString, QString> > mPropertyMappings; + TimeZones mTimeZones; }; QTM_END_NAMESPACE diff --git a/src/versitorganizer/timezones_p.cpp b/src/versitorganizer/timezones_p.cpp new file mode 100644 index 0000000000..341f0511a0 --- /dev/null +++ b/src/versitorganizer/timezones_p.cpp @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "timezones_p.h" +#include "qtorganizer.h" +#include <QDateTime> + +QOrganizerItemManager* TimeZone::getManager() +{ + static QOrganizerItemManager* manager(new QOrganizerItemManager()); + return manager; +} + +QDateTime TimeZone::convert(const QDateTime& dateTime) const +{ + Q_ASSERT(isValid()); + QOrganizerItemManager* manager = getManager(); + int offset; + QDateTime latestPhase; + foreach(const TimeZonePhase& phase, mPhases) { + QOrganizerEvent event; + event.setStartDateTime(phase.startDateTime()); + event.setRecurrenceRules(QList<QOrganizerItemRecurrenceRule>() << phase.recurrenceRule()); + QList<QOrganizerItem> occurrences = + manager->itemInstances(event, phase.startDateTime(), dateTime, 500); + if (!occurrences.isEmpty()) { + QDateTime phaseStart(static_cast<QOrganizerEventOccurrence>(occurrences.last()).startDateTime()); + if (phaseStart > latestPhase) { + latestPhase = phaseStart; + offset = phase.utcOffset(); + } + } + } + QDateTime retn(dateTime); + retn.setTimeSpec(Qt::UTC); + return retn.addSecs(-offset); +} + +QDateTime TimeZones::convert(const QDateTime& dateTime, const QString& tzid) const +{ + if (!mTimeZones.contains(tzid)) + return QDateTime(); + TimeZone tz = mTimeZones.value(tzid); + if (!tz.isValid()) + return QDateTime(); + return tz.convert(dateTime); +} diff --git a/src/versitorganizer/timezones_p.h b/src/versitorganizer/timezones_p.h new file mode 100644 index 0000000000..600400fc88 --- /dev/null +++ b/src/versitorganizer/timezones_p.h @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef TIMEZONES_P_H +#define TIMEZONES_P_H + +#include <QDateTime> +#include <QHash> +#include "qorganizeritemrecurrencerule.h" + +QTM_BEGIN_NAMESPACE +class QOrganizerItemManager; +QTM_END_NAMESPACE + +QTM_USE_NAMESPACE + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +class TimeZonePhase { + public: + TimeZonePhase() : mStandard(true), mUtcOffset(100000) {} // invalid offset + void setStandard(bool standard) { mStandard = standard; } + bool isStandard() const { return mStandard; } + void setUtcOffset(int offset) { mUtcOffset = offset; } + int utcOffset() const { return mUtcOffset; } + void setStartDateTime(const QDateTime& dateTime) { mStartDateTime = dateTime; } + QDateTime startDateTime() const { return mStartDateTime; } + void setRecurrenceRule(const QOrganizerItemRecurrenceRule& rrule) { mRecurrenceRule = rrule; } + QOrganizerItemRecurrenceRule recurrenceRule() const { return mRecurrenceRule; } + bool isValid() const { + return mStartDateTime.isValid() && mUtcOffset < 86400 && mUtcOffset > -86400; + } + private: + bool mStandard; // true for STANDARD, false for DAYLIGHT + int mUtcOffset; // in seconds, the offset to apply after mStartDateTime + QDateTime mStartDateTime; // local time, when the phase comes into effect + QOrganizerItemRecurrenceRule mRecurrenceRule; +}; + +class TimeZone { + public: + QDateTime convert(const QDateTime& dateTime) const; + void setTzid(const QString& tzid) { mTzid = tzid; } + QString tzid() const { return mTzid; } + void addPhase(const TimeZonePhase& phase) { mPhases.append(phase); } + bool isValid() const { + foreach (const TimeZonePhase& phase, mPhases) { + if (!phase.isValid()) return false; + } + return !mPhases.isEmpty(); + } + + private: + static QOrganizerItemManager* getManager(); + QString mTzid; + QList<TimeZonePhase> mPhases; +}; + +class TimeZones { + public: + QDateTime convert(const QDateTime& dateTime, const QString& tzid) const; + void addTimeZone(const TimeZone& timezone) { + if (!timezone.tzid().isEmpty()) + mTimeZones.insert(timezone.tzid(), timezone); + } + private: + QHash<QString, TimeZone> mTimeZones; +}; + +#endif diff --git a/src/versitorganizer/versitorganizer.pro b/src/versitorganizer/versitorganizer.pro index 960d042c92..1a5cfa247e 100644 --- a/src/versitorganizer/versitorganizer.pro +++ b/src/versitorganizer/versitorganizer.pro @@ -30,7 +30,8 @@ PRIVATE_HEADERS += \ qversitorganizerexporter_p.h \ qversitorganizerimporter_p.h \ qversitorganizerdefs_p.h \ - qversitorganizerpluginloader_p.h + qversitorganizerpluginloader_p.h \ + timezones_p.h # Implementation SOURCES += \ @@ -39,7 +40,8 @@ SOURCES += \ qversitorganizerimporter.cpp \ qversitorganizerimporter_p.cpp \ qversitorganizerhandler.cpp \ - qversitorganizerpluginloader_p.cpp + qversitorganizerpluginloader_p.cpp \ + timezones_p.cpp HEADERS += \ $$PUBLIC_HEADERS \ diff --git a/tests/auto/qversitorganizerimporter/tst_qversitorganizerimporter.cpp b/tests/auto/qversitorganizerimporter/tst_qversitorganizerimporter.cpp index f4f3007d79..8291baa843 100644 --- a/tests/auto/qversitorganizerimporter/tst_qversitorganizerimporter.cpp +++ b/tests/auto/qversitorganizerimporter/tst_qversitorganizerimporter.cpp @@ -769,4 +769,160 @@ void tst_QVersitOrganizerImporter::testImportTodoProperties_data() } } +void tst_QVersitOrganizerImporter::testTimeZones() +{ + QFETCH(QString, tzid); + QFETCH(QVersitDocument, timezoneSpec); + QFETCH(QString, datetimeString); + QFETCH(QDateTime, expected); + + QVersitDocument document(QVersitDocument::ICalendar20Type); + document.setComponentType(QLatin1String("VCALENDAR")); + if (!tzid.isEmpty()) { + document.addSubDocument(timezoneSpec); + } + QVersitDocument vevent(QVersitDocument::ICalendar20Type); + vevent.setComponentType(QLatin1String("VEVENT")); + QVersitProperty property; + property.setName(QLatin1String("DTSTART")); + property.setValue(datetimeString); + if (!tzid.isEmpty()) { + property.insertParameter(QLatin1String("TZID"), tzid); + } + vevent.addProperty(property); + document.addSubDocument(vevent); + + QVersitOrganizerImporter importer; + QVERIFY(importer.importDocument(document)); + QVERIFY(importer.errors().isEmpty()); + QList<QOrganizerItem> items = importer.items(); + QCOMPARE(items.size(), 1); + + QOrganizerEvent event = static_cast<QOrganizerEvent>(items.first()); + QCOMPARE(event.type(), QString(QLatin1String(QOrganizerItemType::TypeEvent))); + QDateTime actualDatetime = event.startDateTime(); + QCOMPARE(actualDatetime, expected); + QCOMPARE(actualDatetime.timeSpec(), expected.timeSpec()); +} + +void tst_QVersitOrganizerImporter::testTimeZones_data() +{ + QTest::addColumn<QString>("tzid"); // set this to empty if you don't want to associate a tzid + QTest::addColumn<QVersitDocument>("timezoneSpec"); + QTest::addColumn<QString>("datetimeString"); + QTest::addColumn<QDateTime>("expected"); + + QVersitDocument vtimezone(QVersitDocument::ICalendar20Type); + vtimezone.setComponentType(QLatin1String("VTIMEZONE")); + QTest::newRow("utc") << QString() << QVersitDocument(QVersitDocument::ICalendar20Type) + << QString::fromAscii("20100102T030405Z") + << QDateTime(QDate(2010, 1, 2), QTime(3, 4, 5), Qt::UTC); + + QTest::newRow("floating") << QString() << QVersitDocument(QVersitDocument::ICalendar20Type) + << QString::fromAscii("20100102T030405") + << QDateTime(QDate(2010, 1, 2), QTime(3, 4, 5), Qt::LocalTime); + + { + QVersitDocument vtimezone(QVersitDocument::ICalendar20Type); + vtimezone.setComponentType(QLatin1String("VTIMEZONE")); + + QVersitProperty property; + property.setName(QLatin1String("TZID")); + property.setValue(QLatin1String("Asia/Singapore")); + vtimezone.addProperty(property); + property.setName(QLatin1String("X-LIC-LOCATION")); + property.setValue(QLatin1String("Asia/Singapore")); + vtimezone.addProperty(property); + + QVersitDocument standard(QVersitDocument::ICalendar20Type); + standard.setComponentType(QLatin1String("STANDARD")); + property.setName(QLatin1String("TZOFFSETFROM")); + property.setValue(QLatin1String("+0800")); + standard.addProperty(property); + property.setName(QLatin1String("TZOFFSETTO")); + property.setValue(QLatin1String("+0800")); + standard.addProperty(property); + property.setName(QLatin1String("TZNAME")); + property.setValue(QLatin1String("EST")); + standard.addProperty(property); + property.setName(QLatin1String("DTSTART")); + property.setValue(QLatin1String("19700101T000000")); + standard.addProperty(property); + vtimezone.addSubDocument(standard); + + QTest::newRow("no dst") << QString::fromAscii("Asia/Singapore") + << vtimezone << QString::fromAscii("20100102T100405") + << QDateTime(QDate(2010, 1, 2), QTime(2, 4, 5), Qt::UTC); + } + + { + QVersitDocument vtimezone(QVersitDocument::ICalendar20Type); + vtimezone.setComponentType(QLatin1String("VTIMEZONE")); + + QVersitProperty property; + property.setName(QLatin1String("TZID")); + property.setValue(QLatin1String("Australia/Sydney")); + vtimezone.addProperty(property); + property.setName(QLatin1String("X-LIC-LOCATION")); + property.setValue(QLatin1String("Australia/Sydney")); + vtimezone.addProperty(property); + + QVersitDocument standard(QVersitDocument::ICalendar20Type); + standard.setComponentType(QLatin1String("STANDARD")); + property.setName(QLatin1String("TZOFFSETFROM")); + property.setValue(QLatin1String("+1100")); + standard.addProperty(property); + property.setName(QLatin1String("TZOFFSETTO")); + property.setValue(QLatin1String("+1000")); + standard.addProperty(property); + property.setName(QLatin1String("TZNAME")); + property.setValue(QLatin1String("EST")); + standard.addProperty(property); + property.setName(QLatin1String("DTSTART")); + property.setValue(QLatin1String("19700405T030000")); + standard.addProperty(property); + property.setName(QLatin1String("RRULE")); + property.setValue(QLatin1String("FREQ=YEARLY;BYMONTH=4;BYDAY=1SU")); + standard.addProperty(property); + vtimezone.addSubDocument(standard); + + QVersitDocument daylight(QVersitDocument::ICalendar20Type); + daylight.setComponentType(QLatin1String("DAYLIGHT")); + property.setName(QLatin1String("TZOFFSETFROM")); + property.setValue(QLatin1String("+1000")); + daylight.addProperty(property); + property.setName(QLatin1String("TZOFFSETTO")); + property.setValue(QLatin1String("+1100")); + daylight.addProperty(property); + property.setName(QLatin1String("TZNAME")); + property.setValue(QLatin1String("EST")); + daylight.addProperty(property); + property.setName(QLatin1String("DTSTART")); + property.setValue(QLatin1String("19701004T020000")); + daylight.addProperty(property); + property.setName(QLatin1String("RRULE")); + property.setValue(QLatin1String("FREQ=YEARLY;BYMONTH=10;BYDAY=1SU")); + daylight.addProperty(property); + vtimezone.addSubDocument(daylight); + + QTest::newRow("dst area in standard time") << QString::fromAscii("Australia/Sydney") + << vtimezone << QString::fromAscii("20100502T100405") + << QDateTime(QDate(2010, 5, 2), QTime(0, 4, 5), Qt::UTC); + + QTest::newRow("dst") << QString::fromAscii("Australia/Sydney") + << vtimezone << QString::fromAscii("20100102T100405") + << QDateTime(QDate(2010, 1, 1), QTime(23, 4, 5), Qt::UTC); + } + + { + QVersitDocument vtimezone(QVersitDocument::ICalendar20Type); + vtimezone.setComponentType(QLatin1String("VTIMEZONE")); + + QVersitProperty property; + property.setName(QLatin1String("TZID")); + property.setValue(QLatin1String("test")); + vtimezone.addProperty(property); + } +} + QTEST_MAIN(tst_QVersitOrganizerImporter) diff --git a/tests/auto/qversitorganizerimporter/tst_qversitorganizerimporter.h b/tests/auto/qversitorganizerimporter/tst_qversitorganizerimporter.h index f7096c9d8e..e3987471a9 100644 --- a/tests/auto/qversitorganizerimporter/tst_qversitorganizerimporter.h +++ b/tests/auto/qversitorganizerimporter/tst_qversitorganizerimporter.h @@ -64,6 +64,9 @@ private slots: void testImportTodoProperties(); void testImportTodoProperties_data(); + + void testTimeZones(); + void testTimeZones_data(); }; #endif |