Revision control
++ // Copyright (C) 2022 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++ #include "qlocaltime_p.h"
++ #include "qplatformdefs.h"
++
++ #include "private/qcalendarmath_p.h"
++ #if QT_CONFIG(datetimeparser)
++ #include "private/qdatetimeparser_p.h"
++ #endif
++ #include "private/qgregoriancalendar_p.h"
++ #include "private/qnumeric_p.h"
++ #include "private/qtenvironmentvariables_p.h"
++ #if QT_CONFIG(timezone)
++ #include "private/qtimezoneprivate_p.h"
++ #endif
++
++ #include <QtCore/q20utility.h>
++
++ #include <time.h>
++ #ifdef Q_OS_WIN
++ # include <qt_windows.h>
++ #endif
++
++ #ifdef __GLIBC__ // Extends struct tm with some extra fields:
++ #define HAVE_TM_GMTOFF // tm_gmtoff is the UTC offset.
++ #define HAVE_TM_ZONE // tm_zone is the zone abbreviation.
++ #endif
++
++ QT_BEGIN_NAMESPACE
++
++ using namespace QtPrivate::DateTimeConstants;
++ namespace {
++ /*
++ Qt represents n BCE as -n, whereas struct tm's tm_year field represents a
++ year by the number of years after (negative for before) 1900, so that 1+m
++ BCE is -1900 -m; so treating 1 BCE as 0 CE. We thus shift by different
++ offsets depending on whether the year is BCE or CE.
++ */
++ constexpr int tmYearFromQYear(int year) { return year - (year < 0 ? 1899 : 1900); }
++ constexpr int qYearFromTmYear(int year) { return year + (year < -1899 ? 1899 : 1900); }
++
++ constexpr inline qint64 tmSecsWithinDay(const struct tm &when)
++ {
++ return (when.tm_hour * MINS_PER_HOUR + when.tm_min) * SECS_PER_MIN + when.tm_sec;
++ }
++
++ /* Call mktime() and make sense of the result.
++
++ This packages the call to mktime() with the needed determination of whether
++ that succeeded and whether the call has materially perturbed, including
++ normalizing, the struct tm it was passed (as opposed to merely filling in
++ details).
++ */
++ class MkTimeResult
++ {
++ // mktime()'s return on error; or last second of 1969 UTC:
++ static constexpr time_t maybeError = -1;
++ inline bool meansEnd1969();
++ bool changed(const struct tm &prior) const;
++
++ public:
++ struct tm local = {}; // Describes the local time in familiar form.
++ time_t utcSecs = maybeError; // Seconds since UTC epoch.
++ bool good = false; // Ignore the rest unless this is true.
++ bool adjusted = true; // Is local at odds with prior ?
++ MkTimeResult() { local.tm_isdst = -1; }
++
++ // Note: the calls to qMkTime() and meansEnd1969() potentially modify local.
++ explicit MkTimeResult(const struct tm &prior)
++ : local(prior), utcSecs(qMkTime(&local)),
++ good(utcSecs != maybeError || meansEnd1969()),
++ adjusted(changed(prior))
++ {}
++ };
++
++ /* If mktime() returns -1, is it really an error ?
++
++ 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 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 time-zone offsets are less than a day, so this can only arise
++ if the struct tm describes either the last day of 1969 or the first day of
++ 1970. When we do know the offset (a glibc extension supplies it as a member
++ of struct tm), we can determine whether we're on the last second of the day,
++ refining that check. That makes for a cheap pre-test; if it holds, we can ask
++ mktime() about the preceding second; if it gives us -2, then the -1 we
++ originally saw is not (or at least didn't need to be) an error. We can then
++ synthesize a corrected value for local using the -2 result.
++ */
++ inline bool MkTimeResult::meansEnd1969()
++ {
++ #ifdef Q_OS_WIN
++ return false;
++ #else
++ if (local.tm_year < 69 || local.tm_year > 70
++ # ifdef HAVE_TM_GMTOFF
++ // Africa/Monrovia had offset 00:44:30 at the epoch, so (although all
++ // other zones' offsets were round multiples of five minutes) we need
++ // the offset to determine whether the time might match:
++ || (tmSecsWithinDay(local) - local.tm_gmtoff + 1) % SECS_PER_DAY
++ # endif
++ || (local.tm_year == 69 // ... and less than a day:
++ ? local.tm_mon < 11 || local.tm_mday < 31
++ : local.tm_mon > 0 || local.tm_mday > 1)) {
++ return false;
++ }
++ struct tm copy = local;
++ copy.tm_sec--; // Preceding second should get -2, not -1
++ if (qMkTime(©) != -2)
++ return false;
++ // The original call to qMkTime() may have returned -1 as failure, not
++ // updating local, even though it could have; so fake it here. Assumes there
++ // was no transition in the last minute of the day !
++ local = copy;
++ local.tm_sec++; // Advance back to the intended second
++ return true;
++ #endif
++ }
++
++ bool MkTimeResult::changed(const struct tm &prior) const
++ {
++ // If mktime() has been passed a copy of prior and local is its value on
++ // return, this checks whether mktime() has made a material change
++ // (including normalization) to the value, as opposed to merely filling in
++ // the fields that it's specified to fill in. It returns true if there has
++ // been any material change.
++ return !(prior.tm_year == local.tm_year && prior.tm_mon == local.tm_mon
++ && prior.tm_mday == local.tm_mday && prior.tm_hour == local.tm_hour
++ && prior.tm_min == local.tm_min && prior.tm_sec == local.tm_sec
++ && (prior.tm_isdst == -1
++ ? local.tm_isdst >= 0 : prior.tm_isdst == local.tm_isdst));
++ }
++
++ struct tm timeToTm(qint64 localDay, int secs)
++ {
++ Q_ASSERT(0 <= secs && secs < SECS_PER_DAY);
++ 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 / 3600;
++ local.tm_min = (secs % 3600) / 60;
++ local.tm_sec = (secs % 60);
++ local.tm_isdst = -1;
++ return local;
++ }
++
++ // Transitions account for a small fraction of 1% of the time.
++ // So mark functions only used in handling them as cold.
++ Q_DECL_COLD_FUNCTION
++ struct tm matchYearMonth(struct tm when, const struct tm &base)
++ {
++ // Adjust *when to be a denormal representation of the same point in time
++ // but with tm_year and tm_mon the same as base. In practice this will
++ // represent an adjacent month, so don't worry too much about optimising for
++ // any other case; we almost certainly run zero or one iteration of one of
++ // the year loops then zero or one iteration of one of the month loops.
++ while (when.tm_year > base.tm_year) {
++ --when.tm_year;
++ when.tm_mon += 12;
++ }
++ while (when.tm_year < base.tm_year) {
++ ++when.tm_year;
++ when.tm_mon -= 12;
++ }
++ Q_ASSERT(when.tm_year == base.tm_year);
++ while (when.tm_mon > base.tm_mon) {
++ const auto yearMon = QRoundingDown::qDivMod<12>(when.tm_mon);
++ int year = yearMon.quotient;
++ // We want the month before's Qt month number, which is the tm_mon mod 12:
++ int month = yearMon.remainder;
++ if (month == 0) {
++ --year;
++ month = 12;
++ }
++ year += when.tm_year;
++ when.tm_mday += QGregorianCalendar::monthLength(month, qYearFromTmYear(year));
++ --when.tm_mon;
++ }
++ while (when.tm_mon < base.tm_mon) {
++ const auto yearMon = QRoundingDown::qDivMod<12>(when.tm_mon);
++ // Qt month number is offset from tm_mon by one:
++ when.tm_mday -= QGregorianCalendar::monthLength(
++ yearMon.remainder + 1, qYearFromTmYear(yearMon.quotient + when.tm_year));
++ ++when.tm_mon;
++ }
++ Q_ASSERT(when.tm_mon == base.tm_mon);
++ return when;
++ }
++
++ Q_DECL_COLD_FUNCTION
++ struct tm adjacentDay(struct tm when, int dayStep)
++ {
++ // Before we adjust it, when is a return from timeToTm(), so in normal form.
++ Q_ASSERT(dayStep * dayStep == 1);
++ when.tm_mday += dayStep;
++ // That may have bumped us across a month boundary or even a year one.
++ // So now we normalize it.
++
++ if (dayStep < 0) {
++ if (when.tm_mday <= 0) {
++ // Month before's day-count; but tm_mon's value is one less than Qt's
++ // month numbering so, before we decrement it, it has the value we need,
++ // unless it's 0.
++ int daysInMonth = when.tm_mon
++ ? QGregorianCalendar::monthLength(when.tm_mon, qYearFromTmYear(when.tm_year))
++ : QGregorianCalendar::monthLength(12, qYearFromTmYear(when.tm_year - 1));
++ when.tm_mday += daysInMonth;
++ if (--when.tm_mon < 0) {
++ --when.tm_year;
++ when.tm_mon = 11;
++ }
++ Q_ASSERT(when.tm_mday >= 1);
++ }
++ } else if (when.tm_mday > 28) {
++ // We have to wind through months one at a time, since their lengths vary.
++ int daysInMonth = QGregorianCalendar::monthLength(
++ when.tm_mon + 1, qYearFromTmYear(when.tm_year));
++ if (when.tm_mday > daysInMonth) {
++ when.tm_mday -= daysInMonth;
++ if (++when.tm_mon > 11) {
++ ++when.tm_year;
++ when.tm_mon = 0;
++ }
++ Q_ASSERT(when.tm_mday <= QGregorianCalendar::monthLength(
++ when.tm_mon + 1, qYearFromTmYear(when.tm_year)));
++ }
++ }
++ return when;
++ }
++
++ Q_DECL_COLD_FUNCTION
++ qint64 secondsBetween(const struct tm &start, const struct tm &stop)
++ {
++ // Nominal difference between start and stop, in seconds (negative if start
++ // is after stop); may differ from actual UTC difference if there's a
++ // transition between them.
++ struct tm from = matchYearMonth(start, stop);
++ qint64 diff = stop.tm_mday - from.tm_mday; // in days
++ diff = diff * 24 + stop.tm_hour - from.tm_hour; // in hours
++ diff = diff * 60 + stop.tm_min - from.tm_min; // in minutes
++ return diff * 60 + stop.tm_sec - from.tm_sec; // in seconds
++ }
++
++ Q_DECL_COLD_FUNCTION
++ MkTimeResult hopAcrossGap(const MkTimeResult &outside, const struct tm &base)
++ {
++ // base fell in a gap; outside is one resolution
++ // This returns the other resolution, if possible.
++ const qint64 shift = secondsBetween(outside.local, base);
++ struct tm across;
++ // Shift is the nominal time adjustment between outside and base; now obtain
++ // the actual time that far from outside:
++ if (qLocalTime(outside.utcSecs + shift, &across)) {
++ const qint64 wider = secondsBetween(outside.local, across);
++ // That should be bigger than shift (typically by a factor of two), in
++ // the same direction:
++ if (shift > 0 ? wider > shift : wider < shift) {
++ MkTimeResult result(across);
++ if (result.good && !result.adjusted)
++ return result;
++ }
++ }
++ // This can surely only arise if the other resolution lies outside the
++ // time_t-range supported by the system functions.
++ return {};
++ }
++
++ Q_DECL_COLD_FUNCTION
++ MkTimeResult resolveRejected(struct tm base, MkTimeResult result,
++ QDateTimePrivate::TransitionOptions resolve)
++ {
++ // May result from a time outside the supported range of system time_t
++ // functions, or from a gap (on a platform where mktime() rejects them).
++ // QDateTime filters on times well outside the supported range, but may
++ // pass values only slightly outside the range.
++
++ // The easy case - no need to find a resolution anyway:
++ if (!resolve.testAnyFlags(QDateTimePrivate::GapMask))
++ return {};
++
++ constexpr time_t twoDaysInSeconds = 2 * 24 * 60 * 60;
++ // Bracket base, one day each side (in case the zone skipped a whole day):
++ MkTimeResult early(adjacentDay(base, -1));
++ MkTimeResult later(adjacentDay(base, +1));
++ if (!early.good || !later.good) // Assume out of range, rather than gap.
++ return {};
++
++ // OK, looks like a gap.
++ Q_ASSERT(twoDaysInSeconds + early.utcSecs > later.utcSecs);
++ result.adjusted = true;
++
++ // Extrapolate backwards from later if this option is set:
++ QDateTimePrivate::TransitionOption beforeLater = QDateTimePrivate::GapUseBefore;
++ if (resolve.testFlag(QDateTimePrivate::FlipForReverseDst)) {
++ // Reverse DST has DST before a gap and not after:
++ if (early.local.tm_isdst == 1 && !later.local.tm_isdst)
++ beforeLater = QDateTimePrivate::GapUseAfter;
++ }
++ if (resolve.testFlag(beforeLater)) // Result will be before the gap:
++ result.utcSecs = later.utcSecs - secondsBetween(base, later.local);
++ else // Result will be after the gap:
++ result.utcSecs = early.utcSecs + secondsBetween(early.local, base);
++
++ if (!qLocalTime(result.utcSecs, &result.local)) // Abandon hope.
++ return {};
++
++ return result;
++ }
++
++ Q_DECL_COLD_FUNCTION
++ bool preferAlternative(QDateTimePrivate::TransitionOptions resolve,
++ // is_dst flags of incumbent and an alternative:
++ int gotDst, int altDst,
++ // True precisely if alternative selects a later UTC time:
++ bool altIsLater,
++ // True for a gap, false for a fold:
++ bool inGap)
++ {
++ // If resolve has this option set, prefer the later candidate, else the earlier:
++ QDateTimePrivate::TransitionOption preferLater = inGap ? QDateTimePrivate::GapUseAfter
++ : QDateTimePrivate::FoldUseAfter;
++ if (resolve.testFlag(QDateTimePrivate::FlipForReverseDst)) {
++ // gotDst and altDst are {-1: unknown, 0: standard, 1: daylight-saving}
++ // So gotDst ^ altDst is 1 precisely if exactly one candidate thinks it's DST.
++ if ((altDst ^ gotDst) == 1) {
++ // In this case, we can tell whether we have reversed DST: that's a
++ // gap with DST before it or a fold with DST after it.
++ #if 1
++ const bool isReversed = (altDst == 1) != (altIsLater == inGap);
++ #else // Pedagogic version of the same thing:
++ bool isReversed;
++ if (altIsLater == inGap) // alt is after a gap or before a fold, so summer-time
++ isReversed = altDst != 1; // flip if summer-time isn't DST
++ else // alt is before a gap or after a fold, so winter-time
++ isReversed = altDst == 1; // flip if winter-time is DST
++ #endif
++ if (isReversed) {
++ preferLater = inGap ? QDateTimePrivate::GapUseBefore
++ : QDateTimePrivate::FoldUseBefore;
++ }
++ } // Otherwise, we can't tell, so assume not.
++ }
++ return resolve.testFlag(preferLater) == altIsLater;
++ }
++
++ /*
++ Determine UTC time and offset, if possible, at a given local time.
++
++ The local time is specified as a number of seconds since the epoch (so, in
++ effect, a time_t, albeit delivered as qint64). If the specified local time
++ falls in a transition, resolve determines what to do.
++
++ If the specified local time is outside what the system time_t APIs will
++ handle, this fails.
++ */
++ MkTimeResult resolveLocalTime(qint64 local, QDateTimePrivate::TransitionOptions resolve)
++ {
++ const auto localDaySecs = QRoundingDown::qDivMod<SECS_PER_DAY>(local);
++ struct tm base = timeToTm(localDaySecs.quotient, localDaySecs.remainder);
++
++ // Get provisional result (correct > 99.9 % of the time):
++ MkTimeResult result(base);
++
++ // Our callers (mostly) deal with questions of being within the range that
++ // system time_t functions can handle, and timeToTm() gave us data in
++ // normalized form, so the only excuse for !good or a change to the HH:mm:ss
++ // fields (aside from being at the boundary of time_t's supported range) is
++ // that we hit a gap, although we have to handle these cases differently:
++ if (!result.good) {
++ // Rejected. The tricky case: maybe mktime() doesn't resolve gaps.
++ return resolveRejected(base, result, resolve);
++ } else if (result.local.tm_isdst < 0) {
++ // Apparently success without knowledge of whether this is DST or not.
++ // Should not happen, but that means our usual understanding of what the
++ // system is up to has gone out the window. So just let it be.
++ } else if (result.adjusted) {
++ // Shunted out of a gap.
++ if (!resolve.testAnyFlags(QDateTimePrivate::GapMask)) {
++ result = {};
++ return result;
++ }
++
++ // Try to obtain a matching point on the other side of the gap:
++ const MkTimeResult flipped = hopAcrossGap(result, base);
++ // Even if that failed, result may be the correct resolution
++
++ if (preferAlternative(resolve, result.local.tm_isdst, flipped.local.tm_isdst,
++ flipped.utcSecs > result.utcSecs, true)) {
++ // If hopAcrossGap() failed and we do need its answer, give up.
++ if (!flipped.good || flipped.adjusted)
++ return {};
++
++ // As resolution of local, flipped involves adjustment (across gap):
++ result = flipped;
++ result.adjusted = true;
++ }
++ } else if (resolve.testFlag(QDateTimePrivate::FlipForReverseDst)
++ // In fold, DST counts as before and standard as after -
++ // we may not need to check whether we're in a transition:
++ && resolve.testFlag(result.local.tm_isdst ? QDateTimePrivate::FoldUseBefore
++ : QDateTimePrivate::FoldUseAfter)) {
++ // We prefer DST or standard and got what we wanted, so we're good.
++ // As below, but we don't need to check, because we're on the side of
++ // the transition that it would select as valid, if we were near one.
++ // NB: this branch is routinely exercised, when QDT::Data::isShort()
++ // obliges us to rediscover an offsetFromUtc that ShortData has no space
++ // to store, as it does remember the DST status we got before.
++ } else {
++ // What we gave was valid. However, it might have been in a fall-back.
++ // If so, the same input but with tm_isdst flipped should also be valid.
++ struct tm copy = base;
++ copy.tm_isdst = !result.local.tm_isdst;
++ const MkTimeResult flipped(copy);
++ if (flipped.good && !flipped.adjusted) {
++ // We're in a fall-back
++ if (!resolve.testAnyFlags(QDateTimePrivate::FoldMask)) {
++ result = {};
++ return result;
++ }
++
++ // Work out which repeat to use:
++ if (preferAlternative(resolve, result.local.tm_isdst, flipped.local.tm_isdst,
++ flipped.utcSecs > result.utcSecs, false)) {
++ result = flipped;
++ }
++ } // else: not in a transition, nothing to worry about.
++ }
++ return result;
++ }
++
++ inline std::optional<qint64> tmToJd(const struct tm &date)
++ {
++ return QGregorianCalendar::julianFromParts(qYearFromTmYear(date.tm_year),
++ date.tm_mon + 1, date.tm_mday);
++ }
++
++ #define IC(N) std::integral_constant<qint64, N>()
++
++ // True if combining day and seconds overflows qint64; otherwise, sets *epochSeconds
++ inline bool daysAndSecondsOverflow(qint64 julianDay, qint64 daySeconds, qint64 *epochSeconds)
++ {
++ return qMulOverflow(julianDay - JULIAN_DAY_FOR_EPOCH, IC(SECS_PER_DAY), epochSeconds)
++ || qAddOverflow(*epochSeconds, daySeconds, epochSeconds);
++ }
++
++ // True if combining seconds and millis overflows; otherwise sets *epochMillis
++ inline bool secondsAndMillisOverflow(qint64 epochSeconds, qint64 millis, qint64 *epochMillis)
++ {
++ return qMulOverflow(epochSeconds, IC(MSECS_PER_SEC), epochMillis)
++ || qAddOverflow(*epochMillis, millis, epochMillis);
++ }
++
++ #undef IC
++
++ } // namespace
++
++ namespace QLocalTime {
++
++ #ifndef QT_BOOTSTRAPPED
++ // Even if local time is currently in DST, this returns the standard time offset
++ // (in seconds) nominally in effect at present:
++ int getCurrentStandardUtcOffset()
++ {
++ #ifdef Q_OS_WIN
++ TIME_ZONE_INFORMATION tzInfo;
++ if (GetTimeZoneInformation(&tzInfo) != TIME_ZONE_ID_INVALID) {
++ int bias = tzInfo.Bias; // In minutes.
++ // StandardBias is usually zero, but include it if given:
++ if (tzInfo.StandardDate.wMonth) // Zero month means ignore StandardBias.
++ bias += tzInfo.StandardBias;
++ // MS's bias is +ve in the USA, so minutes *behind* UTC - we want seconds *ahead*:
++ return -bias * SECS_PER_MIN;
++ }
++ #else
++ qTzSet();
++ const time_t curr = time(nullptr);
++ if (curr != -1) {
++ /* Set t to the UTC representation of curr; the time whose local
++ standard time representation coincides with that differs from curr by
++ local time's standard offset. Note that gmtime() leaves the tm_isdst
++ flag set to 0, so mktime() will, even if local time is currently
++ using DST, return the time since epoch at which local standard time
++ would have the same representation as UTC's representation of
++ curr. The fact that mktime() also flips tm_isdst and updates the time
++ fields to the DST-equivalent time needn't concern us here; all that
++ matters is that it returns the time after epoch at which standard
++ time's representation would have matched UTC's, had it been in
++ effect.
++ */
++ # if defined(_POSIX_THREAD_SAFE_FUNCTIONS)
++ struct tm t;
++ if (gmtime_r(&curr, &t)) {
++ time_t mkt = qMkTime(&t);
++ int offset = int(curr - mkt);
++ Q_ASSERT(std::abs(offset) <= SECS_PER_DAY);
++ return offset;
++ }
++ # else
++ if (struct tm *tp = gmtime(&curr)) {
++ struct tm t = *tp; // Copy it quick, hopefully before it can get stomped
++ time_t mkt = qMkTime(&t);
++ int offset = int(curr - mkt);
++ Q_ASSERT(std::abs(offset) <= SECS_PER_DAY);
++ return offset;
++ }
++ # endif
++ } // else, presumably: errno == EOVERFLOW
++ #endif // Platform choice
++ qDebug("Unable to determine current standard time offset from UTC");
++ // We can't tell, presume UTC.
++ return 0;
++ }
++
++ // This is local time's offset (in seconds), at the specified time, including
++ // any DST part.
++ int getUtcOffset(qint64 atMSecsSinceEpoch)
++ {
++ return QDateTimePrivate::expressUtcAsLocal(atMSecsSinceEpoch).offset;
++ }
++ #endif // QT_BOOTSTRAPPED
++
++ // 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 auto epoch = QRoundingDown::qDivMod<MSECS_PER_SEC>(utcMillis);
++ const auto msec = epoch.remainder;
++ Q_ASSERT(msec >= 0 && msec < MSECS_PER_SEC);
++ if (!q20::in_range<time_t>(epoch.quotient))
++ return {utcMillis};
++
++ const auto epochSeconds = time_t(epoch.quotient);
++
++ tm local;
++ if (!qLocalTime(epochSeconds, &local))
++ return {utcMillis};
++
++ auto jd = tmToJd(local);
++ if (Q_UNLIKELY(!jd))
++ return {utcMillis};
++
++ const qint64 daySeconds = tmSecsWithinDay(local);
++ Q_ASSERT(0 <= daySeconds && daySeconds < SECS_PER_DAY);
++ qint64 localSeconds, localMillis;
++ if (Q_UNLIKELY(daysAndSecondsOverflow(*jd, daySeconds, &localSeconds)
++ || secondsAndMillisOverflow(localSeconds, msec, &localMillis))) {
++ return {utcMillis};
++ }
++ const auto dst
++ = local.tm_isdst ? QDateTimePrivate::DaylightTime : QDateTimePrivate::StandardTime;
++ return { localMillis, int(localSeconds - epochSeconds), dst };
++ }
++
++ QString localTimeAbbreviationAt(qint64 local, QDateTimePrivate::TransitionOptions resolve)
++ {
++ auto use = resolveLocalTime(QRoundingDown::qDiv<MSECS_PER_SEC>(local), resolve);
++ if (!use.good)
++ return {};
++ #ifdef HAVE_TM_ZONE
++ if (use.local.tm_zone)
++ return QString::fromLocal8Bit(use.local.tm_zone);
++ #endif
++ return qTzName(use.local.tm_isdst > 0 ? 1 : 0);
++ }
++
++ QDateTimePrivate::ZoneState mapLocalTime(qint64 local, QDateTimePrivate::TransitionOptions resolve)
++ {
++ // Revised later to match what use.local tells us:
++ qint64 localSecs = local / MSECS_PER_SEC;
++ auto use = resolveLocalTime(localSecs, resolve);
++ if (!use.good)
++ return {local};
++
++ qint64 millis = local - localSecs * MSECS_PER_SEC;
++ // Division is defined to round towards zero:
++ Q_ASSERT(local < 0 ? (millis <= 0 && millis > -MSECS_PER_SEC)
++ : (millis >= 0 && millis < MSECS_PER_SEC));
++
++ QDateTimePrivate::DaylightStatus dst =
++ use.local.tm_isdst > 0 ? QDateTimePrivate::DaylightTime : QDateTimePrivate::StandardTime;
++
++ #ifdef HAVE_TM_GMTOFF
++ const int offset = use.local.tm_gmtoff;
++ localSecs = offset + use.utcSecs;
++ #else
++ // Provisional offset, until we have a revised localSecs:
++ int offset = localSecs - use.utcSecs;
++ auto jd = tmToJd(use.local);
++ if (Q_UNLIKELY(!jd))
++ return {local, offset, dst, false};
++
++ qint64 daySecs = tmSecsWithinDay(use.local);
++ Q_ASSERT(0 <= daySecs && daySecs < SECS_PER_DAY);
++ if (daySecs > 0 && *jd < JULIAN_DAY_FOR_EPOCH) {
++ jd = *jd + 1;
++ daySecs -= SECS_PER_DAY;
++ }
++ if (Q_UNLIKELY(daysAndSecondsOverflow(*jd, daySecs, &localSecs)))
++ return {local, offset, dst, false};
++
++ // Use revised localSecs to refine offset:
++ offset = localSecs - use.utcSecs;
++ #endif // HAVE_TM_GMTOFF
++
++ // The only way localSecs and millis can now have opposite sign is for
++ // resolution of the local time to have kicked us across the epoch, in which
++ // case there's no danger of overflow. So if overflow is in danger of
++ // happening, we're already doing the best we can to avoid it.
++ qint64 revised;
++ if (secondsAndMillisOverflow(localSecs, millis, &revised))
++ return {local, offset, QDateTimePrivate::UnknownDaylightTime, false};
++ return {revised, offset, dst, true};
++ }
++
++ /*!
++ \internal
++ Determine the range of the system time_t functions.
++
++ On MS-systems (where time_t is 64-bit by default), the start-point is the
++ epoch, the end-point is the end of the year 3000 (for mktime(); for
++ _localtime64_s it's 18 days later, but we ignore that here). Darwin's range
++ runs from the beginning of 1900 to the end of its 64-bit time_t and Linux
++ uses the full range of time_t (but this might still be 32-bit on some
++ embedded systems).
++
++ (One potential constraint might appear to be the range of struct tm's int
++ tm_year, only allowing time_t to represent times from the start of year
++ 1900+INT_MIN to the end of year INT_MAX. The 26-bit number of seconds in a
++ year means that a 64-bit time_t can indeed represent times outside the range
++ of 32-bit years, by a factor of 32 - but the range of representable
++ milliseconds needs ten more bits than that of seconds, so can't reach the
++ ends of the 32-bit year range.)
++
++ Given the diversity of ranges, we conservatively estimate the actual
++ supported range by experiment on the first call to qdatetime.cpp's
++ millisInSystemRange() by exploration among the known candidates, converting
++ the result to milliseconds and flagging whether each end is the qint64
++ range's bound (so millisInSystemRange will know not to try to pad beyond
++ those bounds). The probed date-times are somewhat inside the range, but
++ close enough to the relevant bound that we can be fairly sure the bound is
++ reached, if the probe succeeds.
++ */
++ SystemMillisRange computeSystemMillisRange()
++ {
++ // Assert this here, as this is called just once, in a static initialization.
++ Q_ASSERT(QGregorianCalendar::julianFromParts(1970, 1, 1) == JULIAN_DAY_FOR_EPOCH);
++
++ constexpr qint64 TIME_T_MAX = std::numeric_limits<time_t>::max();
++ using Bounds = std::numeric_limits<qint64>;
++ constexpr bool isNarrow = Bounds::max() / MSECS_PER_SEC > TIME_T_MAX;
++ if constexpr (isNarrow) {
++ const qint64 msecsMax = quint64(TIME_T_MAX) * MSECS_PER_SEC - 1 + MSECS_PER_SEC;
++ const qint64 msecsMin = -1 - msecsMax; // TIME_T_MIN is -1 - TIME_T_MAX
++ // If we reach back to msecsMin, use it; otherwise, assume 1970 cut-off (MS).
++ struct tm local = {};
++ local.tm_year = tmYearFromQYear(1901);
++ local.tm_mon = 11;
++ local.tm_mday = 15; // A day and a bit after the start of 32-bit time_t:
++ local.tm_isdst = -1;
++ return {qMkTime(&local) == -1 ? 0 : msecsMin, msecsMax, false, false};
++ } else {
++ const struct { int year; qint64 millis; } starts[] = {
++ { int(QDateTime::YearRange::First) + 1, Bounds::min() },
++ // Beginning of the Common Era:
++ { 1, -Q_INT64_C(62135596800000) },
++ // Invention of the Gregorian calendar:
++ { 1582, -Q_INT64_C(12244089600000) },
++ // Its adoption by the anglophone world:
++ { 1752, -Q_INT64_C(6879427200000) },
++ // Before this, struct tm's tm_year is negative (Darwin):
++ { 1900, -Q_INT64_C(2208988800000) },
++ }, ends[] = {
++ { int(QDateTime::YearRange::Last) - 1, Bounds::max() },
++ // MS's end-of-range, end of year 3000:
++ { 3000, Q_INT64_C(32535215999999) },
++ };
++ // Assume we do at least reach the end of a signed 32-bit time_t (since
++ // our actual time_t is bigger than that):
++ qint64 stop =
++ quint64(std::numeric_limits<qint32>::max()) * MSECS_PER_SEC - 1 + MSECS_PER_SEC;
++ // Cleared if first pass round loop fails:
++ bool stopMax = true;
++ for (const auto c : ends) {
++ struct tm local = {};
++ local.tm_year = tmYearFromQYear(c.year);
++ local.tm_mon = 11;
++ local.tm_mday = 31;
++ local.tm_hour = 23;
++ local.tm_min = local.tm_sec = 59;
++ local.tm_isdst = -1;
++ if (qMkTime(&local) != -1) {
++ stop = c.millis;
++ break;
++ }
++ stopMax = false;
++ }
++ bool startMin = true;
++ for (const auto c : starts) {
++ struct tm local {};
++ local.tm_year = tmYearFromQYear(c.year);
++ local.tm_mon = 1;
++ local.tm_mday = 1;
++ local.tm_isdst = -1;
++ if (qMkTime(&local) != -1)
++ return {c.millis, stop, startMin, stopMax};
++ startMin = false;
++ }
++ return {0, stop, false, stopMax};
++ }
++ }
++
++ } // QLocalTime
++
++ QT_END_NAMESPACE