Support parsing time-zone specifiers more selectively
[ChangeLog][QtCore][QDateTime] The 't' format used in fromString() can now be repeated to restrict parsing to particular forms. Thus 'tt' now matches the [+-]hhmm offset format (no colon), 'ttt' the [+-]hh:mm offset format (with colon), and 'tttt' matches an actual zone name. When used singly, 't' still matches anything the parser knows how to interpret as a zone specifier. [ChangeLog][QtCore][QLocale] The 't' format in toDateTime() now has repeated forms, as for QDateTime::fromString(). Task-number: QTBUG-95966 Change-Id: I73753145cb66a56bc25a5c2dd5cb051ba982fa2c Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>bb10
parent
5818dd3cad
commit
0bc92c01e3
|
|
@ -5045,7 +5045,18 @@ QDateTime QDateTime::fromString(QStringView string, Qt::DateFormat format)
|
|||
|
||||
\table
|
||||
\header \li Expression \li Output
|
||||
\row \li t \li the timezone (for example "CEST")
|
||||
\row \li t
|
||||
\li the timezone (offset, name, "Z" or offset with "UTC" prefix)
|
||||
\row \li tt
|
||||
\li the timezone in offset format with no colon between hours and
|
||||
minutes (for example "+0200")
|
||||
\row \li ttt
|
||||
\li the timezone in offset format with a colon between hours and
|
||||
minutes (for example "+02:00")
|
||||
\row \li tttt
|
||||
\li the timezone name (for example "Europe/Berlin"). The name
|
||||
recognized are those known to \l QTimeZone, which may depend on the
|
||||
operating system in use.
|
||||
\endtable
|
||||
|
||||
If no 't' format specifier is present, the system's local time-zone is used.
|
||||
|
|
|
|||
|
|
@ -567,10 +567,8 @@ bool QDateTimeParser::parseFormat(QStringView newFormat)
|
|||
break;
|
||||
case 't':
|
||||
if (parserType == QMetaType::QDateTime) {
|
||||
// TODO (in qlocale.cpp's serialization, too) QTBUG-95966:
|
||||
// decide what different lengths of 't' format should do,
|
||||
// instead of repetition !
|
||||
const SectionNode sn = { TimeZoneSection, i - add, 1, 0 };
|
||||
const SectionNode sn
|
||||
= { TimeZoneSection, i - add, countRepeat(newFormat, i, 4), 0 };
|
||||
newSectionNodes.append(sn);
|
||||
appendSeparator(&newSeparators, newFormat, index, i - index, lastQuote);
|
||||
i += sn.count - 1;
|
||||
|
|
@ -830,7 +828,7 @@ QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionIndex, i
|
|||
case TimeZoneSection:
|
||||
result = findTimeZone(sectionTextRef, defaultValue,
|
||||
absoluteMax(sectionIndex),
|
||||
absoluteMin(sectionIndex));
|
||||
absoluteMin(sectionIndex), sn.count);
|
||||
break;
|
||||
case MonthSection:
|
||||
case DayOfWeekSectionShort:
|
||||
|
|
@ -1697,12 +1695,17 @@ int QDateTimeParser::findDay(const QString &str1, int startDay, int sectionIndex
|
|||
|
||||
Return's .value is UTC offset in seconds.
|
||||
The caller must verify that the offset is within a valid range.
|
||||
The mode is 1 for permissive parsing, 2 and 3 for strict offset-only format
|
||||
(no UTC prefix) with no colon for 2 and a colon for 3.
|
||||
*/
|
||||
QDateTimeParser::ParsedSection QDateTimeParser::findUtcOffset(QStringView str) const
|
||||
QDateTimeParser::ParsedSection QDateTimeParser::findUtcOffset(QStringView str, int mode) const
|
||||
{
|
||||
Q_ASSERT(mode > 0 && mode < 4);
|
||||
const bool startsWithUtc = str.startsWith("UTC"_L1);
|
||||
// Get rid of UTC prefix if it exists
|
||||
// Deal with UTC prefix if present:
|
||||
if (startsWithUtc) {
|
||||
if (mode != 1)
|
||||
return ParsedSection();
|
||||
str = str.sliced(3);
|
||||
if (str.isEmpty())
|
||||
return ParsedSection(Acceptable, 0, 3);
|
||||
|
|
@ -1736,6 +1739,8 @@ QDateTimeParser::ParsedSection QDateTimeParser::findUtcOffset(QStringView str) c
|
|||
i = hoursLength;
|
||||
hasColon = false;
|
||||
}
|
||||
if (mode == (hasColon ? 2 : 3))
|
||||
return ParsedSection();
|
||||
str.truncate(i); // The rest of the string is not part of the UTC offset
|
||||
|
||||
bool isInt = false;
|
||||
|
|
@ -1820,17 +1825,26 @@ QDateTimeParser::findTimeZoneName(QStringView str, const QDateTime &when) const
|
|||
|
||||
Return's .value is zone's offset, zone time - UTC time, in seconds.
|
||||
See QTimeZonePrivate::isValidId() for the format of zone names.
|
||||
*/
|
||||
|
||||
The mode is the number of 't' characters in the field specifier:
|
||||
* 1: any recognized format
|
||||
* 2: only the simple offset format, without colon
|
||||
* 3: only the simple offset format, with colon
|
||||
* 4: only a zone name
|
||||
*/
|
||||
QDateTimeParser::ParsedSection
|
||||
QDateTimeParser::findTimeZone(QStringView str, const QDateTime &when,
|
||||
int maxVal, int minVal) const
|
||||
int maxVal, int minVal, int mode) const
|
||||
{
|
||||
Q_ASSERT(mode > 0 && mode <= 4);
|
||||
// Short-cut Zulu suffix when it's all there is (rather than a prefix match):
|
||||
if (str == u'Z')
|
||||
if (mode == 1 && str == u'Z')
|
||||
return ParsedSection(Acceptable, 0, 1);
|
||||
|
||||
ParsedSection section = findUtcOffset(str);
|
||||
if (section.used <= 0) // if nothing used, try time zone parsing
|
||||
ParsedSection section;
|
||||
if (mode != 4)
|
||||
section = findUtcOffset(str, mode);
|
||||
if (mode != 2 && mode != 3 && section.used <= 0) // if nothing used, try time zone parsing
|
||||
section = findTimeZoneName(str, when);
|
||||
// It can be a well formed time zone specifier, but with value out of range
|
||||
if (section.state == Acceptable && (section.value < minVal || section.value > maxVal))
|
||||
|
|
@ -1838,11 +1852,13 @@ QDateTimeParser::findTimeZone(QStringView str, const QDateTime &when,
|
|||
if (section.used > 0)
|
||||
return section;
|
||||
|
||||
// Check if string is UTC or alias to UTC, after all other options
|
||||
if (str.startsWith("UTC"_L1))
|
||||
return ParsedSection(Acceptable, 0, 3);
|
||||
if (str.startsWith(u'Z'))
|
||||
return ParsedSection(Acceptable, 0, 1);
|
||||
if (mode == 1) {
|
||||
// Check if string is UTC or alias to UTC, after all other options
|
||||
if (str.startsWith("UTC"_L1))
|
||||
return ParsedSection(Acceptable, 0, 3);
|
||||
if (str.startsWith(u'Z'))
|
||||
return ParsedSection(Acceptable, 0, 1);
|
||||
}
|
||||
|
||||
return ParsedSection();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -173,10 +173,10 @@ private:
|
|||
int year, QString *monthName = nullptr, int *used = nullptr) const;
|
||||
int findDay(const QString &str1, int intDaystart, int sectionIndex,
|
||||
QString *dayName = nullptr, int *used = nullptr) const;
|
||||
ParsedSection findUtcOffset(QStringView str) const;
|
||||
ParsedSection findUtcOffset(QStringView str, int mode) const;
|
||||
ParsedSection findTimeZoneName(QStringView str, const QDateTime &when) const;
|
||||
ParsedSection findTimeZone(QStringView str, const QDateTime &when,
|
||||
int maxVal, int minVal) const;
|
||||
int maxVal, int minVal, int mode) const;
|
||||
// Implemented in qlocaltime.cpp:
|
||||
static int startsWithLocalTimeZone(const QStringView name);
|
||||
|
||||
|
|
|
|||
|
|
@ -2937,6 +2937,17 @@ void tst_QDateTime::fromStringStringFormat_data()
|
|||
QTest::newRow("integer overflow found by fuzzer")
|
||||
<< QStringLiteral("EEE1200000MUB") << QStringLiteral("t")
|
||||
<< QDateTime();
|
||||
|
||||
// Rich time-zone specifiers (QTBUG-95966):
|
||||
QTest::newRow("timezone-tt-with-offset:+0300")
|
||||
<< QString("2008-10-13 +0300 11.50") << QString("yyyy-MM-dd tt hh.mm")
|
||||
<< QDateTime(QDate(2008, 10, 13), QTime(11, 50), Qt::OffsetFromUTC, 10800);
|
||||
QTest::newRow("timezone-ttt-with-offset:+03:00")
|
||||
<< QString("2008-10-13 +03:00 11.50") << QString("yyyy-MM-dd ttt hh.mm")
|
||||
<< QDateTime(QDate(2008, 10, 13), QTime(11, 50), Qt::OffsetFromUTC, 10800);
|
||||
QTest::newRow("timezone-tttt-with-offset:+03:00")
|
||||
<< QString("2008-10-13 +03:00 11.50") << QString("yyyy-MM-dd tttt hh.mm")
|
||||
<< QDateTime(); // Offset not valid when zone name expected.
|
||||
}
|
||||
|
||||
void tst_QDateTime::fromStringStringFormat()
|
||||
|
|
@ -2978,14 +2989,23 @@ void tst_QDateTime::fromStringStringFormat_localTimeZone_data()
|
|||
QTimeZone etcGmtWithOffset("Etc/GMT+3");
|
||||
if (etcGmtWithOffset.isValid()) {
|
||||
lacksRows = false;
|
||||
QTest::newRow("local-timezone-with-offset:Etc/GMT+3") << QByteArrayLiteral("GMT")
|
||||
QTest::newRow("local-timezone-t-with-zone:Etc/GMT+3")
|
||||
<< QByteArrayLiteral("GMT")
|
||||
<< QString("2008-10-13 Etc/GMT+3 11.50") << QString("yyyy-MM-dd t hh.mm")
|
||||
<< QDateTime(QDate(2008, 10, 13), QTime(11, 50), etcGmtWithOffset);
|
||||
// TODO QTBUG-95966: find better ways to use repeated 't'
|
||||
QTest::newRow("double-timezone-with-offset:Etc/GMT+3") << QByteArrayLiteral("GMT")
|
||||
<< QString("2008-10-13 Etc/GMT+3Etc/GMT+3 11.50") << QString("yyyy-MM-dd tt hh.mm")
|
||||
QTest::newRow("local-timezone-tttt-with-zone:Etc/GMT+3")
|
||||
<< QByteArrayLiteral("GMT")
|
||||
<< QString("2008-10-13 Etc/GMT+3 11.50") << QString("yyyy-MM-dd tttt hh.mm")
|
||||
<< QDateTime(QDate(2008, 10, 13), QTime(11, 50), etcGmtWithOffset);
|
||||
}
|
||||
QTest::newRow("local-timezone-tt-with-zone:Etc/GMT+3")
|
||||
<< QByteArrayLiteral("GMT")
|
||||
<< QString("2008-10-13 Etc/GMT+3 11.50") << QString("yyyy-MM-dd tt hh.mm")
|
||||
<< QDateTime(); // Zone name not valid when offset expected
|
||||
QTest::newRow("local-timezone-ttt-with-zone:Etc/GMT+3")
|
||||
<< QByteArrayLiteral("GMT")
|
||||
<< QString("2008-10-13 Etc/GMT+3 11.50") << QString("yyyy-MM-dd ttt hh.mm")
|
||||
<< QDateTime(); // Zone name not valid when offset expected
|
||||
QTimeZone gmtWithOffset("GMT-2");
|
||||
if (gmtWithOffset.isValid()) {
|
||||
lacksRows = false;
|
||||
|
|
|
|||
Loading…
Reference in New Issue