summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEdward Welbourne <edward.welbourne@qt.io>2021-02-09 14:24:18 +0100
committerEdward Welbourne <edward.welbourne@qt.io>2021-02-19 03:36:38 +0100
commitea80c076403ea8139fb34366df57cb83f0cb925a (patch)
treea3dfb6777a7f8fa8d1a770e7bc75c51a2b6787f2
parent038fee761a63090990953c3ee59e566c0ca06d21 (diff)
Fix parsing of dates with spaces in day or month names
Some locales have spaces in their names for months and days. This breaks parsing dates by splitting on spaces, as we were doing for TextDate. Reworked the TextDate parsers to take account of this possibility. Reworked and improved tst_QDate::roundtripGermanLocale(). It didn't configure the locale, so it was actually testing whatever locale you happen to run it with, not specifically German. It's a TextDate test, in any case, so should be #if-ed suitably. It merely did the round-trip, with a comment noting that it should produce no warnings. Changed it to do comparisons. This, with LANG=vi_VT, made it a reproducer for the issue. Fixes: QTBUG-88543 Change-Id: Ie0fb8c6d58f98efcdda2e046523172e66f3491b2 Reviewed-by: MÃ¥rten Nordheim <marten.nordheim@qt.io> Reviewed-by: Andrei Golubev <andrei.golubev@qt.io>
-rw-r--r--src/corelib/time/qdatetime.cpp122
-rw-r--r--tests/auto/corelib/time/qdate/tst_qdate.cpp23
2 files changed, 80 insertions, 65 deletions
diff --git a/src/corelib/time/qdatetime.cpp b/src/corelib/time/qdatetime.cpp
index 4ee930a901..6d259ebd35 100644
--- a/src/corelib/time/qdatetime.cpp
+++ b/src/corelib/time/qdatetime.cpp
@@ -1701,22 +1701,25 @@ QT_WARNING_POP
return rfcDateImpl(string).date;
default:
case Qt::TextDate: {
+ // Accept only "ddd MMM d yyyy" form (in contrast with QDateTime), e.g. "Sun Dec 1 1974"
QVector<QStringRef> parts = string.splitRef(QLatin1Char(' '), Qt::SkipEmptyParts);
-
- if (parts.count() != 4)
- return QDate();
-
- bool ok = false;
- int year = parts.at(3).toInt(&ok);
- int day = ok ? parts.at(2).toInt(&ok) : 0;
- if (!ok || !day)
+ const int count = parts.count();
+ bool ok = count > 3;
+ int year = ok ? parts.at(count - 1).toInt(&ok) : 0;
+ int day = ok ? parts.at(count - 2).toInt(&ok) : 0;
+ if (!ok || !day || !year)
return QDate();
- const int month = fromShortMonthName(parts.at(1), year);
- if (month == -1) // Month name matches no English or localised name.
- return QDate();
-
- return QDate(year, month, day);
+ // Some locales have multi-word month names:
+ int i = count - 3;
+ QString monthName = parts.at(i).toString();
+ while (i > 0) {
+ const int month = fromShortMonthName(monthName, year);
+ if (month > 0) // Month name matches an English or localised name.
+ return QDate(year, month, day);
+ monthName = parts.at(--i) + QLatin1Char(' ') + monthName;
+ }
+ return QDate();
}
case Qt::ISODate:
// Semi-strict parsing, must be long enough and have punctuators as separators
@@ -5378,49 +5381,66 @@ QT_WARNING_POP
case Qt::TextDate: {
QVector<QStringRef> parts = string.splitRef(QLatin1Char(' '), Qt::SkipEmptyParts);
- if ((parts.count() < 5) || (parts.count() > 6))
+ const int count = parts.count();
+ if (count < 5)
return QDateTime();
- // Accept "Sun Dec 1 13:02:00 1974" and "Sun 1. Dec 13:02:00 1974"
+ // Accept "Sun Dec 1 13:02:00 1974" and "Sun 1. Dec 13:02:00 1974" with
+ // optional GMT-based zone-suffix, with time and year in either order;
+ // and some locales have spaces even in short names of months and days.
+ int tail = count - 1, zonePart = 0;
+ if (parts.at(tail).startsWith(QLatin1String("GMT"), Qt::CaseInsensitive))
+ zonePart = tail--;
// Year and time can be in either order.
// Guess which by looking for ':' in the time
- int yearPart = 3;
- int timePart = 3;
- if (parts.at(3).contains(QLatin1Char(':')))
- yearPart = 4;
- else if (parts.at(4).contains(QLatin1Char(':')))
- timePart = 4;
+ int yearPart = tail;
+ int timePart = tail;
+ if (parts.at(timePart).contains(QLatin1Char(':')))
+ yearPart--;
+ else if (parts.at(timePart - 1).contains(QLatin1Char(':')))
+ timePart--;
else
return QDateTime();
- int month = 0;
- int day = 0;
- bool ok = false;
+ int dayPart = tail - 2; // but may be earlier, with a comma after:
+ for (int i = 1; i < dayPart; i++) {
+ if (parts.at(i).endsWith(QLatin1Char('.')))
+ dayPart = i; // exits the loop
+ }
+ bool ok = false;
int year = parts.at(yearPart).toInt(&ok);
- if (!ok || year == 0)
+ int day = 0;
+
+ if (ok && year) {
+ QStringRef dayStr = parts.at(dayPart);
+ if (dayStr.endsWith(QLatin1Char('.')))
+ dayStr.chop(1);
+ day = dayStr.toInt(&ok);
+ }
+ if (!ok || !year || !day)
return QDateTime();
- // Next try month then day
- month = fromShortMonthName(parts.at(1), year);
- if (month)
- day = parts.at(2).toInt(&ok);
-
- // If failed, try day then month
- if (!ok || !month || !day) {
- month = fromShortMonthName(parts.at(2), year);
- if (month) {
- QStringRef dayStr = parts.at(1);
- if (dayStr.endsWith(QLatin1Char('.'))) {
- dayStr = dayStr.left(dayStr.size() - 1);
- day = dayStr.toInt(&ok);
- }
+ int month = 0;
+ if (dayPart < tail - 2) {
+ // Easy case, month is the parts from dayPart + 1 to count - 3
+ int i = dayPart + 1;
+ QString monthName = parts.at(i).toString();
+ while (++i < tail - 1)
+ monthName += QLatin1Char(' ') + parts.at(i);
+ month = fromShortMonthName(monthName, year);
+ } else {
+ int i = dayPart - 1;
+ QString monthName = parts.at(i).toString();
+ while (i > 0) {
+ month = fromShortMonthName(monthName, year);
+ if (month > 0)
+ break;
+ monthName = parts.at(--i) + QLatin1Char(' ') + monthName;
}
}
-
- // If both failed, give up
- if (!ok || !month || !day)
+ if (month <= 0)
return QDateTime();
QDate date(year, month, day);
@@ -5464,21 +5484,15 @@ QT_WARNING_POP
if (!time.isValid())
return QDateTime();
- if (parts.count() == 5)
+ if (!zonePart)
return QDateTime(date, time, Qt::LocalTime);
- QStringView tz = parts.at(5);
- if (!tz.startsWith(QLatin1String("GMT"), Qt::CaseInsensitive))
- return QDateTime();
- tz = tz.mid(3);
- if (!tz.isEmpty()) {
- int offset = fromOffsetString(tz, &ok);
- if (!ok)
- return QDateTime();
- return QDateTime(date, time, Qt::OffsetFromUTC, offset);
- } else {
+ const QStringView tz = parts.at(zonePart).mid(3);
+ if (tz.isEmpty())
return QDateTime(date, time, Qt::UTC);
- }
+
+ int offset = fromOffsetString(tz, &ok);
+ return ok ? QDateTime(date, time, Qt::OffsetFromUTC, offset) : QDateTime();
}
}
diff --git a/tests/auto/corelib/time/qdate/tst_qdate.cpp b/tests/auto/corelib/time/qdate/tst_qdate.cpp
index 274bf4f6f0..f94f1c5fa6 100644
--- a/tests/auto/corelib/time/qdate/tst_qdate.cpp
+++ b/tests/auto/corelib/time/qdate/tst_qdate.cpp
@@ -90,7 +90,6 @@ private slots:
void yearsZeroToNinetyNine();
void printNegativeYear_data() const;
void printNegativeYear() const;
- void roundtripGermanLocale() const;
#if QT_CONFIG(textdate) && QT_DEPRECATED_SINCE(5, 10)
void shortDayName() const;
void standaloneShortDayName() const;
@@ -100,6 +99,7 @@ private slots:
void standaloneShortMonthName() const;
void longMonthName() const;
void standaloneLongMonthName() const;
+ void roundtripString() const;
#endif // textdate
void roundtrip() const;
void qdebug() const;
@@ -1484,16 +1484,6 @@ void tst_QDate::printNegativeYear() const
QCOMPARE(date.toString(QLatin1String("yyyy")), expect);
}
-void tst_QDate::roundtripGermanLocale() const
-{
- /* This code path should not result in warnings. */
- const QDate theDate(QDate::currentDate());
- theDate.fromString(theDate.toString(Qt::TextDate), Qt::TextDate);
-
- const QDateTime theDateTime(QDateTime::currentDateTime());
- theDateTime.fromString(theDateTime.toString(Qt::TextDate), Qt::TextDate);
-}
-
#if QT_CONFIG(textdate) && QT_DEPRECATED_SINCE(5, 10)
QT_WARNING_PUSH // the methods tested here are all deprecated
QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations")
@@ -1626,6 +1616,17 @@ void tst_QDate::standaloneLongMonthName() const
}
}
QT_WARNING_POP
+
+void tst_QDate::roundtripString() const
+{
+ /* This code path should not result in warnings, no matter what locale is set. */
+ const QDate date(QDate::currentDate());
+ QCOMPARE(date.fromString(date.toString(Qt::TextDate), Qt::TextDate), date);
+
+ const QDateTime now(QDateTime::currentDateTime());
+ const QDateTime when = now.addMSecs(-now.time().msec()); // TextDate rounds to whole seconds.
+ QCOMPARE(when.fromString(when.toString(Qt::TextDate), Qt::TextDate), when);
+}
#endif // textdate
void tst_QDate::roundtrip() const