diff options
author | Fabian Kosmale <fabian.kosmale@qt.io> | 2020-01-28 16:26:49 +0100 |
---|---|---|
committer | Fabian Kosmale <fabian.kosmale@qt.io> | 2020-01-30 09:51:44 +0100 |
commit | c5f78add12b7d33926631b6df79599df18f65068 (patch) | |
tree | 08a895aa76440a4b267b1f6f500ea11e2722e19d | |
parent | e080f48f905be597b1a645f1641b2b06553df6a2 (diff) |
Reduce Qt Declarative's use of QDateTime's locale-dependent APIs
[ChangeLog][QtQml] Qt.formatDateTime, Qt.formatDate and Qt.formatTime
now support formatting according to a locale and an optional locale
format type. If locale dependent formatting is desired, this method
should be used instead of the locale-related DateFormat enum members.
Fixes: QTBUG-81631
Change-Id: I971231644ebbeaccbf54dd8f036adf4d31547301
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
-rw-r--r-- | src/qml/jsruntime/qv4dateobject.cpp | 6 | ||||
-rw-r--r-- | src/qml/qml/v8/qqmlbuiltinfunctions.cpp | 190 | ||||
-rw-r--r-- | tests/auto/qml/qqmlqt/data/formattingLocale.qml | 12 | ||||
-rw-r--r-- | tests/auto/qml/qqmlqt/tst_qqmlqt.cpp | 52 |
4 files changed, 164 insertions, 96 deletions
diff --git a/src/qml/jsruntime/qv4dateobject.cpp b/src/qml/jsruntime/qv4dateobject.cpp index c2f48ffeac..bebcbd7e44 100644 --- a/src/qml/jsruntime/qv4dateobject.cpp +++ b/src/qml/jsruntime/qv4dateobject.cpp @@ -682,17 +682,17 @@ static inline QString ToTimeString(double t) static inline QString ToLocaleString(double t) { - return ToDateTime(t, Qt::LocalTime).toString(Qt::DefaultLocaleShortDate); + return QLocale().toString(ToDateTime(t, Qt::LocalTime), QLocale::ShortFormat); } static inline QString ToLocaleDateString(double t) { - return ToDateTime(t, Qt::LocalTime).date().toString(Qt::DefaultLocaleShortDate); + return QLocale().toString(ToDateTime(t, Qt::LocalTime).date(), QLocale::ShortFormat); } static inline QString ToLocaleTimeString(double t) { - return ToDateTime(t, Qt::LocalTime).time().toString(Qt::DefaultLocaleShortDate); + return QLocale().toString(ToDateTime(t, Qt::LocalTime).time(), QLocale::ShortFormat); } static double getLocalTZA() diff --git a/src/qml/qml/v8/qqmlbuiltinfunctions.cpp b/src/qml/qml/v8/qqmlbuiltinfunctions.cpp index b1046b254b..70134bad35 100644 --- a/src/qml/qml/v8/qqmlbuiltinfunctions.cpp +++ b/src/qml/qml/v8/qqmlbuiltinfunctions.cpp @@ -728,70 +728,127 @@ ReturnedValue QtObject::method_tint(const FunctionObject *b, const Value *, cons return scope.engine->fromVariant(QQml_colorProvider()->tint(v1, v2)); } +namespace { +template <typename T> +QString formatDateTimeObjectUsingDateFormat(T formatThis, Qt::DateFormat format) { + switch (format) { + case Qt::TextDate: + case Qt::ISODate: + case Qt::RFC2822Date: + case Qt::ISODateWithMs: + return formatThis.toString(format); + // ### Qt 6: Remove all locale dependent cases + QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED + case Qt::SystemLocaleDate: + // case Qt::LocalDate: covered by SystemLocaleDate + return QLocale::system().toString(formatThis); + case Qt::LocaleDate: + case Qt::DefaultLocaleShortDate: + return QLocale().toString(formatThis, QLocale::ShortFormat); + case Qt::SystemLocaleShortDate: + return QLocale::system().toString(formatThis, QLocale::ShortFormat); + case Qt::SystemLocaleLongDate: + return QLocale::system().toString(formatThis, QLocale::LongFormat); + case Qt::DefaultLocaleLongDate: + return QLocale().toString(formatThis, QLocale::LongFormat); + } + QT_WARNING_POP + Q_UNREACHABLE(); + return QString(); +} + +template <typename T> +ReturnedValue formatDateTimeObject(const T &formatThis, const QV4::Scope &scope, const QString &functionName, int argc, const Value *argv) { + + QString formatted; + if (argc >= 2) { + QV4::ScopedString s(scope, argv[1]); + if (s) { + if (argc == 3) + scope.engine->throwError(QLatin1String("%1(): Stay argument, third argument can only be used if second argument is a locale").arg(functionName)); + QString format = s->toQString(); + formatted = formatThis.toString(format); + } else if (argv[1].isNumber()) { + if (argc == 3) + scope.engine->throwError(QLatin1String("%1(): Stay argument, third argument can only be used if second argument is a locale").arg(functionName)); + quint32 intFormat = argv[1].asDouble(); + Qt::DateFormat format = Qt::DateFormat(intFormat); + formatted = formatDateTimeObjectUsingDateFormat(formatThis, format); + } else { + QLocale::FormatType formatOptions = QLocale::ShortFormat; + if (argc == 3) { + if (argv[2].isNumber()) + formatOptions = QLocale::FormatType(quint32(argv[2].asDouble())); + else + scope.engine->throwError(QLatin1String("%1(): Third argument must be a Locale format option").arg(functionName)); + } + auto enginePriv = QQmlEnginePrivate::get(scope.engine->qmlEngine()); + auto localeMetaTypeId = qMetaTypeId<QLocale>(); + QVariant locale = enginePriv->v4engine()->toVariant(argv[1], localeMetaTypeId); + if (!locale.canConvert(localeMetaTypeId)) + scope.engine->throwError(QLatin1String("%1(): Bad second argument (must be either string, number or locale)").arg(functionName)); + formatted = locale.value<QLocale>().toString(formatThis, formatOptions); + } + } else { + formatted = QLocale().toString(formatThis, QLocale::ShortFormat); + } + + return Encode(scope.engine->newString(formatted)); +} + +} + /*! -\qmlmethod string Qt::formatDate(datetime date, variant format) +\qmlmethod string Qt::formatDate(datetime date, variant format, variant localeFormatOption) -Returns a string representation of \a date, optionally formatted according -to \a format. +Returns a string representation of \a date, optionally formatted using \a format. The \a date parameter may be a JavaScript \c Date object, a \l{date}{date} -property, a QDate, or QDateTime value. The \a format parameter may be any of -the possible format values as described for +property, a QDate, or QDateTime value. The \a format and \a localeFormatOption +parameter may be any of the possible format values as described for \l{QtQml::Qt::formatDateTime()}{Qt.formatDateTime()}. If \a format is not specified, \a date is formatted using -\l {Qt::DefaultLocaleShortDate}{Qt.DefaultLocaleShortDate}. +\l {QLocale::FormatType::ShortFormat}{Locale.ShortFormat} using the +default locale. \sa Locale */ ReturnedValue QtObject::method_formatDate(const FunctionObject *b, const Value *, const Value *argv, int argc) { QV4::Scope scope(b); - if (argc < 1 || argc > 2) - THROW_GENERIC_ERROR("Qt.formatDate(): Invalid arguments"); + if (argc < 1) + THROW_GENERIC_ERROR("Qt.formatDate(): Missing argument"); + if (argc > 3) + THROW_GENERIC_ERROR("Qt.formatDate(): Stray arguments; formatDate takes at most 3 arguments."); - Qt::DateFormat enumFormat = Qt::DefaultLocaleShortDate; QDate date = scope.engine->toVariant(argv[0], -1).toDateTime().date(); - QString formattedDate; - if (argc == 2) { - QV4::ScopedString s(scope, argv[1]); - if (s) { - QString format = s->toQString(); - formattedDate = date.toString(format); - } else if (argv[1].isNumber()) { - quint32 intFormat = argv[1].asDouble(); - Qt::DateFormat format = Qt::DateFormat(intFormat); - formattedDate = date.toString(format); - } else { - THROW_GENERIC_ERROR("Qt.formatDate(): Invalid date format"); - } - } else { - formattedDate = date.toString(enumFormat); - } - - return Encode(scope.engine->newString(formattedDate)); + return formatDateTimeObject(date, scope, QLatin1String("Qt.formatDate"), argc, argv); } /*! -\qmlmethod string Qt::formatTime(datetime time, variant format) +\qmlmethod string Qt::formatTime(datetime time, variant format, variant localeFormatOption) -Returns a string representation of \a time, optionally formatted according to -\a format. +Returns a string representation of \a time, optionally formatted using +\a format, and, if provided, \a localeFormatOption. The \a time parameter may be a JavaScript \c Date object, a QTime, or QDateTime -value. The \a format parameter may be any of the possible format values as -described for \l{QtQml::Qt::formatDateTime()}{Qt.formatDateTime()}. +value. The \a format and \a localeFormatOption parameter may be any of the +possible format values as described for +\l{QtQml::Qt::formatDateTime()}{Qt.formatDateTime()}. If \a format is not specified, \a time is formatted using -\l {Qt::DefaultLocaleShortDate}{Qt.DefaultLocaleShortDate}. +\l {QLocale::FormatType::ShortFormat}{Locale.ShortFormat} using the default locale. \sa Locale */ ReturnedValue QtObject::method_formatTime(const FunctionObject *b, const Value *, const Value *argv, int argc) { QV4::Scope scope(b); - if (argc < 1 || argc > 2) - THROW_GENERIC_ERROR("Qt.formatTime(): Invalid arguments"); + if (argc < 1) + THROW_GENERIC_ERROR("Qt.formatTime(): Missing argument"); + if (argc > 3) + THROW_GENERIC_ERROR("Qt.formatTime(): Stray arguments; formatTime takes at most 3 arguments."); QVariant argVariant = scope.engine->toVariant(argv[0], -1); QTime time; @@ -799,47 +856,34 @@ ReturnedValue QtObject::method_formatTime(const FunctionObject *b, const Value * time = argVariant.toDateTime().time(); else // if (argVariant.type() == QVariant::Time), or invalid. time = argVariant.toTime(); - - Qt::DateFormat enumFormat = Qt::DefaultLocaleShortDate; - QString formattedTime; - if (argc == 2) { - QV4::ScopedString s(scope, argv[1]); - if (s) { - QString format = s->toQString(); - formattedTime = time.toString(format); - } else if (argv[1].isNumber()) { - quint32 intFormat = argv[1].asDouble(); - Qt::DateFormat format = Qt::DateFormat(intFormat); - formattedTime = time.toString(format); - } else { - THROW_GENERIC_ERROR("Qt.formatTime(): Invalid time format"); - } - } else { - formattedTime = time.toString(enumFormat); - } - - return Encode(scope.engine->newString(formattedTime)); + return formatDateTimeObject(time, scope, QLatin1String("Qt.formatTime"), argc, argv); } /*! -\qmlmethod string Qt::formatDateTime(datetime dateTime, variant format) +\qmlmethod string Qt::formatDateTime(datetime dateTime, variant format, variant localeFormatOption) -Returns a string representation of \a dateTime, optionally formatted according to +Returns a string representation of \a dateTime, optionally formatted using \a format. The \a dateTime parameter may be a JavaScript \c Date object, a \l{date}{date} property, a QDate, QTime, or QDateTime value. If \a format is not provided, \a dateTime is formatted using -\l {Qt::DefaultLocaleShortDate}{Qt.DefaultLocaleShortDate}. Otherwise, -\a format should be either: +\l {QLocale::FormatType::ShortFormat}{Locale.ShortFormat} using the +default locale. Otherwise, \a format should be either: \list \li One of the Qt::DateFormat enumeration values, such as - \c Qt.DefaultLocaleShortDate or \c Qt.ISODate + \c Qt.RFC2822Date or \c Qt.ISODate. \li A string that specifies the format of the returned string, as detailed below. +\li A \c locale object. \endlist +If \a format specifies a locale object, \dateTime is formatted +with \li{QLocale::toString}. In this case, localeFormatType can hold a value +of type \l {QLocale::FormatType} to further tune the formatting. If none is +provided, \l {QLocale::FormatType::ShortFormat}{Locale.ShortFormat} is used. + If \a format specifies a format string, it should use the following expressions to specify the date: @@ -916,29 +960,13 @@ with the \a format values below to produce the following results: ReturnedValue QtObject::method_formatDateTime(const FunctionObject *b, const Value *, const Value *argv, int argc) { QV4::Scope scope(b); - if (argc < 1 || argc > 2) - THROW_GENERIC_ERROR("Qt.formatDateTime(): Invalid arguments"); + if (argc < 1) + THROW_GENERIC_ERROR("Qt.formatDateTime(): Missing argument"); + if (argc > 3) + THROW_GENERIC_ERROR("Qt.formatDateTime(): Stray arguments; formatDate takes at most 3 arguments."); - Qt::DateFormat enumFormat = Qt::DefaultLocaleShortDate; QDateTime dt = scope.engine->toVariant(argv[0], -1).toDateTime(); - QString formattedDt; - if (argc == 2) { - QV4::ScopedString s(scope, argv[1]); - if (s) { - QString format = s->toQString(); - formattedDt = dt.toString(format); - } else if (argv[1].isNumber()) { - quint32 intFormat = argv[1].asDouble(); - Qt::DateFormat format = Qt::DateFormat(intFormat); - formattedDt = dt.toString(format); - } else { - THROW_GENERIC_ERROR("Qt.formatDateTime(): Invalid datetime format"); - } - } else { - formattedDt = dt.toString(enumFormat); - } - - return Encode(scope.engine->newString(formattedDt)); + return formatDateTimeObject(dt, scope, QLatin1String("Qt.formatDateTime"), argc, argv); } /*! diff --git a/tests/auto/qml/qqmlqt/data/formattingLocale.qml b/tests/auto/qml/qqmlqt/data/formattingLocale.qml new file mode 100644 index 0000000000..9da349b101 --- /dev/null +++ b/tests/auto/qml/qqmlqt/data/formattingLocale.qml @@ -0,0 +1,12 @@ +import QtQml 2.15 + +QtObject { + required property var myDateTime + required property var myDate + property var myTime + + property string dateTimeString: Qt.formatDateTime(myDateTime, Qt.locale("de_DE"), Locale.NarrowFormat) + property string dateString: Qt.formatDate(myDate, Qt.locale("de_DE")) + + function invalidUsage() { Qt.formatTime(myTime, null, "hello") } +} diff --git a/tests/auto/qml/qqmlqt/tst_qqmlqt.cpp b/tests/auto/qml/qqmlqt/tst_qqmlqt.cpp index 15ef31464b..1a54397f1a 100644 --- a/tests/auto/qml/qqmlqt/tst_qqmlqt.cpp +++ b/tests/auto/qml/qqmlqt/tst_qqmlqt.cpp @@ -91,6 +91,7 @@ private slots: void dateTimeFormatting_data(); void dateTimeFormattingVariants(); void dateTimeFormattingVariants_data(); + void dateTimeFormattingWithLocale(); void isQtObject(); void btoa(); void atob(); @@ -780,12 +781,12 @@ void tst_qqmlqt::dateTimeFormatting() QQmlComponent component(&eng, testFileUrl("formatting.qml")); QStringList warnings; - warnings << component.url().toString() + ":37: Error: Qt.formatDate(): Invalid date format" - << component.url().toString() + ":36: Error: Qt.formatDate(): Invalid arguments" - << component.url().toString() + ":40: Error: Qt.formatTime(): Invalid time format" - << component.url().toString() + ":39: Error: Qt.formatTime(): Invalid arguments" - << component.url().toString() + ":43: Error: Qt.formatDateTime(): Invalid datetime format" - << component.url().toString() + ":42: Error: Qt.formatDateTime(): Invalid arguments"; + warnings << component.url().toString() + ":37: Error: Qt.formatDate(): Bad second argument (must be either string, number or locale)" + << component.url().toString() + ":36: Error: Qt.formatDate(): Missing argument" + << component.url().toString() + ":40: Error: Qt.formatTime(): Bad second argument (must be either string, number or locale)" + << component.url().toString() + ":39: Error: Qt.formatTime(): Missing argument" + << component.url().toString() + ":43: Error: Qt.formatDateTime(): Bad second argument (must be either string, number or locale)" + << component.url().toString() + ":42: Error: Qt.formatDateTime(): Missing argument"; foreach (const QString &warning, warnings) QTest::ignoreMessage(QtWarningMsg, qPrintable(warning)); @@ -815,6 +816,8 @@ void tst_qqmlqt::dateTimeFormatting() void tst_qqmlqt::dateTimeFormatting_data() { + QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED + // Test intentionally uses deprecated enumerators from Qt::DateFormat QTest::addColumn<QString>("method"); QTest::addColumn<QStringList>("inputProperties"); QTest::addColumn<QStringList>("expectedResults"); @@ -844,6 +847,7 @@ void tst_qqmlqt::dateTimeFormatting_data() << (QStringList() << dateTime.toString(Qt::DefaultLocaleShortDate) << dateTime.toString(Qt::DefaultLocaleLongDate) << dateTime.toString("M/d/yy H:m:s a")); + QT_WARNING_POP } void tst_qqmlqt::dateTimeFormattingVariants() @@ -856,12 +860,12 @@ void tst_qqmlqt::dateTimeFormattingVariants() QQmlComponent component(&eng, testFileUrl("formatting.qml")); QStringList warnings; - warnings << component.url().toString() + ":37: Error: Qt.formatDate(): Invalid date format" - << component.url().toString() + ":36: Error: Qt.formatDate(): Invalid arguments" - << component.url().toString() + ":40: Error: Qt.formatTime(): Invalid time format" - << component.url().toString() + ":39: Error: Qt.formatTime(): Invalid arguments" - << component.url().toString() + ":43: Error: Qt.formatDateTime(): Invalid datetime format" - << component.url().toString() + ":42: Error: Qt.formatDateTime(): Invalid arguments"; + warnings << component.url().toString() + ":37: Error: Qt.formatDate(): Bad second argument (must be either string, number or locale)" + << component.url().toString() + ":36: Error: Qt.formatDate(): Missing argument" + << component.url().toString() + ":40: Error: Qt.formatTime(): Bad second argument (must be either string, number or locale)" + << component.url().toString() + ":39: Error: Qt.formatTime(): Missing argument" + << component.url().toString() + ":43: Error: Qt.formatDateTime(): Bad second argument (must be either string, number or locale)" + << component.url().toString() + ":42: Error: Qt.formatDateTime(): Missing argument"; foreach (const QString &warning, warnings) QTest::ignoreMessage(QtWarningMsg, qPrintable(warning)); @@ -882,6 +886,8 @@ void tst_qqmlqt::dateTimeFormattingVariants() void tst_qqmlqt::dateTimeFormattingVariants_data() { + QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED + // Test intentionally uses deprecated enumerators from Qt::DateFormat QTest::addColumn<QString>("method"); QTest::addColumn<QVariant>("variant"); QTest::addColumn<QStringList>("expectedResults"); @@ -922,6 +928,28 @@ void tst_qqmlqt::dateTimeFormattingVariants_data() QTest::newRow("formatDate, int") << "formatDate" << QVariant::fromValue(integer) << (QStringList() << temporary.date().toString(Qt::DefaultLocaleShortDate) << temporary.date().toString(Qt::DefaultLocaleLongDate) << temporary.date().toString("ddd MMMM d yy")); QTest::newRow("formatDateTime, int") << "formatDateTime" << QVariant::fromValue(integer) << (QStringList() << temporary.toString(Qt::DefaultLocaleShortDate) << temporary.toString(Qt::DefaultLocaleLongDate) << temporary.toString("M/d/yy H:m:s a")); QTest::newRow("formatTime, int") << "formatTime" << QVariant::fromValue(integer) << (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")); + QT_WARNING_POP +} + +void tst_qqmlqt::dateTimeFormattingWithLocale() +{ + QQmlEngine engine; + auto url = testFileUrl("formattingLocale.qml"); + QQmlComponent comp(&engine, url); + QDateTime dateTime = QDateTime::fromString("M1d1y9800:01:02", + "'M'M'd'd'y'yyhh:mm:ss"); + QDate date(1995, 5, 17); + QScopedPointer<QObject> o(comp.createWithInitialProperties({ {"myDateTime", dateTime}, {"myDate", date} })); + QVERIFY(!o.isNull()); + + auto dateTimeString = o->property("dateTimeString").toString(); + QCOMPARE(dateTimeString, QLocale("de_DE").toString(dateTime, QLocale::NarrowFormat)); + auto dateString = o->property("dateString").toString(); + QCOMPARE(dateString, QLocale("de_DE").toString(date, QLocale::ShortFormat)); + + QString warningMsg = url.toString() + QLatin1String(":11: Error: Qt.formatTime(): Third argument must be a Locale format option"); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, warningMsg.toUtf8().constData()); + QMetaObject::invokeMethod(o.get(), "invalidUsage"); } void tst_qqmlqt::isQtObject() |