diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt index 02854a710ad..279562cae20 100644 --- a/src/corelib/CMakeLists.txt +++ b/src/corelib/CMakeLists.txt @@ -256,12 +256,13 @@ qt_internal_add_module(Core time/qcalendarbackend_p.h time/qcalendarmath_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/qjuliancalendar.cpp time/qjuliancalendar_p.h + time/qlocaltime.cpp time/qlocaltime_p.h time/qmilankoviccalendar.cpp time/qmilankoviccalendar_p.h time/qromancalendar.cpp time/qromancalendar_p.h time/qromancalendar_data_p.h + time/qtimezone.cpp time/qtimezone.h tools/qalgorithms.h tools/qarraydata.cpp tools/qarraydata.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 SOURCES - time/qtimezone.cpp time/qtimezone.h time/qtimezoneprivate.cpp time/qtimezoneprivate_p.h time/qtimezoneprivate_data_p.h ) diff --git a/src/corelib/global/qnamespace.qdoc b/src/corelib/global/qnamespace.qdoc index 004899891c9..ae0f7286118 100644 --- a/src/corelib/global/qnamespace.qdoc +++ b/src/corelib/global/qnamespace.qdoc @@ -728,15 +728,25 @@ Mean Time has zero offset from it. Neither UTC nor OffsetFromUTC 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 of LocalTime-based QDateTime objects created before the change is 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 data (e.g. when politicians change the rules for a zone) may likewise lead to conflicts between the updated time-zone information and data cached by QDateTime objects created before the update, using either LocalTime or TimeZone. + + \sa QTimeZone, QDateTime */ /*! diff --git a/src/corelib/kernel/qcore_foundation.mm b/src/corelib/kernel/qcore_foundation.mm index 360b4aebd4f..cf15e24917c 100644 --- a/src/corelib/kernel/qcore_foundation.mm +++ b/src/corelib/kernel/qcore_foundation.mm @@ -495,9 +495,9 @@ QTimeZone QTimeZone::fromCFTimeZone(CFTimeZoneRef timeZone) CFTimeZoneRef QTimeZone::toCFTimeZone() const { #ifndef QT_NO_DYNAMIC_CAST - Q_ASSERT(dynamic_cast(d.data())); + Q_ASSERT(dynamic_cast(d.d)); #endif - const QMacTimeZonePrivate *p = static_cast(d.data()); + const QMacTimeZonePrivate *p = static_cast(d.d); return reinterpret_cast([p->nsTimeZone() copy]); } diff --git a/src/corelib/time/qdatetime.cpp b/src/corelib/time/qdatetime.cpp index 6018d6e9d6f..76f8565f0e6 100644 --- a/src/corelib/time/qdatetime.cpp +++ b/src/corelib/time/qdatetime.cpp @@ -2704,6 +2704,7 @@ QDateTimePrivate::ZoneState QDateTimePrivate::zoneStateAtMillis(const QTimeZone qint64 millis, DaylightStatus dst) { Q_ASSERT(zone.isValid()); + Q_ASSERT(zone.timeSpec() == Qt::TimeZone); // Get the effective data from QTimeZone QTimeZonePrivate::Data data = zone.d->dataForLocalTime(millis, int(dst)); 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, const QTimeZone &toTimeZone) { + Q_ASSERT(toTimeZone.timeSpec() == Qt::TimeZone); QDateTime::Data result(Qt::TimeZone); 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) - : 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 @@ -3757,10 +3762,11 @@ void QDateTime::setOffsetFromUtc(int offsetSeconds) void QDateTime::setTimeZone(const QTimeZone &toZone) { d.detach(); // always detach - d->m_status = mergeSpec(d->m_status, Qt::TimeZone); - d->m_offsetFromUtc = 0; - d->m_timeZone = toZone; - refreshZonedDateTime(d, Qt::TimeZone); + d->m_status = mergeSpec(d->m_status, toZone.timeSpec()); + d->m_offsetFromUtc = toZone.fixedSecondsAheadOfUtc(); + if (toZone.timeSpec() == Qt::TimeZone) + d->m_timeZone = toZone; + checkValidDateTime(d); } #endif // timezone @@ -4527,6 +4533,16 @@ QDateTime QDateTime::toUTC() 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) return *this; @@ -4939,6 +4955,9 @@ QDateTime QDateTime::fromSecsSinceEpoch(qint64 secs, Qt::TimeSpec spec, int offs */ QDateTime QDateTime::fromMSecsSinceEpoch(qint64 msecs, const QTimeZone &timeZone) { + if (timeZone.timeSpec() != Qt::TimeZone) + return fromMSecsSinceEpoch(msecs, timeZone.timeSpec(), timeZone.fixedSecondsAheadOfUtc()); + QDateTime dt; dt.setTimeZone(timeZone); if (timeZone.isValid()) @@ -4962,6 +4981,9 @@ QDateTime QDateTime::fromMSecsSinceEpoch(qint64 msecs, 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; dt.setTimeZone(timeZone); if (timeZone.isValid()) diff --git a/src/corelib/time/qtimezone.cpp b/src/corelib/time/qtimezone.cpp index 197031c616d..ec4ec84b5c1 100644 --- a/src/corelib/time/qtimezone.cpp +++ b/src/corelib/time/qtimezone.cpp @@ -3,7 +3,9 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qtimezone.h" -#include "qtimezoneprivate_p.h" +#if QT_CONFIG(timezone) +# include "qtimezoneprivate_p.h" +#endif #include #include @@ -16,6 +18,7 @@ QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; +#if QT_CONFIG(timezone) // Create default time zone using appropriate backend static QTimeZonePrivate *newBackendTimeZone() { @@ -82,30 +85,78 @@ public: }; Q_GLOBAL_STATIC(QTimeZoneSingleton, global_tz); +#endif // feature timezone /*! \class QTimeZone \inmodule QtCore \since 5.2 - - \brief The QTimeZone class converts between UTC and local time in a specific - time zone. - \threadsafe - This class provides a stateless calculator for time zone conversions - between UTC and the local time in a specific time zone. By default it uses - the host system time zone data to perform these conversions. + \brief QTimeZone identifies how a time representation relates to UTC. + + When dates and times are combined, the meaning of the result depends on how + time is being represented. There are various international standards for + representing time; one of these, UTC, corresponds to the traditional + standard of solar mean time at Greenwich (a.k.a. GMT). All other time + systems supported by Qt are ultimately specified in relation to UTC. An + instance of this class provides a stateless calculator for conversions + between UTC and other time representations. + + Some time representations are simply defined at a fixed offset to UTC. + Others are defined by governments for use within their jurisdictions. The + latter are properly known as time zones, but QTimeZone (since Qt 6.5) is + unifies their representation with that of general time systems. One time + zone generally supported on most operating systems is designated local time; + this is presumed to correspond to the time zone within which the user is + living. + + For time zones other than local time, UTC and those at fixed offsets from + UTC, Qt can only provide support when the operating system provides some way + to access that information. When Qt is built, the \c timezone feature + controls whether such information is available. When it is not, some + constructors and methods of QTimeZone are excluded from its API; these are + documented as depending on feature \c timezone. Note that, even when Qt is + built with this feature enabled, it may be unavailable to users whose + systems are misconfigured, or where some standard packages (for example, the + \c tzdata package on Linux) are not installed. This feature is enabled by + default when time zone information is available. This class is primarily designed for use in QDateTime; most applications - will not need to access this class directly and should instead use - QDateTime with a Qt::TimeSpec of Qt::TimeZone. + will not need to access this class directly and should instead use an + instance of it when constructing a QDateTime. \note For consistency with QDateTime, QTimeZone does not account for leap seconds. \section1 Remarks + QTimeZone, like QDateTime, measures offsets from UTC in seconds. This + contrasts with their measurement of time generally, which they do in + milliseconds. Real-world time zones generally have UTC offsets that are + whole-number multiples of five minutes (300 seconds), at least since well + before 1970. A positive offset from UTC gives a time representation puts + noon on any given day before UTC noon on that day; a negative offset puts + noon after UTC noon on the same day. + + \section2 Lightweight Time Representations + + QTimeZone can represent UTC, local time and fixed offsets from UTC even when + feature \c timezone is disabled. The form in which it does so is also + available when the feature is enabled; it is a more lightweight form and + processing using it will typically be more efficient, unless methods only + available when feature \c timezone is enabled are being exercised. See \l + Initialization and \l QTimeZone::fromSecondsAheadOfUtc(int) for how to + construct these representations. + + This documentation distinguishes between "time zone", used to describe a + time representation described by system-supplied or standard information, + and time representations more generally, which include these lightweight + forms. The methods available only when feature \c timezone is enabled are + apt to be cheaper for time zones than for lightweight time representations, + for which these methods may construct a suitable transient time zone object + to which to forward the query. + \section2 IANA Time Zone IDs QTimeZone uses the IANA time zone IDs as defined in the IANA Time Zone @@ -120,15 +171,16 @@ Q_GLOBAL_STATIC(QTimeZoneSingleton, global_tz); availableTimeZoneIds() to determine what IANA IDs are available. The IANA IDs and database are also know as the Olson IDs and database, - named after their creator. + named after the original compiler of the database. \section2 UTC Offset Time Zones - A default UTC time zone backend is provided which is always guaranteed to - be available. This provides a set of generic Offset From UTC time zones - in the range UTC-14:00 to UTC+14:00. These time zones can be created - using either the standard ISO format names "UTC+00:00" as listed by - availableTimeZoneIds(), or using the number of offset seconds. + A default UTC time zone backend is provided which is always available when + feature \c timezone is enabled. This provides a set of generic Offset From + UTC time zones in the range UTC-14:00 to UTC+14:00. These time zones can be + created using either the standard ISO format names, such as "UTC+00:00", as + listed by availableTimeZoneIds(), or using a name of similar form in + combination with the number of offset seconds. \section2 Windows Time Zones @@ -215,6 +267,7 @@ Q_GLOBAL_STATIC(QTimeZoneSingleton, global_tz); \value MaxUtcOffsetSecs +14 * 3600 */ +#if QT_CONFIG(timezone) /*! \enum QTimeZone::TimeType @@ -235,6 +288,8 @@ Q_GLOBAL_STATIC(QTimeZoneSingleton, global_tz); either an unknown time or a neutral form. For example when formatting a display name this will show something like "Pacific Time". + + This type is only available when feature \c timezone is enabled. */ /*! @@ -250,6 +305,8 @@ Q_GLOBAL_STATIC(QTimeZoneSingleton, global_tz); The short form of the time zone name, usually an abbreviation, e.g. "CET" \value OffsetName The standard ISO offset form of the time zone name, e.g. "UTC+01:00" + + This type is only available when feature \c timezone is enabled. */ /*! @@ -288,29 +345,105 @@ Q_GLOBAL_STATIC(QTimeZoneSingleton, global_tz); \li daylightTimeOffset = 3600 \li abbreviation = "CEST" \endlist + + This type is only available when feature \c timezone is enabled. */ /*! \typedef QTimeZone::OffsetDataList Synonym for QList. + + This type is only available when feature \c timezone is enabled. */ +#endif // timezone backends + +QTimeZone::Data::Data() noexcept : d(nullptr) +{ + // Assumed by the conversion between spec and mode: + static_assert(int(Qt::TimeZone) == 3); +} + +QTimeZone::Data::Data(const Data &other) noexcept +{ +#if QT_CONFIG(timezone) + if (!other.isShort() && other.d) + other.d->ref.ref(); +#endif + d = other.d; +} + +QTimeZone::Data::Data(Data &&other) noexcept + : d(std::exchange(other.d, nullptr)) +{ +} + +QTimeZone::Data::Data(QTimeZonePrivate *dptr) noexcept + : d(dptr) +{ +#if QT_CONFIG(timezone) + if (d) + d->ref.ref(); +#endif +} + +QTimeZone::Data::~Data() +{ +#if QT_CONFIG(timezone) + if (!isShort() && d && !d->ref.deref()) + delete d; + d = nullptr; +#endif +} + +QTimeZone::Data &QTimeZone::Data::operator=(const QTimeZone::Data &other) noexcept +{ +#if QT_CONFIG(timezone) + if (!other.isShort()) + return *this = other.d; + if (!isShort() && d && !d->ref.deref()) + delete d; +#endif + d = other.d; + return *this; +} /*! Create a null/invalid time zone instance. */ QTimeZone::QTimeZone() noexcept - : d(nullptr) { + // Assumed by (at least) Data::swap() and {copy,move} {assign,construct}: + static_assert(sizeof(ShortData) <= sizeof(Data::d)); + // Needed for ShortData::offset to represent all valid offsets: + static_assert(qintptr(1) << (sizeof(void *) * 8 - 2) >= MaxUtcOffsetSecs); +} + +#if QT_CONFIG(timezone) +QTimeZone::Data &QTimeZone::Data::operator=(QTimeZonePrivate *dptr) noexcept +{ + if (!isShort()) { + if (d == dptr) + return *this; + if (d && !d->ref.deref()) + delete d; + } + if (dptr) + dptr->ref.ref(); + d = dptr; + Q_ASSERT(!isShort()); + return *this; } /*! - Creates an instance of the requested time zone \a ianaId. + Creates a time zone instance with the requested IANA ID \a ianaId. The ID must be one of the available system IDs or a valid UTC-with-offset ID, otherwise an invalid time zone will be returned. + This constructor is only available when feature \c timezone is enabled. + \sa availableTimeZoneIds() */ @@ -321,11 +454,11 @@ QTimeZone::QTimeZone(const QByteArray &ianaId) d = new QUtcTimeZonePrivate(ianaId); // If not a CLDR UTC offset ID then try creating it with the system backend. // Relies on backend not creating valid TZ with invalid name. - if (!d.constData()->isValid()) + if (!d->isValid()) d = ianaId.isEmpty() ? newBackendTimeZone() : newBackendTimeZone(ianaId); // Can also handle UTC with arbitrary (valid) offset, but only do so as // fall-back, since either of the above may handle it more informatively. - if (!d.constData()->isValid()) { + if (!d->isValid()) { qint64 offset = QUtcTimeZonePrivate::offsetFromUtcString(ianaId); if (offset != QTimeZonePrivate::invalidSeconds()) { // Should have abs(offset) < 24 * 60 * 60 = 86400. @@ -338,11 +471,15 @@ QTimeZone::QTimeZone(const QByteArray &ianaId) } /*! - Creates an instance of a time zone with the requested Offset from UTC of - \a offsetSeconds. + Creates a time zone instance with the given offset, \a offsetSeconds, from UTC. The \a offsetSeconds from UTC must be in the range -14 hours to +14 hours otherwise an invalid time zone will be returned. + + This constructor is only available when feature \c timezone is enabled. The + returned instance is equivalent to the lightweight time representation + \c{QTimeZone::fromSecondsAfterUtc(offsetSeconds)}, albeit implemented as a + time zone. */ QTimeZone::QTimeZone(int offsetSeconds) @@ -352,9 +489,11 @@ QTimeZone::QTimeZone(int offsetSeconds) } /*! - Creates a custom time zone with an ID of \a ianaId and an offset from UTC - of \a offsetSeconds. The \a name will be the name used by displayName() - for the LongName, the \a abbreviation will be used by displayName() for the + Creates a custom time zone instance at fixed offset from UTC. + + The returned time zone has an ID of \a ianaId and an offset from UTC of \a + offsetSeconds. The \a name will be the name used by displayName() for the + LongName, the \a abbreviation will be used by displayName() for the ShortName and by abbreviation(), and the optional \a territory will be used by territory(). The \a comment is an optional note that may be displayed in a GUI to assist users in selecting a time zone. @@ -365,19 +504,23 @@ QTimeZone::QTimeZone(int offsetSeconds) If the custom time zone does not have a specific territory then set it to the default value of QLocale::AnyTerritory. + + This constructor is only available when feature \c timezone is enabled. */ QTimeZone::QTimeZone(const QByteArray &ianaId, int offsetSeconds, const QString &name, const QString &abbreviation, QLocale::Territory territory, const QString &comment) + : d(isTimeZoneIdAvailable(ianaId) ? nullptr // Don't let client code hijack a real zone name. + : new QUtcTimeZonePrivate(ianaId, offsetSeconds, name, abbreviation, territory, comment)) { - if (!isTimeZoneIdAvailable(ianaId)) - d = new QUtcTimeZonePrivate(ianaId, offsetSeconds, name, abbreviation, territory, comment); } /*! \internal Private. Create time zone with given private backend + + This constructor is only available when feature \c timezone is enabled. */ QTimeZone::QTimeZone(QTimeZonePrivate &dd) @@ -386,7 +529,143 @@ QTimeZone::QTimeZone(QTimeZonePrivate &dd) } /*! - Copy constructor, copy \a other to this. + \since 6.5 + Converts this QTimeZone to one whose timeSpec() is Qt::TimeZone. + + In all cases, the result's \l timeSpec() is Qt::TimeZone. When this + QTimeZone's timeSpec() is Qt::TimeZone, this QTimeZone itself is returned. + + If timeSpec() is Qt::UTC, QTimeZone::utc() is returned. If it is + Qt::OffsetFromUTC then QTimeZone(int) is passed its offset and the result is + returned. + + If timeSpec() is Qt::LocalTime then an instance of the current system time + zone will be returned. This will not change to reflect any subsequent change + to the system time zone. It represents the local time that was in effect + when asBackendZone() was called. + + When using a lightweight time representation - local time, UTC time or time + at a fixed offset from UTC - using methods only supported when feature \c + timezone is enabled may be more expensive than using a corresponding time + zone. This method maps a lightweight time representation to a corresponding + time zone - that is, an instance based on system-supplied or standard data. + + This method is only available when feature \c timezone is enabled. + + \sa QTimeZone(Initialization), fromSecondsAheadOfUtc() +*/ + +QTimeZone QTimeZone::asBackendZone() const +{ + switch (timeSpec()) { + case Qt::TimeZone: + return *this; + case Qt::LocalTime: + return systemTimeZone(); + case Qt::UTC: + return utc(); + case Qt::OffsetFromUTC: + return QTimeZone(*new QUtcTimeZonePrivate(int(d.s.offset))); + } + return QTimeZone(); +} +#endif // timezone backends + +/*! + \since 6.5 + \enum QTimeZone::Initialization + + The type of the simplest lightweight time representations. + + This enumeration identifies a type of lightweight time representation to + pass to a QTimeZone constructor, where no further data are required. They + correspond to the like-named members of Qt::TimeSpec. + + \value LocalTime This time representation corresponds to the one implicitly + used by system functions using \c time_t and \c {struct tm} + value to map between local time and UTC time. + + \value UTC This time representation, Coordinated Universal Time, is the base + representation to which civil time is referred in all supported + time representations. It is defined by the International + Telecommunication Union. +*/ + +/*! + \since 6.5 + \fn QTimeZone::QTimeZone(Initialization spec) noexcept + + Creates a lightweight instance describing UTC or local time. + + \sa fromSecondsAheadOfUtc(), asBackendZone(), utc(), systemTimeZone() +*/ + +/*! + \since 6.5 + \fn QTimeZone::fromSecondsAheadOfUtc(int offset) + \fn QTimeZone::fromDurationAheadOfUtc(std::chrono::seconds offset) + + Returns a time representation at a fixed \a offset, in seconds, ahead of + UTC. + + The \a offset from UTC must be in the range -14 hours to +14 hours otherwise an + invalid time zone will be returned. The returned QTimeZone is a lightweight + time representation, not a time zone (backed by system-supplied or standard + data). + + If the offset is 0, the \l timeSpec() of the returned instance will be + Qt::UTC. Otherwise, if \a offset is valid, timeSpec() is + Qt::OffsetFromUTC. An invalid time zone, when returned, has Qt::TimeZone as + its timeSpec(). + + \sa QTimeZone(int), asBackendZone(), fixedSecondsAheadOfUtc() +*/ + +/*! + \since 6.5 + \fn Qt::TimeSpec QTimeZone::timeSpec() const noexcept + + Returns a Qt::TimeSpec identifying the type of time representation. + + If the result is Qt::TimeZone, this time description is a time zone (backed + by system-supplied or standard data); otherwise, it is a lightweight time + representation. If the result is Qt::LocalTime it describes local time: see + Qt::TimeSpec for details. + + \sa fixedSecondsAheadOfUtc(), asBackendZone() +*/ + +/*! + \since 6.5 + \fn int QTimeZone::fixedSecondsAheadOfUtc() const noexcept + + For a lightweight time representation whose \l timeSpec() is Qt::OffsetFromUTC, + this returns the fixed offset from UTC that it describes. For any other time + representation it returns 0, even if that time representation does have a + constant offset from UTC. +*/ + +/*! + \since 6.5 + \fn QTimeZone::isUtcOrFixedOffset(Qt::TimeSpec spec) noexcept + + Returns \c true if \a spec is Qt::UTC or Qt::OffsetFromUTC. +*/ + +/*! + \since 6.5 + \fn QTimeZone::isUtcOrFixedOffset() const noexcept + + Returns \c true if \l timeSpec() is Qt::UTC or Qt::OffsetFromUTC. + + When it is true, the time description does not change over time, such as + having seasonal daylight-saving changes, as may happen for local time or a + time zone. Knowing this may save the calling code to need for various other + checks. +*/ + +/*! + Copy constructor: copy \a other to this. */ QTimeZone::QTimeZone(const QTimeZone &other) noexcept @@ -412,7 +691,7 @@ QTimeZone::~QTimeZone() } /*! - \fn QTimeZone::swap(QTimeZone &other) + \fn QTimeZone::swap(QTimeZone &other) noexcept Swaps this time zone instance with \a other. This function is very fast and never fails. @@ -428,36 +707,49 @@ QTimeZone &QTimeZone::operator=(const QTimeZone &other) return *this; } -/* - \fn void QTimeZone::swap(QTimeZone &other) - - Swaps this timezone with \a other. This function is very fast and - never fails. -*/ - /*! \fn QTimeZone &QTimeZone::operator=(QTimeZone &&other) - Move-assigns \a other to this QTimeZone instance, transferring the - ownership of the managed pointer to this instance. + Move-assigns \a other to this QTimeZone instance, transferring the ownership + of its data to this instance. */ /*! - Returns \c true if this time zone is equal to the \a other time zone. + Returns \c true if this time representation is equal to the \a other. + + Two representations are different if they are internally described + differently, even if they agree in their representation of all moments of + time. In particular, a lightweight time representation may coincide with a + time zone but the two will not be equal. */ bool QTimeZone::operator==(const QTimeZone &other) const { - return d == other.d || (d && other.d && *d == *other.d); + if (d.isShort()) + return other.d.isShort() && d.s == other.d.s; + + if (!other.d.isShort()) { + if (d.d == other.d.d) + return true; +#if QT_CONFIG(timezone) + return d.d && other.d.d && *d.d == *other.d.d; +#endif + } + + return false; } /*! Returns \c true if this time zone is not equal to the \a other time zone. -*/ -bool QTimeZone::operator!=(const QTimeZone &other) const + Two representations are different if they are internally described + differently, even if they agree in their representation of all moments of + time. In particular, a lightweight time representation may coincide with a + time zone but the two will not be equal. +*/ +bool QTimeZone::operator!=(const QTimeZone &other) const // ### Qt 7: inline { - return d != other.d && (!d || !other.d || *d != *other.d); + return !(*this == other); } /*! @@ -466,29 +758,59 @@ bool QTimeZone::operator!=(const QTimeZone &other) const bool QTimeZone::isValid() const { - return d && d->isValid(); +#if QT_CONFIG(timezone) + if (!d.isShort()) + return d.d && d->isValid(); +#endif + return d.isShort(); } +#if QT_CONFIG(timezone) /*! Returns the IANA ID for the time zone. - IANA IDs are used on all platforms. On Windows these are translated - from the Windows ID into the closest IANA ID for the time zone and territory. + IANA IDs are used on all platforms. On Windows these are translated from + the Windows ID into the best match IANA ID for the time zone and territory. + + This method is only available when feature \c timezone is enabled. */ QByteArray QTimeZone::id() const { - return d ? d->id() : QByteArray(); + if (d.isShort()) { + switch (d.s.spec()) { + case Qt::UTC: + return QTimeZonePrivate::utcQByteArray(); + case Qt::LocalTime: + return systemTimeZoneId(); + case Qt::OffsetFromUTC: + return QUtcTimeZonePrivate(d.s.offset).id(); + case Qt::TimeZone: + Q_UNREACHABLE(); + break; + } + } else if (d.d) { + return d->id(); + } + return QByteArray(); } /*! \since 6.2 Returns the territory for the time zone. + + This method is only available when feature \c timezone is enabled. */ QLocale::Territory QTimeZone::territory() const { - return isValid() ? d->territory() : QLocale::AnyTerritory; + if (d.isShort()) { + if (d.s.spec() == Qt::LocalTime) + return systemTimeZone().territory(); + } else if (isValid()) { + return d->territory(); + } + return QLocale::AnyTerritory; } #if QT_DEPRECATED_SINCE(6, 6) @@ -496,6 +818,8 @@ QLocale::Territory QTimeZone::territory() const \deprecated [6.6] Use territory() instead. Returns the territory for the time zone. + + This method is only available when feature \c timezone is enabled. */ QLocale::Country QTimeZone::country() const @@ -510,11 +834,18 @@ QLocale::Country QTimeZone::country() const A comment may be provided by the host platform to assist users in choosing the correct time zone. Depending on the platform this may not be localized. + + This method is only available when feature \c timezone is enabled. */ QString QTimeZone::comment() const { - return isValid() ? d->comment() : QString(); + if (d.isShort()) { + // TODO: anything ? Or just stick with empty string ? + } else if (isValid()) { + return d->comment(); + } + return QString(); } /*! @@ -528,14 +859,29 @@ QString QTimeZone::comment() const The display name may change depending on DST or historical events. + This method is only available when feature \c timezone is enabled. + \sa abbreviation() */ QString QTimeZone::displayName(const QDateTime &atDateTime, NameType nameType, const QLocale &locale) const { - if (isValid()) + if (d.isShort()) { + switch (d.s.spec()) { + case Qt::LocalTime: + return systemTimeZone().displayName(atDateTime, nameType, locale); + case Qt::UTC: + case Qt::OffsetFromUTC: + return QUtcTimeZonePrivate(d.s.offset).QTimeZonePrivate::displayName( + atDateTime.toMSecsSinceEpoch(), nameType, locale); + case Qt::TimeZone: + Q_UNREACHABLE(); + break; + } + } else if (isValid()) { return d->displayName(atDateTime.toMSecsSinceEpoch(), nameType, locale); + } return QString(); } @@ -552,14 +898,28 @@ QString QTimeZone::displayName(const QDateTime &atDateTime, NameType nameType, Where the time zone display names have changed over time then the most recent names will be used. + This method is only available when feature \c timezone is enabled. + \sa abbreviation() */ QString QTimeZone::displayName(TimeType timeType, NameType nameType, const QLocale &locale) const { - if (isValid()) + if (d.isShort()) { + switch (d.s.spec()) { + case Qt::LocalTime: + return systemTimeZone().displayName(timeType, nameType, locale); + case Qt::UTC: + case Qt::OffsetFromUTC: + return QUtcTimeZonePrivate(d.s.offset).displayName(timeType, nameType, locale); + case Qt::TimeZone: + Q_UNREACHABLE(); + break; + } + } else if (isValid()) { return d->displayName(timeType, nameType, locale); + } return QString(); } @@ -571,13 +931,27 @@ QString QTimeZone::displayName(TimeType timeType, NameType nameType, Note that the abbreviation is not guaranteed to be unique to this time zone and should not be used in place of the ID or display name. + This method is only available when feature \c timezone is enabled. + \sa displayName() */ QString QTimeZone::abbreviation(const QDateTime &atDateTime) const { - if (isValid()) + if (d.isShort()) { + switch (d.s.spec()) { + case Qt::LocalTime: + return systemTimeZone().abbreviation(atDateTime); + case Qt::UTC: + case Qt::OffsetFromUTC: + return QUtcTimeZonePrivate(d.s.offset).abbreviation(atDateTime.toMSecsSinceEpoch()); + case Qt::TimeZone: + Q_UNREACHABLE(); + break; + } + } else if (isValid()) { return d->abbreviation(atDateTime.toMSecsSinceEpoch()); + } return QString(); } @@ -593,12 +967,25 @@ QString QTimeZone::abbreviation(const QDateTime &atDateTime) const offsetFromUtc() will return +3600 (UTC+01:00), and during DST it will return +7200 (UTC+02:00). + This method is only available when feature \c timezone is enabled. + \sa standardTimeOffset(), daylightTimeOffset() */ int QTimeZone::offsetFromUtc(const QDateTime &atDateTime) const { - if (isValid()) { + if (d.isShort()) { + switch (d.s.spec()) { + case Qt::LocalTime: + return systemTimeZone().offsetFromUtc(atDateTime); + case Qt::UTC: + case Qt::OffsetFromUTC: + return d.s.offset; + case Qt::TimeZone: + Q_UNREACHABLE(); + break; + } + } else if (isValid()) { const int offset = d->offsetFromUtc(atDateTime.toMSecsSinceEpoch()); if (offset != QTimeZonePrivate::invalidSeconds()) return offset; @@ -615,12 +1002,25 @@ int QTimeZone::offsetFromUtc(const QDateTime &atDateTime) const +3600 seconds. During both standard and DST offsetFromUtc() will return +3600 (UTC+01:00). + This method is only available when feature \c timezone is enabled. + \sa offsetFromUtc(), daylightTimeOffset() */ int QTimeZone::standardTimeOffset(const QDateTime &atDateTime) const { - if (isValid()) { + if (d.isShort()) { + switch (d.s.spec()) { + case Qt::LocalTime: + return systemTimeZone().standardTimeOffset(atDateTime); + case Qt::UTC: + case Qt::OffsetFromUTC: + return d.s.offset; + case Qt::TimeZone: + Q_UNREACHABLE(); + break; + } + } else if (isValid()) { const int offset = d->standardTimeOffset(atDateTime.toMSecsSinceEpoch()); if (offset != QTimeZonePrivate::invalidSeconds()) return offset; @@ -637,12 +1037,25 @@ int QTimeZone::standardTimeOffset(const QDateTime &atDateTime) const seconds. During standard time daylightTimeOffset() will return 0, and when daylight-saving is in effect it will return +3600. + This method is only available when feature \c timezone is enabled. + \sa offsetFromUtc(), standardTimeOffset() */ int QTimeZone::daylightTimeOffset(const QDateTime &atDateTime) const { - if (hasDaylightTime()) { + if (d.isShort()) { + switch (d.s.spec()) { + case Qt::LocalTime: + return systemTimeZone().daylightTimeOffset(atDateTime); + case Qt::UTC: + case Qt::OffsetFromUTC: + return 0; + case Qt::TimeZone: + Q_UNREACHABLE(); + break; + } + } else if (hasDaylightTime()) { const int offset = d->daylightTimeOffset(atDateTime.toMSecsSinceEpoch()); if (offset != QTimeZonePrivate::invalidSeconds()) return offset; @@ -653,23 +1066,55 @@ int QTimeZone::daylightTimeOffset(const QDateTime &atDateTime) const /*! Returns \c true if the time zone has practiced daylight-saving at any time. + This method is only available when feature \c timezone is enabled. + \sa isDaylightTime(), daylightTimeOffset() */ bool QTimeZone::hasDaylightTime() const { - return isValid() && d->hasDaylightTime(); + if (d.isShort()) { + switch (d.s.spec()) { + case Qt::LocalTime: + return systemTimeZone().hasDaylightTime(); + case Qt::UTC: + case Qt::OffsetFromUTC: + return false; + case Qt::TimeZone: + Q_UNREACHABLE(); + break; + } + } else if (isValid()) { + return d->hasDaylightTime(); + } + return false; } /*! Returns \c true if daylight-saving was in effect at the given \a atDateTime. + This method is only available when feature \c timezone is enabled. + \sa hasDaylightTime(), daylightTimeOffset() */ bool QTimeZone::isDaylightTime(const QDateTime &atDateTime) const { - return hasDaylightTime() && d->isDaylightTime(atDateTime.toMSecsSinceEpoch()); + if (d.isShort()) { + switch (d.s.spec()) { + case Qt::LocalTime: + return systemTimeZone().isDaylightTime(atDateTime); + case Qt::UTC: + case Qt::OffsetFromUTC: + return false; + case Qt::TimeZone: + Q_UNREACHABLE(); + break; + } + } else if (hasDaylightTime()) { + return d->isDaylightTime(atDateTime.toMSecsSinceEpoch()); + } + return false; } /*! @@ -677,13 +1122,27 @@ bool QTimeZone::isDaylightTime(const QDateTime &atDateTime) const the equivalent of calling offsetFromUtc(), abbreviation(), etc individually but is more efficient. + This method is only available when feature \c timezone is enabled. + \sa offsetFromUtc(), standardTimeOffset(), daylightTimeOffset(), abbreviation() */ QTimeZone::OffsetData QTimeZone::offsetData(const QDateTime &forDateTime) const { - if (hasTransitions()) + if (d.isShort()) { + switch (d.s.spec()) { + case Qt::LocalTime: + return systemTimeZone().offsetData(forDateTime); + case Qt::UTC: + case Qt::OffsetFromUTC: + return { abbreviation(forDateTime), forDateTime, int(d.s.offset), int(d.s.offset), 0 }; + case Qt::TimeZone: + Q_UNREACHABLE(); + break; + } + } else if (hasTransitions()) { return QTimeZonePrivate::toOffsetData(d->data(forDateTime.toMSecsSinceEpoch())); + } return QTimeZonePrivate::invalidOffsetData(); } @@ -694,12 +1153,28 @@ QTimeZone::OffsetData QTimeZone::offsetData(const QDateTime &forDateTime) const Transitions are changes in the time-zone: these happen when DST turns on or off and when authorities alter the offsets for the time-zone. + This method is only available when feature \c timezone is enabled. + \sa nextTransition(), previousTransition(), transitions() */ bool QTimeZone::hasTransitions() const { - return isValid() && d->hasTransitions(); + if (d.isShort()) { + switch (d.s.spec()) { + case Qt::LocalTime: + return systemTimeZone().hasTransitions(); + case Qt::UTC: + case Qt::OffsetFromUTC: + return false; + case Qt::TimeZone: + Q_UNREACHABLE(); + break; + } + } else if (isValid()) { + return d->hasTransitions(); + } + return false; } /*! @@ -712,13 +1187,27 @@ bool QTimeZone::hasTransitions() const The given \a afterDateTime is exclusive. + This method is only available when feature \c timezone is enabled. + \sa hasTransitions(), previousTransition(), transitions() */ QTimeZone::OffsetData QTimeZone::nextTransition(const QDateTime &afterDateTime) const { - if (hasTransitions()) + if (d.isShort()) { + switch (d.s.spec()) { + case Qt::LocalTime: + return systemTimeZone().nextTransition(afterDateTime); + case Qt::UTC: + case Qt::OffsetFromUTC: + break; + case Qt::TimeZone: + Q_UNREACHABLE(); + break; + } + } else if (hasTransitions()) { return QTimeZonePrivate::toOffsetData(d->nextTransition(afterDateTime.toMSecsSinceEpoch())); + } return QTimeZonePrivate::invalidOffsetData(); } @@ -733,13 +1222,28 @@ QTimeZone::OffsetData QTimeZone::nextTransition(const QDateTime &afterDateTime) The given \a beforeDateTime is exclusive. + This method is only available when feature \c timezone is enabled. + \sa hasTransitions(), nextTransition(), transitions() */ QTimeZone::OffsetData QTimeZone::previousTransition(const QDateTime &beforeDateTime) const { - if (hasTransitions()) - return QTimeZonePrivate::toOffsetData(d->previousTransition(beforeDateTime.toMSecsSinceEpoch())); + if (d.isShort()) { + switch (d.s.spec()) { + case Qt::LocalTime: + return systemTimeZone().previousTransition(beforeDateTime); + case Qt::UTC: + case Qt::OffsetFromUTC: + break; + case Qt::TimeZone: + Q_UNREACHABLE(); + break; + } + } else if (hasTransitions()) { + return QTimeZonePrivate::toOffsetData( + d->previousTransition(beforeDateTime.toMSecsSinceEpoch())); + } return QTimeZonePrivate::invalidOffsetData(); } @@ -749,6 +1253,8 @@ QTimeZone::OffsetData QTimeZone::previousTransition(const QDateTime &beforeDateT The given \a fromDateTime and \a toDateTime are inclusive. + This method is only available when feature \c timezone is enabled. + \sa hasTransitions(), nextTransition(), previousTransition() */ @@ -756,7 +1262,18 @@ QTimeZone::OffsetDataList QTimeZone::transitions(const QDateTime &fromDateTime, const QDateTime &toDateTime) const { OffsetDataList list; - if (hasTransitions()) { + if (d.isShort()) { + switch (d.s.spec()) { + case Qt::LocalTime: + return systemTimeZone().transitions(fromDateTime, toDateTime); + case Qt::UTC: + case Qt::OffsetFromUTC: + break; + case Qt::TimeZone: + Q_UNREACHABLE(); + break; + } + } else if (hasTransitions()) { const QTimeZonePrivate::DataList plist = d->transitions(fromDateTime.toMSecsSinceEpoch(), toDateTime.toMSecsSinceEpoch()); list.reserve(plist.size()); @@ -775,6 +1292,10 @@ QTimeZone::OffsetDataList QTimeZone::transitions(const QDateTime &fromDateTime, translation table and the user's selected country. As a consequence there is a small chance any Windows install may have IDs not known by Qt, in which case "UTC" will be returned. + + This method is only available when feature \c timezone is enabled. + + \sa systemTimeZone() */ QByteArray QTimeZone::systemTimeZoneId() @@ -793,10 +1314,15 @@ QByteArray QTimeZone::systemTimeZoneId() /*! \since 5.5 - Returns a QTimeZone object that refers to the local system time, as - specified by systemTimeZoneId(). - \sa utc() + Returns a QTimeZone object that describes local system time. + + This method is only available when feature \c timezone is enabled. The + returned instance is usually equivalent to the lightweight time + representation \c {QTimeZone(QTimeZone::LocalTime)}, albeit implemented as a + time zone. + + \sa utc(), Initialization, asBackendZone() */ QTimeZone QTimeZone::systemTimeZone() { @@ -805,9 +1331,14 @@ QTimeZone QTimeZone::systemTimeZone() /*! \since 5.5 - Returns a QTimeZone object that refers to UTC (Universal Time Coordinated). + Returns a QTimeZone object that describes UTC as a time zone. - \sa systemTimeZone() + This method is only available when feature \c timezone is enabled. It is + equivalent to passing 0 to QTimeZone(int offsetSeconds) and to the + lightweight time representation QTimeZone(QTimeZone::UTC), albeit + implemented as a time zone, unlike the latter. + + \sa systemTimeZone(), Initialization, asBackendZone() */ QTimeZone QTimeZone::utc() { @@ -817,6 +1348,8 @@ QTimeZone QTimeZone::utc() /*! Returns \c true if a given time zone \a ianaId is available on this system. + This method is only available when feature \c timezone is enabled. + \sa availableTimeZoneIds() */ @@ -843,6 +1376,8 @@ static QList set_union(const QList &l1, const QList QTimeZone::availableTimeZoneIds() a list of all time zone IDs for all countries then use the standard availableTimeZoneIds() method. + This method is only available when feature \c timezone is enabled. + \sa isTimeZoneIdAvailable() */ @@ -873,6 +1410,8 @@ QList QTimeZone::availableTimeZoneIds(QLocale::Territory territory) Returns a list of all available IANA time zone IDs with a given standard time offset of \a offsetSeconds. + This method is only available when feature \c timezone is enabled. + \sa isTimeZoneIdAvailable() */ @@ -885,6 +1424,8 @@ QList QTimeZone::availableTimeZoneIds(int offsetSeconds) /*! Returns the Windows ID equivalent to the given \a ianaId. + This method is only available when feature \c timezone is enabled. + \sa windowsIdToDefaultIanaId(), windowsIdToIanaIds() */ @@ -901,6 +1442,8 @@ QByteArray QTimeZone::ianaIdToWindowsId(const QByteArray &ianaId) regard for the country and should thus be used with care. It is usually best to request the default for a specific country. + This method is only available when feature \c timezone is enabled. + \sa ianaIdToWindowsId(), windowsIdToIanaIds() */ @@ -918,6 +1461,8 @@ QByteArray QTimeZone::windowsIdToDefaultIanaId(const QByteArray &windowsId) As a special case, QLocale::AnyTerritory returns the default of those IANA IDs that do not have any specific territory. + This method is only available when feature \c timezone is enabled. + \sa ianaIdToWindowsId(), windowsIdToIanaIds() */ @@ -931,6 +1476,8 @@ QByteArray QTimeZone::windowsIdToDefaultIanaId(const QByteArray &windowsId, QLoc The returned list is sorted alphabetically. + This method is only available when feature \c timezone is enabled. + \sa ianaIdToWindowsId(), windowsIdToDefaultIanaId() */ @@ -948,6 +1495,8 @@ QList QTimeZone::windowsIdToIanaIds(const QByteArray &windowsId) The returned list is in order of frequency of usage, i.e. larger zones within a territory are listed first. + This method is only available when feature \c timezone is enabled. + \sa ianaIdToWindowsId(), windowsIdToDefaultIanaId() */ @@ -963,7 +1512,41 @@ QList QTimeZone::windowsIdToIanaIds(const QByteArray &windowsId, QLo Returns a QTimeZone object representing the same time zone as \a timeZone. The IANA ID of \a timeZone must be one of the available system IDs, otherwise an invalid time zone will be returned. + + This method is only available when feature \c timezone is enabled. */ +#endif // feature timezone + +template +void QTimeZone::Data::serialize(Stream &out, const Wrap &wrap) const +{ + if (isShort()) { + switch (s.spec()) { + case Qt::UTC: + out << wrap("QTimeZone::UTC"); + break; + case Qt::LocalTime: + out << wrap("QTimeZone::LocalTime"); + break; + case Qt::OffsetFromUTC: + out << wrap("AheadOfUtcBy") << int(s.offset); + break; + case Qt::TimeZone: + Q_UNREACHABLE(); + break; + } + return; + } +#if QT_CONFIG(timezone) + if constexpr (std::is_same::value) { + if (d) + d->serialize(out); + } else { + // QDebug, traditionally gets a QString, hence quotes round the (possibly empty) ID: + out << QString::fromUtf8(d ? QByteArrayView(d->id()) : QByteArrayView()); + } +#endif +} #ifndef QT_NO_DATASTREAM // Invalid, as an IANA ID: too long, starts with - and has other invalid characters in it @@ -971,8 +1554,11 @@ static inline QString invalidId() { return QStringLiteral("-No Time Zone Specifi QDataStream &operator<<(QDataStream &ds, const QTimeZone &tz) { + const auto toQString = [](const char *text) { + return QString(QLatin1StringView(text)); + }; if (tz.isValid()) - tz.d->serialize(ds); + tz.d.serialize(ds, toQString); else ds << invalidId(); return ds; @@ -982,6 +1568,7 @@ QDataStream &operator>>(QDataStream &ds, QTimeZone &tz) { QString ianaId; ds >> ianaId; + // That may be various things other than actual IANA IDs: if (ianaId == invalidId()) { tz = QTimeZone(); } else if (ianaId == "OffsetFromUtc"_L1) { @@ -991,6 +1578,7 @@ QDataStream &operator>>(QDataStream &ds, QTimeZone &tz) int territory; QString comment; ds >> ianaId >> utcOffset >> name >> abbreviation >> territory >> comment; +#if QT_CONFIG(timezone) // Try creating as a system timezone, which succeeds (producing a valid // zone) iff ianaId is valid; use this if it is a plain offset from UTC // zone, with the right offset, ignoring the other data: @@ -1001,8 +1589,21 @@ QDataStream &operator>>(QDataStream &ds, QTimeZone &tz) tz = QTimeZone(ianaId.toUtf8(), utcOffset, name, abbreviation, QLocale::Territory(territory), comment); } +#else + tz = QTimeZone::fromSecondsAheadOfUtc(utcOffset); +#endif + } else if (ianaId == "AheadOfUtcBy"_L1) { + int utcOffset; + ds >> utcOffset; + tz = QTimeZone::fromSecondsAheadOfUtc(utcOffset); + } else if (ianaId == "QTimeZone::UTC"_L1) { + tz = QTimeZone(QTimeZone::UTC); + } else if (ianaId == "QTimeZone::LocalTime"_L1) { + tz = QTimeZone(QTimeZone::LocalTime); +#if QT_CONFIG(timezone) } else { tz = QTimeZone(ianaId.toUtf8()); +#endif } return ds; } @@ -1012,8 +1613,11 @@ QDataStream &operator>>(QDataStream &ds, QTimeZone &tz) QDebug operator<<(QDebug dbg, const QTimeZone &tz) { QDebugStateSaver saver(dbg); - //TODO Include backend and data version details? - dbg.nospace() << "QTimeZone(" << QString::fromUtf8(tz.id()) << ')'; + const auto asIs = [](const char *text) { return text; }; + // TODO Include backend and data version details? + dbg.nospace() << "QTimeZone("; + tz.d.serialize(dbg, asIs); + dbg.nospace() << ')'; return dbg; } #endif diff --git a/src/corelib/time/qtimezone.h b/src/corelib/time/qtimezone.h index 796d838a5c0..6e7ee0bb026 100644 --- a/src/corelib/time/qtimezone.h +++ b/src/corelib/time/qtimezone.h @@ -1,18 +1,17 @@ // Copyright (C) 2013 John Layt // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - #ifndef QTIMEZONE_H #define QTIMEZONE_H -#include -#include #include +#include +#include +#include +#include #include -QT_REQUIRE_CONFIG(timezone); - #if (defined(Q_OS_DARWIN) || defined(Q_QDOC)) && !defined(QT_NO_SYSTEMLOCALE) Q_FORWARD_DECLARE_CF_TYPE(CFTimeZone); Q_FORWARD_DECLARE_OBJC_CLASS(NSTimeZone); @@ -24,6 +23,64 @@ class QTimeZonePrivate; 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 + 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: // Sane UTC offsets range from -14 to +14 hours: enum { @@ -33,13 +90,20 @@ public: MaxUtcOffsetSecs = +14 * 3600 }; - QTimeZone() noexcept; - explicit QTimeZone(int offsetSeconds); + enum Initialization { LocalTime, UTC }; + 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); QTimeZone(const QByteArray &zoneId, int offsetSeconds, const QString &name, const QString &abbreviation, QLocale::Territory territory = QLocale::AnyTerritory, const QString &comment = QString()); +#endif // timezone backends + QTimeZone(const QTimeZone &other) noexcept; QTimeZone(QTimeZone &&other) noexcept; ~QTimeZone(); @@ -55,6 +119,27 @@ public: 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 { StandardTime = 0, DaylightTime = 1, @@ -79,10 +164,10 @@ public: QByteArray id() 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") QLocale::Country country() const; -#endif +# endif QString comment() const; QString displayName(const QDateTime &atDateTime, @@ -125,14 +210,14 @@ public: static QList windowsIdToIanaIds(const QByteArray &windowsId, 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); CFTimeZoneRef toCFTimeZone() const Q_DECL_CF_RETURNS_RETAINED; static QTimeZone fromNSTimeZone(const NSTimeZone *timeZone); 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 static QTimeZone fromStdTimeZonePtr(const std::chrono::time_zone *timeZone) { @@ -141,20 +226,25 @@ public: const std::string_view timeZoneName = timeZone->name(); return QTimeZone(QByteArrayView(timeZoneName).toByteArray()); } -#endif - +# endif +#endif // feature timezone private: #ifndef QT_NO_DATASTREAM 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 QTimeZone(QTimeZonePrivate &dd); friend class QTimeZonePrivate; friend class QDateTime; friend class QDateTimePrivate; - QSharedDataPointer d; + Data d; }; +#if QT_CONFIG(timezone) Q_DECLARE_TYPEINFO(QTimeZone::OffsetData, Q_RELOCATABLE_TYPE); +#endif Q_DECLARE_SHARED(QTimeZone) #ifndef QT_NO_DATASTREAM diff --git a/src/tools/bootstrap/CMakeLists.txt b/src/tools/bootstrap/CMakeLists.txt index 2018a29b94e..87c5edd90e7 100644 --- a/src/tools/bootstrap/CMakeLists.txt +++ b/src/tools/bootstrap/CMakeLists.txt @@ -82,6 +82,7 @@ qt_internal_extend_target(Bootstrap ../../corelib/time/qgregoriancalendar.cpp ../../corelib/time/qlocaltime.cpp ../../corelib/time/qromancalendar.cpp + ../../corelib/time/qtimezone.cpp ../../corelib/tools/qarraydata.cpp ../../corelib/tools/qbitarray.cpp ../../corelib/tools/qcommandlineoption.cpp diff --git a/tests/auto/corelib/time/CMakeLists.txt b/tests/auto/corelib/time/CMakeLists.txt index eadedbe363b..7cc0dce306d 100644 --- a/tests/auto/corelib/time/CMakeLists.txt +++ b/tests/auto/corelib/time/CMakeLists.txt @@ -8,6 +8,4 @@ add_subdirectory(qdate) add_subdirectory(qdatetime) add_subdirectory(qdatetimeparser) add_subdirectory(qtime) -if(QT_FEATURE_timezone AND NOT INTEGRITY) - add_subdirectory(qtimezone) -endif() +add_subdirectory(qtimezone) diff --git a/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp b/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp index 6b73291cc2f..4c73bb7c9f1 100644 --- a/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp +++ b/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp @@ -22,8 +22,14 @@ private Q_SLOTS: // Public class default system tests void createTest(); void nullTest(); - void systemZone(); + void assign(); + void compare(); + void timespec(); + void offset(); void dataStreamTest(); +#if QT_CONFIG(timezone) + void asBackendZone(); + void systemZone(); void isTimeZoneIdAvailable(); void availableTimeZoneIds(); void utcOffsetId_data(); @@ -51,16 +57,17 @@ private Q_SLOTS: 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 +#endif // QT_BUILD_INTERNAL && timezone backends // 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) @@ -292,33 +299,138 @@ void tst_QTimeZone::nullTest() 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 +} -#if __cpp_lib_chrono >= 201907L - const std::chrono::time_zone *currentTimeZone = std::chrono::current_zone(); - QCOMPARE(QByteArrayView(currentTimeZone->name()), QByteArrayView(zone.id())); +void tst_QTimeZone::compare() +{ + 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 } 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; @@ -372,6 +484,42 @@ 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(); + 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() @@ -1624,6 +1772,7 @@ void tst_QTimeZone::stdCompatibility() QSKIP("This test requires C++20's ."); #endif } +#endif // timezone backends QTEST_APPLESS_MAIN(tst_QTimeZone) #include "tst_qtimezone.moc"