From 9c2cbee2b1adedf53fa86a1c7a86f561747ab962 Mon Sep 17 00:00:00 2001 From: John Layt Date: Wed, 30 Jan 2013 16:04:23 +0000 Subject: QTimeZone - Add ICU support Add ICU backend for QTimeZone Change-Id: I92e53a848477e366591102064b093e936f0b49d2 Reviewed-by: Thiago Macieira --- src/corelib/tools/qtimezone.cpp | 8 + src/corelib/tools/qtimezoneprivate_icu.cpp | 504 +++++++++++++++++++++ src/corelib/tools/qtimezoneprivate_p.h | 47 ++ src/corelib/tools/tools.pri | 3 +- .../auto/corelib/tools/qtimezone/tst_qtimezone.cpp | 141 ++++++ 5 files changed, 702 insertions(+), 1 deletion(-) create mode 100644 src/corelib/tools/qtimezoneprivate_icu.cpp diff --git a/src/corelib/tools/qtimezone.cpp b/src/corelib/tools/qtimezone.cpp index a739d9c69f..53fd3fb0bf 100644 --- a/src/corelib/tools/qtimezone.cpp +++ b/src/corelib/tools/qtimezone.cpp @@ -52,13 +52,21 @@ QT_BEGIN_NAMESPACE // Create default time zone using appropriate backend static QTimeZonePrivate *newBackendTimeZone() { +#if defined QT_USE_ICU + return new QIcuTimeZonePrivate(); +#else return new QUtcTimeZonePrivate(); +#endif // QT_USE_ICU } // Create named time zone using appropriate backend static QTimeZonePrivate *newBackendTimeZone(const QByteArray &olsenId) { +#if defined QT_USE_ICU + return new QIcuTimeZonePrivate(olsenId); +#else return new QUtcTimeZonePrivate(olsenId); +#endif // QT_USE_ICU } class QTimeZoneSingleton diff --git a/src/corelib/tools/qtimezoneprivate_icu.cpp b/src/corelib/tools/qtimezoneprivate_icu.cpp new file mode 100644 index 0000000000..f43b9276a1 --- /dev/null +++ b/src/corelib/tools/qtimezoneprivate_icu.cpp @@ -0,0 +1,504 @@ +/**************************************************************************** +** +** Copyright (C) 2013 John Layt +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtimezone.h" +#include "qtimezoneprivate_p.h" + +#include + +#include + +QT_BEGIN_NAMESPACE + +/* + Private + + ICU implementation +*/ + +// ICU utilities + +// Convert TimeType and NameType into ICU UCalendarDisplayNameType +static UCalendarDisplayNameType ucalDisplayNameType(QTimeZone::TimeType timeType, QTimeZone::NameType nameType) +{ + // TODO ICU C UCalendarDisplayNameType does not support full set of C++ TimeZone::EDisplayType + switch (nameType) { + case QTimeZone::ShortName : + case QTimeZone::OffsetName : + if (timeType == QTimeZone::DaylightTime) + return UCAL_SHORT_DST; + // Includes GenericTime + return UCAL_SHORT_STANDARD; + case QTimeZone::DefaultName : + case QTimeZone::LongName : + if (timeType == QTimeZone::DaylightTime) + return UCAL_DST; + // Includes GenericTime + return UCAL_STANDARD; + } + return UCAL_STANDARD; +} + +// Qt wrapper around ucal_getDefaultTimeZone() +static QByteArray ucalDefaultTimeZoneId() +{ + int32_t size = 30; + QString result(size, Qt::Uninitialized); + UErrorCode status = U_ZERO_ERROR; + + // size = ucal_getDefaultTimeZone(result, resultLength, status) + size = ucal_getDefaultTimeZone(reinterpret_cast(result.data()), size, &status); + + // If overflow, then resize and retry + if (status == U_BUFFER_OVERFLOW_ERROR) { + result.resize(size); + status = U_ZERO_ERROR; + size = ucal_getDefaultTimeZone(reinterpret_cast(result.data()), size, &status); + } + + // If successful on first or second go, resize and return + if (U_SUCCESS(status)) { + result.resize(size); + return result.toUtf8(); + } + + return QByteArray(); +} + +// Qt wrapper around ucal_getTimeZoneDisplayName() +static QString ucalTimeZoneDisplayName(UCalendar *ucal, QTimeZone::TimeType timeType, + QTimeZone::NameType nameType, + const QString &localeCode) +{ + int32_t size = 50; + QString result(size, Qt::Uninitialized); + UErrorCode status = U_ZERO_ERROR; + + // size = ucal_getTimeZoneDisplayName(cal, type, locale, result, resultLength, status) + size = ucal_getTimeZoneDisplayName(ucal, + ucalDisplayNameType(timeType, nameType), + localeCode.toUtf8(), + reinterpret_cast(result.data()), + size, + &status); + + // If overflow, then resize and retry + if (status == U_BUFFER_OVERFLOW_ERROR) { + result.resize(size); + status = U_ZERO_ERROR; + size = ucal_getTimeZoneDisplayName(ucal, + ucalDisplayNameType(timeType, nameType), + localeCode.toUtf8(), + reinterpret_cast(result.data()), + size, + &status); + } + + // If successful on first or second go, resize and return + if (U_SUCCESS(status)) { + result.resize(size); + return result; + } + + return QString(); +} + +// Qt wrapper around ucal_get() for offsets +static bool ucalOffsetsAtTime(UCalendar *m_ucal, qint64 atMSecsSinceEpoch, + int *utcOffset, int *dstOffset) +{ + *utcOffset = 0; + *dstOffset = 0; + + // Clone the ucal so we don't change the shared object + UErrorCode status = U_ZERO_ERROR; + UCalendar *ucal = ucal_clone(m_ucal, &status); + if (!U_SUCCESS(status)) + return false; + + // Set the date to find the offset for + status = U_ZERO_ERROR; + ucal_setMillis(ucal, atMSecsSinceEpoch, &status); + + int32_t utc = 0; + if (U_SUCCESS(status)) { + status = U_ZERO_ERROR; + // Returns msecs + utc = ucal_get(ucal, UCAL_ZONE_OFFSET, &status) / 1000; + } + + int32_t dst = 0; + if (U_SUCCESS(status)) { + status = U_ZERO_ERROR; + // Returns msecs + dst = ucal_get(ucal, UCAL_DST_OFFSET, &status) / 1000; + } + + ucal_close(ucal); + if (U_SUCCESS(status)) { + *utcOffset = utc; + *dstOffset = dst; + return true; + } + return false; +} + +// ICU Draft api in v50, should be stable in ICU v51. Available in C++ api from ICU v3.8 +#if U_ICU_VERSION_MAJOR_NUM == 50 +// Qt wrapper around qt_ucal_getTimeZoneTransitionDate & ucal_get +static QTimeZonePrivate::Data ucalTimeZoneTransition(UCalendar *m_ucal, + UTimeZoneTransitionType type, + qint64 atMSecsSinceEpoch) +{ + QTimeZonePrivate::Data tran = QTimeZonePrivate::invalidData(); + + // Clone the ucal so we don't change the shared object + UErrorCode status = U_ZERO_ERROR; + UCalendar *ucal = ucal_clone(m_ucal, &status); + if (!U_SUCCESS(status)) + return tran; + + // Set the date to find the transition for + status = U_ZERO_ERROR; + ucal_setMillis(ucal, atMSecsSinceEpoch, &status); + + // Find the transition time + UDate tranMSecs = 0; + status = U_ZERO_ERROR; + bool ok = ucal_getTimeZoneTransitionDate(ucal, type, &tranMSecs, &status); + + // Set the transition time to find the offsets for + if (U_SUCCESS(status) && ok) { + status = U_ZERO_ERROR; + ucal_setMillis(ucal, tranMSecs, &status); + } + + int32_t utc = 0; + if (U_SUCCESS(status) && ok) { + status = U_ZERO_ERROR; + utc = ucal_get(ucal, UCAL_ZONE_OFFSET, &status) / 1000; + } + + int32_t dst = 0; + if (U_SUCCESS(status) && ok) { + status = U_ZERO_ERROR; + dst = ucal_get(ucal, UCAL_DST_OFFSET, &status) / 1000; + } + + ucal_close(ucal); + if (!U_SUCCESS(status) || !ok) + return tran; + tran.atMSecsSinceEpoch = tranMSecs; + tran.offsetFromUtc = utc + dst; + tran.standardTimeOffset = utc; + tran.daylightTimeOffset = dst; + // TODO No ICU API, use short name instead + if (dst == 0) + tran.abbreviation = ucalTimeZoneDisplayName(m_ucal, QTimeZone::StandardTime, + QTimeZone::ShortName, QLocale().name()); + else + tran.abbreviation = ucalTimeZoneDisplayName(m_ucal, QTimeZone::DaylightTime, + QTimeZone::ShortName, QLocale().name()); + return tran; +} +#endif // U_ICU_VERSION_SHORT + +// Convert a uenum to a QList +static QSet uenumToIdSet(UEnumeration *uenum) +{ + QSet set; + int32_t size = 0; + UErrorCode status = U_ZERO_ERROR; + // TODO Perhaps use uenum_unext instead? + QByteArray result = uenum_next(uenum, &size, &status); + while (U_SUCCESS(status) && !result.isEmpty()) { + set << result; + status = U_ZERO_ERROR; + result = uenum_next(uenum, &size, &status); + } + return set; +} + +// Qt wrapper around ucal_getDSTSavings() +static int ucalDaylightOffset(const QByteArray &id) +{ + UErrorCode status = U_ZERO_ERROR; + const int32_t dstMSecs = ucal_getDSTSavings(reinterpret_cast(id.data()), &status); + if (U_SUCCESS(status)) + return (dstMSecs / 1000); + else + return 0; +} + +// Create the system default time zone +QIcuTimeZonePrivate::QIcuTimeZonePrivate() + : m_ucal(0) +{ + // TODO No ICU C API to obtain sysem tz, assume default hasn't been changed + init(ucalDefaultTimeZoneId()); +} + +// Create a named time zone +QIcuTimeZonePrivate::QIcuTimeZonePrivate(const QByteArray &olsenId) + : m_ucal(0) +{ + // Need to check validity here as ICu will create a GMT tz if name is invalid + if (availableTimeZoneIds().contains(olsenId)) + init(olsenId); +} + +QIcuTimeZonePrivate::QIcuTimeZonePrivate(const QIcuTimeZonePrivate &other) + : QTimeZonePrivate(other), m_ucal(0) +{ + // Clone the ucal so we don't close the shared object + UErrorCode status = U_ZERO_ERROR; + m_ucal = ucal_clone(other.m_ucal, &status); + if (!U_SUCCESS(status)) { + m_id.clear(); + m_ucal = 0; + } +} + +QIcuTimeZonePrivate::~QIcuTimeZonePrivate() +{ + ucal_close(m_ucal); +} + +QTimeZonePrivate *QIcuTimeZonePrivate::clone() +{ + return new QIcuTimeZonePrivate(*this); +} + +void QIcuTimeZonePrivate::init(const QByteArray &olsenId) +{ + m_id = olsenId; + + const QString id = QString::fromUtf8(m_id); + UErrorCode status = U_ZERO_ERROR; + //TODO Use UCAL_GREGORIAN for now to match QLocale, change to UCAL_DEFAULT once full ICU support + m_ucal = ucal_open(reinterpret_cast(id.data()), id.size(), + QLocale().name().toUtf8(), UCAL_GREGORIAN, &status); + + if (!U_SUCCESS(status)) { + m_id.clear(); + m_ucal = 0; + } +} + +QString QIcuTimeZonePrivate::displayName(QTimeZone::TimeType timeType, + QTimeZone::NameType nameType, + const QLocale &locale) const +{ + // Return standard offset format name as ICU C api doesn't support it yet + if (nameType == QTimeZone::OffsetName) { + const Data nowData = data(QDateTime::currentDateTimeUtc().toMSecsSinceEpoch()); + // We can't use transitions reliably to find out right dst offset + // Instead use dst offset api to try get it if needed + if (timeType == QTimeZone::DaylightTime) + return isoOffsetFormat(nowData.standardTimeOffset + ucalDaylightOffset(m_id)); + else + return isoOffsetFormat(nowData.standardTimeOffset); + } + return ucalTimeZoneDisplayName(m_ucal, timeType, nameType, locale.name()); +} + +QString QIcuTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch) const +{ + // TODO No ICU API, use short name instead + if (isDaylightTime(atMSecsSinceEpoch)) + return displayName(QTimeZone::DaylightTime, QTimeZone::ShortName, QString()); + else + return displayName(QTimeZone::StandardTime, QTimeZone::ShortName, QString()); +} + +int QIcuTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch) const +{ + int stdOffset = 0; + int dstOffset = 0; + ucalOffsetsAtTime(m_ucal, atMSecsSinceEpoch, &stdOffset, & dstOffset); + return stdOffset + dstOffset; +} + +int QIcuTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch) const +{ + int stdOffset = 0; + int dstOffset = 0; + ucalOffsetsAtTime(m_ucal, atMSecsSinceEpoch, &stdOffset, & dstOffset); + return stdOffset; +} + +int QIcuTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch) const +{ + int stdOffset = 0; + int dstOffset = 0; + ucalOffsetsAtTime(m_ucal, atMSecsSinceEpoch, &stdOffset, & dstOffset); + return dstOffset; +} + +bool QIcuTimeZonePrivate::hasDaylightTime() const +{ + // TODO No direct ICU C api, work-around below not reliable? Find a better way? + return (ucalDaylightOffset(m_id) != 0); +} + +bool QIcuTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch) const +{ + // Clone the ucal so we don't change the shared object + UErrorCode status = U_ZERO_ERROR; + UCalendar *ucal = ucal_clone(m_ucal, &status); + if (!U_SUCCESS(status)) + return false; + + // Set the date to find the offset for + status = U_ZERO_ERROR; + ucal_setMillis(ucal, atMSecsSinceEpoch, &status); + + bool result = false; + if (U_SUCCESS(status)) { + status = U_ZERO_ERROR; + result = ucal_inDaylightTime(ucal, &status); + } + + ucal_close(ucal); + return result; +} + +QTimeZonePrivate::Data QIcuTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const +{ + // Available in ICU C++ api, and draft C api in v50 + // TODO When v51 released see if api is stable + QTimeZonePrivate::Data data = invalidData(); +#if U_ICU_VERSION_MAJOR_NUM == 50 + data = ucalTimeZoneTransition(m_ucal, UCAL_TZ_TRANSITION_PREVIOUS_INCLUSIVE, + forMSecsSinceEpoch); +#else + ucalOffsetsAtTime(m_ucal, forMSecsSinceEpoch, &data.standardTimeOffset, + &data.daylightTimeOffset); + data.offsetFromUtc = data.standardTimeOffset + data.daylightTimeOffset; + data.abbreviation = abbreviation(forMSecsSinceEpoch); +#endif // U_ICU_VERSION_MAJOR_NUM == 50 + data.atMSecsSinceEpoch = forMSecsSinceEpoch; + return data; +} + +bool QIcuTimeZonePrivate::hasTransitions() const +{ + // Available in ICU C++ api, and draft C api in v50 + // TODO When v51 released see if api is stable +#if U_ICU_VERSION_MAJOR_NUM == 50 + return true; +#else + return false; +#endif // U_ICU_VERSION_MAJOR_NUM == 50 +} + +QTimeZonePrivate::Data QIcuTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch) const +{ + // Available in ICU C++ api, and draft C api in v50 + // TODO When v51 released see if api is stable +#if U_ICU_VERSION_MAJOR_NUM == 50 + return ucalTimeZoneTransition(m_ucal, UCAL_TZ_TRANSITION_NEXT, afterMSecsSinceEpoch); +#else + Q_UNUSED(afterMSecsSinceEpoch) + return invalidData(); +#endif // U_ICU_VERSION_MAJOR_NUM == 50 +} + +QTimeZonePrivate::Data QIcuTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const +{ + // Available in ICU C++ api, and draft C api in v50 + // TODO When v51 released see if api is stable +#if U_ICU_VERSION_MAJOR_NUM == 50 + return ucalTimeZoneTransition(m_ucal, UCAL_TZ_TRANSITION_PREVIOUS, beforeMSecsSinceEpoch); +#else + Q_UNUSED(beforeMSecsSinceEpoch) + return invalidData(); +#endif // U_ICU_VERSION_MAJOR_NUM == 50 +} + +QByteArray QIcuTimeZonePrivate::systemTimeZoneId() const +{ + // No ICU C API to obtain sysem tz + // TODO Assume default hasn't been changed and is the latests system + return ucalDefaultTimeZoneId(); +} + +QSet QIcuTimeZonePrivate::availableTimeZoneIds() const +{ + UErrorCode status = U_ZERO_ERROR; + UEnumeration *uenum = ucal_openTimeZones(&status); + QSet set; + if (U_SUCCESS(status)) + set = uenumToIdSet(uenum); + uenum_close(uenum); + return set; +} + +QSet QIcuTimeZonePrivate::availableTimeZoneIds(QLocale::Country country) const +{ + QByteArray regionCode = QLocalePrivate::countryToCode(country).toUtf8(); + UErrorCode status = U_ZERO_ERROR; + UEnumeration *uenum = ucal_openCountryTimeZones(regionCode, &status); + QSet set; + if (U_SUCCESS(status)) + set = uenumToIdSet(uenum); + uenum_close(uenum); + return set; +} + +QSet QIcuTimeZonePrivate::availableTimeZoneIds(int offsetFromUtc) const +{ +// TODO Available directly in C++ api but not C api, from 4.8 onwards new filter method works +#if U_ICU_VERSION_MAJOR_NUM >= 49 || (U_ICU_VERSION_MAJOR_NUM == 4 && U_ICU_VERSION_MINOR_NUM == 8) + UErrorCode status = U_ZERO_ERROR; + UEnumeration *uenum = ucal_openTimeZoneIDEnumeration(UCAL_ZONE_TYPE_ANY, 0, + &offsetFromUtc, &status); + QSet set; + if (U_SUCCESS(status)) + set = uenumToIdSet(uenum); + uenum_close(uenum); + return set; +#else + return QTimeZonePrivate::availableTimeZoneIds(offsetFromUtc); +#endif +} + +QT_END_NAMESPACE diff --git a/src/corelib/tools/qtimezoneprivate_p.h b/src/corelib/tools/qtimezoneprivate_p.h index 94fb49026f..1ecb60a0b2 100644 --- a/src/corelib/tools/qtimezoneprivate_p.h +++ b/src/corelib/tools/qtimezoneprivate_p.h @@ -57,6 +57,10 @@ #include "qtimezone.h" #include "qlocale_p.h" +#ifdef QT_USE_ICU +#include +#endif // QT_USE_ICU + QT_BEGIN_NAMESPACE class Q_CORE_EXPORT QTimeZonePrivate : public QSharedData @@ -193,6 +197,49 @@ private: QString m_comment; }; +#ifdef QT_USE_ICU +class Q_AUTOTEST_EXPORT QIcuTimeZonePrivate Q_DECL_FINAL : public QTimeZonePrivate +{ +public: + // Create default time zone + QIcuTimeZonePrivate(); + // Create named time zone + QIcuTimeZonePrivate(const QByteArray &olsenId); + QIcuTimeZonePrivate(const QIcuTimeZonePrivate &other); + ~QIcuTimeZonePrivate(); + + QTimeZonePrivate *clone(); + + QString displayName(QTimeZone::TimeType timeType, QTimeZone::NameType nameType, + const QLocale &locale) const Q_DECL_OVERRIDE; + QString abbreviation(qint64 atMSecsSinceEpoch) const Q_DECL_OVERRIDE; + + int offsetFromUtc(qint64 atMSecsSinceEpoch) const Q_DECL_OVERRIDE; + int standardTimeOffset(qint64 atMSecsSinceEpoch) const Q_DECL_OVERRIDE; + int daylightTimeOffset(qint64 atMSecsSinceEpoch) const Q_DECL_OVERRIDE; + + bool hasDaylightTime() const Q_DECL_OVERRIDE; + bool isDaylightTime(qint64 atMSecsSinceEpoch) const Q_DECL_OVERRIDE; + + Data data(qint64 forMSecsSinceEpoch) const Q_DECL_OVERRIDE; + + bool hasTransitions() const Q_DECL_OVERRIDE; + Data nextTransition(qint64 afterMSecsSinceEpoch) const Q_DECL_OVERRIDE; + Data previousTransition(qint64 beforeMSecsSinceEpoch) const Q_DECL_OVERRIDE; + + QByteArray systemTimeZoneId() const Q_DECL_OVERRIDE; + + QSet availableTimeZoneIds() const Q_DECL_OVERRIDE; + QSet availableTimeZoneIds(QLocale::Country country) const Q_DECL_OVERRIDE; + QSet availableTimeZoneIds(int offsetFromUtc) const Q_DECL_OVERRIDE; + +private: + void init(const QByteArray &olsenId); + + UCalendar *m_ucal; +}; +#endif // QT_USE_ICU + QT_END_NAMESPACE #endif // QTIMEZONEPRIVATE_P_H diff --git a/src/corelib/tools/tools.pri b/src/corelib/tools/tools.pri index 5633b638d3..56de36e630 100644 --- a/src/corelib/tools/tools.pri +++ b/src/corelib/tools/tools.pri @@ -139,7 +139,8 @@ contains(QT_CONFIG, zlib) { contains(QT_CONFIG,icu) { SOURCES += tools/qlocale_icu.cpp \ - tools/qcollator_icu.cpp + tools/qcollator_icu.cpp \ + tools/qtimezoneprivate_icu.cpp DEFINES += QT_USE_ICU win32 { CONFIG(static, static|shared) { diff --git a/tests/auto/corelib/tools/qtimezone/tst_qtimezone.cpp b/tests/auto/corelib/tools/qtimezone/tst_qtimezone.cpp index 4040005501..c959c0c8e6 100644 --- a/tests/auto/corelib/tools/qtimezone/tst_qtimezone.cpp +++ b/tests/auto/corelib/tools/qtimezone/tst_qtimezone.cpp @@ -60,9 +60,13 @@ private slots: void windowsId(); // Backend tests void utcTest(); + void icuTest(); private: void printTimeZone(const QTimeZone tz); +#ifdef QT_BUILD_INTERNAL + void testCetPrivate(const QTimeZonePrivate &tzp); +#endif // QT_BUILD_INTERNAL bool debug; }; @@ -496,5 +500,142 @@ void tst_QTimeZone::utcTest() #endif // QT_BUILD_INTERNAL } +void tst_QTimeZone::icuTest() +{ +#if defined(QT_BUILD_INTERNAL) && defined(QT_USE_ICU) + // Known datetimes + qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0, 0), Qt::UTC).toMSecsSinceEpoch(); + qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0, 0), Qt::UTC).toMSecsSinceEpoch(); + + // Test default constructor + QIcuTimeZonePrivate tzpd; + QVERIFY(tzpd.isValid()); + + // Test invalid constructor + QIcuTimeZonePrivate tzpi("Gondwana/Erewhon"); + QCOMPARE(tzpi.isValid(), false); + + // Test named constructor + QIcuTimeZonePrivate tzp("Europe/Berlin"); + QVERIFY(tzp.isValid()); + + // Only test names in debug mode, names used can vary by ICU version installed + if (debug) { + // Test display names by type + QLocale enUS("en_US"); + QCOMPARE(tzp.displayName(QTimeZone::StandardTime, QTimeZone::LongName, enUS), + QString("Central European Standard Time")); + QCOMPARE(tzp.displayName(QTimeZone::StandardTime, QTimeZone::ShortName, enUS), + QString("GMT+01:00")); + QCOMPARE(tzp.displayName(QTimeZone::StandardTime, QTimeZone::OffsetName, enUS), + QString("UTC+01:00")); + QCOMPARE(tzp.displayName(QTimeZone::DaylightTime, QTimeZone::LongName, enUS), + QString("Central European Summer Time")); + QCOMPARE(tzp.displayName(QTimeZone::DaylightTime, QTimeZone::ShortName, enUS), + QString("GMT+02:00")); + QCOMPARE(tzp.displayName(QTimeZone::DaylightTime, QTimeZone::OffsetName, enUS), + QString("UTC+02:00")); + // ICU C api does not support Generic Time yet, C++ api does + QCOMPARE(tzp.displayName(QTimeZone::GenericTime, QTimeZone::LongName, enUS), + QString("Central European Standard Time")); + QCOMPARE(tzp.displayName(QTimeZone::GenericTime, QTimeZone::ShortName, enUS), + QString("GMT+01:00")); + QCOMPARE(tzp.displayName(QTimeZone::GenericTime, QTimeZone::OffsetName, enUS), + QString("UTC+01:00")); + + // Test Abbreviations + QCOMPARE(tzp.abbreviation(std), QString("CET")); + QCOMPARE(tzp.abbreviation(dst), QString("CEST")); + } + + testCetPrivate(tzp); +#endif // QT_USE_ICU +} + +#ifdef QT_BUILD_INTERNAL +// Test each private produces the same basic results for CET +void tst_QTimeZone::testCetPrivate(const QTimeZonePrivate &tzp) +{ + // Known datetimes + qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0, 0), Qt::UTC).toMSecsSinceEpoch(); + qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0, 0), Qt::UTC).toMSecsSinceEpoch(); + qint64 prev = QDateTime(QDate(2011, 1, 1), QTime(0, 0, 0), Qt::UTC).toMSecsSinceEpoch(); + + QCOMPARE(tzp.offsetFromUtc(std), 3600); + QCOMPARE(tzp.offsetFromUtc(dst), 7200); + + QCOMPARE(tzp.standardTimeOffset(std), 3600); + QCOMPARE(tzp.standardTimeOffset(dst), 3600); + + QCOMPARE(tzp.daylightTimeOffset(std), 0); + QCOMPARE(tzp.daylightTimeOffset(dst), 3600); + + QCOMPARE(tzp.hasDaylightTime(), true); + QCOMPARE(tzp.isDaylightTime(std), false); + QCOMPARE(tzp.isDaylightTime(dst), true); + + QTimeZonePrivate::Data dat = tzp.data(std); + QCOMPARE(dat.atMSecsSinceEpoch, std); + QCOMPARE(dat.offsetFromUtc, 3600); + QCOMPARE(dat.standardTimeOffset, 3600); + QCOMPARE(dat.daylightTimeOffset, 0); + QCOMPARE(dat.abbreviation, tzp.abbreviation(std)); + + dat = tzp.data(dst); + QCOMPARE(dat.atMSecsSinceEpoch, dst); + QCOMPARE(dat.offsetFromUtc, 7200); + QCOMPARE(dat.standardTimeOffset, 3600); + QCOMPARE(dat.daylightTimeOffset, 3600); + QCOMPARE(dat.abbreviation, tzp.abbreviation(dst)); + + // Only test transitions if host system supports them + if (tzp.hasTransitions()) { + QTimeZonePrivate::Data tran = tzp.nextTransition(std); + QCOMPARE(tran.atMSecsSinceEpoch, (qint64)1332637200000); + QCOMPARE(tran.offsetFromUtc, 7200); + QCOMPARE(tran.standardTimeOffset, 3600); + QCOMPARE(tran.daylightTimeOffset, 3600); + + tran = tzp.nextTransition(dst); + QCOMPARE(tran.atMSecsSinceEpoch, (qint64)1351386000000); + QCOMPARE(tran.offsetFromUtc, 3600); + QCOMPARE(tran.standardTimeOffset, 3600); + QCOMPARE(tran.daylightTimeOffset, 0); + + tran = tzp.previousTransition(std); + QCOMPARE(tran.atMSecsSinceEpoch, (qint64)1319936400000); + QCOMPARE(tran.offsetFromUtc, 3600); + QCOMPARE(tran.standardTimeOffset, 3600); + QCOMPARE(tran.daylightTimeOffset, 0); + + tran = tzp.previousTransition(dst); + QCOMPARE(tran.atMSecsSinceEpoch, (qint64)1332637200000); + QCOMPARE(tran.offsetFromUtc, 7200); + QCOMPARE(tran.standardTimeOffset, 3600); + QCOMPARE(tran.daylightTimeOffset, 3600); + + QTimeZonePrivate::DataList expected; + tran.atMSecsSinceEpoch = (qint64)1301752800000; + tran.offsetFromUtc = 7200; + tran.standardTimeOffset = 3600; + tran.daylightTimeOffset = 3600; + expected << tran; + tran.atMSecsSinceEpoch = (qint64)1316872800000; + tran.offsetFromUtc = 3600; + tran.standardTimeOffset = 3600; + tran.daylightTimeOffset = 0; + expected << tran; + QTimeZonePrivate::DataList result = tzp.transitions(prev, std); + QCOMPARE(result.count(), expected.count()); + for (int i = 0; i > expected.count(); ++i) { + QCOMPARE(result.at(i).atMSecsSinceEpoch, expected.at(i).atMSecsSinceEpoch); + QCOMPARE(result.at(i).offsetFromUtc, expected.at(i).offsetFromUtc); + QCOMPARE(result.at(i).standardTimeOffset, expected.at(i).standardTimeOffset); + QCOMPARE(result.at(i).daylightTimeOffset, expected.at(i).daylightTimeOffset); + } + } +} +#endif // QT_BUILD_INTERNAL + QTEST_APPLESS_MAIN(tst_QTimeZone) #include "tst_qtimezone.moc" -- cgit v1.2.3