Use time-zone transition data before 1970 as well as after
QDateTime has long followed a convention of ignoring what it knows about time-zone transitions before the epoch. This produces unhelpful artefacts (such as an ahistorical spring-forward skipping the first hour of 1970 in Europe/London, which was in permanent DST at the time) and complicates the code. It documented that DST transitions were ignored, but in fact ignored all transitions prior to 1970 and simply assumed that the current time-zone properties (half a century later) applied to all times before 1970. This appears to be based on the fact that the MS APIs using time_t all limit their range to after 1970. Given that we have to resort to "other means" to deal with times after the end of time_t, when it's only 32-bit (and after year 3000, on MS systems), we have the means in place to handle times outside the range supported by the system APIs, so have no need to mimic this restriction. (Those means are not as robust as we might want, but they are less bad than assuming that the present zone properites were always in effect prior to 1970.) On macOS, the time_t functions only reach back to the start of 1900; it reaches to the end of its time_t range and Linux covers the whole range. Given this variety, the range is now auto-detected the first time it is needed (based on some quick and dirty heuristics). Various CET-specific tests now need adjustments in tests of times before the introduction of time-zones (when they are in fact on LMT, not CET). The systemZone() test of QTimeZone can now restore its pre-zone test cases. Various comments on tests needed updates. [ChangeLog][QtCore][QDateTime] Available time-zone information is now used to its full extent, where previously QDateTime used LocalTime's current standard time for all dates before 1970. Where we have time-zone information, it is considered reliable, so we use it. This changes the "best efforts" used for times outside the range supported by the system APIs, in most cases giving less misleading results. Fixes: QTBUG-80421 Change-Id: I7b1df7622dd9be244b0238ed9c08845fb5b32215 Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
parent
d97fd7af2b
commit
35412acd88
@ -2362,39 +2362,6 @@ bool QTime::isValid(int h, int m, int s, int ms)
|
|||||||
typedef QDateTimePrivate::QDateTimeShortData ShortData;
|
typedef QDateTimePrivate::QDateTimeShortData ShortData;
|
||||||
typedef QDateTimePrivate::QDateTimeData QDateTimeData;
|
typedef QDateTimePrivate::QDateTimeData QDateTimeData;
|
||||||
|
|
||||||
// Returns the platform variant of timezone, i.e. the standard time offset
|
|
||||||
// The timezone external variable is documented as always holding the
|
|
||||||
// Standard Time offset as seconds west of Greenwich, i.e. UTC+01:00 is -3600
|
|
||||||
// Note this may not be historicaly accurate.
|
|
||||||
// Relies on tzset, mktime, or localtime having been called to populate timezone
|
|
||||||
static int qt_timezone()
|
|
||||||
{
|
|
||||||
#if defined(_MSC_VER)
|
|
||||||
long offset;
|
|
||||||
_get_timezone(&offset);
|
|
||||||
return offset;
|
|
||||||
#elif defined(Q_OS_BSD4) && !defined(Q_OS_DARWIN)
|
|
||||||
time_t clock = time(NULL);
|
|
||||||
struct tm t;
|
|
||||||
localtime_r(&clock, &t);
|
|
||||||
// QTBUG-36080 Workaround for systems without the POSIX timezone
|
|
||||||
// variable. This solution is not very efficient but fixing it is up to
|
|
||||||
// the libc implementations.
|
|
||||||
//
|
|
||||||
// tm_gmtoff has some important differences compared to the timezone
|
|
||||||
// variable:
|
|
||||||
// - It returns the number of seconds east of UTC, and we want the
|
|
||||||
// number of seconds west of UTC.
|
|
||||||
// - It also takes DST into account, so we need to adjust it to always
|
|
||||||
// get the Standard Time offset.
|
|
||||||
return -t.tm_gmtoff + (t.tm_isdst ? (long)SECS_PER_HOUR : 0L);
|
|
||||||
#elif defined(Q_OS_INTEGRITY) || defined(Q_OS_RTEMS)
|
|
||||||
return 0;
|
|
||||||
#else
|
|
||||||
return timezone;
|
|
||||||
#endif // Q_OS_WIN
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the tzname, assume tzset has been called already
|
// Returns the tzname, assume tzset has been called already
|
||||||
static QString qt_tzname(QDateTimePrivate::DaylightStatus daylightStatus)
|
static QString qt_tzname(QDateTimePrivate::DaylightStatus daylightStatus)
|
||||||
{
|
{
|
||||||
@ -2430,6 +2397,15 @@ int QDateTimeParser::startsWithLocalTimeZone(QStringView name)
|
|||||||
}
|
}
|
||||||
#endif // datetimeparser
|
#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); }
|
||||||
|
|
||||||
// Calls the platform variant of mktime for the given date, time and
|
// Calls the platform variant of mktime for the given date, time and
|
||||||
// daylightStatus, and updates the date, time, daylightStatus and abbreviation
|
// daylightStatus, and updates the date, time, daylightStatus and abbreviation
|
||||||
// with the returned values. If the date falls outside the time_t range
|
// with the returned values. If the date falls outside the time_t range
|
||||||
@ -2443,14 +2419,13 @@ static qint64 qt_mktime(QDate *date, QTime *time, QDateTimePrivate::DaylightStat
|
|||||||
date->getDate(&yy, &mm, &dd);
|
date->getDate(&yy, &mm, &dd);
|
||||||
|
|
||||||
// All other platforms provide standard C library time functions
|
// All other platforms provide standard C library time functions
|
||||||
tm local;
|
tm local = {};
|
||||||
memset(&local, 0, sizeof(local)); // tm_[wy]day plus any non-standard fields
|
|
||||||
local.tm_sec = time->second();
|
local.tm_sec = time->second();
|
||||||
local.tm_min = time->minute();
|
local.tm_min = time->minute();
|
||||||
local.tm_hour = time->hour();
|
local.tm_hour = time->hour();
|
||||||
local.tm_mday = dd;
|
local.tm_mday = dd;
|
||||||
local.tm_mon = mm - 1;
|
local.tm_mon = mm - 1;
|
||||||
local.tm_year = yy - 1900;
|
local.tm_year = tmYearFromQYear(yy);
|
||||||
local.tm_isdst = daylightStatus ? int(*daylightStatus) : -1;
|
local.tm_isdst = daylightStatus ? int(*daylightStatus) : -1;
|
||||||
|
|
||||||
#if defined(Q_OS_WIN)
|
#if defined(Q_OS_WIN)
|
||||||
@ -2464,7 +2439,7 @@ static qint64 qt_mktime(QDate *date, QTime *time, QDateTimePrivate::DaylightStat
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (secsSinceEpoch != time_t(-1)) {
|
if (secsSinceEpoch != time_t(-1)) {
|
||||||
*date = QDate(local.tm_year + 1900, local.tm_mon + 1, local.tm_mday);
|
*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);
|
*time = QTime(local.tm_hour, local.tm_min, local.tm_sec, msec);
|
||||||
#if defined(Q_OS_WIN)
|
#if defined(Q_OS_WIN)
|
||||||
// Windows mktime for the missing hour subtracts 1 hour from the time
|
// Windows mktime for the missing hour subtracts 1 hour from the time
|
||||||
@ -2527,8 +2502,10 @@ static qint64 qt_mktime(QDate *date, QTime *time, QDateTimePrivate::DaylightStat
|
|||||||
static bool qt_localtime(qint64 msecsSinceEpoch, QDate *localDate, QTime *localTime,
|
static bool qt_localtime(qint64 msecsSinceEpoch, QDate *localDate, QTime *localTime,
|
||||||
QDateTimePrivate::DaylightStatus *daylightStatus)
|
QDateTimePrivate::DaylightStatus *daylightStatus)
|
||||||
{
|
{
|
||||||
const time_t secsSinceEpoch = msecsSinceEpoch / MSECS_PER_SEC;
|
const int signFix = msecsSinceEpoch % MSECS_PER_SEC && msecsSinceEpoch < 0 ? 1 : 0;
|
||||||
const int msec = msecsSinceEpoch % MSECS_PER_SEC;
|
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;
|
tm local;
|
||||||
bool valid = false;
|
bool valid = false;
|
||||||
@ -2537,26 +2514,28 @@ static bool qt_localtime(qint64 msecsSinceEpoch, QDate *localDate, QTime *localT
|
|||||||
// localtime_r() does not have this constraint, so make an explicit call.
|
// 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 the timezone info be re-parsed.
|
||||||
qTzSet();
|
qTzSet();
|
||||||
|
if (qint64(secsSinceEpoch) * MSECS_PER_SEC + msec == msecsSinceEpoch) {
|
||||||
#if QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS)
|
#if QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS)
|
||||||
// Use the reentrant version of localtime() where available
|
// Use the reentrant version of localtime() where available
|
||||||
// as is thread-safe and doesn't use a shared static data area
|
// as is thread-safe and doesn't use a shared static data area
|
||||||
if (tm *res = localtime_r(&secsSinceEpoch, &local)) {
|
if (tm *res = localtime_r(&secsSinceEpoch, &local)) {
|
||||||
Q_ASSERT(res == &local);
|
Q_ASSERT(res == &local);
|
||||||
valid = true;
|
valid = true;
|
||||||
}
|
}
|
||||||
#elif defined(Q_CC_MSVC)
|
#elif defined(Q_CC_MSVC)
|
||||||
if (!_localtime64_s(&local, &secsSinceEpoch))
|
if (!_localtime64_s(&local, &secsSinceEpoch))
|
||||||
valid = true;
|
valid = true;
|
||||||
#else
|
#else
|
||||||
// Returns shared static data which may be overwritten at any time
|
// Returns shared static data which may be overwritten at any time
|
||||||
// So copy the result asap
|
// So copy the result asap
|
||||||
if (tm *res = localtime(&secsSinceEpoch)) {
|
if (tm *res = localtime(&secsSinceEpoch)) {
|
||||||
local = *res;
|
local = *res;
|
||||||
valid = true;
|
valid = true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
if (valid) {
|
if (valid) {
|
||||||
*localDate = QDate(local.tm_year + 1900, local.tm_mon + 1, local.tm_mday);
|
*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);
|
*localTime = QTime(local.tm_hour, local.tm_min, local.tm_sec, msec);
|
||||||
if (daylightStatus) {
|
if (daylightStatus) {
|
||||||
if (local.tm_isdst > 0)
|
if (local.tm_isdst > 0)
|
||||||
@ -2609,48 +2588,131 @@ static qint64 timeToMSecs(QDate date, QTime time)
|
|||||||
+ time.msecsSinceStartOfDay();
|
+ time.msecsSinceStartOfDay();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\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:
|
||||||
|
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 32-bit time_t:
|
||||||
|
qint64 stop = quint64(TIME_T_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;
|
||||||
|
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;
|
||||||
|
if (qMkTime(&local) != -1)
|
||||||
|
return R{c.millis, stop, startMin, stopMax};
|
||||||
|
startMin = false;
|
||||||
|
}
|
||||||
|
return R{0, stop, false, stopMax};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\internal
|
\internal
|
||||||
Tests whether system functions can handle a given time.
|
Tests whether system functions can handle a given time.
|
||||||
|
|
||||||
On MS-systems (where time_t is 64-bit by default), the system functions only
|
The range of milliseconds for which the time_t-based functions work depends
|
||||||
work for dates up to the end of year 3000 (for mktime(); for _localtime64_s
|
somewhat on platform (see computeSystemMillisRange() for details). This
|
||||||
it's 18 days later, but we ignore that here). On Unix the supported range
|
function tests whether the UTC time \a millis milliseconds from the epoch is
|
||||||
is as many seconds after the epoch as time_t can represent.
|
in the supported range.
|
||||||
|
|
||||||
This second-range is then mapped to a millisecond range; if \a slack is
|
To test a local time, pass an upper bound on the magnitude of time-zone
|
||||||
passed, the range is extended by this many milliseconds at each end. The
|
correction potentially needed as \a slack: in this case the range is
|
||||||
function returns true precisely if \a millis is within the resulting range.
|
extended by this many milliseconds at each end (where applicable). The
|
||||||
|
function then returns true precisely if \a millis is within this (possibly)
|
||||||
|
widened range. This doesn't guarantee that the time_t functions can handle
|
||||||
|
the time, so check their returns to be sure. Values for which the function
|
||||||
|
returns false should be assumed unrepresentable.
|
||||||
*/
|
*/
|
||||||
static inline bool millisInSystemRange(qint64 millis, qint64 slack = 0)
|
static inline bool millisInSystemRange(qint64 millis, qint64 slack = 0)
|
||||||
{
|
{
|
||||||
#ifdef Q_OS_WIN
|
static const auto bounds = computeSystemMillisRange();
|
||||||
const qint64 msecsMax = Q_INT64_C(32535215999999);
|
return (bounds.minClip || millis >= bounds.min - slack)
|
||||||
return millis <= msecsMax + slack;
|
&& (bounds.maxClip || millis <= bounds.max + slack);
|
||||||
#else
|
|
||||||
if constexpr (std::numeric_limits<qint64>::max() / MSECS_PER_SEC > TIME_T_MAX) {
|
|
||||||
const qint64 msecsMax = quint64(TIME_T_MAX) * MSECS_PER_SEC;
|
|
||||||
return millis <= msecsMax + slack;
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// First year for which system functions give useful answers, when earlier times
|
||||||
|
// aren't handled by those functions (see millisInSystemRange):
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
constexpr int firstSystemTimeYear = 1970;
|
||||||
|
#else // First year fully in 32-bit time_t range:
|
||||||
|
constexpr int firstSystemTimeYear = 1902;
|
||||||
|
#endif
|
||||||
|
|
||||||
// Convert an MSecs Since Epoch into Local Time
|
// Convert an MSecs Since Epoch into Local Time
|
||||||
bool QDateTimePrivate::epochMSecsToLocalTime(qint64 msecs, QDate *localDate, QTime *localTime,
|
bool QDateTimePrivate::epochMSecsToLocalTime(qint64 msecs, QDate *localDate, QTime *localTime,
|
||||||
QDateTimePrivate::DaylightStatus *daylightStatus)
|
QDateTimePrivate::DaylightStatus *daylightStatus)
|
||||||
{
|
{
|
||||||
if (msecs < 0) {
|
|
||||||
// Docs state any LocalTime before 1970-01-01 will *not* have any Daylight Time applied
|
|
||||||
// Instead just use the standard offset from UTC to convert to UTC time
|
|
||||||
qTzSet();
|
|
||||||
msecsToTime(msecs - qt_timezone() * MSECS_PER_SEC, localDate, localTime);
|
|
||||||
if (daylightStatus)
|
|
||||||
*daylightStatus = QDateTimePrivate::StandardTime;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!millisInSystemRange(msecs)) {
|
if (!millisInSystemRange(msecs)) {
|
||||||
// Docs state any LocalTime after 2038-01-18 *will* have any DST applied.
|
// 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.
|
// When this falls outside the supported range, we need to fake it.
|
||||||
@ -2681,17 +2743,17 @@ bool QDateTimePrivate::epochMSecsToLocalTime(qint64 msecs, QDate *localDate, QTi
|
|||||||
msecsToTime(msecs, &utcDate, &utcTime);
|
msecsToTime(msecs, &utcDate, &utcTime);
|
||||||
int year, month, day;
|
int year, month, day;
|
||||||
utcDate.getDate(&year, &month, &day);
|
utcDate.getDate(&year, &month, &day);
|
||||||
// 2037 is not a leap year, so make sure date isn't Feb 29
|
// No boundary year is a leap year, so make sure date isn't Feb 29
|
||||||
if (month == 2 && day == 29)
|
if (month == 2 && day == 29)
|
||||||
--day;
|
--day;
|
||||||
QDate fakeDate(2037, month, day);
|
QDate fakeDate(year < 1970 ? firstSystemTimeYear : 2037, month, day);
|
||||||
qint64 fakeMsecs = QDateTime(fakeDate, utcTime, Qt::UTC).toMSecsSinceEpoch();
|
qint64 fakeMsecs = QDateTime(fakeDate, utcTime, Qt::UTC).toMSecsSinceEpoch();
|
||||||
bool res = qt_localtime(fakeMsecs, localDate, localTime, daylightStatus);
|
bool res = qt_localtime(fakeMsecs, localDate, localTime, daylightStatus);
|
||||||
*localDate = localDate->addDays(fakeDate.daysTo(utcDate));
|
*localDate = localDate->addDays(fakeDate.daysTo(utcDate));
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Falls inside time_t supported range so can use localtime
|
// Falls inside time_t supported range so localtime can handle it:
|
||||||
return qt_localtime(msecs, localDate, localTime, daylightStatus);
|
return qt_localtime(msecs, localDate, localTime, daylightStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2703,16 +2765,15 @@ qint64 QDateTimePrivate::localMSecsToEpochMSecs(qint64 localMsecs,
|
|||||||
QDate *localDate, QTime *localTime,
|
QDate *localDate, QTime *localTime,
|
||||||
QString *abbreviation)
|
QString *abbreviation)
|
||||||
{
|
{
|
||||||
QDate dt;
|
|
||||||
QTime tm;
|
|
||||||
msecsToTime(localMsecs, &dt, &tm);
|
|
||||||
|
|
||||||
// First, if localMsecs is within +/- 1 day of viable range, try mktime() in
|
// First, if localMsecs is within +/- 1 day of viable range, try mktime() in
|
||||||
// case it does fall in the range and gets proper DST conversion:
|
// case it does fall in the range and gets proper DST conversion:
|
||||||
if (localMsecs >= -MSECS_PER_DAY && millisInSystemRange(localMsecs, MSECS_PER_DAY)) {
|
if (millisInSystemRange(localMsecs, MSECS_PER_DAY)) {
|
||||||
bool valid;
|
bool valid;
|
||||||
|
QDate dt;
|
||||||
|
QTime tm;
|
||||||
|
msecsToTime(localMsecs, &dt, &tm);
|
||||||
const qint64 utcMsecs = qt_mktime(&dt, &tm, daylightStatus, abbreviation, &valid);
|
const qint64 utcMsecs = qt_mktime(&dt, &tm, daylightStatus, abbreviation, &valid);
|
||||||
if (valid && utcMsecs >= 0 && millisInSystemRange(utcMsecs)) {
|
if (valid && millisInSystemRange(utcMsecs)) {
|
||||||
// mktime worked and falls in valid range, so use it
|
// mktime worked and falls in valid range, so use it
|
||||||
if (localDate)
|
if (localDate)
|
||||||
*localDate = dt;
|
*localDate = dt;
|
||||||
@ -2720,28 +2781,9 @@ qint64 QDateTimePrivate::localMSecsToEpochMSecs(qint64 localMsecs,
|
|||||||
*localTime = tm;
|
*localTime = tm;
|
||||||
return utcMsecs;
|
return utcMsecs;
|
||||||
}
|
}
|
||||||
// Restore dt and tm, after qt_mktime() stomped them:
|
|
||||||
msecsToTime(localMsecs, &dt, &tm);
|
|
||||||
} else if (localMsecs < MSECS_PER_DAY) {
|
|
||||||
// Didn't call mktime(), but the pre-epoch code below needs mktime()'s
|
|
||||||
// implicit tzset() call to have happened.
|
|
||||||
qTzSet();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (localMsecs <= MSECS_PER_DAY) {
|
// Otherwise, outside the system range.
|
||||||
// Would have been caught above if after UTC epoch, so is before.
|
|
||||||
// Docs state any LocalTime before 1970-01-01 will *not* have any DST applied
|
|
||||||
const qint64 utcMsecs = localMsecs + qt_timezone() * MSECS_PER_SEC;
|
|
||||||
if (localDate || localTime)
|
|
||||||
msecsToTime(localMsecs, localDate, localTime);
|
|
||||||
if (daylightStatus)
|
|
||||||
*daylightStatus = QDateTimePrivate::StandardTime;
|
|
||||||
if (abbreviation)
|
|
||||||
*abbreviation = qt_tzname(QDateTimePrivate::StandardTime);
|
|
||||||
return utcMsecs;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, after the end of the system range.
|
|
||||||
#if QT_CONFIG(timezone)
|
#if QT_CONFIG(timezone)
|
||||||
// Use the system zone:
|
// Use the system zone:
|
||||||
const auto sys = QTimeZone::systemTimeZone();
|
const auto sys = QTimeZone::systemTimeZone();
|
||||||
@ -2764,13 +2806,16 @@ qint64 QDateTimePrivate::localMSecsToEpochMSecs(qint64 localMsecs,
|
|||||||
// Use existing method to fake the conversion (this is deeply flawed as it
|
// Use existing method to fake the conversion (this is deeply flawed as it
|
||||||
// may apply the conversion from the wrong day number, e.g. if rule is last
|
// may apply the conversion from the wrong day number, e.g. if rule is last
|
||||||
// Sunday of month).
|
// Sunday of month).
|
||||||
|
QDate dt;
|
||||||
|
QTime tm;
|
||||||
|
msecsToTime(localMsecs, &dt, &tm);
|
||||||
int year, month, day;
|
int year, month, day;
|
||||||
dt.getDate(&year, &month, &day);
|
dt.getDate(&year, &month, &day);
|
||||||
// 2037 is not a leap year, so make sure date isn't Feb 29
|
// No boundary year is a leap year, so make sure date isn't Feb 29
|
||||||
if (month == 2 && day == 29)
|
if (month == 2 && day == 29)
|
||||||
--day;
|
--day;
|
||||||
bool ok;
|
bool ok;
|
||||||
QDate fakeDate(2037, month, day);
|
QDate fakeDate(year < 1970 ? firstSystemTimeYear : 2037, month, day);
|
||||||
const qint64 fakeDiff = fakeDate.daysTo(dt);
|
const qint64 fakeDiff = fakeDate.daysTo(dt);
|
||||||
const qint64 utcMsecs = qt_mktime(&fakeDate, &tm, daylightStatus, abbreviation, &ok);
|
const qint64 utcMsecs = qt_mktime(&fakeDate, &tm, daylightStatus, abbreviation, &ok);
|
||||||
Q_ASSERT(ok);
|
Q_ASSERT(ok);
|
||||||
@ -3278,15 +3323,8 @@ inline qint64 QDateTimePrivate::zoneMSecsToEpochMSecs(qint64 zoneMSecs, const QT
|
|||||||
// When it falls in a skipped day (Pacific date-line crossings):
|
// When it falls in a skipped day (Pacific date-line crossings):
|
||||||
|| (data.offsetFromUtc - offset) % SECS_PER_DAY == 0;
|
|| (data.offsetFromUtc - offset) % SECS_PER_DAY == 0;
|
||||||
})((zoneMSecs - data.atMSecsSinceEpoch) / MSECS_PER_SEC));
|
})((zoneMSecs - data.atMSecsSinceEpoch) / MSECS_PER_SEC));
|
||||||
// Docs state any time before 1970-01-01 will *not* have any DST applied
|
msecsToTime(data.atMSecsSinceEpoch + data.offsetFromUtc * MSECS_PER_SEC,
|
||||||
// but all affected times afterwards will have DST applied.
|
zoneDate, zoneTime);
|
||||||
if (data.atMSecsSinceEpoch < 0) {
|
|
||||||
msecsToTime(zoneMSecs, zoneDate, zoneTime);
|
|
||||||
return zoneMSecs - data.standardTimeOffset * MSECS_PER_SEC;
|
|
||||||
} else {
|
|
||||||
msecsToTime(data.atMSecsSinceEpoch + data.offsetFromUtc * MSECS_PER_SEC,
|
|
||||||
zoneDate, zoneTime);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return data.atMSecsSinceEpoch;
|
return data.atMSecsSinceEpoch;
|
||||||
}
|
}
|
||||||
@ -3314,13 +3352,13 @@ inline qint64 QDateTimePrivate::zoneMSecsToEpochMSecs(qint64 zoneMSecs, const QT
|
|||||||
time}, to \l{Qt::UTC}{UTC}, to a specified \l{Qt::OffsetFromUTC}{offset from
|
time}, to \l{Qt::UTC}{UTC}, to a specified \l{Qt::OffsetFromUTC}{offset from
|
||||||
UTC} or to a specified \l{Qt::TimeZone}{time zone}, in conjunction with the
|
UTC} or to a specified \l{Qt::TimeZone}{time zone}, in conjunction with the
|
||||||
QTimeZone class. For example, a time zone of "Europe/Berlin" will apply the
|
QTimeZone class. For example, a time zone of "Europe/Berlin" will apply the
|
||||||
daylight-saving rules as used in Germany since 1970. In contrast, an offset
|
daylight-saving rules as used in Germany. In contrast, an offset from UTC of
|
||||||
from UTC of +3600 seconds is one hour ahead of UTC (usually written in ISO
|
+3600 seconds is one hour ahead of UTC (usually written in ISO standard
|
||||||
standard notation as "UTC+01:00"), with no daylight-saving offset or
|
notation as "UTC+01:00"), with no daylight-saving offset or changes. When
|
||||||
changes. When using either local time or a specified time zone, time-zone
|
using either local time or a specified time zone, time-zone transitions such
|
||||||
transitions such as the starts and ends of daylight-saving time (DST; but
|
as the starts and ends of daylight-saving time (DST; but see below) are
|
||||||
see below) are taken into account. The choice of system used to represent a
|
taken into account. The choice of system used to represent a datetime is
|
||||||
datetime is described as its "timespec".
|
described as its "timespec".
|
||||||
|
|
||||||
A QDateTime object is typically created either by giving a date and time
|
A QDateTime object is typically created either by giving a date and time
|
||||||
explicitly in the constructor, or by using a static function such as
|
explicitly in the constructor, or by using a static function such as
|
||||||
@ -3406,14 +3444,14 @@ inline qint64 QDateTimePrivate::zoneMSecsToEpochMSecs(qint64 zoneMSecs, const QT
|
|||||||
performed will take this missing hour into account and return a valid
|
performed will take this missing hour into account and return a valid
|
||||||
result. For example, adding one minute to 01:59:59 will get 03:00:00.
|
result. For example, adding one minute to 01:59:59 will get 03:00:00.
|
||||||
|
|
||||||
The range of valid dates taking DST into account is 1970-01-01 to the
|
For date-times that the system \c time_t can represent (from 1901-12-14 to
|
||||||
present, and rules are in place for handling DST correctly until 2038-01-18
|
2038-01-18 on systems with 32-bit \c time_t; for the full range QDateTime
|
||||||
(or the end of the \c time_t range, if this is later). For dates after the
|
can represent if the type is 64-bit), the standard system APIs are used to
|
||||||
end of this range, QDateTime makes a \e{best guess} using the rules for year
|
determine local time's offset from UTC. For date-times not handled by these
|
||||||
2037, but we can't guarantee accuracy; indeed, for \e{any} future date, the
|
system APIs, QTimeZone::systemTimeZone() is used. In either case, the offset
|
||||||
time-zone may change its rules before that date comes around. For dates
|
information used depends on the system and may be incomplete or, for past
|
||||||
before 1970, QDateTime uses the current abbreviation and offset of local
|
times, historically inaccurate. In any case, for future dates, the local
|
||||||
time's standad time.
|
time zone's offsets and DST rules may change before that date comes around.
|
||||||
|
|
||||||
\section2 Offsets From UTC
|
\section2 Offsets From UTC
|
||||||
|
|
||||||
@ -3421,7 +3459,8 @@ inline qint64 QDateTimePrivate::zoneMSecsToEpochMSecs(qint64 zoneMSecs, const QT
|
|||||||
implicit limit imposed when using the toString() and fromString() methods
|
implicit limit imposed when using the toString() and fromString() methods
|
||||||
which use a [+|-]hh:mm format, effectively limiting the range to +/- 99
|
which use a [+|-]hh:mm format, effectively limiting the range to +/- 99
|
||||||
hours and 59 minutes and whole minutes only. Note that currently no time
|
hours and 59 minutes and whole minutes only. Note that currently no time
|
||||||
zone lies outside the range of +/- 14 hours.
|
zone has an offset outside the range of ±14 hours and all known offsets are
|
||||||
|
multiples of five minutes.
|
||||||
|
|
||||||
\sa QDate, QTime, QDateTimeEdit, QTimeZone
|
\sa QDate, QTime, QDateTimeEdit, QTimeZone
|
||||||
*/
|
*/
|
||||||
@ -3561,10 +3600,11 @@ bool QDateTime::isNull() const
|
|||||||
Returns \c true if both the date and the time are valid and they are valid in
|
Returns \c true if both the date and the time are valid and they are valid in
|
||||||
the current Qt::TimeSpec, otherwise returns \c false.
|
the current Qt::TimeSpec, otherwise returns \c false.
|
||||||
|
|
||||||
If the timeSpec() is Qt::LocalTime or Qt::TimeZone then the date and time are
|
If the timeSpec() is Qt::LocalTime or Qt::TimeZone and this object
|
||||||
checked to see if they fall in the Standard Time to Daylight-Saving Time transition
|
represents a time that was skipped by a forward transition, then it is
|
||||||
hour, i.e. if the transition is at 2am and the clock goes forward to 3am
|
invalid. For example, if DST ends at 2am with the clock advancing to 3am,
|
||||||
then the time from 02:00:00 to 02:59:59.999 is considered to be invalid.
|
then date-times from 02:00:00 to 02:59:59.999 on that day are considered
|
||||||
|
invalid.
|
||||||
|
|
||||||
\sa QDateTime::YearRange, QDate::isValid(), QTime::isValid()
|
\sa QDateTime::YearRange, QDate::isValid(), QTime::isValid()
|
||||||
*/
|
*/
|
||||||
@ -3663,11 +3703,10 @@ QTimeZone QDateTime::timeZone() const
|
|||||||
\endlist
|
\endlist
|
||||||
|
|
||||||
For the last two, the offset at this date and time will be returned, taking
|
For the last two, the offset at this date and time will be returned, taking
|
||||||
account of Daylight-Saving Offset unless the date precedes the start of
|
account of Daylight-Saving Offset. The offset is the difference between the
|
||||||
1970. The offset is the difference between the local time or time in the
|
local time or time in the given time-zone and UTC time; it is positive in
|
||||||
given time-zone and UTC time; it is positive in time-zones ahead of UTC
|
time-zones ahead of UTC (East of The Prime Meridian), negative for those
|
||||||
(East of The Prime Meridian), negative for those behind UTC (West of The
|
behind UTC (West of The Prime Meridian).
|
||||||
Prime Meridian).
|
|
||||||
|
|
||||||
\sa setOffsetFromUtc()
|
\sa setOffsetFromUtc()
|
||||||
*/
|
*/
|
||||||
@ -3981,18 +4020,11 @@ void QDateTime::setMSecsSinceEpoch(qint64 msecs)
|
|||||||
d.detach();
|
d.detach();
|
||||||
if (!d->m_timeZone.isValid())
|
if (!d->m_timeZone.isValid())
|
||||||
break;
|
break;
|
||||||
// Docs state any LocalTime before 1970-01-01 will *not* have any DST applied
|
status = mergeDaylightStatus(status,
|
||||||
// but all affected times afterwards will have DST applied.
|
d->m_timeZone.d->isDaylightTime(msecs)
|
||||||
if (msecs >= 0) {
|
? QDateTimePrivate::DaylightTime
|
||||||
status = mergeDaylightStatus(status,
|
: QDateTimePrivate::StandardTime);
|
||||||
d->m_timeZone.d->isDaylightTime(msecs)
|
d->m_offsetFromUtc = d->m_timeZone.d->offsetFromUtc(msecs);
|
||||||
? QDateTimePrivate::DaylightTime
|
|
||||||
: QDateTimePrivate::StandardTime);
|
|
||||||
d->m_offsetFromUtc = d->m_timeZone.d->offsetFromUtc(msecs);
|
|
||||||
} else {
|
|
||||||
status = mergeDaylightStatus(status, QDateTimePrivate::StandardTime);
|
|
||||||
d->m_offsetFromUtc = d->m_timeZone.d->standardTimeOffset(msecs);
|
|
||||||
}
|
|
||||||
// NB: cast to qint64 here is important to make sure a matching
|
// NB: cast to qint64 here is important to make sure a matching
|
||||||
// add_overflow is found, GCC 7.5.0 fails without this cast
|
// add_overflow is found, GCC 7.5.0 fails without this cast
|
||||||
if (!add_overflow(msecs, qint64(d->m_offsetFromUtc * MSECS_PER_SEC), &msecs))
|
if (!add_overflow(msecs, qint64(d->m_offsetFromUtc * MSECS_PER_SEC), &msecs))
|
||||||
@ -4229,7 +4261,7 @@ static inline void massageAdjustedDateTime(QDateTimeData &d, QDate date, QTime t
|
|||||||
later than the datetime of this object (or earlier if \a ndays is
|
later than the datetime of this object (or earlier if \a ndays is
|
||||||
negative).
|
negative).
|
||||||
|
|
||||||
If the timeSpec() is Qt::LocalTime and the resulting
|
If the timeSpec() is Qt::LocalTime or Qt::TimeZone and the resulting
|
||||||
date and time fall in the Standard Time to Daylight-Saving Time transition
|
date and time fall in the Standard Time to Daylight-Saving Time transition
|
||||||
hour then the result will be adjusted accordingly, i.e. if the transition
|
hour then the result will be adjusted accordingly, i.e. if the transition
|
||||||
is at 2am and the clock goes forward to 3am and the result falls between
|
is at 2am and the clock goes forward to 3am and the result falls between
|
||||||
@ -4254,7 +4286,7 @@ QDateTime QDateTime::addDays(qint64 ndays) const
|
|||||||
later than the datetime of this object (or earlier if \a nmonths
|
later than the datetime of this object (or earlier if \a nmonths
|
||||||
is negative).
|
is negative).
|
||||||
|
|
||||||
If the timeSpec() is Qt::LocalTime and the resulting
|
If the timeSpec() is Qt::LocalTime or Qt::TimeZone and the resulting
|
||||||
date and time fall in the Standard Time to Daylight-Saving Time transition
|
date and time fall in the Standard Time to Daylight-Saving Time transition
|
||||||
hour then the result will be adjusted accordingly, i.e. if the transition
|
hour then the result will be adjusted accordingly, i.e. if the transition
|
||||||
is at 2am and the clock goes forward to 3am and the result falls between
|
is at 2am and the clock goes forward to 3am and the result falls between
|
||||||
@ -4279,7 +4311,7 @@ QDateTime QDateTime::addMonths(int nmonths) const
|
|||||||
later than the datetime of this object (or earlier if \a nyears is
|
later than the datetime of this object (or earlier if \a nyears is
|
||||||
negative).
|
negative).
|
||||||
|
|
||||||
If the timeSpec() is Qt::LocalTime and the resulting
|
If the timeSpec() is Qt::LocalTime or Qt::TimeZone and the resulting
|
||||||
date and time fall in the Standard Time to Daylight-Saving Time transition
|
date and time fall in the Standard Time to Daylight-Saving Time transition
|
||||||
hour then the result will be adjusted accordingly, i.e. if the transition
|
hour then the result will be adjusted accordingly, i.e. if the transition
|
||||||
is at 2am and the clock goes forward to 3am and the result falls between
|
is at 2am and the clock goes forward to 3am and the result falls between
|
||||||
|
@ -708,9 +708,7 @@ static QList<QTimeZonePrivate::Data> calculatePosixTransitions(const QByteArray
|
|||||||
}
|
}
|
||||||
|
|
||||||
const bool useStd = std.isValid() && std.date().year() == year && !stdZone.name.isEmpty();
|
const bool useStd = std.isValid() && std.date().year() == year && !stdZone.name.isEmpty();
|
||||||
const bool useDst = dst.isValid() && dst.date().year() == year && !dstZone.name.isEmpty()
|
const bool useDst = dst.isValid() && dst.date().year() == year && !dstZone.name.isEmpty();
|
||||||
// We ignore DST before 1970 -- for now.
|
|
||||||
&& dstData.atMSecsSinceEpoch >= 0;
|
|
||||||
if (useStd && useDst) {
|
if (useStd && useDst) {
|
||||||
if (dst < std)
|
if (dst < std)
|
||||||
result << dstData << stdData;
|
result << dstData << stdData;
|
||||||
|
@ -296,7 +296,7 @@ qint64 calculateTransitionForYear(const SYSTEMTIME &rule, int year, int bias)
|
|||||||
|
|
||||||
struct TransitionTimePair
|
struct TransitionTimePair
|
||||||
{
|
{
|
||||||
// Transition times after the epoch, in ms:
|
// Transition times, in ms:
|
||||||
qint64 std, dst;
|
qint64 std, dst;
|
||||||
// If either is invalidMSecs(), which shall then be < the other, there is no
|
// If either is invalidMSecs(), which shall then be < the other, there is no
|
||||||
// DST and the other describes a change in actual standard offset.
|
// DST and the other describes a change in actual standard offset.
|
||||||
|
@ -152,6 +152,7 @@ private Q_SLOTS:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
enum { LocalTimeIsUtc = 0, LocalTimeAheadOfUtc = 1, LocalTimeBehindUtc = -1} localTimeType;
|
enum { LocalTimeIsUtc = 0, LocalTimeAheadOfUtc = 1, LocalTimeBehindUtc = -1} localTimeType;
|
||||||
|
int preZoneFix;
|
||||||
bool zoneIsCET;
|
bool zoneIsCET;
|
||||||
|
|
||||||
class TimeZoneRollback
|
class TimeZoneRollback
|
||||||
@ -191,9 +192,9 @@ tst_QDateTime::tst_QDateTime()
|
|||||||
test thoroughly; ideally at every mid-winter or mid-summer in whose
|
test thoroughly; ideally at every mid-winter or mid-summer in whose
|
||||||
half-year any test below assumes zoneIsCET means what it says. (Tests at
|
half-year any test below assumes zoneIsCET means what it says. (Tests at
|
||||||
or near a DST transition implicate both of the half-years that meet
|
or near a DST transition implicate both of the half-years that meet
|
||||||
there.) Years outside the 1970--2038 range, however, are likely not
|
there.) Years outside the +ve half of 32-bit time_t's range, however,
|
||||||
properly handled by the TZ-database; and QDateTime explicitly handles them
|
might not be properly handled by our work-arounds for the MS backend and
|
||||||
differently, so don't probe them here.
|
32-bit time_t; so don't probe them here.
|
||||||
*/
|
*/
|
||||||
const uint day = 24 * 3600; // in seconds
|
const uint day = 24 * 3600; // in seconds
|
||||||
zoneIsCET = (QDateTime(QDate(2038, 1, 19), QTime(4, 14, 7)).toSecsSinceEpoch() == 0x7fffffff
|
zoneIsCET = (QDateTime(QDate(2038, 1, 19), QTime(4, 14, 7)).toSecsSinceEpoch() == 0x7fffffff
|
||||||
@ -214,12 +215,25 @@ tst_QDateTime::tst_QDateTime()
|
|||||||
&& QDateTime(QDate(1970, 1, 1), QTime(1, 0)).toSecsSinceEpoch() == 0);
|
&& QDateTime(QDate(1970, 1, 1), QTime(1, 0)).toSecsSinceEpoch() == 0);
|
||||||
// Use .toMSecsSinceEpoch() if you really need to test anything earlier.
|
// Use .toMSecsSinceEpoch() if you really need to test anything earlier.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Zones which currently appear to be CET may have distinct offsets before
|
||||||
|
the advent of time-zones. The date used here is the eve of the birth of
|
||||||
|
Dr. William Hyde Wollaston, who first proposed a uniform national time,
|
||||||
|
instead of local mean time:
|
||||||
|
*/
|
||||||
|
preZoneFix = zoneIsCET ? QDate(1766, 8, 5).startOfDay().offsetFromUtc() - 3600 : 0;
|
||||||
|
// Madrid, actually west of Greenwich, uses CET as if it were an hour east
|
||||||
|
// of Greenwich; allow that the fix might be more than an hour, either way:
|
||||||
|
Q_ASSERT(preZoneFix > -7200 && preZoneFix < 7200);
|
||||||
|
// So it's OK to add it to a QTime() between 02:00 and 22:00, but otherwise
|
||||||
|
// we must add it to the QDateTime constructed from it.
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Again, rule changes can cause a TZ to look like UTC at some sample dates
|
Again, rule changes can cause a TZ to look like UTC at some sample dates
|
||||||
but deviate at some date relevant to a test using localTimeType. These
|
but deviate at some date relevant to a test using localTimeType. These
|
||||||
tests mostly use years outside the 1970--2038 range for which TZ data is
|
tests mostly use years outside the 1970--2037 range, for which we trust
|
||||||
credible, so we can't helpfully be exhaustive. So scan a sample of years'
|
our TZ data, so we can't helpfully be exhaustive. Instead, scan a sample
|
||||||
starts and middles.
|
of years' starts and middles.
|
||||||
*/
|
*/
|
||||||
const int sampled = 3;
|
const int sampled = 3;
|
||||||
// UTC starts of months in 2004, 2038 and 1970:
|
// UTC starts of months in 2004, 2038 and 1970:
|
||||||
@ -238,15 +252,6 @@ tst_QDateTime::tst_QDateTime()
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
Even so, TZ=Africa/Algiers will fail fromMSecsSinceEpoch(-1) because it
|
|
||||||
switched from WET without DST (i.e. UTC) in the late 1960s to WET with DST
|
|
||||||
for all of 1970 - so they had a DST transition *on the epoch*. They've
|
|
||||||
since switched to CET with no DST, making life simple; but our tests for
|
|
||||||
mistakes around the epoch can't tell the difference between what Algeria
|
|
||||||
really did and the symptoms we can believe a bug might produce: there's
|
|
||||||
not much we can do about that, that wouldn't hide real bugs.
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void tst_QDateTime::initTestCase()
|
void tst_QDateTime::initTestCase()
|
||||||
@ -626,7 +631,7 @@ void tst_QDateTime::setMSecsSinceEpoch_data()
|
|||||||
QTest::newRow("old min (Tue Nov 25 00:00:00 -4714)")
|
QTest::newRow("old min (Tue Nov 25 00:00:00 -4714)")
|
||||||
<< Q_INT64_C(-210866716800000)
|
<< Q_INT64_C(-210866716800000)
|
||||||
<< QDateTime(QDate::fromJulianDay(1), QTime(0, 0), Qt::UTC)
|
<< QDateTime(QDate::fromJulianDay(1), QTime(0, 0), Qt::UTC)
|
||||||
<< QDateTime(QDate::fromJulianDay(1), QTime(1, 0));
|
<< QDateTime(QDate::fromJulianDay(1), QTime(1, 0)).addSecs(preZoneFix);
|
||||||
QTest::newRow("old max (Tue Jun 3 21:59:59 5874898)")
|
QTest::newRow("old max (Tue Jun 3 21:59:59 5874898)")
|
||||||
<< Q_INT64_C(185331720376799999)
|
<< Q_INT64_C(185331720376799999)
|
||||||
<< QDateTime(QDate::fromJulianDay(0x7fffffff), QTime(21, 59, 59, 999), Qt::UTC)
|
<< QDateTime(QDate::fromJulianDay(0x7fffffff), QTime(21, 59, 59, 999), Qt::UTC)
|
||||||
@ -634,7 +639,7 @@ void tst_QDateTime::setMSecsSinceEpoch_data()
|
|||||||
QTest::newRow("min")
|
QTest::newRow("min")
|
||||||
<< std::numeric_limits<qint64>::min()
|
<< std::numeric_limits<qint64>::min()
|
||||||
<< QDateTime(QDate(-292275056, 5, 16), QTime(16, 47, 4, 192), Qt::UTC)
|
<< QDateTime(QDate(-292275056, 5, 16), QTime(16, 47, 4, 192), Qt::UTC)
|
||||||
<< QDateTime(QDate(-292275056, 5, 16), QTime(17, 47, 4, 192), Qt::LocalTime);
|
<< QDateTime(QDate(-292275056, 5, 16), QTime(17, 47, 4, 192).addSecs(preZoneFix));
|
||||||
QTest::newRow("max")
|
QTest::newRow("max")
|
||||||
<< std::numeric_limits<qint64>::max()
|
<< std::numeric_limits<qint64>::max()
|
||||||
<< QDateTime(QDate(292278994, 8, 17), QTime(7, 12, 55, 807), Qt::UTC)
|
<< QDateTime(QDate(292278994, 8, 17), QTime(7, 12, 55, 807), Qt::UTC)
|
||||||
@ -688,8 +693,11 @@ void tst_QDateTime::setMSecsSinceEpoch()
|
|||||||
localDt.setMSecsSinceEpoch(msecs);
|
localDt.setMSecsSinceEpoch(msecs);
|
||||||
|
|
||||||
// LocalTime will overflow for max
|
// LocalTime will overflow for max
|
||||||
if (msecs != std::numeric_limits<qint64>::max())
|
if (msecs != std::numeric_limits<qint64>::max()
|
||||||
|
//... or for min, if this CET zone is west of Greenwich (Europe/Madrid)
|
||||||
|
&& (preZoneFix >= -3600 || msecs != std::numeric_limits<qint64>::min())) {
|
||||||
QCOMPARE(localDt, utc);
|
QCOMPARE(localDt, utc);
|
||||||
|
}
|
||||||
QCOMPARE(localDt.timeSpec(), Qt::LocalTime);
|
QCOMPARE(localDt.timeSpec(), Qt::LocalTime);
|
||||||
|
|
||||||
// Compare result for LocalTime to TimeZone
|
// Compare result for LocalTime to TimeZone
|
||||||
@ -699,12 +707,13 @@ void tst_QDateTime::setMSecsSinceEpoch()
|
|||||||
dt2.setTimeZone(europe);
|
dt2.setTimeZone(europe);
|
||||||
#endif
|
#endif
|
||||||
dt2.setMSecsSinceEpoch(msecs);
|
dt2.setMSecsSinceEpoch(msecs);
|
||||||
QCOMPARE(dt2.date(), cet.date());
|
if (cet.date().year() >= 1970 || cet.date() == utc.date())
|
||||||
|
QCOMPARE(dt2.date(), cet.date());
|
||||||
|
|
||||||
// don't compare the time if the date is too early or too late: prior
|
// Don't compare the time if the date is too early: prior to the early
|
||||||
// to 1916, timezones in Europe were not standardised and some OS APIs
|
// 20th century, timezones in Europe were not standardised. Limit to the
|
||||||
// have hard limits. Let's restrict it to the 32-bit Unix range
|
// same year-range as we used when determining zoneIsCET:
|
||||||
if (dt2.date().year() >= 1970 && dt2.date().year() <= 2037)
|
if (cet.date().year() >= 1970 && cet.date().year() <= 2037)
|
||||||
QCOMPARE(dt2.time(), cet.time());
|
QCOMPARE(dt2.time(), cet.time());
|
||||||
#if QT_CONFIG(timezone)
|
#if QT_CONFIG(timezone)
|
||||||
QCOMPARE(dt2.timeSpec(), Qt::TimeZone);
|
QCOMPARE(dt2.timeSpec(), Qt::TimeZone);
|
||||||
@ -736,17 +745,20 @@ void tst_QDateTime::fromMSecsSinceEpoch()
|
|||||||
QFETCH(qint64, msecs);
|
QFETCH(qint64, msecs);
|
||||||
QFETCH(QDateTime, utc);
|
QFETCH(QDateTime, utc);
|
||||||
QFETCH(QDateTime, cet);
|
QFETCH(QDateTime, cet);
|
||||||
|
using Bound = std::numeric_limits<qint64>;
|
||||||
|
if (msecs == Bound::min())
|
||||||
|
qDebug() << "Local overflow:" << preZoneFix << Qt::hex;
|
||||||
|
|
||||||
QDateTime dtLocal = QDateTime::fromMSecsSinceEpoch(msecs, Qt::LocalTime);
|
QDateTime dtLocal = QDateTime::fromMSecsSinceEpoch(msecs, Qt::LocalTime);
|
||||||
QDateTime dtUtc = QDateTime::fromMSecsSinceEpoch(msecs, Qt::UTC);
|
QDateTime dtUtc = QDateTime::fromMSecsSinceEpoch(msecs, Qt::UTC);
|
||||||
QDateTime dtOffset = QDateTime::fromMSecsSinceEpoch(msecs, Qt::OffsetFromUTC, 60*60);
|
QDateTime dtOffset = QDateTime::fromMSecsSinceEpoch(msecs, Qt::OffsetFromUTC, 60*60);
|
||||||
using Bound = std::numeric_limits<qint64>;
|
|
||||||
// LocalTime will overflow for "min" or "max" tests, depending on whether
|
// LocalTime will overflow for "min" or "max" tests, depending on whether
|
||||||
// you're East or West of Greenwich. In UTC, we won't overflow.
|
// you're East or West of Greenwich. In UTC, we won't overflow. If we're
|
||||||
const bool localOverflow = (localTimeType == LocalTimeAheadOfUtc ? msecs == Bound::max()
|
// actually west of Greenwich but (e.g. Europe/Madrid) our zone claims east,
|
||||||
: localTimeType == LocalTimeBehindUtc ? msecs == Bound::min()
|
// "min" can also overflow (case only caught if local time is CET).
|
||||||
: false);
|
const bool localOverflow = (localTimeType == LocalTimeAheadOfUtc
|
||||||
|
? msecs == Bound::max() || preZoneFix < -3600
|
||||||
|
: localTimeType == LocalTimeBehindUtc && msecs == Bound::min());
|
||||||
if (!localOverflow)
|
if (!localOverflow)
|
||||||
QCOMPARE(dtLocal, utc);
|
QCOMPARE(dtLocal, utc);
|
||||||
|
|
||||||
@ -1439,8 +1451,9 @@ void tst_QDateTime::toTimeSpec_data()
|
|||||||
<< QDateTime(QDate(2004, 1, 1), localStandardTime, Qt::LocalTime);
|
<< QDateTime(QDate(2004, 1, 1), localStandardTime, Qt::LocalTime);
|
||||||
QTest::newRow("winter2") << QDateTime(QDate(2004, 2, 29), utcTime, Qt::UTC)
|
QTest::newRow("winter2") << QDateTime(QDate(2004, 2, 29), utcTime, Qt::UTC)
|
||||||
<< QDateTime(QDate(2004, 2, 29), localStandardTime, Qt::LocalTime);
|
<< QDateTime(QDate(2004, 2, 29), localStandardTime, Qt::LocalTime);
|
||||||
QTest::newRow("winter3") << QDateTime(QDate(1760, 2, 29), utcTime, Qt::UTC)
|
QTest::newRow("winter3")
|
||||||
<< QDateTime(QDate(1760, 2, 29), localStandardTime, Qt::LocalTime);
|
<< QDateTime(QDate(1760, 2, 29), utcTime, Qt::UTC)
|
||||||
|
<< QDateTime(QDate(1760, 2, 29), localStandardTime.addSecs(preZoneFix));
|
||||||
QTest::newRow("winter4") << QDateTime(QDate(6000, 2, 29), utcTime, Qt::UTC)
|
QTest::newRow("winter4") << QDateTime(QDate(6000, 2, 29), utcTime, Qt::UTC)
|
||||||
<< QDateTime(QDate(6000, 2, 29), localStandardTime, Qt::LocalTime);
|
<< QDateTime(QDate(6000, 2, 29), localStandardTime, Qt::LocalTime);
|
||||||
|
|
||||||
@ -1454,16 +1467,17 @@ void tst_QDateTime::toTimeSpec_data()
|
|||||||
|
|
||||||
QTest::newRow("-271821/4/20 00:00 UTC (JavaScript min date, start of day)")
|
QTest::newRow("-271821/4/20 00:00 UTC (JavaScript min date, start of day)")
|
||||||
<< QDateTime(QDate(-271821, 4, 20), QTime(0, 0), Qt::UTC)
|
<< QDateTime(QDate(-271821, 4, 20), QTime(0, 0), Qt::UTC)
|
||||||
<< QDateTime(QDate(-271821, 4, 20), QTime(1, 0), Qt::LocalTime);
|
<< QDateTime(QDate(-271821, 4, 20), QTime(1, 0)).addSecs(preZoneFix);
|
||||||
QTest::newRow("-271821/4/20 23:00 UTC (JavaScript min date, end of day)")
|
QTest::newRow("-271821/4/20 23:00 UTC (JavaScript min date, end of day)")
|
||||||
<< QDateTime(QDate(-271821, 4, 20), QTime(23, 0), Qt::UTC)
|
<< QDateTime(QDate(-271821, 4, 20), QTime(23, 0), Qt::UTC)
|
||||||
<< QDateTime(QDate(-271821, 4, 21), QTime(0, 0), Qt::LocalTime);
|
<< QDateTime(QDate(-271821, 4, 21), QTime(0, 0)).addSecs(preZoneFix);
|
||||||
|
|
||||||
if (zoneIsCET) {
|
if (zoneIsCET) {
|
||||||
QTest::newRow("summer1") << QDateTime(QDate(2004, 6, 30), utcTime, Qt::UTC)
|
QTest::newRow("summer1") << QDateTime(QDate(2004, 6, 30), utcTime, Qt::UTC)
|
||||||
<< QDateTime(QDate(2004, 6, 30), localDaylightTime, Qt::LocalTime);
|
<< QDateTime(QDate(2004, 6, 30), localDaylightTime, Qt::LocalTime);
|
||||||
QTest::newRow("summer2") << QDateTime(QDate(1760, 6, 30), utcTime, Qt::UTC)
|
QTest::newRow("summer2")
|
||||||
<< QDateTime(QDate(1760, 6, 30), localStandardTime, Qt::LocalTime);
|
<< QDateTime(QDate(1760, 6, 30), utcTime, Qt::UTC)
|
||||||
|
<< QDateTime(QDate(1760, 6, 30), localStandardTime.addSecs(preZoneFix));
|
||||||
QTest::newRow("summer3") << QDateTime(QDate(4000, 6, 30), utcTime, Qt::UTC)
|
QTest::newRow("summer3") << QDateTime(QDate(4000, 6, 30), utcTime, Qt::UTC)
|
||||||
<< QDateTime(QDate(4000, 6, 30), localDaylightTime, Qt::LocalTime);
|
<< QDateTime(QDate(4000, 6, 30), localDaylightTime, Qt::LocalTime);
|
||||||
|
|
||||||
@ -3049,15 +3063,15 @@ void tst_QDateTime::zoneAtTime_data()
|
|||||||
ADDROW("epoch:EST", "America/New_York", epoch, -5 * 3600);
|
ADDROW("epoch:EST", "America/New_York", epoch, -5 * 3600);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// QDateTime deliberately ignores DST before the epoch.
|
// QDateTime now takes account of DST even before the epoch.
|
||||||
QDate summer69(1969, 8, 15); // Woodstock started
|
QDate summer69(1969, 8, 15); // Woodstock started
|
||||||
ADDROW("summer69:UTC", "UTC", summer69, 0);
|
ADDROW("summer69:UTC", "UTC", summer69, 0);
|
||||||
ADDROW("summer69:CET", "Europe/Rome", summer69, 3600);
|
ADDROW("summer69:CET", "Europe/Rome", summer69, 2 * 3600);
|
||||||
ADDROW("summer69:PST", "America/Vancouver", summer69, -8 * 3600);
|
ADDROW("summer69:PST", "America/Vancouver", summer69, -7 * 3600);
|
||||||
ADDROW("summer69:EST", "America/New_York", summer69, -5 * 3600);
|
ADDROW("summer69:EST", "America/New_York", summer69, -4 * 3600);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// ... but takes it into account after:
|
// ... and has always taken it into account since:
|
||||||
QDate summer70(1970, 8, 26); // Isle of Wight festival
|
QDate summer70(1970, 8, 26); // Isle of Wight festival
|
||||||
ADDROW("summer70:UTC", "UTC", summer70, 0);
|
ADDROW("summer70:UTC", "UTC", summer70, 0);
|
||||||
ADDROW("summer70:CET", "Europe/Rome", summer70, 2 * 3600);
|
ADDROW("summer70:CET", "Europe/Rome", summer70, 2 * 3600);
|
||||||
@ -3097,10 +3111,7 @@ void tst_QDateTime::zoneAtTime()
|
|||||||
QTimeZone zone(ianaID);
|
QTimeZone zone(ianaID);
|
||||||
QVERIFY(zone.isValid());
|
QVERIFY(zone.isValid());
|
||||||
QCOMPARE(QDateTime(date, noon, zone).offsetFromUtc(), offset);
|
QCOMPARE(QDateTime(date, noon, zone).offsetFromUtc(), offset);
|
||||||
if (date.year() < 1970)
|
QCOMPARE(zone.offsetFromUtc(QDateTime(date, noon, zone)), offset);
|
||||||
QCOMPARE(zone.standardTimeOffset(QDateTime(date, noon, zone)), offset);
|
|
||||||
else // zone.offsetFromUtc *does* include DST, even before epoch
|
|
||||||
QCOMPARE(zone.offsetFromUtc(QDateTime(date, noon, zone)), offset);
|
|
||||||
#else
|
#else
|
||||||
QSKIP("Needs timezone feature enabled");
|
QSKIP("Needs timezone feature enabled");
|
||||||
#endif
|
#endif
|
||||||
|
@ -328,13 +328,10 @@ void tst_QTimeZone::systemZone()
|
|||||||
QCOMPARE(zone, QTimeZone(QTimeZone::systemTimeZoneId()));
|
QCOMPARE(zone, QTimeZone(QTimeZone::systemTimeZoneId()));
|
||||||
// Check it behaves the same as local-time:
|
// Check it behaves the same as local-time:
|
||||||
const QDate dates[] = {
|
const QDate dates[] = {
|
||||||
#if 0 // QTBUG-80421
|
|
||||||
QDate::fromJulianDay(0), // far in the distant past (LMT)
|
QDate::fromJulianDay(0), // far in the distant past (LMT)
|
||||||
QDate(1625, 6, 8), // Before time-zones (date of Cassini's birth)
|
QDate(1625, 6, 8), // Before time-zones (date of Cassini's birth)
|
||||||
QDate(1901, 12, 13), // Last day before 32-bit time_t's range
|
QDate(1901, 12, 13), // Last day before 32-bit time_t's range
|
||||||
#elif !defined(Q_OS_WIN)
|
|
||||||
QDate(1969, 12, 31), // Last day before the epoch
|
QDate(1969, 12, 31), // Last day before the epoch
|
||||||
#endif
|
|
||||||
QDate(1970, 0, 0), // Start of epoch
|
QDate(1970, 0, 0), // Start of epoch
|
||||||
QDate(2000, 2, 29), // An anomalous leap day
|
QDate(2000, 2, 29), // An anomalous leap day
|
||||||
QDate(2038, 1, 20) // First day after 32-bit time_t's range
|
QDate(2038, 1, 20) // First day after 32-bit time_t's range
|
||||||
|
Loading…
Reference in New Issue
Block a user