diff options
author | Ulf Hermann <ulf.hermann@qt.io> | 2023-01-12 10:00:48 +0100 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@qt.io> | 2023-06-06 20:20:04 +0200 |
commit | c7f5507f0ee2c60c7ca16158868d00d6dc13a2b3 (patch) | |
tree | bf21346dee1e7c987eab2e525060656efcf59904 | |
parent | a38f53e6712e81beeee11f0158715b1031dd764f (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.cpp | 11 | ||||
-rw-r--r-- | src/qml/jsapi/qjsengine.h | 42 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4dateobject.cpp | 25 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4dateobject_p.h | 3 | ||||
-rw-r--r-- | tests/auto/qml/qjsengine/tst_qjsengine.cpp | 155 |
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; |