aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorErik Verbruggen <erik.verbruggen@qt.io>2016-07-08 16:40:28 +0200
committerSimon Hausmann <simon.hausmann@qt.io>2016-08-10 17:17:15 +0000
commit6e687e85719cf6e8cad218b0307749f7abd049a6 (patch)
tree6b3ac827dda0be334ecc50adcc3832be0781364c
parent8bf7cfb7880775f49dfbaf9a2be2202479eaaf99 (diff)
V4: Handle QTime->DateObject conversion better
By specification, date conversion functions for dates before the epoch are not DST corrected. We converted QTime to a QDateTime where we set the date part to Jan. 1, 1970, and then convert that to msecs since the epoch UTC. For places on Earth where they had DST on that day (e.g. Hobart in Australia), strange things happen: conversion from a QTime to DateObject will use DST (because it's after the epoch in local time), but conversions from DateObject to QTime won't use the DST because it's before the epoch (in UTC). Now as everyone knows, a 24-hour clock time has no meaning without a date, only "elapsed time" has. But users still expect to be able to pass QTime to QML/JS. So, we do the conversion on day 0 of month 0 of year 0, and all of it in local time. This gives a stable conversion in both directions, and the values in both C++ and QML/JS are the same for any timezone (with or without DST) on this planet. Task-number: QTBUG-54378 Change-Id: I892e16a93f015e92d311c6cae3ae7768b7373f6a Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
-rw-r--r--src/qml/doc/src/cppintegration/data.qdoc12
-rw-r--r--src/qml/jsruntime/qv4dateobject.cpp22
-rw-r--r--src/qml/jsruntime/qv4dateobject_p.h2
-rw-r--r--src/qml/jsruntime/qv4engine.cpp9
-rw-r--r--src/qml/jsruntime/qv4engine_p.h1
-rw-r--r--tests/auto/qml/qqmlqt/data/timeRoundtrip.qml8
-rw-r--r--tests/auto/qml/qqmlqt/tst_qqmlqt.cpp112
7 files changed, 163 insertions, 3 deletions
diff --git a/src/qml/doc/src/cppintegration/data.qdoc b/src/qml/doc/src/cppintegration/data.qdoc
index 7bb4d701e2..cc2fe90483 100644
--- a/src/qml/doc/src/cppintegration/data.qdoc
+++ b/src/qml/doc/src/cppintegration/data.qdoc
@@ -249,6 +249,18 @@ parameter, the value can be created as a JavaScript \c Date object in QML, and
is automatically converted to a QDateTime value when it is passed to C++.
+\section2 QTime to JavaScript Date
+
+The QML engine provides automatic type conversion from QTime values to
+JavaScript \c Date objects. The date component of the resulting Date
+object should not be relied upon, as it is operating system dependent.
+Specifically, the year (and month and day) are set to zero. Conversion
+from a JavaScript \c Date object to QTime is done by converting to a
+QDateTime, and then relying on QVariant to convert it to a QTime. The end
+effect is that the date part of the \c Date object is ignored, but the
+local timezone will be used ignoring any DST complications it may have.
+
+
\section2 Sequence Type to JavaScript Array
Certain C++ sequence types are supported transparently in QML as JavaScript
diff --git a/src/qml/jsruntime/qv4dateobject.cpp b/src/qml/jsruntime/qv4dateobject.cpp
index df648ba9ee..04358fe3b5 100644
--- a/src/qml/jsruntime/qv4dateobject.cpp
+++ b/src/qml/jsruntime/qv4dateobject.cpp
@@ -639,6 +639,28 @@ Heap::DateObject::DateObject(const QDateTime &date)
this->date = date.isValid() ? date.toMSecsSinceEpoch() : qt_qnan();
}
+Heap::DateObject::DateObject(const QTime &time)
+{
+ if (!time.isValid()) {
+ date = qt_qnan();
+ 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.
+ */
+ static const double d = MakeDay(0, 0, 0);
+ double t = MakeTime(time.hour(), time.minute(), time.second(), time.msec());
+ date = TimeClip(UTC(MakeDate(d, t)));
+}
+
QDateTime DateObject::toQDateTime() const
{
return ToDateTime(date(), Qt::LocalTime);
diff --git a/src/qml/jsruntime/qv4dateobject_p.h b/src/qml/jsruntime/qv4dateobject_p.h
index 13e9e04040..c67acdcfa2 100644
--- a/src/qml/jsruntime/qv4dateobject_p.h
+++ b/src/qml/jsruntime/qv4dateobject_p.h
@@ -74,6 +74,8 @@ struct DateObject : Object {
}
DateObject(const QDateTime &date);
double date;
+
+ DateObject(const QTime &time);
};
diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp
index 20294700f6..27397fe3d8 100644
--- a/src/qml/jsruntime/qv4engine.cpp
+++ b/src/qml/jsruntime/qv4engine.cpp
@@ -621,6 +621,13 @@ Heap::DateObject *ExecutionEngine::newDateObject(const QDateTime &dt)
return object->d();
}
+Heap::DateObject *ExecutionEngine::newDateObjectFromTime(const QTime &t)
+{
+ Scope scope(this);
+ Scoped<DateObject> object(scope, memoryManager->allocObject<DateObject>(t));
+ return object->d();
+}
+
Heap::RegExpObject *ExecutionEngine::newRegExpObject(const QString &pattern, int flags)
{
bool global = (flags & IR::RegExp::RegExp_Global);
@@ -1291,7 +1298,7 @@ QV4::ReturnedValue QV4::ExecutionEngine::fromVariant(const QVariant &variant)
case QMetaType::QDate:
return QV4::Encode(newDateObject(QDateTime(*reinterpret_cast<const QDate *>(ptr))));
case QMetaType::QTime:
- return QV4::Encode(newDateObject(QDateTime(QDate(1970,1,1), *reinterpret_cast<const QTime *>(ptr))));
+ return QV4::Encode(newDateObjectFromTime(*reinterpret_cast<const QTime *>(ptr)));
case QMetaType::QRegExp:
return QV4::Encode(newRegExpObject(*reinterpret_cast<const QRegExp *>(ptr)));
case QMetaType::QObjectStar:
diff --git a/src/qml/jsruntime/qv4engine_p.h b/src/qml/jsruntime/qv4engine_p.h
index f42f727295..843a6f4d94 100644
--- a/src/qml/jsruntime/qv4engine_p.h
+++ b/src/qml/jsruntime/qv4engine_p.h
@@ -419,6 +419,7 @@ public:
Heap::DateObject *newDateObject(const Value &value);
Heap::DateObject *newDateObject(const QDateTime &dt);
+ Heap::DateObject *newDateObjectFromTime(const QTime &t);
Heap::RegExpObject *newRegExpObject(const QString &pattern, int flags);
Heap::RegExpObject *newRegExpObject(RegExp *re, bool global);
diff --git a/tests/auto/qml/qqmlqt/data/timeRoundtrip.qml b/tests/auto/qml/qqmlqt/data/timeRoundtrip.qml
new file mode 100644
index 0000000000..9d73640c87
--- /dev/null
+++ b/tests/auto/qml/qqmlqt/data/timeRoundtrip.qml
@@ -0,0 +1,8 @@
+import QtQuick 2.0
+
+QtObject {
+ Component.onCompleted: {
+ var t = tp.time;
+ tp.time = t;
+ }
+}
diff --git a/tests/auto/qml/qqmlqt/tst_qqmlqt.cpp b/tests/auto/qml/qqmlqt/tst_qqmlqt.cpp
index 69791085c5..0576650d01 100644
--- a/tests/auto/qml/qqmlqt/tst_qqmlqt.cpp
+++ b/tests/auto/qml/qqmlqt/tst_qqmlqt.cpp
@@ -46,6 +46,15 @@
#include <QFont>
#include "../../shared/util.h"
+// Copied from tst_qdatetime.cpp
+#ifdef Q_OS_WIN
+# include <qt_windows.h>
+# include <time.h>
+# if defined(Q_OS_WINRT)
+# define tzset()
+# endif
+#endif
+
class tst_qqmlqt : public QQmlDataTest
{
Q_OBJECT
@@ -93,6 +102,9 @@ private slots:
void later();
void qtObjectContents();
+ void timeRoundtrip_data();
+ void timeRoundtrip();
+
private:
QQmlEngine engine;
};
@@ -873,8 +885,6 @@ void tst_qqmlqt::dateTimeFormattingVariants_data()
QTime time(11, 16, 39, 755);
temporary = QDateTime(QDate(1970,1,1), time);
- QTest::newRow("formatDate, qtime") << "formatDate" << QVariant::fromValue(time) << (QStringList() << temporary.date().toString(Qt::DefaultLocaleShortDate) << temporary.date().toString(Qt::DefaultLocaleLongDate) << temporary.date().toString("ddd MMMM d yy"));
- QTest::newRow("formatDateTime, qtime") << "formatDateTime" << QVariant::fromValue(time) << (QStringList() << temporary.toString(Qt::DefaultLocaleShortDate) << temporary.toString(Qt::DefaultLocaleLongDate) << temporary.toString("M/d/yy H:m:s a"));
QTest::newRow("formatTime, qtime") << "formatTime" << QVariant::fromValue(time) << (QStringList() << temporary.time().toString(Qt::DefaultLocaleShortDate) << temporary.time().toString(Qt::DefaultLocaleLongDate) << temporary.time().toString("H:m:s a") << temporary.time().toString("hh:mm:ss.zzz"));
QDate date(2011,5,31);
@@ -1154,6 +1164,104 @@ void tst_qqmlqt::qtObjectContents()
delete object;
}
+class TimeProvider: public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QTime time READ time WRITE setTime NOTIFY timeChanged)
+
+public:
+ TimeProvider(const QTime &t)
+ : m_getTime(t)
+ {}
+
+ QTime time() const { return m_getTime; }
+ void setTime(const QTime &t) { m_putTime = t; emit timeChanged(); }
+
+signals:
+ void timeChanged();
+
+public:
+ QTime m_getTime, m_putTime;
+};
+
+class TimeZoneSwitch
+{
+public:
+ TimeZoneSwitch(const char *newZone)
+ : doChangeZone(qstrcmp(newZone, "localtime") == 0)
+ {
+ if (!doChangeZone)
+ return;
+
+ hadOldZone = qEnvironmentVariableIsSet("TZ");
+ if (hadOldZone) {
+ oldZone = qgetenv("TZ");
+ }
+ qputenv("TZ", newZone);
+ tzset();
+ }
+
+ ~TimeZoneSwitch()
+ {
+ if (!doChangeZone)
+ return;
+
+ if (hadOldZone)
+ qputenv("TZ", oldZone);
+ else
+ qunsetenv("TZ");
+ tzset();
+ }
+
+private:
+ bool doChangeZone;
+ bool hadOldZone;
+ QByteArray oldZone;
+};
+
+void tst_qqmlqt::timeRoundtrip_data()
+{
+ QTest::addColumn<QTime>("time");
+
+ // Local timezone:
+ QTest::newRow("localtime") << QTime(0, 0, 0);
+
+ // No DST:
+ QTest::newRow("UTC") << QTime(0, 0, 0);
+ QTest::newRow("Europe/Amsterdam") << QTime(1, 0, 0);
+ QTest::newRow("Asia/Jakarta") << QTime(7, 0, 0);
+
+ // DST:
+ QTest::newRow("Namibia/Windhoek") << QTime(1, 0, 0);
+ QTest::newRow("Australia/Adelaide") << QTime(10, 0, 0);
+ QTest::newRow("Australia/Hobart") << QTime(10, 0, 0);
+ QTest::newRow("Pacific/Auckland") << QTime(12, 0, 0);
+ QTest::newRow("Pacific/Samoa") << QTime(13, 0, 0);
+}
+
+void tst_qqmlqt::timeRoundtrip()
+{
+#ifdef Q_OS_WIN
+ QSKIP("On Windows, the DateObject doesn't handle DST transitions correctly when the timezone is not localtime."); // I.e.: for this test.
+#endif
+
+ TimeZoneSwitch tzs(QTest::currentDataTag());
+ QFETCH(QTime, time);
+
+ TimeProvider tp(time);
+
+ QQmlEngine eng;
+ eng.rootContext()->setContextProperty(QLatin1String("tp"), &tp);
+ QQmlComponent component(&eng, testFileUrl("timeRoundtrip.qml"));
+ QObject *obj = component.create();
+ QVERIFY(obj != 0);
+
+ // QML reads m_getTime and saves the result as m_putTime; this should come out the same, without
+ // any perturbation (e.g. by DST effects) from converting from QTime to V4's Date and back
+ // again.
+ QCOMPARE(tp.m_getTime, tp.m_putTime);
+}
+
QTEST_MAIN(tst_qqmlqt)
#include "tst_qqmlqt.moc"