From 55f5b29d7975d7e642ec258e44ca23726e1dfa0d Mon Sep 17 00:00:00 2001 From: John Layt Date: Mon, 11 Feb 2013 13:44:37 +0000 Subject: QDateTime - Add QTimeZone support Add support to QDateTime for time zones using the new QTimeZone class. [ChangeLog][QtCore][QDateTime] Add support for a new Qt::TimeZone spec to be used with QTimeZone to define times in a specific time zone. Change-Id: I21bfa52a8ba8989b55bb74e025d1f2b2b623b2a7 Reviewed-by: Thiago Macieira --- src/corelib/tools/qdatetime.cpp | 309 +++++++++++++++++++++++++++++++-- src/corelib/tools/qdatetime.h | 19 ++ src/corelib/tools/qdatetime_p.h | 26 ++- src/corelib/tools/qtimezone.h | 2 + src/corelib/tools/qtimezoneprivate.cpp | 65 +++++++ src/corelib/tools/qtimezoneprivate_p.h | 1 + 6 files changed, 408 insertions(+), 14 deletions(-) (limited to 'src/corelib/tools') diff --git a/src/corelib/tools/qdatetime.cpp b/src/corelib/tools/qdatetime.cpp index 997f0e5ff2..ac9639385a 100644 --- a/src/corelib/tools/qdatetime.cpp +++ b/src/corelib/tools/qdatetime.cpp @@ -47,6 +47,7 @@ #include "qset.h" #include "qlocale.h" #include "qdatetime.h" +#include "qtimezoneprivate_p.h" #include "qregexp.h" #include "qdebug.h" #ifndef Q_OS_WIN @@ -2597,9 +2598,26 @@ QDateTimePrivate::QDateTimePrivate(const QDate &toDate, const QTime &toTime, Qt: setDateTime(toDate, toTime); } +#ifndef QT_BOOTSTRAPPED +QDateTimePrivate::QDateTimePrivate(const QDate &toDate, const QTime &toTime, + const QTimeZone &toTimeZone) + : m_spec(Qt::TimeZone), + m_offsetFromUtc(0), + m_timeZone(toTimeZone), + m_status(0) +{ + setDateTime(toDate, toTime); +} +#endif // QT_BOOTSTRAPPED + void QDateTimePrivate::setTimeSpec(Qt::TimeSpec spec, int offsetSeconds) { clearValidDateTime(); + clearTimeZoneCached(); + +#ifndef QT_BOOTSTRAPPED + m_timeZone = QTimeZone(); +#endif // QT_BOOTSTRAPPED switch (spec) { case Qt::OffsetFromUTC: @@ -2611,6 +2629,11 @@ void QDateTimePrivate::setTimeSpec(Qt::TimeSpec spec, int offsetSeconds) m_offsetFromUtc = offsetSeconds; } break; + case Qt::TimeZone: + // Use system time zone instead + m_spec = Qt::LocalTime; + m_offsetFromUtc = 0; + break; case Qt::UTC: case Qt::LocalTime: m_spec = spec; @@ -2676,6 +2699,12 @@ void QDateTimePrivate::checkValidDateTime() else clearValidDateTime(); break; + case Qt::TimeZone: + // Defer checking until required as can be expensive + clearValidDateTime(); + clearTimeZoneCached(); + m_offsetFromUtc = 0; + break; case Qt::LocalTime: // Defer checking until required as can be expensive clearValidDateTime(); @@ -2687,9 +2716,21 @@ void QDateTimePrivate::checkValidDateTime() // Refresh the LocalTime validity and offset void QDateTimePrivate::refreshDateTime() { - // Always set by setDateTime so just return - if (m_spec == Qt::UTC || m_spec == Qt::OffsetFromUTC) + switch (m_spec) { + case Qt::OffsetFromUTC: + case Qt::UTC: + // Always set by setDateTime so just return return; + case Qt::TimeZone: + // If already cached then don't need to refresh as tz won't change + if (isTimeZoneCached()) + return; + // Flag that will have a cached result after calculations + setTimeZoneCached(); + break; + case Qt::LocalTime: + break; + } // If not valid date and time then is invalid if (!isValidDate() || !isValidTime()) { @@ -2698,7 +2739,7 @@ void QDateTimePrivate::refreshDateTime() return; } - // We have a valid date and time and a Qt::LocalTime that needs calculating + // We have a valid date and time and a Qt::LocalTime or Qt::TimeZone that needs calculating QDate date; QTime time; getDateTime(&date, &time); @@ -2706,7 +2747,13 @@ void QDateTimePrivate::refreshDateTime() // Calling toEpochMSecs will adjust the returned date/time if it does QDate testDate; QTime testTime; - qint64 epochMSecs = localMSecsToEpochMSecs(m_msecs, &testDate, &testTime); + qint64 epochMSecs = 0; +#ifndef QT_BOOTSTRAPPED + if (m_spec == Qt::TimeZone) + epochMSecs = zoneMSecsToEpochMSecs(m_msecs, m_timeZone, &testDate, &testTime); + else +#endif // QT_BOOTSTRAPPED + epochMSecs = localMSecsToEpochMSecs(m_msecs, &testDate, &testTime); if (testDate == date && testTime == time) { setValidDateTime(); // Cache the offset to use in toMSecsSinceEpoch() @@ -2717,6 +2764,25 @@ void QDateTimePrivate::refreshDateTime() } } +#ifndef QT_BOOTSTRAPPED +// Convert a TimeZone time expressed in zone msecs encoding into a UTC epoch msecs +qint64 QDateTimePrivate::zoneMSecsToEpochMSecs(qint64 zoneMSecs, const QTimeZone &zone, + QDate *localDate, QTime *localTime) +{ + // Get the effective data from QTimeZone + QTimeZonePrivate::Data data = zone.d->dataForLocalTime(zoneMSecs); + // Docs state any LocalTime before 1970-01-01 will *not* have any Daylight Time applied + // but all times afterwards will have Daylight Time applied. + if (data.atMSecsSinceEpoch >= 0) { + msecsToTime(data.atMSecsSinceEpoch + (data.offsetFromUtc * 1000), localDate, localTime); + return data.atMSecsSinceEpoch; + } else { + msecsToTime(zoneMSecs, localDate, localTime); + return zoneMSecs - (data.standardTimeOffset * 1000); + } +} +#endif // QT_BOOTSTRAPPED + /***************************************************************************** QDateTime member functions *****************************************************************************/ @@ -2833,7 +2899,17 @@ void QDateTimePrivate::refreshDateTime() to +/- 99 hours and 59 minutes and whole minutes only. Note that currently no time zone lies outside the range of +/- 14 hours. - \sa QDate, QTime, QDateTimeEdit + \section2 Time Zone Support + + A Qt::TimeSpec of Qt::TimeZone is also supported in conjunction with the + QTimeZone class. This allows you to define a datetime in a named time zone + adhering to a consistent set of daylight savings transition rules. For + example a time zone of "Europe/Berlin" will apply the daylight savings + rules as used in Germany since 1970. Note that the transition rules + applied depend on the platform support. See the QTimeZone documentation + for more details. + + \sa QDate, QTime, QDateTimeEdit, QTimeZone */ /*! @@ -2867,6 +2943,10 @@ QDateTime::QDateTime(const QDate &date) If \a spec is Qt::OffsetFromUTC then it will be set to Qt::UTC, i.e. an offset of 0 seconds. To create a Qt::OffsetFromUTC datetime use the correct constructor. + + If \a spec is Qt::TimeZone then the spec will be set to Qt::LocalTime, + i.e. the current system time zone. To create a Qt::TimeZone datetime + use the correct constructor. */ QDateTime::QDateTime(const QDate &date, const QTime &time, Qt::TimeSpec spec) @@ -2886,6 +2966,10 @@ QDateTime::QDateTime(const QDate &date, const QTime &time, Qt::TimeSpec spec) If the \a spec is Qt::OffsetFromUTC and \a offsetSeconds is 0 then the timeSpec() will be set to Qt::UTC, i.e. an offset of 0 seconds. + + If \a spec is Qt::TimeZone then the spec will be set to Qt::LocalTime, + i.e. the current system time zone. To create a Qt::TimeZone datetime + use the correct constructor. */ QDateTime::QDateTime(const QDate &date, const QTime &time, Qt::TimeSpec spec, int offsetSeconds) @@ -2893,6 +2977,24 @@ QDateTime::QDateTime(const QDate &date, const QTime &time, Qt::TimeSpec spec, in { } +#ifndef QT_BOOTSTRAPPED +/*! + \since 5.2 + + Constructs a datetime with the given \a date and \a time, using + the Time Zone specified by \a timeZone. + + If \a date is valid and \a time is not, the time will be set to 00:00:00. + + If \a timeZone is invalid then the datetime will be invalid. +*/ + +QDateTime::QDateTime(const QDate &date, const QTime &time, const QTimeZone &timeZone) + : d(new QDateTimePrivate(date, time, timeZone)) +{ +} +#endif // QT_BOOTSTRAPPED + /*! Constructs a copy of the \a other datetime. */ @@ -2943,7 +3045,7 @@ bool QDateTime::isNull() const Returns true if both the date and the time are valid and they are valid in the current Qt::TimeSpec, otherwise returns false. - If the timeSpec() is Qt::LocalTime then the date and time are + If the timeSpec() is Qt::LocalTime or Qt::TimeZone then the date and time are checked to see if they fall in the Standard Time to Daylight Time transition hour, i.e. if the transition is at 2am and the clock goes forward to 3am then the time from 02:00:00 to 02:59:59.999 is considered to be invalid. @@ -2996,6 +3098,39 @@ Qt::TimeSpec QDateTime::timeSpec() const return d->m_spec; } +#ifndef QT_BOOTSTRAPPED +/*! + \since 5.2 + + Returns the time zone of the datetime. + + If the timeSpec() is Qt::LocalTime then an instance of the current system + time zone will be returned. Note however that if you copy this time zone + the instance will not remain in sync if the system time zone changes. + + \sa setTimeZone(), Qt::TimeSpec +*/ + +QTimeZone QDateTime::timeZone() const +{ + switch (d->m_spec) { + case Qt::OffsetFromUTC: + if (!d->m_timeZone.isValid()) + d->m_timeZone = QTimeZone(d->m_offsetFromUtc); + return d->m_timeZone; + case Qt::UTC: + if (!d->m_timeZone.isValid()) + d->m_timeZone = QTimeZone("UTC"); + return d->m_timeZone; + case Qt::TimeZone : + return d->m_timeZone; + case Qt::LocalTime: + return QTimeZone(QTimeZone::systemTimeZoneId()); + } + return QTimeZone(); +} +#endif // QT_BOOTSTRAPPED + /*! \since 5.2 @@ -3003,6 +3138,9 @@ Qt::TimeSpec QDateTime::timeSpec() const If the timeSpec() is Qt::OffsetFromUTC this will be the value originally set. + If the timeSpec() is Qt::TimeZone this will be the offset effective in the + Time Zone including any Daylight Saving Offset. + If the timeSpec() is Qt::LocalTime this will be the difference between the Local Time and UTC including any Daylight Saving Offset. @@ -3045,6 +3183,10 @@ QString QDateTime::timeZoneAbbreviation() const return QStringLiteral("UTC"); case Qt::OffsetFromUTC: return QLatin1String("UTC") + toOffsetString(Qt::ISODate, d->m_offsetFromUtc); + case Qt::TimeZone: +#ifndef QT_BOOTSTRAPPED + return d->m_timeZone.d->abbreviation(d->toMSecsSinceEpoch()); +#endif // QT_BOOTSTRAPPED case Qt::LocalTime: { QString abbrev; localMSecsToEpochMSecs(d->m_msecs, 0, 0, 0, &abbrev); @@ -3059,7 +3201,7 @@ QString QDateTime::timeZoneAbbreviation() const Returns if this datetime falls in Daylight Savings Time. - If the Qt::TimeSpec is not Qt::LocalTime then will always + If the Qt::TimeSpec is not Qt::LocalTime or Qt::TimeZone then will always return false. \sa timeSpec() @@ -3071,6 +3213,10 @@ bool QDateTime::isDaylightTime() const case Qt::UTC: case Qt::OffsetFromUTC: return false; + case Qt::TimeZone: +#ifndef QT_BOOTSTRAPPED + return d->m_timeZone.d->isDaylightTime(toMSecsSinceEpoch()); +#endif // QT_BOOTSTRAPPED case Qt::LocalTime: { QDateTimePrivate::DaylightStatus status; localMSecsToEpochMSecs(d->m_msecs, 0, 0, &status, 0); @@ -3112,10 +3258,13 @@ void QDateTime::setTime(const QTime &time) If \a spec is Qt::OffsetFromUTC then the timeSpec() will be set to Qt::UTC, i.e. an effective offset of 0. + If \a spec is Qt::TimeZone then the spec will be set to Qt::LocalTime, + i.e. the current system time zone. + Example: \snippet code/src_corelib_tools_qdatetime.cpp 19 - \sa timeSpec(), setDate(), setTime(), Qt::TimeSpec + \sa timeSpec(), setDate(), setTime(), setTimeZone(), Qt::TimeSpec */ void QDateTime::setTimeSpec(Qt::TimeSpec spec) @@ -3147,6 +3296,28 @@ void QDateTime::setOffsetFromUtc(int offsetSeconds) d->checkValidDateTime(); } +#ifndef QT_BOOTSTRAPPED +/*! + \since 5.2 + + Sets the time zone used in this datetime to \a toZone. + The datetime will refer to a different point in time. + + If \a toZone is invalid then the datetime will be invalid. + + \sa timeZone(), Qt::TimeSpec +*/ + +void QDateTime::setTimeZone(const QTimeZone &toZone) +{ + detach(); + d->m_spec = Qt::TimeZone; + d->m_offsetFromUtc = 0; + d->m_timeZone = toZone; + d->m_status = d->m_status & ~QDateTimePrivate::ValidDateTime & ~QDateTimePrivate::TimeZoneCached; +} +#endif // QT_BOOTSTRAPPED + /*! \since 4.7 @@ -3234,6 +3405,22 @@ void QDateTime::setMSecsSinceEpoch(qint64 msecs) | QDateTimePrivate::ValidTime | QDateTimePrivate::ValidDateTime; break; + case Qt::TimeZone: +#ifndef QT_BOOTSTRAPPED + // Docs state any LocalTime before 1970-01-01 will *not* have any Daylight Time applied + // but all times afterwards will have Daylight Time applied. + if (msecs >= 0) + d->m_offsetFromUtc = d->m_timeZone.d->offsetFromUtc(msecs); + else + d->m_offsetFromUtc = d->m_timeZone.d->standardTimeOffset(msecs); + d->m_msecs = msecs + (d->m_offsetFromUtc * 1000); + d->m_status = d->m_status + | QDateTimePrivate::ValidDate + | QDateTimePrivate::ValidTime + | QDateTimePrivate::ValidDateTime + | QDateTimePrivate::TimeZoneCached; +#endif // QT_BOOTSTRAPPED + break; case Qt::LocalTime: { QDate dt; QTime tm; @@ -3499,6 +3686,10 @@ QDateTime QDateTime::addDays(qint64 ndays) const // so call conversion and use the adjusted returned time if (d->m_spec == Qt::LocalTime) localMSecsToEpochMSecs(timeToMSecs(date, time), &date, &time); +#ifndef QT_BOOTSTRAPPED + else if (d->m_spec == Qt::TimeZone) + d->zoneMSecsToEpochMSecs(timeToMSecs(date, time), d->m_timeZone, &date, &time); +#endif // QT_BOOTSTRAPPED dt.d->setDateTime(date, time); return dt; } @@ -3529,6 +3720,10 @@ QDateTime QDateTime::addMonths(int nmonths) const // so call conversion and use the adjusted returned time if (d->m_spec == Qt::LocalTime) localMSecsToEpochMSecs(timeToMSecs(date, time), &date, &time); +#ifndef QT_BOOTSTRAPPED + else if (d->m_spec == Qt::TimeZone) + d->zoneMSecsToEpochMSecs(timeToMSecs(date, time), d->m_timeZone, &date, &time); +#endif // QT_BOOTSTRAPPED dt.d->setDateTime(date, time); return dt; } @@ -3559,6 +3754,10 @@ QDateTime QDateTime::addYears(int nyears) const // so call conversion and use the adjusted returned time if (d->m_spec == Qt::LocalTime) localMSecsToEpochMSecs(timeToMSecs(date, time), &date, &time); +#ifndef QT_BOOTSTRAPPED + else if (d->m_spec == Qt::TimeZone) + d->zoneMSecsToEpochMSecs(timeToMSecs(date, time), d->m_timeZone, &date, &time); +#endif // QT_BOOTSTRAPPED dt.d->setDateTime(date, time); return dt; } @@ -3594,7 +3793,7 @@ QDateTime QDateTime::addMSecs(qint64 msecs) const QDateTime dt(*this); dt.detach(); - if (d->m_spec == Qt::LocalTime) + if (d->m_spec == Qt::LocalTime || d->m_spec == Qt::TimeZone) // Convert to real UTC first in case crosses daylight transition dt.setMSecsSinceEpoch(d->toMSecsSinceEpoch() + msecs); else @@ -3677,10 +3876,13 @@ qint64 QDateTime::msecsTo(const QDateTime &other) const If \a spec is Qt::OffsetFromUTC then it is set to Qt::UTC. To set to a spec of Qt::OffsetFromUTC use toOffsetFromUtc(). + If \a spec is Qt::TimeZone then it is set to Qt::LocalTime, + i.e. the local Time Zone. + Example: \snippet code/src_corelib_tools_qdatetime.cpp 16 - \sa timeSpec(), toUTC(), toLocalTime() + \sa timeSpec(), toTimeZone(), toUTC(), toLocalTime() */ QDateTime QDateTime::toTimeSpec(Qt::TimeSpec spec) const @@ -3706,6 +3908,21 @@ QDateTime QDateTime::toOffsetFromUtc(int offsetSeconds) const return fromMSecsSinceEpoch(toMSecsSinceEpoch(), Qt::OffsetFromUTC, offsetSeconds); } +#ifndef QT_BOOTSTRAPPED +/*! + \since 5.2 + + Returns a copy of this datetime converted to the given \a timeZone + + \sa timeZone(), toTimeSpec() +*/ + +QDateTime QDateTime::toTimeZone(const QTimeZone &timeZone) const +{ + return fromMSecsSinceEpoch(toMSecsSinceEpoch(), timeZone); +} +#endif // QT_BOOTSTRAPPED + /*! Returns true if this datetime is equal to the \a other datetime; otherwise returns false. @@ -3934,6 +4151,22 @@ QDateTime QDateTime::fromTime_t(uint seconds, Qt::TimeSpec spec, int offsetSecon return fromMSecsSinceEpoch((qint64)seconds * 1000, spec, offsetSeconds); } +#ifndef QT_BOOTSTRAPPED +/*! + \since 5.2 + + Returns a datetime whose date and time are the number of \a seconds + that have passed since 1970-01-01T00:00:00, Coordinated Universal + Time (Qt::UTC) and with the given \a timeZone. + + \sa toTime_t(), setTime_t() +*/ +QDateTime QDateTime::fromTime_t(uint seconds, const QTimeZone &timeZone) +{ + return fromMSecsSinceEpoch((qint64)seconds * 1000, timeZone); +} +#endif + /*! \since 4.7 @@ -3968,6 +4201,9 @@ QDateTime QDateTime::fromMSecsSinceEpoch(qint64 msecs) ignored. If the \a spec is Qt::OffsetFromUTC and the \a offsetSeconds is 0 then the spec will be set to Qt::UTC, i.e. an offset of 0 seconds. + If \a spec is Qt::TimeZone then the spec will be set to Qt::LocalTime, + i.e. the current system time zone. + \sa fromTime_t() */ QDateTime QDateTime::fromMSecsSinceEpoch(qint64 msecs, Qt::TimeSpec spec, int offsetSeconds) @@ -3979,6 +4215,25 @@ QDateTime QDateTime::fromMSecsSinceEpoch(qint64 msecs, Qt::TimeSpec spec, int of return dt; } +#ifndef QT_BOOTSTRAPPED +/*! + \since 5.2 + + Returns a datetime whose date and time are the number of milliseconds \a msecs + that have passed since 1970-01-01T00:00:00.000, Coordinated Universal + Time (Qt::UTC) and with the given \a timeZone. + + \sa fromTime_t() +*/ +QDateTime QDateTime::fromMSecsSinceEpoch(qint64 msecs, const QTimeZone &timeZone) +{ + QDateTime dt; + dt.setTimeZone(timeZone); + dt.setMSecsSinceEpoch(msecs); + return dt; +} +#endif + #if QT_DEPRECATED_SINCE(5, 2) /*! \since 4.4 @@ -4480,6 +4735,10 @@ QDataStream &operator<<(QDataStream &out, const QDateTime &dateTime) out << dt << tm << qint8(dateTime.timeSpec()); if (dateTime.timeSpec() == Qt::OffsetFromUTC) out << qint32(dateTime.offsetFromUtc()); +#ifndef QT_BOOTSTRAPPED + else if (dateTime.timeSpec() == Qt::TimeZone) + out << dateTime.timeZone(); +#endif // QT_BOOTSTRAPPED } else if (out.version() == QDataStream::Qt_5_0) { @@ -4506,6 +4765,11 @@ QDataStream &operator<<(QDataStream &out, const QDateTime &dateTime) case Qt::OffsetFromUTC: out << (qint8)QDateTimePrivate::OffsetFromUTC; break; + case Qt::TimeZone: +#ifndef QT_BOOTSTRAPPED + out << (qint8)QDateTimePrivate::TimeZone; + break; +#endif // QT_BOOTSTRAPPED case Qt::LocalTime: out << (qint8)QDateTimePrivate::LocalUnknown; break; @@ -4540,15 +4804,26 @@ QDataStream &operator>>(QDataStream &in, QDateTime &dateTime) qint8 ts = 0; Qt::TimeSpec spec = Qt::LocalTime; qint32 offset = 0; +#ifndef QT_BOOTSTRAPPED + QTimeZone tz; +#endif // QT_BOOTSTRAPPED if (in.version() >= QDataStream::Qt_5_2) { // In 5.2 we switched to using Qt::TimeSpec and added offset support in >> dt >> tm >> ts; spec = static_cast(ts); - if (spec == Qt::OffsetFromUTC) + if (spec == Qt::OffsetFromUTC) { in >> offset; - dateTime = QDateTime(dt, tm, spec, offset); + dateTime = QDateTime(dt, tm, spec, offset); +#ifndef QT_BOOTSTRAPPED + } else if (spec == Qt::TimeZone) { + in >> tz; + dateTime = QDateTime(dt, tm, tz); +#endif // QT_BOOTSTRAPPED + } else { + dateTime = QDateTime(dt, tm, spec); + } } else if (in.version() == QDataStream::Qt_5_0) { @@ -4569,6 +4844,11 @@ QDataStream &operator>>(QDataStream &in, QDateTime &dateTime) case QDateTimePrivate::OffsetFromUTC: spec = Qt::OffsetFromUTC; break; + case QDateTimePrivate::TimeZone: +#ifndef QT_BOOTSTRAPPED + spec = Qt::TimeZone; + break; +#endif // QT_BOOTSTRAPPED case QDateTimePrivate::LocalUnknown: case QDateTimePrivate::LocalStandard: case QDateTimePrivate::LocalDST: @@ -4616,6 +4896,11 @@ QDebug operator<<(QDebug dbg, const QDateTime &date) case Qt::OffsetFromUTC: spec = QString::fromUtf8(" Qt::OffsetFromUTC %1s").arg(date.offsetFromUtc()); break; + case Qt::TimeZone: +#ifndef QT_BOOTSTRAPPED + spec = QStringLiteral(" Qt::TimeZone ") + date.timeZone().id(); + break; +#endif // QT_BOOTSTRAPPED case Qt::LocalTime: spec = QStringLiteral(" Qt::LocalTime"); break; diff --git a/src/corelib/tools/qdatetime.h b/src/corelib/tools/qdatetime.h index 4b97cd797d..b9f6995155 100644 --- a/src/corelib/tools/qdatetime.h +++ b/src/corelib/tools/qdatetime.h @@ -50,6 +50,7 @@ QT_BEGIN_NAMESPACE +class QTimeZone; class Q_CORE_EXPORT QDate { @@ -208,6 +209,9 @@ public: QDateTime(const QDate &, const QTime &, Qt::TimeSpec spec = Qt::LocalTime); // ### Qt 6: Merge with above with default offsetSeconds = 0 QDateTime(const QDate &date, const QTime &time, Qt::TimeSpec spec, int offsetSeconds); +#ifndef QT_BOOTSTRAPPED + QDateTime(const QDate &date, const QTime &time, const QTimeZone &timeZone); +#endif // QT_BOOTSTRAPPED QDateTime(const QDateTime &other); ~QDateTime(); @@ -222,6 +226,9 @@ public: QTime time() const; Qt::TimeSpec timeSpec() const; int offsetFromUtc() const; +#ifndef QT_BOOTSTRAPPED + QTimeZone timeZone() const; +#endif // QT_BOOTSTRAPPED QString timeZoneAbbreviation() const; bool isDaylightTime() const; @@ -233,6 +240,9 @@ public: void setTime(const QTime &time); void setTimeSpec(Qt::TimeSpec spec); void setOffsetFromUtc(int offsetSeconds); +#ifndef QT_BOOTSTRAPPED + void setTimeZone(const QTimeZone &toZone); +#endif // QT_BOOTSTRAPPED void setMSecsSinceEpoch(qint64 msecs); // ### Qt 6: use quint64 instead of uint void setTime_t(uint secsSince1Jan1970UTC); @@ -251,6 +261,9 @@ public: inline QDateTime toLocalTime() const { return toTimeSpec(Qt::LocalTime); } inline QDateTime toUTC() const { return toTimeSpec(Qt::UTC); } QDateTime toOffsetFromUtc(int offsetSeconds) const; +#ifndef QT_BOOTSTRAPPED + QDateTime toTimeZone(const QTimeZone &toZone) const; +#endif // QT_BOOTSTRAPPED qint64 daysTo(const QDateTime &) const; qint64 secsTo(const QDateTime &) const; @@ -279,9 +292,15 @@ public: // ### Qt 6: Merge with above with default spec = Qt::LocalTime static QDateTime fromTime_t(uint secsSince1Jan1970UTC, Qt::TimeSpec spec, int offsetFromUtc = 0); +#ifndef QT_BOOTSTRAPPED + static QDateTime fromTime_t(uint secsSince1Jan1970UTC, const QTimeZone &timeZone); +#endif static QDateTime fromMSecsSinceEpoch(qint64 msecs); // ### Qt 6: Merge with above with default spec = Qt::LocalTime static QDateTime fromMSecsSinceEpoch(qint64 msecs, Qt::TimeSpec spec, int offsetFromUtc = 0); +#ifndef QT_BOOTSTRAPPED + static QDateTime fromMSecsSinceEpoch(qint64 msecs, const QTimeZone &timeZone); +#endif static qint64 currentMSecsSinceEpoch() Q_DECL_NOTHROW; private: diff --git a/src/corelib/tools/qdatetime_p.h b/src/corelib/tools/qdatetime_p.h index 7fd36bc4e0..f52108d764 100644 --- a/src/corelib/tools/qdatetime_p.h +++ b/src/corelib/tools/qdatetime_p.h @@ -57,6 +57,8 @@ #include "QtCore/qatomic.h" #include "QtCore/qdatetime.h" +#include "qtimezone.h" + QT_BEGIN_NAMESPACE class QDateTimePrivate : public QSharedData @@ -69,7 +71,8 @@ public: LocalStandard = 0, LocalDST = 1, UTC = 2, - OffsetFromUTC = 3 + OffsetFromUTC = 3, + TimeZone = 4 }; // Daylight Time Status @@ -86,7 +89,8 @@ public: NullTime = 0x02, ValidDate = 0x04, ValidTime = 0x08, - ValidDateTime = 0x10 + ValidDateTime = 0x10, + TimeZoneCached = 0x20 }; Q_DECLARE_FLAGS(StatusFlags, StatusFlag) @@ -99,16 +103,26 @@ public: QDateTimePrivate(const QDate &toDate, const QTime &toTime, Qt::TimeSpec toSpec, int offsetSeconds); +#ifndef QT_BOOTSTRAPPED + QDateTimePrivate(const QDate &toDate, const QTime &toTime, const QTimeZone & timeZone); +#endif // QT_BOOTSTRAPPED + QDateTimePrivate(const QDateTimePrivate &other) : QSharedData(other), m_msecs(other.m_msecs), m_spec(other.m_spec), m_offsetFromUtc(other.m_offsetFromUtc), +#ifndef QT_BOOTSTRAPPED + m_timeZone(other.m_timeZone), +#endif // QT_BOOTSTRAPPED m_status(other.m_status) {} qint64 m_msecs; Qt::TimeSpec m_spec; int m_offsetFromUtc; +#ifndef QT_BOOTSTRAPPED + QTimeZone m_timeZone; +#endif // QT_BOOTSTRAPPED StatusFlags m_status; void setTimeSpec(Qt::TimeSpec spec, int offsetSeconds); @@ -129,6 +143,14 @@ public: inline bool isValidDateTime() const { return (m_status & ValidDateTime) == ValidDateTime; } inline void setValidDateTime() { m_status = m_status | ValidDateTime; } inline void clearValidDateTime() { m_status = m_status & ~ValidDateTime; } + inline bool isTimeZoneCached() const { return (m_status & TimeZoneCached) == TimeZoneCached; } + inline void setTimeZoneCached() { m_status = m_status | TimeZoneCached; } + inline void clearTimeZoneCached() { m_status = m_status & ~TimeZoneCached; } + +#ifndef QT_BOOTSTRAPPED + static qint64 zoneMSecsToEpochMSecs(qint64 msecs, const QTimeZone &zone, + QDate *localDate, QTime *localTime); +#endif // QT_BOOTSTRAPPED static inline qint64 minJd() { return QDate::minJd(); } static inline qint64 maxJd() { return QDate::maxJd(); } diff --git a/src/corelib/tools/qtimezone.h b/src/corelib/tools/qtimezone.h index 5eeb22f8f3..1a6a923cf4 100644 --- a/src/corelib/tools/qtimezone.h +++ b/src/corelib/tools/qtimezone.h @@ -143,6 +143,8 @@ private: friend Q_CORE_EXPORT QDataStream &operator<<(QDataStream &ds, const QTimeZone &tz); #endif friend class QTimeZonePrivate; + friend class QDateTime; + friend class QDateTimePrivate; QSharedDataPointer d; }; diff --git a/src/corelib/tools/qtimezoneprivate.cpp b/src/corelib/tools/qtimezoneprivate.cpp index 4a8d891759..8d5d60bf37 100644 --- a/src/corelib/tools/qtimezoneprivate.cpp +++ b/src/corelib/tools/qtimezoneprivate.cpp @@ -48,6 +48,10 @@ QT_BEGIN_NAMESPACE +enum { + MSECS_TRAN_WINDOW = 21600000 // 6 hour window for possible recent transitions +}; + /* Static utilities for looking up Windows ID tables */ @@ -242,6 +246,67 @@ QTimeZonePrivate::Data QTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const return invalidData(); } +// Private only method for use by QDateTime to convert local msecs to epoch msecs +// TODO Could be platform optimised if needed +QTimeZonePrivate::Data QTimeZonePrivate::dataForLocalTime(qint64 forLocalMSecs) const +{ + if (!hasDaylightTime() ||!hasTransitions()) { + // No daylight time means same offset for all local msecs + // Having daylight time but no transitions means we can't calculate, so use nearest + return data(forLocalMSecs - (standardTimeOffset(forLocalMSecs) * 1000)); + } + + // Get the transition for the local msecs which most of the time should be the right one + // Only around the transition times might it not be the right one + Data tran = previousTransition(forLocalMSecs); + Data nextTran; + + // If the local msecs is less than the real local time of the transition + // then get the previous transition to use instead + if (forLocalMSecs < tran.atMSecsSinceEpoch + (tran.offsetFromUtc * 1000)) { + while (forLocalMSecs < tran.atMSecsSinceEpoch + (tran.offsetFromUtc * 1000)) { + nextTran = tran; + tran = previousTransition(tran.atMSecsSinceEpoch); + } + } else { + // The zone msecs is after the transition, so check it is before the next tran + // If not try use the next transition instead + nextTran = nextTransition(tran.atMSecsSinceEpoch); + while (forLocalMSecs >= nextTran.atMSecsSinceEpoch + (nextTran.offsetFromUtc * 1000)) { + tran = nextTran; + nextTran = nextTransition(tran.atMSecsSinceEpoch); + } + } + + if (tran.daylightTimeOffset == 0) { + // If tran is in StandardTime, then need to check if falls close either daylight transition + // If it does, then it may need adjusting for missing hour or for second occurrence + qint64 diffPrevTran = forLocalMSecs + - (tran.atMSecsSinceEpoch + (tran.offsetFromUtc * 1000)); + qint64 diffNextTran = nextTran.atMSecsSinceEpoch + (nextTran.offsetFromUtc * 1000) + - forLocalMSecs; + if (diffPrevTran >= 0 && diffPrevTran < MSECS_TRAN_WINDOW) { + // If tran picked is for standard time check if changed from daylight in last 6 hours, + // as the local msecs may be ambiguous and represent two valid utc msecs. + // If in last 6 hours then get prev tran and if diff falls within the daylight offset + // then use the prev tran as we default to the FirstOccurrence + // TODO Check if faster to just always get prev tran, or if faster using 6 hour check. + Data dstTran = previousTransition(tran.atMSecsSinceEpoch); + if (dstTran.daylightTimeOffset > 0 && diffPrevTran < (dstTran.daylightTimeOffset * 1000)) + tran = dstTran; + } else if (diffNextTran >= 0 && diffNextTran <= (nextTran.daylightTimeOffset * 1000)) { + // If time falls within last hour of standard time then is actually the missing hour + // So return the next tran instead and adjust the local time to be valid + tran = nextTran; + forLocalMSecs = forLocalMSecs + (nextTran.daylightTimeOffset * 1000); + } + } + + // tran should now hold the right transition offset to use + tran.atMSecsSinceEpoch = forLocalMSecs - (tran.offsetFromUtc * 1000); + return tran; +} + bool QTimeZonePrivate::hasTransitions() const { return false; diff --git a/src/corelib/tools/qtimezoneprivate_p.h b/src/corelib/tools/qtimezoneprivate_p.h index b4e4090c0b..9f99f49fcf 100644 --- a/src/corelib/tools/qtimezoneprivate_p.h +++ b/src/corelib/tools/qtimezoneprivate_p.h @@ -120,6 +120,7 @@ public: virtual bool isDaylightTime(qint64 atMSecsSinceEpoch) const; virtual Data data(qint64 forMSecsSinceEpoch) const; + virtual Data dataForLocalTime(qint64 forLocalMSecs) const; virtual bool hasTransitions() const; virtual Data nextTransition(qint64 afterMSecsSinceEpoch) const; -- cgit v1.2.3