QTimeZone: Add back-end based on std::chrono::tzdb
Implement QTimeZone back-end based on C++20 std::chrono::tzdb; requires GCC libstdc++ 14, Clang libc++ 19 or MSVC STL 19.29. Fixes: QTBUG-68812 Change-Id: I6581b3fe908c2318befa8f421c4ca8bf7c51a760 Reviewed-by: Thiago Macieira <thiago.macieira@intel.com> Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
This commit is contained in:
parent
d7af695feb
commit
755733f591
@ -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
|
||||
)
|
||||
|
@ -495,6 +495,28 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
")
|
||||
|
||||
# <chrono>
|
||||
qt_config_compile_test(chrono_tzdb
|
||||
LABEL "Support for timezones in C++20 <chrono>"
|
||||
CODE
|
||||
"#include <chrono>
|
||||
#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")
|
||||
|
@ -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);
|
||||
|
146
src/corelib/time/qtimezoneprivate_chrono.cpp
Normal file
146
src/corelib/time/qtimezoneprivate_chrono.cpp
Normal file
@ -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 <chrono>
|
||||
|
||||
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<std::chrono::milliseconds>
|
||||
chronoForEpochMillis(qint64 millis)
|
||||
{
|
||||
return sys_time<milliseconds>(milliseconds(millis));
|
||||
}
|
||||
|
||||
static std::optional<std::chrono::sys_info>
|
||||
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
|
@ -22,6 +22,10 @@
|
||||
#include "private/qlocale_p.h"
|
||||
#include "private/qdatetime_p.h"
|
||||
|
||||
#if QT_CONFIG(timezone_tzdb)
|
||||
#include <chrono>
|
||||
#endif
|
||||
|
||||
#if QT_CONFIG(icu)
|
||||
#include <unicode/ucal.h>
|
||||
#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<QWinTransitionRule> m_tranRules;
|
||||
};
|
||||
#endif // Darwin, Android, Unix, ICU, Win.
|
||||
#endif // C++20, Darwin, Android, Unix, ICU, Win.
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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 <qt_windows.h>
|
||||
# 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));
|
||||
|
@ -13,7 +13,7 @@
|
||||
#include <QOperatingSystemVersion>
|
||||
#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<QByteArray>("zone");
|
||||
QTest::addColumn<QDate>("start");
|
||||
QTest::addColumn<QDate>("stop");
|
||||
@ -771,10 +778,6 @@ void tst_QTimeZone::specificTransition_data()
|
||||
QTest::addColumn<int>("offset");
|
||||
QTest::addColumn<int>("stdoff");
|
||||
QTest::addColumn<int>("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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user