Skip to content

Commit

Permalink
QTimeZone: Add back-end based on std::chrono::tzdb
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Magdalena Stojek committed Oct 3, 2024
1 parent d7af695 commit 755733f
Show file tree
Hide file tree
Showing 8 changed files with 348 additions and 32 deletions.
31 changes: 25 additions & 6 deletions src/corelib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand All @@ -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
)
Expand Down
30 changes: 30 additions & 0 deletions src/corelib/configure.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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")
Expand Down
8 changes: 6 additions & 2 deletions src/corelib/time/qtimezone.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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);
Expand Down
146 changes: 146 additions & 0 deletions src/corelib/time/qtimezoneprivate_chrono.cpp
Original file line number Diff line number Diff line change
@@ -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
34 changes: 32 additions & 2 deletions src/corelib/time/qtimezoneprivate_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -229,7 +233,33 @@ class Q_AUTOTEST_EXPORT QUtcTimeZonePrivate final : public QTimeZonePrivate
};

// 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:
Expand Down Expand Up @@ -495,7 +525,7 @@ class Q_AUTOTEST_EXPORT QWinTimeZonePrivate final : public QTimeZonePrivate
QString m_daylightName;
QList<QWinTransitionRule> m_tranRules;
};
#endif // Darwin, Android, Unix, ICU, Win.
#endif // C++20, Darwin, Android, Unix, ICU, Win.

QT_END_NAMESPACE

Expand Down
Loading

0 comments on commit 755733f

Please sign in to comment.