aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEdward Welbourne <edward.welbourne@qt.io>2021-12-09 16:30:03 +0100
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2022-06-10 20:41:00 +0000
commit3033e99fa88ae4ed2452c4e38fab6386848a63a5 (patch)
tree07cc9d1bf0dfa276fa80db30b0ec3b4b8152a7d5
parent9f9743c8b4c0fc7bdd45b4f8f8607735d463a601 (diff)
Use qtbase's new (private) QLocalTime for Date's UTC offsets
This puts all use of system time_t functions in one place, instead of spreading it out. Its implementation improves on what was formerly done in V4 Date's offset calculations, while simplifying them and eliminating most of the #if-ery. Add four more test-cases to tst_qqmlqt::dateTimeConversion(), based on issues seen on MinGW, getting the time-zone wrong due to the failure of localtime_r(); MinGW can use localtime_s, as QLocalTime now does. Revised tst_qqmllocale::timeZoneUpdated()'s conditions. The QEXPECT_FAIL()s have stopped triggering, at least on Darwin, and the issue isn't that Date.timeZoneUpdated() wasn't working, it's that (now only on Android and Windows) we don't have a way to set the system time-zone referenced by the system functions that QLocalTime calls to get time-zone offsets. Setting the TZ environment variable only works on faithful POSIX implementations. Fixes: QTBUG-85149 Fixes: QTBUG-95993 Fixes: QTBUG-102971 Change-Id: I7bc983b9fd7167e3bab3db41dbc1c6f4a78665b9 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io> (cherry picked from commit dff02466a01caad885b6bd0759cf482342332306) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
-rw-r--r--src/qml/jsruntime/qv4dateobject.cpp137
-rw-r--r--tests/auto/qml/qqmllocale/tst_qqmllocale.cpp84
-rw-r--r--tests/auto/qml/qqmlqt/data/dateTimeConversion.qml5
-rw-r--r--tests/auto/qml/qqmlqt/tst_qqmlqt.cpp12
4 files changed, 55 insertions, 183 deletions
diff --git a/src/qml/jsruntime/qv4dateobject.cpp b/src/qml/jsruntime/qv4dateobject.cpp
index d366f5909a..c0bf5f9afa 100644
--- a/src/qml/jsruntime/qv4dateobject.cpp
+++ b/src/qml/jsruntime/qv4dateobject.cpp
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2021 The Qt Company Ltd.
+** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtQml module of the Qt Toolkit.
@@ -37,7 +37,6 @@
**
****************************************************************************/
-
#include "qv4dateobject_p.h"
#include "qv4objectproto_p.h"
#include "qv4scopedvalue_p.h"
@@ -48,64 +47,11 @@
#include <QtCore/QDebug>
#include <QtCore/QDateTime>
+#include <QtCore/private/qlocaltime_p.h>
#include <QtCore/QStringList>
-#include <time.h>
-
#include <wtf/MathExtras.h>
-#if QT_CONFIG(timezone) && !defined(Q_OS_WIN)
-/*
- 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.
-
- QTimeZone::systemTimeZone() will automatically produce an updated value on
- non-android Linux systems when the TZ environment variable or the relevant
- files in /etc are changed. On other platforms it won't, and the information
- produced here may be incorrect after changes to the system time zone.
-
- We accept this defect for now because the localtime_r approach will
- consistently produce incorrect results for some time zones, not only when
- the system time zone changes. This is a worse problem, see also QTBUG-84474.
-
- On windows we have a better implementation of getLocalTZA that hopefully
- updates on time zone changes. However, we currently use the worse
- implementation of DaylightSavingTA (returning either an hour or 0).
-
- QTimeZone::systemTimeZone() on Linux is also slower than other approaches
- because it has to poll the relevant files (if TZ is not set). See
- QTBUG-75585 for an explanation and possible workarounds.
- */
-#define USE_QTZ_SYSTEM_ZONE
-#elif defined(Q_OS_WASM)
-/*
- TODO: evaluate using this version of the code more generally, rather than
- the #else branches of the various USE_QTZ_SYSTEM_ZONE choices. It might even
- work better than the timezone variant; experiments needed.
-*/
-// Kludge around the lack of time-zone info using QDateTime.
-// It uses localtime() and friends to determine offsets from UTC.
-#define USE_QDT_LOCAL_TIME
-#endif
-
-#ifdef USE_QTZ_SYSTEM_ZONE
-#include <QtCore/QTimeZone>
-#elif defined(USE_QDT_LOCAL_TIME)
-// QDateTime already included above
-#else
-# ifdef Q_OS_WIN
-# include <qt_windows.h>
-# else
-# ifndef Q_OS_VXWORKS
-# include <sys/time.h>
-# else
-# include "qplatformdefs.h"
-# endif
-# include <unistd.h> // for _POSIX_THREAD_SAFE_FUNCTIONS
-# endif
-#endif // USE_QTZ_SYSTEM_ZONE
-
using namespace QV4;
static const double HoursPerDay = 24.0;
@@ -351,7 +297,6 @@ 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
@@ -369,42 +314,10 @@ static inline double MakeDate(double day, double time)
against the ECMAScript spec is https://github.com/tc39/ecma262/issues/725
and they've now changed the spec so that the following conforms to it ;^>
*/
-
-static inline double DaylightSavingTA(double t, double localTZA) // t is a UTC time
-{
- return QTimeZone::systemTimeZone().offsetFromUtc(
- QDateTime::fromMSecsSinceEpoch(qint64(t), Qt::UTC)) * 1e3 - localTZA;
-}
-#elif defined(USE_QDT_LOCAL_TIME)
static inline double DaylightSavingTA(double t, double localTZA) // t is a UTC time
{
- return QDateTime::fromMSecsSinceEpoch(qint64(t), Qt::UTC
- ).toLocalTime().offsetFromUtc() * 1e3 - localTZA;
-}
-#else
-// This implementation fails to take account of past changes in standard offset.
-static inline double DaylightSavingTA(double t, double /*localTZA*/)
-{
- struct tm tmtm;
-#if defined(Q_CC_MSVC)
- __time64_t tt = (__time64_t)(t / msPerSecond);
- // _localtime_64_s returns non-zero on failure
- if (_localtime64_s(&tmtm, &tt) != 0)
-#elif !defined(QT_NO_THREAD) && defined(_POSIX_THREAD_SAFE_FUNCTIONS)
- long int tt = (long int)(t / msPerSecond);
- if (!localtime_r((const time_t*) &tt, &tmtm))
-#else
- // Returns shared static data which may be overwritten at any time
- // (for MinGW/Windows localtime is luckily thread safe)
- long int tt = (long int)(t / msPerSecond);
- if (struct tm *tmtm_p = localtime((const time_t*) &tt))
- tmtm = *tmtm_p;
- else
-#endif
- return 0;
- return (tmtm.tm_isdst > 0) ? msPerHour : 0;
+ return QLocalTime::getUtcOffset(qint64(t)) * 1e3 - localTZA;
}
-#endif // USE_QTZ_SYSTEM_ZONE
static inline double LocalTime(double t, double localTZA)
{
@@ -735,49 +648,7 @@ 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;
-#elif defined(USE_QDT_LOCAL_TIME)
- QDate today = QDate::currentDate();
- QDateTime near = today.startOfDay(Qt::LocalTime);
- // Early out if we're in standard time anyway:
- if (!near.isDaylightTime())
- return near.offsetFromUtc() * 1000;
- int year, month;
- today.getDate(&year, &month, nullptr);
- // One of the solstices is probably in standard time:
- QDate summer(year, 6, 21), winter(year - (month < 7 ? 1 : 0), 12, 21);
- // But check the one closest to the present by preference, in case there's a
- // standard time offset change between them:
- QDateTime far = summer.startOfDay(Qt::LocalTime);
- near = winter.startOfDay(Qt::LocalTime);
- if (month > 3 && month < 10)
- near.swap(far);
- bool isDst = near.isDaylightTime();
- if (isDst && far.isDaylightTime()) // Permanent DST, probably an hour west:
- return (qMin(near.offsetFromUtc(), far.offsetFromUtc()) - 3600) * 1000;
- return (isDst ? far : near).offsetFromUtc() * 1000;
-#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;
- time(&curr);
- 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;
-# endif
-#endif // USE_QTZ_SYSTEM_ZONE
+ return QLocalTime::getCurrentStandardUtcOffset() * 1e3;
}
DEFINE_OBJECT_VTABLE(DateObject);
diff --git a/tests/auto/qml/qqmllocale/tst_qqmllocale.cpp b/tests/auto/qml/qqmllocale/tst_qqmllocale.cpp
index 7ef0275394..d6777465f2 100644
--- a/tests/auto/qml/qqmllocale/tst_qqmllocale.cpp
+++ b/tests/auto/qml/qqmllocale/tst_qqmllocale.cpp
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
@@ -41,6 +41,35 @@
#include <time.h>
+#undef QT_CAN_CHANGE_SYSTEM_ZONE
+/* See QTBUG-56899. We don't (yet) have a proper way to reset the system zone,
+ Testing Date.timeZoneUpdated() is only possible on systems where we have a
+ way to change the system zone in use.
+*/
+#ifdef Q_OS_ANDROID
+/* Android's time_t-related system functions don't seem to care about the TZ
+ environment variable. If we can find a way to change the zone those functions
+ use, try implementing it here.
+*/
+#elif defined(Q_OS_UNIX)
+static void setTimeZone(const QByteArray &tz)
+{
+ if (tz.isEmpty())
+ qunsetenv("TZ");
+ else
+ qputenv("TZ", tz);
+ ::tzset();
+}
+#define QT_CAN_CHANGE_SYSTEM_ZONE
+#else
+/* On Windows, adjusting the timezone (such that GetTimeZoneInformation() will
+ notice) requires additional privileges that aren't normally enabled for a
+ process. This can be achieved by calling AdjustTokenPrivileges() and then
+ SetTimeZoneInformation(), which will require linking to a different library
+ to access that API.
+*/
+#endif
+
class tst_qqmllocale : public QQmlDataTest
{
Q_OBJECT
@@ -74,7 +103,7 @@ private slots:
void dateTimeFormat();
void timeFormat_data();
void timeFormat();
-#if defined(Q_OS_UNIX) && QT_CONFIG(timezone)
+#ifdef QT_CAN_CHANGE_SYSTEM_ZONE
void timeZoneUpdated();
#endif
void formattedDataSize_data();
@@ -1370,6 +1399,7 @@ void tst_qqmllocale::localeAsCppProperty()
QCOMPARE(item->property("testLocale").toLocale().name(), QLatin1String("nb_NO"));
}
+#ifdef QT_CAN_CHANGE_SYSTEM_ZONE
class DateFormatter : public QObject
{
Q_OBJECT
@@ -1386,49 +1416,21 @@ QString DateFormatter::getLocalizedForm(const QString &isoTimestamp)
return locale.toString(input);
}
-#if defined(Q_OS_UNIX) && QT_CONFIG(timezone)
-// Currently disabled on Windows as adjusting the timezone
-// requires additional privileges that aren't normally
-// enabled for a process. This can be achieved by calling
-// AdjustTokenPrivileges() and then SetTimeZoneInformation(),
-// which will require linking to a different library to access that API.
-static void setTimeZone(const QByteArray &tz)
-{
- if (tz.isEmpty())
- qunsetenv("TZ");
- else
- qputenv("TZ", tz);
- ::tzset();
-
-// following left for future reference, see comment above
-// #if defined(Q_OS_WIN32)
-// ::_tzset();
-// #endif
-}
-
void tst_qqmllocale::timeZoneUpdated()
{
- // Note: This test may not reliably hit the QEXPECT_FAIL clauses below if the initial
- // system time zone is equivalent to either Australia/Brisbane or Asia/Kalkota.
-
- // Initialize the system time zone, so that we actually _change_ something below.
- QVERIFY2(QTimeZone::systemTimeZone().isValid(),
- "You know, Toto, I do believe we're not in Kansas any more.");
-
QByteArray original(qgetenv("TZ"));
-
- // Set the timezone to Brisbane time, AEST-10:00
- setTimeZone(QByteArray("Australia/Brisbane"));
-
QScopedPointer<QObject> obj;
+
auto cleanup = qScopeGuard([&original, &obj] {
// Restore to original time zone
setTimeZone(original);
QMetaObject::invokeMethod(obj.data(), "resetTimeZone");
});
- DateFormatter formatter;
+ // Set the timezone to Brisbane time, AEST-10:00
+ setTimeZone(QByteArray("Australia/Brisbane"));
+ DateFormatter formatter;
QQmlEngine e;
e.rootContext()->setContextObject(&formatter);
@@ -1436,27 +1438,15 @@ void tst_qqmllocale::timeZoneUpdated()
QVERIFY2(!c.isError(), qPrintable(c.errorString()));
obj.reset(c.create());
QVERIFY(obj);
-
-#if (!defined(Q_OS_LINUX) && !defined(Q_OS_QNX)) || defined(Q_OS_ANDROID)
- QEXPECT_FAIL("",
- "Date.timeZoneUpdated() only works on non-Android Linux with QT_CONFIG(timezone).",
- Continue);
-#endif
QVERIFY(obj->property("success").toBool());
// Change to Indian time, IST-05:30
setTimeZone(QByteArray("Asia/Kolkata"));
QMetaObject::invokeMethod(obj.data(), "check");
-
-#if (!defined(Q_OS_LINUX) && !defined(Q_OS_QNX)) || defined(Q_OS_ANDROID)
- QEXPECT_FAIL("",
- "Date.timeZoneUpdated() only works on non-Android Linux with QT_CONFIG(timezone).",
- Continue);
-#endif
QVERIFY(obj->property("success").toBool());
}
-#endif // Unix && timezone
+#endif // QT_CAN_CHANGE_SYSTEM_ZONE
QTEST_MAIN(tst_qqmllocale)
diff --git a/tests/auto/qml/qqmlqt/data/dateTimeConversion.qml b/tests/auto/qml/qqmlqt/data/dateTimeConversion.qml
index 300074dec1..7e0d194b3a 100644
--- a/tests/auto/qml/qqmlqt/data/dateTimeConversion.qml
+++ b/tests/auto/qml/qqmlqt/data/dateTimeConversion.qml
@@ -6,6 +6,11 @@ QtObject {
property variant qdate: new Date(2008,11,24) // 2008/12/24 hh:mm:ss.zzz
property variant qdatetime: new Date(2008,11,24,14,15,38,200) // 2008/12/24 14:15:38.200
+ property variant qdatetime0: new Date(2021, 6) // 2021-07-01 00:00:00
+ property variant qdatetime0utc: new Date(Date.UTC(2021, 6)) // 2021-07-01 00:00:00 UTC
+ property variant qdatetime1: new Date(2021, 7, 0) // 2021-07-31 00:00:00
+ property variant qdatetime1utc: new Date(Date.UTC(2021, 7, 0)) // 2021-07-31 00:00:00 UTC
+
property variant qdatetime2: new Date(2852,11,31,23,59,59,500) // 2852/12/31 23:59:59.500
property variant qdatetime3: new Date(2000,0,1,0,0,0,0) // 2000/01/01 00:00:00.000
property variant qdatetime4: new Date(2001,1,2) // 2001/02/02 hh:mm:ss.zzz
diff --git a/tests/auto/qml/qqmlqt/tst_qqmlqt.cpp b/tests/auto/qml/qqmlqt/tst_qqmlqt.cpp
index 60a4c808c7..e0ad9bcd57 100644
--- a/tests/auto/qml/qqmlqt/tst_qqmlqt.cpp
+++ b/tests/auto/qml/qqmlqt/tst_qqmlqt.cpp
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
@@ -766,8 +766,10 @@ void tst_qqmlqt::dateTimeConversion()
QDate date(2008,12,24);
QTime time(14,15,38,200);
QDateTime dateTime(date, time);
- //Note that when converting Date to QDateTime they can argue over historical DST data when converting to local time.
- //Tests should use UTC or recent dates.
+ QDateTime dateTime0(QDate(2021, 7, 1).startOfDay());
+ QDateTime dateTime0utc(QDate(2021, 7, 1).startOfDay(Qt::UTC));
+ QDateTime dateTime1(QDate(2021, 7, 31).startOfDay());
+ QDateTime dateTime1utc(QDate(2021, 7, 31).startOfDay(Qt::UTC));
QDateTime dateTime2(QDate(2852,12,31), QTime(23,59,59,500));
QDateTime dateTime3(QDate(2000,1,1), QTime(0,0,0,0));
QDateTime dateTime4(QDate(2001,2,2), QTime(0,0,0,0));
@@ -785,6 +787,10 @@ void tst_qqmlqt::dateTimeConversion()
QCOMPARE(obj->property("qdate").toDate(), date);
QCOMPARE(obj->property("qtime").toTime(), time);
QCOMPARE(obj->property("qdatetime").toDateTime(), dateTime);
+ QCOMPARE(obj->property("qdatetime0").toDateTime(), dateTime0);
+ QCOMPARE(obj->property("qdatetime0utc").toDateTime(), dateTime0utc);
+ QCOMPARE(obj->property("qdatetime1").toDateTime(), dateTime1);
+ QCOMPARE(obj->property("qdatetime1utc").toDateTime(), dateTime1utc);
QCOMPARE(obj->property("qdatetime2").toDateTime(), dateTime2);
QCOMPARE(obj->property("qdatetime3").toDateTime(), dateTime3);
QCOMPARE(obj->property("qdatetime4").toDateTime(), dateTime4);