diff options
author | Edward Welbourne <edward.welbourne@qt.io> | 2019-10-07 11:09:47 +0200 |
---|---|---|
committer | Edward Welbourne <edward.welbourne@qt.io> | 2019-10-08 21:21:29 +0200 |
commit | 290dc2da70655db9d4590600dfd37b825b81c1f8 (patch) | |
tree | 7f02b2496f5db4e18ef65ff76e52b2dcef1e1e6a | |
parent | 8b9a67dd7b2c270f00bc48e1aa3cff50cdf058e9 (diff) |
Be more fussy about the MakeDay() calculation
The ES Date spec goes into minute detail about doing various
algorithms in the dumbest possible way (like InLeapYear() calling
DaysInYear(), rather than the other way round) but, in MakeDay(),
leaves the implementation to solve the problem of finding the start of
the first day of a specified month in a given year. So exercise the
freedom we have in that to be a little more robust; and actually check
we have met the conditions the spec requires, returning NaN (as
specified) if not.
Added tests for some denormal dates and date-times and for a date
mentioned in QTBUG-78996.
Task-number: QTBUG-78996
Change-Id: I8d9a841dd1f1d9995273a3de8f6f9130207c7c2b
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
14 files changed, 258 insertions, 5 deletions
diff --git a/src/qml/jsruntime/qv4dateobject.cpp b/src/qml/jsruntime/qv4dateobject.cpp index 21c6a5d06b..e313ebe300 100644 --- a/src/qml/jsruntime/qv4dateobject.cpp +++ b/src/qml/jsruntime/qv4dateobject.cpp @@ -296,13 +296,28 @@ static double MakeDay(double year, double month, double day) if (month < 0) month += 12.0; - double d = DayFromYear(year); - bool leap = InLeapYear(d*msPerDay); + /* Quoting the spec: - d += DayFromMonth(month, leap); - d += day - 1; + Find a value t such that YearFromTime(t) is ym and MonthFromTime(t) is mn + and DateFromTime(t) is 1; but if this is not possible (because some + argument is out of range), return NaN. + */ + double first = DayFromYear(year); + /* Beware floating-point glitches: don't test the first millisecond of a + * year, month or day when we could test a moment firmly in the interior of + * the interval. A rounding glitch might give the first millisecond to the + * preceding interval. + */ + bool leap = InLeapYear((first + 60) * msPerDay); - return d; + first += DayFromMonth(month, leap); + const double t = first * msPerDay + msPerDay / 2; // Noon on the first of the month + Q_ASSERT(Day(t) == first); + if (YearFromTime(t) != year || MonthFromTime(t) != month || DateFromTime(t) != 1) { + qWarning("Apparently out-of-range date %.0f-%02.0f-%02.0f", year, month, day); + return qt_qnan(); + } + return first + day - 1; } static inline double MakeDate(double day, double time) diff --git a/tests/auto/qml/qqmlecmascript/data/checkDate-October.qml b/tests/auto/qml/qqmlecmascript/data/checkDate-October.qml new file mode 100644 index 0000000000..6686831e1c --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/checkDate-October.qml @@ -0,0 +1,12 @@ +import Qt.test 1.0 +import QtQuick 2.0 + +MyTypeObject { + Component.onCompleted: { + // QTBUG-78996 + dateProperty = new Date(2019, 9, 3) + boolProperty = (dateProperty.getFullYear() == 2019 + && dateProperty.getMonth() == 9 + && dateProperty.getDate() == 3) + } +} diff --git a/tests/auto/qml/qqmlecmascript/data/checkDate-denormal-Feb.qml b/tests/auto/qml/qqmlecmascript/data/checkDate-denormal-Feb.qml new file mode 100644 index 0000000000..29ec40ffbd --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/checkDate-denormal-Feb.qml @@ -0,0 +1,11 @@ +import Qt.test 1.0 +import QtQuick 2.0 + +MyTypeObject { + Component.onCompleted: { + dateProperty = new Date(2019, 2, 0) // Feb 28th + boolProperty = (dateProperty.getFullYear() == 2019 + && dateProperty.getMonth() == 1 + && dateProperty.getDate() == 28) + } +} diff --git a/tests/auto/qml/qqmlecmascript/data/checkDate-denormal-March.qml b/tests/auto/qml/qqmlecmascript/data/checkDate-denormal-March.qml new file mode 100644 index 0000000000..7fc8bf43bd --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/checkDate-denormal-March.qml @@ -0,0 +1,11 @@ +import Qt.test 1.0 +import QtQuick 2.0 + +MyTypeObject { + Component.onCompleted: { + dateProperty = new Date(2019, 1, 29) // March 1st + boolProperty = (dateProperty.getFullYear() == 2019 + && dateProperty.getMonth() == 2 + && dateProperty.getDate() == 1) + } +} diff --git a/tests/auto/qml/qqmlecmascript/data/checkDate-denormal-leap.qml b/tests/auto/qml/qqmlecmascript/data/checkDate-denormal-leap.qml new file mode 100644 index 0000000000..6dd84810e6 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/checkDate-denormal-leap.qml @@ -0,0 +1,11 @@ +import Qt.test 1.0 +import QtQuick 2.0 + +MyTypeObject { + Component.onCompleted: { + dateProperty = new Date(2020, 2, 0) // Feb 29th + boolProperty = (dateProperty.getFullYear() == 2020 + && dateProperty.getMonth() == 1 + && dateProperty.getDate() == 29) + } +} diff --git a/tests/auto/qml/qqmlecmascript/data/checkDate-denormal-wrap.qml b/tests/auto/qml/qqmlecmascript/data/checkDate-denormal-wrap.qml new file mode 100644 index 0000000000..ddb79727ef --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/checkDate-denormal-wrap.qml @@ -0,0 +1,11 @@ +import Qt.test 1.0 +import QtQuick 2.0 + +MyTypeObject { + Component.onCompleted: { + dateProperty = new Date(2017, 40, -61) // 2020, Feb 29th + boolProperty = (dateProperty.getFullYear() == 2020 + && dateProperty.getMonth() == 1 + && dateProperty.getDate() == 29) + } +} diff --git a/tests/auto/qml/qqmlecmascript/data/checkDate-denormal-year.qml b/tests/auto/qml/qqmlecmascript/data/checkDate-denormal-year.qml new file mode 100644 index 0000000000..90514c39c8 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/checkDate-denormal-year.qml @@ -0,0 +1,11 @@ +import Qt.test 1.0 +import QtQuick 2.0 + +MyTypeObject { + Component.onCompleted: { + dateProperty = new Date(2019, 12, 0) // Dec 31 + boolProperty = (dateProperty.getFullYear() == 2019 + && dateProperty.getMonth() == 11 + && dateProperty.getDate() == 31) + } +} diff --git a/tests/auto/qml/qqmlecmascript/data/checkDateTime-October.qml b/tests/auto/qml/qqmlecmascript/data/checkDateTime-October.qml new file mode 100644 index 0000000000..c97076f887 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/checkDateTime-October.qml @@ -0,0 +1,16 @@ +import Qt.test 1.0 +import QtQuick 2.0 + +MyTypeObject { + Component.onCompleted: { + // QTBUG-78996 + dateTimeProperty = new Date(2019, 9, 3, 12) + boolProperty = (dateTimeProperty.getFullYear() == 2019 + && dateTimeProperty.getMonth() == 9 + && dateTimeProperty.getDate() == 3 + && dateTimeProperty.getHours() == 12 + && dateTimeProperty.getMinutes() == 0 + && dateTimeProperty.getSeconds() == 0 + && dateTimeProperty.getMilliseconds() == 0) + } +} diff --git a/tests/auto/qml/qqmlecmascript/data/checkDateTime-denormal-March.qml b/tests/auto/qml/qqmlecmascript/data/checkDateTime-denormal-March.qml new file mode 100644 index 0000000000..2b6b9af3e1 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/checkDateTime-denormal-March.qml @@ -0,0 +1,15 @@ +import Qt.test 1.0 +import QtQuick 2.0 + +MyTypeObject { + Component.onCompleted: { + dateTimeProperty = new Date(2019, 1, 28, 23, 59, 59, 1001) // 2019-3-1 0:0:0.001 + boolProperty = (dateTimeProperty.getFullYear() == 2019 + && dateTimeProperty.getMonth() == 2 + && dateTimeProperty.getDate() == 1 + && dateTimeProperty.getHours() == 0 + && dateTimeProperty.getMinutes() == 0 + && dateTimeProperty.getSeconds() == 0 + && dateTimeProperty.getMilliseconds() == 1) + } +} diff --git a/tests/auto/qml/qqmlecmascript/data/checkDateTime-denormal-hours.qml b/tests/auto/qml/qqmlecmascript/data/checkDateTime-denormal-hours.qml new file mode 100644 index 0000000000..7d018e2904 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/checkDateTime-denormal-hours.qml @@ -0,0 +1,15 @@ +import Qt.test 1.0 +import QtQuick 2.0 + +MyTypeObject { + Component.onCompleted: { + dateTimeProperty = new Date(2019, 11, 31, 1440) // 2020-2-29 0:0:0 + boolProperty = (dateTimeProperty.getFullYear() == 2020 + && dateTimeProperty.getMonth() == 1 + && dateTimeProperty.getDate() == 29 + && dateTimeProperty.getHours() == 0 + && dateTimeProperty.getMinutes() == 0 + && dateTimeProperty.getSeconds() == 0 + && dateTimeProperty.getMilliseconds() == 0) + } +} diff --git a/tests/auto/qml/qqmlecmascript/data/checkDateTime-denormal-leap.qml b/tests/auto/qml/qqmlecmascript/data/checkDateTime-denormal-leap.qml new file mode 100644 index 0000000000..0a7687c669 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/checkDateTime-denormal-leap.qml @@ -0,0 +1,15 @@ +import Qt.test 1.0 +import QtQuick 2.0 + +MyTypeObject { + Component.onCompleted: { + dateTimeProperty = new Date(2020, 2, 1, 0, 0, 0, -1) // 2020-2-29 23:59:59.999 + boolProperty = (dateTimeProperty.getFullYear() == 2020 + && dateTimeProperty.getMonth() == 1 + && dateTimeProperty.getDate() == 29 + && dateTimeProperty.getHours() == 23 + && dateTimeProperty.getMinutes() == 59 + && dateTimeProperty.getSeconds() == 59 + && dateTimeProperty.getMilliseconds() == 999) + } +} diff --git a/tests/auto/qml/qqmlecmascript/data/checkDateTime-denormal-minutes.qml b/tests/auto/qml/qqmlecmascript/data/checkDateTime-denormal-minutes.qml new file mode 100644 index 0000000000..738d603b4b --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/checkDateTime-denormal-minutes.qml @@ -0,0 +1,15 @@ +import Qt.test 1.0 +import QtQuick 2.0 + +MyTypeObject { + Component.onCompleted: { + dateTimeProperty = new Date(2020, 1, 28, 0, 1440) // 2020-2-29 0:0:0 + boolProperty = (dateTimeProperty.getFullYear() == 2020 + && dateTimeProperty.getMonth() == 1 + && dateTimeProperty.getDate() == 29 + && dateTimeProperty.getHours() == 0 + && dateTimeProperty.getMinutes() == 0 + && dateTimeProperty.getSeconds() == 0 + && dateTimeProperty.getMilliseconds() == 0) + } +} diff --git a/tests/auto/qml/qqmlecmascript/data/checkDateTime-denormal-seconds.qml b/tests/auto/qml/qqmlecmascript/data/checkDateTime-denormal-seconds.qml new file mode 100644 index 0000000000..d48534f0d0 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/checkDateTime-denormal-seconds.qml @@ -0,0 +1,15 @@ +import Qt.test 1.0 +import QtQuick 2.0 + +MyTypeObject { + Component.onCompleted: { + dateTimeProperty = new Date(2020, 1, 28, 23, 0, 3600) // 2020-2-29 0:0:0 + boolProperty = (dateTimeProperty.getFullYear() == 2020 + && dateTimeProperty.getMonth() == 1 + && dateTimeProperty.getDate() == 29 + && dateTimeProperty.getHours() == 0 + && dateTimeProperty.getMinutes() == 0 + && dateTimeProperty.getSeconds() == 0 + && dateTimeProperty.getMilliseconds() == 0) + } +} diff --git a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp index df4963cb6e..7a9c611269 100644 --- a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp +++ b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp @@ -78,6 +78,10 @@ private slots: void assignDate(); void exportDate_data(); void exportDate(); + void checkDate_data(); + void checkDate(); + void checkDateTime_data(); + void checkDateTime(); void idShortcutInvalidates(); void boolPropertiesEvaluateAsBool(); void methods(); @@ -565,6 +569,82 @@ void tst_qqmlecmascript::exportDate() QCOMPARE(object->boolProperty(), true); } +void tst_qqmlecmascript::checkDate_data() +{ + QTest::addColumn<QUrl>("source"); + QTest::addColumn<QDate>("date"); + // NB: JavaScript month-indices are Jan = 0 to Dec = 11; QDate's are Jan = 1 to Dec = 12. + QTest::newRow("denormal-March") + << testFileUrl("checkDate-denormal-March.qml") + << QDate(2019, 3, 1); + QTest::newRow("denormal-leap") + << testFileUrl("checkDate-denormal-leap.qml") + << QDate(2020, 2, 29); + QTest::newRow("denormal-Feb") + << testFileUrl("checkDate-denormal-Feb.qml") + << QDate(2019, 2, 28); + QTest::newRow("denormal-year") + << testFileUrl("checkDate-denormal-year.qml") + << QDate(2019, 12, 31); + QTest::newRow("denormal-wrap") + << testFileUrl("checkDate-denormal-wrap.qml") + << QDate(2020, 2, 29); + QTest::newRow("October") + << testFileUrl("checkDate-October.qml") + << QDate(2019, 10, 3); +} + +void tst_qqmlecmascript::checkDate() +{ + QFETCH(const QUrl, source); + QFETCH(const QDate, date); + QQmlEngine e; + QQmlComponent component(&e, source); + QScopedPointer<QObject> obj(component.create()); + MyTypeObject *object = qobject_cast<MyTypeObject *>(obj.data()); + QVERIFY(object != nullptr); + QCOMPARE(object->dateProperty(), date); + QVERIFY(object->boolProperty()); +} + +void tst_qqmlecmascript::checkDateTime_data() +{ + QTest::addColumn<QUrl>("source"); + QTest::addColumn<QDateTime>("when"); + // NB: JavaScript month-indices are Jan = 0 to Dec = 11; QDate's are Jan = 1 to Dec = 12. + QTest::newRow("denormal-March") + << testFileUrl("checkDateTime-denormal-March.qml") + << QDateTime(QDate(2019, 3, 1), QTime(0, 0, 0, 1), Qt::LocalTime); + QTest::newRow("denormal-leap") + << testFileUrl("checkDateTime-denormal-leap.qml") + << QDateTime(QDate(2020, 2, 29), QTime(23, 59, 59, 999), Qt::LocalTime); + QTest::newRow("denormal-hours") + << testFileUrl("checkDateTime-denormal-hours.qml") + << QDateTime(QDate(2020, 2, 29), QTime(0, 0), Qt::LocalTime); + QTest::newRow("denormal-minutes") + << testFileUrl("checkDateTime-denormal-minutes.qml") + << QDateTime(QDate(2020, 2, 29), QTime(0, 0), Qt::LocalTime); + QTest::newRow("denormal-seconds") + << testFileUrl("checkDateTime-denormal-seconds.qml") + << QDateTime(QDate(2020, 2, 29), QTime(0, 0), Qt::LocalTime); + QTest::newRow("October") + << testFileUrl("checkDateTime-October.qml") + << QDateTime(QDate(2019, 10, 3), QTime(12, 0), Qt::LocalTime); +} + +void tst_qqmlecmascript::checkDateTime() +{ + QFETCH(const QUrl, source); + QFETCH(const QDateTime, when); + QQmlEngine e; + QQmlComponent component(&e, source); + QScopedPointer<QObject> obj(component.create()); + MyTypeObject *object = qobject_cast<MyTypeObject *>(obj.data()); + QVERIFY(object != nullptr); + QCOMPARE(object->dateTimeProperty(), when); + QVERIFY(object->boolProperty()); +} + void tst_qqmlecmascript::idShortcutInvalidates() { QQmlEngine engine; |