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
parent
6f1eb8f127
commit
b931688a1e
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue