diff options
Diffstat (limited to 'tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp')
-rw-r--r-- | tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp | 928 |
1 files changed, 607 insertions, 321 deletions
diff --git a/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp b/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp index 479c2771d7..d2e8f63f24 100644 --- a/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp +++ b/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp @@ -1,34 +1,11 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** 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 The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QTest> #include <qtimezone.h> #include <private/qtimezoneprivate_p.h> +#include <private/qcomparisontesthelper_p.h> + #include <qlocale.h> #if defined(Q_OS_WIN) @@ -43,15 +20,20 @@ class tst_QTimeZone : public QObject { Q_OBJECT -public: - tst_QTimeZone(); - private Q_SLOTS: // Public class default system tests void createTest(); void nullTest(); - void systemZone(); + void assign(); + void compareCompiles(); + void compare_data(); + void compare(); + void timespec(); + void offset(); void dataStreamTest(); +#if QT_CONFIG(timezone) + void asBackendZone(); + void systemZone(); void isTimeZoneIdAvailable(); void availableTimeZoneIds(); void utcOffsetId_data(); @@ -75,28 +57,28 @@ private Q_SLOTS: void macTest(); void darwinTypes(); void winTest(); + void localeSpecificDisplayName_data(); + void localeSpecificDisplayName(); + void stdCompatibility_data(); + void stdCompatibility(); +#endif // timezone backends private: void printTimeZone(const QTimeZone &tz); -#ifdef QT_BUILD_INTERNAL +#if defined(QT_BUILD_INTERNAL) && QT_CONFIG(timezone) // Generic tests of privates, called by implementation-specific private tests: void testCetPrivate(const QTimeZonePrivate &tzp); void testEpochTranPrivate(const QTimeZonePrivate &tzp); -#endif // QT_BUILD_INTERNAL - const bool debug; -}; - -tst_QTimeZone::tst_QTimeZone() +#endif // QT_BUILD_INTERNAL && timezone backends // Set to true to print debug output, test Display Names and run long stress tests - : debug(false) -{ -} + static constexpr bool debug = false; +}; void tst_QTimeZone::printTimeZone(const QTimeZone &tz) { QDateTime now = QDateTime::currentDateTime(); - QDateTime jan = QDateTime(QDate(2012, 1, 1), QTime(0, 0, 0), Qt::UTC); - QDateTime jun = QDateTime(QDate(2012, 6, 1), QTime(0, 0, 0), Qt::UTC); + QDateTime jan = QDateTime(QDate(2012, 1, 1), QTime(0, 0), QTimeZone::UTC); + QDateTime jun = QDateTime(QDate(2012, 6, 1), QTime(0, 0), QTimeZone::UTC); qDebug() << ""; qDebug() << "Time Zone = " << tz; qDebug() << ""; @@ -180,9 +162,9 @@ void tst_QTimeZone::createTest() QCOMPARE(tz.territory(), QLocale::NewZealand); - QDateTime jan = QDateTime(QDate(2012, 1, 1), QTime(0, 0, 0), Qt::UTC); - QDateTime jun = QDateTime(QDate(2012, 6, 1), QTime(0, 0, 0), Qt::UTC); - QDateTime janPrev = QDateTime(QDate(2011, 1, 1), QTime(0, 0, 0), Qt::UTC); + QDateTime jan = QDateTime(QDate(2012, 1, 1), QTime(0, 0), QTimeZone::UTC); + QDateTime jun = QDateTime(QDate(2012, 6, 1), QTime(0, 0), QTimeZone::UTC); + QDateTime janPrev = QDateTime(QDate(2011, 1, 1), QTime(0, 0), QTimeZone::UTC); QCOMPARE(tz.offsetFromUtc(jan), 13 * 3600); QCOMPARE(tz.offsetFromUtc(jun), 12 * 3600); @@ -202,7 +184,8 @@ void tst_QTimeZone::createTest() QTimeZone::OffsetData tran = tz.nextTransition(jan); // 2012-04-01 03:00 NZDT, +13 -> +12 QCOMPARE(tran.atUtc, - QDateTime(QDate(2012, 4, 1), QTime(3, 0), Qt::OffsetFromUTC, 13 * 3600)); + QDateTime(QDate(2012, 4, 1), QTime(3, 0), + QTimeZone::fromSecondsAheadOfUtc(13 * 3600))); QCOMPARE(tran.offsetFromUtc, 12 * 3600); QCOMPARE(tran.standardTimeOffset, 12 * 3600); QCOMPARE(tran.daylightTimeOffset, 0); @@ -210,7 +193,8 @@ void tst_QTimeZone::createTest() tran = tz.nextTransition(jun); // 2012-09-30 02:00 NZST, +12 -> +13 QCOMPARE(tran.atUtc, - QDateTime(QDate(2012, 9, 30), QTime(2, 0), Qt::OffsetFromUTC, 12 * 3600)); + QDateTime(QDate(2012, 9, 30), QTime(2, 0), + QTimeZone::fromSecondsAheadOfUtc(12 * 3600))); QCOMPARE(tran.offsetFromUtc, 13 * 3600); QCOMPARE(tran.standardTimeOffset, 12 * 3600); QCOMPARE(tran.daylightTimeOffset, 3600); @@ -218,7 +202,8 @@ void tst_QTimeZone::createTest() tran = tz.previousTransition(jan); // 2011-09-25 02:00 NZST, +12 -> +13 QCOMPARE(tran.atUtc, - QDateTime(QDate(2011, 9, 25), QTime(2, 0), Qt::OffsetFromUTC, 12 * 3600)); + QDateTime(QDate(2011, 9, 25), QTime(2, 0), + QTimeZone::fromSecondsAheadOfUtc(12 * 3600))); QCOMPARE(tran.offsetFromUtc, 13 * 3600); QCOMPARE(tran.standardTimeOffset, 12 * 3600); QCOMPARE(tran.daylightTimeOffset, 3600); @@ -226,23 +211,26 @@ void tst_QTimeZone::createTest() tran = tz.previousTransition(jun); // 2012-04-01 03:00 NZDT, +13 -> +12 (again) QCOMPARE(tran.atUtc, - QDateTime(QDate(2012, 4, 1), QTime(3, 0), Qt::OffsetFromUTC, 13 * 3600)); + QDateTime(QDate(2012, 4, 1), QTime(3, 0), + QTimeZone::fromSecondsAheadOfUtc(13 * 3600))); QCOMPARE(tran.offsetFromUtc, 12 * 3600); QCOMPARE(tran.standardTimeOffset, 12 * 3600); QCOMPARE(tran.daylightTimeOffset, 0); QTimeZone::OffsetDataList expected; // Reuse 2012's fall-back data for 2011-04-03: - tran.atUtc = QDateTime(QDate(2011, 4, 3), QTime(3, 0), Qt::OffsetFromUTC, 13 * 3600); + tran.atUtc = QDateTime(QDate(2011, 4, 3), QTime(3, 0), + QTimeZone::fromSecondsAheadOfUtc(13 * 3600)); expected << tran; // 2011's spring-forward: - tran.atUtc = QDateTime(QDate(2011, 9, 25), QTime(2, 0), Qt::OffsetFromUTC, 12 * 3600); + tran.atUtc = QDateTime(QDate(2011, 9, 25), QTime(2, 0), + QTimeZone::fromSecondsAheadOfUtc(12 * 3600)); tran.offsetFromUtc = 13 * 3600; tran.daylightTimeOffset = 3600; expected << tran; QTimeZone::OffsetDataList result = tz.transitions(janPrev, jan); - QCOMPARE(result.count(), expected.count()); - for (int i = 0; i < expected.count(); ++i) { + QCOMPARE(result.size(), expected.size()); + for (int i = 0; i < expected.size(); ++i) { QCOMPARE(result.at(i).atUtc, expected.at(i).atUtc); QCOMPARE(result.at(i).offsetFromUtc, expected.at(i).offsetFromUtc); QCOMPARE(result.at(i).standardTimeOffset, expected.at(i).standardTimeOffset); @@ -278,9 +266,9 @@ void tst_QTimeZone::nullTest() QCOMPARE(nullTz1.territory(), QLocale::AnyTerritory); QCOMPARE(nullTz1.comment(), QString()); - QDateTime jan = QDateTime(QDate(2012, 1, 1), QTime(0, 0, 0), Qt::UTC); - QDateTime jun = QDateTime(QDate(2012, 6, 1), QTime(0, 0, 0), Qt::UTC); - QDateTime janPrev = QDateTime(QDate(2011, 1, 1), QTime(0, 0, 0), Qt::UTC); + QDateTime jan = QDateTime(QDate(2012, 1, 1), QTime(0, 0), QTimeZone::UTC); + QDateTime jun = QDateTime(QDate(2012, 6, 1), QTime(0, 0), QTimeZone::UTC); + QDateTime janPrev = QDateTime(QDate(2011, 1, 1), QTime(0, 0), QTimeZone::UTC); QCOMPARE(nullTz1.abbreviation(jan), QString()); QCOMPARE(nullTz1.displayName(jan), QString()); @@ -300,48 +288,178 @@ void tst_QTimeZone::nullTest() QCOMPARE(nullTz1.isDaylightTime(jun), false); QTimeZone::OffsetData data = nullTz1.offsetData(jan); + constexpr auto invalidOffset = std::numeric_limits<int>::min(); QCOMPARE(data.atUtc, QDateTime()); - QCOMPARE(data.offsetFromUtc, std::numeric_limits<int>::min()); - QCOMPARE(data.standardTimeOffset, std::numeric_limits<int>::min()); - QCOMPARE(data.daylightTimeOffset, std::numeric_limits<int>::min()); + QCOMPARE(data.offsetFromUtc, invalidOffset); + QCOMPARE(data.standardTimeOffset, invalidOffset); + QCOMPARE(data.daylightTimeOffset, invalidOffset); QCOMPARE(nullTz1.hasTransitions(), false); data = nullTz1.nextTransition(jan); QCOMPARE(data.atUtc, QDateTime()); - QCOMPARE(data.offsetFromUtc, std::numeric_limits<int>::min()); - QCOMPARE(data.standardTimeOffset, std::numeric_limits<int>::min()); - QCOMPARE(data.daylightTimeOffset, std::numeric_limits<int>::min()); + QCOMPARE(data.offsetFromUtc, invalidOffset); + QCOMPARE(data.standardTimeOffset, invalidOffset); + QCOMPARE(data.daylightTimeOffset, invalidOffset); data = nullTz1.previousTransition(jan); QCOMPARE(data.atUtc, QDateTime()); - QCOMPARE(data.offsetFromUtc, std::numeric_limits<int>::min()); - QCOMPARE(data.standardTimeOffset, std::numeric_limits<int>::min()); - QCOMPARE(data.daylightTimeOffset, std::numeric_limits<int>::min()); + QCOMPARE(data.offsetFromUtc, invalidOffset); + QCOMPARE(data.standardTimeOffset, invalidOffset); + QCOMPARE(data.daylightTimeOffset, invalidOffset); } -void tst_QTimeZone::systemZone() +void tst_QTimeZone::assign() { - const QTimeZone zone = QTimeZone::systemTimeZone(); - QVERIFY(zone.isValid()); - QCOMPARE(zone.id(), QTimeZone::systemTimeZoneId()); - QCOMPARE(zone, QTimeZone(QTimeZone::systemTimeZoneId())); - // Check it behaves the same as local-time: - const QDate dates[] = { - QDate::fromJulianDay(0), // far in the distant past (LMT) - QDate(1625, 6, 8), // Before time-zones (date of Cassini's birth) - QDate(1901, 12, 13), // Last day before 32-bit time_t's range - QDate(1969, 12, 31), // Last day before the epoch - QDate(1970, 0, 0), // Start of epoch - QDate(2000, 2, 29), // An anomalous leap day - QDate(2038, 1, 20) // First day after 32-bit time_t's range - }; - for (const auto &date : dates) - QCOMPARE(date.startOfDay(Qt::LocalTime), date.startOfDay(zone)); + QTimeZone assignee; + QCOMPARE(assignee.timeSpec(), Qt::TimeZone); + assignee = QTimeZone(); + QCOMPARE(assignee.timeSpec(), Qt::TimeZone); + assignee = QTimeZone::UTC; + QCOMPARE(assignee.timeSpec(), Qt::UTC); + assignee = QTimeZone::LocalTime; + QCOMPARE(assignee.timeSpec(), Qt::LocalTime); + assignee = QTimeZone(); + QCOMPARE(assignee.timeSpec(), Qt::TimeZone); + assignee = QTimeZone::fromSecondsAheadOfUtc(1); + QCOMPARE(assignee.timeSpec(), Qt::OffsetFromUTC); + assignee = QTimeZone::fromSecondsAheadOfUtc(0); + QCOMPARE(assignee.timeSpec(), Qt::UTC); +#if QT_CONFIG(timezone) + { + const QTimeZone cet("Europe/Oslo"); + assignee = cet; + QCOMPARE(assignee.timeSpec(), Qt::TimeZone); + } +#endif +} + +void tst_QTimeZone::compareCompiles() +{ + QTestPrivate::testEqualityOperatorsCompile<QTimeZone>(); +} + +void tst_QTimeZone::compare_data() +{ + QTest::addColumn<QTimeZone>("left"); + QTest::addColumn<QTimeZone>("right"); + QTest::addColumn<bool>("expectedEqual"); + + const QTimeZone local; + const QTimeZone utc(QTimeZone::UTC); + const auto secondEast = QTimeZone::fromSecondsAheadOfUtc(1); + const auto zeroOffset = QTimeZone::fromSecondsAheadOfUtc(0); + const auto durationEast = QTimeZone::fromDurationAheadOfUtc(std::chrono::seconds{1}); + + QTest::newRow("local vs default-constructed") << local << QTimeZone() << true; + QTest::newRow("local vs UTC") << local << utc << false; + QTest::newRow("local vs secondEast") << local << secondEast << false; + QTest::newRow("secondEast vs UTC") << secondEast << utc << false; + QTest::newRow("UTC vs zeroOffset") << utc << zeroOffset << true; + QTest::newRow("secondEast vs durationEast") << secondEast << durationEast << true; +} + +void tst_QTimeZone::compare() +{ + QFETCH(QTimeZone, left); + QFETCH(QTimeZone, right); + QFETCH(bool, expectedEqual); + + QT_TEST_EQUALITY_OPS(left, right, expectedEqual); +} + +void tst_QTimeZone::timespec() +{ + using namespace std::chrono_literals; + QCOMPARE(QTimeZone().timeSpec(), Qt::TimeZone); + QCOMPARE(QTimeZone(QTimeZone::UTC).timeSpec(), Qt::UTC); + QCOMPARE(QTimeZone(QTimeZone::LocalTime).timeSpec(), Qt::LocalTime); + QCOMPARE(QTimeZone::fromSecondsAheadOfUtc(0).timeSpec(), Qt::UTC); + QCOMPARE(QTimeZone::fromDurationAheadOfUtc(0s).timeSpec(), Qt::UTC); + QCOMPARE(QTimeZone::fromDurationAheadOfUtc(0min).timeSpec(), Qt::UTC); + QCOMPARE(QTimeZone::fromDurationAheadOfUtc(0h).timeSpec(), Qt::UTC); + QCOMPARE(QTimeZone::fromSecondsAheadOfUtc(1).timeSpec(), Qt::OffsetFromUTC); + QCOMPARE(QTimeZone::fromSecondsAheadOfUtc(-1).timeSpec(), Qt::OffsetFromUTC); + QCOMPARE(QTimeZone::fromSecondsAheadOfUtc(36000).timeSpec(), Qt::OffsetFromUTC); + QCOMPARE(QTimeZone::fromSecondsAheadOfUtc(-36000).timeSpec(), Qt::OffsetFromUTC); + QCOMPARE(QTimeZone::fromDurationAheadOfUtc(3h - 20min +17s).timeSpec(), Qt::OffsetFromUTC); + { + const QTimeZone zone; + QCOMPARE(zone.timeSpec(), Qt::TimeZone); + } + { + const QTimeZone zone = { QTimeZone::UTC }; + QCOMPARE(zone.timeSpec(), Qt::UTC); + } + { + const QTimeZone zone = { QTimeZone::LocalTime }; + QCOMPARE(zone.timeSpec(), Qt::LocalTime); + } + { + const auto zone = QTimeZone::fromSecondsAheadOfUtc(0); + QCOMPARE(zone.timeSpec(), Qt::UTC); + } + { + const auto zone = QTimeZone::fromDurationAheadOfUtc(0s); + QCOMPARE(zone.timeSpec(), Qt::UTC); + } + { + const auto zone = QTimeZone::fromSecondsAheadOfUtc(1); + QCOMPARE(zone.timeSpec(), Qt::OffsetFromUTC); + } + { + const auto zone = QTimeZone::fromDurationAheadOfUtc(1s); + QCOMPARE(zone.timeSpec(), Qt::OffsetFromUTC); + } +#if QT_CONFIG(timezone) + QCOMPARE(QTimeZone("Europe/Oslo").timeSpec(), Qt::TimeZone); +#endif +} + +void tst_QTimeZone::offset() +{ + QCOMPARE(QTimeZone().fixedSecondsAheadOfUtc(), 0); + QCOMPARE(QTimeZone(QTimeZone::UTC).fixedSecondsAheadOfUtc(), 0); + QCOMPARE(QTimeZone::fromSecondsAheadOfUtc(0).fixedSecondsAheadOfUtc(), 0); + QCOMPARE(QTimeZone::fromDurationAheadOfUtc(std::chrono::seconds{}).fixedSecondsAheadOfUtc(), 0); + QCOMPARE(QTimeZone::fromDurationAheadOfUtc(std::chrono::minutes{}).fixedSecondsAheadOfUtc(), 0); + QCOMPARE(QTimeZone::fromDurationAheadOfUtc(std::chrono::hours{}).fixedSecondsAheadOfUtc(), 0); + QCOMPARE(QTimeZone::fromSecondsAheadOfUtc(1).fixedSecondsAheadOfUtc(), 1); + QCOMPARE(QTimeZone::fromSecondsAheadOfUtc(-1).fixedSecondsAheadOfUtc(), -1); + QCOMPARE(QTimeZone::fromSecondsAheadOfUtc(36000).fixedSecondsAheadOfUtc(), 36000); + QCOMPARE(QTimeZone::fromSecondsAheadOfUtc(-36000).fixedSecondsAheadOfUtc(), -36000); + { + const QTimeZone zone; + QCOMPARE(zone.fixedSecondsAheadOfUtc(), 0); + } + { + const QTimeZone zone = { QTimeZone::UTC }; + QCOMPARE(zone.fixedSecondsAheadOfUtc(), 0); + } + { + const auto zone = QTimeZone::fromSecondsAheadOfUtc(0); + QCOMPARE(zone.fixedSecondsAheadOfUtc(), 0); + } + { + const auto zone = QTimeZone::fromDurationAheadOfUtc(std::chrono::seconds{}); + QCOMPARE(zone.fixedSecondsAheadOfUtc(), 0); + } + { + const auto zone = QTimeZone::fromSecondsAheadOfUtc(1); + QCOMPARE(zone.fixedSecondsAheadOfUtc(), 1); + } + { + const auto zone = QTimeZone::fromDurationAheadOfUtc(std::chrono::seconds{1}); + QCOMPARE(zone.fixedSecondsAheadOfUtc(), 1); + } +#if QT_CONFIG(timezone) + QCOMPARE(QTimeZone("Europe/Oslo").fixedSecondsAheadOfUtc(), 0); +#endif } void tst_QTimeZone::dataStreamTest() { +#ifndef QT_NO_DATASTREAM // Test the OffsetFromUtc backend serialization. First with a custom timezone: QTimeZone tz1("QST", 123456, "Qt Standard Time", "QST", QLocale::Norway, "Qt Testing"); QByteArray tmp; @@ -395,14 +513,60 @@ void tst_QTimeZone::dataStreamTest() ds >> tz2; } QCOMPARE(tz2.id(), tz1.id()); +#endif +} + +#if QT_CONFIG(timezone) +void tst_QTimeZone::asBackendZone() +{ + QCOMPARE(QTimeZone(QTimeZone::LocalTime).asBackendZone(), QTimeZone::systemTimeZone()); + QCOMPARE(QTimeZone(QTimeZone::UTC).asBackendZone(), QTimeZone::utc()); + QCOMPARE(QTimeZone::fromSecondsAheadOfUtc(-300).asBackendZone(), QTimeZone(-300)); + QTimeZone cet("Europe/Oslo"); + QCOMPARE(cet.asBackendZone(), cet); +} + +void tst_QTimeZone::systemZone() +{ + const QTimeZone zone = QTimeZone::systemTimeZone(); + QVERIFY2(zone.isValid(), + "Invalid system zone setting, tests are doomed on misconfigured system."); + // This may fail on Windows if CLDR data doesn't map system MS ID to IANA ID: + QCOMPARE(zone.id(), QTimeZone::systemTimeZoneId()); + QCOMPARE(zone, QTimeZone(QTimeZone::systemTimeZoneId())); + // Check it behaves the same as local-time: + const QDate dates[] = { + QDate::fromJulianDay(0), // far in the distant past (LMT) + QDate(1625, 6, 8), // Before time-zones (date of Cassini's birth) + QDate(1901, 12, 13), // Last day before 32-bit time_t's range + QDate(1969, 12, 31), // Last day before the epoch + QDate(1970, 0, 0), // Start of epoch + QDate(2000, 2, 29), // An anomalous leap day + QDate(2038, 1, 20) // First day after 32-bit time_t's range + }; + for (const auto &date : dates) + QCOMPARE(date.startOfDay(QTimeZone::LocalTime), date.startOfDay(zone)); + +#if __cpp_lib_chrono >= 201907L + const std::chrono::time_zone *currentTimeZone = std::chrono::current_zone(); + QCOMPARE(QByteArrayView(currentTimeZone->name()), QByteArrayView(zone.id())); +#endif } void tst_QTimeZone::isTimeZoneIdAvailable() { const QList<QByteArray> available = QTimeZone::availableTimeZoneIds(); for (const QByteArray &id : available) { - QVERIFY(QTimeZone::isTimeZoneIdAvailable(id)); + QVERIFY2(QTimeZone::isTimeZoneIdAvailable(id), id); QVERIFY2(QTimeZone(id).isValid(), id); + QCOMPARE(QTimeZone(id).id(), id); + } + for (qint32 offset = QTimeZone::MinUtcOffsetSecs; + offset <= QTimeZone::MinUtcOffsetSecs; ++offset) { + const QByteArray id = QTimeZone(offset).id(); + QVERIFY2(QTimeZone::isTimeZoneIdAvailable(id), id); + QVERIFY2(QTimeZone(id).isValid(), id); + QCOMPARE(QTimeZone(id).id(), id); } } @@ -420,7 +584,7 @@ void tst_QTimeZone::utcOffsetId_data() #define ROW(name, valid, offset) \ QTest::newRow(name) << QByteArray(name) << valid << offset - // See qtbase/util/locale_database/cldr2qtimezone.py for source + // See qtbase/util/locale_database/zonedata.py for source // CLDR v35.1 IDs: ROW("UTC", true, 0); ROW("UTC-14:00", true, -50400); @@ -467,7 +631,11 @@ void tst_QTimeZone::utcOffsetId_data() ROW("UTC-11", true, -39600); ROW("UTC-09", true, -32400); ROW("UTC-08", true, -28800); + ROW("UTC-8", true, -28800); + ROW("UTC-2:5", true, -7500); ROW("UTC-02", true, -7200); + ROW("UTC+2", true, 7200); + ROW("UTC+2:5", true, 7500); ROW("UTC+12", true, 43200); ROW("UTC+13", true, 46800); // Encountered in bug reports: @@ -511,10 +679,23 @@ void tst_QTimeZone::utcOffsetId() QTimeZone zone(id); QCOMPARE(zone.isValid(), valid); if (valid) { - QDateTime epoch(QDate(1970, 1, 1), QTime(0, 0, 0), Qt::UTC); + QDateTime epoch(QDate(1970, 1, 1), QTime(0, 0), QTimeZone::UTC); QFETCH(int, offset); QCOMPARE(zone.offsetFromUtc(epoch), offset); QVERIFY(!zone.hasDaylightTime()); + + // zone.id() will be an IANA ID with zero minutes field if original was + // a UTC offset by a whole number of hours. It will also zero-pad a + // single-digit hour or minute to two digits. + if (const qsizetype cut = id.indexOf(':'); cut >= 0) { + if (id.size() == cut + 2) // "...:m" -> "...:0m" + id.insert(cut + 1, '0'); + } else if (zone.id().contains(':')) { + id += ":00"; + } + if (id.indexOf(':') == 5) // UTC±h:mm -> UTC±0h:mm + id.insert(4, '0'); + QCOMPARE(zone.id(), id); } } @@ -526,7 +707,7 @@ void tst_QTimeZone::specificTransition_data() QTest::addColumn<QDate>("stop"); QTest::addColumn<int>("count"); QTest::addColumn<QDateTime>("atUtc"); - // In minutes: + // In seconds: QTest::addColumn<int>("offset"); QTest::addColumn<int>("stdoff"); QTest::addColumn<int>("dstoff"); @@ -543,16 +724,16 @@ void tst_QTimeZone::specificTransition_data() { QTest::newRow("Moscow/2014") // From original bug-report << QByteArray("Europe/Moscow") - << QDate(2011, 4, 1) << QDate(2017, 12,31) << 1 - << QDateTime(QDate(2014, 10, 26), QTime(2, 0, 0), - Qt::OffsetFromUTC, 4 * 3600).toUTC() + << QDate(2011, 4, 1) << QDate(2021, 12, 31) << 1 + << QDateTime(QDate(2014, 10, 26), QTime(2, 0), + QTimeZone::fromSecondsAheadOfUtc(4 * 3600)).toUTC() << 3 * 3600 << 3 * 3600 << 0; } QTest::newRow("Moscow/2011") // Transition on 2011-03-27 << QByteArray("Europe/Moscow") << QDate(2010, 11, 1) << QDate(2014, 10, 25) << 1 - << QDateTime(QDate(2011, 3, 27), QTime(2, 0, 0), - Qt::OffsetFromUTC, 3 * 3600).toUTC() + << QDateTime(QDate(2011, 3, 27), QTime(2, 0), + QTimeZone::fromSecondsAheadOfUtc(3 * 3600)).toUTC() << 4 * 3600 << 4 * 3600 << 0; } @@ -573,14 +754,15 @@ void tst_QTimeZone::specificTransition() if (!timeZone.isValid()) QSKIP("Missing time-zone data"); QTimeZone::OffsetDataList transits = - timeZone.transitions(QDateTime(start, QTime(0, 0), timeZone), - QDateTime(stop, QTime(23, 59), timeZone)); - QCOMPARE(transits.length(), count); - const QTimeZone::OffsetData &transition = transits.at(0); - QCOMPARE(transition.offsetFromUtc, offset); - QCOMPARE(transition.standardTimeOffset, stdoff); - QCOMPARE(transition.daylightTimeOffset, dstoff); - QCOMPARE(transition.atUtc, atUtc); + timeZone.transitions(start.startOfDay(timeZone), stop.endOfDay(timeZone)); + QCOMPARE(transits.size(), count); + if (count) { + const QTimeZone::OffsetData &transition = transits.at(0); + QCOMPARE(transition.offsetFromUtc, offset); + QCOMPARE(transition.standardTimeOffset, stdoff); + QCOMPARE(transition.daylightTimeOffset, dstoff); + QCOMPARE(transition.atUtc, atUtc); + } } void tst_QTimeZone::transitionEachZone_data() @@ -595,19 +777,15 @@ void tst_QTimeZone::transitionEachZone_data() int start, stop; int year; } table[] = { - { 25666200, 3, 12, 1970 }, // 1970-10-25 01:30 UTC; North America - { 1288488600, -4, 8, 2010 } // 2010-10-31 01:30 UTC; Europe, Russia + { 1288488600, -4, 8, 2010 }, // 2010-10-31 01:30 UTC; Europe, Russia + { 25666200, 3, 12, 1970 }, // 1970-10-25 01:30 UTC; North America }; const auto zones = QTimeZone::availableTimeZoneIds(); - for (int k = std::size(table); k-- > 0; ) { + for (const auto &entry : table) { for (const QByteArray &zone : zones) { - const QString name = QString::asprintf("%s@%d", zone.constData(), table[k].year); - QTest::newRow(name.toUtf8().constData()) - << zone - << table[k].baseSecs - << table[k].start - << table[k].stop; + QTest::addRow("%s@%d", zone.constData(), entry.year) + << zone << entry.baseSecs << entry.start << entry.stop; } } } @@ -648,17 +826,29 @@ void tst_QTimeZone::transitionEachZone() void tst_QTimeZone::checkOffset_data() { - QTest::addColumn<QByteArray>("zoneName"); + QTest::addColumn<QTimeZone>("zone"); QTest::addColumn<QDateTime>("when"); QTest::addColumn<int>("netOffset"); QTest::addColumn<int>("stdOffset"); QTest::addColumn<int>("dstOffset"); + const QTimeZone UTC = QTimeZone::UTC; + QTest::addRow("UTC") + << UTC << QDate(1970, 1, 1).startOfDay(UTC) << 0 << 0 << 0; + const auto east = QTimeZone::fromSecondsAheadOfUtc(28'800); // 8 hours + QTest::addRow("UTC+8") + << east << QDate(2000, 2, 29).startOfDay(east) << 28'800 << 28'800 << 0; + const auto west = QTimeZone::fromDurationAheadOfUtc(std::chrono::hours{-8}); + QTest::addRow("UTC-8") + << west << QDate(2100, 2, 28).startOfDay(west) << -28'800 << -28'800 << 0; + struct { const char *zone, *nick; int year, month, day, hour, min, sec; int std, dst; } table[] = { + // Exercise the UTC-backend: + { "UTC", "epoch", 1970, 1, 1, 0, 0, 0, 0, 0 }, // Zone with no transitions (QTBUG-74614, QTBUG-74666, when TZ backend uses minimal data) { "Etc/UTC", "epoch", 1970, 1, 1, 0, 0, 0, 0, 0 }, { "Etc/UTC", "pre_int32", 1901, 12, 13, 20, 45, 51, 0, 0 }, @@ -666,15 +856,15 @@ void tst_QTimeZone::checkOffset_data() { "Etc/UTC", "post_uint32", 2106, 2, 7, 6, 28, 17, 0, 0 }, { "Etc/UTC", "initial", -292275056, 5, 16, 16, 47, 5, 0, 0 }, { "Etc/UTC", "final", 292278994, 8, 17, 7, 12, 55, 0, 0 }, - // Kiev: regression test for QTBUG-64122 (on MS): - { "Europe/Kiev", "summer", 2017, 10, 27, 12, 0, 0, 2 * 3600, 3600 }, - { "Europe/Kiev", "winter", 2017, 10, 29, 12, 0, 0, 2 * 3600, 0 } + // Kyiv: regression test for QTBUG-64122 (on MS): + { "Europe/Kyiv", "summer", 2017, 10, 27, 12, 0, 0, 2 * 3600, 3600 }, + { "Europe/Kyiv", "winter", 2017, 10, 29, 12, 0, 0, 2 * 3600, 0 } }; for (const auto &entry : table) { QTimeZone zone(entry.zone); if (zone.isValid()) { QTest::addRow("%s@%s", entry.zone, entry.nick) - << QByteArray(entry.zone) + << zone << QDateTime(QDate(entry.year, entry.month, entry.day), QTime(entry.hour, entry.min, entry.sec), zone) << entry.dst + entry.std << entry.std << entry.dst; @@ -686,18 +876,24 @@ void tst_QTimeZone::checkOffset_data() void tst_QTimeZone::checkOffset() { - QFETCH(QByteArray, zoneName); + QFETCH(QTimeZone, zone); QFETCH(QDateTime, when); QFETCH(int, netOffset); QFETCH(int, stdOffset); QFETCH(int, dstOffset); - QTimeZone zone(zoneName); QVERIFY(zone.isValid()); // It was when _data() added the row ! QCOMPARE(zone.offsetFromUtc(when), netOffset); QCOMPARE(zone.standardTimeOffset(when), stdOffset); QCOMPARE(zone.daylightTimeOffset(when), dstOffset); QCOMPARE(zone.isDaylightTime(when), dstOffset != 0); + + // Also test offsetData(), which gets all this data in one go: + const auto data = zone.offsetData(when); + QCOMPARE(data.atUtc, when); + QCOMPARE(data.offsetFromUtc, netOffset); + QCOMPARE(data.standardTimeOffset, stdOffset); + QCOMPARE(data.daylightTimeOffset, dstOffset); } void tst_QTimeZone::availableTimeZoneIds() @@ -723,12 +919,13 @@ void tst_QTimeZone::availableTimeZoneIds() void tst_QTimeZone::stressTest() { + const auto UTC = QTimeZone::UTC; const QList<QByteArray> idList = QTimeZone::availableTimeZoneIds(); for (const QByteArray &id : idList) { QTimeZone testZone = QTimeZone(id); QCOMPARE(testZone.isValid(), true); QCOMPARE(testZone.id(), id); - QDateTime testDate = QDateTime(QDate(2015, 1, 1), QTime(0, 0, 0), Qt::UTC); + QDateTime testDate = QDateTime(QDate(2015, 1, 1), QTime(0, 0), UTC); testZone.territory(); testZone.comment(); testZone.displayName(testDate); @@ -745,10 +942,10 @@ void tst_QTimeZone::stressTest() testZone.nextTransition(testDate); testZone.previousTransition(testDate); // Dates known to be outside possible tz file pre-calculated rules range - QDateTime lowDate1 = QDateTime(QDate(1800, 1, 1), QTime(0, 0, 0), Qt::UTC); - QDateTime lowDate2 = QDateTime(QDate(1800, 6, 1), QTime(0, 0, 0), Qt::UTC); - QDateTime highDate1 = QDateTime(QDate(2200, 1, 1), QTime(0, 0, 0), Qt::UTC); - QDateTime highDate2 = QDateTime(QDate(2200, 6, 1), QTime(0, 0, 0), Qt::UTC); + QDateTime lowDate1 = QDateTime(QDate(1800, 1, 1), QTime(0, 0), UTC); + QDateTime lowDate2 = QDateTime(QDate(1800, 6, 1), QTime(0, 0), UTC); + QDateTime highDate1 = QDateTime(QDate(2200, 1, 1), QTime(0, 0), UTC); + QDateTime highDate2 = QDateTime(QDate(2200, 6, 1), QTime(0, 0), UTC); testZone.nextTransition(lowDate1); testZone.nextTransition(lowDate2); testZone.previousTransition(lowDate2); @@ -801,46 +998,54 @@ void tst_QTimeZone::windowsId() QByteArray("CST6CDT")); QCOMPARE(QTimeZone::windowsIdToDefaultIanaId(QByteArray()), QByteArray()); - // No country is sorted list of all zones - QList<QByteArray> list; - list << "America/Chicago" << "America/Indiana/Knox" << "America/Indiana/Tell_City" - << "America/Matamoros" << "America/Menominee" << "America/North_Dakota/Beulah" - << "America/North_Dakota/Center" << "America/North_Dakota/New_Salem" - << "America/Rainy_River" << "America/Rankin_Inlet" << "America/Resolute" - << "America/Winnipeg" << "CST6CDT"; - QCOMPARE(QTimeZone::windowsIdToIanaIds("Central Standard Time"), list); - - // Check country with no match returns empty list - list.clear(); - QCOMPARE(QTimeZone::windowsIdToIanaIds("Central Standard Time", QLocale::NewZealand), - list); - - // Check valid country returns list in preference order - list.clear(); - list << "America/Winnipeg" << "America/Rainy_River" << "America/Rankin_Inlet" - << "America/Resolute"; - QCOMPARE(QTimeZone::windowsIdToIanaIds("Central Standard Time", QLocale::Canada), list); - - list.clear(); - list << "America/Matamoros"; - QCOMPARE(QTimeZone::windowsIdToIanaIds("Central Standard Time", QLocale::Mexico), list); - - list.clear(); - list << "America/Chicago" << "America/Indiana/Knox" << "America/Indiana/Tell_City" - << "America/Menominee" << "America/North_Dakota/Beulah" << "America/North_Dakota/Center" - << "America/North_Dakota/New_Salem"; - QCOMPARE(QTimeZone::windowsIdToIanaIds("Central Standard Time", QLocale::UnitedStates), - list); - - list.clear(); - list << "CST6CDT"; - QCOMPARE(QTimeZone::windowsIdToIanaIds("Central Standard Time", QLocale::AnyTerritory), - list); - - // Check no windowsId return empty - list.clear(); - QCOMPARE(QTimeZone::windowsIdToIanaIds(QByteArray()), list); - QCOMPARE(QTimeZone::windowsIdToIanaIds(QByteArray(), QLocale::AnyTerritory), list); + { + // With no country, expect sorted list of all zones for ID + const QList<QByteArray> list = { + "America/Chicago", "America/Indiana/Knox", "America/Indiana/Tell_City", + "America/Matamoros", "America/Menominee", "America/North_Dakota/Beulah", + "America/North_Dakota/Center", "America/North_Dakota/New_Salem", + "America/Ojinaga", "America/Rainy_River", "America/Rankin_Inlet", + "America/Resolute", "America/Winnipeg", "CST6CDT" + }; + QCOMPARE(QTimeZone::windowsIdToIanaIds("Central Standard Time"), list); + } + { + // Check country with no match returns empty list + const QList<QByteArray> empty; + QCOMPARE(QTimeZone::windowsIdToIanaIds("Central Standard Time", QLocale::NewZealand), + empty); + } + { + // Check valid country returns list in preference order + const QList<QByteArray> list = { + "America/Winnipeg", "America/Rainy_River", "America/Rankin_Inlet", "America/Resolute" + }; + QCOMPARE(QTimeZone::windowsIdToIanaIds("Central Standard Time", QLocale::Canada), list); + } + { + const QList<QByteArray> list = { "America/Matamoros", "America/Ojinaga" }; + QCOMPARE(QTimeZone::windowsIdToIanaIds("Central Standard Time", QLocale::Mexico), list); + } + { + const QList<QByteArray> list = { + "America/Chicago", "America/Indiana/Knox", "America/Indiana/Tell_City", + "America/Menominee", "America/North_Dakota/Beulah", "America/North_Dakota/Center", + "America/North_Dakota/New_Salem" + }; + QCOMPARE(QTimeZone::windowsIdToIanaIds("Central Standard Time", QLocale::UnitedStates), + list); + } + { + const QList<QByteArray> list = { "CST6CDT" }; + QCOMPARE(QTimeZone::windowsIdToIanaIds("Central Standard Time", QLocale::AnyTerritory), + list); + } + { + // Check empty if given no windowsId: + const QList<QByteArray> empty; + QCOMPARE(QTimeZone::windowsIdToIanaIds(QByteArray()), empty); + QCOMPARE(QTimeZone::windowsIdToIanaIds(QByteArray(), QLocale::AnyTerritory), empty); + } } void tst_QTimeZone::isValidId_data() @@ -863,7 +1068,7 @@ void tst_QTimeZone::isValidId_data() // Parts separated by '/', each part min 1 and max of 14 chars TESTSET("empty", "", false); TESTSET("minimal", "m", true); -#ifdef Q_OS_ANDROID +#if defined(Q_OS_ANDROID) || QT_CONFIG(icu) TESTSET("maximal", "East-Saskatchewan", true); // Android actually uses this TESTSET("too long", "North-Saskatchewan", false); // ... but thankfully not this. #else @@ -934,7 +1139,7 @@ void tst_QTimeZone::isValidId_data() QTest::newRow("a,z alone") << QByteArray("a,z") << false; QTest::newRow("/z alone") << QByteArray("/z") << false; QTest::newRow("-z alone") << QByteArray("-z") << false; -#ifdef Q_OS_ANDROID +#if defined(Q_OS_ANDROID) || QT_CONFIG(icu) QTest::newRow("long alone") << QByteArray("12345678901234567") << true; QTest::newRow("over-long alone") << QByteArray("123456789012345678") << false; #else @@ -961,6 +1166,7 @@ void tst_QTimeZone::serialize() { int parts = 0; #ifndef QT_NO_DEBUG_STREAM + QTest::ignoreMessage(QtDebugMsg, "QTimeZone(\"\")"); qDebug() << QTimeZone(); // to verify no crash parts++; #endif @@ -1018,18 +1224,25 @@ void tst_QTimeZone::utcTest() QCOMPARE(tzp.hasDaylightTime(), false); QCOMPARE(tzp.hasTransitions(), false); - // Test create from UTC Offset (uses minimal id, skipping minutes if 0) + // Test create from UTC Offset: QDateTime now = QDateTime::currentDateTime(); QTimeZone tz(36000); QVERIFY(tz.isValid()); - QCOMPARE(tz.id(), QByteArray("UTC+10")); + QCOMPARE(tz.id(), QByteArray("UTC+10:00")); QCOMPARE(tz.offsetFromUtc(now), 36000); QCOMPARE(tz.standardTimeOffset(now), 36000); QCOMPARE(tz.daylightTimeOffset(now), 0); - // Test invalid UTC offset, must be in range -14 to +14 hours - int min = -14*60*60; - int max = 14*60*60; + tz = QTimeZone(15 * 3600); // no IANA ID, so uses minimal id, skipping :00 minutes + QVERIFY(tz.isValid()); + QCOMPARE(tz.id(), QByteArray("UTC+15")); + QCOMPARE(tz.offsetFromUtc(now), 15 * 3600); + QCOMPARE(tz.standardTimeOffset(now), 15 * 3600); + QCOMPARE(tz.daylightTimeOffset(now), 0); + + // Test validity range of UTC offsets: + int min = QTimeZone::MinUtcOffsetSecs; + int max = QTimeZone::MaxUtcOffsetSecs; QCOMPARE(QTimeZone(min - 1).isValid(), false); QCOMPARE(QTimeZone(min).isValid(), true); QCOMPARE(QTimeZone(min + 1).isValid(), true); @@ -1060,18 +1273,25 @@ void tst_QTimeZone::utcTest() #endif // QT_BUILD_INTERNAL } +// Relies on local variable names: zone tzp and locale enUS. +#define ZONE_DNAME_CHECK(type, name, val) \ + QCOMPARE(tzp.displayName(QTimeZone::type, QTimeZone::name, enUS), \ + QStringLiteral(val)); + void tst_QTimeZone::icuTest() { #if defined(QT_BUILD_INTERNAL) && QT_CONFIG(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(); + qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch(); + qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch(); // Test default constructor QIcuTimeZonePrivate tzpd; QVERIFY(tzpd.isValid()); - // Test invalid constructor + // Test invalid is not available: + QVERIFY(!tzpd.isTimeZoneIdAvailable("Gondwana/Erewhon")); + // and construction gives an invalid result: QIcuTimeZonePrivate tzpi("Gondwana/Erewhon"); QCOMPARE(tzpi.isValid(), false); @@ -1083,25 +1303,16 @@ void tst_QTimeZone::icuTest() 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")); + ZONE_DNAME_CHECK(StandardTime, LongName, "Central European Standard Time"); + ZONE_DNAME_CHECK(StandardTime, ShortName, "GMT+01:00"); + ZONE_DNAME_CHECK(StandardTime, OffsetName, "UTC+01:00"); + ZONE_DNAME_CHECK(DaylightTime, LongName, "Central European Summer Time"); + ZONE_DNAME_CHECK(DaylightTime, ShortName, "GMT+02:00"); + ZONE_DNAME_CHECK(DaylightTime, OffsetName, "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")); + ZONE_DNAME_CHECK(GenericTime, LongName, "Central European Standard Time"); + ZONE_DNAME_CHECK(GenericTime, ShortName, "GMT+01:00"); + ZONE_DNAME_CHECK(GenericTime, OffsetName, "UTC+01:00"); // Test Abbreviations QCOMPARE(tzp.abbreviation(std), QString("CET")); @@ -1118,9 +1329,10 @@ void tst_QTimeZone::icuTest() void tst_QTimeZone::tzTest() { #if defined QT_BUILD_INTERNAL && defined Q_OS_UNIX && !defined Q_OS_DARWIN && !defined Q_OS_ANDROID + const auto UTC = QTimeZone::UTC; // 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 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), UTC).toMSecsSinceEpoch(); + qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), UTC).toMSecsSinceEpoch(); // Test default constructor QTzTimeZonePrivate tzpd; @@ -1128,7 +1340,7 @@ void tst_QTimeZone::tzTest() // Test invalid constructor QTzTimeZonePrivate tzpi("Gondwana/Erewhon"); - QCOMPARE(tzpi.isValid(), false); + QVERIFY(!tzpi.isValid()); // Test named constructor QTzTimeZonePrivate tzp("Europe/Berlin"); @@ -1153,8 +1365,8 @@ void tst_QTimeZone::tzTest() // It shouldn't have any transitions. QTimeZone::hasTransitions() only says // whether the backend supports them, so ask for transitions in a wide // enough interval that one would show up, if there are any: - QVERIFY(permaDst.transitions(QDate(2015, 1, 1).startOfDay(Qt::UTC).toMSecsSinceEpoch(), - QDate(2020, 1, 1).startOfDay(Qt::UTC).toMSecsSinceEpoch() + QVERIFY(permaDst.transitions(QDate(2015, 1, 1).startOfDay(UTC).toMSecsSinceEpoch(), + QDate(2020, 1, 1).startOfDay(UTC).toMSecsSinceEpoch() ).isEmpty()); QTimeZone tzBrazil("BRT+3"); // parts of Northern Brazil, as a POSIX rule @@ -1166,44 +1378,26 @@ void tst_QTimeZone::tzTest() // Only test names in debug mode, names used can vary by ICU version installed if (debug) { #if QT_CONFIG(icu) - 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")); + ZONE_DNAME_CHECK(StandardTime, LongName, "Central European Standard Time"); + ZONE_DNAME_CHECK(StandardTime, ShortName, "GMT+01:00"); + ZONE_DNAME_CHECK(StandardTime, OffsetName, "UTC+01:00"); + ZONE_DNAME_CHECK(DaylightTime, LongName, "Central European Summer Time"); + ZONE_DNAME_CHECK(DaylightTime, ShortName, "GMT+02:00"); + ZONE_DNAME_CHECK(DaylightTime, OffsetName, "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")); + ZONE_DNAME_CHECK(GenericTime, LongName, "Central European Standard Time"); + ZONE_DNAME_CHECK(GenericTime, ShortName, "GMT+01:00"); + ZONE_DNAME_CHECK(GenericTime, OffsetName, "UTC+01:00"); #else - QCOMPARE(tzp.displayName(QTimeZone::StandardTime, QTimeZone::LongName, enUS), - QString("CET")); - QCOMPARE(tzp.displayName(QTimeZone::StandardTime, QTimeZone::ShortName, enUS), - QString("CET")); - QCOMPARE(tzp.displayName(QTimeZone::StandardTime, QTimeZone::OffsetName, enUS), - QString("CET")); - QCOMPARE(tzp.displayName(QTimeZone::DaylightTime, QTimeZone::LongName, enUS), - QString("CEST")); - QCOMPARE(tzp.displayName(QTimeZone::DaylightTime, QTimeZone::ShortName, enUS), - QString("CEST")); - QCOMPARE(tzp.displayName(QTimeZone::DaylightTime, QTimeZone::OffsetName, enUS), - QString("CEST")); - QCOMPARE(tzp.displayName(QTimeZone::GenericTime, QTimeZone::LongName, enUS), - QString("CET")); - QCOMPARE(tzp.displayName(QTimeZone::GenericTime, QTimeZone::ShortName, enUS), - QString("CET")); - QCOMPARE(tzp.displayName(QTimeZone::GenericTime, QTimeZone::OffsetName, enUS), - QString("CET")); + ZONE_DNAME_CHECK(StandardTime, LongName, "CET"); + ZONE_DNAME_CHECK(StandardTime, ShortName, "CET"); + ZONE_DNAME_CHECK(StandardTime, OffsetName, "CET"); + ZONE_DNAME_CHECK(DaylightTime, LongName, "CEST"); + ZONE_DNAME_CHECK(DaylightTime, ShortName, "CEST"); + ZONE_DNAME_CHECK(DaylightTime, OffsetName, "CEST"); + ZONE_DNAME_CHECK(GenericTime, LongName, "CET"); + ZONE_DNAME_CHECK(GenericTime, ShortName, "CET"); + ZONE_DNAME_CHECK(GenericTime, OffsetName, "CET"); #endif // icu // Test Abbreviations @@ -1222,82 +1416,88 @@ void tst_QTimeZone::tzTest() // Warning: This could vary depending on age of TZ file! // Test low date uses first rule found + constexpr qint64 ancient = -Q_INT64_C(9999999999999); // Note: Depending on the OS in question, the database may be carrying the // Local Mean Time. which for Berlin is 0:53:28 - QTimeZonePrivate::Data dat = tzp.data(-9999999999999); - QCOMPARE(dat.atMSecsSinceEpoch, (qint64)-9999999999999); + QTimeZonePrivate::Data dat = tzp.data(ancient); + QCOMPARE(dat.atMSecsSinceEpoch, ancient); QCOMPARE(dat.daylightTimeOffset, 0); if (dat.abbreviation == "LMT") { QCOMPARE(dat.standardTimeOffset, 3208); } else { QCOMPARE(dat.standardTimeOffset, 3600); + constexpr qint64 invalidTime = std::numeric_limits<qint64>::min(); + constexpr int invalidOffset = std::numeric_limits<int>::min(); // Test previous to low value is invalid - dat = tzp.previousTransition(-9999999999999); - QCOMPARE(dat.atMSecsSinceEpoch, std::numeric_limits<qint64>::min()); - QCOMPARE(dat.standardTimeOffset, std::numeric_limits<int>::min()); - QCOMPARE(dat.daylightTimeOffset, std::numeric_limits<int>::min()); + dat = tzp.previousTransition(ancient); + QCOMPARE(dat.atMSecsSinceEpoch, invalidTime); + QCOMPARE(dat.standardTimeOffset, invalidOffset); + QCOMPARE(dat.daylightTimeOffset, invalidOffset); } - dat = tzp.nextTransition(-9999999999999); - QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, Qt::OffsetFromUTC, 3600), - QDateTime(QDate(1893, 4, 1), QTime(0, 6, 32), Qt::OffsetFromUTC, 3600)); + dat = tzp.nextTransition(ancient); + QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, + QTimeZone::fromSecondsAheadOfUtc(3600)), + QDateTime(QDate(1893, 4, 1), QTime(0, 6, 32), + QTimeZone::fromSecondsAheadOfUtc(3600))); QCOMPARE(dat.standardTimeOffset, 3600); QCOMPARE(dat.daylightTimeOffset, 0); // Date-times late enough to exercise POSIX rules: - qint64 stdHi = QDate(2100, 1, 1).startOfDay(Qt::UTC).toMSecsSinceEpoch(); - qint64 dstHi = QDate(2100, 6, 1).startOfDay(Qt::UTC).toMSecsSinceEpoch(); + qint64 stdHi = QDate(2100, 1, 1).startOfDay(UTC).toMSecsSinceEpoch(); + qint64 dstHi = QDate(2100, 6, 1).startOfDay(UTC).toMSecsSinceEpoch(); // Relevant last Sundays in October and March: QCOMPARE(Qt::DayOfWeek(QDate(2099, 10, 25).dayOfWeek()), Qt::Sunday); QCOMPARE(Qt::DayOfWeek(QDate(2100, 3, 28).dayOfWeek()), Qt::Sunday); QCOMPARE(Qt::DayOfWeek(QDate(2100, 10, 31).dayOfWeek()), Qt::Sunday); dat = tzp.data(stdHi); - QCOMPARE(dat.atMSecsSinceEpoch - stdHi, (qint64)0); + QCOMPARE(dat.atMSecsSinceEpoch - stdHi, qint64(0)); QCOMPARE(dat.offsetFromUtc, 3600); QCOMPARE(dat.standardTimeOffset, 3600); QCOMPARE(dat.daylightTimeOffset, 0); dat = tzp.data(dstHi); - QCOMPARE(dat.atMSecsSinceEpoch - dstHi, (qint64)0); + QCOMPARE(dat.atMSecsSinceEpoch - dstHi, qint64(0)); QCOMPARE(dat.offsetFromUtc, 7200); QCOMPARE(dat.standardTimeOffset, 3600); QCOMPARE(dat.daylightTimeOffset, 3600); dat = tzp.previousTransition(stdHi); QCOMPARE(dat.abbreviation, QStringLiteral("CET")); - QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, Qt::UTC), - QDateTime(QDate(2099, 10, 25), QTime(3, 0), Qt::OffsetFromUTC, 7200)); + QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, UTC), + QDateTime(QDate(2099, 10, 25), QTime(3, 0), QTimeZone::fromSecondsAheadOfUtc(7200))); QCOMPARE(dat.offsetFromUtc, 3600); QCOMPARE(dat.standardTimeOffset, 3600); QCOMPARE(dat.daylightTimeOffset, 0); dat = tzp.previousTransition(dstHi); QCOMPARE(dat.abbreviation, QStringLiteral("CEST")); - QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, Qt::UTC), - QDateTime(QDate(2100, 3, 28), QTime(2, 0), Qt::OffsetFromUTC, 3600)); + QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, UTC), + QDateTime(QDate(2100, 3, 28), QTime(2, 0), QTimeZone::fromSecondsAheadOfUtc(3600))); QCOMPARE(dat.offsetFromUtc, 7200); QCOMPARE(dat.standardTimeOffset, 3600); QCOMPARE(dat.daylightTimeOffset, 3600); dat = tzp.nextTransition(stdHi); QCOMPARE(dat.abbreviation, QStringLiteral("CEST")); - QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, Qt::UTC), - QDateTime(QDate(2100, 3, 28), QTime(2, 0), Qt::OffsetFromUTC, 3600)); + QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, UTC), + QDateTime(QDate(2100, 3, 28), QTime(2, 0), QTimeZone::fromSecondsAheadOfUtc(3600))); QCOMPARE(dat.offsetFromUtc, 7200); QCOMPARE(dat.standardTimeOffset, 3600); QCOMPARE(dat.daylightTimeOffset, 3600); dat = tzp.nextTransition(dstHi); QCOMPARE(dat.abbreviation, QStringLiteral("CET")); - QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, Qt::OffsetFromUTC, 3600), - QDateTime(QDate(2100, 10, 31), QTime(3, 0), Qt::OffsetFromUTC, 7200)); + QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, + QTimeZone::fromSecondsAheadOfUtc(3600)), + QDateTime(QDate(2100, 10, 31), QTime(3, 0), QTimeZone::fromSecondsAheadOfUtc(7200))); QCOMPARE(dat.offsetFromUtc, 3600); QCOMPARE(dat.standardTimeOffset, 3600); QCOMPARE(dat.daylightTimeOffset, 0); - // Test TZ timezone vs UTC timezone for fractionary negative offset + // Test TZ timezone vs UTC timezone for non-whole-hour negative offset: QTzTimeZonePrivate tztz1("America/Caracas"); QUtcTimeZonePrivate tzutc1("UTC-04:30"); QVERIFY(tztz1.isValid()); @@ -1306,22 +1506,25 @@ void tst_QTimeZone::tzTest() QTzTimeZonePrivate::Data datautc1 = tzutc1.data(std); QCOMPARE(datatz1.offsetFromUtc, datautc1.offsetFromUtc); - // Test TZ timezone vs UTC timezone for fractionary positive offset - QTzTimeZonePrivate tztz2("Asia/Calcutta"); + // Test TZ timezone vs UTC timezone for non-whole-hour positive offset: + QTzTimeZonePrivate tztz2k("Asia/Kolkata"); // New name + QTzTimeZonePrivate tztz2c("Asia/Calcutta"); // Legacy name + // Can't assign QtzTZP, so use a reference; prefer new name. + QTzTimeZonePrivate &tztz2 = tztz2k.isValid() ? tztz2k : tztz2c; QUtcTimeZonePrivate tzutc2("UTC+05:30"); - QVERIFY(tztz2.isValid()); + QVERIFY2(tztz2.isValid(), tztz2.id().constData()); QVERIFY(tzutc2.isValid()); QTzTimeZonePrivate::Data datatz2 = tztz2.data(std); QTzTimeZonePrivate::Data datautc2 = tzutc2.data(std); QCOMPARE(datatz2.offsetFromUtc, datautc2.offsetFromUtc); - // Test a timezone with a name that isn't all letters + // Test a timezone with an abbreviation that isn't all letters: QTzTimeZonePrivate tzBarnaul("Asia/Barnaul"); if (tzBarnaul.isValid()) { QCOMPARE(tzBarnaul.data(std).abbreviation, QString("+07")); // first full day of the new rule (tzdata2016b) - QDateTime dt(QDate(2016, 3, 28), QTime(0, 0, 0), Qt::UTC); + QDateTime dt(QDate(2016, 3, 28), QTime(0, 0), UTC); QCOMPARE(tzBarnaul.data(dt.toMSecsSinceEpoch()).abbreviation, QString("+07")); } #endif // QT_BUILD_INTERNAL && Q_OS_UNIX && !Q_OS_DARWIN @@ -1331,8 +1534,8 @@ void tst_QTimeZone::macTest() { #if defined(QT_BUILD_INTERNAL) && defined(Q_OS_DARWIN) // 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 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch(); + qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch(); // Test default constructor QMacTimeZonePrivate tzpd; @@ -1350,25 +1553,16 @@ void tst_QTimeZone::macTest() 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")); + ZONE_DNAME_CHECK(StandardTime, LongName, "Central European Standard Time"); + ZONE_DNAME_CHECK(StandardTime, ShortName, "GMT+01:00"); + ZONE_DNAME_CHECK(StandardTime, OffsetName, "UTC+01:00"); + ZONE_DNAME_CHECK(DaylightTime, LongName, "Central European Summer Time"); + ZONE_DNAME_CHECK(DaylightTime, ShortName, "GMT+02:00"); + ZONE_DNAME_CHECK(DaylightTime, OffsetName, "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 Time")); - QCOMPARE(tzp.displayName(QTimeZone::GenericTime, QTimeZone::ShortName, enUS), - QString("Germany Time")); - QCOMPARE(tzp.displayName(QTimeZone::GenericTime, QTimeZone::OffsetName, enUS), - QString("UTC+01:00")); + ZONE_DNAME_CHECK(GenericTime, LongName, "Central European Time"); + ZONE_DNAME_CHECK(GenericTime, ShortName, "Germany Time"); + ZONE_DNAME_CHECK(GenericTime, OffsetName, "UTC+01:00"); // Test Abbreviations QCOMPARE(tzp.abbreviation(std), QString("CET")); @@ -1396,8 +1590,8 @@ void tst_QTimeZone::winTest() { #if defined(QT_BUILD_INTERNAL) && defined(USING_WIN_TZ) // 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 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch(); + qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch(); // Test default constructor QWinTimeZonePrivate tzpd; @@ -1419,24 +1613,17 @@ void tst_QTimeZone::winTest() if (debug) { // Test display names by type QLocale enUS("en_US"); - QCOMPARE(tzp.displayName(QTimeZone::StandardTime, QTimeZone::LongName, enUS), - QString("W. Europe Standard Time")); - QCOMPARE(tzp.displayName(QTimeZone::StandardTime, QTimeZone::ShortName, enUS), - QString("W. Europe Standard Time")); - QCOMPARE(tzp.displayName(QTimeZone::StandardTime, QTimeZone::OffsetName, enUS), - QString("UTC+01:00")); - QCOMPARE(tzp.displayName(QTimeZone::DaylightTime, QTimeZone::LongName, enUS), - QString("W. Europe Daylight Time")); - QCOMPARE(tzp.displayName(QTimeZone::DaylightTime, QTimeZone::ShortName, enUS), - QString("W. Europe Daylight Time")); - QCOMPARE(tzp.displayName(QTimeZone::DaylightTime, QTimeZone::OffsetName, enUS), - QString("UTC+02:00")); - QCOMPARE(tzp.displayName(QTimeZone::GenericTime, QTimeZone::LongName, enUS), - QString("(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna")); - QCOMPARE(tzp.displayName(QTimeZone::GenericTime, QTimeZone::ShortName, enUS), - QString("(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna")); - QCOMPARE(tzp.displayName(QTimeZone::GenericTime, QTimeZone::OffsetName, enUS), - QString("UTC+01:00")); + ZONE_DNAME_CHECK(StandardTime, LongName, "W. Europe Standard Time"); + ZONE_DNAME_CHECK(StandardTime, ShortName, "W. Europe Standard Time"); + ZONE_DNAME_CHECK(StandardTime, OffsetName, "UTC+01:00"); + ZONE_DNAME_CHECK(DaylightTime, LongName, "W. Europe Daylight Time"); + ZONE_DNAME_CHECK(DaylightTime, ShortName, "W. Europe Daylight Time"); + ZONE_DNAME_CHECK(DaylightTime, OffsetName, "UTC+02:00"); + ZONE_DNAME_CHECK(GenericTime, LongName, + "(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna"); + ZONE_DNAME_CHECK(GenericTime, ShortName, + "(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna"); + ZONE_DNAME_CHECK(GenericTime, OffsetName, "UTC+01:00"); // Test Abbreviations QCOMPARE(tzp.abbreviation(std), QString("W. Europe Standard Time")); @@ -1450,14 +1637,67 @@ void tst_QTimeZone::winTest() #endif // QT_BUILD_INTERNAL && USING_WIN_TZ } +#undef ZONE_DNAME_CHECK + +void tst_QTimeZone::localeSpecificDisplayName_data() +{ +#ifdef USING_WIN_TZ + QSKIP("MS backend does not use locale parameter"); +#endif + QTest::addColumn<QByteArray>("zoneName"); + QTest::addColumn<QLocale>("locale"); + QTest::addColumn<QTimeZone::TimeType>("timeType"); + QTest::addColumn<QString>("expectedName"); + + QStringList names; + QLocale locale; + // Pick a non-system locale; German or French + if (QLocale::system().language() != QLocale::German) { + locale = QLocale(QLocale::German); + names << QString("Mitteleurop\u00e4ische Normalzeit") + << QString("Mitteleurop\u00e4ische Sommerzeit"); + } else { + locale = QLocale(QLocale::French); + names << QString("heure normale d\u2019Europe centrale") + << QString("heure d\u2019\u00E9t\u00E9 d\u2019Europe centrale"); + } + + qsizetype index = 0; + QTest::newRow("Berlin, standard time") + << QByteArray("Europe/Berlin") << locale << QTimeZone::StandardTime + << names.at(index++); + + QTest::newRow("Berlin, summer time") + << QByteArray("Europe/Berlin") << locale << QTimeZone::DaylightTime + << names.at(index++); +} + +void tst_QTimeZone::localeSpecificDisplayName() +{ + // This test checks that QTimeZone::displayName() correctly uses the + // specified locale, NOT the system locale (see QTBUG-101460). + QFETCH(QByteArray, zoneName); + QFETCH(QLocale, locale); + QFETCH(QTimeZone::TimeType, timeType); + QFETCH(QString, expectedName); + + QTimeZone zone(zoneName); + QVERIFY(zone.isValid()); + + const QString localeName = zone.displayName(timeType, QTimeZone::LongName, locale); + QCOMPARE(localeName, expectedName); +} + #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(); + const auto UTC = QTimeZone::UTC; + const auto eastOneHour = QTimeZone::fromSecondsAheadOfUtc(3600); + qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), UTC).toMSecsSinceEpoch(); + qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), UTC).toMSecsSinceEpoch(); + qint64 prev = QDateTime(QDate(2011, 1, 1), QTime(0, 0), UTC).toMSecsSinceEpoch(); QCOMPARE(tzp.offsetFromUtc(std), 3600); QCOMPARE(tzp.offsetFromUtc(dst), 7200); @@ -1490,32 +1730,34 @@ void tst_QTimeZone::testCetPrivate(const QTimeZonePrivate &tzp) if (tzp.hasTransitions()) { QTimeZonePrivate::Data tran = tzp.nextTransition(std); // 2012-03-25 02:00 CET, +1 -> +2 - QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, Qt::UTC), - QDateTime(QDate(2012, 3, 25), QTime(2, 0), Qt::OffsetFromUTC, 3600)); + QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC), + QDateTime(QDate(2012, 3, 25), QTime(2, 0), eastOneHour)); QCOMPARE(tran.offsetFromUtc, 7200); QCOMPARE(tran.standardTimeOffset, 3600); QCOMPARE(tran.daylightTimeOffset, 3600); tran = tzp.nextTransition(dst); // 2012-10-28 03:00 CEST, +2 -> +1 - QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, Qt::UTC), - QDateTime(QDate(2012, 10, 28), QTime(3, 0), Qt::OffsetFromUTC, 2 * 3600)); + QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC), + QDateTime(QDate(2012, 10, 28), QTime(3, 0), + QTimeZone::fromSecondsAheadOfUtc(2 * 3600))); QCOMPARE(tran.offsetFromUtc, 3600); QCOMPARE(tran.standardTimeOffset, 3600); QCOMPARE(tran.daylightTimeOffset, 0); tran = tzp.previousTransition(std); // 2011-10-30 03:00 CEST, +2 -> +1 - QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, Qt::UTC), - QDateTime(QDate(2011, 10, 30), QTime(3, 0), Qt::OffsetFromUTC, 2 * 3600)); + QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC), + QDateTime(QDate(2011, 10, 30), QTime(3, 0), + QTimeZone::fromSecondsAheadOfUtc(2 * 3600))); QCOMPARE(tran.offsetFromUtc, 3600); QCOMPARE(tran.standardTimeOffset, 3600); QCOMPARE(tran.daylightTimeOffset, 0); tran = tzp.previousTransition(dst); // 2012-03-25 02:00 CET, +1 -> +2 (again) - QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, Qt::UTC), - QDateTime(QDate(2012, 3, 25), QTime(2, 0), Qt::OffsetFromUTC, 3600)); + QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC), + QDateTime(QDate(2012, 3, 25), QTime(2, 0), eastOneHour)); QCOMPARE(tran.offsetFromUtc, 7200); QCOMPARE(tran.standardTimeOffset, 3600); QCOMPARE(tran.daylightTimeOffset, 3600); @@ -1523,25 +1765,24 @@ void tst_QTimeZone::testCetPrivate(const QTimeZonePrivate &tzp) QTimeZonePrivate::DataList expected; // 2011-03-27 02:00 CET, +1 -> +2 tran.atMSecsSinceEpoch = QDateTime(QDate(2011, 3, 27), QTime(2, 0), - Qt::OffsetFromUTC, 3600).toMSecsSinceEpoch(); + eastOneHour).toMSecsSinceEpoch(); tran.offsetFromUtc = 7200; tran.standardTimeOffset = 3600; tran.daylightTimeOffset = 3600; expected << tran; // 2011-10-30 03:00 CEST, +2 -> +1 tran.atMSecsSinceEpoch = QDateTime(QDate(2011, 10, 30), QTime(3, 0), - Qt::OffsetFromUTC, 2 * 3600).toMSecsSinceEpoch(); + QTimeZone::fromSecondsAheadOfUtc(2 * 3600) + ).toMSecsSinceEpoch(); 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(QDateTime::fromMSecsSinceEpoch(result.at(i).atMSecsSinceEpoch, - Qt::OffsetFromUTC, 3600), - QDateTime::fromMSecsSinceEpoch(expected.at(i).atMSecsSinceEpoch, - Qt::OffsetFromUTC, 3600)); + QCOMPARE(result.size(), expected.size()); + for (int i = 0; i < expected.size(); ++i) { + QCOMPARE(QDateTime::fromMSecsSinceEpoch(result.at(i).atMSecsSinceEpoch, eastOneHour), + QDateTime::fromMSecsSinceEpoch(expected.at(i).atMSecsSinceEpoch, eastOneHour)); 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); @@ -1555,10 +1796,13 @@ void tst_QTimeZone::testEpochTranPrivate(const QTimeZonePrivate &tzp) if (!tzp.hasTransitions()) return; // test only viable for transitions + const auto UTC = QTimeZone::UTC; + const auto hour = std::chrono::hours{1}; QTimeZonePrivate::Data tran = tzp.nextTransition(0); // i.e. first after epoch // 1970-04-26 02:00 EST, -5 -> -4 - const QDateTime after = QDateTime(QDate(1970, 4, 26), QTime(2, 0), Qt::OffsetFromUTC, -5 * 3600); - const QDateTime found = QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, Qt::UTC); + const QDateTime after = QDateTime(QDate(1970, 4, 26), QTime(2, 0), + QTimeZone::fromDurationAheadOfUtc(-5 * hour)); + const QDateTime found = QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC); #ifdef USING_WIN_TZ // MS gets the date wrong: 5th April instead of 26th. QCOMPARE(found.toOffsetFromUtc(-5 * 3600).time(), after.time()); #else @@ -1569,16 +1813,16 @@ void tst_QTimeZone::testEpochTranPrivate(const QTimeZonePrivate &tzp) QCOMPARE(tran.daylightTimeOffset, 3600); // Pre-epoch time-zones might not be supported at all: - tran = tzp.nextTransition(QDateTime(QDate(1601, 1, 1), QTime(0, 0), - Qt::UTC).toMSecsSinceEpoch()); + tran = tzp.nextTransition(QDateTime(QDate(1601, 1, 1), QTime(0, 0), UTC).toMSecsSinceEpoch()); if (tran.atMSecsSinceEpoch != QTimeZonePrivate::invalidMSecs() // Toronto *did* have a transition before 1970 (DST since 1918): && tran.atMSecsSinceEpoch < 0) { // ... but, if they are, we should be able to search back to them: tran = tzp.previousTransition(0); // i.e. last before epoch // 1969-10-26 02:00 EDT, -4 -> -5 - QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, Qt::UTC), - QDateTime(QDate(1969, 10, 26), QTime(2, 0), Qt::OffsetFromUTC, -4 * 3600)); + QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC), + QDateTime(QDate(1969, 10, 26), QTime(2, 0), + QTimeZone::fromDurationAheadOfUtc(-4 * hour))); QCOMPARE(tran.offsetFromUtc, -5 * 3600); QCOMPARE(tran.standardTimeOffset, -5 * 3600); QCOMPARE(tran.daylightTimeOffset, 0); @@ -1589,5 +1833,47 @@ void tst_QTimeZone::testEpochTranPrivate(const QTimeZonePrivate &tzp) } #endif // QT_BUILD_INTERNAL +#if __cpp_lib_chrono >= 201907L +Q_DECLARE_METATYPE(const std::chrono::time_zone *); +#endif + +void tst_QTimeZone::stdCompatibility_data() +{ +#if __cpp_lib_chrono >= 201907L + QTest::addColumn<const std::chrono::time_zone *>("timeZone"); + const std::chrono::tzdb &tzdb = std::chrono::get_tzdb(); + qDebug() << "Using tzdb version:" << QByteArrayView(tzdb.version); + + for (const std::chrono::time_zone &zone : tzdb.zones) + QTest::addRow(zone.name().data()) << &zone; +#else + QSKIP("This test requires C++20's <chrono>."); +#endif +} + +void tst_QTimeZone::stdCompatibility() +{ +#if __cpp_lib_chrono >= 201907L + QFETCH(const std::chrono::time_zone *, timeZone); + QByteArrayView zoneName = QByteArrayView(timeZone->name()); + QTimeZone tz = QTimeZone::fromStdTimeZonePtr(timeZone); + if (tz.isValid()) { + QCOMPARE(tz.id(), zoneName); + } else { + // QTBUG-102187: a few timezones reported by tzdb might not be + // recognized by QTimeZone. This happens for instance on Windows, where + // tzdb is using ICU, whose database does not match QTimeZone's. + const bool isKnownUnknown = + !zoneName.contains('/') + || zoneName == "Antarctica/Troll" + || zoneName.startsWith("SystemV/"); + QVERIFY(isKnownUnknown); + } +#else + QSKIP("This test requires C++20's <chrono>."); +#endif +} +#endif // timezone backends + QTEST_APPLESS_MAIN(tst_QTimeZone) #include "tst_qtimezone.moc" |