diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt index ac82ca87827..b7a351dac5d 100644 --- a/src/corelib/CMakeLists.txt +++ b/src/corelib/CMakeLists.txt @@ -923,24 +923,40 @@ qt_internal_extend_target(Core CONDITION QT_FEATURE_timezone time/qtimezoneprivate_data_p.h ) -qt_internal_extend_target(Core CONDITION APPLE AND QT_FEATURE_timezone +qt_internal_extend_target(Core + CONDITION + QT_FEATURE_timezone_tzdb + SOURCES + time/qtimezoneprivate_chrono.cpp +) + +qt_internal_extend_target(Core + CONDITION + QT_FEATURE_timezone AND APPLE AND NOT QT_FEATURE_timezone_tzdb SOURCES time/qtimezoneprivate_mac.mm ) -qt_internal_extend_target(Core CONDITION QT_FEATURE_timezone AND ANDROID AND NOT APPLE +qt_internal_extend_target(Core + CONDITION + QT_FEATURE_timezone AND ANDROID + AND NOT APPLE AND NOT QT_FEATURE_timezone_tzdb SOURCES time/qtimezoneprivate_android.cpp ) -qt_internal_extend_target(Core CONDITION QT_FEATURE_timezone AND UNIX AND NOT ANDROID AND NOT APPLE +qt_internal_extend_target(Core + CONDITION + QT_FEATURE_timezone AND UNIX + AND NOT ANDROID AND NOT APPLE AND NOT QT_FEATURE_timezone_tzdb SOURCES time/qtimezoneprivate_tz.cpp ) qt_internal_extend_target(Core CONDITION - QT_FEATURE_icu AND QT_FEATURE_timezone AND NOT UNIX + QT_FEATURE_icu AND QT_FEATURE_timezone + AND NOT UNIX AND NOT QT_FEATURE_timezone_tzdb SOURCES time/qtimezoneprivate_icu.cpp ) @@ -949,18 +965,21 @@ qt_internal_extend_target(Core qt_internal_extend_target(Core CONDITION QT_FEATURE_timezone AND WIN32 AND NOT QT_FEATURE_icu + AND NOT QT_FEATURE_timezone_tzdb SOURCES time/qtimezoneprivate_win.cpp ) qt_internal_extend_target(Core - CONDITION QT_FEATURE_timezone_locale + CONDITION + QT_FEATURE_timezone_locale SOURCES time/qtimezonelocale.cpp time/qtimezonelocale_p.h ) qt_internal_extend_target(Core - CONDITION QT_FEATURE_timezone_locale AND NOT QT_FEATURE_icu + CONDITION + QT_FEATURE_timezone_locale AND NOT QT_FEATURE_icu SOURCES time/qtimezonelocale_data_p.h ) diff --git a/src/corelib/configure.cmake b/src/corelib/configure.cmake index 1b1f99b0c34..20524d49973 100644 --- a/src/corelib/configure.cmake +++ b/src/corelib/configure.cmake @@ -495,6 +495,28 @@ int main(int argc, char** argv) { } ") +# +qt_config_compile_test(chrono_tzdb + LABEL "Support for timezones in C++20 " + CODE +"#include +#if __cpp_lib_chrono < 201907L +#error +#endif + +int main(void) +{ + /* BEGIN TEST: */ + const std::chrono::tzdb &tzdb = std::chrono::get_tzdb(); + auto when = std::chrono::system_clock::now(); + const std::chrono::time_zone *currentZone = tzdb.current_zone(); + auto zoneInfo = currentZone->get_info(when); + /* END TEST: */ + return 0; +} +" +) + #### Features qt_feature("clock-gettime" PRIVATE @@ -908,6 +930,13 @@ qt_feature("timezone_locale" PRIVATE CONDITION QT_FEATURE_timezone AND NOT APPLE AND NOT ANDROID ) +qt_feature("timezone_tzdb" PUBLIC + SECTION "Utilities" + LABEL "std::chrono::tzdb QTZ backend" + PURPOSE "Provides support for a timezone backend using std::chrono." + CONDITION TEST_chrono_tzdb + AUTODETECT OFF +) qt_feature("datetimeparser" PRIVATE SECTION "Utilities" LABEL "QDateTimeParser" @@ -979,6 +1008,7 @@ qt_configure_add_summary_entry(ARGS "system-doubleconversion") qt_configure_add_summary_entry(ARGS "forkfd_pidfd" CONDITION LINUX) qt_configure_add_summary_entry(ARGS "glib") qt_configure_add_summary_entry(ARGS "icu") +qt_configure_add_summary_entry(ARGS "timezone_tzdb") qt_configure_add_summary_entry(ARGS "system-libb2") qt_configure_add_summary_entry(ARGS "mimetype-database") qt_configure_add_summary_entry(ARGS "permissions") diff --git a/src/corelib/time/qtimezone.cpp b/src/corelib/time/qtimezone.cpp index 7c2a3147783..fe1a1420c18 100644 --- a/src/corelib/time/qtimezone.cpp +++ b/src/corelib/time/qtimezone.cpp @@ -22,7 +22,9 @@ using namespace Qt::StringLiterals; // Create default time zone using appropriate backend static QTimeZonePrivate *newBackendTimeZone() { -#if defined(Q_OS_DARWIN) +#if QT_CONFIG(timezone_tzdb) + return new QChronoTimeZonePrivate(); +#elif defined(Q_OS_DARWIN) return new QMacTimeZonePrivate(); #elif defined(Q_OS_ANDROID) return new QAndroidTimeZonePrivate(); @@ -41,7 +43,9 @@ static QTimeZonePrivate *newBackendTimeZone() static QTimeZonePrivate *newBackendTimeZone(const QByteArray &ianaId) { Q_ASSERT(!ianaId.isEmpty()); -#if defined(Q_OS_DARWIN) +#if QT_CONFIG(timezone_tzdb) + return new QChronoTimeZonePrivate(ianaId); +#elif defined(Q_OS_DARWIN) return new QMacTimeZonePrivate(ianaId); #elif defined(Q_OS_ANDROID) return new QAndroidTimeZonePrivate(ianaId); diff --git a/src/corelib/time/qtimezoneprivate_chrono.cpp b/src/corelib/time/qtimezoneprivate_chrono.cpp new file mode 100644 index 00000000000..9b424e322ec --- /dev/null +++ b/src/corelib/time/qtimezoneprivate_chrono.cpp @@ -0,0 +1,146 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qtimezoneprivate_p.h" + +#include + +QT_BEGIN_NAMESPACE + +using namespace std::chrono; +using namespace std::chrono_literals; + +#define EXCEPTION_CHECKED(expression, fallback) \ + QT_TRY { \ + expression; \ + } QT_CATCH (const std::runtime_error &) { \ + fallback; \ + } + +static std::chrono::sys_time +chronoForEpochMillis(qint64 millis) +{ + return sys_time(milliseconds(millis)); +} + +static std::optional +infoAtEpochMillis(const std::chrono::time_zone *zone, qint64 millis) +{ + EXCEPTION_CHECKED(return zone->get_info(chronoForEpochMillis(millis)), return std::nullopt); +} + +static const std::chrono::time_zone *idToZone(std::string_view id) +{ + EXCEPTION_CHECKED(return get_tzdb().locate_zone(id), return nullptr); +} + +static QChronoTimeZonePrivate::Data +fromSysInfo(std::chrono::sys_info info, qint64 atMSecsSinceEpoch) +{ + QString abbreviation = QString::fromLatin1(info.abbrev); + int offsetFromUtc = info.offset.count(); + // offset is in seconds, save is in minutes + int standardTimeOffset = offsetFromUtc - seconds(info.save).count(); + return QChronoTimeZonePrivate::Data(abbreviation, atMSecsSinceEpoch, + offsetFromUtc, standardTimeOffset); +} + +QChronoTimeZonePrivate::QChronoTimeZonePrivate() + : m_timeZone(std::chrono::current_zone()) +{ + if (m_timeZone) + m_id.assign(m_timeZone->name()); +} + +QChronoTimeZonePrivate::QChronoTimeZonePrivate(QByteArrayView id) + : m_timeZone(idToZone(std::string_view(id.data(), id.size()))) +{ + if (m_timeZone) + m_id.assign(m_timeZone->name()); +} + +QChronoTimeZonePrivate::~QChronoTimeZonePrivate() + = default; + +QString QChronoTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch) const +{ + if (auto info = infoAtEpochMillis(m_timeZone, atMSecsSinceEpoch)) + return fromSysInfo(*info, atMSecsSinceEpoch).abbreviation; + return {}; +} + +int QChronoTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch) const +{ + if (auto info = infoAtEpochMillis(m_timeZone, atMSecsSinceEpoch)) + return fromSysInfo(*info, atMSecsSinceEpoch).offsetFromUtc; + return invalidSeconds(); +} + +int QChronoTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch) const +{ + // Subtracting minutes from seconds will convert the minutes to seconds. + if (auto info = infoAtEpochMillis(m_timeZone, atMSecsSinceEpoch)) + return int((info->offset - info->save).count()); + return invalidSeconds(); +} + +int QChronoTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch) const +{ + if (auto info = infoAtEpochMillis(m_timeZone, atMSecsSinceEpoch)) + return int(std::chrono::seconds(info->save).count()); + return invalidSeconds(); +} + +bool QChronoTimeZonePrivate::hasDaylightTime() const +{ + Data data = QTimeZonePrivate::data(QTimeZone::DaylightTime); + return data.daylightTimeOffset != 0 + && data.daylightTimeOffset != invalidSeconds(); +} + +bool QChronoTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch) const +{ + if (auto info = infoAtEpochMillis(m_timeZone, atMSecsSinceEpoch)) + return info->save != 0min; + return false; +} + +QChronoTimeZonePrivate::Data +QChronoTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const +{ + if (auto info = infoAtEpochMillis(m_timeZone, forMSecsSinceEpoch)) + return fromSysInfo(*info, forMSecsSinceEpoch); + return {}; +} + +bool QChronoTimeZonePrivate::hasTransitions() const +{ + return true; +} + +QChronoTimeZonePrivate::Data +QChronoTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch) const +{ + if (const auto info = infoAtEpochMillis(m_timeZone, afterMSecsSinceEpoch)) { + const auto tran = info->end; + qint64 when = milliseconds(tran.time_since_epoch()).count(); + if (when > afterMSecsSinceEpoch) { + return fromSysInfo(*info, afterMSecsSinceEpoch); + } // else we were already at (or after) the end-of-time "transition" + } + return {}; +} + +QChronoTimeZonePrivate::Data +QChronoTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const +{ + if (const auto info = infoAtEpochMillis(m_timeZone, beforeMSecsSinceEpoch - 1)) { + qint64 when = milliseconds(info->begin.time_since_epoch()).count(); + if (when < beforeMSecsSinceEpoch) { + return fromSysInfo(*info, beforeMSecsSinceEpoch); + } // else we were already at (or before) the start-of-time "transition" + } + return {}; +} + +QT_END_NAMESPACE diff --git a/src/corelib/time/qtimezoneprivate_p.h b/src/corelib/time/qtimezoneprivate_p.h index 37e54bf2401..27bbe5b6222 100644 --- a/src/corelib/time/qtimezoneprivate_p.h +++ b/src/corelib/time/qtimezoneprivate_p.h @@ -22,6 +22,10 @@ #include "private/qlocale_p.h" #include "private/qdatetime_p.h" +#if QT_CONFIG(timezone_tzdb) +#include +#endif + #if QT_CONFIG(icu) #include #endif @@ -229,7 +233,33 @@ private: }; // Platform backend cascade: match newBackendTimeZone() in qtimezone.cpp -#ifdef Q_OS_DARWIN +#if QT_CONFIG(timezone_tzdb) +class QChronoTimeZonePrivate final : public QTimeZonePrivate +{ +public: + QChronoTimeZonePrivate(); + QChronoTimeZonePrivate(QByteArrayView id); + ~QChronoTimeZonePrivate() override; + + QString abbreviation(qint64 atMSecsSinceEpoch) const override; + int offsetFromUtc(qint64 atMSecsSinceEpoch) const override; + int standardTimeOffset(qint64 atMSecsSinceEpoch) const override; + int daylightTimeOffset(qint64 atMSecsSinceEpoch) const override; + + bool hasDaylightTime() const override; + bool isDaylightTime(qint64 atMSecsSinceEpoch) const override; + + Data data(qint64 forMSecsSinceEpoch) const override; + + bool hasTransitions() const override; + Data nextTransition(qint64 afterMSecsSinceEpoch) const override; + Data previousTransition(qint64 beforeMSecsSinceEpoch) const override; + +private: + const std::chrono::time_zone *const m_timeZone; + Q_DISABLE_COPY_MOVE(QChronoTimeZonePrivate) +}; +#elif defined(Q_OS_DARWIN) class Q_AUTOTEST_EXPORT QMacTimeZonePrivate final : public QTimeZonePrivate { public: @@ -495,7 +525,7 @@ private: QString m_daylightName; QList m_tranRules; }; -#endif // Darwin, Android, Unix, ICU, Win. +#endif // C++20, Darwin, Android, Unix, ICU, Win. QT_END_NAMESPACE diff --git a/tests/auto/corelib/time/qdate/tst_qdate.cpp b/tests/auto/corelib/time/qdate/tst_qdate.cpp index 954724ba3f4..e34a6c41857 100644 --- a/tests/auto/corelib/time/qdate/tst_qdate.cpp +++ b/tests/auto/corelib/time/qdate/tst_qdate.cpp @@ -19,8 +19,19 @@ using namespace QtPrivate::DateTimeConstants; using namespace Qt::StringLiterals; -#if defined(Q_OS_WIN) && !QT_CONFIG(icu) -# define USING_WIN_TZ +#undef USING_MS_TZDB +#undef USING_WIN_TZ +#ifdef Q_OS_WIN +# if QT_CONFIG(timezone_tzdb) +# define USING_MS_TZDB +# elif !QT_CONFIG(icu) +# define USING_WIN_TZ +# endif +#endif + +#undef GLIBC_TZDB_MISPARSE +#if QT_CONFIG(timezone_tzdb) && defined(__GLIBCXX__) // && _GLIBCXX_RELEASE <= 14 +# define GLIBC_TZDB_MISPARSE // QTBUG-127598 #endif class tst_QDate : public QObject @@ -509,6 +520,7 @@ void tst_QDate::weekNumber_invalid() enum BackendKludge { IgnoreStart = 1, IgnoreEnd = 2, }; Q_DECLARE_FLAGS(BackendKludges, BackendKludge) Q_DECLARE_OPERATORS_FOR_FLAGS(BackendKludges) +#undef KLUDGING void tst_QDate::startOfDay_endOfDay_data() { @@ -524,13 +536,28 @@ void tst_QDate::startOfDay_endOfDay_data() const QTime early(0, 0), late(23, 59, 59, 999), invalid(QDateTime().time()); constexpr BackendKludges Clean = {}; constexpr BackendKludges IgnoreBoth = IgnoreStart | IgnoreEnd; + // Use IgnoreBoth directly for the one transition Android lacks; the other + // two that need kludges also fail this one. +#ifdef Q_OS_ANDROID +#define KLUDGING +#endif #ifdef USING_WIN_TZ constexpr BackendKludges MsNoStart = IgnoreStart; constexpr BackendKludges MsNoBoth = IgnoreBoth; +#define KLUDGING #else constexpr BackendKludges MsNoStart = Clean; constexpr BackendKludges MsNoBoth = Clean; - // And use IgnoreBoth directly for the one transition Android lacks. +#endif +#if QT_CONFIG(timezone) && QT_CONFIG(timezone_tzdb) && defined(__GLIBCXX__) + // The IANA-DB parser in libstdc++ (at least up to _GLIBCXX_RELEASE == 14) gets + // a lot of zone-transitions wrong in C++20's tzdb :-( + constexpr BackendKludges GlibCxxNoStart = IgnoreStart; + constexpr BackendKludges GlibCxxNoBoth = IgnoreBoth; +#define KLUDGING +#else + constexpr BackendKludges GlibCxxNoStart = Clean; + constexpr BackendKludges GlibCxxNoBoth = Clean; #endif const QTimeZone UTC(QTimeZone::UTC); @@ -557,7 +584,8 @@ void tst_QDate::startOfDay_endOfDay_data() 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 }, + { "BajaMexico", "America/Hermosillo", QDate(1970, 1, 1), QTime(1, 0), late, + MsNoStart | GlibCxxNoStart }, // Compare tst_QDateTime::fromStringDateFormat(ISO 24:00 in DST). { "Brazil", "America/Sao_Paulo", QDate(2008, 10, 19), QTime(1, 0), late, Clean }, @@ -569,7 +597,8 @@ void tst_QDate::startOfDay_endOfDay_data() // 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 }, + { "Samoa", "Pacific/Apia", QDate(2011, 12, 30), invalid, invalid, + MsNoBoth | GlibCxxNoBoth }, // TODO: find other zones with transitions at/crossing midnight. }; @@ -612,7 +641,7 @@ void tst_QDate::startOfDay_endOfDay() 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. +#ifdef KLUDGING // Cope with backend limitations: QFETCH(const BackendKludges, kludge); #define UNLESSKLUDGE(flag) if (!kludge.testFlag(flag)) #else @@ -629,6 +658,9 @@ void tst_QDate::startOfDay_endOfDay() if (start.isValid()) { QVERIFY(front.isValid()); QCOMPARE(front.date(), date); +#ifdef USING_MS_TZDB + QEXPECT_FAIL("Brazil", "MS misreads the IANA DB", Continue); +#endif UNLESSKLUDGE(IgnoreStart) QCOMPARE(front.time(), start); } else UNLESSKLUDGE(IgnoreStart) { auto report = qScopeGuard([front]() { qDebug() << "Start of day:" << front; }); @@ -646,6 +678,7 @@ void tst_QDate::startOfDay_endOfDay() } #undef UNLESSKLUDGE } +#undef KLUDGING void tst_QDate::startOfDay_endOfDay_fixed_data() { @@ -700,7 +733,7 @@ void tst_QDate::startOfDay_endOfDay_fixed() // Minimal testing for LocalTime and TimeZone QCOMPARE(date.startOfDay().date(), date); QCOMPARE(date.endOfDay().date(), date); -#if QT_CONFIG(timezone) +#if QT_CONFIG(timezone) && !defined(GLIBC_TZDB_MISPARSE) const QTimeZone cet("Europe/Oslo"); if (cet.isValid()) { QCOMPARE(date.startOfDay(cet).date(), date); diff --git a/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp b/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp index 3739fad1199..15d7b6de258 100644 --- a/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp +++ b/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp @@ -14,6 +14,22 @@ # define USING_WIN_TZ #endif +#undef USING_MS_TZDB +#undef USING_WIN_TZ +#ifdef Q_OS_WIN +# if QT_CONFIG(timezone_tzdb) +# define USING_MS_TZDB +# elif !QT_CONFIG(icu) +# define USING_WIN_TZ +# endif +#endif + +#undef GLIBC_TZDB_MISPARSE +#if QT_CONFIG(timezone_tzdb) && defined(__GLIBCXX__) // && _GLIBCXX_RELEASE <= 14 +# define GLIBC_TZDB_MISPARSE // QTBUG-127598 +#endif + +#undef INADEQUATE_TZ_DATA #ifdef Q_OS_WIN # include # ifdef USING_WIN_TZ @@ -820,6 +836,10 @@ void tst_QDateTime::setMSecsSinceEpoch() QVERIFY(!cet.isValid()); // overflows } else if (zoneIsCET) { QVERIFY(cet.isValid()); +#ifdef USING_MS_TZDB + QEXPECT_FAIL("old max (Tue Jun 3 21:59:59 5874898)", + "MS doesn't handle the distant future", Continue); +#endif QCOMPARE(dt.toLocalTime(), cet); // Test converting from LocalTime to UTC back to LocalTime. @@ -837,6 +857,10 @@ void tst_QDateTime::setMSecsSinceEpoch() dt2.setTimeZone(europe); #endif dt2.setMSecsSinceEpoch(msecs); +#ifdef GLIBC_TZDB_MISPARSE + QEXPECT_FAIL("old max (Tue Jun 3 21:59:59 5874898)", + "QTBUG-127598 Bad libstdc++ data", Continue); +#endif if (cet.date().year() >= 1970 || cet.date() == utc.date()) QCOMPARE(dt2.date(), cet.date()); @@ -913,11 +937,18 @@ void tst_QDateTime::fromMSecsSinceEpoch() QCOMPARE(dtOffset.time(), utc.time().addMSecs(60*60*1000)); } +#ifdef USING_MS_TZDB + if (zoneIsCET && QTest::currentDataTag() == "old max (Tue Jun 3 21:59:59 5874898)"_ba) { + qInfo("Distant future of CET unhandled"); + } else +#endif if (zoneIsCET) { QCOMPARE(dtLocal.toLocalTime(), cet); QCOMPARE(dtUtc.toLocalTime(), cet); if (msecs != Bound::max()) QCOMPARE(dtOffset.toLocalTime(), cet); + } else { + qInfo("CET-specific test skipped"); } if (!localOverflow) @@ -969,6 +1000,9 @@ void tst_QDateTime::fromSecsSinceEpoch() #endif const qint64 last = maxSeconds - qMax(lateZone, 0); +#ifdef GLIBC_TZDB_MISPARSE + QEXPECT_FAIL("", "QTBUG-127598 Bad libstdc++ data", Continue); +#endif QVERIFY(QDateTime::fromSecsSinceEpoch(last).isValid()); QVERIFY(!QDateTime::fromSecsSinceEpoch(last + 1).isValid()); const qint64 first = -maxSeconds - qMin(early.addYears(1).toLocalTime().offsetFromUtc(), 0); @@ -3361,6 +3395,9 @@ void tst_QDateTime::fromStringStringFormat() QDateTime dt = QDateTime::fromString(string, format, baseYear); +#ifdef USING_MS_TZDB + QEXPECT_FAIL("spring-forward-midnight", "MS misreads the IANA DB", Continue); +#endif QCOMPARE(dt, expected); if (expected.isValid()) { QCOMPARE(dt.timeSpec(), expected.timeSpec()); @@ -3661,7 +3698,18 @@ void tst_QDateTime::zoneAtTime() const QTime noon(12, 0); QTimeZone zone(ianaID); +#ifdef USING_MS_TZDB + QEXPECT_FAIL("after:NPT", "MS lacks NPT", Abort); + QEXPECT_FAIL("before:NPT", "MS lacks NPT", Abort); +#endif QVERIFY(zone.isValid()); +#ifdef GLIBC_TZDB_MISPARSE + QEXPECT_FAIL("after:WST", "QTBUG-127598 Bad libstdc++ data", Abort); + QEXPECT_FAIL("before:WST", "QTBUG-127598 Bad libstdc++ data", Abort); +#endif +#ifdef USING_MS_TZDB + QEXPECT_FAIL("after:ACWST", "MS gets ACWST wrong", Abort); +#endif QCOMPARE(QDateTime(date, noon, zone).offsetFromUtc(), offset); QCOMPARE(zone.offsetFromUtc(QDateTime(date, noon, zone)), offset); #else @@ -4401,8 +4449,10 @@ void tst_QDateTime::timeZones() const // 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) { + const QDateTime longYearEarly(QDate(longYear, 3, 24), QTime(12, 0), cet); + const QDateTime longYearLate(QDate(longYear, 3, 31), QTime(12, 0), cet); + if (longYearEarly.isValid() && longYearLate.isValid() + && longYearEarly.msecsTo(longYearLate) < millisInWeek) { inGap = QDateTime(QDate(longYear, 3, 27), QTime(2, 30), cet); QVERIFY(inGap.isValid()); QCOMPARE(inGap.date(), QDate(longYear, 3, 27)); diff --git a/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp b/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp index ecc73823e66..cb3563241f3 100644 --- a/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp +++ b/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp @@ -13,7 +13,7 @@ #include #endif -#if defined(Q_OS_WIN) && !QT_CONFIG(icu) +#if defined(Q_OS_WIN) && !QT_CONFIG(icu) && !QT_CONFIG(timezone_tzdb) # define USING_WIN_TZ #endif @@ -762,6 +762,13 @@ void tst_QTimeZone::hasAlternativeName() void tst_QTimeZone::specificTransition_data() { +#if QT_CONFIG(timezone) && QT_CONFIG(timezone_tzdb) && defined(__GLIBCXX__) + QSKIP("libstdc++'s C++20 misreads the IANA DB for Moscow's transitions (among others)."); +#endif +#if defined Q_OS_ANDROID && !QT_CONFIG(timezone_tzdb) + if (!QTimeZone("Europe/Moscow").hasTransitions()) + QSKIP("Android time-zone back-end has no transition data"); +#endif QTest::addColumn("zone"); QTest::addColumn("start"); QTest::addColumn("stop"); @@ -771,10 +778,6 @@ void tst_QTimeZone::specificTransition_data() QTest::addColumn("offset"); QTest::addColumn("stdoff"); QTest::addColumn("dstoff"); -#ifdef Q_OS_ANDROID - if (!QTimeZone("Europe/Moscow").hasTransitions()) - QSKIP("Android time-zone back-end has no transition data"); -#endif // Moscow ditched DST on 2010-10-31 but has since changed standard offset twice. #ifdef USING_WIN_TZ @@ -1128,7 +1131,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); -#if defined(Q_OS_ANDROID) || QT_CONFIG(icu) +#if (defined(Q_OS_ANDROID) || QT_CONFIG(icu)) && !QT_CONFIG(timezone_tzdb) TESTSET("maximal", "East-Saskatchewan", true); // Android actually uses this TESTSET("too long", "North-Saskatchewan", false); // ... but thankfully not this. #else @@ -1199,7 +1202,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; -#if defined(Q_OS_ANDROID) || QT_CONFIG(icu) +#if (defined(Q_OS_ANDROID) || QT_CONFIG(icu)) && !QT_CONFIG(timezone_tzdb) QTest::newRow("long alone") << QByteArray("12345678901234567") << true; QTest::newRow("over-long alone") << QByteArray("123456789012345678") << false; #else @@ -1340,7 +1343,7 @@ void tst_QTimeZone::utcTest() void tst_QTimeZone::icuTest() { -#if defined(QT_BUILD_INTERNAL) && QT_CONFIG(icu) && !defined(Q_OS_UNIX) +#if defined(QT_BUILD_INTERNAL) && QT_CONFIG(icu) && !QT_CONFIG(timezone_tzdb) && !defined(Q_OS_UNIX) // Known datetimes 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(); @@ -1383,12 +1386,13 @@ void tst_QTimeZone::icuTest() if (QTest::currentTestFailed()) return; testEpochTranPrivate(QIcuTimeZonePrivate("America/Toronto")); -#endif // ICU not on Unix +#endif // ICU not on Unix, without tzdb } void tst_QTimeZone::tzTest() { -#if defined QT_BUILD_INTERNAL && defined Q_OS_UNIX && !defined Q_OS_DARWIN && !defined Q_OS_ANDROID +#if defined QT_BUILD_INTERNAL && defined Q_OS_UNIX \ + && !QT_CONFIG(timezone_tzdb) && !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), UTC).toMSecsSinceEpoch(); @@ -1587,12 +1591,12 @@ void tst_QTimeZone::tzTest() 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 && !Q_OS_ANDROID +#endif // QT_BUILD_INTERNAL && Q_OS_UNIX && !timezone_tzdb && !Q_OS_DARWIN && !Q_OS_ANDROID } void tst_QTimeZone::macTest() { -#if defined(QT_BUILD_INTERNAL) && defined(Q_OS_DARWIN) +#if defined(QT_BUILD_INTERNAL) && defined(Q_OS_DARWIN) && !QT_CONFIG(timezone_tzdb) // Known datetimes 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(); @@ -1633,7 +1637,7 @@ void tst_QTimeZone::macTest() if (QTest::currentTestFailed()) return; testEpochTranPrivate(QMacTimeZonePrivate("America/Toronto")); -#endif // QT_BUILD_INTERNAL && Q_OS_DARWIN +#endif // QT_BUILD_INTERNAL && Q_OS_DARWIN without tzdb } void tst_QTimeZone::darwinTypes()