Move remaining time_t code from qdatetime.cpp to QLocalTime
What remains is all tangled together, but is now at least decoupled from the rest of qdatetime.cpp, so moving it out makes that file easier to read. Task-number: QTBUG-95993 Change-Id: I3fba15aea59b3c8b4cbc6bf1cb03de96d68db0ce Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
parent
6d97cf0e57
commit
2dc82c2177
@ -31,7 +31,6 @@
|
||||
#ifdef Q_OS_WIN
|
||||
# include <qt_windows.h>
|
||||
#endif
|
||||
#include <time.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
@ -42,8 +41,6 @@ using namespace QtPrivate::DateTimeConstants;
|
||||
Date/Time Constants
|
||||
*****************************************************************************/
|
||||
|
||||
constexpr qint64 TIME_T_MAX = std::numeric_limits<time_t>::max();
|
||||
|
||||
/*****************************************************************************
|
||||
QDate static helper functions
|
||||
*****************************************************************************/
|
||||
@ -2402,162 +2399,6 @@ bool QTime::isValid(int h, int m, int s, int ms)
|
||||
typedef QDateTimePrivate::QDateTimeShortData ShortData;
|
||||
typedef QDateTimePrivate::QDateTimeData QDateTimeData;
|
||||
|
||||
// Returns the tzname, assume tzset has been called already
|
||||
static QString qt_tzname(QDateTimePrivate::DaylightStatus daylightStatus)
|
||||
{
|
||||
int isDst = (daylightStatus == QDateTimePrivate::DaylightTime) ? 1 : 0;
|
||||
#if defined(Q_CC_MSVC)
|
||||
size_t s = 0;
|
||||
char name[512];
|
||||
if (_get_tzname(&s, name, 512, isDst))
|
||||
return QString();
|
||||
return QString::fromLocal8Bit(name);
|
||||
#else
|
||||
return QString::fromLocal8Bit(tzname[isDst]);
|
||||
#endif // Q_OS_WIN
|
||||
}
|
||||
|
||||
#if QT_CONFIG(datetimeparser)
|
||||
/*
|
||||
\internal
|
||||
Implemented here to share qt_tzname()
|
||||
*/
|
||||
int QDateTimeParser::startsWithLocalTimeZone(QStringView name)
|
||||
{
|
||||
QDateTimePrivate::DaylightStatus zones[2] = {
|
||||
QDateTimePrivate::StandardTime,
|
||||
QDateTimePrivate::DaylightTime
|
||||
};
|
||||
for (const auto z : zones) {
|
||||
QString zone(qt_tzname(z));
|
||||
if (name.startsWith(zone))
|
||||
return zone.size();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif // datetimeparser
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
static constexpr int tmYearFromQYear(int year) { return year - (year < 0 ? 1899 : 1900); }
|
||||
static constexpr int qYearFromTmYear(int year) { return year + (year < -1899 ? 1899 : 1900); }
|
||||
|
||||
/* 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 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
|
||||
59 by a multiple of 5 on 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 the first
|
||||
second of the same minute; if it gives us -60, then the -1 we originally saw
|
||||
is not an error (or was an error, but needn't have been).
|
||||
*/
|
||||
static inline bool meansEnd1969(tm *local)
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
Q_UNUSED(local);
|
||||
return false;
|
||||
#else
|
||||
if (local->tm_sec < 59 || local->tm_year < 69 || local->tm_year > 70
|
||||
|| local->tm_min % 5 != 4 // Assume zone offset is a multiple of 5 mins
|
||||
|| (local->tm_year == 69
|
||||
? local->tm_mon < 11 || local->tm_mday < 31
|
||||
: local->tm_mon > 0 || local->tm_mday > 1)) {
|
||||
return false;
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
/*
|
||||
Call mktime but bypass its fixing of denormal times.
|
||||
|
||||
The POSIX spec says mktime() accepts a struct tm whose fields lie outside
|
||||
the usual ranges; the parameter is not const-qualified and will be updated
|
||||
to have values in those ranges. However, MS's implementation doesn't do that
|
||||
(or hasn't always done it); and the only member we actually want updated is
|
||||
the tm_isdst flag. (Aside: MS's implementation also only works for tm_year
|
||||
>= 70; this is, in fact, in accordance with the POSIX spec; but all known
|
||||
UNIX libc implementations in fact have a signed time_t and Do The Sensible
|
||||
Thing, to the best of their ability, at least for 0 <= tm_year < 70; see
|
||||
meansEnd1969 for the handling of the last second of UTC's 1969.)
|
||||
|
||||
If we thought we knew tm_isdst and mktime() disagrees, it'll let us know
|
||||
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 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
|
||||
time in a spring-forward gap: read literally, this is an unrepresentable
|
||||
time and it could return -1, setting errno to EOVERFLOW. However, actual
|
||||
implementations chose a time one side or the other of the gap. For example,
|
||||
if we claim to know DST, glibc pushes to the other side of the gap (changing
|
||||
tm_isdst), but stays on the indicated branch of a repetition (no change to
|
||||
tm_isdst); this matches how QTimeZonePrivate::dataForLocalTime() uses its
|
||||
hint; in either case, if we don't claim to know DST, glibc picks the DST
|
||||
candidate. (Experiments conducted with glibc 2.31-9.)
|
||||
*/
|
||||
static inline bool callMkTime(tm *local, time_t *secs)
|
||||
{
|
||||
constexpr time_t maybeError = -1; // mktime()'s return on error; or last second of 1969 UTC.
|
||||
const tm copy = *local;
|
||||
*secs = qMkTime(local);
|
||||
bool good = *secs != maybeError || meansEnd1969(local);
|
||||
if (copy.tm_isdst >= 0 && (!good || local->tm_isdst != copy.tm_isdst)) {
|
||||
// We thought we knew DST-ness, but were wrong:
|
||||
*local = copy;
|
||||
local->tm_isdst = -1;
|
||||
*secs = qMkTime(local);
|
||||
good = *secs != maybeError || meansEnd1969(local);
|
||||
}
|
||||
#if defined(Q_OS_WIN)
|
||||
// Windows mktime for the missing hour backs up 1 hour instead of advancing
|
||||
// 1 hour. If time differs and is standard time then this has happened, so
|
||||
// add 2 hours to the time and 1 hour to the secs
|
||||
if (local->tm_isdst == 0 && local->tm_hour != copy.tm_hour) {
|
||||
local->tm_hour += 2;
|
||||
if (local->tm_hour > 23) {
|
||||
local->tm_hour -= 24;
|
||||
if (++local->tm_mday > QGregorianCalendar::monthLength(
|
||||
local->tm_mon + 1, qYearFromTmYear(local->tm_year))) {
|
||||
local->tm_mday = 1;
|
||||
if (++local->tm_mon > 11) {
|
||||
local->tm_mon = 0;
|
||||
++local->tm_year;
|
||||
}
|
||||
}
|
||||
}
|
||||
*secs += SECS_PER_HOUR;
|
||||
local->tm_isdst = 1;
|
||||
}
|
||||
#endif // Q_OS_WIN
|
||||
return good;
|
||||
}
|
||||
|
||||
// Converts milliseconds since the start of 1970 into a date and/or time:
|
||||
static qint64 msecsToJulianDay(qint64 msecs)
|
||||
{
|
||||
@ -2591,178 +2432,6 @@ 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.
|
||||
|
||||
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 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.
|
||||
*/
|
||||
static auto computeSystemMillisRange()
|
||||
{
|
||||
struct R { qint64 min, max; bool minClip, maxClip; };
|
||||
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 R{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 R{c.millis, stop, startMin, stopMax};
|
||||
startMin = false;
|
||||
}
|
||||
return R{0, stop, false, stopMax};
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
Tests whether system functions can handle a given time.
|
||||
@ -2782,7 +2451,7 @@ static auto computeSystemMillisRange()
|
||||
*/
|
||||
static inline bool millisInSystemRange(qint64 millis, qint64 slack = 0)
|
||||
{
|
||||
static const auto bounds = computeSystemMillisRange();
|
||||
static const auto bounds = QLocalTime::computeSystemMillisRange();
|
||||
return (bounds.minClip || millis >= bounds.min - slack)
|
||||
&& (bounds.maxClip || millis <= bounds.max + slack);
|
||||
}
|
||||
@ -2893,7 +2562,7 @@ QString QDateTimePrivate::localNameAtMillis(qint64 millis, DaylightStatus dst)
|
||||
{
|
||||
QString abbreviation;
|
||||
if (millisInSystemRange(millis, MSECS_PER_DAY)) {
|
||||
abbreviation = localTimeAbbbreviationAt(millis, dst);
|
||||
abbreviation = QLocalTime::localTimeAbbbreviationAt(millis, dst);
|
||||
if (!abbreviation.isEmpty())
|
||||
return abbreviation;
|
||||
}
|
||||
@ -2913,7 +2582,7 @@ QString QDateTimePrivate::localNameAtMillis(qint64 millis, DaylightStatus dst)
|
||||
// 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);
|
||||
return QLocalTime::localTimeAbbbreviationAt(fake.shifted, dst);
|
||||
|
||||
// Overflow, apparently.
|
||||
return {};
|
||||
@ -2925,7 +2594,7 @@ 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:
|
||||
if (millisInSystemRange(millis, MSECS_PER_DAY)) {
|
||||
auto result = mapLocalTime(millis, dst);
|
||||
auto result = QLocalTime::mapLocalTime(millis, dst);
|
||||
if (result.valid)
|
||||
return result;
|
||||
}
|
||||
@ -2942,7 +2611,7 @@ QDateTimePrivate::ZoneState QDateTimePrivate::localStateAtMillis(qint64 millis,
|
||||
// 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);
|
||||
auto result = QLocalTime::mapLocalTime(fake.shifted, dst);
|
||||
if (result.valid) {
|
||||
qint64 adjusted;
|
||||
if (Q_UNLIKELY(add_overflow(result.when, millis - fake.shifted, &adjusted))) {
|
||||
|
@ -177,7 +177,7 @@ private:
|
||||
ParsedSection findTimeZoneName(QStringView str, const QDateTime &when) const;
|
||||
ParsedSection findTimeZone(QStringView str, const QDateTime &when,
|
||||
int maxVal, int minVal) const;
|
||||
// Implemented in qdatetime.cpp:
|
||||
// Implemented in qlocaltime.cpp:
|
||||
static int startsWithLocalTimeZone(const QStringView name);
|
||||
|
||||
enum AmPmFinder {
|
||||
|
@ -4,6 +4,10 @@
|
||||
#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"
|
||||
#if QT_CONFIG(timezone)
|
||||
@ -25,9 +29,136 @@ namespace {
|
||||
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 tmYearFromQYear(int year) { return year - (year < 0 ? 1899 : 1900); }
|
||||
constexpr int qYearFromTmYear(int year) { return year + (year < -1899 ? 1899 : 1900); }
|
||||
|
||||
/* 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 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
|
||||
59 by a multiple of 5 on 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 the first
|
||||
second of the same minute; if it gives us -60, then the -1 we originally saw
|
||||
is not an error (or was an error, but needn't have been).
|
||||
*/
|
||||
inline bool meansEnd1969(tm *local)
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
Q_UNUSED(local);
|
||||
return false;
|
||||
#else
|
||||
if (local->tm_sec < 59 || local->tm_year < 69 || local->tm_year > 70
|
||||
|| local->tm_min % 5 != 4 // Assume zone offset is a multiple of 5 mins
|
||||
|| (local->tm_year == 69
|
||||
? local->tm_mon < 11 || local->tm_mday < 31
|
||||
: local->tm_mon > 0 || local->tm_mday > 1)) {
|
||||
return false;
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
/*
|
||||
Call mktime but bypass its fixing of denormal times.
|
||||
|
||||
The POSIX spec says mktime() accepts a struct tm whose fields lie outside
|
||||
the usual ranges; the parameter is not const-qualified and will be updated
|
||||
to have values in those ranges. However, MS's implementation doesn't do that
|
||||
(or hasn't always done it); and the only member we actually want updated is
|
||||
the tm_isdst flag. (Aside: MS's implementation also only works for tm_year
|
||||
>= 70; this is, in fact, in accordance with the POSIX spec; but all known
|
||||
UNIX libc implementations in fact have a signed time_t and Do The Sensible
|
||||
Thing, to the best of their ability, at least for 0 <= tm_year < 70; see
|
||||
meansEnd1969 for the handling of the last second of UTC's 1969.)
|
||||
|
||||
If we thought we knew tm_isdst and mktime() disagrees, it'll let us know
|
||||
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 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
|
||||
time in a spring-forward gap: read literally, this is an unrepresentable
|
||||
time and it could return -1, setting errno to EOVERFLOW. However, actual
|
||||
implementations chose a time one side or the other of the gap. For example,
|
||||
if we claim to know DST, glibc pushes to the other side of the gap (changing
|
||||
tm_isdst), but stays on the indicated branch of a repetition (no change to
|
||||
tm_isdst); this matches how QTimeZonePrivate::dataForLocalTime() uses its
|
||||
hint; in either case, if we don't claim to know DST, glibc picks the DST
|
||||
candidate. (Experiments conducted with glibc 2.31-9.)
|
||||
*/
|
||||
inline bool callMkTime(tm *local, time_t *secs)
|
||||
{
|
||||
constexpr time_t maybeError = -1; // mktime()'s return on error; or last second of 1969 UTC.
|
||||
const tm copy = *local;
|
||||
*secs = qMkTime(local);
|
||||
bool good = *secs != maybeError || meansEnd1969(local);
|
||||
if (copy.tm_isdst >= 0 && (!good || local->tm_isdst != copy.tm_isdst)) {
|
||||
// We thought we knew DST-ness, but were wrong:
|
||||
*local = copy;
|
||||
local->tm_isdst = -1;
|
||||
*secs = qMkTime(local);
|
||||
good = *secs != maybeError || meansEnd1969(local);
|
||||
}
|
||||
#if defined(Q_OS_WIN)
|
||||
// Windows mktime for the missing hour backs up 1 hour instead of advancing
|
||||
// 1 hour. If time differs and is standard time then this has happened, so
|
||||
// add 2 hours to the time and 1 hour to the secs
|
||||
if (local->tm_isdst == 0 && local->tm_hour != copy.tm_hour) {
|
||||
local->tm_hour += 2;
|
||||
if (local->tm_hour > 23) {
|
||||
local->tm_hour -= 24;
|
||||
if (++local->tm_mday > QGregorianCalendar::monthLength(
|
||||
local->tm_mon + 1, qYearFromTmYear(local->tm_year))) {
|
||||
local->tm_mday = 1;
|
||||
if (++local->tm_mon > 11) {
|
||||
local->tm_mon = 0;
|
||||
++local->tm_year;
|
||||
}
|
||||
}
|
||||
}
|
||||
*secs += 3600;
|
||||
local->tm_isdst = 1;
|
||||
}
|
||||
#endif // Q_OS_WIN
|
||||
return good;
|
||||
}
|
||||
|
||||
struct tm timeToTm(qint64 localDay, int secs, QDateTimePrivate::DaylightStatus dst)
|
||||
{
|
||||
Q_ASSERT(0 <= secs && secs < 3600 * 24);
|
||||
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 = int(dst);
|
||||
return local;
|
||||
}
|
||||
|
||||
bool qtLocalTime(time_t utc, struct tm *local)
|
||||
{
|
||||
// This should really be done under the environmentMutex wrapper qglobal.cpp
|
||||
@ -61,8 +192,44 @@ bool qtLocalTime(time_t utc, struct tm *local)
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Returns the tzname, assume tzset has been called already
|
||||
QString qt_tzname(QDateTimePrivate::DaylightStatus daylightStatus)
|
||||
{
|
||||
int isDst = (daylightStatus == QDateTimePrivate::DaylightTime) ? 1 : 0;
|
||||
#if defined(Q_CC_MSVC)
|
||||
size_t s = 0;
|
||||
char name[512];
|
||||
if (_get_tzname(&s, name, 512, isDst))
|
||||
return QString();
|
||||
return QString::fromLocal8Bit(name);
|
||||
#else
|
||||
return QString::fromLocal8Bit(tzname[isDst]);
|
||||
#endif // Q_OS_WIN
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#if QT_CONFIG(datetimeparser)
|
||||
/*
|
||||
\internal
|
||||
Implemented here to share qt_tzname()
|
||||
*/
|
||||
int QDateTimeParser::startsWithLocalTimeZone(QStringView name)
|
||||
{
|
||||
QDateTimePrivate::DaylightStatus zones[2] = {
|
||||
QDateTimePrivate::StandardTime,
|
||||
QDateTimePrivate::DaylightTime
|
||||
};
|
||||
for (const auto z : zones) {
|
||||
QString zone(qt_tzname(z));
|
||||
if (name.startsWith(zone))
|
||||
return zone.size();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif // datetimeparser
|
||||
|
||||
namespace QLocalTime {
|
||||
|
||||
// Calls the platform variant of localtime() for the given utcMillis, and
|
||||
@ -103,6 +270,165 @@ QDateTimePrivate::ZoneState utcToLocal(qint64 utcMillis)
|
||||
return { localMillis, int(localSeconds - epochSeconds), dst };
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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};
|
||||
}
|
||||
|
||||
/*!
|
||||
\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.
|
||||
[[maybe_unused]] qint64 epochJd;
|
||||
Q_ASSERT(QGregorianCalendar::julianFromParts(1970, 1, 1, &epochJd)
|
||||
&& epochJd == 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
|
||||
|
@ -24,6 +24,11 @@ QT_BEGIN_NAMESPACE
|
||||
namespace QLocalTime {
|
||||
// Support for QDateTime
|
||||
QDateTimePrivate::ZoneState utcToLocal(qint64 utcMillis);
|
||||
QString localTimeAbbbreviationAt(qint64 local, QDateTimePrivate::DaylightStatus dst);
|
||||
QDateTimePrivate::ZoneState mapLocalTime(qint64 local, QDateTimePrivate::DaylightStatus dst);
|
||||
|
||||
struct SystemMillisRange { qint64 min, max; bool minClip, maxClip; };
|
||||
SystemMillisRange computeSystemMillisRange();
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
Loading…
Reference in New Issue
Block a user