summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEdward Welbourne <edward.welbourne@qt.io>2022-05-10 19:35:16 +0200
committerEdward Welbourne <edward.welbourne@qt.io>2022-05-20 13:08:54 +0200
commit1e295cff491b30a88367259115fe429f18bab5f7 (patch)
tree54c03cb2d9aab019b840f0f12786ba086cd46b44
parenta534483518252248e68f44fa1913192aa203466d (diff)
Replace qt_mktime(), avoiding out-parameters and conversions
Since qt_mktime() took QDate and QTime in/out parameters, its callers had to convert the milliseconds they had to that form and back again after. Since it combined optional look-up of zone abbreviation with determination of offset and DST-ness (both ignore when the abbreviation was asked for) it made sense to split it in two. Each takes the millis and whatever knowledge we have of DST. One returns tha abbreviation; the other returns a QDTP::ZoneState for the caller that'll be returning that. Outside the system zone, when we have to fall back to a kludge, their callers can now use the Julian Day number change for its fake date to directly adjust the millis there and back (effectively inlining former conversions to-and-from date and time representation, but optimising out some of the work). Change-Id: I7aa4583171f77fc0f4aa80c8f564e76995ca69d2 Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
-rw-r--r--src/corelib/time/qdatetime.cpp246
1 files changed, 131 insertions, 115 deletions
diff --git a/src/corelib/time/qdatetime.cpp b/src/corelib/time/qdatetime.cpp
index 959967b510..817f37d4a4 100644
--- a/src/corelib/time/qdatetime.cpp
+++ b/src/corelib/time/qdatetime.cpp
@@ -2457,12 +2457,11 @@ static constexpr int qYearFromTmYear(int year) { return year + (year < -1899 ? 1
It might return -1 because we're looking at the last second of 1969 and
mktime does support times before 1970 (POSIX says "If the year is <1970 or
the value is negative, the relationship is undefined" and MS rejects the
- value, consistent with that; so we don't call qt_mktime() on MS in this case
- and can't get -1 unless it's a real error). However, on UNIX, that's -1 UTC
- time and all we know, aside from mktime's return, is the local time. (We
- could check errno, but we call mktime from within a
- qt_scoped_lock(QBasicMutex), whose unlocking and destruction of the locker
- might frob errno.)
+ value, consistent with that; so we don't call mktime() on MS in this case and
+ can't get -1 unless it's a real error). However, on UNIX, that's -1 UTC time
+ and all we know, aside from mktime's return, is the local time. (We could
+ check errno, but we call mktime from within a qt_scoped_lock(QBasicMutex),
+ whose unlocking and destruction of the locker might frob errno.)
We can assume the zone offset is a multiple of five minutes and less than a
day, so this can only arise for the last second of a minute that differs from
@@ -2514,8 +2513,8 @@ static inline bool meansEnd1969(tm *local)
either by correcting it - in which case it adjusts the struct tm to reflect
the same time, but represented using the right tm_isdst, so typically an
hour earlier or later - or by returning -1. When this happens, the way we
- actually use qt_mktime(), we don't want a revised time with corrected DST,
- we want the original time with its corrected DST; so we retry the call, this
+ actually use mktime(), we don't want a revised time with corrected DST, we
+ want the original time with its corrected DST; so we retry the call, this
time not claiming to know the DST-ness.
POSIX doesn't actually say what to do if the specified struct tm describes a
@@ -2565,75 +2564,6 @@ static inline bool callMkTime(tm *local, time_t *secs)
return good;
}
-// Calls the platform variant of mktime for the given date, time and
-// daylightStatus, and updates the date, time, daylightStatus and abbreviation
-// with the returned values. If the date falls outside the time_t range
-// supported by mktime, then date/time will not be updated and *ok is set false.
-static qint64 qt_mktime(QDate *date, QTime *time, QDateTimePrivate::DaylightStatus *daylightStatus,
- QString *abbreviation, bool *ok)
-{
- Q_ASSERT(ok && date && time);
- qint64 msec = time->msec();
- Q_ASSERT(msec < MSECS_PER_SEC);
- int yy, mm, dd;
- date->getDate(&yy, &mm, &dd);
-
- tm local = {};
- local.tm_sec = time->second();
- local.tm_min = time->minute();
- local.tm_hour = time->hour();
- local.tm_mday = dd;
- local.tm_mon = mm - 1;
- local.tm_year = tmYearFromQYear(yy);
- local.tm_isdst = daylightStatus ? int(*daylightStatus) : -1;
-
- time_t secsSinceEpoch;
- if (!callMkTime(&local, &secsSinceEpoch)) {
- *date = QDate();
- *time = QTime();
- if (daylightStatus)
- *daylightStatus = QDateTimePrivate::UnknownDaylightTime;
- if (abbreviation)
- *abbreviation = QString();
- *ok = false;
- return 0;
- }
-
- // Store date and time:
- *date = QDate(qYearFromTmYear(local.tm_year), local.tm_mon + 1, local.tm_mday);
- *time = QTime(local.tm_hour, local.tm_min, local.tm_sec, msec);
-
- // Store zone details:
- if (local.tm_isdst > 0) {
- if (daylightStatus)
- *daylightStatus = QDateTimePrivate::DaylightTime;
- if (abbreviation)
- *abbreviation = qt_tzname(QDateTimePrivate::DaylightTime);
- } else {
- if (daylightStatus) {
- *daylightStatus = (local.tm_isdst == 0
- ? QDateTimePrivate::StandardTime
- : QDateTimePrivate::UnknownDaylightTime);
- }
- if (abbreviation)
- *abbreviation = qt_tzname(QDateTimePrivate::StandardTime);
- }
-
- // Compute final UTC milliseconds since epoch:
- if (secsSinceEpoch < 0 && msec > 0) {
- secsSinceEpoch++;
- msec -= MSECS_PER_SEC;
- }
- qint64 millis;
- const bool overflow =
- mul_overflow(qint64(secsSinceEpoch),
- std::integral_constant<qint64, MSECS_PER_SEC>(), &millis)
- || add_overflow(millis, msec, &msec);
- *ok = !overflow;
-
- return msec;
-}
-
namespace {
bool qtLocalTime(time_t utc, struct tm *local)
{
@@ -2734,6 +2664,84 @@ static qint64 timeToMSecs(QDate date, QTime time)
return msecs;
}
+namespace {
+
+struct tm timeToTm(qint64 localDay, int secs, QDateTimePrivate::DaylightStatus dst)
+{
+ const auto ymd = QGregorianCalendar::partsFromJulian(JULIAN_DAY_FOR_EPOCH + localDay);
+ struct tm local = {};
+ local.tm_year = tmYearFromQYear(ymd.year);
+ local.tm_mon = ymd.month - 1;
+ local.tm_mday = ymd.day;
+ local.tm_hour = secs / SECS_PER_HOUR;
+ local.tm_min = (secs % SECS_PER_HOUR) / SECS_PER_MIN;
+ local.tm_sec = (secs % SECS_PER_MIN);
+ local.tm_isdst = int(dst);
+ return local;
+}
+
+static QString localTimeAbbbreviationAt(qint64 local, QDateTimePrivate::DaylightStatus dst)
+{
+ const qint64 localDays = QRoundingDown::qDiv(local, MSECS_PER_DAY);
+ qint64 millis = local - localDays * MSECS_PER_DAY;
+ Q_ASSERT(0 <= millis && millis < MSECS_PER_DAY); // Definition of QRD::qDiv.
+ struct tm tmLocal = timeToTm(localDays, int(millis / MSECS_PER_SEC), dst);
+ time_t utcSecs;
+ if (!callMkTime(&tmLocal, &utcSecs))
+ return {};
+ return qt_tzname(tmLocal.tm_isdst > 0 ? QDateTimePrivate::DaylightTime
+ : QDateTimePrivate::StandardTime);
+}
+
+static QDateTimePrivate::ZoneState mapLocalTime(qint64 local, QDateTimePrivate::DaylightStatus dst)
+{
+ const qint64 localDays = QRoundingDown::qDiv(local, MSECS_PER_DAY);
+ qint64 millis = local - localDays * MSECS_PER_DAY;
+ Q_ASSERT(0 <= millis && millis < MSECS_PER_DAY); // Definition of QRD::qDiv.
+ struct tm tmLocal = timeToTm(localDays, int(millis / MSECS_PER_SEC), dst);
+ millis %= MSECS_PER_SEC;
+ time_t utcSecs;
+ if (!callMkTime(&tmLocal, &utcSecs))
+ return {local};
+
+ // TODO: for glibc, we could use tmLocal.tm_gmtoff
+ // That would give us offset directly, hence localSecs = offset + utcSecs
+ // Provisional offset, until we have a revised localSeconds:
+ int offset = QRoundingDown::qDiv(local, MSECS_PER_SEC) - utcSecs;
+ dst = tmLocal.tm_isdst > 0 ? QDateTimePrivate::DaylightTime : QDateTimePrivate::StandardTime;
+ qint64 jd;
+ if (Q_UNLIKELY(!QGregorianCalendar::julianFromParts(
+ qYearFromTmYear(tmLocal.tm_year), tmLocal.tm_mon + 1, tmLocal.tm_mday,
+ &jd))) {
+ return {local, offset, dst, false};
+ }
+ qint64 daySecs = tmLocal.tm_sec + 60 * (tmLocal.tm_min + 60 * tmLocal.tm_hour);
+ Q_ASSERT(0 <= daySecs && daySecs < SECS_PER_DAY);
+ if (daySecs > 0 && jd < JULIAN_DAY_FOR_EPOCH) {
+ ++jd;
+ daySecs -= SECS_PER_DAY;
+ }
+ qint64 localSecs;
+ if (Q_UNLIKELY(mul_overflow(jd - JULIAN_DAY_FOR_EPOCH,
+ std::integral_constant<qint64, SECS_PER_DAY>(), &localSecs)
+ || add_overflow(localSecs, daySecs, &localSecs))) {
+ return {local, offset, dst, false};
+ }
+ offset = localSecs - utcSecs;
+
+ if (localSecs < 0 && millis > 0) {
+ ++localSecs;
+ millis -= MSECS_PER_SEC;
+ }
+ qint64 revised;
+ const bool overflow =
+ mul_overflow(localSecs, std::integral_constant<qint64, MSECS_PER_SEC>(), &revised)
+ || add_overflow(revised, millis, &revised);
+ return {overflow ? local : revised, offset, dst, !overflow};
+}
+
+} // namespace
+
/*!
\internal
Determine the range of the system time_t functions.
@@ -2938,15 +2946,28 @@ QDateTimePrivate::ZoneState QDateTimePrivate::expressUtcAsLocal(qint64 utcMSecs)
return result;
}
+static auto millisToWithinRange(qint64 millis)
+{
+ struct R {
+ qint64 shifted = 0;
+ bool good = false;
+ } result;
+ qint64 jd = msecsToJulianDay(millis), fakeJd, diffMillis;
+ auto ymd = QGregorianCalendar::partsFromJulian(jd);
+ result.good = QGregorianCalendar::julianFromParts(systemTimeYearMatching(ymd.year),
+ ymd.month, ymd.day, &fakeJd)
+ && !mul_overflow(fakeJd - jd, std::integral_constant<qint64, MSECS_PER_DAY>(),
+ &diffMillis)
+ && !add_overflow(diffMillis, millis, &result.shifted);
+ return result;
+}
+
QString QDateTimePrivate::localNameAtMillis(qint64 millis, DaylightStatus dst)
{
QString abbreviation;
- bool valid = false;
if (millisInSystemRange(millis, MSECS_PER_DAY)) {
- QDate dt = msecsToDate(millis);
- QTime tm = msecsToTime(millis);
- qt_mktime(&dt, &tm, &dst, &abbreviation, &valid);
- if (valid)
+ abbreviation = localTimeAbbbreviationAt(millis, dst);
+ if (!abbreviation.isEmpty())
return abbreviation;
}
@@ -2960,14 +2981,15 @@ QString QDateTimePrivate::localNameAtMillis(qint64 millis, DaylightStatus dst)
return sys.d->abbreviation(state.when - state.offset * MSECS_PER_SEC);
}
#endif // timezone
+
// Kludge
- // Use existing method to fake the conversion.
- QTime tm = msecsToTime(millis);
- auto ymd = QGregorianCalendar::partsFromJulian(msecsToJulianDay(millis));
- QDate fakeDate(systemTimeYearMatching(ymd.year), ymd.month, ymd.day);
- qt_mktime(&fakeDate, &tm, &dst, &abbreviation, &valid);
- Q_ASSERT(valid);
- return abbreviation;
+ // Use a time in the system range with the same day-of-week pattern to its year:
+ auto fake = millisToWithinRange(millis);
+ if (Q_LIKELY(fake.good))
+ return localTimeAbbbreviationAt(fake.shifted, dst);
+
+ // Overflow, apparently.
+ return {};
}
// Determine the offset from UTC at the given local time as millis.
@@ -2975,16 +2997,10 @@ QDateTimePrivate::ZoneState QDateTimePrivate::localStateAtMillis(qint64 millis,
{
// First, if millis is within a day of the viable range, try mktime() in
// case it does fall in the range and gets useful information:
- bool valid = false;
if (millisInSystemRange(millis, MSECS_PER_DAY)) {
- QDate dt = msecsToDate(millis);
- QTime tm = msecsToTime(millis);
- const qint64 utc = qt_mktime(&dt, &tm, &dst, nullptr, &valid);
- if (valid && millisInSystemRange(utc)) {
- // mktime() worked and the result falls in its valid range, so use its results
- const qint64 when = timeToMSecs(dt, tm);
- return { when, int((when - utc) / MSECS_PER_SEC), dst };
- }
+ auto result = mapLocalTime(millis, dst);
+ if (result.valid)
+ return result;
}
// Otherwise, outside the system range.
@@ -2996,24 +3012,24 @@ QDateTimePrivate::ZoneState QDateTimePrivate::localStateAtMillis(qint64 millis,
#endif // timezone
// Kludge
- // Use existing method to fake the conversion.
- QTime tm = QTime::fromMSecsSinceStartOfDay(QRoundingDown::qMod(millis, MSECS_PER_DAY));
- const qint64 jd = msecsToJulianDay(millis);
- auto ymd = QGregorianCalendar::partsFromJulian(jd);
- QDate fakeDate(systemTimeYearMatching(ymd.year), ymd.month, ymd.day);
- const qint64 fakeDiff = fakeDate.daysTo(QDate::fromJulianDay(jd));
- const qint64 fakeUtc = qt_mktime(&fakeDate, &tm, &dst, nullptr, &valid);
- Q_ASSERT(valid);
- const qint64 fakeMillis = timeToMSecs(fakeDate, tm);
- const int offset = (fakeMillis - fakeUtc) / MSECS_PER_SEC;
- qint64 diffMsecs;
- // Adjust fakeMillis by the fake diff, to get revised millis:
- if (mul_overflow(fakeDiff, std::integral_constant<qint64, MSECS_PER_DAY>(), &diffMsecs)
- || add_overflow(fakeMillis, diffMsecs, &millis)) {
- using Bound = std::numeric_limits<qint64>;
- return {fakeDiff < 0 ? Bound::min() : Bound::max(), offset, dst, false};
+ // Use a time in the system range with the same day-of-week pattern to its year:
+ auto fake = millisToWithinRange(millis);
+ if (Q_LIKELY(fake.good)) {
+ auto result = mapLocalTime(fake.shifted, dst);
+ if (result.valid) {
+ qint64 adjusted;
+ if (Q_UNLIKELY(add_overflow(result.when, millis - fake.shifted, &adjusted))) {
+ using Bound = std::numeric_limits<qint64>;
+ adjusted = millis < fake.shifted ? Bound::min() : Bound::max();
+ }
+ result.when = adjusted;
+ } else {
+ result.when = millis;
+ }
+ return result;
}
- return ZoneState(millis, offset, dst);
+ // Overflow, apparently.
+ return {millis};
}
#if QT_CONFIG(timezone)