From 6f1eb8f127ac93e764b61a2aa31d41f64df9b87a Mon Sep 17 00:00:00 2001 From: Edward Welbourne Date: Tue, 3 May 2022 16:06:35 +0200 Subject: [PATCH] QDateTime::setMSecsSinceEpoch(): push more work into a helper function Formerly, setMSecsSinceEpoch() passed a QDate and a QTime to epochMSecsToLocalTime() as in/out parameters, then obtained what it actually wants, a count of milliseconds, from their modified values and computed an offset from that. Pass the epoch milliseconds from which the QDate and QTime were derived in place of them and return the local milliseconds, offset and DST status back in a structure, rather than via pointer parameters. This prepares the way for pushing one step further, back into qt_localtime(), so that we can simply get the count of milliseconds, along with DST flag and offset from UTC, without going via QDate and QTime (except in the kludge that copes with times outside the time_t range; and even that can be short-cut somewhat). Change-Id: I37ebb1efb355e81e1a116d03cbd52f9bb4a1f114 Reviewed-by: Thiago Macieira --- src/corelib/time/qdatetime.cpp | 134 +++++++++++++++++---------------- src/corelib/time/qdatetime_p.h | 17 ++++- 2 files changed, 84 insertions(+), 67 deletions(-) diff --git a/src/corelib/time/qdatetime.cpp b/src/corelib/time/qdatetime.cpp index 1588801e42..589ba0f1ce 100644 --- a/src/corelib/time/qdatetime.cpp +++ b/src/corelib/time/qdatetime.cpp @@ -2889,45 +2889,60 @@ static int systemTimeYearMatching(int year) return res; } -// Convert an MSecs Since Epoch into Local Time -bool QDateTimePrivate::epochMSecsToLocalTime(qint64 msecs, QDate *localDate, QTime *localTime, - DaylightStatus *daylightStatus) +// Sets up d and status to represent local time at the given UTC msecs since epoch: +QDateTimePrivate::ZoneState QDateTimePrivate::expressUtcAsLocal(qint64 utcMSecs) { - if (!millisInSystemRange(msecs)) { + // TODO: refactor qt_localtime to return milliseconds in place of date and time + QDate localDate; + QTime localTime; + ZoneState result{utcMSecs}; + + // Within the time_t supported range, localtime() can handle it: + if (millisInSystemRange(utcMSecs)) { + if (!Q_LIKELY(qt_localtime(utcMSecs, &localDate, &localTime, &result.dst))) + return result; // Docs state any LocalTime after 2038-01-18 *will* have any DST applied. // When this falls outside the supported range, we need to fake it. -#if QT_CONFIG(timezone) - // Use the system time-zone. - const auto sys = QTimeZone::systemTimeZone(); - if (sys.isValid()) { - if (daylightStatus) { - *daylightStatus = sys.d->isDaylightTime(msecs) - ? QDateTimePrivate::DaylightTime - : QDateTimePrivate::StandardTime; - } - if (add_overflow(msecs, sys.d->offsetFromUtc(msecs) * MSECS_PER_SEC, &msecs)) - return false; - msecsToTime(msecs, localDate, localTime); - return true; - } +#if QT_CONFIG(timezone) // Use the system time-zone. + } else if (const auto sys = QTimeZone::systemTimeZone(); sys.isValid()) { + result.offset = sys.d->offsetFromUtc(utcMSecs); + if (add_overflow(utcMSecs, result.offset * MSECS_PER_SEC, &result.when)) + return result; + result.dst = sys.d->isDaylightTime(utcMSecs) ? DaylightTime : StandardTime; + result.valid = true; + return result; #endif // timezone + } else { // Kludge // Use existing method to fake the conversion. QDate utcDate; QTime utcTime; - msecsToTime(msecs, &utcDate, &utcTime); + msecsToTime(utcMSecs, &utcDate, &utcTime); int year, month, day; utcDate.getDate(&year, &month, &day); - QDate fakeDate(systemTimeYearMatching(year), month, day); - qint64 fakeMsecs = QDateTime(fakeDate, utcTime, Qt::UTC).toMSecsSinceEpoch(); - bool res = qt_localtime(fakeMsecs, localDate, localTime, daylightStatus); - *localDate = localDate->addDays(fakeDate.daysTo(utcDate)); - return res; + const QDate fakeDate(systemTimeYearMatching(year), month, day); + const qint64 fakeUtc = QDateTime(fakeDate, utcTime, Qt::UTC).toMSecsSinceEpoch(); + if (!qt_localtime(fakeUtc, &localDate, &localTime, &result.dst)) + return result; + localDate = localDate.addDays(fakeDate.daysTo(utcDate)); } - - // Falls inside time_t supported range so localtime can handle it: - return qt_localtime(msecs, localDate, localTime, daylightStatus); + qint64 days = localDate.toJulianDay() - JULIAN_DAY_FOR_EPOCH; + qint64 ds = localTime.msecsSinceStartOfDay(); + Q_ASSERT(ds < MSECS_PER_DAY); + if (days < 0 && ds > 0) { + ++days; + ds -= MSECS_PER_DAY; + } + qint64 msecs; + if (mul_overflow(days, std::integral_constant(), &msecs) + || add_overflow(msecs, ds, &result.when)) { + return result; + } + result.offset = (result.when - utcMSecs) / MSECS_PER_SEC; + // result.dst already set + result.valid = true; + return result; } // Convert a LocalTime expressed in local msecs encoding and the corresponding @@ -4164,58 +4179,49 @@ void QDateTime::setMSecsSinceEpoch(qint64 msecs) auto status = getStatus(d); const auto spec = extractSpec(status); Q_ASSERT(spec == Qt::UTC || spec == Qt::LocalTime || !d.isShort()); - qint64 local = msecs; - int offsetFromUtc = 0; + QDateTimePrivate::ZoneState state(msecs); status &= ~QDateTimePrivate::ValidityMask; if (spec == Qt::UTC || spec == Qt::OffsetFromUTC) { if (spec == Qt::OffsetFromUTC) - offsetFromUtc = d->m_offsetFromUtc; - if (!offsetFromUtc || !add_overflow(msecs, offsetFromUtc * MSECS_PER_SEC, &local)) + state.offset = d->m_offsetFromUtc; + if (!state.offset || !add_overflow(msecs, state.offset * MSECS_PER_SEC, &state.when)) status |= QDateTimePrivate::ValidWhenMask; - } else { - auto dst = extractDaylightStatus(status); - if (spec == Qt::LocalTime) { - QDate dt; - QTime tm; - if (QDateTimePrivate::epochMSecsToLocalTime(msecs, &dt, &tm, &dst)) { - setDateTime(d, dt, tm); - status = getStatus(d); - } // else leave status marked invalid. - if ((status & QDateTimePrivate::ValidDate) && (status & QDateTimePrivate::ValidTime)) { - local = getMSecs(d); - offsetFromUtc = (local - msecs) / MSECS_PER_SEC; - status |= QDateTimePrivate::ValidWhenMask; - } + } else if (spec == Qt::LocalTime) { + state = QDateTimePrivate::expressUtcAsLocal(msecs); + if (state.valid) + status = mergeDaylightStatus(status | QDateTimePrivate::ValidWhenMask, state.dst); #if QT_CONFIG(timezone) - } else if (spec == Qt::TimeZone && (d.detach(), d->m_timeZone.isValid())) { - const auto data = d->m_timeZone.d->data(msecs); - if (data.offsetFromUtc != QTimeZonePrivate::invalidSeconds()) { - dst = data.daylightTimeOffset - ? QDateTimePrivate::DaylightTime - : QDateTimePrivate::StandardTime; - offsetFromUtc = data.offsetFromUtc; - if (!offsetFromUtc - || !add_overflow(msecs, offsetFromUtc * MSECS_PER_SEC, &local)) { - status |= QDateTimePrivate::ValidWhenMask; - } - } + } else if (spec == Qt::TimeZone && (d.detach(), d->m_timeZone.isValid())) { + const auto data = d->m_timeZone.d->data(msecs); + if (Q_LIKELY(data.offsetFromUtc != QTimeZonePrivate::invalidSeconds())) { + state.offset = data.offsetFromUtc; + Q_ASSERT(state.offset >= -SECS_PER_DAY && state.offset <= SECS_PER_DAY); + if (!state.offset + || !Q_UNLIKELY(add_overflow(msecs, state.offset * MSECS_PER_SEC, &state.when))) { + d->m_status = mergeDaylightStatus(status | QDateTimePrivate::ValidWhenMask, + data.daylightTimeOffset + ? QDateTimePrivate::DaylightTime + : QDateTimePrivate::StandardTime); + d->m_msecs = state.when; + d->m_offsetFromUtc = state.offset; + return; + } // else: zone can't represent this UTC time + } // else: zone unable to represent given UTC time (should only happen on overflow). #endif // timezone - } - status = mergeDaylightStatus(status, dst); } Q_ASSERT(!(status & QDateTimePrivate::ValidDateTime) - || (offsetFromUtc >= -SECS_PER_DAY && offsetFromUtc <= SECS_PER_DAY)); + || (state.offset >= -SECS_PER_DAY && state.offset <= SECS_PER_DAY)); - if (msecsCanBeSmall(local) && d.isShort()) { + if (msecsCanBeSmall(state.when) && d.isShort()) { // we can keep short - d.data.msecs = qintptr(local); + d.data.msecs = qintptr(state.when); d.data.status = status.toInt(); } else { d.detach(); d->m_status = status & ~QDateTimePrivate::ShortData; - d->m_msecs = local; - d->m_offsetFromUtc = offsetFromUtc; + d->m_msecs = state.when; + d->m_offsetFromUtc = state.offset; } } diff --git a/src/corelib/time/qdatetime_p.h b/src/corelib/time/qdatetime_p.h index c452a80fc8..d3246fe4ac 100644 --- a/src/corelib/time/qdatetime_p.h +++ b/src/corelib/time/qdatetime_p.h @@ -1,4 +1,4 @@ -// Copyright (C) 2021 The Qt Company Ltd. +// Copyright (C) 2022 The Qt Company Ltd. // Copyright (C) 2016 Intel Corporation. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only @@ -75,6 +75,17 @@ public: TimeSpecShift = 4, }; + struct ZoneState { + qint64 when; // ms after zone/local 1970 start + int offset = 0; // seconds + DaylightStatus dst = UnknownDaylightTime; + bool valid = false; + + ZoneState(qint64 local) : when(local) {} + ZoneState(qint64 w, int o, DaylightStatus d, bool v = true) + : when(w), offset(o), dst(d), valid(v) {} + }; + static QDateTime::Data create(QDate toDate, QTime toTime, Qt::TimeSpec toSpec, int offsetSeconds); @@ -87,8 +98,8 @@ public: QString *abbreviation = nullptr); #endif // timezone - static bool epochMSecsToLocalTime(qint64 msecs, QDate *localDate, QTime *localTime, - DaylightStatus *daylightStatus = nullptr); + static ZoneState expressUtcAsLocal(qint64 utcMSecs); + static qint64 localMSecsToEpochMSecs(qint64 localMsecs, DaylightStatus *daylightStatus, QDate *localDate = nullptr, QTime *localTime = nullptr,