diff options
Diffstat (limited to 'tests/auto/corelib/time')
-rw-r--r-- | tests/auto/corelib/time/CMakeLists.txt | 7 | ||||
-rw-r--r-- | tests/auto/corelib/time/qcalendar/CMakeLists.txt | 11 | ||||
-rw-r--r-- | tests/auto/corelib/time/qcalendar/tst_qcalendar.cpp | 126 | ||||
-rw-r--r-- | tests/auto/corelib/time/qdate/CMakeLists.txt | 12 | ||||
-rw-r--r-- | tests/auto/corelib/time/qdate/tst_qdate.cpp | 889 | ||||
-rw-r--r-- | tests/auto/corelib/time/qdatetime/CMakeLists.txt | 18 | ||||
-rw-r--r-- | tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp | 2371 | ||||
-rw-r--r-- | tests/auto/corelib/time/qdatetime/tst_qdatetime_mac.mm | 33 | ||||
-rw-r--r-- | tests/auto/corelib/time/qdatetimeparser/CMakeLists.txt | 11 | ||||
-rw-r--r-- | tests/auto/corelib/time/qdatetimeparser/tst_qdatetimeparser.cpp | 91 | ||||
-rw-r--r-- | tests/auto/corelib/time/qtime/CMakeLists.txt | 12 | ||||
-rw-r--r-- | tests/auto/corelib/time/qtime/tst_qtime.cpp | 227 | ||||
-rw-r--r-- | tests/auto/corelib/time/qtimezone/CMakeLists.txt | 14 | ||||
-rw-r--r-- | tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp | 926 | ||||
-rw-r--r-- | tests/auto/corelib/time/qtimezone/tst_qtimezone_darwin.mm | 33 |
15 files changed, 2974 insertions, 1807 deletions
diff --git a/tests/auto/corelib/time/CMakeLists.txt b/tests/auto/corelib/time/CMakeLists.txt index e861a1ac71..f2dfbfa527 100644 --- a/tests/auto/corelib/time/CMakeLists.txt +++ b/tests/auto/corelib/time/CMakeLists.txt @@ -1,10 +1,9 @@ -# Generated from time.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause add_subdirectory(qcalendar) add_subdirectory(qdate) add_subdirectory(qdatetime) add_subdirectory(qdatetimeparser) add_subdirectory(qtime) -if(QT_FEATURE_timezone) - add_subdirectory(qtimezone) -endif() +add_subdirectory(qtimezone) diff --git a/tests/auto/corelib/time/qcalendar/CMakeLists.txt b/tests/auto/corelib/time/qcalendar/CMakeLists.txt index 4a8ab7f846..5b1b2dfc2a 100644 --- a/tests/auto/corelib/time/qcalendar/CMakeLists.txt +++ b/tests/auto/corelib/time/qcalendar/CMakeLists.txt @@ -1,10 +1,19 @@ -# Generated from qcalendar.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qcalendar Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qcalendar LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qcalendar SOURCES tst_qcalendar.cpp + LIBRARIES + Qt::CorePrivate ) diff --git a/tests/auto/corelib/time/qcalendar/tst_qcalendar.cpp b/tests/auto/corelib/time/qcalendar/tst_qcalendar.cpp index b0da158775..61999202d2 100644 --- a/tests/auto/corelib/time/qcalendar/tst_qcalendar.cpp +++ b/tests/auto/corelib/time/qcalendar/tst_qcalendar.cpp @@ -1,34 +1,10 @@ -/**************************************************************************** -** -** Copyright (C) 2020 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) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QTest> #include <QCalendar> +#include <private/qgregoriancalendar_p.h> Q_DECLARE_METATYPE(QCalendar::System) class tst_QCalendar : public QObject @@ -51,8 +27,53 @@ private slots: void properties_data(); void properties(); void aliases(); + + void gregory(); }; +static void checkCenturyResolution(const QCalendar &cal, const QCalendar::YearMonthDay &base) +{ + quint8 weekDayMask = 0; + for (int offset = -7; offset < 8; ++offset) { + const auto probe = QDate(base.year, base.month, base.day, cal).addYears(100 * offset, cal); + const int dow = cal.dayOfWeek(probe); + if (probe.isValid() && dow > 0 && dow < 8) + weekDayMask |= 1 << quint8(dow - 1); + } + for (int j = 1; j < 8; ++j) { + const bool seen = weekDayMask & (1 << quint8(j - 1)); + const QDate check = cal.matchCenturyToWeekday(base, j); + if (check.isValid()) { + const auto parts = cal.partsFromDate(check); + const int dow = cal.dayOfWeek(check); + QCOMPARE(dow, j); + QCOMPARE(parts.day, base.day); + QCOMPARE(parts.month, base.month); + int gap = parts.year - base.year; + if (!cal.hasYearZero() && (parts.year > 0) != (base.year > 0)) + gap += parts.year > 0 ? -1 : +1; + auto report = qScopeGuard([parts, base]() { + qDebug("Wrongly matched year: %d replaced %d", parts.year, base.year); + }); + QCOMPARE(gap % 100, 0); + // We searched 7 centuries each side of base. + if (seen) { + QCOMPARE_LT(gap / 100, 8); + QCOMPARE_GT(gap / 100, -8); + } else { + QVERIFY(gap / 100 >= 8 || gap / 100 <= -8); + } + report.dismiss(); + } else { + auto report = qScopeGuard([j, base]() { + qDebug("Missed dow[%d] for %d/%d/%d", j, base.year, base.month, base.day); + }); + QVERIFY(!seen); + report.dismiss(); + } + } +} + // Support for basic(): void tst_QCalendar::checkYear(const QCalendar &cal, int year, bool normal) { @@ -71,7 +92,7 @@ void tst_QCalendar::checkYear(const QCalendar &cal, int year, bool normal) int sum = 0; const int longest = cal.maximumDaysInMonth(); - for (int i = moons; i > 0; i--) { + for (int i = moons; i > 0; --i) { const int last = cal.daysInMonth(i, year); sum += last; // Valid month has some days and no more than max: @@ -84,6 +105,10 @@ void tst_QCalendar::checkYear(const QCalendar &cal, int year, bool normal) QVERIFY(!cal.isDateValid(year, i, last + 1)); if (normal) // Unspecified year gets same daysInMonth(): QCOMPARE(cal.daysInMonth(i), last); + + checkCenturyResolution(cal, {year, i, (last + 1) / 2}); + if (QTest::currentTestFailed()) + return; } // Months add up to the whole year: QCOMPARE(sum, days); @@ -380,5 +405,50 @@ void tst_QCalendar::aliases() QCOMPARE(QCalendar(QCalendar::System::User).name(), QString()); } +void tst_QCalendar::gregory() +{ + // Test QGregorianCalendar's internal-use methods. + + // Julian day number 0 is in 4713; and reach past the end of four-digit years: + for (int year = -4720; year < 12345; ++year) { + // Test yearStartWeekDay() and yearSharingWeekDays() are consistent with + // dateToJulianDay() and weekDayOfJulian(): + if (!year) // No year zero. + continue; + const auto first = QGregorianCalendar::julianFromParts(year, 1, 1); + QVERIFY2(first, "Only year zero should lack a first day"); + QCOMPARE(QGregorianCalendar::yearStartWeekDay(year), + QGregorianCalendar::weekDayOfJulian(*first)); + const auto last = QGregorianCalendar::julianFromParts(year, 12, 31); + QVERIFY2(last, "Only year zero should lack a last day"); + + const int lastTwo = (year + (year < 0 ? 1 : 0)) % 100 + (year < -1 ? 100 : 0); + const QDate probe(year, lastTwo && lastTwo <= 12 ? lastTwo : 8, + lastTwo <= 31 && lastTwo > 12 ? lastTwo : 17); + const int match = QGregorianCalendar::yearSharingWeekDays(probe); + // A post-epoch year, no later than 2400 (implies four-digit): + QVERIFY(match >= 1970); + QVERIFY(match <= 2400); + // Either that's the year we started with or: + if (match != year) { + // Its last two digits can't be mistaken for month or day: + QVERIFY(match % 100 != probe.month()); + QVERIFY(match % 100 != probe.day()); + // If that wasn't in danger of happening, with year positive, they match lastTwo: + if (year > 0 && lastTwo > 31) + QCOMPARE(match % 100, lastTwo); + // Its first and last days of the year match those of year: + auto day = QGregorianCalendar::julianFromParts(match, 1, 1); + QVERIFY(day); + QCOMPARE(QGregorianCalendar::weekDayOfJulian(*day), + QGregorianCalendar::weekDayOfJulian(*first)); + day = QGregorianCalendar::julianFromParts(match, 12, 31); + QVERIFY(day); + QCOMPARE(QGregorianCalendar::weekDayOfJulian(*day), + QGregorianCalendar::weekDayOfJulian(*last)); + } + } +} + QTEST_APPLESS_MAIN(tst_QCalendar) #include "tst_qcalendar.moc" diff --git a/tests/auto/corelib/time/qdate/CMakeLists.txt b/tests/auto/corelib/time/qdate/CMakeLists.txt index 647be9a6f8..4d0f04a967 100644 --- a/tests/auto/corelib/time/qdate/CMakeLists.txt +++ b/tests/auto/corelib/time/qdate/CMakeLists.txt @@ -1,15 +1,23 @@ -# Generated from qdate.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qdate Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qdate LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qdate SOURCES tst_qdate.cpp DEFINES QT_NO_FOREACH QT_NO_KEYWORDS - PUBLIC_LIBRARIES + LIBRARIES Qt::CorePrivate + Qt::TestPrivate ) diff --git a/tests/auto/corelib/time/qdate/tst_qdate.cpp b/tests/auto/corelib/time/qdate/tst_qdate.cpp index 6f8699bc98..cacdad307f 100644 --- a/tests/auto/corelib/time/qdate/tst_qdate.cpp +++ b/tests/auto/corelib/time/qdate/tst_qdate.cpp @@ -1,37 +1,27 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Copyright (C) 2016 Intel Corporation. -** 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. +// Copyright (C) 2016 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only -#include <private/qglobal_p.h> // for the icu feature test +#include <QDateTime> #include <QTest> + +#include <QLocale> +#include <QMap> #include <QTimeZone> -#include <qdatetime.h> -#include <qlocale.h> + +#include <private/qglobal_p.h> // for the icu feature test +#include <private/qcomparisontesthelper_p.h> +#include <private/qdatetime_p.h> +#if !QT_CONFIG(timezone) +# include <private/qtenvironmentvariables_p.h> // for qTzName() +#endif + +using namespace QtPrivate::DateTimeConstants; +using namespace Qt::StringLiterals; + +#if defined(Q_OS_WIN) && !QT_CONFIG(icu) +# define USING_WIN_TZ +#endif class tst_QDate : public QObject { @@ -56,10 +46,8 @@ private Q_SLOTS: void weekNumber_invalid(); void weekNumber_data(); void weekNumber(); -#if QT_CONFIG(timezone) void startOfDay_endOfDay_data(); void startOfDay_endOfDay(); -#endif void startOfDay_endOfDay_fixed_data(); void startOfDay_endOfDay_fixed(); void startOfDay_endOfDay_bounds(); @@ -71,12 +59,12 @@ private Q_SLOTS: void addYears_data(); void addYears(); void daysTo(); + void orderingCompiles(); void operator_eq_eq_data(); void operator_eq_eq(); - void operator_lt(); - void operator_gt(); - void operator_lt_eq(); - void operator_gt_eq(); + void ordering_data(); + void ordering(); + void ordering_chrono_types(); void operator_insert_extract_data(); void operator_insert_extract(); #if QT_CONFIG(datestring) @@ -102,6 +90,15 @@ private Q_SLOTS: void qdebug() const; private: QDate defDate() const { return QDate(1900, 1, 1); } + + QDate epochDate() const { + using namespace QtPrivate::DateTimeConstants; + Q_ASSERT(JULIAN_DAY_FOR_EPOCH == QDate(1970, 1, 1).toJulianDay()); + return QDate::fromJulianDay(JULIAN_DAY_FOR_EPOCH); + } + + static constexpr qint64 minJd = JulianDayMin; + static constexpr qint64 maxJd = JulianDayMax; QDate invalidDate() const { return QDate(); } }; @@ -112,9 +109,6 @@ void tst_QDate::isNull_data() QTest::addColumn<qint64>("jd"); QTest::addColumn<bool>("null"); - qint64 minJd = Q_INT64_C(-784350574879); - qint64 maxJd = Q_INT64_C( 784354017364); - QTest::newRow("qint64 min") << std::numeric_limits<qint64>::min() << true; QTest::newRow("minJd - 1") << minJd - 1 << true; QTest::newRow("minJd") << minJd << false; @@ -198,6 +192,32 @@ void tst_QDate::isValid_data() QTest::newRow("jd latest formula") << 1400000 << 12 << 31 << qint64(513060925) << true; } +#if __cpp_lib_chrono >= 201907L +// QDate has a bigger range than year_month_date. The tests use this bigger +// range. However building a year_month_time with "out of range" data has +// unspecified results, so don't do that. See [time.cal.year], +// [time.cal.month], [time.cal.day]. Also, std::chrono::year has a year 0, so +// take that into account. +static std::optional<std::chrono::year_month_day> convertToStdYearMonthDay(int y, int m, int d) +{ + using namespace std::chrono; + + if (y >= int((year::min)()) + && y <= int((year::max)()) + && m >= 0 + && m <= 255 + && d >= 0 + && d <= 255) + { + if (y < 0) + ++y; + return std::make_optional(year(y) / m / d); + } + + return std::nullopt; +} +#endif + void tst_QDate::isValid() { QFETCH(int, year); @@ -217,6 +237,19 @@ void tst_QDate::isValid() QCOMPARE(d.year(), year); QCOMPARE(d.month(), month); QCOMPARE(d.day(), day); +#if __cpp_lib_chrono >= 201907L + std::optional<std::chrono::year_month_day> ymd = convertToStdYearMonthDay(year, month, day); + if (ymd) { + QDate d = *ymd; + QCOMPARE(d.year(), year); + QCOMPARE(d.month(), month); + QCOMPARE(d.day(), day); + + const std::chrono::sys_days qdateSysDays = d.toStdSysDays(); + const std::chrono::sys_days ymdSysDays = *ymd; + QCOMPARE(qdateSysDays, ymdSysDays); + } +#endif } else { QCOMPARE(d.year(), 0); QCOMPARE(d.month(), 0); @@ -463,113 +496,178 @@ void tst_QDate::weekNumber_invalid() QCOMPARE( dt.weekNumber( &yearNumber ), 0 ); } -#if QT_CONFIG(timezone) +/* The MS backend tends to lack data for historical transitions. So some of the + transition-based tests will get wrong results, that we can't do anything + about, when using that backend. Rather than complicating the #if-ery more, + overtly record, in a flags column, which we need to ignore and merely make + the testing of these flags subject to #if-ery. + + Android appears to lack at least one other. +*/ +enum BackendKludge { IgnoreStart = 1, IgnoreEnd = 2, }; +Q_DECLARE_FLAGS(BackendKludges, BackendKludge) +Q_DECLARE_OPERATORS_FOR_FLAGS(BackendKludges) + void tst_QDate::startOfDay_endOfDay_data() { QTest::addColumn<QDate>("date"); // Typically a spring-forward. // A zone in which that date's start and end are worth checking: - QTest::addColumn<QByteArray>("zoneName"); + QTest::addColumn<QTimeZone>("zone"); // The start and end times in that zone: QTest::addColumn<QTime>("start"); QTest::addColumn<QTime>("end"); - - const QTime initial(0, 0), final(23, 59, 59, 999), invalid(QDateTime().time()); - - QTest::newRow("epoch") - << QDate(1970, 1, 1) << QByteArray("UTC") - << initial << final; - QTest::newRow("Brazil") - << QDate(2008, 10, 19) << QByteArray("America/Sao_Paulo") - << QTime(1, 0) << final; -#if QT_CONFIG(icu) || !defined(Q_OS_WIN) // MS's TZ APIs lack data - QTest::newRow("Sofia") - << QDate(1994, 3, 27) << QByteArray("Europe/Sofia") - << QTime(1, 0) << final; + // Ignored for backends that don't need it: + QTest::addColumn<BackendKludges>("kludge"); + + const QTime early(0, 0), late(23, 59, 59, 999), invalid(QDateTime().time()); + constexpr BackendKludges Clean = {}; + constexpr BackendKludges IgnoreBoth = IgnoreStart | IgnoreEnd; +#ifdef USING_WIN_TZ + constexpr BackendKludges MsNoStart = IgnoreStart; + constexpr BackendKludges MsNoBoth = IgnoreBoth; +#else + constexpr BackendKludges MsNoStart = Clean; + constexpr BackendKludges MsNoBoth = Clean; + // And use IgnoreBoth directly for the one transition Android lacks. #endif - QTest::newRow("Kiritimati") - << QDate(1994, 12, 31) << QByteArray("Pacific/Kiritimati") - << invalid << invalid; - QTest::newRow("Samoa") - << QDate(2011, 12, 30) << QByteArray("Pacific/Apia") - << invalid << invalid; - // TODO: find other zones with transitions at/crossing midnight. + const QTimeZone UTC(QTimeZone::UTC); + + using Bound = std::numeric_limits<qint64>; + const auto dateAtMillis = [UTC](qint64 millis) { + return QDateTime::fromMSecsSinceEpoch(millis, UTC).date(); + }; + + // UTC and fixed offset are always available and predictable: + QTest::newRow("epoch") << epochDate() << UTC << early << late << Clean; + + // First and last days in QDateTime's supported range: + QTest::newRow("earliest") + << dateAtMillis(Bound::min()) << UTC << invalid << late << Clean; + QTest::newRow("latest") + << dateAtMillis(Bound::max()) << UTC << early << invalid << Clean; + + const struct { + const char *test; + const char *zone; + const QDate day; + const QTime start; + const QTime end; + const BackendKludges msOpt; + } transitions[] = { + // The western Mexico time-zones skipped the first hour of 1970. + { "BajaMexico", "America/Hermosillo", QDate(1970, 1, 1), QTime(1, 0), late, MsNoStart }, + + // Compare tst_QDateTime::fromStringDateFormat(ISO 24:00 in DST). + { "Brazil", "America/Sao_Paulo", QDate(2008, 10, 19), QTime(1, 0), late, Clean }, + + // Several southern zones within EET (but not the northern ones) spent + // part of the 1990s using midnight as spring transition. + { "Sofia", "Europe/Sofia", QDate(1994, 3, 27), QTime(1, 0), late, MsNoStart }, + + // Two Pacific zones skipped days to get on the west of the + // International Date Line; those days have neither start nor end. + { "Kiritimati", "Pacific/Kiritimati", QDate(1994, 12, 31), invalid, invalid, IgnoreBoth }, + { "Samoa", "Pacific/Apia", QDate(2011, 12, 30), invalid, invalid, MsNoBoth }, + + // TODO: find other zones with transitions at/crossing midnight. + }; + const QTimeZone local = QTimeZone::LocalTime; + +#if QT_CONFIG(timezone) + const QTimeZone sys = QTimeZone::systemTimeZone(); + QVERIFY2(sys.isValid(), "Test depends on properly configured system"); + for (const auto &tran : transitions) { + if (QTimeZone zone(tran.zone); zone.isValid()) { + QTest::newRow(tran.test) + << tran.day << zone << tran.start << tran.end << tran.msOpt; + if (zone == sys) { + QTest::addRow("Local=%s", tran.test) + << tran.day << local << tran.start << tran.end << tran.msOpt; + } + } + } +#else + const auto isLocalZone = [](const char *zone) { + const QLatin1StringView name(zone); + for (int i = 0; i < 2; ++i) { + if (qTzName(i) == name) + return true; + } + return false; + }; + for (const auto &tran : transitions) { + if (isLocalZone(tran.zone)) { // Might need a different name to match + QTest::addRow("Local=%s", tran.test) + << tran.day << local << tran.start << tran.end << tran.msOpt; + } + } +#endif // timezone } void tst_QDate::startOfDay_endOfDay() { - QFETCH(QDate, date); - QFETCH(QByteArray, zoneName); - QFETCH(QTime, start); - QFETCH(QTime, end); - const QTimeZone zone(zoneName); - const bool isSystem = QTimeZone::systemTimeZone() == zone; + QFETCH(const QDate, date); + QFETCH(const QTimeZone, zone); + QFETCH(const QTime, start); + QFETCH(const QTime, end); +#if defined(USING_WIN_TZ) || defined(Q_OS_ANDROID) // Coping with backend limitations. + QFETCH(const BackendKludges, kludge); +#define UNLESSKLUDGE(flag) if (!kludge.testFlag(flag)) +#else +#define UNLESSKLUDGE(flag) +#endif + QVERIFY(zone.isValid()); + QDateTime front(date.startOfDay(zone)), back(date.endOfDay(zone)); if (end.isValid()) QCOMPARE(date.addDays(1).startOfDay(zone).addMSecs(-1), back); if (start.isValid()) QCOMPARE(date.addDays(-1).endOfDay(zone).addMSecs(1), front); - do { // Avoids duplicating these tests for local-time when it *is* zone: - if (start.isValid()) { - QCOMPARE(front.date(), date); - QCOMPARE(front.time(), start); - } - if (end.isValid()) { - QCOMPARE(back.date(), date); - QCOMPARE(back.time(), end); - } - if (front.timeSpec() == Qt::LocalTime) - break; - front = date.startOfDay(Qt::LocalTime); - back = date.endOfDay(Qt::LocalTime); - } while (isSystem); - if (end.isValid()) - QCOMPARE(date.addDays(1).startOfDay(Qt::LocalTime).addMSecs(-1), back); - if (start.isValid()) - QCOMPARE(date.addDays(-1).endOfDay(Qt::LocalTime).addMSecs(1), front); - if (!isSystem) { - // These might fail if system zone coincides with zone; but only if it - // did something similarly unusual on the date picked for this test. - if (start.isValid()) { - QCOMPARE(front.date(), date); - QCOMPARE(front.time(), QTime(0, 0)); - } - if (end.isValid()) { - QCOMPARE(back.date(), date); - QCOMPARE(back.time(), QTime(23, 59, 59, 999)); - } + + if (start.isValid()) { + QVERIFY(front.isValid()); + QCOMPARE(front.date(), date); + UNLESSKLUDGE(IgnoreStart) QCOMPARE(front.time(), start); + } else UNLESSKLUDGE(IgnoreStart) { + auto report = qScopeGuard([front]() { qDebug() << "Start of day:" << front; }); + QVERIFY(!front.isValid()); + report.dismiss(); + } + if (end.isValid()) { + QVERIFY(back.isValid()); + QCOMPARE(back.date(), date); + UNLESSKLUDGE(IgnoreEnd) QCOMPARE(back.time(), end); + } else UNLESSKLUDGE(IgnoreEnd) { + auto report = qScopeGuard([back]() { qDebug() << "End of day:" << back; }); + QVERIFY(!back.isValid()); + report.dismiss(); } +#undef UNLESSKLUDGE } -#endif // timezone void tst_QDate::startOfDay_endOfDay_fixed_data() { + QTest::addColumn<QDate>("date"); + const qint64 kilo(1000); using Bounds = std::numeric_limits<qint64>; - const QDateTime - first(QDateTime::fromMSecsSinceEpoch(Bounds::min() + 1, Qt::UTC)), - start32sign(QDateTime::fromMSecsSinceEpoch(-0x80000000L * kilo, Qt::UTC)), - end32sign(QDateTime::fromMSecsSinceEpoch(0x80000000L * kilo, Qt::UTC)), - end32unsign(QDateTime::fromMSecsSinceEpoch(0x100000000L * kilo, Qt::UTC)), - last(QDateTime::fromMSecsSinceEpoch(Bounds::max(), Qt::UTC)); - - const struct { - const char *name; - QDate date; - } data[] = { - { "epoch", QDate(1970, 1, 1) }, - { "y2k-leap-day", QDate(2000, 2, 29) }, - // Just outside the start and end of 32-bit time_t: - { "pre-sign32", QDate(start32sign.date().year(), 1, 1) }, - { "post-sign32", QDate(end32sign.date().year(), 12, 31) }, - { "post-uint32", QDate(end32unsign.date().year(), 12, 31) }, - // Just inside the start and end of QDateTime's range: - { "first-full", first.date().addDays(1) }, - { "last-full", last.date().addDays(-1) } - }; - - QTest::addColumn<QDate>("date"); - for (const auto &r : data) - QTest::newRow(r.name) << r.date; + const auto UTC = QTimeZone::UTC; + const QDateTime first(QDateTime::fromMSecsSinceEpoch(Bounds::min() + 1, UTC)); + const QDateTime start32sign(QDateTime::fromMSecsSinceEpoch(Q_INT64_C(-0x80000000) * kilo, UTC)); + const QDateTime end32sign(QDateTime::fromMSecsSinceEpoch(Q_INT64_C(0x80000000) * kilo, UTC)); + const QDateTime end32unsign(QDateTime::fromMSecsSinceEpoch(Q_INT64_C(0x100000000) * kilo, UTC)); + const QDateTime last(QDateTime::fromMSecsSinceEpoch(Bounds::max(), UTC)); + + QTest::newRow("epoch") << epochDate(); + QTest::newRow("y2k-leap-day") << QDate(2000, 2, 29); + QTest::newRow("start-1900") << QDate(1900, 1, 1); // QTBUG-99747 + // Just outside the start and end of 32-bit time_t: + QTest::newRow("pre-sign32") << QDate(start32sign.date().year(), 1, 1); + QTest::newRow("post-sign32") << QDate(end32sign.date().year(), 12, 31); + QTest::newRow("post-uint32") << QDate(end32unsign.date().year(), 12, 31); + // Just inside the start and end of QDateTime's range: + QTest::newRow("first-full") << first.date().addDays(1); + QTest::newRow("last-full") << last.date().addDays(-1); } void tst_QDate::startOfDay_endOfDay_fixed() @@ -577,56 +675,79 @@ void tst_QDate::startOfDay_endOfDay_fixed() const QTime early(0, 0), late(23, 59, 59, 999); QFETCH(QDate, date); - QDateTime start(date.startOfDay(Qt::UTC)); - QDateTime end(date.endOfDay(Qt::UTC)); + QDateTime start(date.startOfDay(QTimeZone::UTC)); + QDateTime end(date.endOfDay(QTimeZone::UTC)); QCOMPARE(start.date(), date); QCOMPARE(end.date(), date); QCOMPARE(start.time(), early); QCOMPARE(end.time(), late); - QCOMPARE(date.addDays(1).startOfDay(Qt::UTC).addMSecs(-1), end); - QCOMPARE(date.addDays(-1).endOfDay(Qt::UTC).addMSecs(1), start); + QCOMPARE(date.addDays(1).startOfDay(QTimeZone::UTC).addMSecs(-1), end); + QCOMPARE(date.addDays(-1).endOfDay(QTimeZone::UTC).addMSecs(1), start); for (int offset = -60 * 16; offset <= 60 * 16; offset += 65) { - start = date.startOfDay(Qt::OffsetFromUTC, offset); - end = date.endOfDay(Qt::OffsetFromUTC, offset); + const auto zone = QTimeZone::fromSecondsAheadOfUtc(offset); + start = date.startOfDay(zone); + end = date.endOfDay(zone); QCOMPARE(start.date(), date); QCOMPARE(end.date(), date); QCOMPARE(start.time(), early); QCOMPARE(end.time(), late); - QCOMPARE(date.addDays(1).startOfDay(Qt::OffsetFromUTC, offset).addMSecs(-1), end); - QCOMPARE(date.addDays(-1).endOfDay(Qt::OffsetFromUTC, offset).addMSecs(1), start); + QCOMPARE(date.addDays(1).startOfDay(zone).addMSecs(-1), end); + QCOMPARE(date.addDays(-1).endOfDay(zone).addMSecs(1), start); } + + // Minimal testing for LocalTime and TimeZone + QCOMPARE(date.startOfDay().date(), date); + QCOMPARE(date.endOfDay().date(), date); +#if QT_CONFIG(timezone) + const QTimeZone cet("Europe/Oslo"); + if (cet.isValid()) { + QCOMPARE(date.startOfDay(cet).date(), date); + QCOMPARE(date.endOfDay(cet).date(), date); + } +#endif } void tst_QDate::startOfDay_endOfDay_bounds() { // Check the days in which QDateTime's range starts and ends: using Bounds = std::numeric_limits<qint64>; + const auto UTC = QTimeZone::UTC; const QDateTime - first(QDateTime::fromMSecsSinceEpoch(Bounds::min(), Qt::UTC)), - last(QDateTime::fromMSecsSinceEpoch(Bounds::max(), Qt::UTC)), - epoch(QDateTime::fromMSecsSinceEpoch(0, Qt::UTC)); + first(QDateTime::fromMSecsSinceEpoch(Bounds::min(), UTC)), + last(QDateTime::fromMSecsSinceEpoch(Bounds::max(), UTC)), + epoch(QDateTime::fromMSecsSinceEpoch(0, UTC)); // First, check these *are* the start and end of QDateTime's range: QVERIFY(first.isValid()); QVERIFY(last.isValid()); QVERIFY(first < epoch); QVERIFY(last > epoch); - // QDateTime's addMSecs doesn't check against {und,ov}erflow ... QVERIFY(!first.addMSecs(-1).isValid() || first.addMSecs(-1) > first); QVERIFY(!last.addMSecs(1).isValid() || last.addMSecs(1) < last); // Now test start/end methods with them: - QCOMPARE(first.date().endOfDay(Qt::UTC).time(), QTime(23, 59, 59, 999)); - QCOMPARE(last.date().startOfDay(Qt::UTC).time(), QTime(0, 0)); - QVERIFY(!first.date().startOfDay(Qt::UTC).isValid()); - QVERIFY(!last.date().endOfDay(Qt::UTC).isValid()); + QCOMPARE(first.date().endOfDay(UTC).time(), QTime(23, 59, 59, 999)); + QCOMPARE(last.date().startOfDay(UTC).time(), QTime(0, 0)); + QVERIFY(!first.date().startOfDay(UTC).isValid()); + QVERIFY(!last.date().endOfDay(UTC).isValid()); + + // Test for QTBUG-100873, shouldn't assert: + const QDate qdteMin(1752, 9, 14); // Used by QDateTimeEdit + QCOMPARE(qdteMin.startOfDay(UTC).date(), qdteMin); + QCOMPARE(qdteMin.startOfDay().date(), qdteMin); +#if QT_CONFIG(timezone) + const QTimeZone sys = QTimeZone::systemTimeZone(); + QVERIFY2(sys.isValid(), "Test depends on properly configured system"); + QCOMPARE(qdteMin.startOfDay(sys).date(), qdteMin); + QTimeZone berlin("Europe/Berlin"); + if (berlin.isValid()) + QCOMPARE(qdteMin.startOfDay(berlin).date(), qdteMin); +#endif } void tst_QDate::julianDaysLimits() { qint64 min = std::numeric_limits<qint64>::min(); qint64 max = std::numeric_limits<qint64>::max(); - qint64 minJd = Q_INT64_C(-784350574879); - qint64 maxJd = Q_INT64_C( 784354017364); QDate maxDate = QDate::fromJulianDay(maxJd); QDate minDate = QDate::fromJulianDay(minJd); @@ -694,11 +815,19 @@ void tst_QDate::addDays() QFETCH( int, expectedDay ); QDate dt( year, month, day ); - dt = dt.addDays( amountToAdd ); + QDate dt2 = dt.addDays( amountToAdd ); - QCOMPARE( dt.year(), expectedYear ); - QCOMPARE( dt.month(), expectedMonth ); - QCOMPARE( dt.day(), expectedDay ); + QCOMPARE( dt2.year(), expectedYear ); + QCOMPARE( dt2.month(), expectedMonth ); + QCOMPARE( dt2.day(), expectedDay ); + +#if __cpp_lib_chrono >= 201907L + QDate dt3 = dt.addDuration( std::chrono::days( amountToAdd ) ); + + QCOMPARE( dt3.year(), expectedYear ); + QCOMPARE( dt3.month(), expectedMonth ); + QCOMPARE( dt3.day(), expectedDay ); +#endif } void tst_QDate::addDays_data() @@ -841,9 +970,6 @@ void tst_QDate::addYears_data() void tst_QDate::daysTo() { - qint64 minJd = Q_INT64_C(-784350574879); - qint64 maxJd = Q_INT64_C( 784354017364); - QDate dt1(2000, 1, 1); QDate dt2(2000, 1, 5); QCOMPARE(dt1.daysTo(dt2), (qint64) 4); @@ -868,6 +994,17 @@ void tst_QDate::daysTo() QCOMPARE(zeroDate.daysTo(minDate), minJd); } +void tst_QDate::orderingCompiles() +{ + QTestPrivate::testAllComparisonOperatorsCompile<QDate>(); +#if __cpp_lib_chrono >= 201907L + QTestPrivate::testAllComparisonOperatorsCompile<QDate, std::chrono::year_month_day>(); + QTestPrivate::testAllComparisonOperatorsCompile<QDate, std::chrono::year_month_day_last>(); + QTestPrivate::testAllComparisonOperatorsCompile<QDate, std::chrono::year_month_weekday>(); + QTestPrivate::testAllComparisonOperatorsCompile<QDate, std::chrono::year_month_weekday_last>(); +#endif +} + void tst_QDate::operator_eq_eq_data() { QTest::addColumn<QDate>("d1"); @@ -904,137 +1041,83 @@ void tst_QDate::operator_eq_eq() QFETCH(QDate, d2); QFETCH(bool, expectEqual); - bool equal = d1 == d2; - QCOMPARE(equal, expectEqual); - bool notEqual = d1 != d2; - QCOMPARE(notEqual, !expectEqual); + QT_TEST_EQUALITY_OPS(d1, d2, expectEqual); - if (equal) + if (expectEqual) QVERIFY(qHash(d1) == qHash(d2)); } -void tst_QDate::operator_lt() +void tst_QDate::ordering_data() { - QDate d1(2000,1,2); - QDate d2(2000,1,2); - QVERIFY( !(d1 < d2) ); - - d1 = QDate(2001,12,4); - d2 = QDate(2001,12,5); - QVERIFY( d1 < d2 ); - - d1 = QDate(2001,11,5); - d2 = QDate(2001,12,5); - QVERIFY( d1 < d2 ); - - d1 = QDate(2000,12,5); - d2 = QDate(2001,12,5); - QVERIFY( d1 < d2 ); - - d1 = QDate(2002,12,5); - d2 = QDate(2001,12,5); - QVERIFY( !(d1 < d2) ); - - d1 = QDate(2001,12,5); - d2 = QDate(2001,11,5); - QVERIFY( !(d1 < d2) ); - - d1 = QDate(2001,12,6); - d2 = QDate(2001,12,5); - QVERIFY( !(d1 < d2) ); + QTest::addColumn<QDate>("left"); + QTest::addColumn<QDate>("right"); + QTest::addColumn<Qt::strong_ordering>("expectedOrdering"); + + QTest::newRow("2000-1-2_vs_2000-1-2") + << QDate(2000, 1, 2) << QDate(2000, 1, 2) << Qt::strong_ordering::equivalent; + QTest::newRow("2001-12-4_vs_2001-12-5") + << QDate(2001, 12, 4) << QDate(2001, 12, 5) << Qt::strong_ordering::less; + QTest::newRow("2001-11-5_vs_2001-12-5") + << QDate(2001, 11, 5) << QDate(2001, 12, 5) << Qt::strong_ordering::less; + QTest::newRow("2000-12-5_vs_2001-12-5") + << QDate(2000, 12, 5) << QDate(2001, 12, 5) << Qt::strong_ordering::less; + QTest::newRow("2002-12-5_vs_2001-12-5") + << QDate(2002, 12, 5) << QDate(2001, 12, 5) << Qt::strong_ordering::greater; + QTest::newRow("2001-12-5_vs_2001-11-5") + << QDate(2001, 12, 5) << QDate(2001, 11, 5) << Qt::strong_ordering::greater; + QTest::newRow("2001-12-6_vs_2001-12-5") + << QDate(2001, 12, 6) << QDate(2001, 12, 5) << Qt::strong_ordering::greater; } -void tst_QDate::operator_gt() +void tst_QDate::ordering() { - QDate d1(2000,1,2); - QDate d2(2000,1,2); - QVERIFY( !(d1 > d2) ); - - d1 = QDate(2001,12,4); - d2 = QDate(2001,12,5); - QVERIFY( !(d1 > d2) ); - - d1 = QDate(2001,11,5); - d2 = QDate(2001,12,5); - QVERIFY( !(d1 > d2) ); - - d1 = QDate(2000,12,5); - d2 = QDate(2001,12,5); - QVERIFY( !(d1 > d2) ); + QFETCH(QDate, left); + QFETCH(QDate, right); + QFETCH(Qt::strong_ordering, expectedOrdering); - d1 = QDate(2002,12,5); - d2 = QDate(2001,12,5); - QVERIFY( d1 > d2 ); - - d1 = QDate(2001,12,5); - d2 = QDate(2001,11,5); - QVERIFY( d1 > d2 ); - - d1 = QDate(2001,12,6); - d2 = QDate(2001,12,5); - QVERIFY( d1 > d2 ); + QT_TEST_ALL_COMPARISON_OPS(left, right, expectedOrdering); } -void tst_QDate::operator_lt_eq() +void tst_QDate::ordering_chrono_types() { - QDate d1(2000,1,2); - QDate d2(2000,1,2); - QVERIFY( d1 <= d2 ); - - d1 = QDate(2001,12,4); - d2 = QDate(2001,12,5); - QVERIFY( d1 <= d2 ); - - d1 = QDate(2001,11,5); - d2 = QDate(2001,12,5); - QVERIFY( d1 <= d2 ); - - d1 = QDate(2000,12,5); - d2 = QDate(2001,12,5); - QVERIFY( d1 <= d2 ); - - d1 = QDate(2002,12,5); - d2 = QDate(2001,12,5); - QVERIFY( !(d1 <= d2) ); - - d1 = QDate(2001,12,5); - d2 = QDate(2001,11,5); - QVERIFY( !(d1 <= d2) ); - - d1 = QDate(2001,12,6); - d2 = QDate(2001,12,5); - QVERIFY( !(d1 <= d2) ); -} - -void tst_QDate::operator_gt_eq() -{ - QDate d1(2000,1,2); - QDate d2(2000,1,2); - QVERIFY( d1 >= d2 ); - - d1 = QDate(2001,12,4); - d2 = QDate(2001,12,5); - QVERIFY( !(d1 >= d2) ); - - d1 = QDate(2001,11,5); - d2 = QDate(2001,12,5); - QVERIFY( !(d1 >= d2) ); - - d1 = QDate(2000,12,5); - d2 = QDate(2001,12,5); - QVERIFY( !(d1 >= d2) ); - - d1 = QDate(2002,12,5); - d2 = QDate(2001,12,5); - QVERIFY( d1 >= d2 ); - - d1 = QDate(2001,12,5); - d2 = QDate(2001,11,5); - QVERIFY( d1 >= d2 ); - - d1 = QDate(2001,12,6); - d2 = QDate(2001,12,5); - QVERIFY( d1 >= d2 ); +#if __cpp_lib_chrono >= 201907L + using namespace std::chrono; + QDate friday(2001, 11, 30); // the 5th Friday of November 2001 + // std::chrono::year_month_day + QT_TEST_ALL_COMPARISON_OPS(friday, year_month_day(2001y, November, 29d), + Qt::strong_ordering::greater); + QT_TEST_ALL_COMPARISON_OPS(friday, year_month_day(2001y, November, 30d), + Qt::strong_ordering::equivalent); + QT_TEST_ALL_COMPARISON_OPS(friday, year_month_day(2001y, December, 1d), + Qt::strong_ordering::less); + + // std::chrono::year_month_day_last + QT_TEST_ALL_COMPARISON_OPS(friday, year_month_day_last(2001y, {October / last}), + Qt::strong_ordering::greater); + QT_TEST_ALL_COMPARISON_OPS(friday, year_month_day_last(2001y, {November / last}), + Qt::strong_ordering::equivalent); + QT_TEST_ALL_COMPARISON_OPS(friday, year_month_day_last(2001y, {December / last}), + Qt::strong_ordering::less); + + // std::chrono::year_month_weekday + QT_TEST_ALL_COMPARISON_OPS(friday, year_month_weekday(2001y, November, Thursday[5]), + Qt::strong_ordering::greater); + QT_TEST_ALL_COMPARISON_OPS(friday, year_month_weekday(2001y, November, Friday[5]), + Qt::strong_ordering::equivalent); + QT_TEST_ALL_COMPARISON_OPS(friday, year_month_weekday(2001y, December, Saturday[1]), + Qt::strong_ordering::less); + + // std::chrono::year_month_weekday_last + QDate thursday(2001, 11, 29); // the last Thursday of November 2001 + QT_TEST_ALL_COMPARISON_OPS(thursday, year_month_weekday_last(2001y, November, Wednesday[last]), + Qt::strong_ordering::greater); + QT_TEST_ALL_COMPARISON_OPS(thursday, year_month_weekday_last(2001y, November, Thursday[last]), + Qt::strong_ordering::equivalent); + QT_TEST_ALL_COMPARISON_OPS(thursday, year_month_weekday_last(2001y, November, Friday[last]), + Qt::strong_ordering::less); +#else + QSKIP("This test requires C++20-level <chrono> support enabled in the standard library."); +#endif // __cpp_lib_chrono >= 201907L } Q_DECLARE_METATYPE(QDataStream::Version) @@ -1138,13 +1221,13 @@ void tst_QDate::fromStringDateFormat_data() << QString::fromLatin1(" 13 Feb 1987 13:24:51 +0100") << Qt::RFC2822Date << QDate(1987, 2, 13); QTest::newRow("RFC 2822 with day") << QString::fromLatin1("Thu, 01 Jan 1970 00:12:34 +0000") - << Qt::RFC2822Date << QDate(1970, 1, 1); + << Qt::RFC2822Date << epochDate(); QTest::newRow("RFC 2822 with day after space") << QString::fromLatin1(" Thu, 01 Jan 1970 00:12:34 +0000") - << Qt::RFC2822Date << QDate(1970, 1, 1); + << Qt::RFC2822Date << epochDate(); // No timezone QTest::newRow("RFC 2822 no timezone") << QString::fromLatin1("01 Jan 1970 00:12:34") - << Qt::RFC2822Date << QDate(1970, 1, 1); + << Qt::RFC2822Date << epochDate(); // No time specified QTest::newRow("RFC 2822 date only") << QString::fromLatin1("01 Nov 2002") << Qt::RFC2822Date << QDate(2002, 11, 1); @@ -1183,7 +1266,7 @@ void tst_QDate::fromStringDateFormat_data() << Qt::RFC2822Date << QDate(1987, 2, 13); // No timezone QTest::newRow("RFC 850 and 1036 no timezone") << QString::fromLatin1("Thu Jan 01 00:12:34 1970") - << Qt::RFC2822Date << QDate(1970, 1, 1); + << Qt::RFC2822Date << epochDate(); // No time specified QTest::newRow("RFC 850 and 1036 date only") << QString::fromLatin1("Fri Nov 01 2002") << Qt::RFC2822Date << QDate(2002, 11, 1); @@ -1224,152 +1307,172 @@ void tst_QDate::fromStringFormat_data() { QTest::addColumn<QString>("string"); QTest::addColumn<QString>("format"); + QTest::addColumn<int>("baseYear"); QTest::addColumn<QDate>("expected"); - // Get names: - const QString january = QStringLiteral("January"); - const QString february = QStringLiteral("February"); - const QString march = QStringLiteral("March"); - const QString august = QStringLiteral("August"); - const QString mon = QStringLiteral("Mon"); - const QString monday = QStringLiteral("Monday"); - const QString tuesday = QStringLiteral("Tuesday"); - const QString wednesday = QStringLiteral("Wednesday"); - const QString thursday = QStringLiteral("Thursday"); - const QString friday = QStringLiteral("Friday"); - const QString saturday = QStringLiteral("Saturday"); - const QString sunday = QStringLiteral("Sunday"); - - QTest::newRow("data0") << QString("") << QString("") << defDate(); - QTest::newRow("data1") << QString(" ") << QString("") << invalidDate(); - QTest::newRow("data2") << QString(" ") << QString(" ") << defDate(); - QTest::newRow("data3") << QString("-%$%#") << QString("$*(#@") << invalidDate(); - QTest::newRow("data4") << QString("d") << QString("'d'") << defDate(); - QTest::newRow("data5") << QString("101010") << QString("dMyy") << QDate(1910, 10, 10); - QTest::newRow("data6") << QString("101010b") << QString("dMyy") << invalidDate(); - QTest::newRow("data7") << january << QString("MMMM") << defDate(); - QTest::newRow("data8") << QString("ball") << QString("balle") << invalidDate(); - QTest::newRow("data9") << QString("balleh") << QString("balleh") << defDate(); - QTest::newRow("data10") << QString("10.01.1") << QString("M.dd.d") << QDate(defDate().year(), 10, 1); - QTest::newRow("data11") << QString("-1.01.1") << QString("M.dd.d") << invalidDate(); - QTest::newRow("data12") << QString("11010") << QString("dMMyy") << invalidDate(); - QTest::newRow("data13") << QString("-2") << QString("d") << invalidDate(); - QTest::newRow("data14") << QString("132") << QString("Md") << invalidDate(); - QTest::newRow("data15") << february << QString("MMMM") << QDate(defDate().year(), 2, 1); - - QString date = mon + QLatin1Char(' ') + august + " 8 2005"; - QTest::newRow("data16") << date << QString("ddd MMMM d yyyy") << QDate(2005, 8, 8); - QTest::newRow("data17") << QString("2000:00") << QString("yyyy:yy") << QDate(2000, 1, 1); - QTest::newRow("data18") << QString("1999:99") << QString("yyyy:yy") << QDate(1999, 1, 1); - QTest::newRow("data19") << QString("2099:99") << QString("yyyy:yy") << QDate(2099, 1, 1); - QTest::newRow("data20") << QString("2001:01") << QString("yyyy:yy") << QDate(2001, 1, 1); - QTest::newRow("data21") << QString("99") << QString("yy") << QDate(1999, 1, 1); - QTest::newRow("data22") << QString("01") << QString("yy") << QDate(1901, 1, 1); - - QTest::newRow("data23") << monday << QString("dddd") << QDate(1900, 1, 1); - QTest::newRow("data24") << tuesday << QString("dddd") << QDate(1900, 1, 2); - QTest::newRow("data25") << wednesday << QString("dddd") << QDate(1900, 1, 3); - QTest::newRow("data26") << thursday << QString("dddd") << QDate(1900, 1, 4); - QTest::newRow("data27") << friday << QString("dddd") << QDate(1900, 1, 5); - QTest::newRow("data28") << saturday << QString("dddd") << QDate(1900, 1, 6); - QTest::newRow("data29") << sunday << QString("dddd") << QDate(1900, 1, 7); - - QTest::newRow("data30") << monday + " 2006" << QString("dddd yyyy") << QDate(2006, 1, 2); - QTest::newRow("data31") << tuesday + " 2006" << QString("dddd yyyy") << QDate(2006, 1, 3); - QTest::newRow("data32") << wednesday + " 2006" << QString("dddd yyyy") << QDate(2006, 1, 4); - QTest::newRow("data33") << thursday + " 2006" << QString("dddd yyyy") << QDate(2006, 1, 5); - QTest::newRow("data34") << friday + " 2006" << QString("dddd yyyy") << QDate(2006, 1, 6); - QTest::newRow("data35") << saturday + " 2006" << QString("dddd yyyy") << QDate(2006, 1, 7); - QTest::newRow("data36") << sunday + " 2006" << QString("dddd yyyy") << QDate(2006, 1, 1); - - QTest::newRow("data37") << tuesday + " 2007 " + march << QString("dddd yyyy MMMM") << QDate(2007, 3, 6); - - QTest::newRow("data38") << QString("21052006") << QString("ddMMyyyy") << QDate(2006,5,21); - QTest::newRow("data39") << QString("210506") << QString("ddMMyy") << QDate(1906,5,21); - QTest::newRow("data40") << QString("21/5/2006") << QString("d/M/yyyy") << QDate(2006,5,21); - QTest::newRow("data41") << QString("21/5/06") << QString("d/M/yy") << QDate(1906,5,21); - QTest::newRow("data42") << QString("20060521") << QString("yyyyMMdd") << QDate(2006,5,21); - QTest::newRow("data43") << QString("060521") << QString("yyMMdd") << QDate(1906,5,21); - QTest::newRow("lateMarch") << QString("9999-03-06") << QString("yyyy-MM-dd") << QDate(9999, 3, 6); - QTest::newRow("late") << QString("9999-12-31") << QString("yyyy-MM-dd") << QDate(9999, 12, 31); + QTest::newRow("empty") << u""_s << u""_s << 1900 << defDate(); + QTest::newRow("space-as-empty") << u" "_s << u""_s << 1900 << invalidDate(); + QTest::newRow("space") << u" "_s << u" "_s << 1900 << defDate(); + QTest::newRow("mispunc") << u"-%$%#"_s << u"$*(#@"_s << 1900 << invalidDate(); + QTest::newRow("literal-d") << u"d"_s << u"'d'"_s << 1900 << defDate(); + QTest::newRow("greedy") << u"101010"_s << u"dMyy"_s << 1900 << QDate(1910, 10, 10); + QTest::newRow("greedy-miss") << u"101010b"_s << u"dMyy"_s << 1900 << invalidDate(); + QTest::newRow("January") << u"January"_s << u"MMMM"_s << 1900 << defDate(); + QTest::newRow("mistext") << u"ball"_s << u"balle"_s << 1900 << invalidDate(); + QTest::newRow("text") << u"balleh"_s << u"balleh"_s << 1900 << defDate(); + QTest::newRow("yearless:19") << u"10.01.1"_s << u"M.dd.d"_s << 1900 << QDate(1900, 10, 1); + QTest::newRow("yearless:20") << u"10.01.1"_s << u"M.dd.d"_s << 2000 << QDate(2000, 10, 1); + QTest::newRow("neg-month") << u"-1.01.1"_s << u"M.dd.d"_s << 1900 << invalidDate(); + QTest::newRow("greedy-break") << u"11010"_s << u"dMMyy"_s << 1900 << invalidDate(); + QTest::newRow("neg-day") << u"-2"_s << u"d"_s << 1900 << invalidDate(); + QTest::newRow("Md:132") << u"132"_s << u"Md"_s << 1900 << invalidDate(); + QTest::newRow("February") << u"February"_s << u"MMMM"_s << 1900 << QDate(1900, 2, 1); + + QTest::newRow("mon-aug-8th") + << u"Mon August 8 2005"_s << u"ddd MMMM d yyyy"_s << 1900 << QDate(2005, 8, 8); + QTest::newRow("year-match-20000") << u"2000:00"_s << u"yyyy:yy"_s << 1900 << QDate(2000, 1, 1); + QTest::newRow("year-match-1999") << u"1999:99"_s << u"yyyy:yy"_s << 1900 << QDate(1999, 1, 1); + QTest::newRow("year-match-2099") << u"2099:99"_s << u"yyyy:yy"_s << 1900 << QDate(2099, 1, 1); + QTest::newRow("year-match-2001") << u"2001:01"_s << u"yyyy:yy"_s << 1900 << QDate(2001, 1, 1); + QTest::newRow("just-yy-1999") << u"99"_s << u"yy"_s << 1900 << QDate(1999, 1, 1); + QTest::newRow("just-yy-1901") << u"01"_s << u"yy"_s << 1900 << QDate(1901, 1, 1); + QTest::newRow("just-yy-2001") << u"01"_s << u"yy"_s << 1970 << QDate(2001, 1, 1); + + QTest::newRow("Monday") << u"Monday"_s << u"dddd"_s << 1900 << QDate(1900, 1, 1); + QTest::newRow("Tuesday") << u"Tuesday"_s << u"dddd"_s << 1900 << QDate(1900, 1, 2); + QTest::newRow("Wednesday") << u"Wednesday"_s << u"dddd"_s << 1900 << QDate(1900, 1, 3); + QTest::newRow("Thursday") << u"Thursday"_s << u"dddd"_s << 1900 << QDate(1900, 1, 4); + QTest::newRow("Friday") << u"Friday"_s << u"dddd"_s << 1900 << QDate(1900, 1, 5); + QTest::newRow("Saturday") << u"Saturday"_s << u"dddd"_s << 1900 << QDate(1900, 1, 6); + QTest::newRow("Sunday") << u"Sunday"_s << u"dddd"_s << 1900 << QDate(1900, 1, 7); + + QTest::newRow("Mon06") << u"Monday 2006"_s << u"dddd yyyy"_s << 1900 << QDate(2006, 1, 2); + QTest::newRow("Tues06") << u"Tuesday 2006"_s << u"dddd yyyy"_s << 1900 << QDate(2006, 1, 3); + QTest::newRow("Wed06") << u"Wednesday 2006"_s << u"dddd yyyy"_s << 1900 << QDate(2006, 1, 4); + QTest::newRow("Thu06") << u"Thursday 2006"_s << u"dddd yyyy"_s << 1900 << QDate(2006, 1, 5); + QTest::newRow("Fri06") << u"Friday 2006"_s << u"dddd yyyy"_s << 1900 << QDate(2006, 1, 6); + QTest::newRow("Sat06") << u"Saturday 2006"_s << u"dddd yyyy"_s << 1900 << QDate(2006, 1, 7); + QTest::newRow("Sun06") << u"Sunday 2006"_s << u"dddd yyyy"_s << 1900 << QDate(2006, 1, 1); + QTest::newRow("Tue07Mar") + << u"Tuesday 2007 March"_s << u"dddd yyyy MMMM"_s << 1900 << QDate(2007, 3, 6); + + QTest::newRow("21May2006") + << u"21052006"_s << u"ddMMyyyy"_s << 1900 << QDate(2006, 5, 21); + QTest::newRow("21May06:19") + << u"210506"_s << u"ddMMyy"_s << 1900 << QDate(1906, 5, 21); + QTest::newRow("21May06:20") + << u"210506"_s << u"ddMMyy"_s << 1970 << QDate(2006, 5, 21); + QTest::newRow("21/May/2006") + << u"21/5/2006"_s << u"d/M/yyyy"_s << 1900 << QDate(2006, 5, 21); + QTest::newRow("21/5/06") + << u"21/5/06"_s << u"d/M/yy"_s << 1900 << QDate(1906, 5, 21); + QTest::newRow("21/5/06:19") + << u"21/5/06"_s << u"d/M/yy"_s << 1900 << QDate(1906, 5, 21); + QTest::newRow("21/5/06:20") + << u"21/5/06"_s << u"d/M/yy"_s << 1910 << QDate(2006, 5, 21); + QTest::newRow("2006May21") + << u"20060521"_s << u"yyyyMMdd"_s << 1900 << QDate(2006, 5, 21); + QTest::newRow("06May21:19") + << u"060521"_s << u"yyMMdd"_s << 1900 << QDate(1906, 5, 21); + QTest::newRow("06May21:20") + << u"060521"_s << u"yyMMdd"_s << 1907 << QDate(2006, 5, 21); + QTest::newRow("lateMarch") + << u"9999-03-06"_s << u"yyyy-MM-dd"_s << 1900 << QDate(9999, 3, 6); + QTest::newRow("late") + << u"9999-12-31"_s << u"yyyy-MM-dd"_s << 1900 << QDate(9999, 12, 31); + + QTest::newRow("quoted-dd") + << u"21dd-05-2006"_s << u"dd'dd'-MM-yyyy"_s << 1900 << QDate(2006, 5, 21); + QTest::newRow("quoted-MM") + << u"21-MM05-2006"_s << u"dd-'MM'MM-yyyy"_s << 1900 << QDate(2006, 5, 21); + QTest::newRow("quotes-empty") + << u"21-'05-2006"_s << u"dd-MM-''yy"_s << 1900 << QDate(2006, 5, 21); // Test unicode handling. QTest::newRow("Unicode in format string") - << QString(u8"2020🤣09🤣21") << QString(u8"yyyy🤣MM🤣dd") << QDate(2020, 9, 21); - QTest::newRow("Unicode in quoted format string") - << QString(u8"🤣🤣2020👍09🤣21") << QString(u8"'🤣🤣'yyyy👍MM🤣dd") << QDate(2020, 9, 21); + << QString(u8"2020🤣09🤣21") << QString(u8"yyyy🤣MM🤣dd") << 1900 << QDate(2020, 9, 21); + QTest::newRow("Unicode-in-format-string-quoted-emoji") + << QString(u8"🤣🤣2020👍09🤣21") << QString(u8"'🤣🤣'yyyy👍MM🤣dd") << 1900 + << QDate(2020, 9, 21); + QTest::newRow("Unicode-in-quoted-dd-format-string") + << QString(u8"🤣🤣2020👍09🤣21dd") << QString(u8"🤣🤣yyyy👍MM🤣dd'dd'") << 1900 + << QDate(2020, 9, 21); + QTest::newRow("Unicode-in-all-formats-quoted-string") + << QString(u8"🤣🤣yyyy2020👍MM09🤣21dd") << QString(u8"🤣🤣'yyyy'yyyy👍'MM'MM🤣dd'dd'") + << 1900 << QDate(2020, 9, 21); // QTBUG-84334 QTest::newRow("-ve year: front, nosep") - << QString("-20060521") << QString("yyyyMMdd") << QDate(-2006, 5, 21); + << u"-20060521"_s << u"yyyyMMdd"_s << 1900 << QDate(-2006, 5, 21); QTest::newRow("-ve year: mid, nosep") - << QString("05-200621") << QString("MMyyyydd") << QDate(-2006, 5, 21); + << u"05-200621"_s << u"MMyyyydd"_s << 1900 << QDate(-2006, 5, 21); QTest::newRow("-ve year: back, nosep") - << QString("0521-2006") << QString("MMddyyyy") << QDate(-2006, 5, 21); + << u"0521-2006"_s << u"MMddyyyy"_s << 1900 << QDate(-2006, 5, 21); // - as separator should not interfere with negative year numbers: QTest::newRow("-ve year: front, dash") - << QString("-2006-05-21") << QString("yyyy-MM-dd") << QDate(-2006, 5, 21); + << u"-2006-05-21"_s << u"yyyy-MM-dd"_s << 1900 << QDate(-2006, 5, 21); QTest::newRow("positive year: front, dash") - << QString("-2006-05-21") << QString("-yyyy-MM-dd") << QDate(2006, 5, 21); + << u"-2006-05-21"_s << u"-yyyy-MM-dd"_s << 1900 << QDate(2006, 5, 21); QTest::newRow("-ve year: mid, dash") - << QString("05--2006-21") << QString("MM-yyyy-dd") << QDate(-2006, 5, 21); + << u"05--2006-21"_s << u"MM-yyyy-dd"_s << 1900 << QDate(-2006, 5, 21); QTest::newRow("-ve year: back, dash") - << QString("05-21--2006") << QString("MM-dd-yyyy") << QDate(-2006, 5, 21); + << u"05-21--2006"_s << u"MM-dd-yyyy"_s << 1900 << QDate(-2006, 5, 21); // negative three digit year numbers should be rejected: QTest::newRow("-ve 3digit year: front") - << QString("-206-05-21") << QString("yyyy-MM-dd") << QDate(); + << u"-206-05-21"_s << u"yyyy-MM-dd"_s << 1900 << QDate(); QTest::newRow("-ve 3digit year: mid") - << QString("05--206-21") << QString("MM-yyyy-dd") << QDate(); + << u"05--206-21"_s << u"MM-yyyy-dd"_s << 1900 << QDate(); QTest::newRow("-ve 3digit year: back") - << QString("05-21--206") << QString("MM-dd-yyyy") << QDate(); + << u"05-21--206"_s << u"MM-dd-yyyy"_s << 1900 << QDate(); // negative month numbers should be rejected: QTest::newRow("-ve 2digit month: mid") - << QString("2060--05-21") << QString("yyyy-MM-dd") << QDate(); + << u"2060--05-21"_s << u"yyyy-MM-dd"_s << 1900 << QDate(); QTest::newRow("-ve 2digit month: front") - << QString("-05-2060-21") << QString("MM-yyyy-dd") << QDate(); + << u"-05-2060-21"_s << u"MM-yyyy-dd"_s << 1900 << QDate(); QTest::newRow("-ve 2digit month: back") - << QString("21-2060--05") << QString("dd-yyyy-MM") << QDate(); + << u"21-2060--05"_s << u"dd-yyyy-MM"_s << 1900 << QDate(); // negative single digit month numbers should be rejected: QTest::newRow("-ve 1digit month: mid") - << QString("2060--5-21") << QString("yyyy-MM-dd") << QDate(); + << u"2060--5-21"_s << u"yyyy-MM-dd"_s << 1900 << QDate(); QTest::newRow("-ve 1digit month: front") - << QString("-5-2060-21") << QString("MM-yyyy-dd") << QDate(); + << u"-5-2060-21"_s << u"MM-yyyy-dd"_s << 1900 << QDate(); QTest::newRow("-ve 1digit month: back") - << QString("21-2060--5") << QString("dd-yyyy-MM") << QDate(); + << u"21-2060--5"_s << u"dd-yyyy-MM"_s << 1900 << QDate(); // negative day numbers should be rejected: QTest::newRow("-ve 2digit day: front") - << QString("-21-2060-05") << QString("dd-yyyy-MM") << QDate(); + << u"-21-2060-05"_s << u"dd-yyyy-MM"_s << 1900 << QDate(); QTest::newRow("-ve 2digit day: mid") - << QString("2060--21-05") << QString("yyyy-dd-MM") << QDate(); + << u"2060--21-05"_s << u"yyyy-dd-MM"_s << 1900 << QDate(); QTest::newRow("-ve 2digit day: back") - << QString("05-2060--21") << QString("MM-yyyy-dd") << QDate(); + << u"05-2060--21"_s << u"MM-yyyy-dd"_s << 1900 << QDate(); // negative single digit day numbers should be rejected: QTest::newRow("-ve 1digit day: front") - << QString("-2-2060-05") << QString("dd-yyyy-MM") << QDate(); + << u"-2-2060-05"_s << u"dd-yyyy-MM"_s << 1900 << QDate(); QTest::newRow("-ve 1digit day: mid") - << QString("05--2-2060") << QString("MM-dd-yyyy") << QDate(); + << u"05--2-2060"_s << u"MM-dd-yyyy"_s << 1900 << QDate(); QTest::newRow("-ve 1digit day: back") - << QString("2060-05--2") << QString("yyyy-MM-dd") << QDate(); + << u"2060-05--2"_s << u"yyyy-MM-dd"_s << 1900 << QDate(); // positive three digit year numbers should be rejected: - QTest::newRow("3digit year, front") << QString("206-05-21") << QString("yyyy-MM-dd") << QDate(); - QTest::newRow("3digit year, mid") << QString("05-206-21") << QString("MM-yyyy-dd") << QDate(); - QTest::newRow("3digit year, back") << QString("05-21-206") << QString("MM-dd-yyyy") << QDate(); + QTest::newRow("3digit year, front") << u"206-05-21"_s << u"yyyy-MM-dd"_s << 1900 << QDate(); + QTest::newRow("3digit year, mid") << u"05-206-21"_s << u"MM-yyyy-dd"_s << 1900 << QDate(); + QTest::newRow("3digit year, back") << u"05-21-206"_s << u"MM-dd-yyyy"_s << 1900 << QDate(); // positive five digit year numbers should be rejected: QTest::newRow("5digit year, front") - << QString("00206-05-21") << QString("yyyy-MM-dd") << QDate(); - QTest::newRow("5digit year, mid") << QString("05-00206-21") << QString("MM-yyyy-dd") << QDate(); + << u"00206-05-21"_s << u"yyyy-MM-dd"_s << 1900 << QDate(); + QTest::newRow("5digit year, mid") + << u"05-00206-21"_s << u"MM-yyyy-dd"_s << 1900 << QDate(); QTest::newRow("5digit year, back") - << QString("05-21-00206") << QString("MM-dd-yyyy") << QDate(); + << u"05-21-00206"_s << u"MM-dd-yyyy"_s << 1900 << QDate(); QTest::newRow("dash separator, no year at end") - << QString("05-21-") << QString("dd-MM-yyyy") << QDate(); + << u"05-21-"_s << u"dd-MM-yyyy"_s << 1900 << QDate(); QTest::newRow("slash separator, no year at end") - << QString("11/05/") << QString("d/MM/yyyy") << QDate(); + << u"11/05/"_s << u"d/MM/yyyy"_s << 1900 << QDate(); // QTBUG-84349 - QTest::newRow("+ sign in year field") << QString("+0200322") << QString("yyyyMMdd") << QDate(); - QTest::newRow("+ sign in month field") << QString("2020+322") << QString("yyyyMMdd") << QDate(); - QTest::newRow("+ sign in day field") << QString("202003+1") << QString("yyyyMMdd") << QDate(); + QTest::newRow("+ sign in year field") << u"+0200322"_s << u"yyyyMMdd"_s << 1900 << QDate(); + QTest::newRow("+ sign in month field") << u"2020+322"_s << u"yyyyMMdd"_s << 1900 << QDate(); + QTest::newRow("+ sign in day field") << u"202003+1"_s << u"yyyyMMdd"_s << 1900 << QDate(); } @@ -1377,9 +1480,12 @@ void tst_QDate::fromStringFormat() { QFETCH(QString, string); QFETCH(QString, format); + QFETCH(int, baseYear); QFETCH(QDate, expected); - QDate dt = QDate::fromString(string, format); + QDate dt = QDate::fromString(string, format, baseYear); + QEXPECT_FAIL("quotes-empty", "QTBUG-110669: doubled single-quotes in format mishandled", + Continue); QCOMPARE(dt, expected); } #endif // datetimeparser @@ -1592,9 +1698,6 @@ void tst_QDate::roundtrip() const loopDate = loopDate.addDays(1); } - qint64 minJd = Q_INT64_C(-784350574879); - qint64 maxJd = Q_INT64_C( 784354017364); - // Test Gregorian round trip at top end of conversion range loopDate = QDate::fromJulianDay(maxJd); while (loopDate.toJulianDay() >= maxJd - 146397) { @@ -1610,6 +1713,38 @@ void tst_QDate::roundtrip() const QCOMPARE(loopDate.toJulianDay(), testDate.toJulianDay()); loopDate = loopDate.addDays(1); } + +#if __cpp_lib_chrono >= 201907L + // Test roundtrip for from/to std::chrono conversions. + // Compile-time test, to verify it's all constexpr. + using namespace std::chrono; + { + constexpr sys_days expected{days{minJd}}; + constexpr sys_days actual{QDate::fromStdSysDays(expected).toStdSysDays()}; + static_assert(actual == expected); + } + { + // constexpr year_month_day expected{sys_days{days{maxJd}}}; // Overflow at least on MSVC + constexpr year_month_day expected{1970y, January, 1d}; + constexpr sys_days actual{QDate(expected).toStdSysDays()}; + static_assert(actual == sys_days(expected)); + } + { + constexpr year_month_day_last expected{2001y, {October / last}}; + constexpr sys_days actual{QDate(expected).toStdSysDays()}; + static_assert(actual == sys_days(expected)); + } + { + constexpr year_month_weekday expected{2001y, December, Saturday[1]}; + constexpr sys_days actual{QDate(expected).toStdSysDays()}; + static_assert(actual == sys_days(expected)); + } + { + constexpr year_month_weekday_last expected{2001y, November, Friday[last]}; + constexpr sys_days actual{QDate(expected).toStdSysDays()}; + static_assert(actual == sys_days(expected)); + } +#endif // __cpp_lib_chrono >= 201907L } void tst_QDate::qdebug() const diff --git a/tests/auto/corelib/time/qdatetime/CMakeLists.txt b/tests/auto/corelib/time/qdatetime/CMakeLists.txt index c5b8d2bb6e..499369c131 100644 --- a/tests/auto/corelib/time/qdatetime/CMakeLists.txt +++ b/tests/auto/corelib/time/qdatetime/CMakeLists.txt @@ -1,29 +1,33 @@ -# Generated from qdatetime.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qdatetime Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qdatetime LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qdatetime SOURCES tst_qdatetime.cpp DEFINES QT_NO_FOREACH QT_NO_KEYWORDS - PUBLIC_LIBRARIES + LIBRARIES Qt::CorePrivate + Qt::TestPrivate ) ## Scopes: ##################################################################### -#### Keys ignored in scope 2:.:.:qdatetime.pro:MSVC: -# QMAKE_CFLAGS_RELEASE = "--O1" -# QMAKE_CXXFLAGS_RELEASE = "--O1" - qt_internal_extend_target(tst_qdatetime CONDITION APPLE SOURCES tst_qdatetime_mac.mm - PUBLIC_LIBRARIES + LIBRARIES ${FWFoundation} ) diff --git a/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp b/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp index ef0be8a97c..f9c6afc795 100644 --- a/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp +++ b/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp @@ -1,41 +1,28 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Copyright (C) 2016 Intel Corporation. -** 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. +// Copyright (C) 2016 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +#include <QDateTime> #include <QTest> -#include <time.h> -#include <qdatetime.h> + +#include <QTimeZone> #include <private/qdatetime_p.h> +#include <private/qtenvironmentvariables_p.h> // for qTzSet(), qTzName() +#include <private/qcomparisontesthelper_p.h> #ifdef Q_OS_WIN -# include <qt_windows.h> +# include <qt_windows.h> +# if !QT_CONFIG(icu) +// The native MS back-end for time-zones lacks info about historic transitions: +# define INADEQUATE_TZ_DATA +# endif +#endif +#ifdef Q_OS_ANDROID // Also seems to lack full-day zone transitions: +# define INADEQUATE_TZ_DATA #endif +using namespace Qt::StringLiterals; + class tst_QDateTime : public QObject { Q_OBJECT @@ -48,11 +35,14 @@ public Q_SLOTS: private Q_SLOTS: void ctor(); void operator_eq(); + void moveSemantics(); void isNull(); void isValid(); void date(); void time(); +#if QT_DEPRECATED_SINCE(6, 9) void timeSpec(); +#endif void toSecsSinceEpoch_data(); void toSecsSinceEpoch(); void daylightSavingsTimeChange_data(); @@ -62,13 +52,17 @@ private Q_SLOTS: void setDate(); void setTime_data(); void setTime(); + void setTimeZone_data(); + void setTimeZone(); +#if QT_DEPRECATED_SINCE(6, 9) void setTimeSpec_data(); void setTimeSpec(); +#endif void setSecsSinceEpoch(); void setMSecsSinceEpoch_data(); void setMSecsSinceEpoch(); void fromSecsSinceEpoch(); - void fromMSecsSinceEpoch_data(); + void fromMSecsSinceEpoch_data() { setMSecsSinceEpoch_data(); } void fromMSecsSinceEpoch(); #if QT_CONFIG(datestring) void toString_isoDate_data(); @@ -92,20 +86,25 @@ private Q_SLOTS: void addSecs(); void addMSecs_data(); void addMSecs(); +#if QT_DEPRECATED_SINCE(6, 9) void toTimeSpec_data(); void toTimeSpec(); - void toLocalTime_data(); + void toLocalTime_data() { toTimeSpec_data(); } void toLocalTime(); - void toUTC_data(); + void toUTC_data() { toTimeSpec_data(); } void toUTC(); void toUTC_extra(); +#endif void daysTo(); void secsTo_data(); void secsTo(); - void msecsTo_data(); + void msecsTo_data() { addMSecs_data(); } void msecsTo(); + void orderingCompiles(); void operator_eqeq_data(); void operator_eqeq(); + void ordering_data(); + void ordering(); void operator_insert_extract_data(); void operator_insert_extract(); void currentDateTime(); @@ -123,8 +122,10 @@ private Q_SLOTS: #endif void offsetFromUtc(); +#if QT_DEPRECATED_SINCE(6, 9) void setOffsetFromUtc(); void toOffsetFromUtc(); +#endif void zoneAtTime_data(); void zoneAtTime(); @@ -151,11 +152,44 @@ private Q_SLOTS: void macTypes(); + void stdCompatibilitySysTime_data(); + void stdCompatibilitySysTime(); + void stdCompatibilityLocalTime_data(); + void stdCompatibilityLocalTime(); +#if QT_CONFIG(timezone) + void stdCompatibilityZonedTime_data(); + void stdCompatibilityZonedTime(); +#endif + private: - enum { LocalTimeIsUtc = 0, LocalTimeAheadOfUtc = 1, LocalTimeBehindUtc = -1} localTimeType; + /* + Various zones close to UTC (notably Iceland, the WET zones and several in + West Africa) or nominally assigned to it historically (north Canada, the + Antarctic) and those that have crossed the international date-line (by + skipping or repeating a day) don't have a consistent answer to "which side + of UTC is it ?" So the various LocalTimeType members may be different. + */ + enum LocalTimeType { LocalTimeIsUtc = 0, LocalTimeAheadOfUtc = 1, LocalTimeBehindUtc = -1}; + const LocalTimeType solarMeanType, epochTimeType, futureTimeType, distantTimeType; + static constexpr auto UTC = QTimeZone::UTC; + static constexpr qint64 epochJd = Q_INT64_C(2440588); int preZoneFix; bool zoneIsCET; + static LocalTimeType timeTypeFor(qint64 jand, qint64 juld) + { + constexpr uint day = 24 * 3600; // in seconds + QDateTime jan = QDateTime::fromSecsSinceEpoch(jand * day); + QDateTime jul = QDateTime::fromSecsSinceEpoch(juld * day); + if (jan.date().toJulianDay() < jand + epochJd || jul.date().toJulianDay() < juld + epochJd) + return LocalTimeBehindUtc; + if (jan.date().toJulianDay() > jand + epochJd || jul.date().toJulianDay() > juld + epochJd + || jan.time().hour() > 0 || jul.time().hour() > 0) { + return LocalTimeAheadOfUtc; + } + return LocalTimeIsUtc; + } + class TimeZoneRollback { const QByteArray prior; @@ -166,7 +200,7 @@ private: { reset(zone); } void reset(const QByteArray &zone) { - qputenv("TZ", zone.constData()); + qputenv("TZ", zone); qTzSet(); } ~TimeZoneRollback() @@ -174,7 +208,7 @@ private: if (prior.isNull()) qunsetenv("TZ"); else - qputenv("TZ", prior.constData()); + qputenv("TZ", prior); qTzSet(); } }; @@ -183,7 +217,18 @@ private: Q_DECLARE_METATYPE(Qt::TimeSpec) Q_DECLARE_METATYPE(Qt::DateFormat) -tst_QDateTime::tst_QDateTime() +tst_QDateTime::tst_QDateTime() : + // UTC starts of January and July in the commented years: + solarMeanType(timeTypeFor(-62091, -61910)), // 1800 + epochTimeType(timeTypeFor(0, 181)), // 1970 + // Use stable future, to which current rule is extrapolated, as surrogate for variable current: + futureTimeType(timeTypeFor(24837, 25018)), // 2038 + // The glibc functions only handle DST as far as a 32-bit signed day-count + // from some date in 1970 reaches; the future extreme of that is in the + // second half of 5'881'580 CE. Beyond 5'881'581 CE it treats all zones as + // being in their January state, regardless of time of year. So use data for + // this later year for tests of QDateTime's upper bound. + distantTimeType(timeTypeFor(0x800000adLL, 0x80000162LL)) { /* Due to some jurisdictions changing their zones and rules, it's possible @@ -197,7 +242,6 @@ tst_QDateTime::tst_QDateTime() might not be properly handled by our work-arounds for the MS backend and 32-bit time_t; so don't probe them here. */ - const uint day = 24 * 3600; // in seconds zoneIsCET = (QDateTime(QDate(2038, 1, 19), QTime(4, 14, 7)).toSecsSinceEpoch() == 0x7fffffff // Entries a year apart robustly differ by multiples of day. && QDate(2015, 7, 1).startOfDay().toSecsSinceEpoch() == 1435701600 @@ -227,31 +271,6 @@ tst_QDateTime::tst_QDateTime() Q_ASSERT(preZoneFix > -7200 && preZoneFix < 7200); // So it's OK to add it to a QTime() between 02:00 and 22:00, but otherwise // we must add it to the QDateTime constructed from it. - - /* - Again, rule changes can cause a TZ to look like UTC at some sample dates - but deviate at some date relevant to a test using localTimeType. These - tests mostly use years outside the 1970--2037 range, for which we trust - our TZ data, so we can't helpfully be exhaustive. Instead, scan a sample - of years' starts and middles. - */ - const int sampled = 3; - // UTC starts of months in 2004, 2038 and 1970: - qint64 jans[sampled] = { 12418 * day, 24837 * day, 0 }; - qint64 juls[sampled] = { 12600 * day, 25018 * day, 181 * day }; - localTimeType = LocalTimeIsUtc; - for (int i = sampled; i-- > 0; ) { - QDateTime jan = QDateTime::fromSecsSinceEpoch(jans[i]); - QDateTime jul = QDateTime::fromSecsSinceEpoch(juls[i]); - if (jan.date().year() < 1970 || jul.date().month() < 7) { - localTimeType = LocalTimeBehindUtc; - break; - } else if (jan.time().hour() > 0 || jul.time().hour() > 0 - || jan.date().day() > 1 || jul.date().day() > 1) { - localTimeType = LocalTimeAheadOfUtc; - break; - } - } } void tst_QDateTime::initTestCase() @@ -259,7 +278,7 @@ void tst_QDateTime::initTestCase() // Never construct a message like this in an i18n context... const char *typemsg1 = "exactly"; const char *typemsg2 = "and therefore not"; - switch (localTimeType) { + switch (futureTimeType) { case LocalTimeIsUtc: break; case LocalTimeBehindUtc: @@ -282,9 +301,9 @@ void tst_QDateTime::ctor() { QDateTime dt1(QDate(2004, 1, 2), QTime(1, 2, 3)); QCOMPARE(dt1.timeSpec(), Qt::LocalTime); - QDateTime dt2(QDate(2004, 1, 2), QTime(1, 2, 3), Qt::LocalTime); + QDateTime dt2(QDate(2004, 1, 2), QTime(1, 2, 3)); QCOMPARE(dt2.timeSpec(), Qt::LocalTime); - QDateTime dt3(QDate(2004, 1, 2), QTime(1, 2, 3), Qt::UTC); + QDateTime dt3(QDate(2004, 1, 2), QTime(1, 2, 3), UTC); QCOMPARE(dt3.timeSpec(), Qt::UTC); QVERIFY(dt1 == dt2); @@ -298,25 +317,26 @@ void tst_QDateTime::ctor() QDate offsetDate(2013, 1, 1); QTime offsetTime(1, 2, 3); - QDateTime offset1(offsetDate, offsetTime, Qt::OffsetFromUTC); + QDateTime offset1(offsetDate, offsetTime, QTimeZone::fromSecondsAheadOfUtc(0)); QCOMPARE(offset1.timeSpec(), Qt::UTC); QCOMPARE(offset1.offsetFromUtc(), 0); QCOMPARE(offset1.date(), offsetDate); QCOMPARE(offset1.time(), offsetTime); - QDateTime offset2(offsetDate, offsetTime, Qt::OffsetFromUTC, 0); + QDateTime offset2(offsetDate, offsetTime, + QTimeZone::fromDurationAheadOfUtc(std::chrono::seconds{})); QCOMPARE(offset2.timeSpec(), Qt::UTC); QCOMPARE(offset2.offsetFromUtc(), 0); QCOMPARE(offset2.date(), offsetDate); QCOMPARE(offset2.time(), offsetTime); - QDateTime offset3(offsetDate, offsetTime, Qt::OffsetFromUTC, 60 * 60); + QDateTime offset3(offsetDate, offsetTime, QTimeZone::fromSecondsAheadOfUtc(60 * 60)); QCOMPARE(offset3.timeSpec(), Qt::OffsetFromUTC); QCOMPARE(offset3.offsetFromUtc(), 60 * 60); QCOMPARE(offset3.date(), offsetDate); QCOMPARE(offset3.time(), offsetTime); - QDateTime offset4(offsetDate, QTime(0, 0), Qt::OffsetFromUTC, 60 * 60); + QDateTime offset4(offsetDate, QTime(0, 0), QTimeZone::fromSecondsAheadOfUtc(60 * 60)); QCOMPARE(offset4.timeSpec(), Qt::OffsetFromUTC); QCOMPARE(offset4.offsetFromUtc(), 60 * 60); QCOMPARE(offset4.date(), offsetDate); @@ -325,13 +345,25 @@ void tst_QDateTime::ctor() void tst_QDateTime::operator_eq() { - QVERIFY(QDateTime() != QDateTime(QDate(1970, 1, 1), QTime(0, 0))); // QTBUG-79006 - QDateTime dt1(QDate(2004, 3, 24), QTime(23, 45, 57), Qt::UTC); - QDateTime dt2(QDate(2005, 3, 11), QTime(0, 0), Qt::UTC); + QVERIFY(QDateTime() != QDate(1970, 1, 1).startOfDay()); // QTBUG-79006 + QDateTime dt1(QDate(2004, 3, 24), QTime(23, 45, 57), UTC); + QDateTime dt2(QDate(2005, 3, 11), QTime(0, 0), UTC); dt2 = dt1; QVERIFY(dt1 == dt2); } +void tst_QDateTime::moveSemantics() +{ + QDateTime dt1{QDate{2004, 3, 24}, QTime{23, 45, 57}, UTC}; + QDateTime dt2{QDate{2005, 3, 11}, QTime{0, 0}, UTC}; + QDateTime copy = dt1; + QDateTime moved = std::move(dt1); + QCOMPARE(copy, moved); + copy = dt2; + moved = std::move(dt2); + QCOMPARE(copy, moved); +} + void tst_QDateTime::isNull() { QDateTime dt1; @@ -340,15 +372,27 @@ void tst_QDateTime::isNull() QVERIFY(dt1.isNull()); dt1.setTime(QTime()); QVERIFY(dt1.isNull()); - dt1.setTimeSpec(Qt::UTC); - QVERIFY(dt1.isNull()); // maybe it should return false? + dt1.setTimeZone(UTC); + QVERIFY(dt1.isNull()); + dt1.setTime(QTime(12, 34, 56)); + QVERIFY(!dt1.isNull()); + dt1.setTime(QTime()); // Date still invalid, so this really clears time. + QVERIFY(dt1.isNull()); dt1.setDate(QDate(2004, 1, 2)); QVERIFY(!dt1.isNull()); dt1.setTime(QTime(12, 34, 56)); QVERIFY(!dt1.isNull()); - dt1.setTime(QTime()); + dt1.setTime(QTime()); // Actually sets time to QTime(0, 0), as date is still valid. + QVERIFY(!dt1.isNull()); + dt1.setDate(QDate()); // Time remains valid QVERIFY(!dt1.isNull()); + dt1.setTime(QTime()); // Now really sets time invalid, too + QVERIFY(dt1.isNull()); + + // Either date or time non-null => date-time isn't null: + QVERIFY(!QDateTime(QDate(), QTime(0, 0)).isNull()); + QVERIFY(!QDateTime(QDate(2022, 2, 16), QTime()).isNull()); } void tst_QDateTime::isValid() @@ -359,49 +403,61 @@ void tst_QDateTime::isValid() QVERIFY(!dt1.isValid()); dt1.setTime(QTime()); QVERIFY(!dt1.isValid()); - dt1.setTimeSpec(Qt::UTC); + dt1.setTimeZone(UTC); QVERIFY(!dt1.isValid()); dt1.setDate(QDate(2004, 1, 2)); QVERIFY(dt1.isValid()); + dt1.setTime(QTime()); // Effectively QTime(0, 0) + QVERIFY(dt1.isValid()); dt1.setDate(QDate()); QVERIFY(!dt1.isValid()); dt1.setTime(QTime(12, 34, 56)); QVERIFY(!dt1.isValid()); - dt1.setTime(QTime()); + dt1.setTime(QTime()); // Does sets time invalid, as date is invalid QVERIFY(!dt1.isValid()); + dt1.setDate(QDate(2004, 1, 2)); // Kicks time back to QTime(0, 0) + QVERIFY(dt1.isValid()); + + // Invalid date => invalid date-time: + QVERIFY(!QDateTime(QDate(), QTime(0, 0)).isValid()); + // Invalid time gets replaced with QTime(0, 0) when date is valid: + QVERIFY(QDateTime(QDate(2022, 2, 16), QTime()).isValid()); } void tst_QDateTime::date() { - QDateTime dt1(QDate(2004, 3, 24), QTime(23, 45, 57), Qt::LocalTime); + QDateTime dt1(QDate(2004, 3, 24), QTime(23, 45, 57)); QCOMPARE(dt1.date(), QDate(2004, 3, 24)); - QDateTime dt2(QDate(2004, 3, 25), QTime(0, 45, 57), Qt::LocalTime); + QDateTime dt2(QDate(2004, 3, 25), QTime(0, 45, 57)); QCOMPARE(dt2.date(), QDate(2004, 3, 25)); - QDateTime dt3(QDate(2004, 3, 24), QTime(23, 45, 57), Qt::UTC); + QDateTime dt3(QDate(2004, 3, 24), QTime(23, 45, 57), UTC); QCOMPARE(dt3.date(), QDate(2004, 3, 24)); - QDateTime dt4(QDate(2004, 3, 25), QTime(0, 45, 57), Qt::UTC); + QDateTime dt4(QDate(2004, 3, 25), QTime(0, 45, 57), UTC); QCOMPARE(dt4.date(), QDate(2004, 3, 25)); } void tst_QDateTime::time() { - QDateTime dt1(QDate(2004, 3, 24), QTime(23, 45, 57), Qt::LocalTime); + QDateTime dt1(QDate(2004, 3, 24), QTime(23, 45, 57)); QCOMPARE(dt1.time(), QTime(23, 45, 57)); - QDateTime dt2(QDate(2004, 3, 25), QTime(0, 45, 57), Qt::LocalTime); + QDateTime dt2(QDate(2004, 3, 25), QTime(0, 45, 57)); QCOMPARE(dt2.time(), QTime(0, 45, 57)); - QDateTime dt3(QDate(2004, 3, 24), QTime(23, 45, 57), Qt::UTC); + QDateTime dt3(QDate(2004, 3, 24), QTime(23, 45, 57), UTC); QCOMPARE(dt3.time(), QTime(23, 45, 57)); - QDateTime dt4(QDate(2004, 3, 25), QTime(0, 45, 57), Qt::UTC); + QDateTime dt4(QDate(2004, 3, 25), QTime(0, 45, 57), UTC); QCOMPARE(dt4.time(), QTime(0, 45, 57)); } +#if QT_DEPRECATED_SINCE(6, 9) +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED void tst_QDateTime::timeSpec() { QDateTime dt1(QDate(2004, 1, 24), QTime(23, 45, 57)); @@ -415,49 +471,51 @@ void tst_QDateTime::timeSpec() QCOMPARE(dt1.toTimeSpec(Qt::LocalTime).timeSpec(), Qt::LocalTime); QCOMPARE(dt1.toTimeSpec(Qt::UTC).timeSpec(), Qt::UTC); - QDateTime dt2(QDate(2004, 1, 24), QTime(23, 45, 57), Qt::LocalTime); + QDateTime dt2(QDate(2004, 1, 24), QTime(23, 45, 57)); QCOMPARE(dt2.timeSpec(), Qt::LocalTime); - QDateTime dt3(QDate(2004, 1, 25), QTime(0, 45, 57), Qt::UTC); + QDateTime dt3(QDate(2004, 1, 25), QTime(0, 45, 57), UTC); QCOMPARE(dt3.timeSpec(), Qt::UTC); QDateTime dt4 = QDateTime::currentDateTime(); QCOMPARE(dt4.timeSpec(), Qt::LocalTime); } +QT_WARNING_POP +#endif void tst_QDateTime::setDate() { - QDateTime dt1(QDate(2004, 3, 25), QTime(0, 45, 57), Qt::UTC); + QDateTime dt1(QDate(2004, 3, 25), QTime(0, 45, 57), UTC); dt1.setDate(QDate(2004, 6, 25)); QCOMPARE(dt1.date(), QDate(2004, 6, 25)); QCOMPARE(dt1.time(), QTime(0, 45, 57)); QCOMPARE(dt1.timeSpec(), Qt::UTC); - QDateTime dt2(QDate(2004, 3, 25), QTime(0, 45, 57), Qt::LocalTime); + QDateTime dt2(QDate(2004, 3, 25), QTime(0, 45, 57)); dt2.setDate(QDate(2004, 6, 25)); QCOMPARE(dt2.date(), QDate(2004, 6, 25)); QCOMPARE(dt2.time(), QTime(0, 45, 57)); QCOMPARE(dt2.timeSpec(), Qt::LocalTime); - QDateTime dt3(QDate(4004, 3, 25), QTime(0, 45, 57), Qt::UTC); + QDateTime dt3(QDate(4004, 3, 25), QTime(0, 45, 57), UTC); dt3.setDate(QDate(4004, 6, 25)); QCOMPARE(dt3.date(), QDate(4004, 6, 25)); QCOMPARE(dt3.time(), QTime(0, 45, 57)); QCOMPARE(dt3.timeSpec(), Qt::UTC); - QDateTime dt4(QDate(4004, 3, 25), QTime(0, 45, 57), Qt::LocalTime); + QDateTime dt4(QDate(4004, 3, 25), QTime(0, 45, 57)); dt4.setDate(QDate(4004, 6, 25)); QCOMPARE(dt4.date(), QDate(4004, 6, 25)); QCOMPARE(dt4.time(), QTime(0, 45, 57)); QCOMPARE(dt4.timeSpec(), Qt::LocalTime); - QDateTime dt5(QDate(1760, 3, 25), QTime(0, 45, 57), Qt::UTC); + QDateTime dt5(QDate(1760, 3, 25), QTime(0, 45, 57), UTC); dt5.setDate(QDate(1760, 6, 25)); QCOMPARE(dt5.date(), QDate(1760, 6, 25)); QCOMPARE(dt5.time(), QTime(0, 45, 57)); QCOMPARE(dt5.timeSpec(), Qt::UTC); - QDateTime dt6(QDate(1760, 3, 25), QTime(0, 45, 57), Qt::LocalTime); + QDateTime dt6(QDate(1760, 3, 25), QTime(0, 45, 57)); dt6.setDate(QDate(1760, 6, 25)); QCOMPARE(dt6.date(), QDate(1760, 6, 25)); QCOMPARE(dt6.time(), QTime(0, 45, 57)); @@ -469,12 +527,18 @@ void tst_QDateTime::setTime_data() QTest::addColumn<QDateTime>("dateTime"); QTest::addColumn<QTime>("newTime"); - QTest::newRow("data0") << QDateTime(QDate(2004, 3, 25), QTime(0, 45, 57), Qt::UTC) << QTime(23, 11, 22); - QTest::newRow("data1") << QDateTime(QDate(2004, 3, 25), QTime(0, 45, 57), Qt::LocalTime) << QTime(23, 11, 22); - QTest::newRow("data2") << QDateTime(QDate(4004, 3, 25), QTime(0, 45, 57), Qt::UTC) << QTime(23, 11, 22); - QTest::newRow("data3") << QDateTime(QDate(4004, 3, 25), QTime(0, 45, 57), Qt::LocalTime) << QTime(23, 11, 22); - QTest::newRow("data4") << QDateTime(QDate(1760, 3, 25), QTime(0, 45, 57), Qt::UTC) << QTime(23, 11, 22); - QTest::newRow("data5") << QDateTime(QDate(1760, 3, 25), QTime(0, 45, 57), Qt::LocalTime) << QTime(23, 11, 22); + QTest::newRow("data0") + << QDateTime(QDate(2004, 3, 25), QTime(0, 45, 57), UTC) << QTime(23, 11, 22); + QTest::newRow("data1") + << QDateTime(QDate(2004, 3, 25), QTime(0, 45, 57)) << QTime(23, 11, 22); + QTest::newRow("data2") + << QDateTime(QDate(4004, 3, 25), QTime(0, 45, 57), UTC) << QTime(23, 11, 22); + QTest::newRow("data3") + << QDateTime(QDate(4004, 3, 25), QTime(0, 45, 57)) << QTime(23, 11, 22); + QTest::newRow("data4") + << QDateTime(QDate(1760, 3, 25), QTime(0, 45, 57), UTC) << QTime(23, 11, 22); + QTest::newRow("data5") + << QDateTime(QDate(1760, 3, 25), QTime(0, 45, 57)) << QTime(23, 11, 22); QTest::newRow("set on std/dst") << QDateTime::currentDateTime() << QTime(23, 11, 22); } @@ -494,16 +558,73 @@ void tst_QDateTime::setTime() QCOMPARE(dateTime.timeSpec(), expectedTimeSpec); } +void tst_QDateTime::setTimeZone_data() +{ + QTest::addColumn<QDateTime>("dateTime"); + QTest::addColumn<QTimeZone>("zone"); + const QDate day(2004, 3, 25); + const QTime time(0, 45, 57); + struct { + const char *id; + QTimeZone zone; + } data[] = { + { nullptr, QTimeZone() }, // For time-zone, when supported. + { "UTC", UTC }, + { "LocalTime", QTimeZone() }, + { "Offset", QTimeZone::fromSecondsAheadOfUtc(3600) } + }; +#if QT_CONFIG(timezone) + const QTimeZone cet("Europe/Oslo"); + if (cet.isValid()) { + data[0].zone = cet; + data[0].id = "Zone"; + } +#endif + for (const auto &from : data) { + if (from.id) { + for (const auto &to : data) { + if (to.id) { + QTest::addRow("%s => %s", from.id, to.id) + << QDateTime(day, time, from.zone) << to.zone; + } + } + } + } +} + +void tst_QDateTime::setTimeZone() +{ + QFETCH(QDateTime, dateTime); + QFETCH(QTimeZone, zone); + + // QDateTime::setTimeZone() preserves the date and time rather than + // converting to the new time representation. + const QDate expectedDate(dateTime.date()); + const QTime expectedTime(dateTime.time()); + + dateTime.setTimeZone(zone); + + QCOMPARE(dateTime.date(), expectedDate); + QCOMPARE(dateTime.time(), expectedTime); + QCOMPARE(dateTime.timeRepresentation(), zone); +} + +#if QT_DEPRECATED_SINCE(6, 9) void tst_QDateTime::setTimeSpec_data() { QTest::addColumn<QDateTime>("dateTime"); QTest::addColumn<Qt::TimeSpec>("newTimeSpec"); - QTest::newRow("UTC => UTC") << QDateTime(QDate(2004, 3, 25), QTime(0, 45, 57), Qt::UTC) << Qt::UTC; - QTest::newRow("UTC => LocalTime") << QDateTime(QDate(2004, 3, 25), QTime(0, 45, 57), Qt::UTC) << Qt::LocalTime; - QTest::newRow("UTC => OffsetFromUTC") << QDateTime(QDate(2004, 3, 25), QTime(0, 45, 57), Qt::UTC) << Qt::OffsetFromUTC; + QTest::newRow("UTC => UTC") + << QDateTime(QDate(2004, 3, 25), QTime(0, 45, 57), UTC) << Qt::UTC; + QTest::newRow("UTC => LocalTime") + << QDateTime(QDate(2004, 3, 25), QTime(0, 45, 57), UTC) << Qt::LocalTime; + QTest::newRow("UTC => OffsetFromUTC") + << QDateTime(QDate(2004, 3, 25), QTime(0, 45, 57), UTC) << Qt::OffsetFromUTC; } +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED void tst_QDateTime::setTimeSpec() { QFETCH(QDateTime, dateTime); @@ -521,59 +642,61 @@ void tst_QDateTime::setTimeSpec() else QCOMPARE(dateTime.timeSpec(), newTimeSpec); } +QT_WARNING_POP +#endif void tst_QDateTime::setSecsSinceEpoch() { QDateTime dt1; dt1.setSecsSinceEpoch(0); - QCOMPARE(dt1.toUTC(), QDate(1970, 1, 1).startOfDay(Qt::UTC)); + QCOMPARE(dt1.toUTC(), QDate(1970, 1, 1).startOfDay(UTC)); QCOMPARE(dt1.timeSpec(), Qt::LocalTime); - dt1.setTimeSpec(Qt::UTC); + dt1.setTimeZone(UTC); dt1.setSecsSinceEpoch(0); - QCOMPARE(dt1, QDate(1970, 1, 1).startOfDay(Qt::UTC)); + QCOMPARE(dt1, QDate(1970, 1, 1).startOfDay(UTC)); QCOMPARE(dt1.timeSpec(), Qt::UTC); dt1.setSecsSinceEpoch(123456); - QCOMPARE(dt1, QDateTime(QDate(1970, 1, 2), QTime(10, 17, 36), Qt::UTC)); + QCOMPARE(dt1, QDateTime(QDate(1970, 1, 2), QTime(10, 17, 36), UTC)); if (zoneIsCET) { QDateTime dt2; dt2.setSecsSinceEpoch(123456); - QCOMPARE(dt2, QDateTime(QDate(1970, 1, 2), QTime(11, 17, 36), Qt::LocalTime)); + QCOMPARE(dt2, QDateTime(QDate(1970, 1, 2), QTime(11, 17, 36))); } dt1.setSecsSinceEpoch((uint)(quint32)-123456); - QCOMPARE(dt1, QDateTime(QDate(2106, 2, 5), QTime(20, 10, 40), Qt::UTC)); + QCOMPARE(dt1, QDateTime(QDate(2106, 2, 5), QTime(20, 10, 40), UTC)); if (zoneIsCET) { QDateTime dt2; dt2.setSecsSinceEpoch((uint)(quint32)-123456); - QCOMPARE(dt2, QDateTime(QDate(2106, 2, 5), QTime(21, 10, 40), Qt::LocalTime)); + QCOMPARE(dt2, QDateTime(QDate(2106, 2, 5), QTime(21, 10, 40))); } dt1.setSecsSinceEpoch(1214567890); - QCOMPARE(dt1, QDateTime(QDate(2008, 6, 27), QTime(11, 58, 10), Qt::UTC)); + QCOMPARE(dt1, QDateTime(QDate(2008, 6, 27), QTime(11, 58, 10), UTC)); if (zoneIsCET) { QDateTime dt2; dt2.setSecsSinceEpoch(1214567890); - QCOMPARE(dt2, QDateTime(QDate(2008, 6, 27), QTime(13, 58, 10), Qt::LocalTime)); + QCOMPARE(dt2, QDateTime(QDate(2008, 6, 27), QTime(13, 58, 10))); } dt1.setSecsSinceEpoch(0x7FFFFFFF); - QCOMPARE(dt1, QDateTime(QDate(2038, 1, 19), QTime(3, 14, 7), Qt::UTC)); + QCOMPARE(dt1, QDateTime(QDate(2038, 1, 19), QTime(3, 14, 7), UTC)); if (zoneIsCET) { QDateTime dt2; dt2.setSecsSinceEpoch(0x7FFFFFFF); - QCOMPARE(dt2, QDateTime(QDate(2038, 1, 19), QTime(4, 14, 7), Qt::LocalTime)); + QCOMPARE(dt2, QDateTime(QDate(2038, 1, 19), QTime(4, 14, 7))); } - dt1 = QDateTime(QDate(2013, 1, 1), QTime(0, 0), Qt::OffsetFromUTC, 60 * 60); + dt1 = QDateTime(QDate(2013, 1, 1), QTime(0, 0), QTimeZone::fromSecondsAheadOfUtc(60 * 60)); dt1.setSecsSinceEpoch(123456); - QCOMPARE(dt1, QDateTime(QDate(1970, 1, 2), QTime(10, 17, 36), Qt::UTC)); + QCOMPARE(dt1, QDateTime(QDate(1970, 1, 2), QTime(10, 17, 36), UTC)); QCOMPARE(dt1.timeSpec(), Qt::OffsetFromUTC); QCOMPARE(dt1.offsetFromUtc(), 60 * 60); // Only testing UTC; see fromSecsSinceEpoch() for fuller test. - dt1.setTimeSpec(Qt::UTC); + dt1.setTimeZone(UTC); const qint64 maxSeconds = std::numeric_limits<qint64>::max() / 1000; dt1.setSecsSinceEpoch(maxSeconds); QVERIFY(dt1.isValid()); @@ -595,56 +718,56 @@ void tst_QDateTime::setMSecsSinceEpoch_data() QTest::newRow("zero") << Q_INT64_C(0) - << QDateTime(QDate(1970, 1, 1), QTime(0, 0), Qt::UTC) + << QDateTime(QDate(1970, 1, 1), QTime(0, 0), UTC) << QDateTime(QDate(1970, 1, 1), QTime(1, 0)); QTest::newRow("+1ms") << Q_INT64_C(+1) - << QDateTime(QDate(1970, 1, 1), QTime(0, 0, 0, 1), Qt::UTC) + << QDateTime(QDate(1970, 1, 1), QTime(0, 0, 0, 1), UTC) << QDateTime(QDate(1970, 1, 1), QTime(1, 0, 0, 1)); QTest::newRow("+1s") << Q_INT64_C(+1000) - << QDateTime(QDate(1970, 1, 1), QTime(0, 0, 1), Qt::UTC) + << QDateTime(QDate(1970, 1, 1), QTime(0, 0, 1), UTC) << QDateTime(QDate(1970, 1, 1), QTime(1, 0, 1)); QTest::newRow("-1ms") << Q_INT64_C(-1) - << QDateTime(QDate(1969, 12, 31), QTime(23, 59, 59, 999), Qt::UTC) + << QDateTime(QDate(1969, 12, 31), QTime(23, 59, 59, 999), UTC) << QDateTime(QDate(1970, 1, 1), QTime(0, 59, 59, 999)); QTest::newRow("-1s") << Q_INT64_C(-1000) - << QDateTime(QDate(1969, 12, 31), QTime(23, 59, 59), Qt::UTC) + << QDateTime(QDate(1969, 12, 31), QTime(23, 59, 59), UTC) << QDateTime(QDate(1970, 1, 1), QTime(0, 59, 59)); QTest::newRow("123456789") << Q_INT64_C(123456789) - << QDateTime(QDate(1970, 1, 2), QTime(10, 17, 36, 789), Qt::UTC) - << QDateTime(QDate(1970, 1, 2), QTime(11, 17, 36, 789), Qt::LocalTime); + << QDateTime(QDate(1970, 1, 2), QTime(10, 17, 36, 789), UTC) + << QDateTime(QDate(1970, 1, 2), QTime(11, 17, 36, 789)); QTest::newRow("-123456789") << Q_INT64_C(-123456789) - << QDateTime(QDate(1969, 12, 30), QTime(13, 42, 23, 211), Qt::UTC) - << QDateTime(QDate(1969, 12, 30), QTime(14, 42, 23, 211), Qt::LocalTime); + << QDateTime(QDate(1969, 12, 30), QTime(13, 42, 23, 211), UTC) + << QDateTime(QDate(1969, 12, 30), QTime(14, 42, 23, 211)); QTest::newRow("post-32-bit-time_t") << (Q_INT64_C(1000) << 32) - << QDateTime(QDate(2106, 2, 7), QTime(6, 28, 16), Qt::UTC) + << QDateTime(QDate(2106, 2, 7), QTime(6, 28, 16), UTC) << QDateTime(QDate(2106, 2, 7), QTime(7, 28, 16)); QTest::newRow("very-large") << (Q_INT64_C(123456) << 32) - << QDateTime(QDate(18772, 8, 15), QTime(1, 8, 14, 976), Qt::UTC) + << QDateTime(QDate(18772, 8, 15), QTime(1, 8, 14, 976), UTC) << QDateTime(QDate(18772, 8, 15), QTime(3, 8, 14, 976)); QTest::newRow("old min (Tue Nov 25 00:00:00 -4714)") << Q_INT64_C(-210866716800000) - << QDateTime(QDate::fromJulianDay(1), QTime(0, 0), Qt::UTC) + << QDateTime(QDate::fromJulianDay(1), QTime(0, 0), UTC) << QDateTime(QDate::fromJulianDay(1), QTime(1, 0)).addSecs(preZoneFix); QTest::newRow("old max (Tue Jun 3 21:59:59 5874898)") << Q_INT64_C(185331720376799999) - << QDateTime(QDate::fromJulianDay(0x7fffffff), QTime(21, 59, 59, 999), Qt::UTC) + << QDateTime(QDate::fromJulianDay(0x7fffffff), QTime(21, 59, 59, 999), UTC) << QDateTime(QDate::fromJulianDay(0x7fffffff), QTime(23, 59, 59, 999)); QTest::newRow("min") << std::numeric_limits<qint64>::min() - << QDateTime(QDate(-292275056, 5, 16), QTime(16, 47, 4, 192), Qt::UTC) + << QDateTime(QDate(-292275056, 5, 16), QTime(16, 47, 4, 192), UTC) << QDateTime(QDate(-292275056, 5, 16), QTime(17, 47, 4, 192).addSecs(preZoneFix)); QTest::newRow("max") << std::numeric_limits<qint64>::max() - << QDateTime(QDate(292278994, 8, 17), QTime(7, 12, 55, 807), Qt::UTC) - << QDateTime(QDate(292278994, 8, 17), QTime(9, 12, 55, 807), Qt::LocalTime); + << QDateTime(QDate(292278994, 8, 17), QTime(7, 12, 55, 807), UTC) + << QDateTime(QDate(292278994, 8, 17), QTime(9, 12, 55, 807)); } void tst_QDateTime::setMSecsSinceEpoch() @@ -652,9 +775,10 @@ void tst_QDateTime::setMSecsSinceEpoch() QFETCH(qint64, msecs); QFETCH(QDateTime, utc); QFETCH(QDateTime, cet); + using Bound = std::numeric_limits<qint64>; QDateTime dt; - dt.setTimeSpec(Qt::UTC); + dt.setTimeZone(UTC); dt.setMSecsSinceEpoch(msecs); QCOMPARE(dt, utc); @@ -663,14 +787,14 @@ void tst_QDateTime::setMSecsSinceEpoch() QCOMPARE(dt.timeSpec(), Qt::UTC); { - QDateTime dt1 = QDateTime::fromMSecsSinceEpoch(msecs, Qt::UTC); + QDateTime dt1 = QDateTime::fromMSecsSinceEpoch(msecs, UTC); QCOMPARE(dt1, utc); QCOMPARE(dt1.date(), utc.date()); QCOMPARE(dt1.time(), utc.time()); QCOMPARE(dt1.timeSpec(), Qt::UTC); } { - QDateTime dt1(utc.date(), utc.time(), Qt::UTC); + QDateTime dt1(utc.date(), utc.time(), UTC); QCOMPARE(dt1, utc); QCOMPARE(dt1.date(), utc.date()); QCOMPARE(dt1.time(), utc.time()); @@ -685,10 +809,10 @@ void tst_QDateTime::setMSecsSinceEpoch() QCOMPARE(dt1.timeSpec(), Qt::UTC); } - if (zoneIsCET && (msecs == std::numeric_limits<qint64>::max() + if (zoneIsCET && (msecs == Bound::max() // LocalTime will also overflow for min in a CET zone west // of Greenwich (Europe/Madrid): - || (preZoneFix < -3600 && msecs == std::numeric_limits<qint64>::min()))) { + || (preZoneFix < -3600 && msecs == Bound::min()))) { QVERIFY(!cet.isValid()); // overflows } else if (zoneIsCET) { QVERIFY(cet.isValid()); @@ -696,7 +820,7 @@ void tst_QDateTime::setMSecsSinceEpoch() // Test converting from LocalTime to UTC back to LocalTime. QDateTime localDt; - localDt.setTimeSpec(Qt::LocalTime); + localDt.setTimeZone(QTimeZone::LocalTime); localDt.setMSecsSinceEpoch(msecs); QCOMPARE(localDt, utc); @@ -726,53 +850,51 @@ void tst_QDateTime::setMSecsSinceEpoch() QCOMPARE(dt.toMSecsSinceEpoch(), msecs); QCOMPARE(qint64(dt.toSecsSinceEpoch()), msecs / 1000); - QDateTime reference(QDate(1970, 1, 1), QTime(0, 0), Qt::UTC); + QDateTime reference(QDate(1970, 1, 1), QTime(0, 0), UTC); QCOMPARE(dt, reference.addMSecs(msecs)); // Tests that we correctly recognize when we fall off the extremities: - if (msecs == std::numeric_limits<qint64>::max()) { - QDateTime off(QDate(1970, 1, 1).startOfDay(Qt::OffsetFromUTC, 1)); + if (msecs == Bound::max()) { + QDateTime off(QDate(1970, 1, 1).startOfDay(QTimeZone::fromSecondsAheadOfUtc(1))); off.setMSecsSinceEpoch(msecs); QVERIFY(!off.isValid()); - } else if (msecs == std::numeric_limits<qint64>::min()) { - QDateTime off(QDate(1970, 1, 1).startOfDay(Qt::OffsetFromUTC, -1)); + } else if (msecs == Bound::min()) { + QDateTime off(QDate(1970, 1, 1).startOfDay(QTimeZone::fromSecondsAheadOfUtc(-1))); off.setMSecsSinceEpoch(msecs); QVERIFY(!off.isValid()); } - if ((localTimeType == LocalTimeAheadOfUtc && msecs == std::numeric_limits<qint64>::max()) - || (localTimeType == LocalTimeBehindUtc && msecs == std::numeric_limits<qint64>::min())) { + // Check overflow; only robust if local time is the same at epoch as relevant bound. + // See setting of LocalTimeType values for details. + if (epochTimeType == LocalTimeAheadOfUtc + ? distantTimeType == LocalTimeAheadOfUtc && msecs == Bound::max() + : (solarMeanType == LocalTimeBehindUtc && msecs == Bound::min() + && epochTimeType == LocalTimeBehindUtc)) { QDateTime curt = QDate(1970, 1, 1).startOfDay(); // initially in short-form curt.setMSecsSinceEpoch(msecs); // Overflows due to offset QVERIFY(!curt.isValid()); } } -void tst_QDateTime::fromMSecsSinceEpoch_data() -{ - setMSecsSinceEpoch_data(); -} - void tst_QDateTime::fromMSecsSinceEpoch() { QFETCH(qint64, msecs); QFETCH(QDateTime, utc); QFETCH(QDateTime, cet); using Bound = std::numeric_limits<qint64>; - if (msecs == Bound::min()) - qDebug() << "Local overflow:" << preZoneFix << Qt::hex; - QDateTime dtLocal = QDateTime::fromMSecsSinceEpoch(msecs, Qt::LocalTime); - QDateTime dtUtc = QDateTime::fromMSecsSinceEpoch(msecs, Qt::UTC); - QDateTime dtOffset = QDateTime::fromMSecsSinceEpoch(msecs, Qt::OffsetFromUTC, 60*60); + QDateTime dtLocal = QDateTime::fromMSecsSinceEpoch(msecs); + QDateTime dtUtc = QDateTime::fromMSecsSinceEpoch(msecs, UTC); + QDateTime dtOffset + = QDateTime::fromMSecsSinceEpoch(msecs, QTimeZone::fromSecondsAheadOfUtc(60 * 60)); // LocalTime will overflow for "min" or "max" tests, depending on whether // you're East or West of Greenwich. In UTC, we won't overflow. If we're // actually west of Greenwich but (e.g. Europe/Madrid) our zone claims east, // "min" can also overflow (case only caught if local time is CET). - const bool localOverflow = (localTimeType == LocalTimeAheadOfUtc - ? msecs == Bound::max() || preZoneFix < -3600 - : localTimeType == LocalTimeBehindUtc && msecs == Bound::min()); - if (!localOverflow) + const bool localOverflow = + (distantTimeType == LocalTimeAheadOfUtc && (msecs == Bound::max() || preZoneFix < -3600)) + || (solarMeanType == LocalTimeBehindUtc && msecs == Bound::min()); + if (!localOverflow) // Can fail if offset changes sign, e.g. Alaska, Philippines. QCOMPARE(dtLocal, utc); QCOMPARE(dtUtc, utc); @@ -806,7 +928,7 @@ void tst_QDateTime::fromMSecsSinceEpoch() if (msecs != Bound::max()) QCOMPARE(qint64(dtOffset.toSecsSinceEpoch()), msecs / 1000); - QDateTime reference(QDate(1970, 1, 1), QTime(0, 0), Qt::UTC); + QDateTime reference(QDate(1970, 1, 1), QTime(0, 0), UTC); if (!localOverflow) QCOMPARE(dtLocal, reference.addMSecs(msecs)); QCOMPARE(dtUtc, reference.addMSecs(msecs)); @@ -818,16 +940,31 @@ void tst_QDateTime::fromSecsSinceEpoch() { // Compare setSecsSinceEpoch() const qint64 maxSeconds = std::numeric_limits<qint64>::max() / 1000; - const QDateTime early = QDateTime::fromSecsSinceEpoch(-maxSeconds, Qt::UTC); - const QDateTime late = QDateTime::fromSecsSinceEpoch(maxSeconds, Qt::UTC); + const QDateTime early = QDateTime::fromSecsSinceEpoch(-maxSeconds, UTC); + const QDateTime late = QDateTime::fromSecsSinceEpoch(maxSeconds, UTC); QVERIFY(late.isValid()); - QVERIFY(!QDateTime::fromSecsSinceEpoch(maxSeconds + 1, Qt::UTC).isValid()); + QVERIFY(!QDateTime::fromSecsSinceEpoch(maxSeconds + 1, UTC).isValid()); QVERIFY(early.isValid()); - QVERIFY(!QDateTime::fromSecsSinceEpoch(-maxSeconds - 1, Qt::UTC).isValid()); + QVERIFY(!QDateTime::fromSecsSinceEpoch(-maxSeconds - 1, UTC).isValid()); // Local time: need to adjust for its zone offset - const qint64 last = maxSeconds - qMax(late.addYears(-1).toLocalTime().offsetFromUtc(), 0); + const int lateOffset = late.addYears(-1).toLocalTime().offsetFromUtc(); +#if QT_CONFIG(timezone) + // Check what system zone believes in, as it's used as fall-back to cope + // with times outside the system time_t functions' range, or overflow on the + // results of using those functions. (It seems glibc's handling of + // Australasian zones parts company with the IANA DB after about 5881580 CE, + // leaving NZ in permanent DST after that, for example.) Of course, if + // that's less than lateOffset (as it is for glibc's similar handling of + // MET), the fall-back code will also fail when the primary code fails, so + // use the lesser of these late offsets. + const int lateZone = qMin(QTimeZone::systemTimeZone().offsetFromUtc(late), lateOffset); +#else + const int lateZone = lateOffset; +#endif + + const qint64 last = maxSeconds - qMax(lateZone, 0); QVERIFY(QDateTime::fromSecsSinceEpoch(last).isValid()); QVERIFY(!QDateTime::fromSecsSinceEpoch(last + 1).isValid()); const qint64 first = -maxSeconds - qMin(early.addYears(1).toLocalTime().offsetFromUtc(), 0); @@ -835,10 +972,14 @@ void tst_QDateTime::fromSecsSinceEpoch() QVERIFY(!QDateTime::fromSecsSinceEpoch(first - 1).isValid()); // Use an offset for which .toUTC()'s return would flip the validity: - QVERIFY(QDateTime::fromSecsSinceEpoch(maxSeconds - 7200, Qt::OffsetFromUTC, 7200).isValid()); - QVERIFY(!QDateTime::fromSecsSinceEpoch(maxSeconds - 7199, Qt::OffsetFromUTC, 7200).isValid()); - QVERIFY(QDateTime::fromSecsSinceEpoch(7200 - maxSeconds, Qt::OffsetFromUTC, -7200).isValid()); - QVERIFY(!QDateTime::fromSecsSinceEpoch(7199 - maxSeconds, Qt::OffsetFromUTC, -7200).isValid()); + QVERIFY(QDateTime::fromSecsSinceEpoch(maxSeconds - 7200, + QTimeZone::fromSecondsAheadOfUtc(7200)).isValid()); + QVERIFY(!QDateTime::fromSecsSinceEpoch(maxSeconds - 7199, + QTimeZone::fromSecondsAheadOfUtc(7200)).isValid()); + QVERIFY(QDateTime::fromSecsSinceEpoch(7200 - maxSeconds, + QTimeZone::fromSecondsAheadOfUtc(-7200)).isValid()); + QVERIFY(!QDateTime::fromSecsSinceEpoch(7199 - maxSeconds, + QTimeZone::fromSecondsAheadOfUtc(-7200)).isValid()); #if QT_CONFIG(timezone) // As for offset, use zones each side of UTC: @@ -861,23 +1002,23 @@ void tst_QDateTime::toString_isoDate_data() << QDateTime(QDate(1978, 11, 9), QTime(13, 28, 34)) << Qt::ISODate << QString("1978-11-09T13:28:34"); QTest::newRow("UTC") - << QDateTime(QDate(1978, 11, 9), QTime(13, 28, 34), Qt::UTC) + << QDateTime(QDate(1978, 11, 9), QTime(13, 28, 34), UTC) << Qt::ISODate << QString("1978-11-09T13:28:34Z"); QDateTime dt(QDate(1978, 11, 9), QTime(13, 28, 34)); - dt.setOffsetFromUtc(19800); + dt.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(19800)); QTest::newRow("positive OffsetFromUTC") << dt << Qt::ISODate << QString("1978-11-09T13:28:34+05:30"); - dt.setOffsetFromUtc(-7200); + dt.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(-7200)); QTest::newRow("negative OffsetFromUTC") << dt << Qt::ISODate << QString("1978-11-09T13:28:34-02:00"); - dt.setOffsetFromUtc(-900); + dt.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(-900)); QTest::newRow("negative non-integral OffsetFromUTC") << dt << Qt::ISODate << QString("1978-11-09T13:28:34-00:15"); QTest::newRow("invalid") // ISODate < 2019 doesn't allow -ve year numbers; QTBUG-91070 - << QDateTime(QDate(-1, 11, 9), QTime(13, 28, 34), Qt::UTC) + << QDateTime(QDate(-1, 11, 9), QTime(13, 28, 34), UTC) << Qt::ISODate << QString(); QTest::newRow("without-ms") << QDateTime(QDate(1978, 11, 9), QTime(13, 28, 34, 20)) @@ -914,7 +1055,7 @@ void tst_QDateTime::toString_isoDate() void tst_QDateTime::toString_isoDate_extra() { - QDateTime dt = QDateTime::fromMSecsSinceEpoch(0, Qt::UTC); + QDateTime dt = QDateTime::fromMSecsSinceEpoch(0, UTC); QCOMPARE(dt.toString(Qt::ISODate), QLatin1String("1970-01-01T00:00:00Z")); #if QT_CONFIG(timezone) QTimeZone PST("America/Vancouver"); @@ -943,15 +1084,15 @@ void tst_QDateTime::toString_textDate_data() + ' ' + QLocale::c().monthName(1, QLocale::ShortFormat); // ### Qt 7 GMT: change to UTC - see matching QDateTime::fromString() comment - QTest::newRow("localtime") << QDateTime(QDate(2013, 1, 2), QTime(1, 2, 3), Qt::LocalTime) + QTest::newRow("localtime") << QDateTime(QDate(2013, 1, 2), QTime(1, 2, 3)) << wednesdayJanuary + QString(" 2 01:02:03 2013"); - QTest::newRow("utc") << QDateTime(QDate(2013, 1, 2), QTime(1, 2, 3), Qt::UTC) + QTest::newRow("utc") << QDateTime(QDate(2013, 1, 2), QTime(1, 2, 3), UTC) << wednesdayJanuary + QString(" 2 01:02:03 2013 GMT"); - QTest::newRow("offset+") << QDateTime(QDate(2013, 1, 2), QTime(1, 2, 3), Qt::OffsetFromUTC, - 10 * 60 * 60) + QTest::newRow("offset+") << QDateTime(QDate(2013, 1, 2), QTime(1, 2, 3), + QTimeZone::fromSecondsAheadOfUtc(10 * 60 * 60)) << wednesdayJanuary + QString(" 2 01:02:03 2013 GMT+1000"); - QTest::newRow("offset-") << QDateTime(QDate(2013, 1, 2), QTime(1, 2, 3), Qt::OffsetFromUTC, - -10 * 60 * 60) + QTest::newRow("offset-") << QDateTime(QDate(2013, 1, 2), QTime(1, 2, 3), + QTimeZone::fromSecondsAheadOfUtc(-10 * 60 * 60)) << wednesdayJanuary + QString(" 2 01:02:03 2013 GMT-1000"); QTest::newRow("invalid") << QDateTime() << QString(""); @@ -981,9 +1122,9 @@ void tst_QDateTime::toString_textDate_extra() auto endsWithGmt = [](const QDateTime &dt) { return dt.toString().endsWith(QLatin1String("GMT")); }; - QDateTime dt = QDateTime::fromMSecsSinceEpoch(0, Qt::LocalTime); + QDateTime dt = QDateTime::fromMSecsSinceEpoch(0); QVERIFY(!endsWithGmt(dt)); - dt = QDateTime::fromMSecsSinceEpoch(0, Qt::UTC).toLocalTime(); + dt = QDateTime::fromMSecsSinceEpoch(0, UTC).toLocalTime(); QVERIFY(!endsWithGmt(dt)); #if QT_CONFIG(timezone) @@ -1016,7 +1157,7 @@ void tst_QDateTime::toString_textDate_extra() else QCOMPARE(dt.toString(), QLatin1String("Thu Jan 1 00:00:00 1970")); #endif - dt = QDateTime::fromMSecsSinceEpoch(0, Qt::UTC); + dt = QDateTime::fromMSecsSinceEpoch(0, UTC); QVERIFY(endsWithGmt(dt)); } @@ -1031,22 +1172,22 @@ void tst_QDateTime::toString_rfcDate_data() << QString("09 Nov 1978 13:28:34 +0100"); } QTest::newRow("UTC") - << QDateTime(QDate(1978, 11, 9), QTime(13, 28, 34), Qt::UTC) + << QDateTime(QDate(1978, 11, 9), QTime(13, 28, 34), UTC) << QString("09 Nov 1978 13:28:34 +0000"); QDateTime dt(QDate(1978, 11, 9), QTime(13, 28, 34)); - dt.setOffsetFromUtc(19800); + dt.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(19800)); QTest::newRow("positive OffsetFromUTC") << dt << QString("09 Nov 1978 13:28:34 +0530"); - dt.setOffsetFromUtc(-7200); + dt.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(-7200)); QTest::newRow("negative OffsetFromUTC") << dt << QString("09 Nov 1978 13:28:34 -0200"); QTest::newRow("invalid") - << QDateTime(QDate(1978, 13, 9), QTime(13, 28, 34), Qt::UTC) + << QDateTime(QDate(1978, 13, 9), QTime(13, 28, 34), UTC) << QString(); QTest::newRow("999 milliseconds UTC") - << QDateTime(QDate(2000, 1, 1), QTime(13, 28, 34, 999), Qt::UTC) + << QDateTime(QDate(2000, 1, 1), QTime(13, 28, 34, 999), UTC) << QString("01 Jan 2000 13:28:34 +0000"); } @@ -1078,29 +1219,44 @@ void tst_QDateTime::toString_strformat() { // Most tests are in QLocale, just test that the api works. QDate testDate(2013, 1, 1); - QTime testTime(1, 2, 3); - QDateTime testDateTime(testDate, testTime, Qt::UTC); + QTime testTime(1, 2, 3, 456); + QDateTime testDateTime(testDate, testTime, UTC); QCOMPARE(testDate.toString("yyyy-MM-dd"), QString("2013-01-01")); QCOMPARE(testTime.toString("hh:mm:ss"), QString("01:02:03")); + QCOMPARE(testTime.toString("hh:mm:ss.zz"), QString("01:02:03.456")); QCOMPARE(testDateTime.toString("yyyy-MM-dd hh:mm:ss t"), QString("2013-01-01 01:02:03 UTC")); - // TODO QTBUG-95966: find better ways to use repeated 't' - QCOMPARE(testDateTime.toString("yyyy-MM-dd hh:mm:ss tt"), QString("2013-01-01 01:02:03 UTCUTC")); + QCOMPARE(testDateTime.toString("yyyy-MM-dd hh:mm:ss tt"), QString("2013-01-01 01:02:03 +0000")); + QCOMPARE(testDateTime.toString("yyyy-MM-dd hh:mm:ss ttt"), QString("2013-01-01 01:02:03 +00:00")); + QCOMPARE(testDateTime.toString("yyyy-MM-dd hh:mm:ss tttt"), QString("2013-01-01 01:02:03 UTC")); } #endif // datestring void tst_QDateTime::addDays() { - for (int pass = 0; pass < 2; ++pass) { - QDateTime dt(QDate(2004, 1, 1), QTime(12, 34, 56), pass == 0 ? Qt::LocalTime : Qt::UTC); - dt = dt.addDays(185); + const QTimeZone zones[] = { + QTimeZone(QTimeZone::LocalTime), + QTimeZone(QTimeZone::UTC), +#if QT_CONFIG(timezone) + QTimeZone("Europe/Oslo"), +#endif + QTimeZone::fromSecondsAheadOfUtc(3600) + }; + for (const auto &zone : zones) { + QDateTime dt = QDateTime(QDate(2004, 1, 1), QTime(12, 34, 56), zone).addDays(185); QVERIFY(dt.date().year() == 2004 && dt.date().month() == 7 && dt.date().day() == 4); QVERIFY(dt.time().hour() == 12 && dt.time().minute() == 34 && dt.time().second() == 56 && dt.time().msec() == 0); - QCOMPARE(dt.timeSpec(), (pass == 0 ? Qt::LocalTime : Qt::UTC)); + QCOMPARE(dt.timeRepresentation(), zone); dt = dt.addDays(-185); QCOMPARE(dt.date(), QDate(2004, 1, 1)); QCOMPARE(dt.time(), QTime(12, 34, 56)); + + // Test we can do this before time-zones existed: + dt = QDateTime(QDate(1704, 1, 1), QTime(12, 0), zone).addDays(185); + QCOMPARE(dt.date(), QDate(1704, 7, 4)); + QCOMPARE(dt.time(), QTime(12, 0)); + QCOMPARE(dt.timeRepresentation(), zone); } QDateTime dt(QDate(1752, 9, 14), QTime(12, 34, 56)); @@ -1115,32 +1271,91 @@ void tst_QDateTime::addDays() } // Test preserves TimeSpec - QDateTime dt1(QDate(2013, 1, 1), QTime(0, 0), Qt::UTC); + QDateTime dt1(QDate(2013, 1, 1), QTime(0, 0), UTC); QDateTime dt2 = dt1.addDays(2); QCOMPARE(dt2.date(), QDate(2013, 1, 3)); QCOMPARE(dt2.time(), QTime(0, 0)); QCOMPARE(dt2.timeSpec(), Qt::UTC); - dt1 = QDateTime(QDate(2013, 1, 1), QTime(0, 0), Qt::LocalTime); + dt1 = QDateTime(QDate(2013, 1, 1), QTime(0, 0)); dt2 = dt1.addDays(2); QCOMPARE(dt2.date(), QDate(2013, 1, 3)); QCOMPARE(dt2.time(), QTime(0, 0)); QCOMPARE(dt2.timeSpec(), Qt::LocalTime); - dt1 = QDateTime(QDate(2013, 1, 1), QTime(0, 0), Qt::OffsetFromUTC, 60*60); + dt1 = QDateTime(QDate(2013, 1, 1), QTime(0, 0), QTimeZone::fromSecondsAheadOfUtc(60 * 60)); dt2 = dt1.addDays(2); QCOMPARE(dt2.date(), QDate(2013, 1, 3)); QCOMPARE(dt2.time(), QTime(0, 0)); QCOMPARE(dt2.timeSpec(), Qt::OffsetFromUTC); QCOMPARE(dt2.offsetFromUtc(), 60 * 60); - // Test last UTC second of 1969 *is* valid (despite being time_t(-1)) - dt1 = QDateTime(QDate(1969, 12, 30), QTime(23, 59, 59), Qt::UTC).toLocalTime().addDays(1); - QVERIFY(dt1.isValid()); - QCOMPARE(dt1.toSecsSinceEpoch(), -1); - dt2 = QDateTime(QDate(1970, 1, 1), QTime(23, 59, 59), Qt::UTC).toLocalTime().addDays(-1); - QVERIFY(dt2.isValid()); - QCOMPARE(dt2.toSecsSinceEpoch(), -1); +#if QT_CONFIG(timezone) + const QTimeZone cet("Europe/Oslo"); + if (cet.isValid()) { + dt1 = QDate(2022, 1, 10).startOfDay(cet); + dt2 = dt1.addDays(2); // QTBUG-99668: should not assert + QCOMPARE(dt2.date(), QDate(2022, 1, 12)); + QCOMPARE(dt2.time(), QTime(0, 0)); + QCOMPARE(dt2.timeSpec(), Qt::TimeZone); + QCOMPARE(dt2.timeZone(), cet); + } +# ifndef INADEQUATE_TZ_DATA + if (const QTimeZone lint("Pacific/Kiritimati"); lint.isValid()) { + // Line Islands Time skipped Sat 1994-12-31: + dt1 = QDateTime(QDate(1994, 12, 30), QTime(12, 0), lint); + dt2 = QDateTime(QDate(1995, 1, 1), QTime(12, 0), lint); + // Trying to step into the hole gets the other side: + QCOMPARE(dt1.addDays(1), dt2); + QCOMPARE(dt2.addDays(-1), dt1); + // But the other side is in fact two days away: + QCOMPARE(dt1.addDays(2), dt2); + QCOMPARE(dt2.addDays(-2), dt1); + QCOMPARE(dt1.daysTo(dt2), 2); + } +# ifndef Q_OS_DARWIN + if (const QTimeZone alaska("America/Anchorage"); alaska.isValid()) { + // On Julian date 1867, Sat Oct 7 (at 14:31 local solar mean time for + // Anchorage, 15:30 LMT in Sitka, which hosted the transfer ceremony) + // Russia sold Alaska to the USA, which changed the calendar to + // Gregorian, hence the date to Fri Oct 18. Compare addSecs:Alaska-Day. + // Friday evening and Saturday morning were repeated, with different dates. + // Friday noon, as described by the Russians: + dt1 = QDateTime(QDate(1867, 10, 6, QCalendar(QCalendar::System::Julian)), + QTime(12, 0), alaska); + // Sunday noon, as described by the Americans: + dt2 = QDateTime(QDate(1867, 10, 20), QTime(12, 0), alaska); + // Three elapsed days, but daysTo() and addDays only see two: + QCOMPARE(dt1.addDays(2), dt2); + QCOMPARE(dt2.addDays(-2), dt1); + QCOMPARE(dt1.daysTo(dt2), 2); + // Stepping into the duplicated day (Julian 7th, Gregorian 19th) gets + // the nearer side, with the same nominal date (and time): + QCOMPARE(dt1.addDays(1).date(), dt2.addDays(-1).date()); + QCOMPARE(dt1.addDays(1).time(), dt2.addDays(-1).time()); + QCOMPARE(dt1.addDays(1).daysTo(dt2.addDays(-1)), 0); + // Yet they differ by a day: + QCOMPARE_NE(dt1.addDays(1), dt2.addDays(-1)); + QCOMPARE(dt1.addDays(1).secsTo(dt2.addDays(-1)), 24 * 60 * 60); + // Stepping from one duplicate one day towards the other jumps it: + QCOMPARE(dt1, dt2.addDays(-1).addDays(-1)); + QCOMPARE(dt1.addDays(1).addDays(1), dt2); + } +# endif // Darwin +# endif // inadequate zone data +#endif // timezone + + // Baja Mexico has a transition at the epoch, see fromStringDateFormat_data(). + if (QDateTime(QDate(1969, 12, 30), QTime(0, 0)).secsTo( + QDateTime(QDate(1970, 1, 2), QTime(0, 0))) == 3 * 24 * 60 * 60) { + // Test last UTC second of 1969 *is* valid (despite being time_t(-1)) + dt1 = QDateTime(QDate(1969, 12, 30), QTime(23, 59, 59), UTC).toLocalTime().addDays(1); + QVERIFY(dt1.isValid()); + QCOMPARE(dt1.toSecsSinceEpoch(), -1); + dt2 = QDateTime(QDate(1970, 1, 1), QTime(23, 59, 59), UTC).toLocalTime().addDays(-1); + QVERIFY(dt2.isValid()); + QCOMPARE(dt2.toSecsSinceEpoch(), -1); + } } void tst_QDateTime::addInvalid() @@ -1160,7 +1375,7 @@ void tst_QDateTime::addInvalid() offset = bad.addMSecs(73); QVERIFY(offset.isNull()); - QDateTime bound = QDateTime::fromMSecsSinceEpoch(std::numeric_limits<qint64>::min(), Qt::UTC); + QDateTime bound = QDateTime::fromMSecsSinceEpoch(std::numeric_limits<qint64>::min(), UTC); QVERIFY(bound.isValid()); offset = bound.addMSecs(-1); QVERIFY(!offset.isValid()); @@ -1239,13 +1454,13 @@ void tst_QDateTime::addMonths() QCOMPARE(end.time(), testTime); QCOMPARE(end.timeSpec(), Qt::LocalTime); - start = QDateTime(testDate, testTime, Qt::UTC); + start = QDateTime(testDate, testTime, UTC); end = start.addMonths(months); QCOMPARE(end.date(), resultDate); QCOMPARE(end.time(), testTime); QCOMPARE(end.timeSpec(), Qt::UTC); - start = QDateTime(testDate, testTime, Qt::OffsetFromUTC, 60 * 60); + start = QDateTime(testDate, testTime, QTimeZone::fromSecondsAheadOfUtc(60 * 60)); end = start.addMonths(months); QCOMPARE(end.date(), resultDate); QCOMPARE(end.time(), testTime); @@ -1291,13 +1506,13 @@ void tst_QDateTime::addYears() QCOMPARE(end.time(), testTime); QCOMPARE(end.timeSpec(), Qt::LocalTime); - start = QDateTime(startDate, testTime, Qt::UTC); + start = QDateTime(startDate, testTime, UTC); end = start.addYears(years1).addYears(years2); QCOMPARE(end.date(), resultDate); QCOMPARE(end.time(), testTime); QCOMPARE(end.timeSpec(), Qt::UTC); - start = QDateTime(startDate, testTime, Qt::OffsetFromUTC, 60 * 60); + start = QDateTime(startDate, testTime, QTimeZone::fromSecondsAheadOfUtc(60 * 60)); end = start.addYears(years1).addYears(years2); QCOMPARE(end.date(), resultDate); QCOMPARE(end.time(), testTime); @@ -1316,107 +1531,144 @@ void tst_QDateTime::addMSecs_data() const qint64 daySecs(86400); QTest::newRow("utc0") - << QDateTime(QDate(2004, 1, 1), standardTime, Qt::UTC) << daySecs - << QDateTime(QDate(2004, 1, 2), standardTime, Qt::UTC); + << QDateTime(QDate(2004, 1, 1), standardTime, UTC) << daySecs + << QDateTime(QDate(2004, 1, 2), standardTime, UTC); QTest::newRow("utc1") - << QDateTime(QDate(2004, 1, 1), standardTime, Qt::UTC) << (daySecs * 185) - << QDateTime(QDate(2004, 7, 4), standardTime, Qt::UTC); + << QDateTime(QDate(2004, 1, 1), standardTime, UTC) << (daySecs * 185) + << QDateTime(QDate(2004, 7, 4), standardTime, UTC); QTest::newRow("utc2") - << QDateTime(QDate(2004, 1, 1), standardTime, Qt::UTC) << (daySecs * 366) - << QDateTime(QDate(2005, 1, 1), standardTime, Qt::UTC); + << QDateTime(QDate(2004, 1, 1), standardTime, UTC) << (daySecs * 366) + << QDateTime(QDate(2005, 1, 1), standardTime, UTC); QTest::newRow("utc3") - << QDateTime(QDate(1760, 1, 1), standardTime, Qt::UTC) << daySecs - << QDateTime(QDate(1760, 1, 2), standardTime, Qt::UTC); + << QDateTime(QDate(1760, 1, 1), standardTime, UTC) << daySecs + << QDateTime(QDate(1760, 1, 2), standardTime, UTC); QTest::newRow("utc4") - << QDateTime(QDate(1760, 1, 1), standardTime, Qt::UTC) << (daySecs * 185) - << QDateTime(QDate(1760, 7, 4), standardTime, Qt::UTC); + << QDateTime(QDate(1760, 1, 1), standardTime, UTC) << (daySecs * 185) + << QDateTime(QDate(1760, 7, 4), standardTime, UTC); QTest::newRow("utc5") - << QDateTime(QDate(1760, 1, 1), standardTime, Qt::UTC) << (daySecs * 366) - << QDateTime(QDate(1761, 1, 1), standardTime, Qt::UTC); + << QDateTime(QDate(1760, 1, 1), standardTime, UTC) << (daySecs * 366) + << QDateTime(QDate(1761, 1, 1), standardTime, UTC); QTest::newRow("utc6") - << QDateTime(QDate(4000, 1, 1), standardTime, Qt::UTC) << daySecs - << QDateTime(QDate(4000, 1, 2), standardTime, Qt::UTC); + << QDateTime(QDate(4000, 1, 1), standardTime, UTC) << daySecs + << QDateTime(QDate(4000, 1, 2), standardTime, UTC); QTest::newRow("utc7") - << QDateTime(QDate(4000, 1, 1), standardTime, Qt::UTC) << (daySecs * 185) - << QDateTime(QDate(4000, 7, 4), standardTime, Qt::UTC); + << QDateTime(QDate(4000, 1, 1), standardTime, UTC) << (daySecs * 185) + << QDateTime(QDate(4000, 7, 4), standardTime, UTC); QTest::newRow("utc8") - << QDateTime(QDate(4000, 1, 1), standardTime, Qt::UTC) << (daySecs * 366) - << QDateTime(QDate(4001, 1, 1), standardTime, Qt::UTC); + << QDateTime(QDate(4000, 1, 1), standardTime, UTC) << (daySecs * 366) + << QDateTime(QDate(4001, 1, 1), standardTime, UTC); QTest::newRow("utc9") - << QDateTime(QDate(4000, 1, 1), standardTime, Qt::UTC) << qint64(0) - << QDateTime(QDate(4000, 1, 1), standardTime, Qt::UTC); + << QDateTime(QDate(4000, 1, 1), standardTime, UTC) << qint64(0) + << QDateTime(QDate(4000, 1, 1), standardTime, UTC); if (zoneIsCET) { QTest::newRow("cet0") - << QDateTime(QDate(2004, 1, 1), standardTime, Qt::LocalTime) << daySecs - << QDateTime(QDate(2004, 1, 2), standardTime, Qt::LocalTime); + << QDateTime(QDate(2004, 1, 1), standardTime) << daySecs + << QDateTime(QDate(2004, 1, 2), standardTime); QTest::newRow("cet1") - << QDateTime(QDate(2004, 1, 1), standardTime, Qt::LocalTime) << (daySecs * 185) - << QDateTime(QDate(2004, 7, 4), daylightTime, Qt::LocalTime); + << QDateTime(QDate(2004, 1, 1), standardTime) << (daySecs * 185) + << QDateTime(QDate(2004, 7, 4), daylightTime); QTest::newRow("cet2") - << QDateTime(QDate(2004, 1, 1), standardTime, Qt::LocalTime) << (daySecs * 366) - << QDateTime(QDate(2005, 1, 1), standardTime, Qt::LocalTime); + << QDateTime(QDate(2004, 1, 1), standardTime) << (daySecs * 366) + << QDateTime(QDate(2005, 1, 1), standardTime); QTest::newRow("cet3") - << QDateTime(QDate(1760, 1, 1), standardTime, Qt::LocalTime) << daySecs - << QDateTime(QDate(1760, 1, 2), standardTime, Qt::LocalTime); + << QDateTime(QDate(1760, 1, 1), standardTime) << daySecs + << QDateTime(QDate(1760, 1, 2), standardTime); QTest::newRow("cet4") - << QDateTime(QDate(1760, 1, 1), standardTime, Qt::LocalTime) << (daySecs * 185) - << QDateTime(QDate(1760, 7, 4), standardTime, Qt::LocalTime); + << QDateTime(QDate(1760, 1, 1), standardTime) << (daySecs * 185) + << QDateTime(QDate(1760, 7, 4), standardTime); QTest::newRow("cet5") - << QDateTime(QDate(1760, 1, 1), standardTime, Qt::LocalTime) << (daySecs * 366) - << QDateTime(QDate(1761, 1, 1), standardTime, Qt::LocalTime); + << QDateTime(QDate(1760, 1, 1), standardTime) << (daySecs * 366) + << QDateTime(QDate(1761, 1, 1), standardTime); QTest::newRow("cet6") - << QDateTime(QDate(4000, 1, 1), standardTime, Qt::LocalTime) << daySecs - << QDateTime(QDate(4000, 1, 2), standardTime, Qt::LocalTime); + << QDateTime(QDate(4000, 1, 1), standardTime) << daySecs + << QDateTime(QDate(4000, 1, 2), standardTime); QTest::newRow("cet7") - << QDateTime(QDate(4000, 1, 1), standardTime, Qt::LocalTime) << (daySecs * 185) - << QDateTime(QDate(4000, 7, 4), daylightTime, Qt::LocalTime); + << QDateTime(QDate(4000, 1, 1), standardTime) << (daySecs * 185) + << QDateTime(QDate(4000, 7, 4), daylightTime); QTest::newRow("cet8") - << QDateTime(QDate(4000, 1, 1), standardTime, Qt::LocalTime) << (daySecs * 366) - << QDateTime(QDate(4001, 1, 1), standardTime, Qt::LocalTime); + << QDateTime(QDate(4000, 1, 1), standardTime) << (daySecs * 366) + << QDateTime(QDate(4001, 1, 1), standardTime); QTest::newRow("cet9") - << QDateTime(QDate(4000, 1, 1), standardTime, Qt::LocalTime) << qint64(0) - << QDateTime(QDate(4000, 1, 1), standardTime, Qt::LocalTime); + << QDateTime(QDate(4000, 1, 1), standardTime) << qint64(0) + << QDateTime(QDate(4000, 1, 1), standardTime); } // Year sign change QTest::newRow("toNegative") - << QDateTime(QDate(1, 1, 1), QTime(0, 0), Qt::UTC) << qint64(-1) - << QDateTime(QDate(-1, 12, 31), QTime(23, 59, 59), Qt::UTC); + << QDateTime(QDate(1, 1, 1), QTime(0, 0), UTC) << qint64(-1) + << QDateTime(QDate(-1, 12, 31), QTime(23, 59, 59), UTC); QTest::newRow("toPositive") - << QDateTime(QDate(-1, 12, 31), QTime(23, 59, 59), Qt::UTC) << qint64(1) - << QDateTime(QDate(1, 1, 1), QTime(0, 0), Qt::UTC); + << QDateTime(QDate(-1, 12, 31), QTime(23, 59, 59), UTC) << qint64(1) + << QDateTime(QDate(1, 1, 1), QTime(0, 0), UTC); QTest::newRow("invalid") << QDateTime() << qint64(1) << QDateTime(); // Check Offset details are preserved QTest::newRow("offset0") - << QDateTime(QDate(2013, 1, 1), QTime(1, 2, 3), Qt::OffsetFromUTC, 60 * 60) + << QDateTime(QDate(2013, 1, 1), QTime(1, 2, 3), QTimeZone::fromSecondsAheadOfUtc(60 * 60)) << qint64(60 * 60) - << QDateTime(QDate(2013, 1, 1), QTime(2, 2, 3), Qt::OffsetFromUTC, 60 * 60); + << QDateTime(QDate(2013, 1, 1), QTime(2, 2, 3), QTimeZone::fromSecondsAheadOfUtc(60 * 60)); // Check last second of 1969 QTest::newRow("epoch-1s-utc") - << QDateTime(QDate(1970, 1, 1), QTime(0, 0), Qt::UTC) << qint64(-1) - << QDateTime(QDate(1969, 12, 31), QTime(23, 59, 59), Qt::UTC); + << QDate(1970, 1, 1).startOfDay(UTC) << qint64(-1) + << QDateTime(QDate(1969, 12, 31), QTime(23, 59, 59), UTC); QTest::newRow("epoch-1s-local") - << QDateTime(QDate(1970, 1, 1), QTime(0, 0)) << qint64(-1) + << QDate(1970, 1, 1).startOfDay() << qint64(-1) << QDateTime(QDate(1969, 12, 31), QTime(23, 59, 59)); QTest::newRow("epoch-1s-utc-as-local") - << QDate(1970, 1, 1).startOfDay(Qt::UTC).toLocalTime() << qint64(-1) - << QDateTime(QDate(1969, 12, 31), QTime(23, 59, 59), Qt::UTC).toLocalTime(); + << QDate(1970, 1, 1).startOfDay(UTC).toLocalTime() << qint64(-1) + << QDateTime(QDate(1969, 12, 31), QTime(23, 59, 59), UTC).toLocalTime(); // Overflow and Underflow const qint64 maxSeconds = std::numeric_limits<qint64>::max() / 1000; QTest::newRow("after-last") - << QDateTime::fromSecsSinceEpoch(maxSeconds, Qt::UTC) << qint64(1) << QDateTime(); + << QDateTime::fromSecsSinceEpoch(maxSeconds, UTC) << qint64(1) << QDateTime(); QTest::newRow("to-last") - << QDateTime::fromSecsSinceEpoch(maxSeconds - 1, Qt::UTC) << qint64(1) - << QDateTime::fromSecsSinceEpoch(maxSeconds, Qt::UTC); + << QDateTime::fromSecsSinceEpoch(maxSeconds - 1, UTC) << qint64(1) + << QDateTime::fromSecsSinceEpoch(maxSeconds, UTC); QTest::newRow("before-first") - << QDateTime::fromSecsSinceEpoch(-maxSeconds, Qt::UTC) << qint64(-1) << QDateTime(); + << QDateTime::fromSecsSinceEpoch(-maxSeconds, UTC) << qint64(-1) << QDateTime(); QTest::newRow("to-first") - << QDateTime::fromSecsSinceEpoch(1 - maxSeconds, Qt::UTC) << qint64(-1) - << QDateTime::fromSecsSinceEpoch(-maxSeconds, Qt::UTC); + << QDateTime::fromSecsSinceEpoch(1 - maxSeconds, UTC) << qint64(-1) + << QDateTime::fromSecsSinceEpoch(-maxSeconds, UTC); + +#if QT_CONFIG(timezone) + if (const QTimeZone cet("Europe/Oslo"); cet.isValid()) { + QTest::newRow("CET-spring-forward") + << QDateTime(QDate(2023, 3, 26), QTime(1, 30), cet) << qint64(60 * 60) + << QDateTime(QDate(2023, 3, 26), QTime(3, 30), cet); + QTest::newRow("CET-fall-back") + << QDateTime(QDate(2023, 10, 29), QTime(1, 30), cet) << qint64(3 * 60 * 60) + << QDateTime(QDate(2023, 10, 29), QTime(3, 30), cet); + } +# ifndef INADEQUATE_TZ_DATA + const QTimeZone lint("Pacific/Kiritimati"); + if (lint.isValid()) { + // Line Islands Time skipped Sat 1994-12-31: + QTest::newRow("Kiritimati-day-off") + << QDateTime(QDate(1994, 12, 30), QTime(23, 30), lint) << qint64(60 * 60) + << QDateTime(QDate(1995, 1, 1), QTime(0, 30), lint); + } +# ifndef Q_OS_DARWIN + if (const QTimeZone alaska("America/Anchorage"); alaska.isValid()) { + // On Julian date 1867, Sat Oct 7 (at 14:31 local solar mean time for + // Anchorage, 15:30 LMT in Sitka, which hosted the transfer ceremony) + // Russia sold Alaska to the USA, which changed the calendar to + // Gregorian, hence the date to Fri Oct 18. Contrast addDays(). + const QDate sat(1867, 10, 19); + Q_ASSERT(sat == QDate(1867, 10, 7, QCalendar(QCalendar::System::Julian))); + // At the start of the day, it was Sat 7th; by evening it was Fri 18th; + // then the next day was Sat 19th. + QTest::newRow("Alaska-Day") + // The actual morning of the hand-over: + << QDateTime(sat, QTime(6, 0), alaska) << qint64(12 * 60 * 60) + // The evening of the same day. + << QDateTime(sat, QTime(18, 0), alaska).addDays(-1); + } +# endif // Darwin +# endif // inadequate zone data +#endif // timezone } void tst_QDateTime::addSecs_data() @@ -1426,9 +1678,9 @@ void tst_QDateTime::addSecs_data() const qint64 maxSeconds = std::numeric_limits<qint64>::max() / 1000; // Results would be representable, but the step isn't QTest::newRow("leap-up") - << QDateTime::fromSecsSinceEpoch(-1, Qt::UTC) << 1 + maxSeconds << QDateTime(); + << QDateTime::fromSecsSinceEpoch(-1, UTC) << 1 + maxSeconds << QDateTime(); QTest::newRow("leap-down") - << QDateTime::fromSecsSinceEpoch(1, Qt::UTC) << -1 - maxSeconds << QDateTime(); + << QDateTime::fromSecsSinceEpoch(1, UTC) << -1 - maxSeconds << QDateTime(); } void tst_QDateTime::addSecs() @@ -1437,14 +1689,30 @@ void tst_QDateTime::addSecs() QFETCH(const qint64, nsecs); QFETCH(const QDateTime, result); QDateTime test = dt.addSecs(nsecs); + QDateTime test2 = dt + std::chrono::seconds(nsecs); + QDateTime test3 = dt; + test3 += std::chrono::seconds(nsecs); if (!result.isValid()) { QVERIFY(!test.isValid()); + QVERIFY(!test2.isValid()); + QVERIFY(!test3.isValid()); } else { QCOMPARE(test, result); + QCOMPARE(test2, result); + QCOMPARE(test3, result); QCOMPARE(test.timeSpec(), dt.timeSpec()); - if (test.timeSpec() == Qt::OffsetFromUTC) + QCOMPARE(test2.timeSpec(), dt.timeSpec()); + QCOMPARE(test3.timeSpec(), dt.timeSpec()); + if (test.timeSpec() == Qt::OffsetFromUTC) { QCOMPARE(test.offsetFromUtc(), dt.offsetFromUtc()); + QCOMPARE(test2.offsetFromUtc(), dt.offsetFromUtc()); + QCOMPARE(test3.offsetFromUtc(), dt.offsetFromUtc()); + } QCOMPARE(result.addSecs(-nsecs), dt); + QCOMPARE(result - std::chrono::seconds(nsecs), dt); + test3 -= std::chrono::seconds(nsecs); + QCOMPARE(test3, dt); + QCOMPARE(dt.secsTo(result), nsecs); } } @@ -1454,18 +1722,29 @@ void tst_QDateTime::addMSecs() QFETCH(const qint64, nsecs); QFETCH(const QDateTime, result); - QDateTime test = dt.addMSecs(qint64(nsecs) * 1000); - if (!result.isValid()) { - QVERIFY(!test.isValid()); - } else { - QCOMPARE(test, result); - QCOMPARE(test.timeSpec(), dt.timeSpec()); - if (test.timeSpec() == Qt::OffsetFromUTC) - QCOMPARE(test.offsetFromUtc(), dt.offsetFromUtc()); - QCOMPARE(result.addMSecs(qint64(-nsecs) * 1000), dt); - } + const auto verify = [&](const QDateTime &test) { + if (!result.isValid()) { + QVERIFY(!test.isValid()); + } else { + QCOMPARE(test, result); + QCOMPARE(test.timeSpec(), dt.timeSpec()); + if (test.timeSpec() == Qt::OffsetFromUTC) + QCOMPARE(test.offsetFromUtc(), dt.offsetFromUtc()); + QCOMPARE(result.addMSecs(qint64(-nsecs) * 1000), dt); + } + }; +#define VERIFY(datum) \ + verify(datum); \ + if (QTest::currentTestFailed()) \ + return + + VERIFY(dt.addMSecs(qint64(nsecs) * 1000)); + VERIFY(dt.addDuration(std::chrono::seconds(nsecs))); + VERIFY(dt.addDuration(std::chrono::milliseconds(nsecs * 1000))); +#undef VERIFY } +#if QT_DEPRECATED_SINCE(6, 9) void tst_QDateTime::toTimeSpec_data() { if (!zoneIsCET) @@ -1478,56 +1757,64 @@ void tst_QDateTime::toTimeSpec_data() QTime localStandardTime(5, 20, 30); QTime localDaylightTime(6, 20, 30); - QTest::newRow("winter1") << QDateTime(QDate(2004, 1, 1), utcTime, Qt::UTC) - << QDateTime(QDate(2004, 1, 1), localStandardTime, Qt::LocalTime); - QTest::newRow("winter2") << QDateTime(QDate(2004, 2, 29), utcTime, Qt::UTC) - << QDateTime(QDate(2004, 2, 29), localStandardTime, Qt::LocalTime); + QTest::newRow("winter1") + << QDateTime(QDate(2004, 1, 1), utcTime, UTC) + << QDateTime(QDate(2004, 1, 1), localStandardTime); + QTest::newRow("winter2") + << QDateTime(QDate(2004, 2, 29), utcTime, UTC) + << QDateTime(QDate(2004, 2, 29), localStandardTime); QTest::newRow("winter3") - << QDateTime(QDate(1760, 2, 29), utcTime, Qt::UTC) + << QDateTime(QDate(1760, 2, 29), utcTime, UTC) << QDateTime(QDate(1760, 2, 29), localStandardTime.addSecs(preZoneFix)); - QTest::newRow("winter4") << QDateTime(QDate(6000, 2, 29), utcTime, Qt::UTC) - << QDateTime(QDate(6000, 2, 29), localStandardTime, Qt::LocalTime); + QTest::newRow("winter4") + << QDateTime(QDate(6000, 2, 29), utcTime, UTC) + << QDateTime(QDate(6000, 2, 29), localStandardTime); // Test mktime boundaries (1970 - 2038) and adjustDate(). QTest::newRow("1969/12/31 23:00 UTC") - << QDateTime(QDate(1969, 12, 31), QTime(23, 0), Qt::UTC) - << QDateTime(QDate(1970, 1, 1), QTime(0, 0), Qt::LocalTime); + << QDateTime(QDate(1969, 12, 31), QTime(23, 0), UTC) + << QDateTime(QDate(1970, 1, 1), QTime(0, 0)); QTest::newRow("1969/12/31 23:59:59 UTC") - << QDateTime(QDate(1969, 12, 31), QTime(23, 59, 59), Qt::UTC) - << QDateTime(QDate(1970, 1, 1), QTime(0, 59, 59), Qt::LocalTime); + << QDateTime(QDate(1969, 12, 31), QTime(23, 59, 59), UTC) + << QDateTime(QDate(1970, 1, 1), QTime(0, 59, 59)); QTest::newRow("2037/12/31 23:00 UTC") - << QDateTime(QDate(2037, 12, 31), QTime(23, 0), Qt::UTC) - << QDateTime(QDate(2038, 1, 1), QTime(0, 0), Qt::LocalTime); + << QDateTime(QDate(2037, 12, 31), QTime(23, 0), UTC) + << QDateTime(QDate(2038, 1, 1), QTime(0, 0)); QTest::newRow("-271821/4/20 00:00 UTC (JavaScript min date, start of day)") - << QDateTime(QDate(-271821, 4, 20), QTime(0, 0), Qt::UTC) + << QDateTime(QDate(-271821, 4, 20), QTime(0, 0), UTC) << QDateTime(QDate(-271821, 4, 20), QTime(1, 0)).addSecs(preZoneFix); QTest::newRow("-271821/4/20 23:00 UTC (JavaScript min date, end of day)") - << QDateTime(QDate(-271821, 4, 20), QTime(23, 0), Qt::UTC) + << QDateTime(QDate(-271821, 4, 20), QTime(23, 0), UTC) << QDateTime(QDate(-271821, 4, 21), QTime(0, 0)).addSecs(preZoneFix); if (zoneIsCET) { - QTest::newRow("summer1") << QDateTime(QDate(2004, 6, 30), utcTime, Qt::UTC) - << QDateTime(QDate(2004, 6, 30), localDaylightTime, Qt::LocalTime); + QTest::newRow("summer1") + << QDateTime(QDate(2004, 6, 30), utcTime, UTC) + << QDateTime(QDate(2004, 6, 30), localDaylightTime); QTest::newRow("summer2") - << QDateTime(QDate(1760, 6, 30), utcTime, Qt::UTC) + << QDateTime(QDate(1760, 6, 30), utcTime, UTC) << QDateTime(QDate(1760, 6, 30), localStandardTime.addSecs(preZoneFix)); - QTest::newRow("summer3") << QDateTime(QDate(4000, 6, 30), utcTime, Qt::UTC) - << QDateTime(QDate(4000, 6, 30), localDaylightTime, Qt::LocalTime); + QTest::newRow("summer3") + << QDateTime(QDate(4000, 6, 30), utcTime, UTC) + << QDateTime(QDate(4000, 6, 30), localDaylightTime); QTest::newRow("275760/9/23 00:00 UTC (JavaScript max date, start of day)") - << QDateTime(QDate(275760, 9, 23), QTime(0, 0), Qt::UTC) - << QDateTime(QDate(275760, 9, 23), QTime(2, 0), Qt::LocalTime); + << QDate(275760, 9, 23).startOfDay(UTC) + << QDateTime(QDate(275760, 9, 23), QTime(2, 0)); QTest::newRow("275760/9/23 22:00 UTC (JavaScript max date, end of day)") - << QDateTime(QDate(275760, 9, 23), QTime(22, 0), Qt::UTC) - << QDateTime(QDate(275760, 9, 24), QTime(0, 0), Qt::LocalTime); + << QDateTime(QDate(275760, 9, 23), QTime(22, 0), UTC) + << QDate(275760, 9, 24).startOfDay(); } - QTest::newRow("msec") << QDateTime(QDate(4000, 6, 30), utcTime.addMSecs(1), Qt::UTC) - << QDateTime(QDate(4000, 6, 30), localDaylightTime.addMSecs(1), Qt::LocalTime); + QTest::newRow("msec") + << QDateTime(QDate(4000, 6, 30), utcTime.addMSecs(1), UTC) + << QDateTime(QDate(4000, 6, 30), localDaylightTime.addMSecs(1)); } +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED void tst_QDateTime::toTimeSpec() { QFETCH(QDateTime, fromUtc); @@ -1586,11 +1873,6 @@ void tst_QDateTime::toTimeSpec() QCOMPARE(localToOffset.toTimeSpec(Qt::LocalTime), fromLocal); } -void tst_QDateTime::toLocalTime_data() -{ - toTimeSpec_data(); -} - void tst_QDateTime::toLocalTime() { QFETCH(QDateTime, fromUtc); @@ -1601,11 +1883,6 @@ void tst_QDateTime::toLocalTime() QCOMPARE(fromUtc.toLocalTime(), fromLocal.toLocalTime()); } -void tst_QDateTime::toUTC_data() -{ - toTimeSpec_data(); -} - void tst_QDateTime::toUTC() { QFETCH(QDateTime, fromUtc); @@ -1625,6 +1902,8 @@ void tst_QDateTime::toUTC_extra() QString t = dt.toUTC().toString("zzz"); QCOMPARE(s, t); } +QT_WARNING_POP +#endif // 6.9 deprecation void tst_QDateTime::daysTo() { @@ -1656,11 +1935,11 @@ void tst_QDateTime::secsTo_data() addSecs_data(); QTest::newRow("disregard milliseconds #1") - << QDateTime(QDate(2012, 3, 7), QTime(0, 58, 0, 0)) << qint64(60) + << QDateTime(QDate(2012, 3, 7), QTime(0, 58)) << qint64(60) << QDateTime(QDate(2012, 3, 7), QTime(0, 59, 0, 400)); QTest::newRow("disregard milliseconds #2") - << QDateTime(QDate(2012, 3, 7), QTime(0, 59, 0, 0)) << qint64(60) + << QDateTime(QDate(2012, 3, 7), QTime(0, 59)) << qint64(60) << QDateTime(QDate(2012, 3, 7), QTime(1, 0, 0, 400)); } @@ -1685,11 +1964,6 @@ void tst_QDateTime::secsTo() } } -void tst_QDateTime::msecsTo_data() -{ - addMSecs_data(); -} - void tst_QDateTime::msecsTo() { QFETCH(const QDateTime, dt); @@ -1698,7 +1972,9 @@ void tst_QDateTime::msecsTo() if (result.isValid()) { QCOMPARE(dt.msecsTo(result), qint64(nsecs) * 1000); + QCOMPARE(result - dt, std::chrono::milliseconds(nsecs * 1000)); QCOMPARE(result.msecsTo(dt), -qint64(nsecs) * 1000); + QCOMPARE(dt - result, -std::chrono::milliseconds(nsecs * 1000)); QVERIFY((dt == result) == (0 == (qint64(nsecs) * 1000))); QVERIFY((dt != result) == (0 != (qint64(nsecs) * 1000))); QVERIFY((dt < result) == (0 < (qint64(nsecs) * 1000))); @@ -1707,10 +1983,17 @@ void tst_QDateTime::msecsTo() QVERIFY((dt >= result) == (0 >= (qint64(nsecs) * 1000))); } else { QVERIFY(dt.msecsTo(result) == 0); + QCOMPARE(result - dt, std::chrono::milliseconds(0)); QVERIFY(result.msecsTo(dt) == 0); + QCOMPARE(dt - result, std::chrono::milliseconds(0)); } } +void tst_QDateTime::orderingCompiles() +{ + QTestPrivate::testAllComparisonOperatorsCompile<QDateTime>(); +} + void tst_QDateTime::currentDateTime() { time_t buf1, buf2; @@ -1730,30 +2013,32 @@ void tst_QDateTime::currentDateTime() // 1 second difference from the more accurate method used by QDateTime::currentDateTime() upperBound = upperBound.addSecs(2); - QString details = QString("\n" - "lowerBound: %1\n" - "dt1: %2\n" - "dt2: %3\n" - "dt3: %4\n" - "upperBound: %5\n") - .arg(lowerBound.toSecsSinceEpoch()) - .arg(dt1.toSecsSinceEpoch()) - .arg(dt2.toSecsSinceEpoch()) - .arg(dt3.toSecsSinceEpoch()) - .arg(upperBound.toSecsSinceEpoch()); - - QVERIFY2(lowerBound < upperBound, qPrintable(details)); + auto reporter = qScopeGuard([=]() { + qInfo("\n" + "lowerBound: %lld\n" + "dt1: %lld\n" + "dt2: %lld\n" + "dt3: %lld\n" + "upperBound: %lld\n", + lowerBound.toSecsSinceEpoch(), + dt1.toSecsSinceEpoch(), + dt2.toSecsSinceEpoch(), + dt3.toSecsSinceEpoch(), + upperBound.toSecsSinceEpoch()); + }); + + QCOMPARE_LT(lowerBound, upperBound); + QCOMPARE_LE(lowerBound, dt1); + QCOMPARE_LT(dt1, upperBound); + QCOMPARE_LE(lowerBound, dt2); + QCOMPARE_LT(dt2, upperBound); + QCOMPARE_LE(lowerBound, dt3); + QCOMPARE_LT(dt3, upperBound); + reporter.dismiss(); - QVERIFY2(lowerBound <= dt1, qPrintable(details)); - QVERIFY2(dt1 < upperBound, qPrintable(details)); - QVERIFY2(lowerBound <= dt2, qPrintable(details)); - QVERIFY2(dt2 < upperBound, qPrintable(details)); - QVERIFY2(lowerBound <= dt3, qPrintable(details)); - QVERIFY2(dt3 < upperBound, qPrintable(details)); - - QVERIFY(dt1.timeSpec() == Qt::LocalTime); - QVERIFY(dt2.timeSpec() == Qt::LocalTime); - QVERIFY(dt3.timeSpec() == Qt::UTC); + QCOMPARE(dt1.timeSpec(), Qt::LocalTime); + QCOMPARE(dt2.timeSpec(), Qt::LocalTime); + QCOMPARE(dt3.timeSpec(), Qt::UTC); } void tst_QDateTime::currentDateTimeUtc() @@ -1776,30 +2061,32 @@ void tst_QDateTime::currentDateTimeUtc() // 1 second difference from the more accurate method used by QDateTime::currentDateTime() upperBound = upperBound.addSecs(2); - QString details = QString("\n" - "lowerBound: %1\n" - "dt1: %2\n" - "dt2: %3\n" - "dt3: %4\n" - "upperBound: %5\n") - .arg(lowerBound.toSecsSinceEpoch()) - .arg(dt1.toSecsSinceEpoch()) - .arg(dt2.toSecsSinceEpoch()) - .arg(dt3.toSecsSinceEpoch()) - .arg(upperBound.toSecsSinceEpoch()); - - QVERIFY2(lowerBound < upperBound, qPrintable(details)); + auto reporter = qScopeGuard([=]() { + qInfo("\n" + "lowerBound: %lld\n" + "dt1: %lld\n" + "dt2: %lld\n" + "dt3: %lld\n" + "upperBound: %lld\n", + lowerBound.toSecsSinceEpoch(), + dt1.toSecsSinceEpoch(), + dt2.toSecsSinceEpoch(), + dt3.toSecsSinceEpoch(), + upperBound.toSecsSinceEpoch()); + }); + + QCOMPARE_LT(lowerBound, upperBound); + QCOMPARE_LE(lowerBound, dt1); + QCOMPARE_LT(dt1, upperBound); + QCOMPARE_LE(lowerBound, dt2); + QCOMPARE_LT(dt2, upperBound); + QCOMPARE_LE(lowerBound, dt3); + QCOMPARE_LT(dt3, upperBound); + reporter.dismiss(); - QVERIFY2(lowerBound <= dt1, qPrintable(details)); - QVERIFY2(dt1 < upperBound, qPrintable(details)); - QVERIFY2(lowerBound <= dt2, qPrintable(details)); - QVERIFY2(dt2 < upperBound, qPrintable(details)); - QVERIFY2(lowerBound <= dt3, qPrintable(details)); - QVERIFY2(dt3 < upperBound, qPrintable(details)); - - QVERIFY(dt1.timeSpec() == Qt::UTC); - QVERIFY(dt2.timeSpec() == Qt::LocalTime); - QVERIFY(dt3.timeSpec() == Qt::UTC); + QCOMPARE(dt1.timeSpec(), Qt::UTC); + QCOMPARE(dt2.timeSpec(), Qt::LocalTime); + QCOMPARE(dt3.timeSpec(), Qt::UTC); } void tst_QDateTime::currentDateTimeUtc2() @@ -1898,7 +2185,7 @@ void tst_QDateTime::daylightSavingsTimeChange() QFETCH(int, months); // First with simple construction - QDateTime dt = QDateTime(outDST, QTime(0, 0, 0), Qt::LocalTime); + QDateTime dt = outDST.startOfDay(); int outDSTsecs = dt.toSecsSinceEpoch(); dt.setDate(inDST); @@ -1923,29 +2210,29 @@ void tst_QDateTime::daylightSavingsTimeChange() // now using fromSecsSinceEpoch dt = QDateTime::fromSecsSinceEpoch(outDSTsecs); - QCOMPARE(dt, QDateTime(outDST, QTime(0, 0, 0))); + QCOMPARE(dt, outDST.startOfDay()); dt.setDate(inDST); dt = dt.addSecs(60); - QCOMPARE(dt, QDateTime(inDST, QTime(0, 1, 0))); + QCOMPARE(dt, QDateTime(inDST, QTime(0, 1))); // using addMonths: dt = dt.addMonths(months).addSecs(60); - QCOMPARE(dt, QDateTime(outDST, QTime(0, 2, 0))); + QCOMPARE(dt, QDateTime(outDST, QTime(0, 2))); // back again: dt = dt.addMonths(-months).addSecs(60); - QCOMPARE(dt, QDateTime(inDST, QTime(0, 3, 0))); + QCOMPARE(dt, QDateTime(inDST, QTime(0, 3))); // using addDays: dt = dt.addDays(days).addSecs(60); - QCOMPARE(dt, QDateTime(outDST, QTime(0, 4, 0))); + QCOMPARE(dt, QDateTime(outDST, QTime(0, 4))); // back again: dt = dt.addDays(-days).addSecs(60); - QCOMPARE(dt, QDateTime(inDST, QTime(0, 5, 0))); + QCOMPARE(dt, QDateTime(inDST, QTime(0, 5))); // Now use the result of a UTC -> LocalTime conversion - dt = QDateTime(outDST, QTime(0, 0), Qt::LocalTime).toUTC(); - dt = QDateTime(dt.date(), dt.time(), Qt::UTC).toLocalTime(); + dt = outDST.startOfDay().toUTC(); + dt = QDateTime(dt.date(), dt.time(), UTC).toLocalTime(); QCOMPARE(dt, QDateTime(outDST, QTime(0, 0))); // using addDays: @@ -1970,6 +2257,7 @@ void tst_QDateTime::daylightSavingsTimeChange() void tst_QDateTime::springForward_data() { + QTest::addColumn<QTimeZone>("zone"); QTest::addColumn<QDate>("day"); // day of DST transition QTest::addColumn<QTime>("time"); // in the "missing hour" QTest::addColumn<int>("step"); // days to step; +ve from before, -ve from after @@ -1983,67 +2271,136 @@ void tst_QDateTime::springForward_data() document any such conflicts, if discovered. See http://www.timeanddate.com/time/zones/ for data on more candidates to - test. - */ + test. Note, however, that the IANA DB disagrees with it for some zones, + and is authoritative. + */ - uint winter = QDate(2015, 1, 1).startOfDay().toSecsSinceEpoch(); - uint summer = QDate(2015, 7, 1).startOfDay().toSecsSinceEpoch(); + const QTimeZone local(QTimeZone::LocalTime); + const uint winter = QDate(2015, 1, 1).startOfDay(local).toSecsSinceEpoch(); + const uint summer = QDate(2015, 7, 1).startOfDay(local).toSecsSinceEpoch(); if (winter == 1420066800 && summer == 1435701600) { - QTest::newRow("CET from day before") << QDate(2015, 3, 29) << QTime(2, 30, 0) << 1 << 60; - QTest::newRow("CET from day after") << QDate(2015, 3, 29) << QTime(2, 30, 0) << -1 << 120; + QTest::newRow("Local (CET) from day before") + << local << QDate(2015, 3, 29) << QTime(2, 30) << 1 << 60; + QTest::newRow("Local (CET) from day after") + << local << QDate(2015, 3, 29) << QTime(2, 30) << -1 << 120; } else if (winter == 1420063200 && summer == 1435698000) { - // e.g. Finland, where our CI runs ... - QTest::newRow("EET from day before") << QDate(2015, 3, 29) << QTime(3, 30, 0) << 1 << 120; - QTest::newRow("EET from day after") << QDate(2015, 3, 29) << QTime(3, 30, 0) << -1 << 180; + // EET: but there's some variation in the date and time. + // Asia/{Amman,Beirut,Gaza,Hebron}, Europe/Chisinau and Israel: at start of + QDate date(2015, 3, 29); // Sunday by default. + QTime time(0, 30); + if (auto thursday = QDate(2015, 3, 26); thursday.startOfDay(local).time() > time) { + // Asia/Damascus: start of March 26th. + date = thursday; + } else if (auto friday = QDate(2015, 3, 27); friday.startOfDay(local).time() > time) { + // Israel, Asia/{Jerusalem,Tel_Aviv}: start of March 27th (IANA DB). + date = friday; + } else if (friday.startOfDay(local).addSecs(2 * 60 * 60).time() == QTime(3, 0)) { + // Israel, Asia/{Jerusalem,Tel_Aviv} according to glibc at 02:00 on March 27th. + date = friday; + time = QTime(2, 30); + } else if (date.startOfDay(local).time() < time) { + // Most of Europeean EET, e.g. Finland. + time = QTime(3, 30); + } + QTest::newRow("Local (EET) from day before") + << local << date << time << 1 << 120; + QTest::newRow("Local (EET) from day after") + << local << date << time << -1 << 180; } else if (winter == 1420070400 && summer == 1435705200) { // Western European Time, WET/WEST; a.k.a. GMT/BST - QTest::newRow("WET from day before") << QDate(2015, 3, 29) << QTime(1, 30, 0) << 1 << 0; - QTest::newRow("WET from day after") << QDate(2015, 3, 29) << QTime(1, 30, 0) << -1 << 60; + QTest::newRow("Local (WET) from day before") + << local << QDate(2015, 3, 29) << QTime(1, 30) << 1 << 0; + QTest::newRow("Local (WET) from day after") + << local << QDate(2015, 3, 29) << QTime(1, 30) << -1 << 60; } else if (winter == 1420099200 && summer == 1435734000) { // Western USA, Canada: Pacific Time (e.g. US/Pacific) - QTest::newRow("PT from day before") << QDate(2015, 3, 8) << QTime(2, 30, 0) << 1 << -480; - QTest::newRow("PT from day after") << QDate(2015, 3, 8) << QTime(2, 30, 0) << -1 << -420; + QDate date(2015, 3, 8); + // America/Ensenada did its transition on April 5th, like the rest of Mexico. + if (QDate(2015, 4, 1).startOfDay().toSecsSinceEpoch() == 1427875200) + date = QDate(2015, 4, 5); + QTest::newRow("Local (PT) from day before") + << local << date << QTime(2, 30) << 1 << -480; + QTest::newRow("Local (PT) from day after") + << local << date << QTime(2, 30) << -1 << -420; } else if (winter == 1420088400 && summer == 1435723200) { // Eastern USA, Canada: Eastern Time (e.g. US/Eastern) - QTest::newRow("ET from day before") << QDate(2015, 3, 8) << QTime(2, 30, 0) << 1 << -300; - QTest::newRow("ET from day after") << QDate(2015, 3, 8) << QTime(2, 30, 0) << -1 << -240; + // Havana matches offset and date, but at midnight. + const QTime start = QDate(2015, 3, 8).startOfDay(local).time(); + const QTime when = start == QTime(0, 0) ? QTime(2, 30) : QTime(0, 30); + QTest::newRow("Local(ET) from day before") + << local << QDate(2015, 3, 8) << when << 1 << -300; + QTest::newRow("Local(ET) from day after") + << local << QDate(2015, 3, 8) << when << -1 << -240; +#if !QT_CONFIG(timezone) } else { // Includes the numbers you need to test for your zone, as above: QString msg(QString::fromLatin1("No spring forward test data for this TZ (%1, %2)" ).arg(winter).arg(summer)); QSKIP(qPrintable(msg)); +#endif + } +#if QT_CONFIG(timezone) + if (const QTimeZone cet("Europe/Oslo"); cet.isValid()) { + QTest::newRow("CET from day before") + << cet << QDate(2015, 3, 29) << QTime(2, 30) << 1 << 60; + QTest::newRow("CET from day after") + << cet << QDate(2015, 3, 29) << QTime(2, 30) << -1 << 120; + } + if (const QTimeZone eet("Europe/Helsinki"); eet.isValid()) { + QTest::newRow("EET from day before") + << eet << QDate(2015, 3, 29) << QTime(3, 30) << 1 << 120; + QTest::newRow("EET from day after") + << eet << QDate(2015, 3, 29) << QTime(3, 30) << -1 << 180; + } + if (const QTimeZone wet("Europe/Lisbon"); wet.isValid()) { + QTest::newRow("WET from day before") + << wet << QDate(2015, 3, 29) << QTime(1, 30) << 1 << 0; + QTest::newRow("WET from day after") + << wet << QDate(2015, 3, 29) << QTime(1, 30) << -1 << 60; } + if (const QTimeZone pacific("America/Vancouver"); pacific.isValid()) { + QTest::newRow("PT from day before") + << pacific << QDate(2015, 3, 8) << QTime(2, 30) << 1 << -480; + QTest::newRow("PT from day after") + << pacific << QDate(2015, 3, 8) << QTime(2, 30) << -1 << -420; + } + if (const QTimeZone eastern("America/Ottawa"); eastern.isValid()) { + QTest::newRow("ET from day before") + << eastern << QDate(2015, 3, 8) << QTime(2, 30) << 1 << -300; + QTest::newRow("ET from day after") + << eastern << QDate(2015, 3, 8) << QTime(2, 30) << -1 << -240; + } +#endif } void tst_QDateTime::springForward() { + QFETCH(QTimeZone, zone); QFETCH(QDate, day); QFETCH(QTime, time); QFETCH(int, step); QFETCH(int, adjust); - QDateTime direct = QDateTime(day.addDays(-step), time, Qt::LocalTime).addDays(step); - if (direct.isValid()) { // mktime() may deem a time in the gap invalid - QCOMPARE(direct.date(), day); - QCOMPARE(direct.time().minute(), time.minute()); - QCOMPARE(direct.time().second(), time.second()); - int off = direct.time().hour() - time.hour(); - QVERIFY(off == 1 || off == -1); - // Note: function doc claims always +1, but this should be reviewed ! - } - - // Repeat, but getting there via .toLocalTime(): - QDateTime detour = QDateTime(day.addDays(-step), - time.addSecs(-60 * adjust), - Qt::UTC).toLocalTime(); + QDateTime direct = QDateTime(day.addDays(-step), time, zone).addDays(step); + QVERIFY(direct.isValid()); + QCOMPARE(direct.date(), day); + QCOMPARE(direct.time().minute(), time.minute()); + QCOMPARE(direct.time().second(), time.second()); + const int off = step < 0 ? -1 : 1; + QCOMPARE(direct.time().hour() - time.hour(), off); + // adjust is the offset on the other side of the gap: + QCOMPARE(direct.offsetFromUtc(), (adjust + off * 60) * 60); + + // Repeat, but getting there via .toTimeZone(). Apply adjust to datetime, + // not time, as the time wraps round if the adjustment crosses midnight. + QDateTime detour = QDateTime(day.addDays(-step), time, + UTC).addSecs(-60 * adjust).toTimeZone(zone); QCOMPARE(detour.time(), time); detour = detour.addDays(step); // Insist on consistency: - if (direct.isValid()) - QCOMPARE(detour, direct); - else - QVERIFY(!detour.isValid()); + QCOMPARE(detour, direct); + QCOMPARE(detour.offsetFromUtc(), direct.offsetFromUtc()); } void tst_QDateTime::operator_eqeq_data() @@ -2057,14 +2414,14 @@ void tst_QDateTime::operator_eqeq_data() QDateTime dateTime1a = dateTime1.addMSecs(1); QDateTime dateTime2(QDate(2012, 20, 6), QTime(14, 33, 2, 500)); // Invalid QDateTime dateTime2a = dateTime2.addMSecs(-1); // Still invalid - QDateTime dateTime3(QDate(1970, 1, 1), QTime(0, 0), Qt::UTC); // UTC epoch + QDateTime dateTime3(QDate(1970, 1, 1), QTime(0, 0), UTC); // UTC epoch QDateTime dateTime3a = dateTime3.addDays(1); QDateTime dateTime3b = dateTime3.addDays(-1); // Ensure that different times may be equal when considering timezone. QDateTime dateTime3c(dateTime3.addSecs(3600)); - dateTime3c.setOffsetFromUtc(3600); + dateTime3c.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(3600)); QDateTime dateTime3d(dateTime3.addSecs(-3600)); - dateTime3d.setOffsetFromUtc(-3600); + dateTime3d.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(-3600)); QDateTime dateTime3e(dateTime3.date(), dateTime3.time()); // Local time's epoch QTest::newRow("data0") << dateTime1 << dateTime1 << true << false; @@ -2080,7 +2437,7 @@ void tst_QDateTime::operator_eqeq_data() QTest::newRow("data10") << dateTime3 << dateTime3c << true << false; QTest::newRow("data11") << dateTime3 << dateTime3d << true << false; QTest::newRow("data12") << dateTime3c << dateTime3d << true << false; - if (localTimeType == LocalTimeIsUtc) + if (epochTimeType == LocalTimeIsUtc) QTest::newRow("data13") << dateTime3 << dateTime3e << true << false; // ... but a zone (sometimes) ahead of or behind UTC (e.g. Europe/London) // might agree with UTC about the epoch, all the same. @@ -2090,8 +2447,8 @@ void tst_QDateTime::operator_eqeq_data() if (zoneIsCET) { QTest::newRow("data14") - << QDateTime(QDate(2004, 1, 2), QTime(2, 2, 3), Qt::LocalTime) - << QDateTime(QDate(2004, 1, 2), QTime(1, 2, 3), Qt::UTC) << true << true; + << QDateTime(QDate(2004, 1, 2), QTime(2, 2, 3)) + << QDateTime(QDate(2004, 1, 2), QTime(1, 2, 3), UTC) << true << true; QTest::newRow("local-fall-back") // Sun, 31 Oct 2004, 02:30, both ways round: << QDateTime::fromMSecsSinceEpoch(Q_INT64_C(1099186200000)) << QDateTime::fromMSecsSinceEpoch(Q_INT64_C(1099182600000)) @@ -2115,23 +2472,16 @@ void tst_QDateTime::operator_eqeq() QFETCH(bool, expectEqual); QFETCH(bool, checkEuro); - QVERIFY(dt1 == dt1); - QVERIFY(!(dt1 != dt1)); - - QVERIFY(dt2 == dt2); - QVERIFY(!(dt2 != dt2)); + QT_TEST_EQUALITY_OPS(dt1, dt1, true); + QT_TEST_EQUALITY_OPS(dt2, dt2, true); + QT_TEST_EQUALITY_OPS(dt1, dt2, expectEqual); QVERIFY(dt1 != QDateTime::currentDateTime()); QVERIFY(dt2 != QDateTime::currentDateTime()); QVERIFY(dt1.toUTC() == dt1.toUTC()); - bool equal = dt1 == dt2; - QCOMPARE(equal, expectEqual); - bool notEqual = dt1 != dt2; - QCOMPARE(notEqual, !expectEqual); - - if (equal) + if (expectEqual) QVERIFY(qHash(dt1) == qHash(dt2)); if (checkEuro && zoneIsCET) { @@ -2140,6 +2490,64 @@ void tst_QDateTime::operator_eqeq() } } +void tst_QDateTime::ordering_data() +{ + QTest::addColumn<QDateTime>("left"); + QTest::addColumn<QDateTime>("right"); + QTest::addColumn<Qt::weak_ordering>("expectedOrdering"); + + Q_CONSTINIT static const auto constructName = [](const QDateTime &dt) -> QByteArray { + if (dt.isNull()) + return "null"; + if (!dt.isValid()) + return "invalid"; + return dt.toString(Qt::ISODateWithMs).toLatin1(); + }; + + Q_CONSTINIT static const auto generateRow = + [](const QDateTime &left, const QDateTime &right, Qt::weak_ordering ordering) { + const QByteArray leftStr = constructName(left); + const QByteArray rightStr = constructName(right); + QTest::addRow("%s_vs_%s", leftStr.constData(), rightStr.constData()) + << left << right << ordering; + }; + + QDateTime june(QDate(2012, 6, 20), QTime(14, 33, 2, 500)); + QDateTime juneLater = june.addMSecs(1); + QDateTime badDay(QDate(2012, 20, 6), QTime(14, 33, 2, 500)); // Invalid + QDateTime epoch(QDate(1970, 1, 1), QTime(0, 0), UTC); // UTC epoch + QDateTime nextDay = epoch.addDays(1); + QDateTime prevDay = epoch.addDays(-1); + // Ensure that different times may be equal when considering timezone. + QDateTime epochEast1h(epoch.addSecs(3600)); + epochEast1h.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(3600)); + QDateTime epochWest1h(epoch.addSecs(-3600)); + epochWest1h.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(-3600)); + QDateTime local1970(epoch.date(), epoch.time()); // Local time's epoch + + generateRow(june, june, Qt::weak_ordering::equivalent); + generateRow(june, juneLater, Qt::weak_ordering::less); + generateRow(june, badDay, Qt::weak_ordering::greater); + generateRow(badDay, QDateTime(), Qt::weak_ordering::equivalent); + generateRow(june, QDateTime(), Qt::weak_ordering::greater); + generateRow(epoch, nextDay, Qt::weak_ordering::less); + generateRow(epoch, prevDay, Qt::weak_ordering::greater); + generateRow(epoch, epochEast1h, Qt::weak_ordering::equivalent); + generateRow(epoch, epochWest1h, Qt::weak_ordering::equivalent); + generateRow(epochEast1h, epochWest1h, Qt::weak_ordering::equivalent); + if (epochTimeType == LocalTimeIsUtc) + generateRow(epoch, local1970, Qt::weak_ordering::equivalent); +} + +void tst_QDateTime::ordering() +{ + QFETCH(QDateTime, left); + QFETCH(QDateTime, right); + QFETCH(Qt::weak_ordering, expectedOrdering); + + QT_TEST_ALL_COMPARISON_OPS(left, right, expectedOrdering); +} + Q_DECLARE_METATYPE(QDataStream::Version) void tst_QDateTime::operator_insert_extract_data() @@ -2179,7 +2587,7 @@ void tst_QDateTime::operator_insert_extract() TimeZoneRollback useZone(serialiseAs); // It is important that dateTime is created after the time zone shift - QDateTime dateTime(QDate(yearNumber, 8, 14), QTime(8, 0), Qt::LocalTime); + QDateTime dateTime(QDate(yearNumber, 8, 14), QTime(8, 0)); QDateTime dateTimeAsUTC(dateTime.toUTC()); QByteArray byteArray; @@ -2205,7 +2613,8 @@ void tst_QDateTime::operator_insert_extract() // Ensure that a change in timezone between serialisation and deserialisation // still results in identical UTC-converted datetimes. useZone.reset(deserialiseAs); - QDateTime expectedLocalTime(dateTimeAsUTC.toLocalTime()); + QDateTime expectedLocalTime(dateTimeAsUTC.toLocalTime()); // *After* resetting zone. + QCOMPARE(expectedLocalTime, dateTimeAsUTC); // Different description, same moment in time. { // Deserialise whole QDateTime at once. QDataStream dataStream(&byteArray, QIODevice::ReadOnly); @@ -2221,7 +2630,7 @@ void tst_QDateTime::operator_insert_extract() if (dataStreamVersion < QDataStream::Qt_4_0) { // Versions lower than Qt 4 don't serialise the timeSpec, instead // assuming that everything is LocalTime. - deserialised.setTimeSpec(Qt::UTC); + deserialised.setTimeZone(UTC); } // Qt 4.* versions do serialise the timeSpec, so we only need to convert from UTC here. deserialised = deserialised.toLocalTime(); @@ -2239,9 +2648,17 @@ void tst_QDateTime::operator_insert_extract() qint8 deserialisedSpec; if (dataStreamVersion >= QDataStream::Qt_4_0) dataStream >> deserialisedSpec; - deserialised = QDateTime(deserialisedDate, deserialisedTime, Qt::UTC); - if (dataStreamVersion >= QDataStream::Qt_4_0) - deserialised = deserialised.toTimeSpec(static_cast<Qt::TimeSpec>(deserialisedSpec)); + deserialised = QDateTime(deserialisedDate, deserialisedTime, UTC); + QCOMPARE(deserialised.toLocalTime(), deserialised); + const auto isLocalTime = [](qint8 spec) -> bool { + // The spec is in fact a QDateTimePrivate::Spec, not Qt::TimeSpec; + // and no offset or zone is stored, so only UTC and LocalTime are + // really supported. Fortunately this test only uses those. + const auto decoded = static_cast<QDateTimePrivate::Spec>(spec); + return decoded != QDateTimePrivate::UTC && decoded != QDateTimePrivate::OffsetFromUTC; + }; + if (dataStreamVersion >= QDataStream::Qt_4_0 && isLocalTime(deserialisedSpec)) + deserialised = deserialised.toTimeZone(QTimeZone::LocalTime); // Ensure local time is still correct. QCOMPARE(deserialised, expectedLocalTime); // Sanity check UTC times. @@ -2264,54 +2681,61 @@ void tst_QDateTime::fromStringDateFormat_data() QTest::addColumn<Qt::DateFormat>("dateFormat"); QTest::addColumn<QDateTime>("expected"); + // Fails 1970 start dates in western Mexico + // due to changing from PST to MST at the start of 1970. + const bool goodEpochStart = QDateTime(QDate(1970, 1, 1), QTime(0, 0)).isValid(); + // Test Qt::TextDate format. QTest::newRow("text date") << QString::fromLatin1("Tue Jun 17 08:00:10 2003") - << Qt::TextDate << QDateTime(QDate(2003, 6, 17), QTime(8, 0, 10, 0), Qt::LocalTime); + << Qt::TextDate << QDateTime(QDate(2003, 6, 17), QTime(8, 0, 10)); QTest::newRow("text date Year 0999") << QString::fromLatin1("Tue Jun 17 08:00:10 0999") - << Qt::TextDate << QDateTime(QDate(999, 6, 17), QTime(8, 0, 10, 0), Qt::LocalTime); + << Qt::TextDate << QDateTime(QDate(999, 6, 17), QTime(8, 0, 10)); QTest::newRow("text date Year 999") << QString::fromLatin1("Tue Jun 17 08:00:10 999") - << Qt::TextDate << QDateTime(QDate(999, 6, 17), QTime(8, 0, 10, 0), Qt::LocalTime); + << Qt::TextDate << QDateTime(QDate(999, 6, 17), QTime(8, 0, 10)); QTest::newRow("text date Year 12345") << QString::fromLatin1("Tue Jun 17 08:00:10 12345") - << Qt::TextDate << QDateTime(QDate(12345, 6, 17), QTime(8, 0, 10, 0), Qt::LocalTime); + << Qt::TextDate << QDateTime(QDate(12345, 6, 17), QTime(8, 0, 10)); QTest::newRow("text date Year -4712") << QString::fromLatin1("Tue Jan 1 00:01:02 -4712") - << Qt::TextDate << QDateTime(QDate(-4712, 1, 1), QTime(0, 1, 2, 0), Qt::LocalTime); - QTest::newRow("text epoch") - << QString::fromLatin1("Thu Jan 1 00:00:00 1970") << Qt::TextDate - << QDateTime(QDate(1970, 1, 1), QTime(0, 0), Qt::LocalTime); + << Qt::TextDate << QDateTime(QDate(-4712, 1, 1), QTime(0, 1, 2)); QTest::newRow("text data1") << QString::fromLatin1("Thu Jan 2 12:34 1970") - << Qt::TextDate << QDateTime(QDate(1970, 1, 2), QTime(12, 34, 0), Qt::LocalTime); + << Qt::TextDate << QDateTime(QDate(1970, 1, 2), QTime(12, 34)); + if (goodEpochStart) { + QTest::newRow("text epoch year after time") + << QString::fromLatin1("Thu Jan 1 00:00:00 1970") << Qt::TextDate + << QDate(1970, 1, 1).startOfDay(); + QTest::newRow("text epoch spaced") + << QString::fromLatin1(" Thu Jan 1 00:00:00 1970 ") + << Qt::TextDate << QDate(1970, 1, 1).startOfDay(); + QTest::newRow("text epoch time after year") + << QString::fromLatin1("Thu Jan 1 1970 00:00:00") + << Qt::TextDate << QDate(1970, 1, 1).startOfDay(); + } QTest::newRow("text epoch terse") << QString::fromLatin1("Thu Jan 1 00 1970") << Qt::TextDate << QDateTime(); QTest::newRow("text epoch stray :00") << QString::fromLatin1("Thu Jan 1 00:00:00:00 1970") << Qt::TextDate << QDateTime(); - QTest::newRow("text epoch spaced") - << QString::fromLatin1(" Thu Jan 1 00:00:00 1970 ") - << Qt::TextDate << QDateTime(QDate(1970, 1, 1), QTime(0, 0), Qt::LocalTime); QTest::newRow("text data6") << QString::fromLatin1("Thu Jan 1 00:00:00") << Qt::TextDate << QDateTime(); - QTest::newRow("text data7") << QString::fromLatin1("Thu Jan 1 1970 00:00:00") - << Qt::TextDate << QDateTime(QDate(1970, 1, 1), QTime(0, 0), Qt::LocalTime); QTest::newRow("text bad offset") << QString::fromLatin1("Thu Jan 1 00:12:34 1970 UTC+foo") << Qt::TextDate << QDateTime(); QTest::newRow("text UTC early") << QString::fromLatin1("Thu Jan 1 00:12:34 1970 UTC") - << Qt::TextDate << QDateTime(QDate(1970, 1, 1), QTime(0, 12, 34), Qt::UTC); + << Qt::TextDate << QDateTime(QDate(1970, 1, 1), QTime(0, 12, 34), UTC); QTest::newRow("text UTC-3 early") << QString::fromLatin1("Thu Jan 1 00:12:34 1970 UTC-0300") - << Qt::TextDate << QDateTime(QDate(1970, 1, 1), QTime(3, 12, 34), Qt::UTC); + << Qt::TextDate << QDateTime(QDate(1970, 1, 1), QTime(3, 12, 34), UTC); QTest::newRow("text UTC+3 early") << QString::fromLatin1("Thu Jan 1 00:12:34 1970 UTC+0300") - << Qt::TextDate << QDateTime(QDate(1969, 12, 31), QTime(21, 12, 34), Qt::UTC); + << Qt::TextDate << QDateTime(QDate(1969, 12, 31), QTime(21, 12, 34), UTC); QTest::newRow("text UTC+1 early") << QString::fromLatin1("Thu Jan 1 1970 00:12:34 UTC+0100") - << Qt::TextDate << QDateTime(QDate(1969, 12, 31), QTime(23, 12, 34), Qt::UTC); + << Qt::TextDate << QDateTime(QDate(1969, 12, 31), QTime(23, 12, 34), UTC); // We produce use GMT as prefix, so need to parse it: QTest::newRow("text GMT early") << QString::fromLatin1("Thu Jan 1 00:12:34 1970 GMT") << Qt::TextDate - << QDateTime(QDate(1970, 1, 1), QTime(0, 12, 34), Qt::UTC); + << QDateTime(QDate(1970, 1, 1), QTime(0, 12, 34), UTC); QTest::newRow("text GMT+3 early") << QString::fromLatin1("Thu Jan 1 00:12:34 1970 GMT+0300") << Qt::TextDate - << QDateTime(QDate(1969, 12, 31), QTime(21, 12, 34), Qt::UTC); + << QDateTime(QDate(1969, 12, 31), QTime(21, 12, 34), UTC); // ... and we match (only) it case-insensitively: QTest::newRow("text gmt early") << QString::fromLatin1("Thu Jan 1 00:12:34 1970 gmt") << Qt::TextDate - << QDateTime(QDate(1970, 1, 1), QTime(0, 12, 34), Qt::UTC); + << QDateTime(QDate(1970, 1, 1), QTime(0, 12, 34), UTC); QTest::newRow("text empty") << QString::fromLatin1("") << Qt::TextDate << QDateTime(); @@ -2407,83 +2831,95 @@ void tst_QDateTime::fromStringDateFormat_data() // Normal usage: QTest::newRow("ISO +01:00") << QString::fromLatin1("1987-02-13T13:24:51+01:00") - << Qt::ISODate << QDateTime(QDate(1987, 2, 13), QTime(12, 24, 51), Qt::UTC); + << Qt::ISODate << QDateTime(QDate(1987, 2, 13), QTime(12, 24, 51), UTC); QTest::newRow("ISO +00:01") << QString::fromLatin1("1987-02-13T13:24:51+00:01") - << Qt::ISODate << QDateTime(QDate(1987, 2, 13), QTime(13, 23, 51), Qt::UTC); + << Qt::ISODate << QDateTime(QDate(1987, 2, 13), QTime(13, 23, 51), UTC); QTest::newRow("ISO -01:00") << QString::fromLatin1("1987-02-13T13:24:51-01:00") - << Qt::ISODate << QDateTime(QDate(1987, 2, 13), QTime(14, 24, 51), Qt::UTC); + << Qt::ISODate << QDateTime(QDate(1987, 2, 13), QTime(14, 24, 51), UTC); QTest::newRow("ISO -00:01") << QString::fromLatin1("1987-02-13T13:24:51-00:01") - << Qt::ISODate << QDateTime(QDate(1987, 2, 13), QTime(13, 25, 51), Qt::UTC); + << Qt::ISODate << QDateTime(QDate(1987, 2, 13), QTime(13, 25, 51), UTC); QTest::newRow("ISO +0000") << QString::fromLatin1("1970-01-01T00:12:34+0000") - << Qt::ISODate << QDateTime(QDate(1970, 1, 1), QTime(0, 12, 34), Qt::UTC); + << Qt::ISODate << QDateTime(QDate(1970, 1, 1), QTime(0, 12, 34), UTC); QTest::newRow("ISO +00:00") << QString::fromLatin1("1970-01-01T00:12:34+00:00") - << Qt::ISODate << QDateTime(QDate(1970, 1, 1), QTime(0, 12, 34), Qt::UTC); + << Qt::ISODate << QDateTime(QDate(1970, 1, 1), QTime(0, 12, 34), UTC); QTest::newRow("ISO -03") << QString::fromLatin1("2014-12-15T12:37:09-03") - << Qt::ISODate << QDateTime(QDate(2014, 12, 15), QTime(15, 37, 9), Qt::UTC); + << Qt::ISODate << QDateTime(QDate(2014, 12, 15), QTime(15, 37, 9), UTC); QTest::newRow("ISO zzz-03") << QString::fromLatin1("2014-12-15T12:37:09.745-03") - << Qt::ISODate << QDateTime(QDate(2014, 12, 15), QTime(15, 37, 9, 745), Qt::UTC); + << Qt::ISODate << QDateTime(QDate(2014, 12, 15), QTime(15, 37, 9, 745), UTC); QTest::newRow("ISO -3") << QString::fromLatin1("2014-12-15T12:37:09-3") - << Qt::ISODate << QDateTime(QDate(2014, 12, 15), QTime(15, 37, 9), Qt::UTC); + << Qt::ISODate << QDateTime(QDate(2014, 12, 15), QTime(15, 37, 9), UTC); QTest::newRow("ISO zzz-3") << QString::fromLatin1("2014-12-15T12:37:09.745-3") - << Qt::ISODate << QDateTime(QDate(2014, 12, 15), QTime(15, 37, 9, 745), Qt::UTC); + << Qt::ISODate << QDateTime(QDate(2014, 12, 15), QTime(15, 37, 9, 745), UTC); QTest::newRow("ISO lower-case") << QString::fromLatin1("2005-06-28T07:57:30.002z") - << Qt::ISODate << QDateTime(QDate(2005, 6, 28), QTime(7, 57, 30, 2), Qt::UTC); + << Qt::ISODate << QDateTime(QDate(2005, 6, 28), QTime(7, 57, 30, 2), UTC); // No time specified - defaults to Qt::LocalTime. QTest::newRow("ISO data3") << QString::fromLatin1("2002-10-01") - << Qt::ISODate << QDateTime(QDate(2002, 10, 1), QTime(0, 0), Qt::LocalTime); + << Qt::ISODate << QDate(2002, 10, 1).startOfDay(); // Excess digits in milliseconds, round correctly: QTest::newRow("ISO") << QString::fromLatin1("2005-06-28T07:57:30.0010000000Z") - << Qt::ISODate << QDateTime(QDate(2005, 6, 28), QTime(7, 57, 30, 1), Qt::UTC); + << Qt::ISODate << QDateTime(QDate(2005, 6, 28), QTime(7, 57, 30, 1), UTC); QTest::newRow("ISO rounding") << QString::fromLatin1("2005-06-28T07:57:30.0015Z") - << Qt::ISODate << QDateTime(QDate(2005, 6, 28), QTime(7, 57, 30, 2), Qt::UTC); + << Qt::ISODate << QDateTime(QDate(2005, 6, 28), QTime(7, 57, 30, 2), UTC); // ... and accept comma as separator: QTest::newRow("ISO with comma 1") << QString::fromLatin1("2005-06-28T07:57:30,0040000000Z") - << Qt::ISODate << QDateTime(QDate(2005, 6, 28), QTime(7, 57, 30, 4), Qt::UTC); + << Qt::ISODate << QDateTime(QDate(2005, 6, 28), QTime(7, 57, 30, 4), UTC); QTest::newRow("ISO with comma 2") << QString::fromLatin1("2005-06-28T07:57:30,0015Z") - << Qt::ISODate << QDateTime(QDate(2005, 6, 28), QTime(7, 57, 30, 2), Qt::UTC); + << Qt::ISODate << QDateTime(QDate(2005, 6, 28), QTime(7, 57, 30, 2), UTC); QTest::newRow("ISO with comma 3") << QString::fromLatin1("2005-06-28T07:57:30,0014Z") - << Qt::ISODate << QDateTime(QDate(2005, 6, 28), QTime(7, 57, 30, 1), Qt::UTC); + << Qt::ISODate << QDateTime(QDate(2005, 6, 28), QTime(7, 57, 30, 1), UTC); QTest::newRow("ISO with comma 4") << QString::fromLatin1("2005-06-28T07:57:30,1Z") - << Qt::ISODate << QDateTime(QDate(2005, 6, 28), QTime(7, 57, 30, 100), Qt::UTC); + << Qt::ISODate << QDateTime(QDate(2005, 6, 28), QTime(7, 57, 30, 100), UTC); QTest::newRow("ISO with comma 5") << QString::fromLatin1("2005-06-28T07:57:30,11") - << Qt::ISODate << QDateTime(QDate(2005, 6, 28), QTime(7, 57, 30, 110), Qt::LocalTime); + << Qt::ISODate << QDateTime(QDate(2005, 6, 28), QTime(7, 57, 30, 110)); // 24:00:00 Should be next day according to ISO 8601 section 4.2.3. QTest::newRow("ISO 24:00") << QString::fromLatin1("2012-06-04T24:00:00") - << Qt::ISODate << QDateTime(QDate(2012, 6, 5), QTime(0, 0), Qt::LocalTime); - QTest::newRow("ISO 24:00 in DST") // Only special if TZ=America/Sao_Paulo + << Qt::ISODate << QDate(2012, 6, 5).startOfDay(); +#if QT_CONFIG(timezone) + const QByteArray sysId = QTimeZone::systemTimeZoneId(); + const bool midnightSkip = sysId == "America/Sao_Paulo" || sysId == "America/Asuncion" + || sysId == "America/Cordoba" || sysId == "America/Argentina/Cordoba" + || sysId == "America/Campo_Grande" + || sysId == "America/Cuiaba" || sysId == "America/Buenos_Aires" + || sysId == "America/Argentina/Buenos_Aires" + || sysId == "America/Argentina/Tucuman" || sysId == "Brazil/East"; + QTest::newRow("ISO 24:00 in DST") // Midnight spring forward in some of South America. << QString::fromLatin1("2008-10-18T24:00") << Qt::ISODate - << QDateTime(QDate(2008, 10, 19), - QTime(QTimeZone::systemTimeZoneId() == "America/Sao_Paulo" ? 1 : 0, 0), - Qt::LocalTime); - QTest::newRow("ISO 24:00 end of month") << QString::fromLatin1("2012-06-30T24:00:00") - << Qt::ISODate << QDateTime(QDate(2012, 7, 1), QTime(0, 0), Qt::LocalTime); - QTest::newRow("ISO 24:00 end of year") << QString::fromLatin1("2012-12-31T24:00:00") - << Qt::ISODate << QDateTime(QDate(2013, 1, 1), QTime(0, 0), Qt::LocalTime); - QTest::newRow("ISO 24:00, fract ms") << QString::fromLatin1("2012-01-01T24:00:00.000") - << Qt::ISODate << QDateTime(QDate(2012, 1, 2), QTime(0, 0), Qt::LocalTime); - QTest::newRow("ISO 24:00 end of year, fract ms") << QString::fromLatin1("2012-12-31T24:00:00.000") - << Qt::ISODate << QDateTime(QDate(2013, 1, 1), QTime(0, 0), Qt::LocalTime); + << QDateTime(QDate(2008, 10, 19), QTime(midnightSkip ? 1 : 0, 0)); +#endif + QTest::newRow("ISO 24:00 end of month") + << QString::fromLatin1("2012-06-30T24:00:00") + << Qt::ISODate << QDate(2012, 7, 1).startOfDay(); + QTest::newRow("ISO 24:00 end of year") + << QString::fromLatin1("2012-12-31T24:00:00") + << Qt::ISODate << QDate(2013, 1, 1).startOfDay(); + QTest::newRow("ISO 24:00, fract ms") + << QString::fromLatin1("2012-01-01T24:00:00.000") + << Qt::ISODate << QDate(2012, 1, 2).startOfDay(); + QTest::newRow("ISO 24:00 end of year, fract ms") + << QString::fromLatin1("2012-12-31T24:00:00.000") + << Qt::ISODate << QDate(2013, 1, 1).startOfDay(); // Test fractional seconds. - QTest::newRow("ISO .0 of a second (period)") << QString::fromLatin1("2012-01-01T08:00:00.0") - << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0, 0, 0), Qt::LocalTime); + QTest::newRow("ISO .0 of a second (period)") + << QString::fromLatin1("2012-01-01T08:00:00.0") + << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0)); QTest::newRow("ISO .00 of a second (period)") << QString::fromLatin1("2012-01-01T08:00:00.00") - << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0, 0, 0), Qt::LocalTime); + << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0)); QTest::newRow("ISO .000 of a second (period)") << QString::fromLatin1("2012-01-01T08:00:00.000") - << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0, 0, 0), Qt::LocalTime); + << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0)); QTest::newRow("ISO .1 of a second (comma)") << QString::fromLatin1("2012-01-01T08:00:00,1") - << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0, 0, 100), Qt::LocalTime); + << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0, 0, 100)); QTest::newRow("ISO .99 of a second (comma)") << QString::fromLatin1("2012-01-01T08:00:00,99") - << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0, 0, 990), Qt::LocalTime); + << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0, 0, 990)); QTest::newRow("ISO .998 of a second (comma)") << QString::fromLatin1("2012-01-01T08:00:00,998") - << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0, 0, 998), Qt::LocalTime); + << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0, 0, 998)); QTest::newRow("ISO .999 of a second (comma)") << QString::fromLatin1("2012-01-01T08:00:00,999") - << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0, 0, 999), Qt::LocalTime); + << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0, 0, 999)); QTest::newRow("ISO .3335 of a second (comma)") << QString::fromLatin1("2012-01-01T08:00:00,3335") - << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0, 0, 334), Qt::LocalTime); + << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0, 0, 334)); QTest::newRow("ISO .333333 of a second (comma)") << QString::fromLatin1("2012-01-01T08:00:00,333333") - << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0, 0, 333), Qt::LocalTime); + << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0, 0, 333)); QTest::newRow("ISO .00009 of a second (period)") << QString::fromLatin1("2012-01-01T08:00:00.00009") - << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0, 0, 0), Qt::LocalTime); + << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0)); QTest::newRow("ISO second fraction") << QString::fromLatin1("2013-05-06T01:02:03.456") << Qt::ISODate << QDateTime(QDate(2013, 5, 6), QTime(1, 2, 3, 456)); QTest::newRow("ISO max milli") @@ -2510,17 +2946,17 @@ void tst_QDateTime::fromStringDateFormat_data() << Qt::ISODate << QDateTime(); // Test fractional minutes. QTest::newRow("ISO .0 of a minute (period)") << QString::fromLatin1("2012-01-01T08:00.0") - << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0, 0, 0), Qt::LocalTime); + << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0)); QTest::newRow("ISO .8 of a minute (period)") << QString::fromLatin1("2012-01-01T08:00.8") - << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0, 48, 0), Qt::LocalTime); + << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0, 48)); QTest::newRow("ISO .99999 of a minute (period)") << QString::fromLatin1("2012-01-01T08:00.99999") - << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0, 59, 999), Qt::LocalTime); + << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0, 59, 999)); QTest::newRow("ISO .0 of a minute (comma)") << QString::fromLatin1("2012-01-01T08:00,0") - << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0, 0, 0), Qt::LocalTime); + << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0)); QTest::newRow("ISO .8 of a minute (comma)") << QString::fromLatin1("2012-01-01T08:00,8") - << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0, 48, 0), Qt::LocalTime); + << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0, 48)); QTest::newRow("ISO .99999 of a minute (comma)") << QString::fromLatin1("2012-01-01T08:00,99999") - << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0, 59, 999), Qt::LocalTime); + << Qt::ISODate << QDateTime(QDate(2012, 1, 1), QTime(8, 0, 59, 999)); QTest::newRow("ISO empty") << QString::fromLatin1("") << Qt::ISODate << QDateTime(); QTest::newRow("ISO short") << QString::fromLatin1("2017-07-01T") << Qt::ISODate << QDateTime(); QTest::newRow("ISO zoned date") @@ -2532,32 +2968,28 @@ void tst_QDateTime::fromStringDateFormat_data() // Test Qt::RFC2822Date format (RFC 2822). QTest::newRow("RFC 2822 +0100") << QString::fromLatin1("13 Feb 1987 13:24:51 +0100") - << Qt::RFC2822Date << QDateTime(QDate(1987, 2, 13), QTime(12, 24, 51), Qt::UTC); + << Qt::RFC2822Date << QDateTime(QDate(1987, 2, 13), QTime(12, 24, 51), UTC); QTest::newRow("RFC 2822 after space +0100") << QString::fromLatin1(" 13 Feb 1987 13:24:51 +0100") - << Qt::RFC2822Date << QDateTime(QDate(1987, 2, 13), QTime(12, 24, 51), Qt::UTC); + << Qt::RFC2822Date << QDateTime(QDate(1987, 2, 13), QTime(12, 24, 51), UTC); QTest::newRow("RFC 2822 with day +0100") << QString::fromLatin1("Fri, 13 Feb 1987 13:24:51 +0100") - << Qt::RFC2822Date << QDateTime(QDate(1987, 2, 13), QTime(12, 24, 51), Qt::UTC); + << Qt::RFC2822Date << QDateTime(QDate(1987, 2, 13), QTime(12, 24, 51), UTC); QTest::newRow("RFC 2822 with day after space +0100") << QString::fromLatin1(" Fri, 13 Feb 1987 13:24:51 +0100") - << Qt::RFC2822Date << QDateTime(QDate(1987, 2, 13), QTime(12, 24, 51), Qt::UTC); + << Qt::RFC2822Date << QDateTime(QDate(1987, 2, 13), QTime(12, 24, 51), UTC); QTest::newRow("RFC 2822 -0100") << QString::fromLatin1("13 Feb 1987 13:24:51 -0100") - << Qt::RFC2822Date << QDateTime(QDate(1987, 2, 13), QTime(14, 24, 51), Qt::UTC); + << Qt::RFC2822Date << QDateTime(QDate(1987, 2, 13), QTime(14, 24, 51), UTC); QTest::newRow("RFC 2822 with day -0100") << QString::fromLatin1("Fri, 13 Feb 1987 13:24:51 -0100") - << Qt::RFC2822Date << QDateTime(QDate(1987, 2, 13), QTime(14, 24, 51), Qt::UTC); + << Qt::RFC2822Date << QDateTime(QDate(1987, 2, 13), QTime(14, 24, 51), UTC); QTest::newRow("RFC 2822 +0000") << QString::fromLatin1("01 Jan 1970 00:12:34 +0000") - << Qt::RFC2822Date << QDateTime(QDate(1970, 1, 1), QTime(0, 12, 34), Qt::UTC); + << Qt::RFC2822Date << QDateTime(QDate(1970, 1, 1), QTime(0, 12, 34), UTC); QTest::newRow("RFC 2822 with day +0000") << QString::fromLatin1("Thu, 01 Jan 1970 00:12:34 +0000") - << Qt::RFC2822Date << QDateTime(QDate(1970, 1, 1), QTime(0, 12, 34), Qt::UTC); - QTest::newRow("RFC 2822 +0000") << QString::fromLatin1("01 Jan 1970 00:12:34 +0000") - << Qt::RFC2822Date << QDateTime(QDate(1970, 1, 1), QTime(0, 12, 34), Qt::UTC); - QTest::newRow("RFC 2822 with day +0000") << QString::fromLatin1("Thu, 01 Jan 1970 00:12:34 +0000") - << Qt::RFC2822Date << QDateTime(QDate(1970, 1, 1), QTime(0, 12, 34), Qt::UTC); + << Qt::RFC2822Date << QDateTime(QDate(1970, 1, 1), QTime(0, 12, 34), UTC); QTest::newRow("RFC 2822 missing space before +0100") << QString::fromLatin1("Thu, 01 Jan 1970 00:12:34+0100") << Qt::RFC2822Date << QDateTime(); // No timezone assume UTC QTest::newRow("RFC 2822 no timezone") << QString::fromLatin1("01 Jan 1970 00:12:34") - << Qt::RFC2822Date << QDateTime(QDate(1970, 1, 1), QTime(0, 12, 34), Qt::UTC); + << Qt::RFC2822Date << QDateTime(QDate(1970, 1, 1), QTime(0, 12, 34), UTC); // No time specified QTest::newRow("RFC 2822 date only") << QString::fromLatin1("01 Nov 2002") << Qt::RFC2822Date << QDateTime(); @@ -2602,23 +3034,21 @@ void tst_QDateTime::fromStringDateFormat_data() // sure *it's* not what's invalid: QTest::newRow("RFC 2822 (not invalid)") << QString::fromLatin1("01 Jan 2012 08:00:00 +0100") - << Qt::RFC2822Date << QDateTime(QDate(2012, 1, 1), QTime(7, 0), Qt::UTC); + << Qt::RFC2822Date << QDateTime(QDate(2012, 1, 1), QTime(7, 0), UTC); // Test Qt::RFC2822Date format (RFC 850 and 1036, permissive). QTest::newRow("RFC 850 and 1036 +0100") << QString::fromLatin1("Fri Feb 13 13:24:51 1987 +0100") - << Qt::RFC2822Date << QDateTime(QDate(1987, 2, 13), QTime(12, 24, 51), Qt::UTC); + << Qt::RFC2822Date << QDateTime(QDate(1987, 2, 13), QTime(12, 24, 51), UTC); QTest::newRow("RFC 1036 after space +0100") << QString::fromLatin1(" Fri Feb 13 13:24:51 1987 +0100") - << Qt::RFC2822Date << QDateTime(QDate(1987, 2, 13), QTime(12, 24, 51), Qt::UTC); + << Qt::RFC2822Date << QDateTime(QDate(1987, 2, 13), QTime(12, 24, 51), UTC); QTest::newRow("RFC 850 and 1036 -0100") << QString::fromLatin1("Fri Feb 13 13:24:51 1987 -0100") - << Qt::RFC2822Date << QDateTime(QDate(1987, 2, 13), QTime(14, 24, 51), Qt::UTC); - QTest::newRow("RFC 850 and 1036 +0000") << QString::fromLatin1("Thu Jan 01 00:12:34 1970 +0000") - << Qt::RFC2822Date << QDateTime(QDate(1970, 1, 1), QTime(0, 12, 34), Qt::UTC); + << Qt::RFC2822Date << QDateTime(QDate(1987, 2, 13), QTime(14, 24, 51), UTC); QTest::newRow("RFC 850 and 1036 +0000") << QString::fromLatin1("Thu Jan 01 00:12:34 1970 +0000") - << Qt::RFC2822Date << QDateTime(QDate(1970, 1, 1), QTime(0, 12, 34), Qt::UTC); + << Qt::RFC2822Date << QDateTime(QDate(1970, 1, 1), QTime(0, 12, 34), UTC); // No timezone assume UTC QTest::newRow("RFC 850 and 1036 no timezone") << QString::fromLatin1("Thu Jan 01 00:12:34 1970") - << Qt::RFC2822Date << QDateTime(QDate(1970, 1, 1), QTime(0, 12, 34), Qt::UTC); + << Qt::RFC2822Date << QDateTime(QDate(1970, 1, 1), QTime(0, 12, 34), UTC); // No time specified QTest::newRow("RFC 850 and 1036 date only") << QString::fromLatin1("Fri Nov 01 2002") @@ -2642,7 +3072,7 @@ void tst_QDateTime::fromStringDateFormat_data() // Again, check the text in the "invalid character" tests isn't the source of invalidity: QTest::newRow("RFC 850 and 1036 (not invalid)") << QString::fromLatin1("Sun Jan 01 08:00:00 2012 +0100") - << Qt::RFC2822Date << QDateTime(QDate(2012, 1, 1), QTime(7, 0), Qt::UTC); + << Qt::RFC2822Date << QDateTime(QDate(2012, 1, 1), QTime(7, 0), UTC); QTest::newRow("RFC empty") << QString::fromLatin1("") << Qt::RFC2822Date << QDateTime(); } @@ -2662,234 +3092,271 @@ void tst_QDateTime::fromStringStringFormat_data() { QTest::addColumn<QString>("string"); QTest::addColumn<QString>("format"); + QTest::addColumn<int>("baseYear"); QTest::addColumn<QDateTime>("expected"); - const QDate defDate(1900, 1, 1); - QTest::newRow("data0") - << QString("101010") << QString("dMyy") << QDate(1910, 10, 10).startOfDay(); - QTest::newRow("data1") << QString("1020") << QString("sss") << QDateTime(); - QTest::newRow("data2") - << QString("1010") << QString("sss") << QDateTime(defDate, QTime(0, 0, 10)); - QTest::newRow("data3") << QString("10hello20") << QString("ss'hello'ss") << QDateTime(); - QTest::newRow("data4") << QString("10") << QString("''") << QDateTime(); - QTest::newRow("data5") << QString("10") << QString("'") << QDateTime(); - QTest::newRow("data6") << QString("pm") << QString("ap") << QDateTime(defDate, QTime(12, 0)); - QTest::newRow("data7") << QString("foo") << QString("ap") << QDateTime(); + // Indian/Cocos had a transition at the start of 1900, so its Jan 1st starts + // at 00:02:20 on that day; this leads to perverse results. QTBUG-77948. + if (const QDate defDate(1900, 1, 1); defDate.startOfDay().time() == QTime(0, 0)) { + QTest::newRow("dMyy-only:19") + << u"101010"_s << u"dMyy"_s << 1900 << QDate(1910, 10, 10).startOfDay(); + QTest::newRow("dMyy-only:20") + << u"101010"_s << u"dMyy"_s << 1911 << QDate(2010, 10, 10).startOfDay(); + QTest::newRow("secs-repeat-valid") + << u"1010"_s << u"sss"_s << 1900 << QDateTime(defDate, QTime(0, 0, 10)); + QTest::newRow("pm-only") + << u"pm"_s << u"ap"_s << 1900 << QDateTime(defDate, QTime(12, 0)); + QTest::newRow("date-only:19") + << u"10 Oct 10"_s << u"dd MMM yy"_s << 1900 << QDate(1910, 10, 10).startOfDay(); + QTest::newRow("date-only:20") + << u"10 Oct 10"_s << u"dd MMM yy"_s << 1950 << QDate(2010, 10, 10).startOfDay(); + QTest::newRow("dow-date-only") + << u"Fri December 3 2004"_s << u"ddd MMMM d yyyy"_s << 1900 + << QDate(2004, 12, 3).startOfDay(); + QTest::newRow("dow-mon-yr-only") + << u"Thu January 2004"_s << u"ddd MMMM yyyy"_s << 1900 + << QDate(2004, 1, 1).startOfDay(); + } + QTest::newRow("yy=24/Mar/20") // QTBUG-123579 + << u"Wed, 20 Mar 24 16:17:00"_s << u"ddd, dd MMM yy HH:mm:ss"_s << 1900 + << QDateTime(QDate(2024, 3, 20), QTime(16, 17)); + QTest::newRow("secs-conflict") << u"1020"_s << u"sss"_s << 1900 << QDateTime(); + QTest::newRow("secs-split-conflict") + << u"10hello20"_s << u"ss'hello'ss"_s << 1900 << QDateTime(); + QTest::newRow("nomatch-quote-twice") << u"10"_s << u"''"_s << 1900 << QDateTime(); + QTest::newRow("momatch-quote") << u"10"_s << u"'"_s << 1900 << QDateTime(); + QTest::newRow("nomatch-am-pm") << u"foo"_s << u"ap"_s << 1900 << QDateTime(); // Day non-conflict should not hide earlier year conflict (1963-03-01 was a // Friday; asking for Thursday moves this, without conflict, to the 7th): - QTest::newRow("data8") - << QString("77 03 1963 Thu") << QString("yy MM yyyy ddd") << QDateTime(); - QTest::newRow("data9") - << QString("101010") << QString("dMyy") << QDate(1910, 10, 10).startOfDay(); - QTest::newRow("data10") - << QString("101010") << QString("dMyy") << QDate(1910, 10, 10).startOfDay(); - QTest::newRow("data11") - << QString("10 Oct 10") << QString("dd MMM yy") << QDate(1910, 10, 10).startOfDay(); - QTest::newRow("data12") - << QString("Fri December 3 2004") << QString("ddd MMMM d yyyy") - << QDate(2004, 12, 3).startOfDay(); - QTest::newRow("data13") << QString("30.02.2004") << QString("dd.MM.yyyy") << QDateTime(); - QTest::newRow("data14") << QString("32.01.2004") << QString("dd.MM.yyyy") << QDateTime(); - QTest::newRow("data15") - << QString("Thu January 2004") << QString("ddd MMMM yyyy") - << QDate(2004, 1, 1).startOfDay(); - QTest::newRow("data16") << QString("2005-06-28T07:57:30.001Z") - << QString("yyyy-MM-ddThh:mm:ss.zt") - << QDateTime(QDate(2005, 06, 28), QTime(07, 57, 30, 1), Qt::UTC); + QTest::newRow("year-conflict") + << u"77 03 1963 Thu"_s << u"yy MM yyyy ddd"_s << 1900 << QDateTime(); + QTest::newRow("Feb-overflow") << u"30.02.2004"_s << u"dd.MM.yyyy"_s << 1900 << QDateTime(); + QTest::newRow("Jan-overflow") << u"32.01.2004"_s << u"dd.MM.yyyy"_s << 1900 << QDateTime(); + QTest::newRow("zulu-time-with-z-centisec") + << u"2005-06-28T07:57:30.01Z"_s << u"yyyy-MM-ddThh:mm:ss.zt"_s << 1900 + << QDateTime(QDate(2005, 06, 28), QTime(07, 57, 30, 10), UTC); + QTest::newRow("zulu-time-with-zz-decisec") + << u"2005-06-28T07:57:30.1Z"_s << u"yyyy-MM-ddThh:mm:ss.zzt"_s << 1900 + << QDateTime(QDate(2005, 06, 28), QTime(07, 57, 30, 100), UTC); + QTest::newRow("zulu-time-with-zzz-centisec") + << u"2005-06-28T07:57:30.01Z"_s << u"yyyy-MM-ddThh:mm:ss.zzzt"_s << 1900 + << QDateTime(); // Invalid because too few digits for zzz + QTest::newRow("zulu-time-with-z-millisec") + << u"2005-06-28T07:57:30.001Z"_s << u"yyyy-MM-ddThh:mm:ss.zt"_s << 1900 + << QDateTime(QDate(2005, 06, 28), QTime(07, 57, 30, 1), UTC); QTest::newRow("utc-time-spec-as:UTC+0") - << QString("2005-06-28T07:57:30.001UTC+0") << QString("yyyy-MM-ddThh:mm:ss.zt") - << QDateTime(QDate(2005, 6, 28), QTime(7, 57, 30, 1), Qt::UTC); + << u"2005-06-28T07:57:30.001UTC+0"_s << u"yyyy-MM-ddThh:mm:ss.zt"_s << 1900 + << QDateTime(QDate(2005, 6, 28), QTime(7, 57, 30, 1), UTC); QTest::newRow("utc-time-spec-as:UTC-0") - << QString("2005-06-28T07:57:30.001UTC-0") << QString("yyyy-MM-ddThh:mm:ss.zt") - << QDateTime(QDate(2005, 6, 28), QTime(7, 57, 30, 1), Qt::UTC); + << u"2005-06-28T07:57:30.001UTC-0"_s << u"yyyy-MM-ddThh:mm:ss.zt"_s << 1900 + << QDateTime(QDate(2005, 6, 28), QTime(7, 57, 30, 1), UTC); QTest::newRow("offset-from-utc:UTC+1") - << QString("2001-09-13T07:33:01.001 UTC+1") << QString("yyyy-MM-ddThh:mm:ss.z t") - << QDateTime(QDate(2001, 9, 13), QTime(7, 33, 1, 1), Qt::OffsetFromUTC, 3600); + << u"2001-09-13T07:33:01.001 UTC+1"_s << u"yyyy-MM-ddThh:mm:ss.z t"_s << 1900 + << QDateTime(QDate(2001, 9, 13), QTime(7, 33, 1, 1), + QTimeZone::fromSecondsAheadOfUtc(3600)); QTest::newRow("offset-from-utc:UTC-11:01") - << QString("2008-09-13T07:33:01.001 UTC-11:01") << QString("yyyy-MM-ddThh:mm:ss.z t") - << QDateTime(QDate(2008, 9, 13), QTime(7, 33, 1, 1), Qt::OffsetFromUTC, -39660); + << u"2008-09-13T07:33:01.001 UTC-11:01"_s << u"yyyy-MM-ddThh:mm:ss.z t"_s << 1900 + << QDateTime(QDate(2008, 9, 13), QTime(7, 33, 1, 1), + QTimeZone::fromSecondsAheadOfUtc(-39660)); QTest::newRow("offset-from-utc:UTC+02:57") - << QString("2001-09-15T09:33:01.001UTC+02:57") << QString("yyyy-MM-ddThh:mm:ss.zt") - << QDateTime(QDate(2001, 9, 15), QTime(9, 33, 1, 1), Qt::OffsetFromUTC, 10620); + << u"2001-09-15T09:33:01.001UTC+02:57"_s << u"yyyy-MM-ddThh:mm:ss.zt"_s << 1900 + << QDateTime(QDate(2001, 9, 15), QTime(9, 33, 1, 1), + QTimeZone::fromSecondsAheadOfUtc(10620)); QTest::newRow("offset-from-utc:-03:00") // RFC 3339 offset format - << QString("2001-09-15T09:33:01.001-03:00") << QString("yyyy-MM-ddThh:mm:ss.zt") - << QDateTime(QDate(2001, 9, 15), QTime(9, 33, 1, 1), Qt::OffsetFromUTC, -10800); + << u"2001-09-15T09:33:01.001-03:00"_s << u"yyyy-MM-ddThh:mm:ss.zt"_s << 1900 + << QDateTime(QDate(2001, 9, 15), QTime(9, 33, 1, 1), + QTimeZone::fromSecondsAheadOfUtc(-10800)); QTest::newRow("offset-from-utc:+0205") // ISO 8601 basic offset format - << QString("2001-09-15T09:33:01.001+0205") << QString("yyyy-MM-ddThh:mm:ss.zt") - << QDateTime(QDate(2001, 9, 15), QTime(9, 33, 1, 1), Qt::OffsetFromUTC, 7500); + << u"2001-09-15T09:33:01.001+0205"_s << u"yyyy-MM-ddThh:mm:ss.zt"_s << 1900 + << QDateTime(QDate(2001, 9, 15), QTime(9, 33, 1, 1), + QTimeZone::fromSecondsAheadOfUtc(7500)); QTest::newRow("offset-from-utc:-0401") // ISO 8601 basic offset format - << QString("2001-09-15T09:33:01.001-0401") << QString("yyyy-MM-ddThh:mm:ss.zt") - << QDateTime(QDate(2001, 9, 15), QTime(9, 33, 1, 1), Qt::OffsetFromUTC, -14460); + << u"2001-09-15T09:33:01.001-0401"_s << u"yyyy-MM-ddThh:mm:ss.zt"_s << 1900 + << QDateTime(QDate(2001, 9, 15), QTime(9, 33, 1, 1), + QTimeZone::fromSecondsAheadOfUtc(-14460)); QTest::newRow("offset-from-utc:+10") // ISO 8601 basic (hour-only) offset format - << QString("2001-09-15T09:33:01.001 +10") << QString("yyyy-MM-ddThh:mm:ss.z t") - << QDateTime(QDate(2001, 9, 15), QTime(9, 33, 1, 1), Qt::OffsetFromUTC, 36000); + << u"2001-09-15T09:33:01.001 +10"_s << u"yyyy-MM-ddThh:mm:ss.z t"_s << 1900 + << QDateTime(QDate(2001, 9, 15), QTime(9, 33, 1, 1), + QTimeZone::fromSecondsAheadOfUtc(36000)); QTest::newRow("offset-from-utc:UTC+10:00") // Time-spec specifier at the beginning - << QString("UTC+10:00 2008-10-13T07:33") << QString("t yyyy-MM-ddThh:mm") - << QDateTime(QDate(2008, 10, 13), QTime(7, 33), Qt::OffsetFromUTC, 36000); + << u"UTC+10:00 2008-10-13T07:33"_s << u"t yyyy-MM-ddThh:mm"_s << 1900 + << QDateTime(QDate(2008, 10, 13), QTime(7, 33), + QTimeZone::fromSecondsAheadOfUtc(36000)); QTest::newRow("offset-from-utc:UTC-03:30") // Time-spec specifier in the middle - << QString("2008-10-13 UTC-03:30 11.50") << QString("yyyy-MM-dd t hh.mm") - << QDateTime(QDate(2008, 10, 13), QTime(11, 50), Qt::OffsetFromUTC, -12600); + << u"2008-10-13 UTC-03:30 11.50"_s << u"yyyy-MM-dd t hh.mm"_s << 1900 + << QDateTime(QDate(2008, 10, 13), QTime(11, 50), + QTimeZone::fromSecondsAheadOfUtc(-12600)); QTest::newRow("offset-from-utc:UTC-2") // Time-spec specifier joined with text/time - << QString("2008-10-13 UTC-2Z11.50") << QString("yyyy-MM-dd tZhh.mm") - << QDateTime(QDate(2008, 10, 13), QTime(11, 50), Qt::OffsetFromUTC, -7200); + << u"2008-10-13 UTC-2Z11.50"_s << u"yyyy-MM-dd tZhh.mm"_s << 1900 + << QDateTime(QDate(2008, 10, 13), QTime(11, 50), + QTimeZone::fromSecondsAheadOfUtc(-7200)); QTest::newRow("offset-from-utc:followed-by-colon") - << QString("2008-10-13 UTC-0100:11.50") << QString("yyyy-MM-dd t:hh.mm") - << QDateTime(QDate(2008, 10, 13), QTime(11, 50), Qt::OffsetFromUTC, -3600); + << u"2008-10-13 UTC-0100:11.50"_s << u"yyyy-MM-dd t:hh.mm"_s << 1900 + << QDateTime(QDate(2008, 10, 13), QTime(11, 50), + QTimeZone::fromSecondsAheadOfUtc(-3600)); QTest::newRow("offset-from-utc:late-colon") - << QString("2008-10-13 UTC+05T:11.50") << QString("yyyy-MM-dd tT:hh.mm") - << QDateTime(QDate(2008, 10, 13), QTime(11, 50), Qt::OffsetFromUTC, 18000); + << u"2008-10-13 UTC+05T:11.50"_s << u"yyyy-MM-dd tT:hh.mm"_s << 1900 + << QDateTime(QDate(2008, 10, 13), QTime(11, 50), + QTimeZone::fromSecondsAheadOfUtc(18000)); QTest::newRow("offset-from-utc:merged-with-time") - << QString("2008-10-13 UTC+010011.50") << QString("yyyy-MM-dd thh.mm") - << QDateTime(QDate(2008, 10, 13), QTime(11, 50), Qt::OffsetFromUTC, 3600); + << u"2008-10-13 UTC+010011.50"_s << u"yyyy-MM-dd thh.mm"_s << 1900 + << QDateTime(QDate(2008, 10, 13), QTime(11, 50), + QTimeZone::fromSecondsAheadOfUtc(3600)); QTest::newRow("offset-from-utc:double-colon-delimiter") - << QString("2008-10-13 UTC+12::11.50") << QString("yyyy-MM-dd t::hh.mm") - << QDateTime(QDate(2008, 10, 13), QTime(11, 50), Qt::OffsetFromUTC, 43200); + << u"2008-10-13 UTC+12::11.50"_s << u"yyyy-MM-dd t::hh.mm"_s << 1900 + << QDateTime(QDate(2008, 10, 13), QTime(11, 50), + QTimeZone::fromSecondsAheadOfUtc(43200)); QTest::newRow("offset-from-utc:3-digit-with-colon") - << QString("2008-10-13 -4:30 11.50") << QString("yyyy-MM-dd t hh.mm") - << QDateTime(QDate(2008, 10, 13), QTime(11, 50), Qt::OffsetFromUTC, -16200); - QTest::newRow("offset-from-utc:merged-with-time") - << QString("2008-10-13 UTC+010011.50") << QString("yyyy-MM-dd thh.mm") - << QDateTime(QDate(2008, 10, 13), QTime(11, 50), Qt::OffsetFromUTC, 3600); + << u"2008-10-13 -4:30 11.50"_s << u"yyyy-MM-dd t hh.mm"_s << 1900 + << QDateTime(QDate(2008, 10, 13), QTime(11, 50), + QTimeZone::fromSecondsAheadOfUtc(-16200)); QTest::newRow("offset-from-utc:with-colon-merged-with-time") - << QString("2008-10-13 UTC+01:0011.50") << QString("yyyy-MM-dd thh.mm") - << QDateTime(QDate(2008, 10, 13), QTime(11, 50), Qt::OffsetFromUTC, 3600); + << u"2008-10-13 UTC+01:0011.50"_s << u"yyyy-MM-dd thh.mm"_s << 1900 + << QDateTime(QDate(2008, 10, 13), QTime(11, 50), + QTimeZone::fromSecondsAheadOfUtc(3600)); QTest::newRow("invalid-offset-from-utc:out-of-range") - << QString("2001-09-15T09:33:01.001-50") << QString("yyyy-MM-ddThh:mm:ss.zt") - << QDateTime(); + << u"2001-09-15T09:33:01.001-50"_s << u"yyyy-MM-ddThh:mm:ss.zt"_s << 1900 + << QDateTime(); QTest::newRow("invalid-offset-from-utc:single-digit-format") - << QString("2001-09-15T09:33:01.001+5") << QString("yyyy-MM-ddThh:mm:ss.zt") - << QDateTime(); + << u"2001-09-15T09:33:01.001+5"_s << u"yyyy-MM-ddThh:mm:ss.zt"_s << 1900 << QDateTime(); QTest::newRow("invalid-offset-from-utc:three-digit-format") - << QString("2001-09-15T09:33:01.001-701") << QString("yyyy-MM-ddThh:mm:ss.zt") - << QDateTime(); + << u"2001-09-15T09:33:01.001-701"_s << u"yyyy-MM-ddThh:mm:ss.zt"_s << 1900 + << QDateTime(); QTest::newRow("invalid-offset-from-utc:three-digit-minutes") - << QString("2001-09-15T09:33:01.001+11:570") << QString("yyyy-MM-ddThh:mm:ss.zt") - << QDateTime(); + << u"2001-09-15T09:33:01.001+11:570"_s << u"yyyy-MM-ddThh:mm:ss.zt"_s << 1900 + << QDateTime(); QTest::newRow("invalid-offset-from-utc:single-digit-minutes") - << QString("2001-09-15T09:33:01.001+11:5") << QString("yyyy-MM-ddThh:mm:ss.zt") - << QDateTime(); + << u"2001-09-15T09:33:01.001+11:5"_s << u"yyyy-MM-ddThh:mm:ss.zt"_s << 1900 + << QDateTime(); QTest::newRow("invalid-offset-from-utc:invalid-sign-symbol") - << QString("2001-09-15T09:33:01.001 ~11:30") << QString("yyyy-MM-ddThh:mm:ss.z t") - << QDateTime(); + << u"2001-09-15T09:33:01.001 ~11:30"_s << u"yyyy-MM-ddThh:mm:ss.z t"_s << 1900 + << QDateTime(); QTest::newRow("invalid-offset-from-utc:symbol-in-hours") - << QString("2001-09-15T09:33:01.001 UTC+o8:30") << QString("yyyy-MM-ddThh:mm:ss.z t") - << QDateTime(); + << u"2001-09-15T09:33:01.001 UTC+o8:30"_s << u"yyyy-MM-ddThh:mm:ss.z t"_s << 1900 + << QDateTime(); QTest::newRow("invalid-offset-from-utc:symbol-in-minutes") - << QString("2001-09-15T09:33:01.001 UTC+08:3i") << QString("yyyy-MM-ddThh:mm:ss.z t") - << QDateTime(); + << u"2001-09-15T09:33:01.001 UTC+08:3i"_s << u"yyyy-MM-ddThh:mm:ss.z t"_s << 1900 + << QDateTime(); QTest::newRow("invalid-offset-from-utc:UTC+123") // Invalid offset (UTC and 3 digit format) - << QString("2001-09-15T09:33:01.001 UTC+123") << QString("yyyy-MM-ddThh:mm:ss.z t") - << QDateTime(); + << u"2001-09-15T09:33:01.001 UTC+123"_s << u"yyyy-MM-ddThh:mm:ss.z t"_s << 1900 + << QDateTime(); QTest::newRow("invalid-offset-from-utc:UTC+00005") // Invalid offset with leading zeroes - << QString("2001-09-15T09:33:01.001 UTC+00005") << QString("yyyy-MM-ddThh:mm:ss.z t") - << QDateTime(); + << u"2001-09-15T09:33:01.001 UTC+00005"_s << u"yyyy-MM-ddThh:mm:ss.z t"_s << 1900 + << QDateTime(); QTest::newRow("invalid-offset-from-utc:three-digit-with-colon-delimiter") - << QString("2008-10-13 +123:11.50") << QString("yyyy-MM-dd t:hh.mm") - << QDateTime(); + << u"2008-10-13 +123:11.50"_s << u"yyyy-MM-dd t:hh.mm"_s << 1900 << QDateTime(); QTest::newRow("invalid-offset-from-utc:double-colon-as-part-of-offset") - << QString("2008-10-13 UTC+12::11.50") << QString("yyyy-MM-dd thh.mm") - << QDateTime(); + << u"2008-10-13 UTC+12::11.50"_s << u"yyyy-MM-dd thh.mm"_s << 1900 << QDateTime(); QTest::newRow("invalid-offset-from-utc:single-colon-as-part-of-offset") - << QString("2008-10-13 UTC+12::11.50") << QString("yyyy-MM-dd t:hh.mm") - << QDateTime(); + << u"2008-10-13 UTC+12::11.50"_s << u"yyyy-MM-dd t:hh.mm"_s << 1900 << QDateTime(); QTest::newRow("invalid-offset-from-utc:starts-with-colon") - << QString("2008-10-13 UTC+:59 11.50") << QString("yyyy-MM-dd t hh.mm") - << QDateTime(); + << u"2008-10-13 UTC+:59 11.50"_s << u"yyyy-MM-dd t hh.mm"_s << 1900 << QDateTime(); QTest::newRow("invalid-offset-from-utc:empty-offset") - << QString("2008-10-13 UTC+ 11.50") << QString("yyyy-MM-dd t hh.mm") - << QDateTime(); + << u"2008-10-13 UTC+ 11.50"_s << u"yyyy-MM-dd t hh.mm"_s << 1900 << QDateTime(); QTest::newRow("invalid-offset-from-utc:time-section-instead-of-offset") - << QString("2008-10-13 UTC+11.50") << QString("yyyy-MM-dd thh.mm") - << QDateTime(); + << u"2008-10-13 UTC+11.50"_s << u"yyyy-MM-dd thh.mm"_s << 1900 << QDateTime(); QTest::newRow("invalid-offset-from-utc:missing-minutes-if-colon") - << QString("2008-10-13 +05: 11.50") << QString("yyyy-MM-dd t hh.mm") - << QDateTime(); + << u"2008-10-13 +05: 11.50"_s << u"yyyy-MM-dd t hh.mm"_s << 1900 << QDateTime(); QTest::newRow("invalid-offset-from-utc:1-digit-minutes-if-colon") - << QString("2008-10-13 UTC+05:1 11.50") << QString("yyyy-MM-dd t hh.mm") - << QDateTime(); + << u"2008-10-13 UTC+05:1 11.50"_s << u"yyyy-MM-dd t hh.mm"_s << 1900 << QDateTime(); QTest::newRow("invalid-time-spec:random-symbol") - << QString("2001-09-15T09:33:01.001 $") << QString("yyyy-MM-ddThh:mm:ss.z t") - << QDateTime(); + << u"2001-09-15T09:33:01.001 $"_s << u"yyyy-MM-ddThh:mm:ss.z t"_s << 1900 + << QDateTime(); QTest::newRow("invalid-time-spec:random-digit") - << QString("2001-09-15T09:33:01.001 1") << QString("yyyy-MM-ddThh:mm:ss.z t") - << QDateTime(); + << u"2001-09-15T09:33:01.001 1"_s << u"yyyy-MM-ddThh:mm:ss.z t"_s << 1900 + << QDateTime(); QTest::newRow("invalid-offset-from-utc:merged-with-time") - << QString("2008-10-13 UTC+0111.50") << QString("yyyy-MM-dd thh.mm") - << QDateTime(); + << u"2008-10-13 UTC+0111.50"_s << u"yyyy-MM-dd thh.mm"_s << 1900 << QDateTime(); QTest::newRow("invalid-offset-from-utc:with-colon-3-digit-merged-with-time") - << QString("2008-10-13 UTC+01:011.50") << QString("yyyy-MM-dd thh.mm") - << QDateTime(); + << u"2008-10-13 UTC+01:011.50"_s << u"yyyy-MM-dd thh.mm"_s << 1900 << QDateTime(); QTest::newRow("invalid-time-spec:empty") - << QString("2001-09-15T09:33:01.001 ") << QString("yyyy-MM-ddThh:mm:ss.z t") - << QDateTime(); + << u"2001-09-15T09:33:01.001 "_s << u"yyyy-MM-ddThh:mm:ss.z t"_s << 1900 << QDateTime(); #if QT_CONFIG(timezone) QTimeZone southBrazil("America/Sao_Paulo"); if (southBrazil.isValid()) { QTest::newRow("spring-forward-midnight") - << QString("2008-10-19 23:45.678 America/Sao_Paulo") << QString("yyyy-MM-dd mm:ss.zzz t") - // That's in the hour skipped - expect the matching time after the spring-forward, in DST: - << QDateTime(QDate(2008, 10, 19), QTime(1, 23, 45, 678), southBrazil); + // NB: no hour field, so hour takes its default, 0, so that + // default can be over-ridden: + << u"2008-10-19 23:45.678 America/Sao_Paulo"_s << u"yyyy-MM-dd mm:ss.zzz t"_s << 1900 + // That's in the hour skipped - expect the matching time after + // the spring-forward, in DST: + << QDateTime(QDate(2008, 10, 19), QTime(1, 23, 45, 678), southBrazil); } QTimeZone berlintz("Europe/Berlin"); if (berlintz.isValid()) { QTest::newRow("begin-of-high-summer-time-with-tz") - << QString("1947-05-11 03:23:45.678 Europe/Berlin") - << QString("yyyy-MM-dd hh:mm:ss.zzz t") - // That's in the hour skipped - expecting an invalid DateTime - << QDateTime(QDate(1947, 5, 11), QTime(3, 23, 45, 678), berlintz); + << u"1947-05-11 03:23:45.678 Europe/Berlin"_s << u"yyyy-MM-dd hh:mm:ss.zzz t"_s + // That's in the hour skipped - expecting an invalid DateTime + << 1900 << QDateTime(QDate(1947, 5, 11), QTime(3, 23, 45, 678), berlintz); } #endif - QTest::newRow("late") << QString("9999-12-31T23:59:59.999Z") - << QString("yyyy-MM-ddThh:mm:ss.zZ") - << QDateTime(QDate(9999, 12, 31), QTime(23, 59, 59, 999)); + QTest::newRow("late") + << u"9999-12-31T23:59:59.999Z"_s << u"yyyy-MM-ddThh:mm:ss.zZ"_s << 1900 + << QDateTime(QDate(9999, 12, 31), QTime(23, 59, 59, 999)); // Separators match /([^aAdhHMmstyz]*)/ QTest::newRow("oddly-separated") // To show broken-separator's format is valid. - << QStringLiteral("2018 wilful long working block relief 12-19T21:09 cruel blurb encore flux") - << QStringLiteral("yyyy wilful long working block relief MM-ddThh:mm cruel blurb encore flux") - << QDateTime(QDate(2018, 12, 19), QTime(21, 9)); + << u"2018 wilful long working block relief 12-19T21:09 cruel blurb encore flux"_s + << u"yyyy wilful long working block relief MM-ddThh:mm cruel blurb encore flux"_s + << 1900 << QDateTime(QDate(2018, 12, 19), QTime(21, 9)); QTest::newRow("broken-separator") - << QStringLiteral("2018 wilful") - << QStringLiteral("yyyy wilful long working block relief MM-ddThh:mm cruel blurb encore flux") - << QDateTime(); + << u"2018 wilful"_s + << u"yyyy wilful long working block relief MM-ddThh:mm cruel blurb encore flux"_s + << 1900 << QDateTime(); QTest::newRow("broken-terminator") - << QStringLiteral("2018 wilful long working block relief 12-19T21:09 cruel") - << QStringLiteral("yyyy wilful long working block relief MM-ddThh:mm cruel blurb encore flux") - << QDateTime(); + << u"2018 wilful long working block relief 12-19T21:09 cruel"_s + << u"yyyy wilful long working block relief MM-ddThh:mm cruel blurb encore flux"_s + << 1900 << QDateTime(); // test unicode - QTest::newRow("unicode handling") << QString(u8"2005🤣06🤣28T07🤣57🤣30.001Z") - << QString(u8"yyyy🤣MM🤣ddThh🤣mm🤣ss.zt") - << QDateTime(QDate(2005, 6, 28), QTime(7, 57, 30, 1), Qt::UTC); - - // QTBUG-84349 - QTest::newRow("QTBUG-84349: positive sign in month") - << QStringLiteral("9922+221102233Z") << QStringLiteral("yyyyMMddHHmmsst") - << QDateTime(); + QTest::newRow("unicode handling") + << QString(u8"2005🤣06🤣28T07🤣57🤣30.001Z") + << QString(u8"yyyy🤣MM🤣ddThh🤣mm🤣ss.zt") << 1900 + << QDateTime(QDate(2005, 6, 28), QTime(7, 57, 30, 1), UTC); + + // Two tests derived from malformed ASN.1 strings (QTBUG-84349): + QTest::newRow("curft+ASN.1:UTC") + << u"22+221102233Z"_s << u"yyMMddHHmmsst"_s << 1900 << QDateTime(); + QTest::newRow("curft+ASN.1:Generalized") + << u"9922+221102233Z"_s << u"yyyyMMddHHmmsst"_s << 1900 << QDateTime(); + // Verify baseYear needed by plain ASN.1 works: + QTest::newRow("ASN.1:UTC-start") + << u"500101000000Z"_s << u"yyMMddHHmmsst"_s << 1950 + << QDate(1950, 1, 1).startOfDay(QTimeZone::UTC); + QTest::newRow("ASN.1:UTC-end") + << u"491231235959Z"_s << u"yyMMddHHmmsst"_s << 1950 + << QDate(2049, 12, 31).endOfDay(QTimeZone::UTC).addMSecs(-999); // fuzzer test QTest::newRow("integer overflow found by fuzzer") - << QStringLiteral("EEE1200000MUB") << QStringLiteral("t") - << QDateTime(); + << u"EEE1200000MUB"_s << u"t"_s << 1900 << QDateTime(); + + // Rich time-zone specifiers (QTBUG-95966): + const auto east3hours = QTimeZone::fromSecondsAheadOfUtc(10800); + QTest::newRow("timezone-tt-with-offset:+0300") + << u"2008-10-13 +0300 11.50"_s << u"yyyy-MM-dd tt hh.mm"_s + << 1900 << QDateTime(QDate(2008, 10, 13), QTime(11, 50), east3hours); + QTest::newRow("timezone-ttt-with-offset:+03:00") + << u"2008-10-13 +03:00 11.50"_s << u"yyyy-MM-dd ttt hh.mm"_s + << 1900 << QDateTime(QDate(2008, 10, 13), QTime(11, 50), east3hours); + QTest::newRow("timezone-tttt-with-offset:+03:00") + << u"2008-10-13 +03:00 11.50"_s << u"yyyy-MM-dd tttt hh.mm"_s + << 1900 << QDateTime(); // Offset not valid when zone name expected. } void tst_QDateTime::fromStringStringFormat() { QFETCH(QString, string); QFETCH(QString, format); + QFETCH(int, baseYear); QFETCH(QDateTime, expected); - QDateTime dt = QDateTime::fromString(string, format); + QDateTime dt = QDateTime::fromString(string, format, baseYear); QCOMPARE(dt, expected); if (expected.isValid()) { QCOMPARE(dt.timeSpec(), expected.timeSpec()); -#if QT_CONFIG(timezone) - if (expected.timeSpec() == Qt::TimeZone) - QCOMPARE(dt.timeZone(), expected.timeZone()); -#endif - // OffsetFromUTC needs an offset check - we may as well do it for all: - QCOMPARE(dt.offsetFromUtc(), expected.offsetFromUtc()); + QCOMPARE(dt.timeRepresentation(), dt.timeRepresentation()); } else { QCOMPARE(dt.isValid(), expected.isValid()); QCOMPARE(dt.toMSecsSinceEpoch(), expected.toMSecsSinceEpoch()); @@ -2901,43 +3368,73 @@ void tst_QDateTime::fromStringStringFormat_localTimeZone_data() QTest::addColumn<QByteArray>("localTimeZone"); QTest::addColumn<QString>("string"); QTest::addColumn<QString>("format"); + QTest::addColumn<int>("baseYear"); QTest::addColumn<QDateTime>("expected"); #if QT_CONFIG(timezone) + bool lacksRows = true; + // Note that the localTimeZone needn't match the zone used in the string and + // expected date-time; indeed, having them different is probably best. + // Both zones need to be valid; GMT always is, so is a safe one to use for + // whichever the test-case doesn't care about (if that applies to either). QTimeZone etcGmtWithOffset("Etc/GMT+3"); if (etcGmtWithOffset.isValid()) { - QTest::newRow("local-timezone-with-offset:Etc/GMT+3") << QByteArrayLiteral("GMT") - << QString("2008-10-13 Etc/GMT+3 11.50") << QString("yyyy-MM-dd t hh.mm") - << QDateTime(QDate(2008, 10, 13), QTime(11, 50), etcGmtWithOffset); - // TODO QTBUG-95966: find better ways to use repeated 't' - QTest::newRow("double-timezone-with-offset:Etc/GMT+3") << QByteArrayLiteral("GMT") - << QString("2008-10-13 Etc/GMT+3Etc/GMT+3 11.50") << QString("yyyy-MM-dd tt hh.mm") - << QDateTime(QDate(2008, 10, 13), QTime(11, 50), etcGmtWithOffset); + lacksRows = false; + QTest::newRow("local-timezone-t-with-zone:Etc/GMT+3") + << "GMT"_ba << u"2008-10-13 Etc/GMT+3 11.50"_s << u"yyyy-MM-dd t hh.mm"_s << 1900 + << QDateTime(QDate(2008, 10, 13), QTime(11, 50), etcGmtWithOffset); + QTest::newRow("local-timezone-tttt-with-zone:Etc/GMT+3") + << "GMT"_ba << u"2008-10-13 Etc/GMT+3 11.50"_s << u"yyyy-MM-dd tttt hh.mm"_s << 1900 + << QDateTime(QDate(2008, 10, 13), QTime(11, 50), etcGmtWithOffset); } + QTest::newRow("local-timezone-tt-with-zone:Etc/GMT+3") + << "GMT"_ba << u"2008-10-13 Etc/GMT+3 11.50"_s << u"yyyy-MM-dd tt hh.mm"_s << 1900 + << QDateTime(); // Zone name not valid when offset expected + QTest::newRow("local-timezone-ttt-with-zone:Etc/GMT+3") + << "GMT"_ba << u"2008-10-13 Etc/GMT+3 11.50"_s << u"yyyy-MM-dd ttt hh.mm"_s << 1900 + << QDateTime(); // Zone name not valid when offset expected QTimeZone gmtWithOffset("GMT-2"); if (gmtWithOffset.isValid()) { - QTest::newRow("local-timezone-with-offset:GMT-2") << QByteArrayLiteral("GMT") - << QString("2008-10-13 GMT-2 11.50") << QString("yyyy-MM-dd t hh.mm") - << QDateTime(QDate(2008, 10, 13), QTime(11, 50), gmtWithOffset); + lacksRows = false; + QTest::newRow("local-timezone-with-offset:GMT-2") + << "GMT"_ba << u"2008-10-13 GMT-2 11.50"_s << u"yyyy-MM-dd t hh.mm"_s << 1900 + << QDateTime(QDate(2008, 10, 13), QTime(11, 50), gmtWithOffset); } QTimeZone gmt("GMT"); if (gmt.isValid()) { - QTest::newRow("local-timezone-with-offset:GMT") << QByteArrayLiteral("GMT") - << QString("2008-10-13 GMT 11.50") << QString("yyyy-MM-dd t hh.mm") - << QDateTime(QDate(2008, 10, 13), QTime(11, 50), gmt); + lacksRows = false; + const bool fullyLocal = ([]() { + TimeZoneRollback useZone("GMT"); + return qTzName(0) == u"GMT"_s; + })(); + QTest::newRow("local-timezone-with-offset:GMT") + << "GMT"_ba << u"2008-10-13 GMT 11.50"_s << u"yyyy-MM-dd t hh.mm"_s << 1900 + << QDateTime(QDate(2008, 10, 13), QTime(11, 50), + fullyLocal ? QTimeZone(QTimeZone::LocalTime) : gmt); } QTimeZone helsinki("Europe/Helsinki"); if (helsinki.isValid()) { - // QTBUG-96861: QAsn1Element::toDateTime() tripped over an assert in - // QTimeZonePrivate::dataForLocalTime() on macOS and iOS. - // The first 20m 11s of 1921-05-01 were skipped, so the parser's attempt - // to construct a local time after scanning yyMM tripped up on the start - // of the day, when the zone backend lacked transition data. - QTest::newRow("Helsinki-joins-EET") - << QByteArrayLiteral("Europe/Helsinki") - << QString("210506000000Z") << QString("yyMMddHHmmsst") - << QDateTime(QDate(1921, 5, 6), QTime(0, 0), Qt::UTC); + lacksRows = false; + // QTBUG-96861: QAsn1Element::toDateTime() tripped over an assert due to + // the first 20m 11s of 1921-05-01 being skipped, so the parser's + // attempt to construct a local time after scanning yyMM tripped up on + // the start of the day, when the zone backend lacked transition data. + // (Because QDTP tries to use local time until it reads the final zone + // field, constructing a new QDT after reading each field, hence + // transiently wanting 1921-05-01 00:00:00 before reading the dd field.) + QTest::newRow("Helsinki-joins-EET:19") + << "Europe/Helsinki"_ba << u"210506000000Z"_s << u"yyMMddHHmmsst"_s << 1900 + << QDateTime(QDate(1921, 5, 6), QTime(0, 0), UTC); + // Strictly, ASN.1 wants us to parse that with a different baseYear, so + // check that, too, but tweak to match the 1921 transition's mid-point: + QTest::newRow("Helsinki-joins-EET:20") + << "Europe/Helsinki"_ba << u"210501001006Z"_s << u"yyMMddHHmmsst"_s << 1950 + << QDateTime(QDate(2021, 5, 1), QTime(0, 10, 6), UTC); } + if (lacksRows) + QSKIP("Testcases all use zones unsupported on this platform"); +#else + QSKIP("Test only possible with timezone support enabled"); #endif } @@ -2956,25 +3453,26 @@ void tst_QDateTime::offsetFromUtc() QCOMPARE(QDateTime().offsetFromUtc(), 0); // Offset constructor - QDateTime dt1(QDate(2013, 1, 1), QTime(1, 0), Qt::OffsetFromUTC, 60 * 60); + QDateTime dt1(QDate(2013, 1, 1), QTime(1, 0), QTimeZone::fromSecondsAheadOfUtc(60 * 60)); QCOMPARE(dt1.offsetFromUtc(), 60 * 60); + QVERIFY(dt1.timeRepresentation().isValid()); #if QT_CONFIG(timezone) QVERIFY(dt1.timeZone().isValid()); #endif - dt1 = QDateTime(QDate(2013, 1, 1), QTime(1, 0), Qt::OffsetFromUTC, -60 * 60); + dt1 = QDateTime(QDate(2013, 1, 1), QTime(1, 0), QTimeZone::fromSecondsAheadOfUtc(-60 * 60)); QCOMPARE(dt1.offsetFromUtc(), -60 * 60); // UTC should be 0 offset - QDateTime dt2(QDate(2013, 1, 1), QTime(0, 0), Qt::UTC); + QDateTime dt2(QDate(2013, 1, 1), QTime(0, 0), UTC); QCOMPARE(dt2.offsetFromUtc(), 0); // LocalTime should vary if (zoneIsCET) { // Time definitely in Standard Time so 1 hour ahead - QDateTime dt3(QDate(2013, 1, 1), QTime(0, 0), Qt::LocalTime); + QDateTime dt3 = QDate(2013, 1, 1).startOfDay(); QCOMPARE(dt3.offsetFromUtc(), 1 * 60 * 60); // Time definitely in Daylight Time so 2 hours ahead - QDateTime dt4(QDate(2013, 6, 1), QTime(0, 0), Qt::LocalTime); + QDateTime dt4 = QDate(2013, 6, 1).startOfDay(); QCOMPARE(dt4.offsetFromUtc(), 2 * 60 * 60); } else { qDebug("Skipped some tests specific to Central European Time " @@ -2990,6 +3488,9 @@ void tst_QDateTime::offsetFromUtc() #endif } +#if QT_DEPRECATED_SINCE(6, 9) +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED void tst_QDateTime::setOffsetFromUtc() { /* Basic tests. */ @@ -3065,7 +3566,7 @@ void tst_QDateTime::setOffsetFromUtc() void tst_QDateTime::toOffsetFromUtc() { - QDateTime dt1(QDate(2013, 1, 1), QTime(0, 0), Qt::UTC); + QDateTime dt1(QDate(2013, 1, 1), QTime(0, 0), QTimeZone::UTC); QDateTime dt2 = dt1.toOffsetFromUtc(60 * 60); QCOMPARE(dt2, dt1); @@ -3085,6 +3586,8 @@ void tst_QDateTime::toOffsetFromUtc() QCOMPARE(dt2.date(), QDate(2013, 1, 1)); QCOMPARE(dt2.time(), QTime(0, 0)); } +QT_WARNING_POP +#endif // 6.9 deprecation void tst_QDateTime::zoneAtTime_data() { @@ -3160,28 +3663,46 @@ void tst_QDateTime::zoneAtTime() void tst_QDateTime::timeZoneAbbreviation() { - QDateTime dt1(QDate(2013, 1, 1), QTime(1, 0), Qt::OffsetFromUTC, 60 * 60); + QDateTime dt1(QDate(2013, 1, 1), QTime(1, 0), QTimeZone::fromSecondsAheadOfUtc(60 * 60)); QCOMPARE(dt1.timeZoneAbbreviation(), QString("UTC+01:00")); - QDateTime dt2(QDate(2013, 1, 1), QTime(1, 0), Qt::OffsetFromUTC, -60 * 60); + QDateTime dt2(QDate(2013, 1, 1), QTime(1, 0), QTimeZone::fromSecondsAheadOfUtc(-60 * 60)); QCOMPARE(dt2.timeZoneAbbreviation(), QString("UTC-01:00")); - QDateTime dt3(QDate(2013, 1, 1), QTime(0, 0), Qt::UTC); + QDateTime dt3(QDate(2013, 1, 1), QTime(0, 0), UTC); QCOMPARE(dt3.timeZoneAbbreviation(), QString("UTC")); // LocalTime should vary if (zoneIsCET) { // Time definitely in Standard Time - QDateTime dt4(QDate(2013, 1, 1), QTime(0, 0), Qt::LocalTime); + QDateTime dt4 = QDate(2013, 1, 1).startOfDay(); + /* Note that MET is functionally an alias for CET (their zoneinfo files + differ only in the first letter of the abbreviations), unlike the + various zones that give CET as their abbreviation. + */ + { + const auto abbrev = dt4.timeZoneAbbreviation(); + auto reporter = qScopeGuard([abbrev]() { + qDebug() << "Unexpected abbreviation" << abbrev; + }); #ifdef Q_OS_WIN - QEXPECT_FAIL("", "Windows only reports long name (QTBUG-32759)", Continue); + QEXPECT_FAIL("", "Windows only reports long name (QTBUG-32759)", Continue); #endif - QCOMPARE(dt4.timeZoneAbbreviation(), QStringLiteral("CET")); + QVERIFY(abbrev == u"CET"_s || abbrev == u"MET"_s); + reporter.dismiss(); + } // Time definitely in Daylight Time - QDateTime dt5(QDate(2013, 6, 1), QTime(0, 0), Qt::LocalTime); + QDateTime dt5 = QDate(2013, 6, 1).startOfDay(); + { + const auto abbrev = dt5.timeZoneAbbreviation(); + auto reporter = qScopeGuard([abbrev]() { + qDebug() << "Unexpected abbreviation" << abbrev; + }); #ifdef Q_OS_WIN - QEXPECT_FAIL("", "Windows only reports long name (QTBUG-32759)", Continue); + QEXPECT_FAIL("", "Windows only reports long name (QTBUG-32759)", Continue); #endif - QCOMPARE(dt5.timeZoneAbbreviation(), QStringLiteral("CEST")); + QVERIFY(abbrev == u"CEST"_s || abbrev == u"MEST"_s); + reporter.dismiss(); + } } else { qDebug("(Skipped some CET-only tests)"); } @@ -3274,8 +3795,8 @@ void tst_QDateTime::utcOffsetLessThan() const QDateTime dt1(QDate(2002, 10, 10), QTime(0, 0)); QDateTime dt2(dt1); - dt1.setOffsetFromUtc(-(2 * 60 * 60)); // Minus two hours. - dt2.setOffsetFromUtc(-(3 * 60 * 60)); // Minus three hours. + dt1.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(-(2 * 60 * 60))); // Minus two hours. + dt2.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(-(3 * 60 * 60))); // Minus three hours. QVERIFY(dt1 != dt2); QVERIFY(!(dt1 == dt2)); @@ -3285,14 +3806,14 @@ void tst_QDateTime::utcOffsetLessThan() const void tst_QDateTime::isDaylightTime() const { - QDateTime utc1(QDate(2012, 1, 1), QTime(0, 0), Qt::UTC); + QDateTime utc1(QDate(2012, 1, 1), QTime(0, 0), UTC); QVERIFY(!utc1.isDaylightTime()); - QDateTime utc2(QDate(2012, 6, 1), QTime(0, 0), Qt::UTC); + QDateTime utc2(QDate(2012, 6, 1), QTime(0, 0), UTC); QVERIFY(!utc2.isDaylightTime()); - QDateTime offset1(QDate(2012, 1, 1), QTime(0, 0), Qt::OffsetFromUTC, 1 * 60 * 60); + QDateTime offset1(QDate(2012, 1, 1), QTime(0, 0), QTimeZone::fromSecondsAheadOfUtc(60 * 60)); QVERIFY(!offset1.isDaylightTime()); - QDateTime offset2(QDate(2012, 6, 1), QTime(0, 0), Qt::OffsetFromUTC, 1 * 60 * 60); + QDateTime offset2(QDate(2012, 6, 1), QTime(0, 0), QTimeZone::fromSecondsAheadOfUtc(60 * 60)); QVERIFY(!offset2.isDaylightTime()); if (zoneIsCET) { @@ -3321,8 +3842,8 @@ void tst_QDateTime::daylightTransitions() const const qint64 spring2012 = 1332637200000; const qint64 autumn2012 = 1351386000000; const qint64 msecsOneHour = 3600000; - QCOMPARE(spring2012, QDateTime(QDate(2012, 3, 25), QTime(1, 0), Qt::UTC).toMSecsSinceEpoch()); - QCOMPARE(autumn2012, QDateTime(QDate(2012, 10, 28), QTime(1, 0), Qt::UTC).toMSecsSinceEpoch()); + QCOMPARE(spring2012, QDateTime(QDate(2012, 3, 25), QTime(1, 0), UTC).toMSecsSinceEpoch()); + QCOMPARE(autumn2012, QDateTime(QDate(2012, 10, 28), QTime(1, 0), UTC).toMSecsSinceEpoch()); // Test for correct behviour for StandardTime -> DaylightTime transition, i.e. missing hour @@ -3330,19 +3851,32 @@ void tst_QDateTime::daylightTransitions() const QDateTime before(QDate(2012, 3, 25), QTime(1, 59, 59, 999)); QVERIFY(before.isValid()); + QVERIFY(!before.isDaylightTime()); QCOMPARE(before.date(), QDate(2012, 3, 25)); QCOMPARE(before.time(), QTime(1, 59, 59, 999)); QCOMPARE(before.toMSecsSinceEpoch(), spring2012 - 1); - QDateTime missing(QDate(2012, 3, 25), QTime(2, 0)); - QVERIFY(!missing.isValid()); - QCOMPARE(missing.date(), QDate(2012, 3, 25)); - QCOMPARE(missing.time(), QTime(2, 0)); - // datetimeparser relies on toMSecsSinceEpoch to still work: - QCOMPARE(missing.toMSecsSinceEpoch(), spring2012); + QDateTime entering(QDate(2012, 3, 25), QTime(2, 0), + QDateTime::TransitionResolution::PreferBefore); + QVERIFY(entering.isValid()); + QVERIFY(!entering.isDaylightTime()); + QCOMPARE(entering.date(), QDate(2012, 3, 25)); + QCOMPARE(entering.time(), QTime(1, 0)); + // QDateTimeParser relies on toMSecsSinceEpoch() to still work: + QCOMPARE(entering.toMSecsSinceEpoch(), spring2012 - msecsOneHour); + + QDateTime leaving(QDate(2012, 3, 25), QTime(2, 0), + QDateTime::TransitionResolution::PreferAfter); + QVERIFY(leaving.isValid()); + QVERIFY(leaving.isDaylightTime()); + QCOMPARE(leaving.date(), QDate(2012, 3, 25)); + QCOMPARE(leaving.time(), QTime(3, 0)); + // QDateTimeParser relies on toMSecsSinceEpoch to still work: + QCOMPARE(leaving.toMSecsSinceEpoch(), spring2012); QDateTime after(QDate(2012, 3, 25), QTime(3, 0)); QVERIFY(after.isValid()); + QVERIFY(after.isDaylightTime()); QCOMPARE(after.date(), QDate(2012, 3, 25)); QCOMPARE(after.time(), QTime(3, 0)); QCOMPARE(after.toMSecsSinceEpoch(), spring2012); @@ -3362,19 +3896,18 @@ void tst_QDateTime::daylightTransitions() const QCOMPARE(after.toMSecsSinceEpoch(), spring2012); // Test changing time spec re-validates the date/time - - QDateTime utc(QDate(2012, 3, 25), QTime(2, 0), Qt::UTC); + QDateTime utc(QDate(2012, 3, 25), QTime(2, 0), UTC); QVERIFY(utc.isValid()); QCOMPARE(utc.date(), QDate(2012, 3, 25)); QCOMPARE(utc.time(), QTime(2, 0)); - utc.setTimeSpec(Qt::LocalTime); - QVERIFY(!utc.isValid()); + utc.setTimeZone(QTimeZone::LocalTime); // Resolved to RelativeToBefore. + QVERIFY(utc.isValid()); QCOMPARE(utc.date(), QDate(2012, 3, 25)); - QCOMPARE(utc.time(), QTime(2, 0)); - utc.setTimeSpec(Qt::UTC); + QCOMPARE(utc.time(), QTime(3, 0)); + utc.setTimeZone(UTC); // Preserves the changed time(). QVERIFY(utc.isValid()); QCOMPARE(utc.date(), QDate(2012, 3, 25)); - QCOMPARE(utc.time(), QTime(2, 0)); + QCOMPARE(utc.time(), QTime(3, 0)); // Test date maths, if result falls in missing hour then becomes next // hour (or is always invalid; mktime() may reject gap-times). @@ -3412,19 +3945,17 @@ void tst_QDateTime::daylightTransitions() const #undef CHECK_SPRING_FORWARD // Test for correct behviour for DaylightTime -> StandardTime transition, fall-back - // TODO (QTBUG-79923): Compare to results of direct QDateTime(date, time, fold) - // construction; see Prior/Post commented-out tests. QDateTime autumnMidnight = QDate(2012, 10, 28).startOfDay(); QVERIFY(autumnMidnight.isValid()); - // QCOMPARE(autumnMidnight, QDateTime(QDate(2012, 10, 28), QTime(2, 0), Prior)); QCOMPARE(autumnMidnight.date(), QDate(2012, 10, 28)); QCOMPARE(autumnMidnight.time(), QTime(0, 0)); QCOMPARE(autumnMidnight.toMSecsSinceEpoch(), autumn2012 - 3 * msecsOneHour); QDateTime startFirst = autumnMidnight.addMSecs(2 * msecsOneHour); QVERIFY(startFirst.isValid()); - // QCOMPARE(startFirst, QDateTime(QDate(2012, 10, 28), QTime(2, 0), Prior)); + QCOMPARE(startFirst, QDateTime(QDate(2012, 10, 28), QTime(2, 0), + QDateTime::TransitionResolution::PreferBefore)); QCOMPARE(startFirst.date(), QDate(2012, 10, 28)); QCOMPARE(startFirst.time(), QTime(2, 0)); QCOMPARE(startFirst.toMSecsSinceEpoch(), autumn2012 - msecsOneHour); @@ -3432,7 +3963,9 @@ void tst_QDateTime::daylightTransitions() const // 1 msec before transition is 2:59:59.999 FirstOccurrence QDateTime endFirst = startFirst.addMSecs(msecsOneHour - 1); QVERIFY(endFirst.isValid()); - // QCOMPARE(endFirst, QDateTime(QDate(2012, 10, 28), QTime(2, 59, 59, 999), Prior)); + QCOMPARE(endFirst, + QDateTime(QDate(2012, 10, 28), QTime(2, 59, 59, 999), + QDateTime::TransitionResolution::PreferBefore)); QCOMPARE(endFirst.date(), QDate(2012, 10, 28)); QCOMPARE(endFirst.time(), QTime(2, 59, 59, 999)); QCOMPARE(endFirst.toMSecsSinceEpoch(), autumn2012 - 1); @@ -3440,7 +3973,8 @@ void tst_QDateTime::daylightTransitions() const // At the transition, starting the second pass QDateTime startRepeat = endFirst.addMSecs(1); QVERIFY(startRepeat.isValid()); - // QCOMPARE(startRepeat, QDateTime(QDate(2012, 10, 28), QTime(2, 0), Post)); + QCOMPARE(startRepeat, QDateTime(QDate(2012, 10, 28), QTime(2, 0), + QDateTime::TransitionResolution::PreferAfter)); QCOMPARE(startRepeat.date(), QDate(2012, 10, 28)); QCOMPARE(startRepeat.time(), QTime(2, 0)); QCOMPARE(startRepeat.toMSecsSinceEpoch(), autumn2012); @@ -3448,7 +3982,9 @@ void tst_QDateTime::daylightTransitions() const // 59:59.999 after transition is 2:59:59.999 SecondOccurrence QDateTime endRepeat = endFirst.addMSecs(msecsOneHour); QVERIFY(endRepeat.isValid()); - // QCOMPARE(endRepeat, QDateTime(QDate(2012, 10, 28), QTime(2, 59, 59, 999), Post)); + QCOMPARE(endRepeat, + QDateTime(QDate(2012, 10, 28), QTime(2, 59, 59, 999), + QDateTime::TransitionResolution::PreferAfter)); QCOMPARE(endRepeat.date(), QDate(2012, 10, 28)); QCOMPARE(endRepeat.time(), QTime(2, 59, 59, 999)); QCOMPARE(endRepeat.toMSecsSinceEpoch(), autumn2012 + msecsOneHour - 1); @@ -3704,9 +4240,9 @@ void tst_QDateTime::timeZones() const { #if QT_CONFIG(timezone) QTimeZone invalidTz = QTimeZone("Vulcan/ShiKahr"); - QCOMPARE(invalidTz.isValid(), false); + QVERIFY(!invalidTz.isValid()); QDateTime invalidDateTime = QDateTime(QDate(2000, 1, 1), QTime(0, 0), invalidTz); - QCOMPARE(invalidDateTime.isValid(), false); + QVERIFY(!invalidDateTime.isValid()); QCOMPARE(invalidDateTime.date(), QDate(2000, 1, 1)); QCOMPARE(invalidDateTime.time(), QTime(0, 0)); @@ -3714,39 +4250,39 @@ void tst_QDateTime::timeZones() const QTimeZone nzTzOffset = QTimeZone(12 * 3600); // During Standard Time NZ is +12:00 - QDateTime utcStd(QDate(2012, 6, 1), QTime(0, 0), Qt::UTC); + QDateTime utcStd(QDate(2012, 6, 1), QTime(0, 0), UTC); QDateTime nzStd(QDate(2012, 6, 1), QTime(12, 0), nzTz); QDateTime nzStdOffset(QDate(2012, 6, 1), QTime(12, 0), nzTzOffset); - QCOMPARE(nzStd.isValid(), true); + QVERIFY(nzStd.isValid()); QCOMPARE(nzStd.timeSpec(), Qt::TimeZone); QCOMPARE(nzStd.date(), QDate(2012, 6, 1)); QCOMPARE(nzStd.time(), QTime(12, 0)); QVERIFY(nzStd.timeZone() == nzTz); QCOMPARE(nzStd.timeZone().id(), QByteArray("Pacific/Auckland")); QCOMPARE(nzStd.offsetFromUtc(), 43200); - QCOMPARE(nzStd.isDaylightTime(), false); + QVERIFY(!nzStd.isDaylightTime()); QCOMPARE(nzStd.toMSecsSinceEpoch(), utcStd.toMSecsSinceEpoch()); - QCOMPARE(nzStdOffset.isValid(), true); + QVERIFY(nzStdOffset.isValid()); QCOMPARE(nzStdOffset.timeSpec(), Qt::TimeZone); QCOMPARE(nzStdOffset.date(), QDate(2012, 6, 1)); QCOMPARE(nzStdOffset.time(), QTime(12, 0)); QVERIFY(nzStdOffset.timeZone() == nzTzOffset); - QCOMPARE(nzStdOffset.timeZone().id(), QByteArray("UTC+12")); + QCOMPARE(nzStdOffset.timeZone().id(), QByteArray("UTC+12:00")); QCOMPARE(nzStdOffset.offsetFromUtc(), 43200); - QCOMPARE(nzStdOffset.isDaylightTime(), false); + QVERIFY(!nzStdOffset.isDaylightTime()); QCOMPARE(nzStdOffset.toMSecsSinceEpoch(), utcStd.toMSecsSinceEpoch()); // During Daylight Time NZ is +13:00 - QDateTime utcDst(QDate(2012, 1, 1), QTime(0, 0), Qt::UTC); + QDateTime utcDst(QDate(2012, 1, 1), QTime(0, 0), UTC); QDateTime nzDst(QDate(2012, 1, 1), QTime(13, 0), nzTz); - QCOMPARE(nzDst.isValid(), true); + QVERIFY(nzDst.isValid()); QCOMPARE(nzDst.date(), QDate(2012, 1, 1)); QCOMPARE(nzDst.time(), QTime(13, 0)); QCOMPARE(nzDst.offsetFromUtc(), 46800); - QCOMPARE(nzDst.isDaylightTime(), true); + QVERIFY(nzDst.isDaylightTime()); QCOMPARE(nzDst.toMSecsSinceEpoch(), utcDst.toMSecsSinceEpoch()); QDateTime utc = nzStd.toUTC(); @@ -3766,7 +4302,7 @@ void tst_QDateTime::timeZones() const QCOMPARE(aus.date(), QDate(2012, 6, 1)); QCOMPARE(aus.time(), QTime(10, 0)); - QDateTime dt1(QDate(2012, 6, 1), QTime(0, 0), Qt::UTC); + QDateTime dt1(QDate(2012, 6, 1), QTime(0, 0), UTC); QCOMPARE(dt1.timeSpec(), Qt::UTC); dt1.setTimeZone(nzTz); QCOMPARE(dt1.timeSpec(), Qt::TimeZone); @@ -3778,13 +4314,13 @@ void tst_QDateTime::timeZones() const QCOMPARE(dt2.date(), dt1.date()); QCOMPARE(dt2.time(), dt1.time()); QCOMPARE(dt2.timeSpec(), dt1.timeSpec()); - QCOMPARE(dt2.timeZone(), dt1.timeZone()); + QCOMPARE(dt2.timeRepresentation(), dt1.timeRepresentation()); QDateTime dt3 = QDateTime::fromMSecsSinceEpoch(1338465600000, nzTz); QCOMPARE(dt3.date(), dt1.date()); QCOMPARE(dt3.time(), dt1.time()); QCOMPARE(dt3.timeSpec(), dt1.timeSpec()); - QCOMPARE(dt3.timeZone(), dt1.timeZone()); + QCOMPARE(dt3.timeRepresentation(), dt1.timeRepresentation()); // The start of year 1 should be *describable* in any zone (QTBUG-78051) dt3 = QDateTime(QDate(1, 1, 1), QTime(0, 0), ausTz); @@ -3814,7 +4350,7 @@ void tst_QDateTime::timeZones() const // Standard Time to Daylight Time 2013 on 2013-03-31 is 2:00 local time / 1:00 UTC const qint64 gapMSecs = 1364691600000; - QCOMPARE(gapMSecs, QDateTime(QDate(2013, 3, 31), QTime(1, 0), Qt::UTC).toMSecsSinceEpoch()); + QCOMPARE(gapMSecs, QDateTime(QDate(2013, 3, 31), QTime(1, 0), UTC).toMSecsSinceEpoch()); // Test MSecs to local // - Test 1 msec before tran = 01:59:59.999 @@ -3841,18 +4377,57 @@ void tst_QDateTime::timeZones() const QCOMPARE(atGap.toMSecsSinceEpoch(), gapMSecs); // - Test transition hole, setting 02:00:00 is invalid QDateTime inGap = QDateTime(QDate(2013, 3, 31), QTime(2, 0), cet); - QVERIFY(!inGap.isValid()); + QVERIFY(inGap.isValid()); QCOMPARE(inGap.date(), QDate(2013, 3, 31)); - QCOMPARE(inGap.time(), QTime(2, 0)); - // - Test transition hole, setting 02:59:59.999 is invalid + QCOMPARE(inGap.time(), QTime(3, 0)); + QCOMPARE(inGap.offsetFromUtc(), 7200); + // - Test transition hole, 02:59:59.999 was skipped: inGap = QDateTime(QDate(2013, 3, 31), QTime(2, 59, 59, 999), cet); - QVERIFY(!inGap.isValid()); + QVERIFY(inGap.isValid()); QCOMPARE(inGap.date(), QDate(2013, 3, 31)); - QCOMPARE(inGap.time(), QTime(2, 59, 59, 999)); + QCOMPARE(inGap.time(), QTime(3, 59, 59, 999)); + QCOMPARE(inGap.offsetFromUtc(), 7200); + // Test similar for local time, if it's CET: + if (zoneIsCET) { + inGap = QDateTime(QDate(2013, 3, 31), QTime(2, 30)); + QVERIFY(inGap.isValid()); + QCOMPARE(inGap.date(), QDate(2013, 3, 31)); + QCOMPARE(inGap.offsetFromUtc(), 7200); + QCOMPARE(inGap.time(), QTime(3, 30)); + } + + // Test a gap more than 1'141'707.91-years from 1970, outside ShortData's range, + // The zone version is non-short in any case, but check it anyway. + // However, we can only test this if the underlying OS believes CET continues + // exercising DST indefinitely; Darwin, for example, assumes we'll have all + // kicked the habit by the end of 2100. + constexpr int longYear = 1'143'678; + constexpr qint64 millisInWeek = qint64(7) * 24 * 60 * 60 * 1000; + if (QDateTime(QDate(longYear, 3, 24), QTime(12, 0), cet).msecsTo( + QDateTime(QDate(longYear, 3, 31), QTime(12, 0), cet)) < millisInWeek) { + inGap = QDateTime(QDate(longYear, 3, 27), QTime(2, 30), cet); + QVERIFY(inGap.isValid()); + QCOMPARE(inGap.date(), QDate(longYear, 3, 27)); + QCOMPARE(inGap.time(), QTime(3, 30)); + QCOMPARE(inGap.offsetFromUtc(), 7200); + } else { + qDebug("Skipping far-future check beyond zoned end of DST"); + } + if (zoneIsCET && QDateTime(QDate(longYear, 3, 24), QTime(12, 0)).msecsTo( + QDateTime(QDate(longYear, 3, 31), QTime(12, 0))) < millisInWeek) { + inGap = QDateTime(QDate(longYear, 3, 27), QTime(2, 30)); + QVERIFY(inGap.isValid()); + QCOMPARE(inGap.date(), QDate(longYear, 3, 27)); + QCOMPARE(inGap.offsetFromUtc(), 7200); + QCOMPARE(inGap.time(), QTime(3, 30)); + } else { + qDebug(zoneIsCET ? "Skipping far-future check beyond local end of DST" + : "Skipping CET-specific test"); + } // Standard Time to Daylight Time 2013 on 2013-10-27 is 3:00 local time / 1:00 UTC const qint64 replayMSecs = 1382835600000; - QCOMPARE(replayMSecs, QDateTime(QDate(2013, 10, 27), QTime(1, 0), Qt::UTC).toMSecsSinceEpoch()); + QCOMPARE(replayMSecs, QDateTime(QDate(2013, 10, 27), QTime(1, 0), UTC).toMSecsSinceEpoch()); // Test MSecs to local // - Test 1 hour before tran = 02:00:00 local first occurrence @@ -3874,7 +4449,7 @@ void tst_QDateTime::timeZones() const // - Test 1 hour after tran = 03:00:00 local QDateTime hourAfter = QDateTime::fromMSecsSinceEpoch(replayMSecs + 3600000, cet); QCOMPARE(hourAfter.date(), QDate(2013, 10, 27)); - QCOMPARE(hourAfter.time(), QTime(3, 0, 0)); + QCOMPARE(hourAfter.time(), QTime(3, 0)); // TODO (QTBUG-79923): Compare to results of direct QDateTime(date, time, cet, fold) // construction; see Prior/Post commented-out tests. @@ -3926,14 +4501,14 @@ void tst_QDateTime::systemTimeZoneChange() const // Start out in Brisbane time: TimeZoneRollback useZone(QByteArray("AEST-10:00")); - if (QDateTime(date, early, Qt::LocalTime).offsetFromUtc() != 600 * 60) + if (QDateTime(date, early).offsetFromUtc() != 600 * 60) QSKIP("Test depends on system support for changing zone to AEST-10:00"); #if QT_CONFIG(timezone) QVERIFY(QTimeZone::systemTimeZone().isValid()); #endif - const QDateTime localDate = QDateTime(date, early, Qt::LocalTime); - const QDateTime utcDate = QDateTime(date, early, Qt::UTC); + const QDateTime localDate = QDateTime(date, early); + const QDateTime utcDate = QDateTime(date, early, UTC); const qint64 localMsecs = localDate.toMSecsSinceEpoch(); const qint64 utcMsecs = utcDate.toMSecsSinceEpoch(); #if QT_CONFIG(timezone) @@ -3950,16 +4525,16 @@ void tst_QDateTime::systemTimeZoneChange() const // Change to Indian time useZone.reset(QByteArray("IST-05:30")); - if (QDateTime(date, early, Qt::LocalTime).offsetFromUtc() != 330 * 60) + if (QDateTime(date, early).offsetFromUtc() != 330 * 60) QSKIP("Test depends on system support for changing zone to IST-05:30"); #if QT_CONFIG(timezone) QVERIFY(QTimeZone::systemTimeZone().isValid()); #endif - QCOMPARE(localDate, QDateTime(date, early, Qt::LocalTime)); + QCOMPARE(localDate, QDateTime(date, early)); // Note: localDate.toMSecsSinceEpoch == localMsecs, unchanged, iff localDate is pimpled. - QVERIFY(localMsecs != QDateTime(date, early, Qt::LocalTime).toMSecsSinceEpoch()); - QCOMPARE(utcDate, QDateTime(date, early, Qt::UTC)); + QVERIFY(localMsecs != QDateTime(date, early).toMSecsSinceEpoch()); + QCOMPARE(utcDate, QDateTime(date, early, UTC)); QCOMPARE(utcDate.toMSecsSinceEpoch(), utcMsecs); #if QT_CONFIG(timezone) QCOMPARE(tzDate.toMSecsSinceEpoch(), tzMsecs); @@ -3979,17 +4554,19 @@ void tst_QDateTime::invalid_data() const QTest::newRow("simple") << invalidDate << Qt::LocalTime << true; QTest::newRow("UTC") << invalidDate.toUTC() << Qt::UTC << true; QTest::newRow("offset") - << invalidDate.toOffsetFromUtc(3600) << Qt::OffsetFromUTC << true; + << invalidDate.toTimeZone(QTimeZone::fromSecondsAheadOfUtc(3600)) << Qt::OffsetFromUTC + << true; #if QT_CONFIG(timezone) QTest::newRow("CET") << invalidDate.toTimeZone(QTimeZone("Europe/Oslo")) << Qt::TimeZone << true; // Crash tests, QTBUG-80146: QTest::newRow("nozone+construct") - << QDateTime(QDate(1970, 1, 1), QTime(12, 0), QTimeZone()) << Qt::TimeZone << false; + << QDateTime(QDate(1970, 1, 1), QTime(12, 0), QTimeZone()) + << Qt::TimeZone << false; QTest::newRow("nozone+fromMSecs") << QDateTime::fromMSecsSinceEpoch(42, QTimeZone()) << Qt::TimeZone << false; - QDateTime valid(QDate(1970, 1, 1), QTime(12, 0), Qt::UTC); + QDateTime valid(QDate(1970, 1, 1), QTime(12, 0), UTC); QTest::newRow("tonozone") << valid.toTimeZone(QTimeZone()) << Qt::TimeZone << false; #endif } @@ -4005,38 +4582,36 @@ void tst_QDateTime::invalid() const if (!goodZone) QCOMPARE(when.toMSecsSinceEpoch(), 0); QVERIFY(!when.isDaylightTime()); -#if QT_CONFIG(timezone) - QCOMPARE(when.timeZone().isValid(), goodZone); -#endif + QCOMPARE(when.timeRepresentation().isValid(), goodZone); } void tst_QDateTime::range() const { using Bounds = std::numeric_limits<qint64>; - QCOMPARE(QDateTime::fromMSecsSinceEpoch(Bounds::min() + 1, Qt::UTC).date().year(), + QCOMPARE(QDateTime::fromMSecsSinceEpoch(Bounds::min() + 1, UTC).date().year(), int(QDateTime::YearRange::First)); - QCOMPARE(QDateTime::fromMSecsSinceEpoch(Bounds::max() - 1, Qt::UTC).date().year(), + QCOMPARE(QDateTime::fromMSecsSinceEpoch(Bounds::max() - 1, UTC).date().year(), int(QDateTime::YearRange::Last)); constexpr qint64 millisPerDay = 24 * 3600 * 1000; constexpr qint64 wholeDays = Bounds::max() / millisPerDay; constexpr qint64 millisRemainder = Bounds::max() % millisPerDay; QVERIFY(QDateTime(QDate(1970, 1, 1).addDays(wholeDays), QTime::fromMSecsSinceStartOfDay(millisRemainder), - Qt::UTC).isValid()); + UTC).isValid()); QVERIFY(!QDateTime(QDate(1970, 1, 1).addDays(wholeDays), QTime::fromMSecsSinceStartOfDay(millisRemainder + 1), - Qt::UTC).isValid()); + UTC).isValid()); QVERIFY(QDateTime(QDate(1970, 1, 1).addDays(-wholeDays - 1), QTime::fromMSecsSinceStartOfDay(3600 * 24000 - millisRemainder - 1), - Qt::UTC).isValid()); + UTC).isValid()); QVERIFY(!QDateTime(QDate(1970, 1, 1).addDays(-wholeDays - 1), QTime::fromMSecsSinceStartOfDay(3600 * 24000 - millisRemainder - 2), - Qt::UTC).isValid()); + UTC).isValid()); } void tst_QDateTime::macTypes() { -#ifndef Q_OS_MAC +#ifndef Q_OS_DARWIN QSKIP("This is a Apple-only test"); #else extern void tst_QDateTime_macTypes(); // in qdatetime_mac.mm @@ -4044,5 +4619,223 @@ void tst_QDateTime::macTypes() #endif } +#if __cpp_lib_chrono >= 201907L +using StdSysMillis = std::chrono::sys_time<std::chrono::milliseconds>; +Q_DECLARE_METATYPE(StdSysMillis); +#endif + +void tst_QDateTime::stdCompatibilitySysTime_data() +{ +#if __cpp_lib_chrono >= 201907L + QTest::addColumn<StdSysMillis>("sysTime"); + QTest::addColumn<QDateTime>("expected"); + + using namespace std::chrono; + + QTest::newRow("zero") + << StdSysMillis(0s) + << QDateTime(QDate(1970, 1, 1), QTime(0, 0), UTC); + QTest::newRow("1s") + << StdSysMillis(1s) + << QDateTime(QDate(1970, 1, 1), QTime(0, 0, 1), UTC); + QTest::newRow("1ms") + << StdSysMillis(1ms) + << QDateTime(QDate(1970, 1, 1), QTime(0, 0, 0, 1), UTC); + QTest::newRow("365d") + << StdSysMillis(days(365)) + << QDateTime(QDate(1971, 1, 1), QTime(0, 0), UTC); + QTest::newRow("-1s") + << StdSysMillis(-1s) + << QDateTime(QDate(1969, 12, 31), QTime(23, 59, 59), UTC); + QTest::newRow("-1ms") + << StdSysMillis(-1ms) + << QDateTime(QDate(1969, 12, 31), QTime(23, 59, 59, 999), UTC); + + { + // The first leap second occurred on 30 June 1972 at 23:59:60. + // Check that QDateTime does not take that leap second into account (like sys_time) + const year_month_day firstLeapSecondDate = 1972y/July/1; + const sys_days firstLeapSecondDateAsSysDays = firstLeapSecondDate; + QTest::newRow("first_leap_second") + << StdSysMillis(firstLeapSecondDateAsSysDays) + << QDateTime(QDate(1972, 7, 1), QTime(0, 0), UTC); + } + + { + // Random date + const sys_days date = 2000y/January/31; + const StdSysMillis dateTime = date + 3h + 10min + 42s; + QTest::newRow("2000-01-31 03:10:42") + << dateTime + << QDateTime(QDate(2000, 1, 31), QTime(3, 10, 42), UTC); + } +#else + QSKIP("This test requires C++20's <chrono>."); +#endif +} + +void tst_QDateTime::stdCompatibilitySysTime() +{ +#if __cpp_lib_chrono >= 201907L + QFETCH(StdSysMillis, sysTime); + QFETCH(QDateTime, expected); + + using namespace std::chrono; + + // system_clock in milliseconds -> QDateTime + QDateTime dtFromSysTime = QDateTime::fromStdTimePoint(sysTime); + QCOMPARE(dtFromSysTime, expected); + QCOMPARE(dtFromSysTime.timeSpec(), Qt::UTC); + + // QDateTime -> system_clock in milliseconds + StdSysMillis sysTimeFromDt = dtFromSysTime.toStdSysMilliseconds(); + QCOMPARE(sysTimeFromDt, sysTime); + + // system_clock in seconds -> QDateTime + sys_seconds sysTimeSecs = floor<seconds>(sysTime); + QDateTime dtFromSysSeconds = QDateTime::fromStdTimePoint(sysTimeSecs); + QDateTime expectedInSeconds = expected.addMSecs(-expected.time().msec()); // "floor" + QCOMPARE(dtFromSysSeconds, expectedInSeconds); + QCOMPARE(dtFromSysSeconds.timeSpec(), Qt::UTC); + + // QDateTime -> system_clock in seconds + sys_seconds sysTimeFromDtSecs = dtFromSysSeconds.toStdSysSeconds(); + QCOMPARE(sysTimeFromDtSecs, sysTimeSecs); + + // utc_clock in milliseconds -> QDateTime + utc_time<std::chrono::milliseconds> utcTime = utc_clock::from_sys(sysTime); + QDateTime dtFromUtcTime = QDateTime::fromStdTimePoint(utcTime); + QCOMPARE(dtFromUtcTime, expected); + QCOMPARE(dtFromUtcTime.timeSpec(), Qt::UTC); + + // QDateTime -> system_clock in milliseconds + sysTimeFromDt = dtFromUtcTime.toStdSysMilliseconds(); + QCOMPARE(sysTimeFromDt, sysTime); +#else + QSKIP("This test requires C++20's <chrono>."); +#endif +} + +#if __cpp_lib_chrono >= 201907L +using StdLocalMillis = std::chrono::local_time<std::chrono::milliseconds>; +Q_DECLARE_METATYPE(StdLocalMillis); +#endif + +void tst_QDateTime::stdCompatibilityLocalTime_data() +{ +#if __cpp_lib_chrono >= 201907L + QTest::addColumn<StdLocalMillis>("localTime"); + QTest::addColumn<QDateTime>("expected"); + + using namespace std::chrono; + + QTest::newRow("zero") + << StdLocalMillis(0s) + << QDateTime(QDate(1970, 1, 1), QTime(0, 0)); + QTest::newRow("1s") + << StdLocalMillis(1s) + << QDateTime(QDate(1970, 1, 1), QTime(0, 0, 1)); + QTest::newRow("1ms") + << StdLocalMillis(1ms) + << QDateTime(QDate(1970, 1, 1), QTime(0, 0, 0, 1)); + QTest::newRow("365d") + << StdLocalMillis(days(365)) + << QDateTime(QDate(1971, 1, 1), QTime(0, 0)); + QTest::newRow("-1s") + << StdLocalMillis(-1s) + << QDateTime(QDate(1969, 12, 31), QTime(23, 59, 59)); + QTest::newRow("-1ms") + << StdLocalMillis(-1ms) + << QDateTime(QDate(1969, 12, 31), QTime(23, 59, 59, 999)); + { + // Random date + const local_days date = local_days(2000y/January/31); + const StdLocalMillis dateTime = date + 3h + 10min + 42s; + QTest::newRow("2000-01-31 03:10:42") + << dateTime + << QDateTime(QDate(2000, 1, 31), QTime(3, 10, 42)); + } +#else + QSKIP("This test requires C++20's <chrono>."); +#endif +} + +void tst_QDateTime::stdCompatibilityLocalTime() +{ +#if __cpp_lib_chrono >= 201907L + QFETCH(StdLocalMillis, localTime); + QFETCH(QDateTime, expected); + + using namespace std::chrono; + + QDateTime dtFromLocalTime = QDateTime::fromStdLocalTime(localTime); + QCOMPARE(dtFromLocalTime, expected); + QCOMPARE(dtFromLocalTime.timeSpec(), Qt::LocalTime); + + const time_zone *tz = current_zone(); + QVERIFY(tz); + const StdSysMillis sysMillis = tz->to_sys(localTime); + QCOMPARE(dtFromLocalTime.toStdSysMilliseconds(), sysMillis); +#else + QSKIP("This test requires C++20's <chrono>."); +#endif +} + +#if QT_CONFIG(timezone) +#if __cpp_lib_chrono >= 201907L +using StdZonedMillis = std::chrono::zoned_time<std::chrono::milliseconds>; +Q_DECLARE_METATYPE(StdZonedMillis); +#endif + +void tst_QDateTime::stdCompatibilityZonedTime_data() +{ +#if __cpp_lib_chrono >= 201907L + QTest::addColumn<StdZonedMillis>("zonedTime"); + QTest::addColumn<QDateTime>("expected"); + + using namespace std::chrono; + using namespace std::literals; + + const char timeZoneName[] = "Europe/Oslo"; + const QTimeZone timeZone(timeZoneName); + + { + StdZonedMillis zs(timeZoneName, local_days(2021y/1/1)); + QTest::addRow("localTimeOslo") + << zs + << QDateTime(QDate(2021, 1, 1), QTime(0, 0), timeZone); + } + { + StdZonedMillis zs(timeZoneName, sys_days(2021y/1/1)); + QTest::addRow("sysTimeOslo") + << zs + << QDateTime(QDate(2021, 1, 1), QTime(1, 0), timeZone); + } + { + StdZonedMillis zs(timeZoneName, sys_days(2021y/7/1)); + QTest::addRow("sysTimeOslo summer") + << zs + << QDateTime(QDate(2021, 7, 1), QTime(2, 0), timeZone); + } +#else + QSKIP("This test requires C++20's <chrono>."); +#endif +} + +void tst_QDateTime::stdCompatibilityZonedTime() +{ +#if __cpp_lib_chrono >= 201907L + QFETCH(StdZonedMillis, zonedTime); + QFETCH(QDateTime, expected); + + QDateTime dtFromZonedTime = QDateTime::fromStdZonedTime(zonedTime); + QCOMPARE(dtFromZonedTime, expected); + QCOMPARE(dtFromZonedTime.timeSpec(), Qt::TimeZone); +#else + QSKIP("This test requires C++20's <chrono>."); +#endif +} +#endif // QT_CONFIG(timezone) + QTEST_APPLESS_MAIN(tst_QDateTime) #include "tst_qdatetime.moc" diff --git a/tests/auto/corelib/time/qdatetime/tst_qdatetime_mac.mm b/tests/auto/corelib/time/qdatetime/tst_qdatetime_mac.mm index 295454c472..08379ccb41 100644 --- a/tests/auto/corelib/time/qdatetime/tst_qdatetime_mac.mm +++ b/tests/auto/corelib/time/qdatetime/tst_qdatetime_mac.mm @@ -1,35 +1,12 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Copyright (C) 2014 Petroules Corporation. -** 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) 2020 The Qt Company Ltd. +// Copyright (C) 2014 Petroules Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QtCore/QDateTime> #include <QTest> +#include <QtCore/private/qcore_mac_p.h> + #include <CoreFoundation/CoreFoundation.h> #include <Foundation/Foundation.h> diff --git a/tests/auto/corelib/time/qdatetimeparser/CMakeLists.txt b/tests/auto/corelib/time/qdatetimeparser/CMakeLists.txt index 9bdeb62501..6bdd66ebfc 100644 --- a/tests/auto/corelib/time/qdatetimeparser/CMakeLists.txt +++ b/tests/auto/corelib/time/qdatetimeparser/CMakeLists.txt @@ -1,12 +1,19 @@ -# Generated from qdatetimeparser.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qdatetimeparser Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qdatetimeparser LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qdatetimeparser SOURCES tst_qdatetimeparser.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::CorePrivate ) diff --git a/tests/auto/corelib/time/qdatetimeparser/tst_qdatetimeparser.cpp b/tests/auto/corelib/time/qdatetimeparser/tst_qdatetimeparser.cpp index 98dd36bc32..bfc811eebe 100644 --- a/tests/auto/corelib/time/qdatetimeparser/tst_qdatetimeparser.cpp +++ b/tests/auto/corelib/time/qdatetimeparser/tst_qdatetimeparser.cpp @@ -1,34 +1,11 @@ -/**************************************************************************** -** -** Copyright (C) 2020 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) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QTest> #include <private/qdatetimeparser_p.h> +using namespace Qt::StringLiterals; + QT_BEGIN_NAMESPACE // access to needed members in QDateTimeParser @@ -71,6 +48,7 @@ class tst_QDateTimeParser : public QObject Q_OBJECT private Q_SLOTS: + void reparse(); void parseSection_data(); void parseSection(); @@ -78,6 +56,61 @@ private Q_SLOTS: void intermediateYear(); }; +void tst_QDateTimeParser::reparse() +{ + const QDateTime when = QDate(2023, 6, 15).startOfDay(); + // QTBUG-114575: 6.2 through 6.5 got back a bogus Qt::TimeZone (with zero offset): + const auto expect = ([](QStringView name) { + // When local time is UTC or a fixed offset from it, the parser prefers + // to interpret a UTC or offset suffix as such, rather than as local + // time (thereby avoiding DST-ness checks). We have to match that here. + if (name == "UTC"_L1) + return Qt::UTC; + if (name.startsWith(u'+') || name.startsWith(u'-')) { + if (std::all_of(name.begin() + 1, name.end(), [](QChar ch) { return ch == u'0'; })) + return Qt::UTC; + if (std::all_of(name.begin() + 1, name.end(), [](QChar ch) { return ch.isDigit(); })) + return Qt::OffsetFromUTC; + // Potential hh:mm offset ? Not yet seen as local tzname[] entry. + } + return Qt::LocalTime; + }); + + const QStringView format = u"dd/MM/yyyy HH:mm t"; + QDateTimeParser who(QMetaType::QDateTime, QDateTimeParser::DateTimeEdit); + QVERIFY(who.parseFormat(format)); + { + // QDTP defaults to the system locale. + const auto state = who.parse(QLocale::system().toString(when, format), -1, when, false); + QCOMPARE(state.state, QDateTimeParser::Acceptable); + QVERIFY(!state.conflicts); + QCOMPARE(state.padded, 0); + QCOMPARE(state.value.timeSpec(), expect(when.timeZoneAbbreviation())); + QCOMPARE(state.value, when); + } + { + // QDT::toString() uses the C locale: + who.setDefaultLocale(QLocale::c()); + const QString zoneName = ([when]() { +#if QT_CONFIG(timezone) + if (QLocale::c() != QLocale::system()) { + const QString local = when.timeRepresentation().displayName( + when, QTimeZone::ShortName, QLocale::c()); + if (!local.isEmpty()) + return local; + } +#endif + return when.timeZoneAbbreviation(); + })(); + const auto state = who.parse(when.toString(format), -1, when, false); + QCOMPARE(state.state, QDateTimeParser::Acceptable); + QVERIFY(!state.conflicts); + QCOMPARE(state.padded, 0); + QCOMPARE(state.value.timeSpec(), expect(zoneName)); + QCOMPARE(state.value, when); + } +} + void tst_QDateTimeParser::parseSection_data() { QTest::addColumn<QString>("format"); @@ -165,10 +198,12 @@ void tst_QDateTimeParser::intermediateYear() QVERIFY(testParser.parseFormat(format)); + // Indian/Cocos has a transition at the start of 1900, so it started this + // day at 00:02:20, throwing a time offset into QDTP. QDateTime val(QDate(1900, 1, 1).startOfDay()); const QDateTimeParser::StateNode tmp = testParser.parse(input, -1, val, false); QCOMPARE(tmp.state, QDateTimeParser::Intermediate); - QCOMPARE(tmp.value, expected.startOfDay()); + QCOMPARE(tmp.value.date(), expected); } QTEST_APPLESS_MAIN(tst_QDateTimeParser) diff --git a/tests/auto/corelib/time/qtime/CMakeLists.txt b/tests/auto/corelib/time/qtime/CMakeLists.txt index 6d3034c595..6fe2968107 100644 --- a/tests/auto/corelib/time/qtime/CMakeLists.txt +++ b/tests/auto/corelib/time/qtime/CMakeLists.txt @@ -1,15 +1,23 @@ -# Generated from qtime.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qtime Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qtime LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qtime SOURCES tst_qtime.cpp DEFINES QT_NO_FOREACH QT_NO_KEYWORDS - PUBLIC_LIBRARIES + LIBRARIES Qt::CorePrivate + Qt::TestPrivate ) diff --git a/tests/auto/corelib/time/qtime/tst_qtime.cpp b/tests/auto/corelib/time/qtime/tst_qtime.cpp index 480fbcc14f..c0fdb07115 100644 --- a/tests/auto/corelib/time/qtime/tst_qtime.cpp +++ b/tests/auto/corelib/time/qtime/tst_qtime.cpp @@ -1,32 +1,8 @@ -/**************************************************************************** -** -** Copyright (C) 2020 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) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <private/qglobal_p.h> +#include <private/qcomparisontesthelper_p.h> #include <QTest> #include "qdatetime.h" #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) @@ -52,12 +28,11 @@ private Q_SLOTS: void addMSecs(); void addSecs_data(); void addSecs(); + void orderingCompiles(); void operator_eq_eq_data(); void operator_eq_eq(); - void operator_lt(); - void operator_gt(); - void operator_lt_eq(); - void operator_gt_eq(); + void ordering_data(); + void ordering(); #if QT_CONFIG(datestring) # if QT_CONFIG(datetimeparser) void fromStringFormat_data(); @@ -309,7 +284,7 @@ void tst_QTime::secsTo_data() QTest::newRow("disregard msec (1s)") << QTime(12, 30, 1, 500) << QTime(12, 30, 2, 400) << 1; QTest::newRow("disregard msec (0s)") << QTime(12, 30, 1, 500) << QTime(12, 30, 1, 900) << 0; QTest::newRow("disregard msec (-1s)") << QTime(12, 30, 2, 400) << QTime(12, 30, 1, 500) << -1; - QTest::newRow("disregard msec (0s)") << QTime(12, 30, 1, 900) << QTime(12, 30, 1, 500) << 0; + QTest::newRow("disregard msec (-0s)") << QTime(12, 30, 1, 900) << QTime(12, 30, 1, 500) << 0; } void tst_QTime::secsTo() @@ -345,6 +320,11 @@ void tst_QTime::msecsTo() QCOMPARE( t1.msecsTo( t2 ), delta ); } +void tst_QTime::orderingCompiles() +{ + QTestPrivate::testAllComparisonOperatorsCompile<QTime>(); +} + void tst_QTime::operator_eq_eq_data() { QTest::addColumn<QTime>("t1"); @@ -370,169 +350,42 @@ void tst_QTime::operator_eq_eq() QFETCH(QTime, t2); QFETCH(bool, expectEqual); - bool equal = t1 == t2; - QCOMPARE(equal, expectEqual); - bool notEqual = t1 != t2; - QCOMPARE(notEqual, !expectEqual); + QT_TEST_EQUALITY_OPS(t1, t2, expectEqual); - if (equal) + if (expectEqual) QVERIFY(qHash(t1) == qHash(t2)); } -void tst_QTime::operator_lt() +void tst_QTime::ordering_data() { - QTime t1(0,0,0,0); - QTime t2(0,0,0,0); - QVERIFY( !(t1 < t2) ); - - t1 = QTime(12,34,56,20); - t2 = QTime(12,34,56,30); - QVERIFY( t1 < t2 ); - - t1 = QTime(13,34,46,20); - t2 = QTime(13,34,56,20); - QVERIFY( t1 < t2 ); - - t1 = QTime(13,24,56,20); - t2 = QTime(13,34,56,20); - QVERIFY( t1 < t2 ); - - t1 = QTime(12,34,56,20); - t2 = QTime(13,34,56,20); - QVERIFY( t1 < t2 ); - - t1 = QTime(14,34,56,20); - t2 = QTime(13,34,56,20); - QVERIFY( !(t1 < t2) ); - - t1 = QTime(13,44,56,20); - t2 = QTime(13,34,56,20); - QVERIFY( !(t1 < t2) ); - - t1 = QTime(13,34,56,20); - t2 = QTime(13,34,46,20); - QVERIFY( !(t1 < t2) ); - - t1 = QTime(13,44,56,30); - t2 = QTime(13,44,56,20); - QVERIFY( !(t1 < t2) ); + QTest::addColumn<QTime>("left"); + QTest::addColumn<QTime>("right"); + QTest::addColumn<Qt::strong_ordering>("expectedOrdering"); + + auto generateRow = [](QTime t1, QTime t2, Qt::strong_ordering ordering) { + const QByteArray t1Str = t1.toString("hh:mm:ss.zz").toLatin1(); + const QByteArray t2Str = t2.toString("hh:mm:ss.zz").toLatin1(); + QTest::addRow("%s_vs_%s", t1Str.constData(), t2Str.constData()) << t1 << t2 << ordering; + }; + + generateRow(QTime(0, 0), QTime(0, 0), Qt::strong_ordering::equivalent); + generateRow(QTime(12, 34, 56, 20), QTime(12, 34, 56, 30), Qt::strong_ordering::less); + generateRow(QTime(13, 34, 46, 20), QTime(13, 34, 56, 20), Qt::strong_ordering::less); + generateRow(QTime(13, 24, 56, 20), QTime(13, 34, 56, 20), Qt::strong_ordering::less); + generateRow(QTime(12, 34, 56, 20), QTime(13, 34, 56, 20), Qt::strong_ordering::less); + generateRow(QTime(14, 34, 56, 20), QTime(13, 34, 56, 20), Qt::strong_ordering::greater); + generateRow(QTime(13, 44, 56, 20), QTime(13, 34, 56, 20), Qt::strong_ordering::greater); + generateRow(QTime(13, 34, 56, 20), QTime(13, 34, 46, 20), Qt::strong_ordering::greater); + generateRow(QTime(13, 34, 56, 30), QTime(13, 34, 56, 20), Qt::strong_ordering::greater); } -void tst_QTime::operator_gt() +void tst_QTime::ordering() { - QTime t1(0,0,0,0); - QTime t2(0,0,0,0); - QVERIFY( !(t1 > t2) ); + QFETCH(QTime, left); + QFETCH(QTime, right); + QFETCH(Qt::strong_ordering, expectedOrdering); - t1 = QTime(12,34,56,20); - t2 = QTime(12,34,56,30); - QVERIFY( !(t1 > t2) ); - - t1 = QTime(13,34,46,20); - t2 = QTime(13,34,56,20); - QVERIFY( !(t1 > t2) ); - - t1 = QTime(13,24,56,20); - t2 = QTime(13,34,56,20); - QVERIFY( !(t1 > t2) ); - - t1 = QTime(12,34,56,20); - t2 = QTime(13,34,56,20); - QVERIFY( !(t1 > t2) ); - - t1 = QTime(14,34,56,20); - t2 = QTime(13,34,56,20); - QVERIFY( t1 > t2 ); - - t1 = QTime(13,44,56,20); - t2 = QTime(13,34,56,20); - QVERIFY( t1 > t2 ); - - t1 = QTime(13,34,56,20); - t2 = QTime(13,34,46,20); - QVERIFY( t1 > t2 ); - - t1 = QTime(13,44,56,30); - t2 = QTime(13,44,56,20); - QVERIFY( t1 > t2 ); -} - -void tst_QTime::operator_lt_eq() -{ - QTime t1(0,0,0,0); - QTime t2(0,0,0,0); - QVERIFY( t1 <= t2 ); - - t1 = QTime(12,34,56,20); - t2 = QTime(12,34,56,30); - QVERIFY( t1 <= t2 ); - - t1 = QTime(13,34,46,20); - t2 = QTime(13,34,56,20); - QVERIFY( t1 <= t2 ); - - t1 = QTime(13,24,56,20); - t2 = QTime(13,34,56,20); - QVERIFY( t1 <= t2 ); - - t1 = QTime(12,34,56,20); - t2 = QTime(13,34,56,20); - QVERIFY( t1 <= t2 ); - - t1 = QTime(14,34,56,20); - t2 = QTime(13,34,56,20); - QVERIFY( !(t1 <= t2) ); - - t1 = QTime(13,44,56,20); - t2 = QTime(13,34,56,20); - QVERIFY( !(t1 <= t2) ); - - t1 = QTime(13,34,56,20); - t2 = QTime(13,34,46,20); - QVERIFY( !(t1 <= t2) ); - - t1 = QTime(13,44,56,30); - t2 = QTime(13,44,56,20); - QVERIFY( !(t1 <= t2) ); -} - -void tst_QTime::operator_gt_eq() -{ - QTime t1(0,0,0,0); - QTime t2(0,0,0,0); - QVERIFY( t1 >= t2 ); - - t1 = QTime(12,34,56,20); - t2 = QTime(12,34,56,30); - QVERIFY( !(t1 >= t2) ); - - t1 = QTime(13,34,46,20); - t2 = QTime(13,34,56,20); - QVERIFY( !(t1 >= t2) ); - - t1 = QTime(13,24,56,20); - t2 = QTime(13,34,56,20); - QVERIFY( !(t1 >= t2) ); - - t1 = QTime(12,34,56,20); - t2 = QTime(13,34,56,20); - QVERIFY( !(t1 >= t2) ); - - t1 = QTime(14,34,56,20); - t2 = QTime(13,34,56,20); - QVERIFY( t1 >= t2 ); - - t1 = QTime(13,44,56,20); - t2 = QTime(13,34,56,20); - QVERIFY( t1 >= t2 ); - - t1 = QTime(13,34,56,20); - t2 = QTime(13,34,46,20); - QVERIFY( t1 >= t2 ); - - t1 = QTime(13,44,56,30); - t2 = QTime(13,44,56,20); - QVERIFY( t1 >= t2 ); + QT_TEST_ALL_COMPARISON_OPS(left, right, expectedOrdering); } #if QT_CONFIG(datestring) @@ -718,7 +571,7 @@ void tst_QTime::fromStringDateFormat_data() << Qt::RFC2822Date << invalidTime(); // The common date text used by the "invalid character" tests, just to be // sure *it's* not what's invalid: - QTest::newRow("RFC 850 and 1036 invalid character at end") + QTest::newRow("RFC 850 and 1036 no invalid character") << QString::fromLatin1("Sun Jan 01 08:00:00 2012 +0100") << Qt::RFC2822Date << QTime(8, 0, 0); diff --git a/tests/auto/corelib/time/qtimezone/CMakeLists.txt b/tests/auto/corelib/time/qtimezone/CMakeLists.txt index 160ade2afd..612bab0db5 100644 --- a/tests/auto/corelib/time/qtimezone/CMakeLists.txt +++ b/tests/auto/corelib/time/qtimezone/CMakeLists.txt @@ -1,17 +1,25 @@ -# Generated from qtimezone.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qtimezone Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qtimezone LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qtimezone SOURCES tst_qtimezone.cpp DEFINES QT_NO_FOREACH QT_NO_KEYWORDS - PUBLIC_LIBRARIES + LIBRARIES Qt::CorePrivate + Qt::TestPrivate ) ## Scopes: @@ -25,6 +33,6 @@ qt_internal_extend_target(tst_qtimezone CONDITION QT_FEATURE_icu qt_internal_extend_target(tst_qtimezone CONDITION APPLE SOURCES tst_qtimezone_darwin.mm - PUBLIC_LIBRARIES + LIBRARIES ${FWFoundation} ) diff --git a/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp b/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp index 479c2771d7..a350ffeb04 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); } } @@ -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" diff --git a/tests/auto/corelib/time/qtimezone/tst_qtimezone_darwin.mm b/tests/auto/corelib/time/qtimezone/tst_qtimezone_darwin.mm index 040bd941ae..f4ef15036d 100644 --- a/tests/auto/corelib/time/qtimezone/tst_qtimezone_darwin.mm +++ b/tests/auto/corelib/time/qtimezone/tst_qtimezone_darwin.mm @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2017 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) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QtCore/QTimeZone> #include <QTest> @@ -34,7 +9,7 @@ void tst_QTimeZone_darwinTypes() { -#if !defined(QT_NO_SYSTEMLOCALE) +#if QT_CONFIG(timezone) // QTimeZone <-> CFTimeZone { QTimeZone qtTimeZone("America/Los_Angeles"); @@ -64,5 +39,5 @@ void tst_QTimeZone_darwinTypes() QVERIFY([qtTimeZone.toNSTimeZone() isEqual:nsTimeZone]); [autoreleasepool release]; } -#endif +#endif // feature timezone } |