summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEdward Welbourne <edward.welbourne@qt.io>2022-04-01 12:07:10 +0200
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2022-04-08 06:07:50 +0000
commitd4800e542dd80ae1df89a7857c9fe65f40a2815c (patch)
tree06e1a885b30bd393a3e89d7aa2ab0703108e8892
parent8c4f8ca931bf64e877f991547732c024d2b6cecc (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.cpp11
-rw-r--r--tests/auto/corelib/text/qlocale/tst_qlocale.cpp15
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 &currentValue, 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 &currentValue, 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 &currentValue, 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, &sectiontext, &used);
} else {
@@ -900,7 +905,7 @@ QDateTimeParser::parseSection(const QDateTime &currentValue, 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;