Improve week-day-based resolution of two-digit year numbers

Where a date format only gives the last two digits of the year, along
with month and day of month, the day of the week can help settle the
question of which century to use. The date-time parser has used this
for some time, rather crudely, but only considered centuries adjacent
to the default (1900 through 1999).

Refine that by using QCalendar's new matchCenturyToWeekday(). Simplify
the account of parsing now that client code doesn't have to do this
for itself.

Task-number: QTBUG-46843
Change-Id: Id054cc87ec867a2498145932d721c3368210cba0
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Edward Welbourne 2023-11-07 18:34:50 +01:00
parent 4799516de9
commit d2f15d65c7
2 changed files with 14 additions and 30 deletions

View File

@ -1731,14 +1731,10 @@ QDate QDate::fromString(QStringView string, Qt::DateFormat format)
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. When the
format also includes month, day (of month) and day-of-week, these suffice to
imply the century. Parsing all but the day of week and then using
QCalendar::matchCenturyToWeekday() to combine that with the day of the week
could disambiguate such a date, but runs the risk of turning a user error -
that would otherwise be recognized by the invalid result of parsing - into a
valid result in another century, that wasn't the user's intent. At present,
the date parser only considers the century indicated by \a baseYear and the
centuries immediately after and before it, to limit the scope for such
mistakes. See \l {Date ambiguities} for further details,
imply the century. In such a case, a matching date is selected in the
nearest century to the one indicated by \a baseYear, prefering later over
earlier. See \l QCalendar::matchCenturyToWeekday() and \l {Date ambiguities}
for further details,
The following examples demonstrate the default values:
@ -1774,15 +1770,12 @@ QDate QDate::fromString(QStringView string, Qt::DateFormat format)
Including a day of the week in the format can also resolve the century of a
date specified using only the last two digits of its year. Unfortunately,
when combined with a date in which the user (or other source of data) has
mixed up two of the fields, this resolution could lead to finding a date
which does match the format's reading but isn't the one intended by its
author. This would be in a different century, which would in many cases at
least make it possible to recognize there is a problem with the data. At
present, date parsing considers the centuries after and before the one
indicated by \a baseYear, which may fall foul of this problem. See the
discussion above about using QCalendar::matchCenturyToWeekday() to extend
that to a wider range of centuries, if that can safely be applied in your
use-case.
mixed up two of the fields, this resolution can lead to finding a date which
does match the format's reading but isn't the one intended by its author.
Likewise, if the user simply gets the day of the week wrong, in an otherwise
correct date, this can lead a date in a different century. In each case,
finding a date in a different century can turn a wrongly-input date into a
wildly different one.
The best way to avoid date ambiguities is to use four-digit years and months
specified by name (whether full or abbreviated), ideally collected via user

View File

@ -1120,20 +1120,11 @@ static QDate actualDate(QDateTimeParser::Sections known, const QCalendar &calend
if ((known & QDateTimeParser::YearSection) == 0) {
if (known & QDateTimeParser::YearSection2Digits) {
/*
Two-digit year and month are specified; choice of century can only
fix this if diff is in one of {1, 2, 5} or {2, 4, 6}; but not if
diff is in the other. It's also only reasonable to consider
adjacent century, e.g. if year thinks it's 2012 and two-digit year
is '97, it makes sense to consider 1997. If either adjacent
century does work, the other won't.
*/
actual = QDate(year + 100, month, day, calendar);
if (calendar.dayOfWeek(actual) == dayofweek)
return actual;
actual = QDate(year - 100, month, day, calendar);
if (calendar.dayOfWeek(actual) == dayofweek)
actual = calendar.matchCenturyToWeekday({year, month, day}, dayofweek);
if (actual.isValid()) {
Q_ASSERT(calendar.dayOfWeek(actual) == dayofweek);
return actual;
}
} else {
// Offset by 7 is usually enough, but rare cases may need more:
for (int y = 1; y < 12; y++) {