summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEdward Welbourne <edward.welbourne@qt.io>2022-05-05 13:39:11 +0200
committerEdward Welbourne <edward.welbourne@qt.io>2022-05-20 13:07:51 +0200
commitb931688a1e52b9e9bdf3144d392e7ee29e8c3ead (patch)
tree4a26a809a8d92ad9dadc5a016dd49b000324521d
parent6f1eb8f127ac93e764b61a2aa31d41f64df9b87a (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.cpp172
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;
}