aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2023-01-12 10:00:48 +0100
committerUlf Hermann <ulf.hermann@qt.io>2023-06-06 20:20:04 +0200
commitc7f5507f0ee2c60c7ca16158868d00d6dc13a2b3 (patch)
treebf21346dee1e7c987eab2e525060656efcf59904
parenta38f53e6712e81beeee11f0158715b1031dd764f (diff)
QJSEngine: Add efficient conversions for QDateTime and friends
The JavaScript date and time conversions are different from Qt's. Add them to coerceValue. Task-number: QTBUG-109380 Change-Id: Ic0d7dd8ff51fb8e29d80d9084d4415becaa76259 Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io> (cherry picked from commit c108a817f4ccd023c3b9d9d19427b8d65eb4e348) Reviewed-by: Sami Shalayel <sami.shalayel@qt.io>
-rw-r--r--src/qml/jsapi/qjsengine.cpp11
-rw-r--r--src/qml/jsapi/qjsengine.h42
-rw-r--r--src/qml/jsruntime/qv4dateobject.cpp25
-rw-r--r--src/qml/jsruntime/qv4dateobject_p.h3
-rw-r--r--tests/auto/qml/qjsengine/tst_qjsengine.cpp155
5 files changed, 233 insertions, 3 deletions
diff --git a/src/qml/jsapi/qjsengine.cpp b/src/qml/jsapi/qjsengine.cpp
index 504d11f29e..eca6562b90 100644
--- a/src/qml/jsapi/qjsengine.cpp
+++ b/src/qml/jsapi/qjsengine.cpp
@@ -12,6 +12,7 @@
#include "private/qv4globalobject_p.h"
#include "private/qv4script_p.h"
#include "private/qv4runtime_p.h"
+#include <private/qv4dateobject_p.h>
#include <private/qqmlbuiltinfunctions_p.h>
#include <private/qqmldebugconnector_p.h>
#include <private/qv4qobjectwrapper_p.h>
@@ -949,6 +950,16 @@ QString QJSEngine::convertQObjectToString(QObject *object)
handle(), object ? object->metaObject() : nullptr, object);
}
+QString QJSEngine::convertDateTimeToString(const QDateTime &dateTime)
+{
+ return QV4::DateObject::dateTimeToString(dateTime, handle());
+}
+
+QDate QJSEngine::convertDateTimeToDate(const QDateTime &dateTime)
+{
+ return QV4::DateObject::dateTimeToDate(dateTime);
+}
+
/*! \fn template <typename T> QJSValue QJSEngine::toScriptValue(const T &value)
Creates a QJSValue with the given \a value.
diff --git a/src/qml/jsapi/qjsengine.h b/src/qml/jsapi/qjsengine.h
index 6df5b21bf7..dbeecb45ae 100644
--- a/src/qml/jsapi/qjsengine.h
+++ b/src/qml/jsapi/qjsengine.h
@@ -9,6 +9,7 @@
#include <QtCore/qvariant.h>
#include <QtCore/qsharedpointer.h>
#include <QtCore/qobject.h>
+#include <QtCore/qtimezone.h>
#include <QtQml/qjsvalue.h>
#include <QtQml/qjsmanagedvalue.h>
#include <QtQml/qqmldebug.h>
@@ -210,9 +211,41 @@ public:
if constexpr (std::is_same_v<To, QVariant>)
return QVariant::fromValue(from);
- if constexpr (std::is_same_v<To, QString>
- && std::is_base_of_v<QObject, std::remove_const_t<std::remove_pointer_t<From>>>) {
- return convertQObjectToString(from);
+ if constexpr (std::is_same_v<To, QString>) {
+ if constexpr (std::is_base_of_v<QObject, std::remove_const_t<std::remove_pointer_t<From>>>)
+ return convertQObjectToString(from);
+ }
+
+ if constexpr (std::is_same_v<From, QDateTime>) {
+ if constexpr (std::is_same_v<To, QDate>)
+ return convertDateTimeToDate(from.toLocalTime());
+ if constexpr (std::is_same_v<To, QTime>)
+ return from.toLocalTime().time();
+ if constexpr (std::is_same_v<To, QString>)
+ return convertDateTimeToString(from.toLocalTime());
+ }
+
+ if constexpr (std::is_same_v<From, QDate>) {
+ if constexpr (std::is_same_v<To, QDateTime>)
+ return from.startOfDay(QTimeZone::UTC);
+ if constexpr (std::is_same_v<To, QTime>) {
+ // This is the current time zone offset, for better or worse
+ return coerceValue<QDateTime, QTime>(coerceValue<QDate, QDateTime>(from));
+ }
+ if constexpr (std::is_same_v<To, QString>)
+ return convertDateTimeToString(coerceValue<QDate, QDateTime>(from));
+ }
+
+ if constexpr (std::is_same_v<From, QTime>) {
+ if constexpr (std::is_same_v<To, QDate>) {
+ // Yes. April Fools' 1971. See qv4dateobject.cpp.
+ return from.isValid() ? QDate(1971, 4, 1) : QDate();
+ }
+
+ if constexpr (std::is_same_v<To, QDateTime>)
+ return QDateTime(coerceValue<QTime, QDate>(from), from, QTimeZone::LocalTime);
+ if constexpr (std::is_same_v<To, QString>)
+ return convertDateTimeToString(coerceValue<QTime, QDateTime>(from));
}
if constexpr (std::is_same_v<To, std::remove_const_t<std::remove_pointer_t<To>> const *>) {
@@ -289,7 +322,10 @@ private:
bool convertVariant(const QVariant &value, QMetaType metaType, void *ptr);
bool convertMetaType(QMetaType fromType, const void *from, QMetaType toType, void *to);
+
QString convertQObjectToString(QObject *object);
+ QString convertDateTimeToString(const QDateTime &dateTime);
+ static QDate convertDateTimeToDate(const QDateTime &dateTime);
template<typename T>
friend inline T qjsvalue_cast(const QJSValue &);
diff --git a/src/qml/jsruntime/qv4dateobject.cpp b/src/qml/jsruntime/qv4dateobject.cpp
index f1d008e2d1..196b9d9112 100644
--- a/src/qml/jsruntime/qv4dateobject.cpp
+++ b/src/qml/jsruntime/qv4dateobject.cpp
@@ -707,6 +707,31 @@ QDateTime DateObject::toQDateTime() const
return d()->toQDateTime();
}
+QString DateObject::dateTimeToString(const QDateTime &dateTime, ExecutionEngine *engine)
+{
+ if (!dateTime.isValid())
+ return QStringLiteral("Invalid Date");
+ return ToString(TimeClip(dateTime.toMSecsSinceEpoch()), engine->localTZA);
+}
+
+QDate DateObject::dateTimeToDate(const QDateTime &dateTime)
+{
+ // If the Date object was parse()d from a string with no time part
+ // or zone specifier it's really the UTC start of the relevant day,
+ // but it's here represented as a local time, which may fall in the
+ // preceding day. See QTBUG-92466 for the gory details.
+ const auto utc = dateTime.toUTC();
+ if (utc.date() != dateTime.date() && utc.addSecs(-1).date() == dateTime.date())
+ return utc.date();
+
+ // This may, of course, be The Wrong Thing if the date was
+ // constructed as a full local date-time that happens to coincide
+ // with the start of a UTC day; however, that would be an odd value
+ // to give to something that, apparently, someone thinks belongs in
+ // a QDate.
+ return dateTime.date();
+}
+
DEFINE_OBJECT_VTABLE(DateCtor);
void Heap::DateCtor::init(QV4::ExecutionContext *scope)
diff --git a/src/qml/jsruntime/qv4dateobject_p.h b/src/qml/jsruntime/qv4dateobject_p.h
index 5f1bf57c59..a9537afa7d 100644
--- a/src/qml/jsruntime/qv4dateobject_p.h
+++ b/src/qml/jsruntime/qv4dateobject_p.h
@@ -203,6 +203,9 @@ struct DateObject: ReferenceObject {
double date() const { return d()->date(); }
Q_QML_PRIVATE_EXPORT QDateTime toQDateTime() const;
+
+ static QString dateTimeToString(const QDateTime &dateTime, ExecutionEngine *engine);
+ static QDate dateTimeToDate(const QDateTime &dateTime);
};
template<>
diff --git a/tests/auto/qml/qjsengine/tst_qjsengine.cpp b/tests/auto/qml/qjsengine/tst_qjsengine.cpp
index 0f3d45c7e3..42117f5f49 100644
--- a/tests/auto/qml/qjsengine/tst_qjsengine.cpp
+++ b/tests/auto/qml/qjsengine/tst_qjsengine.cpp
@@ -29,6 +29,27 @@ using namespace Qt::StringLiterals;
Q_DECLARE_METATYPE(QList<int>)
Q_DECLARE_METATYPE(QObjectList)
+class DateTimeHolder : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QDateTime dateTime MEMBER m_dateTime NOTIFY dateTimeChanged)
+ Q_PROPERTY(QDate date MEMBER m_date NOTIFY dateChanged)
+ Q_PROPERTY(QTime time MEMBER m_time NOTIFY timeChanged)
+ Q_PROPERTY(QString string MEMBER m_string NOTIFY stringChanged)
+
+signals:
+ void dateTimeChanged();
+ void dateChanged();
+ void timeChanged();
+ void stringChanged();
+
+public:
+ QDateTime m_dateTime;
+ QDate m_date;
+ QTime m_time;
+ QString m_string;
+};
+
class tst_QJSEngine : public QObject
{
Q_OBJECT
@@ -276,6 +297,10 @@ private slots:
void tdzViolations();
void coerceValue();
+
+ void coerceDateTime_data();
+ void coerceDateTime();
+
void callWithSpreadOnElement();
void spreadNoOverflow();
@@ -290,6 +315,7 @@ signals:
tst_QJSEngine::tst_QJSEngine()
{
+ qmlRegisterType<DateTimeHolder>("Test", 1, 0, "DateTimeHolder");
}
tst_QJSEngine::~tst_QJSEngine()
@@ -5899,6 +5925,135 @@ void tst_QJSEngine::coerceValue()
QCOMPARE((engine.coerceValue<UnknownToJS, QTypeRevision>(u)), w);
}
+void tst_QJSEngine::coerceDateTime_data()
+{
+ QTest::addColumn<QDateTime>("dateTime");
+
+ QTest::newRow("invalid") << QDateTime();
+ QTest::newRow("now") << QDateTime::currentDateTime();
+
+ QTest::newRow("denormal-March") << QDateTime(QDate(2019, 3, 1), QTime(0, 0, 0, 1));
+ QTest::newRow("denormal-leap") << QDateTime(QDate(2020, 2, 29), QTime(23, 59, 59, 999));
+ QTest::newRow("denormal-time") << QDateTime(QDate(2020, 2, 29), QTime(0, 0));
+ QTest::newRow("October") << QDateTime(QDate(2019, 10, 3), QTime(12, 0));
+ QTest::newRow("nonstandard-format") << QDateTime::fromString("1991-08-25 20:57:08 GMT+0000", "yyyy-MM-dd hh:mm:ss t");
+ QTest::newRow("nonstandard-format2") << QDateTime::fromString("Sun, 25 Mar 2018 11:10:49 GMT", "ddd, d MMM yyyy hh:mm:ss t");
+
+ const QDate date(2009, 5, 12);
+ const QTime early(0, 0, 1);
+ const QTime late(23, 59, 59);
+ const int offset = (11 * 60 + 30) * 60;
+
+ QTest::newRow("Local time early") << QDateTime(date, early);
+ QTest::newRow("Local time late") << QDateTime(date, late);
+ QTest::newRow("UTC early") << QDateTime(date, early, QTimeZone::UTC);
+ QTest::newRow("UTC late") << QDateTime(date, late, QTimeZone::UTC);
+ QTest::newRow("+11:30 early") << QDateTime(date, early, QTimeZone::fromSecondsAheadOfUtc(offset));
+ QTest::newRow("+11:30 late") << QDateTime(date, late, QTimeZone::fromSecondsAheadOfUtc(offset));
+ QTest::newRow("-11:30 early") << QDateTime(date, early, QTimeZone::fromSecondsAheadOfUtc(-offset));
+ QTest::newRow("-11:30 late") << QDateTime(date, late, QTimeZone::fromSecondsAheadOfUtc(-offset));
+
+ QTest::newRow("dt0") << QDateTime(QDate(1900, 1, 2), QTime( 8, 14));
+ QTest::newRow("dt1") << QDateTime(QDate(2000, 11, 22), QTime(10, 45));
+}
+
+void tst_QJSEngine::coerceDateTime()
+{
+ QFETCH(QDateTime, dateTime);
+ const QDate date = dateTime.date();
+ const QTime time = dateTime.time();
+
+
+ QQmlEngine engine;
+
+ {
+ QQmlComponent c(&engine);
+ c.setData(R"(
+ import Test
+ DateTimeHolder {
+ string: dateTime
+ date: dateTime
+ time: dateTime
+ }
+ )", QUrl(u"fromDateTime.qml"_s));
+
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(!o.isNull());
+
+ DateTimeHolder *holder = qobject_cast<DateTimeHolder *>(o.data());
+ QVERIFY(holder);
+
+ holder->m_dateTime = dateTime;
+ emit holder->dateTimeChanged();
+
+ QEXPECT_FAIL("", "QML produces QDateTime::toString()", Continue);
+ QCOMPARE((engine.coerceValue<QDateTime, QString>(dateTime)), holder->m_string);
+
+ QCOMPARE((engine.coerceValue<QDateTime, QDate>(dateTime)), holder->m_date);
+
+ QCOMPARE((engine.coerceValue<QDateTime, QTime>(dateTime)), holder->m_time);
+ }
+
+ {
+ QQmlComponent c(&engine);
+ c.setData(R"(
+ import Test
+ DateTimeHolder {
+ dateTime: date
+ time: date
+ string: date
+ }
+ )", QUrl(u"fromDate.qml"_s));
+
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(!o.isNull());
+
+ DateTimeHolder *holder = qobject_cast<DateTimeHolder *>(o.data());
+ QVERIFY(holder);
+
+ holder->m_date = date;
+ emit holder->dateChanged();
+
+ QCOMPARE((engine.coerceValue<QDate, QDateTime>(date)), holder->m_dateTime);
+
+ QEXPECT_FAIL("", "QML produces QDateTime::toString()", Continue);
+ QCOMPARE((engine.coerceValue<QDate, QString>(date)), holder->m_string);
+
+ QCOMPARE((engine.coerceValue<QDate, QTime>(date)), holder->m_time);
+ }
+
+ {
+ QQmlComponent c(&engine);
+ c.setData(R"(
+ import Test
+ DateTimeHolder {
+ dateTime: time
+ date: time
+ string: time
+ }
+ )", QUrl(u"fromTime.qml"_s));
+
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(!o.isNull());
+
+ DateTimeHolder *holder = qobject_cast<DateTimeHolder *>(o.data());
+ QVERIFY(holder);
+
+ holder->m_time = time;
+ emit holder->timeChanged();
+
+ QCOMPARE((engine.coerceValue<QTime, QDateTime>(time)), holder->m_dateTime);
+
+ QEXPECT_FAIL("", "QML produces QDateTime::toString()", Continue);
+ QCOMPARE((engine.coerceValue<QTime, QString>(time)), holder->m_string);
+
+ QCOMPARE((engine.coerceValue<QTime, QDate>(time)), holder->m_date);
+ }
+}
+
void tst_QJSEngine::callWithSpreadOnElement()
{
QJSEngine engine;