aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEdward Welbourne <edward.welbourne@qt.io>2016-10-28 17:24:46 +0200
committerEdward Welbourne <edward.welbourne@qt.io>2018-12-07 16:50:57 +0000
commit24af53cfeb998dcf3a6c6d49d312e50809a4a324 (patch)
tree62c7b94bc8ec2fa88521e06c59b1b3708d34f499
parent93c3844a84f0acd382be5021d2ebe896269b7277 (diff)
V4 Date: fix what we can within ECMA 262's limitations
Use QDateTime and QTimeZone to simplify code (when we can). Note which defective methods are so because the ECMAScript spec requires those defects, reference my bug report against those defects. Fix currentTime to return a UTC time. Fix getLocalTZA to actually deliver the standard offset, without daylight-saving corrections. Fix DaylightSavingTA(t) to return the difference between current standard time offset and pertinent total offset at time t (explaining why that's the least broken answer), rather than assuming DST is one hour when active. (In some places it's half an hour, in some places two hours; and the bugs in the spec break UTC() and LocalTime() if we only return actual DST offset, without adjusting for historical changes to standard offset.) Fix tests to use actual IANA IDs for zones. Change QTime-to-Time conversion to use a modernish date, for which time-zone data is likely to at least be consistently handled, instead of MakeDay(0, 0, 0); that's (nominal proleptic Gregorian) 2 BC, December 31st (represented denormally) for which any time-zone data we have is artificial and contrived. I chose Cassini's third centennary, as it's not likely to be near any zone transitions and is within the era of sane time-zone data. Task-number: QTBUG-72109 Task-number: QTBUG-56787 Change-Id: I5f21ee2010070a5c1798134fdd2915a723208fd9 Reviewed-by: Lars Knoll <lars.knoll@qt.io> (cherry picked from commit 2b8b7a162be52f8cd6c2bc39f498a1ddfb59dd68) Reviewed-by: Liang Qi <liang.qi@qt.io>
-rw-r--r--src/qml/jsruntime/qv4dateobject.cpp119
-rwxr-xr-xtests/auto/qml/ecmascripttests/test262.py4
-rw-r--r--tests/auto/qml/qqmllocale/tst_qqmllocale.cpp8
3 files changed, 86 insertions, 45 deletions
diff --git a/src/qml/jsruntime/qv4dateobject.cpp b/src/qml/jsruntime/qv4dateobject.cpp
index b90c335b1c..324c4059b0 100644
--- a/src/qml/jsruntime/qv4dateobject.cpp
+++ b/src/qml/jsruntime/qv4dateobject.cpp
@@ -54,16 +54,29 @@
#include <wtf/MathExtras.h>
-#ifdef Q_OS_WIN
-# include <windows.h>
+#ifdef Q_OS_LINUX
+/*
+ See QTBUG-56899. Although we don't (yet) have a proper way to reset the
+ system zone, the code below, that uses QTimeZone::systemTimeZone(), works
+ adequately on Linux, when the TZ environment variable is changed.
+ */
+#define USE_QTZ_SYSTEM_ZONE
+#endif
+
+#ifdef USE_QTZ_SYSTEM_ZONE
+#include <QtCore/QTimeZone>
#else
-# ifndef Q_OS_VXWORKS
-# include <sys/time.h>
+# ifdef Q_OS_WIN
+# include <windows.h>
# else
-# include "qplatformdefs.h"
+# ifndef Q_OS_VXWORKS
+# include <sys/time.h>
+# else
+# include "qplatformdefs.h"
+# endif
+# include <unistd.h> // for _POSIX_THREAD_SAFE_FUNCTIONS
# endif
-# include <unistd.h> // for _POSIX_THREAD_SAFE_FUNCTIONS
-#endif
+#endif // USE_QTZ_SYSTEM_ZONE
using namespace QV4;
@@ -75,6 +88,7 @@ static const double msPerMinute = 60000.0;
static const double msPerHour = 3600000.0;
static const double msPerDay = 86400000.0;
+// The current *standard* time offset, regardless of DST:
static double LocalTZA = 0.0; // initialized at startup
static inline double TimeWithinDay(double t)
@@ -285,6 +299,31 @@ static inline double MakeDate(double day, double time)
return day * msPerDay + time;
}
+#ifdef USE_QTZ_SYSTEM_ZONE
+/*
+ ECMAScript specifies use of a fixed (current, standard) time-zone offset,
+ LocalTZA; and LocalTZA + DaylightSavingTA(t) is taken to be (see LocalTime and
+ UTC, following) local time's offset from UTC at time t. For simple zones,
+ DaylightSavingTA(t) is thus the DST offset applicable at date/time t; however,
+ if a zone has changed its standard offset, the only way to make LocalTime and
+ UTC (if implemented in accord with the spec) perform correct transformations
+ is to have DaylightSavingTA(t) correct for the zone's standard offset change
+ as well as its actual DST offset.
+
+ This means we have to treat any historical changes in the zone's standard
+ offset as DST perturbations, regardless of historical reality. (This shall
+ mean a whole day of DST offset for some zones, that have crossed the
+ international date line. This shall confuse client code.) The bug report
+ against the ECMAScript spec is https://github.com/tc39/ecma262/issues/725
+*/
+
+static inline double DaylightSavingTA(double t) // t is a UTC time
+{
+ return QTimeZone::systemTimeZone().offsetFromUtc(
+ QDateTime::fromMSecsSinceEpoch(qint64(t), Qt::UTC)) * 1e3 - LocalTZA;
+}
+#else
+// This implementation fails to take account of past changes in standard offset.
static inline double DaylightSavingTA(double t)
{
struct tm tmtm;
@@ -306,34 +345,26 @@ static inline double DaylightSavingTA(double t)
return 0;
return (tmtm.tm_isdst > 0) ? msPerHour : 0;
}
+#endif // USE_QTZ_SYSTEM_ZONE
static inline double LocalTime(double t)
{
+ // Flawed, yet verbatim from the spec:
return t + LocalTZA + DaylightSavingTA(t);
}
+// The spec does note [*] that UTC and LocalTime are not quite mutually inverse.
+// [*] http://www.ecma-international.org/ecma-262/7.0/index.html#sec-utc-t
+
static inline double UTC(double t)
{
+ // Flawed, yet verbatim from the spec:
return t - LocalTZA - DaylightSavingTA(t - LocalTZA);
}
static inline double currentTime()
{
-#ifndef Q_OS_WIN
- struct timeval tv;
-
- gettimeofday(&tv, 0);
- return ::floor(tv.tv_sec * msPerSecond + (tv.tv_usec / 1000.0));
-#else
- SYSTEMTIME st;
- GetSystemTime(&st);
- FILETIME ft;
- SystemTimeToFileTime(&st, &ft);
- LARGE_INTEGER li;
- li.LowPart = ft.dwLowDateTime;
- li.HighPart = ft.dwHighDateTime;
- return double(li.QuadPart - Q_INT64_C(116444736000000000)) / 10000.0;
-#endif
+ return QDateTime::currentDateTimeUtc().toMSecsSinceEpoch();
}
static inline double TimeClip(double t)
@@ -616,20 +647,28 @@ static inline QString ToLocaleTimeString(double t)
static double getLocalTZA()
{
#ifndef Q_OS_WIN
+ tzset();
+#endif
+#ifdef USE_QTZ_SYSTEM_ZONE
+ // TODO: QTimeZone::resetSystemTimeZone(), see QTBUG-56899 and comment above.
+ // Standard offset, with no daylight-savings adjustment, in ms:
+ return QTimeZone::systemTimeZone().standardTimeOffset(QDateTime::currentDateTime()) * 1e3;
+#else
+# ifdef Q_OS_WIN
+ TIME_ZONE_INFORMATION tzInfo;
+ GetTimeZoneInformation(&tzInfo);
+ return -tzInfo.Bias * 60.0 * 1000.0;
+# else
struct tm t;
time_t curr;
- tzset();
time(&curr);
- localtime_r(&curr, &t);
+ localtime_r(&curr, &t); // Wrong: includes DST offset
time_t locl = mktime(&t);
gmtime_r(&curr, &t);
time_t globl = mktime(&t);
return (double(locl) - double(globl)) * 1000.0;
-#else
- TIME_ZONE_INFORMATION tzInfo;
- GetTimeZoneInformation(&tzInfo);
- return -tzInfo.Bias * 60.0 * 1000.0;
-#endif
+# endif
+#endif // USE_QTZ_SYSTEM_ZONE
}
DEFINE_OBJECT_VTABLE(DateObject);
@@ -648,17 +687,19 @@ void Heap::DateObject::init(const QTime &time)
return;
}
- /* All programmers know that stuff starts at 0. Whatever that may mean in this context (and
- * local timezone), it's before the epoch, so there is defenitely no DST problem. Specifically:
- * you can't start with a date before the epoch, add some[*] hours, and end up with a date
- * after. That's a problem for timezones where new year happens during DST, like
- * Australia/Hobart, because we have to ignore DST before the epoch (but honor it after the
- * epoch).
- *
- * [*] Well, when "some" is in the range 0-24. If you add something like 1M then this might
- * still happen.
+ /* We have to chose a date on which to instantiate this time. All we really
+ * care about is that it round-trips back to the same time if we extract the
+ * time from it, which shall (via toQDateTime(), below) discard the date
+ * part. We need a date for which time-zone data is likely to be sane (so
+ * MakeDay(0, 0, 0) was a bad choice; 2 BC, December 31st is before
+ * time-zones were standardized), with no transition nearby in date. We
+ * ignore DST transitions before 1970, but even then zone transitions did
+ * happen. Some do happen at new year, others on DST transitions in spring
+ * and autumn; so pick the three hundredth anniversary of the birth of
+ * Giovanni Domenico Cassini (1625-06-08), whose work first let us
+ * synchronize clocks tolerably accurately at distant locations.
*/
- static const double d = MakeDay(0, 0, 0);
+ static const double d = MakeDay(1925, 5, 8);
double t = MakeTime(time.hour(), time.minute(), time.second(), time.msec());
date = TimeClip(UTC(MakeDate(d, t)));
}
diff --git a/tests/auto/qml/ecmascripttests/test262.py b/tests/auto/qml/ecmascripttests/test262.py
index 437cd1b27d..ae4c54df9d 100755
--- a/tests/auto/qml/ecmascripttests/test262.py
+++ b/tests/auto/qml/ecmascripttests/test262.py
@@ -552,10 +552,10 @@ class TestSuite(object):
def Main():
- # Some date tests rely on being run in pacific time.
# Uncomment the next line for more logging info.
#logging.basicConfig(level=logging.DEBUG)
- os.environ["TZ"] = "PST8PDT"
+ # Some date tests rely on being run in pacific time and the USA's locale:
+ os.environ["TZ"] = "America/Vancouver"
os.environ["LANG"] = "en_US.UTF-8"
os.environ["LC_TIME"] = "en_US.UTF-8"
parser = BuildOptions()
diff --git a/tests/auto/qml/qqmllocale/tst_qqmllocale.cpp b/tests/auto/qml/qqmllocale/tst_qqmllocale.cpp
index d0ce83b997..4f4deaafe5 100644
--- a/tests/auto/qml/qqmllocale/tst_qqmllocale.cpp
+++ b/tests/auto/qml/qqmllocale/tst_qqmllocale.cpp
@@ -1268,8 +1268,8 @@ void tst_qqmllocale::timeZoneUpdated()
{
QByteArray original(qgetenv("TZ"));
- // Set the timezone to Brisbane time
- setTimeZone(QByteArray("AEST-10:00"));
+ // Set the timezone to Brisbane time, AEST-10:00
+ setTimeZone(QByteArray("Australia/Brisbane"));
DateFormatter formatter;
@@ -1281,8 +1281,8 @@ void tst_qqmllocale::timeZoneUpdated()
QVERIFY(obj);
QVERIFY(obj->property("success").toBool());
- // Change to Indian time
- setTimeZone(QByteArray("IST-05:30"));
+ // Change to Indian time, IST-05:30
+ setTimeZone(QByteArray("Asia/Kolkata"));
QMetaObject::invokeMethod(obj.data(), "check");