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 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 \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 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 imply the century. In such a case, a matching date is selected in the
QCalendar::matchCenturyToWeekday() to combine that with the day of the week nearest century to the one indicated by \a baseYear, prefering later over
could disambiguate such a date, but runs the risk of turning a user error - earlier. See \l QCalendar::matchCenturyToWeekday() and \l {Date ambiguities}
that would otherwise be recognized by the invalid result of parsing - into a for further details,
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,
The following examples demonstrate the default values: 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 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, 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 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 mixed up two of the fields, this resolution can lead to finding a date which
which does match the format's reading but isn't the one intended by its does match the format's reading but isn't the one intended by its author.
author. This would be in a different century, which would in many cases at Likewise, if the user simply gets the day of the week wrong, in an otherwise
least make it possible to recognize there is a problem with the data. At correct date, this can lead a date in a different century. In each case,
present, date parsing considers the centuries after and before the one finding a date in a different century can turn a wrongly-input date into a
indicated by \a baseYear, which may fall foul of this problem. See the wildly different one.
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.
The best way to avoid date ambiguities is to use four-digit years and months 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 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::YearSection) == 0) {
if (known & QDateTimeParser::YearSection2Digits) { if (known & QDateTimeParser::YearSection2Digits) {
/* actual = calendar.matchCenturyToWeekday({year, month, day}, dayofweek);
Two-digit year and month are specified; choice of century can only if (actual.isValid()) {
fix this if diff is in one of {1, 2, 5} or {2, 4, 6}; but not if Q_ASSERT(calendar.dayOfWeek(actual) == dayofweek);
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)
return actual; return actual;
}
} else { } else {
// Offset by 7 is usually enough, but rare cases may need more: // Offset by 7 is usually enough, but rare cases may need more:
for (int y = 1; y < 12; y++) { for (int y = 1; y < 12; y++) {