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 <thiago.macieira@intel.com>
bb10
Edward Welbourne 2022-05-03 16:06:35 +02:00
parent fc1a844111
commit 6f1eb8f127
2 changed files with 84 additions and 67 deletions

View File

@ -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<qint64, MSECS_PER_DAY>(), &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;
}
}

View File

@ -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,