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 <thiago.macieira@intel.com>
This commit is contained in:
Edward Welbourne 2021-02-11 16:14:05 +01:00
parent 73fbf8bd30
commit 3cf84287e7
3 changed files with 52 additions and 25 deletions

View File

@ -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 <giuseppe.dangelo@kdab.com>
** 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()
*/

View File

@ -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();
}
}

View File

@ -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));