Refactor qt_localtime()

Break out the part that calls the appropriate platform variant, so
that the logic of what we do with the result of that isn't tangled up
with the #if-ery. Rework the remainder to return a struct type that
packages the data we need, to get rid of the three out-parameter
pointers.

Change-Id: Ibba6f8de0954fab3fddf9e95adc18ef85e45ff72
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
bb10
Edward Welbourne 2022-05-05 13:39:11 +02:00
parent 6f1eb8f127
commit b931688a1e
1 changed files with 87 additions and 85 deletions

View File

@ -2634,62 +2634,71 @@ static qint64 qt_mktime(QDate *date, QTime *time, QDateTimePrivate::DaylightStat
return msec;
}
// Calls the platform variant of localtime for the given msecs, and updates
// the date, time, and DST status with the returned values.
static bool qt_localtime(qint64 msecsSinceEpoch, QDate *localDate, QTime *localTime,
QDateTimePrivate::DaylightStatus *daylightStatus)
namespace {
bool qtLocalTime(time_t utc, struct tm *local)
{
const int signFix = msecsSinceEpoch % MSECS_PER_SEC && msecsSinceEpoch < 0 ? 1 : 0;
const time_t secsSinceEpoch = msecsSinceEpoch / MSECS_PER_SEC - signFix;
const int msec = msecsSinceEpoch % MSECS_PER_SEC + signFix * MSECS_PER_SEC;
Q_ASSERT(msec >= 0 && msec < MSECS_PER_SEC);
tm local;
bool valid = false;
// localtime() is specified to work as if it called tzset().
// localtime_r() does not have this constraint, so make an explicit call.
// The explicit call should also request the timezone info be re-parsed.
// The explicit call should also request a re-parse of timezone info.
qTzSet();
if (qint64(secsSinceEpoch) * MSECS_PER_SEC + msec == msecsSinceEpoch) {
#if QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS)
// Use the reentrant version of localtime() where available
// as is thread-safe and doesn't use a shared static data area
if (tm *res = localtime_r(&secsSinceEpoch, &local)) {
Q_ASSERT(res == &local);
valid = true;
}
#elif defined(Q_OS_WIN)
if (!localtime_s(&local, &secsSinceEpoch))
valid = true;
#else
// Returns shared static data which may be overwritten at any time
// So copy the result asap
if (tm *res = localtime(&secsSinceEpoch)) {
local = *res;
valid = true;
}
#endif
}
if (valid) {
*localDate = QDate(qYearFromTmYear(local.tm_year), local.tm_mon + 1, local.tm_mday);
*localTime = QTime(local.tm_hour, local.tm_min, local.tm_sec, msec);
if (daylightStatus) {
if (local.tm_isdst > 0)
*daylightStatus = QDateTimePrivate::DaylightTime;
else if (local.tm_isdst < 0)
*daylightStatus = QDateTimePrivate::UnknownDaylightTime;
else
*daylightStatus = QDateTimePrivate::StandardTime;
}
#if defined(Q_OS_WIN)
return !localtime_s(local, &utc);
#elif QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS)
// Use the reentrant version of localtime() where available
// as is thread-safe and doesn't use a shared static data area
if (tm *res = localtime_r(&utc, local)) {
Q_ASSERT(res == local);
return true;
} else {
*localDate = QDate();
*localTime = QTime();
if (daylightStatus)
*daylightStatus = QDateTimePrivate::UnknownDaylightTime;
return false;
}
return false;
#else
// Returns shared static data which may be overwritten at any time
// So copy the result asap
if (tm *res = localtime(&utc)) {
*local = *res;
return true;
}
return false;
#endif
}
// Calls the platform variant of localtime() for the given utcMillis, and
// returns the local milliseconds, offset from UTC and DST status.
QDateTimePrivate::ZoneState utcToLocal(qint64 utcMillis)
{
const int signFix = utcMillis % MSECS_PER_SEC && utcMillis < 0 ? 1 : 0;
const time_t epochSeconds = utcMillis / MSECS_PER_SEC - signFix;
const int msec = utcMillis % MSECS_PER_SEC + signFix * MSECS_PER_SEC;
Q_ASSERT(msec >= 0 && msec < MSECS_PER_SEC);
if (qint64(epochSeconds) * MSECS_PER_SEC + msec != utcMillis)
return {utcMillis};
tm local;
if (!qtLocalTime(epochSeconds, &local))
return {utcMillis};
qint64 jd;
if (Q_UNLIKELY(!QGregorianCalendar::julianFromParts(qYearFromTmYear(local.tm_year),
local.tm_mon + 1, local.tm_mday, &jd))) {
return {utcMillis};
}
const qint64 daySeconds
= local.tm_hour * SECS_PER_HOUR + local.tm_min * SECS_PER_MIN + local.tm_sec;
Q_ASSERT(0 <= daySeconds && daySeconds < SECS_PER_DAY);
qint64 localSeconds, localMillis;
if (Q_UNLIKELY(
mul_overflow(jd - JULIAN_DAY_FOR_EPOCH, std::integral_constant<qint64, SECS_PER_DAY>(),
&localSeconds)
|| add_overflow(localSeconds, daySeconds, &localSeconds)
|| mul_overflow(localSeconds, std::integral_constant<qint64, MSECS_PER_SEC>(),
&localMillis)
|| add_overflow(localMillis, qint64(msec), &localMillis))) {
return {utcMillis};
}
const auto dst
= local.tm_isdst ? QDateTimePrivate::DaylightTime : QDateTimePrivate::StandardTime;
return { localMillis, int(localSeconds - epochSeconds), dst };
}
}
// Converts milliseconds since the start of 1970 into a date and/or time:
@ -2892,56 +2901,49 @@ static int systemTimeYearMatching(int year)
// Sets up d and status to represent local time at the given UTC msecs since epoch:
QDateTimePrivate::ZoneState QDateTimePrivate::expressUtcAsLocal(qint64 utcMSecs)
{
// 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)))
result = utcToLocal(utcMSecs);
if (result.valid)
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.
}
// 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.
} else if (const auto sys = QTimeZone::systemTimeZone(); sys.isValid()) {
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(utcMSecs, &utcDate, &utcTime);
int year, month, day;
utcDate.getDate(&year, &month, &day);
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));
}
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)) {
// Kludge
// Do the conversion in a year with the same days of the week, so DST
// dates might be right, and adjust by the number of days that was off:
const qint64 jd = msecsToJulianDay(utcMSecs);
const auto ymd = QGregorianCalendar::partsFromJulian(jd);
qint64 fakeJd, diffMillis, fakeUtc;
if (Q_UNLIKELY(!QGregorianCalendar::julianFromParts(systemTimeYearMatching(ymd.year),
ymd.month, ymd.day, &fakeJd)
|| mul_overflow(jd - fakeJd, std::integral_constant<qint64, MSECS_PER_DAY>(),
&diffMillis)
|| sub_overflow(utcMSecs, diffMillis, &fakeUtc))) {
return result;
}
result.offset = (result.when - utcMSecs) / MSECS_PER_SEC;
// result.dst already set
result.valid = true;
result = utcToLocal(fakeUtc);
// Now correct result.when for the use of the fake date:
if (!result.valid || add_overflow(result.when, diffMillis, &result.when)) {
// If utcToLocal() failed, its return has the fake when; restore utcMSecs.
// Fail on overflow, but preserve offset and DST-ness.
result.when = utcMSecs;
result.valid = false;
}
return result;
}