summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEdward Welbourne <edward.welbourne@qt.io>2023-11-08 17:08:49 +0100
committerEdward Welbourne <edward.welbourne@qt.io>2023-12-08 11:40:36 +0100
commit41f84f3ddb780ec751e3fc706dd242fc4a99de7a (patch)
treec513e83065b9a62f11ed26270af58f336af81424
parentb8fac538032cee6eb6f4e5dcbb5a31485bb59e46 (diff)
Give the caller control over the century used for two-digit dates
The twentieth century is now some way behind us, so using its years when parsing a date-time format that only provides the last two digits is increasingly likely to produce unwelcome results. Most such formats are saved by the "redundant" presence of a day-of-week field but, for those that are not (notably including ASN.1 date fields), there is a need to provide some way to over-ride the twentieth century default. Allow the caller to pass a base year to the fromString() methods, of QDate and QDateTime, and to QLocale's toDate() and toDateTime(), that indicates the first of 100 consecutive years, among which the two digits given can select a year. Add some test-cases to exercise the new API. [ChangeLog][QtCore][QDate] When fromString() has only a two-digit year to go on, it is now possible to set the start-year of the century within which this selects. [ChangeLog][QtCore][QDateTime] When fromString() has only a two-digit year to go on, it is now possible to set the start-year of the century within which this selects. [ChangeLog][QtCore][QLocale] When toDate() or toDateTime() has only a two-digit year to go on, it is now possible to set the start-year of the century within which this selects. Fixes: QTBUG-46843 Change-Id: Ieb312ee9e0b80557a15edcb0e6d75a57b10d7a62 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: MÃ¥rten Nordheim <marten.nordheim@qt.io>
-rw-r--r--src/corelib/compat/removed_api.cpp40
-rw-r--r--src/corelib/text/qlocale.cpp62
-rw-r--r--src/corelib/text/qlocale.h31
-rw-r--r--src/corelib/time/qdatetime.cpp64
-rw-r--r--src/corelib/time/qdatetime.h61
-rw-r--r--src/corelib/time/qdatetimeparser.cpp36
-rw-r--r--src/corelib/time/qdatetimeparser_p.h6
-rw-r--r--tests/auto/corelib/time/qdate/tst_qdate.cpp11
-rw-r--r--tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp29
9 files changed, 282 insertions, 58 deletions
diff --git a/src/corelib/compat/removed_api.cpp b/src/corelib/compat/removed_api.cpp
index ac4aea9ef3..355d37bdb8 100644
--- a/src/corelib/compat/removed_api.cpp
+++ b/src/corelib/compat/removed_api.cpp
@@ -792,6 +792,46 @@ QString QLocale::bcp47Name() const
return bcp47Name(TagSeparator::Dash);
}
+QDate QLocale::toDate(const QString &string, FormatType format) const
+{
+ return toDate(string, dateFormat(format), FirstTwoDigitYear);
+}
+
+QDate QLocale::toDate(const QString &string, FormatType format, QCalendar cal) const
+{
+ return toDate(string, dateFormat(format), cal, FirstTwoDigitYear);
+}
+
+QDateTime QLocale::toDateTime(const QString &string, FormatType format) const
+{
+ return toDateTime(string, dateTimeFormat(format), FirstTwoDigitYear);
+}
+
+QDateTime QLocale::toDateTime(const QString &string, FormatType format, QCalendar cal) const
+{
+ return toDateTime(string, dateTimeFormat(format), cal, FirstTwoDigitYear);
+}
+
+QDate QLocale::toDate(const QString &string, const QString &format) const
+{
+ return toDate(string, format, QCalendar(), FirstTwoDigitYear);
+}
+
+QDate QLocale::toDate(const QString &string, const QString &format, QCalendar cal) const
+{
+ return toDate(string, format, cal, FirstTwoDigitYear);
+}
+
+QDateTime QLocale::toDateTime(const QString &string, const QString &format) const
+{
+ return toDateTime(string, format, QCalendar(), FirstTwoDigitYear);
+}
+
+QDateTime QLocale::toDateTime(const QString &string, const QString &format, QCalendar cal) const
+{
+ return toDateTime(string, format, cal, FirstTwoDigitYear);
+}
+
#include "qobject.h"
void qt_qFindChildren_helper(const QObject *parent, const QString &name, const QMetaObject &mo,
diff --git a/src/corelib/text/qlocale.cpp b/src/corelib/text/qlocale.cpp
index adaa1774ee..7e74712ac7 100644
--- a/src/corelib/text/qlocale.cpp
+++ b/src/corelib/text/qlocale.cpp
@@ -2432,6 +2432,16 @@ QTime QLocale::toTime(const QString &string, FormatType format) const
Parses \a string and returns the date it represents. The format of the date
string is chosen according to the \a format parameter (see dateFormat()).
+//! [base-year-for-short]
+ Some locales use, particularly for ShortFormat, only the last two digits of
+ the year. In such a case, the 100 years starting at \a baseYear are the
+ candidates first considered. Prior to 6.7 there was no \a baseYear parameter
+ and 1900 was always used. This is the default for \a baseYear, selecting a
+ year from then to 1999. In some cases, other fields may lead to the next or
+ previous century being selected, to get a result consistent with all fields
+ given. See \l QDate::fromString() for details.
+//! [base-year-for-short]
+
\note Month and day names, where used, must be given in the locale's
language.
@@ -2439,18 +2449,18 @@ QTime QLocale::toTime(const QString &string, FormatType format) const
\sa dateFormat(), toTime(), toDateTime(), QDate::fromString()
*/
-QDate QLocale::toDate(const QString &string, FormatType format) const
+QDate QLocale::toDate(const QString &string, FormatType format, int baseYear) const
{
- return toDate(string, dateFormat(format));
+ return toDate(string, dateFormat(format), baseYear);
}
/*!
\since 5.14
\overload
*/
-QDate QLocale::toDate(const QString &string, FormatType format, QCalendar cal) const
+QDate QLocale::toDate(const QString &string, FormatType format, QCalendar cal, int baseYear) const
{
- return toDate(string, dateFormat(format), cal);
+ return toDate(string, dateFormat(format), cal, baseYear);
}
/*!
@@ -2462,6 +2472,8 @@ QDate QLocale::toDate(const QString &string, FormatType format, QCalendar cal) c
date string is chosen according to the \a format parameter (see
dateFormat()).
+ \include qlocale.cpp base-year-for-short
+
\note Month and day names, where used, must be given in the locale's
language. Any am/pm indicators used must match \l amText() or \l pmText(),
ignoring case.
@@ -2470,18 +2482,19 @@ QDate QLocale::toDate(const QString &string, FormatType format, QCalendar cal) c
\sa dateTimeFormat(), toTime(), toDate(), QDateTime::fromString()
*/
-QDateTime QLocale::toDateTime(const QString &string, FormatType format) const
+QDateTime QLocale::toDateTime(const QString &string, FormatType format, int baseYear) const
{
- return toDateTime(string, dateTimeFormat(format));
+ return toDateTime(string, dateTimeFormat(format), baseYear);
}
/*!
\since 5.14
\overload
*/
-QDateTime QLocale::toDateTime(const QString &string, FormatType format, QCalendar cal) const
+QDateTime QLocale::toDateTime(const QString &string, FormatType format, QCalendar cal,
+ int baseYear) const
{
- return toDateTime(string, dateTimeFormat(format), cal);
+ return toDateTime(string, dateTimeFormat(format), cal, baseYear);
}
/*!
@@ -2522,6 +2535,16 @@ QTime QLocale::toTime(const QString &string, const QString &format) const
Parses \a string and returns the date it represents. See QDate::fromString()
for the interpretation of \a format.
+//! [base-year-for-two-digit]
+ When \a format only specifies the last two digits of a year, the 100 years
+ starting at \a baseYear are the candidates first considered. Prior to 6.7
+ there was no \a baseYear parameter and 1900 was always used. This is the
+ default for \a baseYear, selecting a year from then to 1999. In some cases,
+ other fields may lead to the next or previous century being selected, to get
+ a result consistent with all fields given. See \l QDate::fromString() for
+ details.
+//! [base-year-for-two-digit]
+
\note Month and day names, where used, must be given in the locale's
language.
@@ -2529,26 +2552,27 @@ QTime QLocale::toTime(const QString &string, const QString &format) const
\sa dateFormat(), toTime(), toDateTime(), QDate::fromString()
*/
-QDate QLocale::toDate(const QString &string, const QString &format) const
+QDate QLocale::toDate(const QString &string, const QString &format, int baseYear) const
{
- return toDate(string, format, QCalendar());
+ return toDate(string, format, QCalendar(), baseYear);
}
/*!
\since 5.14
\overload
*/
-QDate QLocale::toDate(const QString &string, const QString &format, QCalendar cal) const
+QDate QLocale::toDate(const QString &string, const QString &format, QCalendar cal, int baseYear) const
{
QDate date;
#if QT_CONFIG(datetimeparser)
QDateTimeParser dt(QMetaType::QDate, QDateTimeParser::FromString, cal);
dt.setDefaultLocale(*this);
if (dt.parseFormat(format))
- dt.fromString(string, &date, nullptr);
+ dt.fromString(string, &date, nullptr, baseYear);
#else
Q_UNUSED(string);
Q_UNUSED(format);
+ Q_UNUSED(baseYear);
Q_UNUSED(cal);
#endif
return date;
@@ -2562,6 +2586,8 @@ QDate QLocale::toDate(const QString &string, const QString &format, QCalendar ca
Parses \a string and returns the date-time it represents. See
QDateTime::fromString() for the interpretation of \a format.
+ \include qlocale.cpp base-year-for-two-digit
+
\note Month and day names, where used, must be given in the locale's
language. Any am/pm indicators used must match \l amText() or \l pmText(),
ignoring case.
@@ -2575,27 +2601,31 @@ QDate QLocale::toDate(const QString &string, const QString &format, QCalendar ca
\sa dateTimeFormat(), toTime(), toDate(), QDateTime::fromString()
*/
-QDateTime QLocale::toDateTime(const QString &string, const QString &format) const
+QDateTime QLocale::toDateTime(const QString &string, const QString &format, int baseYear) const
{
- return toDateTime(string, format, QCalendar());
+ return toDateTime(string, format, QCalendar(), baseYear);
}
/*!
\since 5.14
\overload
*/
-QDateTime QLocale::toDateTime(const QString &string, const QString &format, QCalendar cal) const
+QDateTime QLocale::toDateTime(const QString &string, const QString &format, QCalendar cal,
+ int baseYear) const
{
#if QT_CONFIG(datetimeparser)
QDateTime datetime;
QDateTimeParser dt(QMetaType::QDateTime, QDateTimeParser::FromString, cal);
dt.setDefaultLocale(*this);
- if (dt.parseFormat(format) && (dt.fromString(string, &datetime) || !datetime.isValid()))
+ if (dt.parseFormat(format) && (dt.fromString(string, &datetime, baseYear)
+ || !datetime.isValid())) {
return datetime;
+ }
#else
Q_UNUSED(string);
Q_UNUSED(format);
+ Q_UNUSED(baseYear);
Q_UNUSED(cal);
#endif
return QDateTime();
diff --git a/src/corelib/text/qlocale.h b/src/corelib/text/qlocale.h
index 8137bb31f5..bc04ae5cdc 100644
--- a/src/corelib/text/qlocale.h
+++ b/src/corelib/text/qlocale.h
@@ -35,6 +35,8 @@ class Q_CORE_EXPORT QLocale
friend class QTextStream;
friend class QTextStreamPrivate;
+ static constexpr int FirstTwoDigitYear = 1900; // sync with QDate
+
public:
// see qlocale_data_p.h for more info on generated data
// GENERATED PART STARTS HERE
@@ -1011,18 +1013,39 @@ public:
QString dateFormat(FormatType format = LongFormat) const;
QString timeFormat(FormatType format = LongFormat) const;
QString dateTimeFormat(FormatType format = LongFormat) const;
+ // QCalendar's header has to #include QLocale's, preventing the reverse, so
+ // QCalendar parameters can't have defaults here.
#if QT_CONFIG(datestring)
- QDate toDate(const QString &string, FormatType = LongFormat) const;
QTime toTime(const QString &string, FormatType = LongFormat) const;
- QDateTime toDateTime(const QString &string, FormatType format = LongFormat) const;
- QDate toDate(const QString &string, const QString &format) const;
QTime toTime(const QString &string, const QString &format) const;
+# if QT_CORE_REMOVED_SINCE(6, 7)
+ QDate toDate(const QString &string, FormatType = LongFormat) const;
+ QDate toDate(const QString &string, const QString &format) const;
+ QDateTime toDateTime(const QString &string, FormatType format = LongFormat) const;
QDateTime toDateTime(const QString &string, const QString &format) const;
// Calendar-aware API
QDate toDate(const QString &string, FormatType format, QCalendar cal) const;
- QDateTime toDateTime(const QString &string, FormatType format, QCalendar cal) const;
QDate toDate(const QString &string, const QString &format, QCalendar cal) const;
+ QDateTime toDateTime(const QString &string, FormatType format, QCalendar cal) const;
QDateTime toDateTime(const QString &string, const QString &format, QCalendar cal) const;
+# endif
+ QDate toDate(const QString &string, FormatType = LongFormat,
+ int baseYear = FirstTwoDigitYear) const;
+ QDate toDate(const QString &string, const QString &format,
+ int baseYear = FirstTwoDigitYear) const;
+ QDateTime toDateTime(const QString &string, FormatType format = LongFormat,
+ int baseYear = FirstTwoDigitYear) const;
+ QDateTime toDateTime(const QString &string, const QString &format,
+ int baseYear = FirstTwoDigitYear) const;
+ // Calendar-aware API
+ QDate toDate(const QString &string, FormatType format, QCalendar cal,
+ int baseYear = FirstTwoDigitYear) const;
+ QDate toDate(const QString &string, const QString &format, QCalendar cal,
+ int baseYear = FirstTwoDigitYear) const;
+ QDateTime toDateTime(const QString &string, FormatType format, QCalendar cal,
+ int baseYear = FirstTwoDigitYear) const;
+ QDateTime toDateTime(const QString &string, const QString &format, QCalendar cal,
+ int baseYear = FirstTwoDigitYear) const;
#endif
QString decimalPoint() const;
diff --git a/src/corelib/time/qdatetime.cpp b/src/corelib/time/qdatetime.cpp
index 273c38767a..99f6522abd 100644
--- a/src/corelib/time/qdatetime.cpp
+++ b/src/corelib/time/qdatetime.cpp
@@ -1670,7 +1670,7 @@ QDate QDate::fromString(QStringView string, Qt::DateFormat format)
}
/*!
- \fn QDate QDate::fromString(const QString &string, const QString &format, QCalendar cal)
+ \fn QDate QDate::fromString(const QString &string, const QString &format, int baseYear, QCalendar cal)
Returns the QDate represented by the \a string, using the \a
format given, or an invalid date if the string cannot be parsed.
@@ -1720,11 +1720,19 @@ QDate QDate::fromString(QStringView string, Qt::DateFormat format)
\table
\header \li Field \li Default value
- \row \li Year \li 1900
+ \row \li Year \li \a baseYear (or 1900)
\row \li Month \li 1 (January)
\row \li Day \li 1
\endtable
+ When \a format only specifies the last two digits of a year, the 100 years
+ starting at \a baseYear are the candidates first considered. Prior to 6.7
+ there was no \a baseYear parameter and 1900 was always used. This is the
+ default for \a baseYear, selecting a year from then to 1999. Passing 1976 as
+ \a baseYear will select a year from 1976 through 2075, for example. In some
+ cases, other fields may lead to the next or previous century being selected,
+ to get a result consistent with all fields given.
+
The following examples demonstrate the default values:
\snippet code/src_corelib_time_qdatetime.cpp 3
@@ -1751,21 +1759,40 @@ QDate QDate::fromString(QStringView string, Qt::DateFormat format)
\overload
\since 6.0
*/
-QDate QDate::fromString(const QString &string, QStringView format, QCalendar cal)
+QDate QDate::fromString(const QString &string, QStringView format, int baseYear, QCalendar cal)
{
QDate date;
#if QT_CONFIG(datetimeparser)
QDateTimeParser dt(QMetaType::QDate, QDateTimeParser::FromString, cal);
dt.setDefaultLocale(QLocale::c());
if (dt.parseFormat(format))
- dt.fromString(string, &date, nullptr);
+ dt.fromString(string, &date, nullptr, baseYear);
#else
Q_UNUSED(string);
Q_UNUSED(format);
+ Q_UNUSED(baseYear);
Q_UNUSED(cal);
#endif
return date;
}
+
+/*!
+ \fn QDate QDate::fromString(const QString &string, const QString &format, QCalendar cal)
+ \overload
+ \since 5.14
+*/
+
+/*!
+ \fn QDate QDate::fromString(const QString &string, QStringView format, QCalendar cal)
+ \overload
+ \since 6.0
+*/
+
+/*!
+ \fn QDate QDate::fromString(QStringView string, QStringView format, int baseYear, QCalendar cal)
+ \overload
+ \since 6.7
+*/
#endif // datestring
/*!
@@ -5636,13 +5663,15 @@ QDateTime QDateTime::fromString(QStringView string, Qt::DateFormat format)
}
/*!
- \fn QDateTime QDateTime::fromString(const QString &string, const QString &format, QCalendar cal)
+ \fn QDateTime QDateTime::fromString(const QString &string, const QString &format, int baseYear, QCalendar cal)
Returns the QDateTime represented by the \a string, using the \a
format given, or an invalid datetime if the string cannot be parsed.
Uses the calendar \a cal if supplied, else Gregorian.
+ \include qlocale.cpp base-year-for-two-digit
+
In addition to the expressions, recognized in the format string to represent
parts of the date and time, by QDate::fromString() and QTime::fromString(),
this method supports:
@@ -5730,23 +5759,44 @@ QDateTime QDateTime::fromString(QStringView string, Qt::DateFormat format)
\overload
\since 6.0
*/
-QDateTime QDateTime::fromString(const QString &string, QStringView format, QCalendar cal)
+QDateTime QDateTime::fromString(const QString &string, QStringView format, int baseYear,
+ QCalendar cal)
{
#if QT_CONFIG(datetimeparser)
QDateTime datetime;
QDateTimeParser dt(QMetaType::QDateTime, QDateTimeParser::FromString, cal);
dt.setDefaultLocale(QLocale::c());
- if (dt.parseFormat(format) && (dt.fromString(string, &datetime) || !datetime.isValid()))
+ if (dt.parseFormat(format) && (dt.fromString(string, &datetime, baseYear)
+ || !datetime.isValid())) {
return datetime;
+ }
#else
Q_UNUSED(string);
Q_UNUSED(format);
+ Q_UNUSED(baseYear);
Q_UNUSED(cal);
#endif
return QDateTime();
}
+/*!
+ \fn QDateTime QDateTime::fromString(const QString &string, const QString &format, QCalendar cal)
+ \overload
+ \since 5.14
+*/
+
+/*!
+ \fn QDateTime QDateTime::fromString(const QString &string, QStringView format, QCalendar cal)
+ \overload
+ \since 6.0
+*/
+
+/*!
+ \fn QDateTime QDateTime::fromString(QStringView string, QStringView format, int baseYear, QCalendar cal)
+ \overload
+ \since 6.7
+*/
#endif // datestring
/*****************************************************************************
diff --git a/src/corelib/time/qdatetime.h b/src/corelib/time/qdatetime.h
index 13c6c6b8fe..ce58520475 100644
--- a/src/corelib/time/qdatetime.h
+++ b/src/corelib/time/qdatetime.h
@@ -27,6 +27,7 @@ class QDateTime;
class Q_CORE_EXPORT QDate
{
explicit constexpr QDate(qint64 julianDay) : jd(julianDay) {}
+ static constexpr int FirstTwoDigitYear = 1900; // sync with QLocale
public:
constexpr QDate() : jd(nullJd()) {}
QDate(int y, int m, int d);
@@ -127,15 +128,29 @@ public:
static QDate currentDate();
#if QT_CONFIG(datestring)
+ // No DateFormat accepts a two-digit year, so no need for baseYear:
static QDate fromString(QStringView string, Qt::DateFormat format = Qt::TextDate);
- static QDate fromString(QStringView string, QStringView format, QCalendar cal = QCalendar())
- { return fromString(string.toString(), format, cal); }
- static QDate fromString(const QString &string, QStringView format, QCalendar cal = QCalendar());
static QDate fromString(const QString &string, Qt::DateFormat format = Qt::TextDate)
{ return fromString(qToStringViewIgnoringNull(string), format); }
+
+ // Accept calendar without over-ride of base year:
+ static QDate fromString(QStringView string, QStringView format, QCalendar cal)
+ { return fromString(string.toString(), format, FirstTwoDigitYear, cal); }
+ QT_CORE_INLINE_SINCE(6, 7)
+ static QDate fromString(const QString &string, QStringView format, QCalendar cal);
+ static QDate fromString(const QString &string, const QString &format, QCalendar cal)
+ { return fromString(string, qToStringViewIgnoringNull(format), FirstTwoDigitYear, cal); }
+
+ // Overriding base year is likely more common than overriding calendar (and
+ // likely to get more so, as the legacy base drops ever further behind us).
+ static QDate fromString(QStringView string, QStringView format,
+ int baseYear = FirstTwoDigitYear, QCalendar cal = QCalendar())
+ { return fromString(string.toString(), format, baseYear, cal); }
+ static QDate fromString(const QString &string, QStringView format,
+ int baseYear = FirstTwoDigitYear, QCalendar cal = QCalendar());
static QDate fromString(const QString &string, const QString &format,
- QCalendar cal = QCalendar())
- { return fromString(string, qToStringViewIgnoringNull(format), cal); }
+ int baseYear = FirstTwoDigitYear, QCalendar cal = QCalendar())
+ { return fromString(string, qToStringViewIgnoringNull(format), baseYear, cal); }
#endif
static bool isValid(int y, int m, int d);
static bool isLeapYear(int year);
@@ -169,6 +184,7 @@ private:
qint64 jd;
friend class QDateTime;
+ friend class QDateTimeParser;
friend class QDateTimePrivate;
friend constexpr bool comparesEqual(const QDate &lhs, const QDate &rhs) noexcept
@@ -416,17 +432,32 @@ public:
static QDateTime currentDateTime();
static QDateTime currentDateTimeUtc();
#if QT_CONFIG(datestring)
+ // No DateFormat accepts a two-digit year, so no need for baseYear:
static QDateTime fromString(QStringView string, Qt::DateFormat format = Qt::TextDate);
+ static QDateTime fromString(const QString &string, Qt::DateFormat format = Qt::TextDate)
+ { return fromString(qToStringViewIgnoringNull(string), format); }
+
+ // Accept calendar without over-ride of base year:
+ static QDateTime fromString(QStringView string, QStringView format, QCalendar cal)
+ { return fromString(string.toString(), format, QDate::FirstTwoDigitYear, cal); }
+ QT_CORE_INLINE_SINCE(6, 7)
+ static QDateTime fromString(const QString &string, QStringView format, QCalendar cal);
+ static QDateTime fromString(const QString &string, const QString &format, QCalendar cal)
+ { return fromString(string, qToStringViewIgnoringNull(format), QDate::FirstTwoDigitYear, cal); }
+
+ // Overriding base year is likely more common than overriding calendar (and
+ // likely to get more so, as the legacy base drops ever further behind us).
static QDateTime fromString(QStringView string, QStringView format,
+ int baseYear = QDate::FirstTwoDigitYear,
QCalendar cal = QCalendar())
- { return fromString(string.toString(), format, cal); }
+ { return fromString(string.toString(), format, baseYear, cal); }
static QDateTime fromString(const QString &string, QStringView format,
+ int baseYear = QDate::FirstTwoDigitYear,
QCalendar cal = QCalendar());
- static QDateTime fromString(const QString &string, Qt::DateFormat format = Qt::TextDate)
- { return fromString(qToStringViewIgnoringNull(string), format); }
static QDateTime fromString(const QString &string, const QString &format,
+ int baseYear = QDate::FirstTwoDigitYear,
QCalendar cal = QCalendar())
- { return fromString(string, qToStringViewIgnoringNull(format), cal); }
+ { return fromString(string, qToStringViewIgnoringNull(format), baseYear, cal); }
#endif
#if QT_DEPRECATED_SINCE(6, 9)
@@ -592,6 +623,18 @@ Q_CORE_EXPORT size_t qHash(const QDateTime &key, size_t seed = 0);
Q_CORE_EXPORT size_t qHash(QDate key, size_t seed = 0) noexcept;
Q_CORE_EXPORT size_t qHash(QTime key, size_t seed = 0) noexcept;
+#if QT_CORE_INLINE_IMPL_SINCE(6, 7)
+QDate QDate::fromString(const QString &string, QStringView format, QCalendar cal)
+{
+ return fromString(string, format, FirstTwoDigitYear, cal);
+}
+
+QDateTime QDateTime::fromString(const QString &string, QStringView format, QCalendar cal)
+{
+ return fromString(string, format, QDate::FirstTwoDigitYear, cal);
+}
+#endif
+
QT_END_NAMESPACE
#endif // QDATETIME_H
diff --git a/src/corelib/time/qdatetimeparser.cpp b/src/corelib/time/qdatetimeparser.cpp
index 35448a92c2..6837caaa8a 100644
--- a/src/corelib/time/qdatetimeparser.cpp
+++ b/src/corelib/time/qdatetimeparser.cpp
@@ -993,6 +993,17 @@ static int weekDayWithinMonth(QCalendar calendar, int year, int month, int day,
}
/*!
+ \internal
+ Returns whichever of baseYear through baseYear + 99 has its % 100 == y2d.
+*/
+static int yearInCenturyFrom(int y2d, int baseYear)
+{
+ Q_ASSERT(0 <= y2d && y2d < 100);
+ const int year = baseYear - baseYear % 100 + y2d;
+ return year < baseYear ? year + 100 : year;
+}
+
+/*!
\internal
Returns a date consistent with the given data on parts specified by known,
@@ -1000,7 +1011,7 @@ static int weekDayWithinMonth(QCalendar calendar, int year, int month, int day,
when on valid date is consistent with the data.
*/
-static QDate actualDate(QDateTimeParser::Sections known, const QCalendar &calendar,
+static QDate actualDate(QDateTimeParser::Sections known, const QCalendar &calendar, int baseYear,
int year, int year2digits, int month, int day, int dayofweek)
{
QDate actual(year, month, day, calendar);
@@ -1014,7 +1025,7 @@ static QDate actualDate(QDateTimeParser::Sections known, const QCalendar &calend
if (year % 100 != year2digits) {
if (known & QDateTimeParser::YearSection2Digits) {
// Over-ride year, even if specified:
- year += year2digits - year % 100;
+ year = yearInCenturyFrom(year2digits, baseYear);
known &= ~QDateTimeParser::YearSection;
} else {
year2digits = year % 100;
@@ -1260,8 +1271,8 @@ QDateTimeParser::scanString(const QDateTime &defaultValue, bool fixup) const
int zoneOffset; // Needed to serve as *current when setting zone
const SectionNode sn = sectionNodes.at(index);
const QDateTime usedDateTime = [&] {
- const QDate date = actualDate(isSet, calendar, year, year2digits,
- month, day, dayofweek);
+ const QDate date = actualDate(isSet, calendar, defaultCenturyStart,
+ year, year2digits, month, day, dayofweek);
const QTime time = actualTime(isSet, hour, hour12, ampm, minute, second, msec);
return QDateTime(date, time, timeZone);
}();
@@ -1358,15 +1369,12 @@ QDateTimeParser::scanString(const QDateTime &defaultValue, bool fixup) const
if (parserType != QMetaType::QTime) {
if (year % 100 != year2digits && (isSet & YearSection2Digits)) {
if (!(isSet & YearSection)) {
- year = (year / 100) * 100;
- year += year2digits;
+ year = yearInCenturyFrom(year2digits, defaultCenturyStart);
} else {
conflicts = true;
const SectionNode &sn = sectionNode(currentSectionIndex);
- if (sn.type == YearSection2Digits) {
- year = (year / 100) * 100;
- year += year2digits;
- }
+ if (sn.type == YearSection2Digits)
+ year = yearInCenturyFrom(year2digits, defaultCenturyStart);
}
}
@@ -2231,7 +2239,7 @@ QString QDateTimeParser::stateName(State s) const
*/
QDateTime QDateTimeParser::baseDate(const QTimeZone &zone) const
{
- QDateTime when = QDate(1900, 1, 1).startOfDay(zone);
+ QDateTime when = QDate(defaultCenturyStart, 1, 1).startOfDay(zone);
if (const QDateTime start = getMinimum(); when < start)
return start;
if (const QDateTime end = getMaximum(); when > end)
@@ -2240,8 +2248,9 @@ QDateTime QDateTimeParser::baseDate(const QTimeZone &zone) const
}
// Only called when we want only one of date or time; use UTC to avoid bogus DST issues.
-bool QDateTimeParser::fromString(const QString &t, QDate *date, QTime *time) const
+bool QDateTimeParser::fromString(const QString &t, QDate *date, QTime *time, int baseYear) const
{
+ defaultCenturyStart = baseYear;
const StateNode tmp = parse(t, -1, baseDate(QTimeZone::UTC), false);
if (tmp.state != Acceptable || tmp.conflicts)
return false;
@@ -2265,8 +2274,9 @@ bool QDateTimeParser::fromString(const QString &t, QDate *date, QTime *time) con
}
// Only called when we want both date and time; default to local time.
-bool QDateTimeParser::fromString(const QString &t, QDateTime *datetime) const
+bool QDateTimeParser::fromString(const QString &t, QDateTime *datetime, int baseYear) const
{
+ defaultCenturyStart = baseYear;
const StateNode tmp = parse(t, -1, baseDate(QTimeZone::LocalTime), false);
if (datetime)
*datetime = tmp.value;
diff --git a/src/corelib/time/qdatetimeparser_p.h b/src/corelib/time/qdatetimeparser_p.h
index 55bd517865..53d48c1009 100644
--- a/src/corelib/time/qdatetimeparser_p.h
+++ b/src/corelib/time/qdatetimeparser_p.h
@@ -136,8 +136,9 @@ public:
StateNode parse(const QString &input, int position,
const QDateTime &defaultValue, bool fixup) const;
- bool fromString(const QString &text, QDate *date, QTime *time) const;
- bool fromString(const QString &text, QDateTime* datetime) const;
+ bool fromString(const QString &text, QDate *date, QTime *time,
+ int baseYear = QDate::FirstTwoDigitYear) const;
+ bool fromString(const QString &text, QDateTime *datetime, int baseYear) const;
bool parseFormat(QStringView format);
enum FieldInfoFlag {
@@ -235,6 +236,7 @@ protected: // for the benefit of QDateTimeEditPrivate
virtual QLocale locale() const { return defaultLocale; }
mutable int currentSectionIndex = int(NoSectionIndex);
+ mutable int defaultCenturyStart = QDate::FirstTwoDigitYear;
Sections display;
/*
This stores the most recently selected day.
diff --git a/tests/auto/corelib/time/qdate/tst_qdate.cpp b/tests/auto/corelib/time/qdate/tst_qdate.cpp
index ce135fceb9..42fbdba3a9 100644
--- a/tests/auto/corelib/time/qdate/tst_qdate.cpp
+++ b/tests/auto/corelib/time/qdate/tst_qdate.cpp
@@ -1329,6 +1329,7 @@ void tst_QDate::fromStringFormat_data()
QTest::newRow("mistext") << u"ball"_s << u"balle"_s << 1900 << invalidDate();
QTest::newRow("text") << u"balleh"_s << u"balleh"_s << 1900 << defDate();
QTest::newRow("yearless:19") << u"10.01.1"_s << u"M.dd.d"_s << 1900 << QDate(1900, 10, 1);
+ QTest::newRow("yearless:20") << u"10.01.1"_s << u"M.dd.d"_s << 2000 << QDate(2000, 10, 1);
QTest::newRow("neg-month") << u"-1.01.1"_s << u"M.dd.d"_s << 1900 << invalidDate();
QTest::newRow("greedy-break") << u"11010"_s << u"dMMyy"_s << 1900 << invalidDate();
QTest::newRow("neg-day") << u"-2"_s << u"d"_s << 1900 << invalidDate();
@@ -1343,6 +1344,7 @@ void tst_QDate::fromStringFormat_data()
QTest::newRow("year-match-2001") << u"2001:01"_s << u"yyyy:yy"_s << 1900 << QDate(2001, 1, 1);
QTest::newRow("year-match-1999") << u"99"_s << u"yy"_s << 1900 << QDate(1999, 1, 1);
QTest::newRow("just-yy-1901") << u"01"_s << u"yy"_s << 1900 << QDate(1901, 1, 1);
+ QTest::newRow("just-yy-2001") << u"01"_s << u"yy"_s << 1970 << QDate(2001, 1, 1);
QTest::newRow("Monday") << u"Monday"_s << u"dddd"_s << 1900 << QDate(1900, 1, 1);
QTest::newRow("Tuesday") << u"Tuesday"_s << u"dddd"_s << 1900 << QDate(1900, 1, 2);
@@ -1366,16 +1368,22 @@ void tst_QDate::fromStringFormat_data()
<< u"21052006"_s << u"ddMMyyyy"_s << 1900 << QDate(2006, 5, 21);
QTest::newRow("21May06:19")
<< u"210506"_s << u"ddMMyy"_s << 1900 << QDate(1906, 5, 21);
+ QTest::newRow("21May06:20")
+ << u"210506"_s << u"ddMMyy"_s << 1970 << QDate(2006, 5, 21);
QTest::newRow("21/May/2006")
<< u"21/5/2006"_s << u"d/M/yyyy"_s << 1900 << QDate(2006, 5, 21);
QTest::newRow("21/5/06")
<< u"21/5/06"_s << u"d/M/yy"_s << 1900 << QDate(1906, 5, 21);
QTest::newRow("21/5/06:19")
<< u"21/5/06"_s << u"d/M/yy"_s << 1900 << QDate(1906, 5, 21);
+ QTest::newRow("21/5/06:20")
+ << u"21/5/06"_s << u"d/M/yy"_s << 1910 << QDate(2006, 5, 21);
QTest::newRow("2006May21")
<< u"20060521"_s << u"yyyyMMdd"_s << 1900 << QDate(2006, 5, 21);
QTest::newRow("06May21:19")
<< u"060521"_s << u"yyMMdd"_s << 1900 << QDate(1906, 5, 21);
+ QTest::newRow("06May21:20")
+ << u"060521"_s << u"yyMMdd"_s << 1907 << QDate(2006, 5, 21);
QTest::newRow("lateMarch")
<< u"9999-03-06"_s << u"yyyy-MM-dd"_s << 1900 << QDate(9999, 3, 6);
QTest::newRow("late")
@@ -1480,9 +1488,10 @@ void tst_QDate::fromStringFormat()
{
QFETCH(QString, string);
QFETCH(QString, format);
+ QFETCH(int, baseYear);
QFETCH(QDate, expected);
- QDate dt = QDate::fromString(string, format);
+ QDate dt = QDate::fromString(string, format, baseYear);
QEXPECT_FAIL("quotes-empty", "QTBUG-110669: doubled single-quotes in format mishandled",
Continue);
QCOMPARE(dt, expected);
diff --git a/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp b/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp
index c9348a2725..7c21d413ff 100644
--- a/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp
+++ b/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp
@@ -3106,14 +3106,18 @@ void tst_QDateTime::fromStringStringFormat_data()
// Indian/Cocos had a transition at the start of 1900, so its Jan 1st starts
// at 00:02:20 on that day; this leads to perverse results. QTBUG-77948.
if (const QDate defDate(1900, 1, 1); defDate.startOfDay().time() == QTime(0, 0)) {
- QTest::newRow("dMyy-only")
+ QTest::newRow("dMyy-only:19")
<< u"101010"_s << u"dMyy"_s << 1900 << QDate(1910, 10, 10).startOfDay();
+ QTest::newRow("dMyy-only:20")
+ << u"101010"_s << u"dMyy"_s << 1911 << QDate(2010, 10, 10).startOfDay();
QTest::newRow("secs-repeat-valid")
<< u"1010"_s << u"sss"_s << 1900 << QDateTime(defDate, QTime(0, 0, 10));
QTest::newRow("pm-only")
<< u"pm"_s << u"ap"_s << 1900 << QDateTime(defDate, QTime(12, 0));
- QTest::newRow("date-only")
+ QTest::newRow("date-only:19")
<< u"10 Oct 10"_s << u"dd MMM yy"_s << 1900 << QDate(1910, 10, 10).startOfDay();
+ QTest::newRow("date-only:20")
+ << u"10 Oct 10"_s << u"dd MMM yy"_s << 1950 << QDate(2010, 10, 10).startOfDay();
QTest::newRow("dow-date-only")
<< u"Fri December 3 2004"_s << u"ddd MMMM d yyyy"_s << 1900
<< QDate(2004, 12, 3).startOfDay();
@@ -3316,10 +3320,17 @@ void tst_QDateTime::fromStringStringFormat_data()
<< QDateTime(QDate(2005, 6, 28), QTime(7, 57, 30, 1), UTC);
// Two tests derived from malformed ASN.1 strings (QTBUG-84349):
- QTest::newRow("ASN.1:UTC")
+ QTest::newRow("curft+ASN.1:UTC")
<< u"22+221102233Z"_s << u"yyMMddHHmmsst"_s << 1900 << QDateTime();
- QTest::newRow("ASN.1:Generalized")
+ QTest::newRow("curft+ASN.1:Generalized")
<< u"9922+221102233Z"_s << u"yyyyMMddHHmmsst"_s << 1900 << QDateTime();
+ // Verify baseYear needed by plain ASN.1 works:
+ QTest::newRow("ASN.1:UTC-start")
+ << u"500101000000Z"_s << u"yyMMddHHmmsst"_s << 1950
+ << QDate(1950, 1, 1).startOfDay(QTimeZone::UTC);
+ QTest::newRow("ASN.1:UTC-end")
+ << u"491231235959Z"_s << u"yyMMddHHmmsst"_s << 1950
+ << QDate(2049, 12, 31).endOfDay(QTimeZone::UTC).addMSecs(-999);
// fuzzer test
QTest::newRow("integer overflow found by fuzzer")
@@ -3342,9 +3353,10 @@ void tst_QDateTime::fromStringStringFormat()
{
QFETCH(QString, string);
QFETCH(QString, format);
+ QFETCH(int, baseYear);
QFETCH(QDateTime, expected);
- QDateTime dt = QDateTime::fromString(string, format);
+ QDateTime dt = QDateTime::fromString(string, format, baseYear);
QCOMPARE(dt, expected);
if (expected.isValid()) {
@@ -3415,9 +3427,14 @@ void tst_QDateTime::fromStringStringFormat_localTimeZone_data()
// (Because QDTP tries to use local time until it reads the final zone
// field, constructing a new QDT after reading each field, hence
// transiently wanting 1921-05-01 00:00:00 before reading the dd field.)
- QTest::newRow("Helsinki-joins-EET")
+ QTest::newRow("Helsinki-joins-EET:19")
<< "Europe/Helsinki"_ba << u"210506000000Z"_s << u"yyMMddHHmmsst"_s << 1900
<< QDateTime(QDate(1921, 5, 6), QTime(0, 0), UTC);
+ // Strictly, ASN.1 wants us to parse that with a different baseYear, so
+ // check that, too, but tweak to match the 1921 transition's mid-point:
+ QTest::newRow("Helsinki-joins-EET:20")
+ << "Europe/Helsinki"_ba << u"210501001006Z"_s << u"yyMMddHHmmsst"_s << 1950
+ << QDateTime(QDate(2021, 5, 1), QTime(0, 10, 6), UTC);
}
if (lacksRows)
QSKIP("Testcases all use zones unsupported on this platform");