diff options
author | Edward Welbourne <edward.welbourne@qt.io> | 2022-04-01 12:07:10 +0200 |
---|---|---|
committer | Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> | 2022-04-08 06:07:50 +0000 |
commit | d4800e542dd80ae1df89a7857c9fe65f40a2815c (patch) | |
tree | 06e1a885b30bd393a3e89d7aa2ab0703108e8892 | |
parent | 8c4f8ca931bf64e877f991547732c024d2b6cecc (diff) |
Fix assertion failure when parsing a doubly-invalid date-time text
When the date-time string falls in a spring-forward (so is invalid)
and one of the fields of the parsed string doesn't match the format
it's meant to (e.g. a single-digit seconds field when format ss was
specified), a check that the current fall-back date-time is between
the minimum and maximum for the parser object failed, triggering an
assertion.
In any case, an invalid default-value wasn't useful to the code that
parsed a single section of the date-time string, so brute-force the
current value to a valid date-time (when possible) using the usual
round-trip via milliseconds since the epoch.
Added the test-case which first revealed the problem, plus a couple
more informed by it, to exercise the same code-paths with fewer things
failing.
Fixes: QTBUG-102199
Change-Id: I658308614505ef25f4c97d0de6148acb54a65a0f
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
(cherry picked from commit 9fee35a2eda68b5968d911d5e14d298b91418d2c)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
-rw-r--r-- | src/corelib/time/qdatetimeparser.cpp | 11 | ||||
-rw-r--r-- | tests/auto/corelib/text/qlocale/tst_qlocale.cpp | 15 |
2 files changed, 22 insertions, 4 deletions
diff --git a/src/corelib/time/qdatetimeparser.cpp b/src/corelib/time/qdatetimeparser.cpp index 0741ddcc57..d2375d258f 100644 --- a/src/corelib/time/qdatetimeparser.cpp +++ b/src/corelib/time/qdatetimeparser.cpp @@ -759,6 +759,11 @@ QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionIndex, i && m_text.at(offset) == QLatin1Char('-')); const int negativeYearOffset = negate ? 1 : 0; + // If the fields we've read thus far imply a time in a spring-forward, + // coerce to a nearby valid time: + const QDateTime defaultValue = currentValue.isValid() ? currentValue + : QDateTime::fromMSecsSinceEpoch(currentValue.toMSecsSinceEpoch()); + QStringView sectionTextRef = QStringView { m_text }.mid(offset + negativeYearOffset, sectionmaxsize); @@ -794,7 +799,7 @@ QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionIndex, i m_text.replace(offset, used, sectiontext.constData(), used); break; } case TimeZoneSection: - result = findTimeZone(sectionTextRef, currentValue, + result = findTimeZone(sectionTextRef, defaultValue, absoluteMax(sectionIndex), absoluteMin(sectionIndex)); break; @@ -806,7 +811,7 @@ QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionIndex, i int num = 0, used = 0; if (sn.type == MonthSection) { const QDate minDate = getMinimum().date(); - const int year = currentValue.date().year(calendar); + const int year = defaultValue.date().year(calendar); const int min = (year == minDate.year(calendar)) ? minDate.month(calendar) : 1; num = findMonth(sectiontext.toLower(), min, sectionIndex, year, §iontext, &used); } else { @@ -900,7 +905,7 @@ QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionIndex, i } } else if (unfilled && (fi & (FixedWidth | Numeric)) == (FixedWidth | Numeric)) { - if (skipToNextSection(sectionIndex, currentValue, digitsStr)) { + if (skipToNextSection(sectionIndex, defaultValue, digitsStr)) { const int missingZeroes = sectionmaxsize - digitsStr.size(); result = ParsedSection(Acceptable, last, sectionmaxsize, missingZeroes); m_text.insert(offset, QString(missingZeroes, QLatin1Char('0'))); diff --git a/tests/auto/corelib/text/qlocale/tst_qlocale.cpp b/tests/auto/corelib/text/qlocale/tst_qlocale.cpp index 17e8e0cfb3..8a81151ea6 100644 --- a/tests/auto/corelib/text/qlocale/tst_qlocale.cpp +++ b/tests/auto/corelib/text/qlocale/tst_qlocale.cpp @@ -1859,7 +1859,8 @@ void tst_QLocale::toDateTime_data() QTest::addColumn<QDateTime>("result"); QTest::addColumn<QString>("format"); QTest::addColumn<QString>("string"); - QTest::addColumn<bool>("clean"); // No non-format letters in format string + // No non-format letters in format string, no time-zone (t format): + QTest::addColumn<bool>("clean"); QTest::newRow("1C") << "C" << QDateTime(QDate(1974, 12, 1), QTime(5, 14, 0)) << "d/M/yyyy hh:h:mm" << "1/12/1974 05:5:14" << true; @@ -1913,6 +1914,18 @@ void tst_QLocale::toDateTime_data() QTest::newRow("12no_NO") << "no_NO" << QDateTime(QDate(1974, 12, 1), QTime(15, 0, 0)) << "d'd'dd/M/yyh" << "1d01/12/7415" << false; + QTest::newRow("short-ss") // QTBUG-102199: trips over an assert in CET + << "C" << QDateTime() // Single-digit seconds does not match ss format. + << u"ddd, d MMM yyyy HH:mm:ss"_qs << u"Sun, 29 Mar 2020 02:26:3"_qs << true; + + QTest::newRow("short-ss-Z") // Same, but with a valid date-time: + << "C" << QDateTime() + << u"ddd, d MMM yyyy HH:mm:ss t"_qs << u"Sun, 29 Mar 2020 02:26:3 Z"_qs << false; + + QTest::newRow("s-Z") // Same, but with a format that accepts the single digit: + << "C" << QDateTime(QDate(2020, 3, 29), QTime(2, 26, 3), Qt::UTC) + << u"ddd, d MMM yyyy HH:mm:s t"_qs << u"Sun, 29 Mar 2020 02:26:3 Z"_qs << false; + QTest::newRow("RFC-1123") << "C" << QDateTime(QDate(2007, 11, 1), QTime(18, 8, 30)) << "ddd, dd MMM yyyy hh:mm:ss 'GMT'" << "Thu, 01 Nov 2007 18:08:30 GMT" << false; |