Adapt QTimeZone to handle Qt::TimeSpec machinery

[ChangeLog][QtCore][QTimeZone] QTimeZone is now always defined;
feature timezone now controls most of its prior API and some new API
is added, most of it always present, to enable QTimeZone to package a
Qt::TimeSpec and, for Qt::OffsetFromUTC, its offset. Prior to this
change, APIs using Qt::TimeSpec had to provide a separate function
taking a QTimeZone alongside a function taking a Qt::TimeSpec and
optional offset; it will now be possible to unify these into a single
function taking a QTimeZone. Adaptation of other Qt classes to do so
shall follow.

Task-number: QTBUG-108199
Change-Id: If5ec3cc63920af882ebb333bf69cde266b1f6ad7
Reviewed-by: Ivan Solovev <ivan.solovev@qt.io>
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
Edward Welbourne 2022-11-10 18:52:01 +01:00
parent ab15c02991
commit ae6186c7e8
9 changed files with 997 additions and 123 deletions

View File

@ -256,12 +256,13 @@ qt_internal_add_module(Core
time/qcalendarbackend_p.h time/qcalendarbackend_p.h
time/qcalendarmath_p.h time/qcalendarmath_p.h
time/qdatetime.cpp time/qdatetime.h time/qdatetime_p.h time/qdatetime.cpp time/qdatetime.h time/qdatetime_p.h
time/qlocaltime.cpp time/qlocaltime_p.h
time/qgregoriancalendar.cpp time/qgregoriancalendar_p.h time/qgregoriancalendar.cpp time/qgregoriancalendar_p.h
time/qjuliancalendar.cpp time/qjuliancalendar_p.h time/qjuliancalendar.cpp time/qjuliancalendar_p.h
time/qlocaltime.cpp time/qlocaltime_p.h
time/qmilankoviccalendar.cpp time/qmilankoviccalendar_p.h time/qmilankoviccalendar.cpp time/qmilankoviccalendar_p.h
time/qromancalendar.cpp time/qromancalendar_p.h time/qromancalendar.cpp time/qromancalendar_p.h
time/qromancalendar_data_p.h time/qromancalendar_data_p.h
time/qtimezone.cpp time/qtimezone.h
tools/qalgorithms.h tools/qalgorithms.h
tools/qarraydata.cpp tools/qarraydata.h tools/qarraydata.cpp tools/qarraydata.h
tools/qarraydataops.h tools/qarraydataops.h
@ -872,7 +873,6 @@ qt_internal_extend_target(Core CONDITION QT_FEATURE_jalalicalendar
qt_internal_extend_target(Core CONDITION QT_FEATURE_timezone qt_internal_extend_target(Core CONDITION QT_FEATURE_timezone
SOURCES SOURCES
time/qtimezone.cpp time/qtimezone.h
time/qtimezoneprivate.cpp time/qtimezoneprivate_p.h time/qtimezoneprivate.cpp time/qtimezoneprivate_p.h
time/qtimezoneprivate_data_p.h time/qtimezoneprivate_data_p.h
) )

View File

@ -728,15 +728,25 @@
Mean Time has zero offset from it. Neither UTC nor OffsetFromUTC Mean Time has zero offset from it. Neither UTC nor OffsetFromUTC
has any transitions. has any transitions.
When specifying a datetime using OffsetFromUTC, the offset from UTC must
also be supplied (it is measured in seconds). To specify a datetime using
TimeZone, a QTimeZone must be supplied. From Qt 6.5, a QTimeZone can now
package a timespec with, where needed, an offset as a lightweight time
description, so that passing a QTimeZone now provides a uniform way to use
datetime APIs, saving the need to call them differently for different
timespecs.
\note After a change to the system time-zone setting, the behavior \note After a change to the system time-zone setting, the behavior
of LocalTime-based QDateTime objects created before the change is of LocalTime-based QDateTime objects created before the change is
undefined: QDateTime may have cached data that the change undefined: QDateTime may have cached data that the change
invalidates. (This is not triggered by transitions of the system invalidates. (This is not triggered by \e transitions of the system
time-zone.) In long-running processes, updates to the system's time-zone.) In long-running processes, updates to the system's
time-zone data (e.g. when politicians change the rules for a zone) time-zone data (e.g. when politicians change the rules for a zone)
may likewise lead to conflicts between the updated time-zone may likewise lead to conflicts between the updated time-zone
information and data cached by QDateTime objects created before information and data cached by QDateTime objects created before
the update, using either LocalTime or TimeZone. the update, using either LocalTime or TimeZone.
\sa QTimeZone, QDateTime
*/ */
/*! /*!

View File

@ -495,9 +495,9 @@ QTimeZone QTimeZone::fromCFTimeZone(CFTimeZoneRef timeZone)
CFTimeZoneRef QTimeZone::toCFTimeZone() const CFTimeZoneRef QTimeZone::toCFTimeZone() const
{ {
#ifndef QT_NO_DYNAMIC_CAST #ifndef QT_NO_DYNAMIC_CAST
Q_ASSERT(dynamic_cast<const QMacTimeZonePrivate *>(d.data())); Q_ASSERT(dynamic_cast<const QMacTimeZonePrivate *>(d.d));
#endif #endif
const QMacTimeZonePrivate *p = static_cast<const QMacTimeZonePrivate *>(d.data()); const QMacTimeZonePrivate *p = static_cast<const QMacTimeZonePrivate *>(d.d);
return reinterpret_cast<CFTimeZoneRef>([p->nsTimeZone() copy]); return reinterpret_cast<CFTimeZoneRef>([p->nsTimeZone() copy]);
} }

View File

@ -2704,6 +2704,7 @@ QDateTimePrivate::ZoneState QDateTimePrivate::zoneStateAtMillis(const QTimeZone
qint64 millis, DaylightStatus dst) qint64 millis, DaylightStatus dst)
{ {
Q_ASSERT(zone.isValid()); Q_ASSERT(zone.isValid());
Q_ASSERT(zone.timeSpec() == Qt::TimeZone);
// Get the effective data from QTimeZone // Get the effective data from QTimeZone
QTimeZonePrivate::Data data = zone.d->dataForLocalTime(millis, int(dst)); QTimeZonePrivate::Data data = zone.d->dataForLocalTime(millis, int(dst));
if (data.offsetFromUtc == QTimeZonePrivate::invalidSeconds()) if (data.offsetFromUtc == QTimeZonePrivate::invalidSeconds())
@ -3170,6 +3171,7 @@ QDateTime::Data QDateTimePrivate::create(QDate toDate, QTime toTime, Qt::TimeSpe
inline QDateTime::Data QDateTimePrivate::create(QDate toDate, QTime toTime, inline QDateTime::Data QDateTimePrivate::create(QDate toDate, QTime toTime,
const QTimeZone &toTimeZone) const QTimeZone &toTimeZone)
{ {
Q_ASSERT(toTimeZone.timeSpec() == Qt::TimeZone);
QDateTime::Data result(Qt::TimeZone); QDateTime::Data result(Qt::TimeZone);
Q_ASSERT(!result.isShort()); Q_ASSERT(!result.isShort());
@ -3396,7 +3398,10 @@ QDateTime::QDateTime(QDate date, QTime time, Qt::TimeSpec spec, int offsetSecond
*/ */
QDateTime::QDateTime(QDate date, QTime time, const QTimeZone &timeZone) QDateTime::QDateTime(QDate date, QTime time, const QTimeZone &timeZone)
: d(QDateTimePrivate::create(date, time, timeZone)) : d(timeZone.timeSpec() == Qt::TimeZone
? QDateTimePrivate::create(date, time, timeZone)
: QDateTimePrivate::create(date, time, timeZone.timeSpec(),
timeZone.fixedSecondsAheadOfUtc()))
{ {
} }
#endif // timezone #endif // timezone
@ -3757,10 +3762,11 @@ void QDateTime::setOffsetFromUtc(int offsetSeconds)
void QDateTime::setTimeZone(const QTimeZone &toZone) void QDateTime::setTimeZone(const QTimeZone &toZone)
{ {
d.detach(); // always detach d.detach(); // always detach
d->m_status = mergeSpec(d->m_status, Qt::TimeZone); d->m_status = mergeSpec(d->m_status, toZone.timeSpec());
d->m_offsetFromUtc = 0; d->m_offsetFromUtc = toZone.fixedSecondsAheadOfUtc();
d->m_timeZone = toZone; if (toZone.timeSpec() == Qt::TimeZone)
refreshZonedDateTime(d, Qt::TimeZone); d->m_timeZone = toZone;
checkValidDateTime(d);
} }
#endif // timezone #endif // timezone
@ -4527,6 +4533,16 @@ QDateTime QDateTime::toUTC() const
QDateTime QDateTime::toTimeZone(const QTimeZone &timeZone) const QDateTime QDateTime::toTimeZone(const QTimeZone &timeZone) const
{ {
switch (timeZone.timeSpec()) {
case Qt::OffsetFromUTC:
return toOffsetFromUtc(timeZone.fixedSecondsAheadOfUtc());
case Qt::LocalTime:
return toLocalTime();
case Qt::UTC:
return toUTC();
case Qt::TimeZone:
break;
}
if (getSpec(d) == Qt::TimeZone && d->m_timeZone == timeZone) if (getSpec(d) == Qt::TimeZone && d->m_timeZone == timeZone)
return *this; return *this;
@ -4939,6 +4955,9 @@ QDateTime QDateTime::fromSecsSinceEpoch(qint64 secs, Qt::TimeSpec spec, int offs
*/ */
QDateTime QDateTime::fromMSecsSinceEpoch(qint64 msecs, const QTimeZone &timeZone) QDateTime QDateTime::fromMSecsSinceEpoch(qint64 msecs, const QTimeZone &timeZone)
{ {
if (timeZone.timeSpec() != Qt::TimeZone)
return fromMSecsSinceEpoch(msecs, timeZone.timeSpec(), timeZone.fixedSecondsAheadOfUtc());
QDateTime dt; QDateTime dt;
dt.setTimeZone(timeZone); dt.setTimeZone(timeZone);
if (timeZone.isValid()) if (timeZone.isValid())
@ -4962,6 +4981,9 @@ QDateTime QDateTime::fromMSecsSinceEpoch(qint64 msecs, const QTimeZone &timeZone
*/ */
QDateTime QDateTime::fromSecsSinceEpoch(qint64 secs, const QTimeZone &timeZone) QDateTime QDateTime::fromSecsSinceEpoch(qint64 secs, const QTimeZone &timeZone)
{ {
if (timeZone.timeSpec() != Qt::TimeZone)
return fromSecsSinceEpoch(secs, timeZone.timeSpec(), timeZone.fixedSecondsAheadOfUtc());
QDateTime dt; QDateTime dt;
dt.setTimeZone(timeZone); dt.setTimeZone(timeZone);
if (timeZone.isValid()) if (timeZone.isValid())

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +1,17 @@
// Copyright (C) 2013 John Layt <jlayt@kde.org> // Copyright (C) 2013 John Layt <jlayt@kde.org>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef QTIMEZONE_H #ifndef QTIMEZONE_H
#define QTIMEZONE_H #define QTIMEZONE_H
#include <QtCore/qshareddata.h>
#include <QtCore/qlocale.h>
#include <QtCore/qdatetime.h> #include <QtCore/qdatetime.h>
#include <QtCore/qlocale.h>
#include <QtCore/qshareddata.h>
#include <QtCore/qswap.h>
#include <QtCore/qtclasshelpermacros.h>
#include <chrono> #include <chrono>
QT_REQUIRE_CONFIG(timezone);
#if (defined(Q_OS_DARWIN) || defined(Q_QDOC)) && !defined(QT_NO_SYSTEMLOCALE) #if (defined(Q_OS_DARWIN) || defined(Q_QDOC)) && !defined(QT_NO_SYSTEMLOCALE)
Q_FORWARD_DECLARE_CF_TYPE(CFTimeZone); Q_FORWARD_DECLARE_CF_TYPE(CFTimeZone);
Q_FORWARD_DECLARE_OBJC_CLASS(NSTimeZone); Q_FORWARD_DECLARE_OBJC_CLASS(NSTimeZone);
@ -24,6 +23,64 @@ class QTimeZonePrivate;
class Q_CORE_EXPORT QTimeZone class Q_CORE_EXPORT QTimeZone
{ {
struct ShortData
{
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
quintptr mode : 2;
#endif
qintptr offset : sizeof(void *) * 8 - 2;
#if Q_BYTE_ORDER == Q_BIG_ENDIAN
quintptr mode : 2;
#endif
// mode is a cycled Qt::TimeSpec, (int(spec) + 1) % 4, so that zero
// (lowest bits of a pointer) matches spec being Qt::TimeZone, for which
// Data holds a QTZP pointer instead of ShortData.
// Passing Qt::TimeZone gets the equivalent of a null QTZP; it is not short.
constexpr ShortData(Qt::TimeSpec spec, int secondsAhead = 0)
#if Q_BYTE_ORDER == Q_BIG_ENDIAN
: offset(spec == Qt::OffsetFromUTC ? secondsAhead : 0),
mode((int(spec) + 1) & 3)
#else
: mode((int(spec) + 1) & 3),
offset(spec == Qt::OffsetFromUTC ? secondsAhead : 0)
#endif
{
}
friend constexpr bool operator==(const ShortData &lhs, const ShortData &rhs)
{ return lhs.mode == rhs.mode && lhs.offset == rhs.offset; }
constexpr Qt::TimeSpec spec() const { return Qt::TimeSpec((mode + 3) & 3); }
};
union Data
{
Data() noexcept;
Data(ShortData &&sd) : s(std::move(sd)) {}
Data(const Data &other) noexcept;
Data(Data &&other) noexcept;
Data &operator=(const Data &other) noexcept;
Data &operator=(Data &&other) noexcept { swap(other); return *this; }
~Data();
void swap(Data &other) noexcept { qt_ptr_swap(d, other.d); }
// isShort() is equivalent to s.spec() != Qt::TimeZone
bool isShort() const { return s.mode; } // a.k.a. quintptr(d) & 3
// Typse must support: out << wrap("C-strings");
template <typename Stream, typename Wrap>
void serialize(Stream &out, const Wrap &wrap) const;
Data(QTimeZonePrivate *dptr) noexcept;
Data &operator=(QTimeZonePrivate *dptr) noexcept;
const QTimeZonePrivate *operator->() const { Q_ASSERT(!isShort()); return d; }
QTimeZonePrivate *operator->() { Q_ASSERT(!isShort()); return d; }
QTimeZonePrivate *d = nullptr;
ShortData s;
};
QTimeZone(ShortData &&sd) : d(std::move(sd)) {}
public: public:
// Sane UTC offsets range from -14 to +14 hours: // Sane UTC offsets range from -14 to +14 hours:
enum { enum {
@ -33,13 +90,20 @@ public:
MaxUtcOffsetSecs = +14 * 3600 MaxUtcOffsetSecs = +14 * 3600
}; };
QTimeZone() noexcept; enum Initialization { LocalTime, UTC };
explicit QTimeZone(int offsetSeconds);
QTimeZone() noexcept;
Q_IMPLICIT QTimeZone(Initialization spec) noexcept
: d(ShortData(spec == UTC ? Qt::UTC : Qt::LocalTime)) {}
#if QT_CONFIG(timezone)
explicit QTimeZone(int offsetSeconds);
explicit QTimeZone(const QByteArray &ianaId); explicit QTimeZone(const QByteArray &ianaId);
QTimeZone(const QByteArray &zoneId, int offsetSeconds, const QString &name, QTimeZone(const QByteArray &zoneId, int offsetSeconds, const QString &name,
const QString &abbreviation, QLocale::Territory territory = QLocale::AnyTerritory, const QString &abbreviation, QLocale::Territory territory = QLocale::AnyTerritory,
const QString &comment = QString()); const QString &comment = QString());
#endif // timezone backends
QTimeZone(const QTimeZone &other) noexcept; QTimeZone(const QTimeZone &other) noexcept;
QTimeZone(QTimeZone &&other) noexcept; QTimeZone(QTimeZone &&other) noexcept;
~QTimeZone(); ~QTimeZone();
@ -55,6 +119,27 @@ public:
bool isValid() const; bool isValid() const;
static QTimeZone fromDurationAheadOfUtc(std::chrono::seconds offset)
{
return fromSecondsAheadOfUtc(int(offset.count()));
}
static QTimeZone fromSecondsAheadOfUtc(int offset)
{
return QTimeZone((offset >= MinUtcOffsetSecs && offset <= MaxUtcOffsetSecs)
? ShortData(offset ? Qt::OffsetFromUTC : Qt::UTC, offset)
: ShortData(Qt::TimeZone));
}
constexpr Qt::TimeSpec timeSpec() const noexcept { return d.s.spec(); }
constexpr int fixedSecondsAheadOfUtc() const noexcept
{ return timeSpec() == Qt::OffsetFromUTC ? int(d.s.offset) : 0; }
static constexpr bool isUtcOrFixedOffset(Qt::TimeSpec spec) noexcept
{ return spec == Qt::UTC || spec == Qt::OffsetFromUTC; }
constexpr bool isUtcOrFixedOffset() const noexcept { return isUtcOrFixedOffset(timeSpec()); }
#if QT_CONFIG(timezone)
QTimeZone asBackendZone() const;
enum TimeType { enum TimeType {
StandardTime = 0, StandardTime = 0,
DaylightTime = 1, DaylightTime = 1,
@ -79,10 +164,10 @@ public:
QByteArray id() const; QByteArray id() const;
QLocale::Territory territory() const; QLocale::Territory territory() const;
#if QT_DEPRECATED_SINCE(6, 6) # if QT_DEPRECATED_SINCE(6, 6)
QT_DEPRECATED_VERSION_X_6_6("Use territory() instead") QT_DEPRECATED_VERSION_X_6_6("Use territory() instead")
QLocale::Country country() const; QLocale::Country country() const;
#endif # endif
QString comment() const; QString comment() const;
QString displayName(const QDateTime &atDateTime, QString displayName(const QDateTime &atDateTime,
@ -125,14 +210,14 @@ public:
static QList<QByteArray> windowsIdToIanaIds(const QByteArray &windowsId, static QList<QByteArray> windowsIdToIanaIds(const QByteArray &windowsId,
QLocale::Territory territory); QLocale::Territory territory);
#if (defined(Q_OS_DARWIN) || defined(Q_QDOC)) && !defined(QT_NO_SYSTEMLOCALE) # if (defined(Q_OS_DARWIN) || defined(Q_QDOC)) && !defined(QT_NO_SYSTEMLOCALE)
static QTimeZone fromCFTimeZone(CFTimeZoneRef timeZone); static QTimeZone fromCFTimeZone(CFTimeZoneRef timeZone);
CFTimeZoneRef toCFTimeZone() const Q_DECL_CF_RETURNS_RETAINED; CFTimeZoneRef toCFTimeZone() const Q_DECL_CF_RETURNS_RETAINED;
static QTimeZone fromNSTimeZone(const NSTimeZone *timeZone); static QTimeZone fromNSTimeZone(const NSTimeZone *timeZone);
NSTimeZone *toNSTimeZone() const Q_DECL_NS_RETURNS_AUTORELEASED; NSTimeZone *toNSTimeZone() const Q_DECL_NS_RETURNS_AUTORELEASED;
#endif # endif
#if __cpp_lib_chrono >= 201907L || defined(Q_QDOC) # if __cpp_lib_chrono >= 201907L || defined(Q_QDOC)
QT_POST_CXX17_API_IN_EXPORTED_CLASS QT_POST_CXX17_API_IN_EXPORTED_CLASS
static QTimeZone fromStdTimeZonePtr(const std::chrono::time_zone *timeZone) static QTimeZone fromStdTimeZonePtr(const std::chrono::time_zone *timeZone)
{ {
@ -141,20 +226,25 @@ public:
const std::string_view timeZoneName = timeZone->name(); const std::string_view timeZoneName = timeZone->name();
return QTimeZone(QByteArrayView(timeZoneName).toByteArray()); return QTimeZone(QByteArrayView(timeZoneName).toByteArray());
} }
#endif # endif
#endif // feature timezone
private: private:
#ifndef QT_NO_DATASTREAM #ifndef QT_NO_DATASTREAM
friend Q_CORE_EXPORT QDataStream &operator<<(QDataStream &ds, const QTimeZone &tz); friend Q_CORE_EXPORT QDataStream &operator<<(QDataStream &ds, const QTimeZone &tz);
#endif
#ifndef QT_NO_DEBUG_STREAM
friend Q_CORE_EXPORT QDebug operator<<(QDebug dbg, const QTimeZone &tz);
#endif #endif
QTimeZone(QTimeZonePrivate &dd); QTimeZone(QTimeZonePrivate &dd);
friend class QTimeZonePrivate; friend class QTimeZonePrivate;
friend class QDateTime; friend class QDateTime;
friend class QDateTimePrivate; friend class QDateTimePrivate;
QSharedDataPointer<QTimeZonePrivate> d; Data d;
}; };
#if QT_CONFIG(timezone)
Q_DECLARE_TYPEINFO(QTimeZone::OffsetData, Q_RELOCATABLE_TYPE); Q_DECLARE_TYPEINFO(QTimeZone::OffsetData, Q_RELOCATABLE_TYPE);
#endif
Q_DECLARE_SHARED(QTimeZone) Q_DECLARE_SHARED(QTimeZone)
#ifndef QT_NO_DATASTREAM #ifndef QT_NO_DATASTREAM

View File

@ -82,6 +82,7 @@ qt_internal_extend_target(Bootstrap
../../corelib/time/qgregoriancalendar.cpp ../../corelib/time/qgregoriancalendar.cpp
../../corelib/time/qlocaltime.cpp ../../corelib/time/qlocaltime.cpp
../../corelib/time/qromancalendar.cpp ../../corelib/time/qromancalendar.cpp
../../corelib/time/qtimezone.cpp
../../corelib/tools/qarraydata.cpp ../../corelib/tools/qarraydata.cpp
../../corelib/tools/qbitarray.cpp ../../corelib/tools/qbitarray.cpp
../../corelib/tools/qcommandlineoption.cpp ../../corelib/tools/qcommandlineoption.cpp

View File

@ -8,6 +8,4 @@ add_subdirectory(qdate)
add_subdirectory(qdatetime) add_subdirectory(qdatetime)
add_subdirectory(qdatetimeparser) add_subdirectory(qdatetimeparser)
add_subdirectory(qtime) add_subdirectory(qtime)
if(QT_FEATURE_timezone AND NOT INTEGRITY) add_subdirectory(qtimezone)
add_subdirectory(qtimezone)
endif()

View File

@ -22,8 +22,14 @@ private Q_SLOTS:
// Public class default system tests // Public class default system tests
void createTest(); void createTest();
void nullTest(); void nullTest();
void systemZone(); void assign();
void compare();
void timespec();
void offset();
void dataStreamTest(); void dataStreamTest();
#if QT_CONFIG(timezone)
void asBackendZone();
void systemZone();
void isTimeZoneIdAvailable(); void isTimeZoneIdAvailable();
void availableTimeZoneIds(); void availableTimeZoneIds();
void utcOffsetId_data(); void utcOffsetId_data();
@ -51,16 +57,17 @@ private Q_SLOTS:
void localeSpecificDisplayName(); void localeSpecificDisplayName();
void stdCompatibility_data(); void stdCompatibility_data();
void stdCompatibility(); void stdCompatibility();
#endif // timezone backends
private: private:
void printTimeZone(const QTimeZone &tz); 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: // Generic tests of privates, called by implementation-specific private tests:
void testCetPrivate(const QTimeZonePrivate &tzp); void testCetPrivate(const QTimeZonePrivate &tzp);
void testEpochTranPrivate(const QTimeZonePrivate &tzp); void testEpochTranPrivate(const QTimeZonePrivate &tzp);
#endif // QT_BUILD_INTERNAL #endif // QT_BUILD_INTERNAL && timezone backends
// Set to true to print debug output, test Display Names and run long stress tests // Set to true to print debug output, test Display Names and run long stress tests
const bool debug = false; static constexpr bool debug = false;
}; };
void tst_QTimeZone::printTimeZone(const QTimeZone &tz) void tst_QTimeZone::printTimeZone(const QTimeZone &tz)
@ -292,33 +299,138 @@ void tst_QTimeZone::nullTest()
QCOMPARE(data.daylightTimeOffset, invalidOffset); QCOMPARE(data.daylightTimeOffset, invalidOffset);
} }
void tst_QTimeZone::systemZone() void tst_QTimeZone::assign()
{ {
const QTimeZone zone = QTimeZone::systemTimeZone(); QTimeZone assignee;
QVERIFY(zone.isValid()); QCOMPARE(assignee.timeSpec(), Qt::TimeZone);
QCOMPARE(zone.id(), QTimeZone::systemTimeZoneId()); assignee = QTimeZone();
QCOMPARE(zone, QTimeZone(QTimeZone::systemTimeZoneId())); QCOMPARE(assignee.timeSpec(), Qt::TimeZone);
// Check it behaves the same as local-time: assignee = QTimeZone::UTC;
const QDate dates[] = { QCOMPARE(assignee.timeSpec(), Qt::UTC);
QDate::fromJulianDay(0), // far in the distant past (LMT) assignee = QTimeZone::LocalTime;
QDate(1625, 6, 8), // Before time-zones (date of Cassini's birth) QCOMPARE(assignee.timeSpec(), Qt::LocalTime);
QDate(1901, 12, 13), // Last day before 32-bit time_t's range assignee = QTimeZone();
QDate(1969, 12, 31), // Last day before the epoch QCOMPARE(assignee.timeSpec(), Qt::TimeZone);
QDate(1970, 0, 0), // Start of epoch assignee = QTimeZone::fromSecondsAheadOfUtc(1);
QDate(2000, 2, 29), // An anomalous leap day QCOMPARE(assignee.timeSpec(), Qt::OffsetFromUTC);
QDate(2038, 1, 20) // First day after 32-bit time_t's range assignee = QTimeZone::fromSecondsAheadOfUtc(0);
}; QCOMPARE(assignee.timeSpec(), Qt::UTC);
for (const auto &date : dates) #if QT_CONFIG(timezone)
QCOMPARE(date.startOfDay(Qt::LocalTime), date.startOfDay(zone)); {
const QTimeZone cet("Europe/Oslo");
assignee = cet;
QCOMPARE(assignee.timeSpec(), Qt::TimeZone);
}
#endif
}
#if __cpp_lib_chrono >= 201907L void tst_QTimeZone::compare()
const std::chrono::time_zone *currentTimeZone = std::chrono::current_zone(); {
QCOMPARE(QByteArrayView(currentTimeZone->name()), QByteArrayView(zone.id())); const QTimeZone local;
const QTimeZone utc(QTimeZone::UTC);
const auto secondEast = QTimeZone::fromSecondsAheadOfUtc(1);
QCOMPARE_NE(local, utc);
QCOMPARE_NE(utc, secondEast);
QCOMPARE_NE(secondEast, local);
QCOMPARE(local, QTimeZone());
QCOMPARE(utc, QTimeZone::fromSecondsAheadOfUtc(0));
QCOMPARE(secondEast, QTimeZone::fromDurationAheadOfUtc(std::chrono::seconds{1}));
}
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 #endif
} }
void tst_QTimeZone::dataStreamTest() void tst_QTimeZone::dataStreamTest()
{ {
#ifndef QT_NO_DATASTREAM
// Test the OffsetFromUtc backend serialization. First with a custom timezone: // Test the OffsetFromUtc backend serialization. First with a custom timezone:
QTimeZone tz1("QST", 123456, "Qt Standard Time", "QST", QLocale::Norway, "Qt Testing"); QTimeZone tz1("QST", 123456, "Qt Standard Time", "QST", QLocale::Norway, "Qt Testing");
QByteArray tmp; QByteArray tmp;
@ -372,6 +484,42 @@ void tst_QTimeZone::dataStreamTest()
ds >> tz2; ds >> tz2;
} }
QCOMPARE(tz2.id(), tz1.id()); 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();
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));
#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() void tst_QTimeZone::isTimeZoneIdAvailable()
@ -1624,6 +1772,7 @@ void tst_QTimeZone::stdCompatibility()
QSKIP("This test requires C++20's <chrono>."); QSKIP("This test requires C++20's <chrono>.");
#endif #endif
} }
#endif // timezone backends
QTEST_APPLESS_MAIN(tst_QTimeZone) QTEST_APPLESS_MAIN(tst_QTimeZone)
#include "tst_qtimezone.moc" #include "tst_qtimezone.moc"