diff options
author | Edward Welbourne <edward.welbourne@qt.io> | 2022-05-05 13:39:11 +0200 |
---|---|---|
committer | Edward Welbourne <edward.welbourne@qt.io> | 2022-05-20 13:07:51 +0200 |
commit | b931688a1e52b9e9bdf3144d392e7ee29e8c3ead (patch) | |
tree | 4a26a809a8d92ad9dadc5a016dd49b000324521d | |
parent | 6f1eb8f127ac93e764b61a2aa31d41f64df9b87a (diff) |
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>
-rw-r--r-- | src/corelib/time/qdatetime.cpp | 172 |
1 files changed, 87 insertions, 85 deletions
diff --git a/src/corelib/time/qdatetime.cpp b/src/corelib/time/qdatetime.cpp index 589ba0f1ce..5c96927dbf 100644 --- a/src/corelib/time/qdatetime.cpp +++ b/src/corelib/time/qdatetime.cpp @@ -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) -{ - 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; - +namespace { +bool qtLocalTime(time_t utc, struct tm *local) +{ // 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; +#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; + } + return false; #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; - } + // 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}; } - 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; - } - return true; - } else { - *localDate = QDate(); - *localTime = QTime(); - if (daylightStatus) - *daylightStatus = QDateTimePrivate::UnknownDaylightTime; - return false; + 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)) { +#endif // timezone + + // 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; } |