diff --git a/src/corelib/tools/qdatetime.cpp b/src/corelib/tools/qdatetime.cpp index eeefea8137..e5fbf5af5e 100644 --- a/src/corelib/tools/qdatetime.cpp +++ b/src/corelib/tools/qdatetime.cpp @@ -2607,7 +2607,6 @@ QDateTimePrivate::QDateTimePrivate(const QDate &toDate, const QTime &toTime, void QDateTimePrivate::setTimeSpec(Qt::TimeSpec spec, int offsetSeconds) { clearValidDateTime(); - clearTimeZoneCached(); clearSetToDaylightStatus(); #ifndef QT_BOOTSTRAPPED @@ -2707,27 +2706,44 @@ QDateTimePrivate::DaylightStatus QDateTimePrivate::daylightStatus() const return UnknownDaylightTime; } +qint64 QDateTimePrivate::toMSecsSinceEpoch() const +{ + switch (m_spec) { + case Qt::OffsetFromUTC: + case Qt::UTC: + return (m_msecs - (m_offsetFromUtc * 1000)); + + case Qt::LocalTime: + // recalculate the local timezone + return localMSecsToEpochMSecs(m_msecs); + + case Qt::TimeZone: +#ifndef QT_BOOTSTRAPPED + return zoneMSecsToEpochMSecs(m_msecs, m_timeZone); +#endif + break; + } + Q_UNREACHABLE(); + return 0; +} + // Check the UTC / offsetFromUTC validity void QDateTimePrivate::checkValidDateTime() { switch (m_spec) { case Qt::OffsetFromUTC: case Qt::UTC: + // for these, a valid date and a valid time imply a valid QDateTime if (isValidDate() && isValidTime()) setValidDateTime(); 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(); - m_offsetFromUtc = 0; + // for these, we need to check whether the timezone is valid and whether + // the time is valid in that timezone. Expensive, but no other option. + refreshDateTime(); break; } } @@ -2741,12 +2757,6 @@ void QDateTimePrivate::refreshDateTime() // 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; } @@ -3082,7 +3092,6 @@ bool QDateTime::isNull() const bool QDateTime::isValid() const { - d->refreshDateTime(); return (d->isValidDateTime()); } @@ -3143,13 +3152,11 @@ Qt::TimeSpec QDateTime::timeSpec() const 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: return QTimeZone::utc(); + case Qt::OffsetFromUTC: case Qt::TimeZone: + Q_ASSERT(d->m_timeZone.isValid()); return d->m_timeZone; case Qt::LocalTime: return QTimeZone::systemTimeZone(); @@ -3178,7 +3185,6 @@ QTimeZone QDateTime::timeZone() const int QDateTime::offsetFromUtc() const { - d->refreshDateTime(); return d->m_offsetFromUtc; } @@ -3350,7 +3356,7 @@ void QDateTime::setTimeZone(const QTimeZone &toZone) d->m_spec = Qt::TimeZone; d->m_offsetFromUtc = 0; d->m_timeZone = toZone; - d->m_status = d->m_status & ~QDateTimePrivate::ValidDateTime & ~QDateTimePrivate::TimeZoneCached; + d->refreshDateTime(); } #endif // QT_BOOTSTRAPPED @@ -3371,7 +3377,6 @@ void QDateTime::setTimeZone(const QTimeZone &toZone) */ qint64 QDateTime::toMSecsSinceEpoch() const { - d->refreshDateTime(); return d->toMSecsSinceEpoch(); } @@ -3453,8 +3458,8 @@ void QDateTime::setMSecsSinceEpoch(qint64 msecs) d->m_status = d->m_status | QDateTimePrivate::ValidDate | QDateTimePrivate::ValidTime - | QDateTimePrivate::ValidDateTime - | QDateTimePrivate::TimeZoneCached; + | QDateTimePrivate::ValidDateTime; + d->refreshDateTime(); #endif // QT_BOOTSTRAPPED break; case Qt::LocalTime: { diff --git a/src/corelib/tools/qdatetime.h b/src/corelib/tools/qdatetime.h index 784aced71a..88288872df 100644 --- a/src/corelib/tools/qdatetime.h +++ b/src/corelib/tools/qdatetime.h @@ -326,7 +326,7 @@ private: // ### Qt6: Using a private here has high impact on runtime // on users such as QFileInfo. In Qt 6, the data members // should be inlined. - QExplicitlySharedDataPointer d; + QSharedDataPointer d; #ifndef QT_NO_DATASTREAM friend Q_CORE_EXPORT QDataStream &operator<<(QDataStream &, const QDateTime &); diff --git a/src/corelib/tools/qdatetime_p.h b/src/corelib/tools/qdatetime_p.h index a139390a9d..b8934f7f70 100644 --- a/src/corelib/tools/qdatetime_p.h +++ b/src/corelib/tools/qdatetime_p.h @@ -79,10 +79,9 @@ public: enum StatusFlag { NullDate = 0x01, NullTime = 0x02, - ValidDate = 0x04, - ValidTime = 0x08, - ValidDateTime = 0x10, - TimeZoneCached = 0x20, + ValidDate = 0x04, // just the date field + ValidTime = 0x08, // just the time field + ValidDateTime = 0x10, // the whole object (including timezone) SetToStandardTime = 0x40, SetToDaylightTime = 0x80 }; @@ -120,7 +119,7 @@ public: DaylightStatus daylightStatus() const; // Returns msecs since epoch, assumes offset value is current - inline qint64 toMSecsSinceEpoch() const { return (m_msecs - (m_offsetFromUtc * 1000)); } + inline qint64 toMSecsSinceEpoch() const; void checkValidDateTime(); void refreshDateTime(); @@ -133,14 +132,11 @@ public: inline bool isValidDateTime() const { return m_status & ValidDateTime; } inline void setValidDateTime() { m_status |= ValidDateTime; } inline void clearValidDateTime() { m_status &= ~ValidDateTime; } - inline bool isTimeZoneCached() const { return m_status & TimeZoneCached; } - inline void setTimeZoneCached() { m_status |= TimeZoneCached; } - inline void clearTimeZoneCached() { m_status &= ~TimeZoneCached; } inline void clearSetToDaylightStatus() { m_status &= ~(SetToStandardTime | SetToDaylightTime); } #ifndef QT_BOOTSTRAPPED static qint64 zoneMSecsToEpochMSecs(qint64 msecs, const QTimeZone &zone, - QDate *localDate, QTime *localTime); + QDate *localDate = 0, QTime *localTime = 0); #endif // QT_BOOTSTRAPPED static inline qint64 minJd() { return QDate::minJd(); } diff --git a/tests/auto/corelib/tools/qdatetime/tst_qdatetime.cpp b/tests/auto/corelib/tools/qdatetime/tst_qdatetime.cpp index c6806086d3..446e56e936 100644 --- a/tests/auto/corelib/tools/qdatetime/tst_qdatetime.cpp +++ b/tests/auto/corelib/tools/qdatetime/tst_qdatetime.cpp @@ -142,6 +142,9 @@ private slots: void isDaylightTime() const; void daylightTransitions() const; void timeZones() const; +#if defined(Q_OS_UNIX) + void systemTimeZoneChange() const; +#endif void invalid() const; @@ -2984,6 +2987,56 @@ void tst_QDateTime::timeZones() const QCOMPARE(future.offsetFromUtc(), 28800); } +#if defined(Q_OS_UNIX) +// Currently disabled on Windows as adjusting the timezone +// requires additional privileges that aren't normally +// enabled for a process. This can be achieved by calling +// AdjustTokenPrivileges() and then SetTimeZoneInformation(), +// which will require linking to a different library to access that API. +static void setTimeZone(const QByteArray &tz) +{ + qputenv("TZ", tz); + ::tzset(); + +// following left for future reference, see comment above +// #if defined(Q_OS_WIN32) +// ::_tzset(); +// #endif +} + +void tst_QDateTime::systemTimeZoneChange() const +{ + struct ResetTZ { + QByteArray original; + ResetTZ() : original(qgetenv("TZ")) {} + ~ResetTZ() { setTimeZone(original); } + } scopedReset; + + // Set the timezone to Brisbane time + setTimeZone(QByteArray("AEST-10:00")); + + QDateTime localDate = QDateTime(QDate(2012, 6, 1), QTime(2, 15, 30), Qt::LocalTime); + QDateTime utcDate = QDateTime(QDate(2012, 6, 1), QTime(2, 15, 30), Qt::UTC); + QDateTime tzDate = QDateTime(QDate(2012, 6, 1), QTime(2, 15, 30), QTimeZone("Australia/Brisbane")); + qint64 localMsecs = localDate.toMSecsSinceEpoch(); + qint64 utcMsecs = utcDate.toMSecsSinceEpoch(); + qint64 tzMsecs = tzDate.toMSecsSinceEpoch(); + + // check that Australia/Brisbane is known + QVERIFY(tzDate.timeZone().isValid()); + + // Change to Indian time + setTimeZone(QByteArray("IST-05:30")); + + QCOMPARE(localDate, QDateTime(QDate(2012, 6, 1), QTime(2, 15, 30), Qt::LocalTime)); + QVERIFY(localMsecs != localDate.toMSecsSinceEpoch()); + QCOMPARE(utcDate, QDateTime(QDate(2012, 6, 1), QTime(2, 15, 30), Qt::UTC)); + QCOMPARE(utcDate.toMSecsSinceEpoch(), utcMsecs); + QCOMPARE(tzDate, QDateTime(QDate(2012, 6, 1), QTime(2, 15, 30), QTimeZone("Australia/Brisbane"))); + QCOMPARE(tzDate.toMSecsSinceEpoch(), tzMsecs); +} +#endif + void tst_QDateTime::invalid() const { QDateTime invalidDate = QDateTime(QDate(0, 0, 0), QTime(-1, -1, -1));