From 3cf84287e71d8698c46ce31a6daad71056db8027 Mon Sep 17 00:00:00 2001 From: Edward Welbourne Date: Thu, 11 Feb 2021 16:14:05 +0100 Subject: [PATCH] Prepare TextDate to use UTC-offset rather than GMT-offset zone suffixes There are GMT-offset zones whose convention for the sign of the offset is the reverse of what we are (still) using, which is the usual convention for UTC-offset zone: for example, the Olson Database's Etc/GMT+3 has offset -3 hours in the UTC-based system we use, so we give it suffix GMT-0300. The UTC-based suffix is also what we use as the abbreviation for OffsetFromUTC() in toString(). For now this only adds support for parsing a planned future form: the old form using GMT is retained, to give client code some chance to prepare for a backwards-compatible transition. Although the GMT prefix is matched case-insensitively, only match UTC if fully upper-case; there is no meaningful precedent for case-insensitive usage here. [ChangeLog][QtCore][QDateTime] The Qt::TextDate format now recognizes UTC-based offset suffixes in addition to suffixes based on the deprecated alias GMT. This prepares for toString() to use such UTC-based suffixes for time-zones (fromString() cannot parse the present abbreviation suffix). A future release of Qt shall use UTC-based suffixes in place of the present GMT-based suffixes (which conflict with GMT-based IANA zone names) for Qt::LocalTime and Qt::OffsetFromUTC time-specs. Client code is encouraged to use and recognize UTC-based zone suffixes in preparation for that transition, unless compatibility with versions before 6.2 is required. Change-Id: I5a42a488f1232a30f4b427b7954759283423b9b3 Reviewed-by: Thiago Macieira --- src/corelib/global/qnamespace.qdoc | 19 +++++++--- src/corelib/time/qdatetime.cpp | 22 +++++++----- .../corelib/time/qdatetime/tst_qdatetime.cpp | 36 ++++++++++++------- 3 files changed, 52 insertions(+), 25 deletions(-) diff --git a/src/corelib/global/qnamespace.qdoc b/src/corelib/global/qnamespace.qdoc index dd6fccd678..c100a7996c 100644 --- a/src/corelib/global/qnamespace.qdoc +++ b/src/corelib/global/qnamespace.qdoc @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2020 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Copyright (C) 2020 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Giuseppe D'Angelo ** Contact: https://www.qt.io/licensing/ ** @@ -669,9 +669,15 @@ names will be short names in English (C locale). This effectively uses, for a date, format \c{ddd MMM d yyyy}, for a time \c{HH:mm:ss} and combines these as \c{ddd MMM d HH:mm:ss yyyy} for a date-time, with an optional - suffix indicating time-zone or offset from UTC, where relevant. A fractional - part is also recognized on the seconds of a time part, as \c{HH:mm:ss.zzz}, - when reading from a string. + zone-offset suffix, where relevant. When reading from a string, a + fractional part is also recognized on the seconds of a time part, as + \c{HH:mm:ss.zzz}, and some minor variants on the format may be recognized, + for compatibility with earlier versions of Qt and with changes to the format + planned for the future. In particular, the zone-offset suffix presently uses + \c{GMT[±tzoff]} with a \c{tzoff} in \c{HH[[:]mm]} format (two-digit hour and + optional two-digit minutes, with optional colon separator); this shall + change to use \c{UTC} in place of \c{GMT} in a future release of Qt, so the + planned \c{UTC} format is recognized. \value ISODateWithMs \l{ISO 8601} extended format: uses \c{yyyy-MM-dd} for dates, \c{HH:mm:ss.zzz} for times or \c{yyyy-MM-ddTHH:mm:ss.zzz} @@ -721,6 +727,11 @@ of the format. The plus-or-minus character \c{'±'} here stands for either sign character, \c{'-'} for minus or \c{'+'} for plus. + \note Zone offsets are measured positive to the east of Greenwich, negative + to the west, as is usual for UTC-based offset notations (conflicting with + some GMT-based zones-names, such as \c{Etc/GMT+3}, which use the opposite + convention). + \sa QDate::toString(), QTime::toString(), QDateTime::toString(), QDate::fromString(), QTime::fromString(), QDateTime::fromString() */ diff --git a/src/corelib/time/qdatetime.cpp b/src/corelib/time/qdatetime.cpp index 9ad9c9c597..28b3cbb059 100644 --- a/src/corelib/time/qdatetime.cpp +++ b/src/corelib/time/qdatetime.cpp @@ -4033,7 +4033,11 @@ QString QDateTime::toString(Qt::DateFormat format) const break; #endif default: +#if 0 // ### Qt 7 GMT: use UTC instead, see qnamespace.qdoc documentation + buf += QLatin1String(" UTC"); +#else buf += QLatin1String(" GMT"); +#endif if (getSpec(d) == Qt::OffsetFromUTC) buf += toOffsetString(Qt::TextDate, offsetFromUtc()); } @@ -4933,17 +4937,17 @@ QDateTime QDateTime::fromString(QStringView string, Qt::DateFormat format) return QDateTime(date, time, Qt::LocalTime); QStringView tz = parts.at(5); - if (!tz.startsWith(QLatin1String("GMT"), Qt::CaseInsensitive)) - return QDateTime(); - tz = tz.sliced(3); - if (!tz.isEmpty()) { + if (tz.startsWith(QLatin1String("UTC")) + // GMT has long been deprecated as an alias for UTC. + || tz.startsWith(QLatin1String("GMT"), Qt::CaseInsensitive)) { + tz = tz.sliced(3); + if (tz.isEmpty()) + return QDateTime(date, time, Qt::UTC); + int offset = fromOffsetString(tz, &ok); - if (!ok) - return QDateTime(); - return QDateTime(date, time, Qt::OffsetFromUTC, offset); - } else { - return QDateTime(date, time, Qt::UTC); + return ok ? QDateTime(date, time, Qt::OffsetFromUTC, offset) : QDateTime(); } + return QDateTime(); } } diff --git a/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp b/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp index 10d276cd5f..4bf78cb93a 100644 --- a/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp +++ b/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp @@ -895,6 +895,7 @@ void tst_QDateTime::toString_textDate_data() const QString wednesdayJanuary = QLocale::c().dayName(3, QLocale::ShortFormat) + ' ' + QLocale::c().monthName(1, QLocale::ShortFormat); + // ### Qt 7 GMT: change to UTC - see matching QDateTime::fromString() comment QTest::newRow("localtime") << QDateTime(QDate(2013, 1, 2), QTime(1, 2, 3), Qt::LocalTime) << wednesdayJanuary + QString(" 2 01:02:03 2013"); QTest::newRow("utc") << QDateTime(QDate(2013, 1, 2), QTime(1, 2, 3), Qt::UTC) @@ -929,6 +930,7 @@ void tst_QDateTime::toString_textDate() void tst_QDateTime::toString_textDate_extra() { + // ### Qt 7 GMT: change to UTC - see matching QDateTime::fromString() comment auto endsWithGmt = [](const QDateTime &dt) { return dt.toString().endsWith(QLatin1String("GMT")); }; @@ -2247,21 +2249,31 @@ void tst_QDateTime::fromStringDateFormat_data() << Qt::TextDate << QDateTime(); QTest::newRow("text data7") << QString::fromLatin1("Thu Jan 1 1970 00:00:00") << Qt::TextDate << QDateTime(QDate(1970, 1, 1), QTime(0, 0), Qt::LocalTime); - QTest::newRow("text data8") << QString::fromLatin1("Thu Jan 1 00:12:34 1970 GMT+foo") + QTest::newRow("text bad offset") << QString::fromLatin1("Thu Jan 1 00:12:34 1970 UTC+foo") << Qt::TextDate << QDateTime(); - QTest::newRow("text data9") << QString::fromLatin1("Thu Jan 1 00:12:34 1970 GMT") + QTest::newRow("text UTC early") << QString::fromLatin1("Thu Jan 1 00:12:34 1970 UTC") << Qt::TextDate << QDateTime(QDate(1970, 1, 1), QTime(0, 12, 34), Qt::UTC); - QTest::newRow("text data10") << QString::fromLatin1("Thu Jan 1 00:12:34 1970 GMT-0300") + QTest::newRow("text UTC-3 early") << QString::fromLatin1("Thu Jan 1 00:12:34 1970 UTC-0300") << Qt::TextDate << QDateTime(QDate(1970, 1, 1), QTime(3, 12, 34), Qt::UTC); - QTest::newRow("text data11") << QString::fromLatin1("Thu Jan 1 00:12:34 1970 GMT+0300") + QTest::newRow("text UTC+3 early") << QString::fromLatin1("Thu Jan 1 00:12:34 1970 UTC+0300") << Qt::TextDate << QDateTime(QDate(1969, 12, 31), QTime(21, 12, 34), Qt::UTC); - QTest::newRow("text data12") << QString::fromLatin1("Thu Jan 1 00:12:34 1970 gmt") - << Qt::TextDate << QDateTime(QDate(1970, 1, 1), QTime(0, 12, 34), Qt::UTC); - QTest::newRow("text data13") << QString::fromLatin1("Thu Jan 1 1970 00:12:34 GMT+0100") + QTest::newRow("text UTC+1 early") << QString::fromLatin1("Thu Jan 1 1970 00:12:34 UTC+0100") << Qt::TextDate << QDateTime(QDate(1969, 12, 31), QTime(23, 12, 34), Qt::UTC); + // We produce use GMT as prefix, so need to parse it: + QTest::newRow("text GMT early") + << QString::fromLatin1("Thu Jan 1 00:12:34 1970 GMT") << Qt::TextDate + << QDateTime(QDate(1970, 1, 1), QTime(0, 12, 34), Qt::UTC); + QTest::newRow("text GMT+3 early") + << QString::fromLatin1("Thu Jan 1 00:12:34 1970 GMT+0300") << Qt::TextDate + << QDateTime(QDate(1969, 12, 31), QTime(21, 12, 34), Qt::UTC); + // ... and we match (only) it case-insensitively: + QTest::newRow("text gmt early") + << QString::fromLatin1("Thu Jan 1 00:12:34 1970 gmt") << Qt::TextDate + << QDateTime(QDate(1970, 1, 1), QTime(0, 12, 34), Qt::UTC); + QTest::newRow("text empty") << QString::fromLatin1("") << Qt::TextDate << QDateTime(); - QTest::newRow("text too many parts") << QString::fromLatin1("Thu Jan 1 00:12:34 1970 gmt +0100") + QTest::newRow("text too many parts") << QString::fromLatin1("Thu Jan 1 00:12:34 1970 UTC +0100") << Qt::TextDate << QDateTime(); QTest::newRow("text invalid month name") << QString::fromLatin1("Thu Jaz 1 1970 00:12:34") << Qt::TextDate << QDateTime(); @@ -2283,13 +2295,13 @@ void tst_QDateTime::fromStringDateFormat_data() << Qt::TextDate << QDateTime(); QTest::newRow("text invalid second") << QString::fromLatin1("Thu 1. Jan 1970 00:00:0X") << Qt::TextDate << QDateTime(); - QTest::newRow("text invalid gmt specifier #1") << QString::fromLatin1("Thu 1. Jan 1970 00:00:00 DMT") + QTest::newRow("text bad UTC specifier #1") << QString::fromLatin1("Thu 1. Jan 1970 00:00:00 DMT") << Qt::TextDate << QDateTime(); - QTest::newRow("text invalid gmt specifier #2") << QString::fromLatin1("Thu 1. Jan 1970 00:00:00 GMTx0200") + QTest::newRow("text bad UTC specifier #2") << QString::fromLatin1("Thu 1. Jan 1970 00:00:00 UTCx0200") << Qt::TextDate << QDateTime(); - QTest::newRow("text invalid gmt hour") << QString::fromLatin1("Thu 1. Jan 1970 00:00:00 GMT+0X00") + QTest::newRow("text bad UTC hour") << QString::fromLatin1("Thu 1. Jan 1970 00:00:00 UTC+0X00") << Qt::TextDate << QDateTime(); - QTest::newRow("text invalid gmt minute") << QString::fromLatin1("Thu 1. Jan 1970 00:00:00 GMT+000X") + QTest::newRow("text bad UTC minute") << QString::fromLatin1("Thu 1. Jan 1970 00:00:00 UTC+000X") << Qt::TextDate << QDateTime(); QTest::newRow("text second fraction") << QString::fromLatin1("Mon 6. May 2013 01:02:03.456") << Qt::TextDate << QDateTime(QDate(2013, 5, 6), QTime(1, 2, 3, 456));