Implement some TODOs: make use of GLibc's tm_gmtoff and tm_zone

Save some complicated computations using struct tm and (effectively)
time_t used in working out local-time's UTC offset and updating the
local time's representation in terms of seconds (since a nominal local
epoch). GLibc gives us the offset for free, making computation of the
seconds easy.

Use the same extension to catch an opportunity for a seldom-needed
early return in one more case.

When determining a zone's abbreviation, we can use tm_zone as a
short-cut to save some mutex-locking.

Change-Id: Id2958f75c1d49ad4aed8f9c483ec13f4fe3a86c2
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
Edward Welbourne 2023-08-16 10:37:18 +02:00
parent b6761d098b
commit 781042efcb

View File

@ -20,6 +20,11 @@
# include <qt_windows.h> # include <qt_windows.h>
#endif #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 QT_BEGIN_NAMESPACE
using namespace QtPrivate::DateTimeConstants; using namespace QtPrivate::DateTimeConstants;
@ -80,10 +85,12 @@ public:
We can assume time-zone offsets are less than a day, so this can only arise 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 if the struct tm describes either the last day of 1969 or the first day of
1970. That makes for a cheap pre-test; if it holds, we can ask mktime about 1970. When we do know the offset (a glibc extension supplies it as a member
the preceding second; if it gives us -2, then the -1 we originally saw is not of struct tm), we can determine whether we're on the last second of the day,
an error (or was an error, but needn't have been). We can then synthesize a refining that check. That makes for a cheap pre-test; if it holds, we can ask
corrected value for local using the -2 result. 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() inline bool MkTimeResult::meansEnd1969()
{ {
@ -91,6 +98,12 @@ inline bool MkTimeResult::meansEnd1969()
return false; return false;
#else #else
if (local.tm_year < 69 || local.tm_year > 70 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_year == 69 // ... and less than a day:
? local.tm_mon < 11 || local.tm_mday < 31 ? local.tm_mon < 11 || local.tm_mday < 31
: local.tm_mon > 0 || local.tm_mday > 1)) { : local.tm_mon > 0 || local.tm_mday > 1)) {
@ -555,7 +568,10 @@ QString localTimeAbbbreviationAt(qint64 local, QDateTimePrivate::DaylightStatus
auto use = resolveLocalTime(QRoundingDown::qDiv<MSECS_PER_SEC>(local), dst); auto use = resolveLocalTime(QRoundingDown::qDiv<MSECS_PER_SEC>(local), dst);
if (!use.good) if (!use.good)
return {}; return {};
// TODO: for glibc, use.local.tm_zone is the zone abbreviation. #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); return qTzName(use.local.tm_isdst > 0 ? 1 : 0);
} }
@ -572,12 +588,14 @@ QDateTimePrivate::ZoneState mapLocalTime(qint64 local, QDateTimePrivate::Dayligh
Q_ASSERT(local < 0 ? (millis <= 0 && millis > -MSECS_PER_SEC) Q_ASSERT(local < 0 ? (millis <= 0 && millis > -MSECS_PER_SEC)
: (millis >= 0 && millis < MSECS_PER_SEC)); : (millis >= 0 && millis < MSECS_PER_SEC));
// TODO: for glibc, use.local.tm_gmtoff is the offset
// That would give us offset directly, hence localSecs = offset + use.utcSecs
// Provisional offset, until we have a revised localSecs:
int offset = localSecs - use.utcSecs;
// Revise our original hint-dst to what it resolved to: // Revise our original hint-dst to what it resolved to:
dst = use.local.tm_isdst > 0 ? QDateTimePrivate::DaylightTime : QDateTimePrivate::StandardTime; 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); auto jd = tmToJd(use.local);
if (Q_UNLIKELY(!jd)) if (Q_UNLIKELY(!jd))
return {local, offset, dst, false}; return {local, offset, dst, false};
@ -593,6 +611,7 @@ QDateTimePrivate::ZoneState mapLocalTime(qint64 local, QDateTimePrivate::Dayligh
// Use revised localSecs to refine offset: // Use revised localSecs to refine offset:
offset = localSecs - use.utcSecs; offset = localSecs - use.utcSecs;
#endif // HAVE_TM_GMTOFF
// The only way localSecs and millis can now have opposite sign is for // 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 // resolution of the local time to have kicked us across the epoch, in which