Use year with same day-of-week pattern as fallback for out-of-range
The kludge previously implemented for handling dates outside the supported time_t range, by asking for a corresponding date in a year in the range, had a comment remarking that this is broken due to the wrong day of the week and, consequently, the placement of DST transitions, e.g. those scheduled by the last Sunday of a month, happening on different dates in the year asked about and the in-range year passed to the system time_t functions. This can be handled by selecting a year in the time_t range which has the same pattern days of the week (and length of the year, i.e leap-ness). That may (particularly for leap years) need to select a year far from the end of the range (and there may be a change to the zone's rules between the selected year and the end). Change-Id: Ia353b2cc7b7d266b7abf80e37cac61544ce95c2d Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
parent
9661cde161
commit
1f4b237dad
@ -2788,13 +2788,42 @@ static inline bool millisInSystemRange(qint64 millis, qint64 slack = 0)
|
||||
&& (bounds.maxClip || millis <= bounds.max + slack);
|
||||
}
|
||||
|
||||
// First year for which system functions give useful answers, when earlier times
|
||||
// aren't handled by those functions (see millisInSystemRange):
|
||||
#ifdef Q_OS_WIN
|
||||
constexpr int firstSystemTimeYear = 1970;
|
||||
#else // First year fully in 32-bit time_t range:
|
||||
constexpr int firstSystemTimeYear = 1902;
|
||||
/*!
|
||||
\internal
|
||||
Returns a year, in the system range, with the same day-of-week pattern
|
||||
|
||||
Returns the number of a year, in the range supported by system time_t
|
||||
functions, that starts and ends on the same days of the week as \a year.
|
||||
This implies it is a leap year precisely if \a year is. If year is before
|
||||
the epoch, a year early in the supported range is used; otherwise, one late
|
||||
in that range. For a leap year, this may be as much as 26 years years from
|
||||
the range's relevant end; for normal years at most a decade from the end.
|
||||
|
||||
This ensures that any DST rules based on, e.g., the last Sunday in a
|
||||
particular month will select the same date in the returned year as they
|
||||
would if applied to \a year. Of course, the zone's rules may be different in
|
||||
\a year than in the selected year, but it's hard to do better.
|
||||
*/
|
||||
static int systemTimeYearMatching(int year)
|
||||
{
|
||||
#ifdef Q_OS_WIN // Doesn't suppor times before epoch
|
||||
static constexpr int forLeapEarly[] = { 1984, 1996, 1980, 1992, 1976, 1988, 1972 };
|
||||
static constexpr int regularEarly[] = { 1978, 1973, 1974, 1975, 1970, 1971, 1977 };
|
||||
#else // First year fully in 32-bit time_t range is 1902
|
||||
static constexpr int forLeapEarly[] = { 1928, 1912, 1924, 1908, 1916, 1904, 1920 };
|
||||
static constexpr int regularEarly[] = { 1905, 1906, 1907, 1902, 1903, 1909, 1910 };
|
||||
#endif
|
||||
static constexpr int forLeapLate[] = { 2012, 2024, 2036, 2020, 2032, 2016, 2028 };
|
||||
static constexpr int regularLate[] = { 2034, 2035, 2030, 2031, 2037, 2027, 2033 };
|
||||
const int dow = QGregorianCalendar::yearStartWeekDay(year);
|
||||
Q_ASSERT(dow == QDate(year, 1, 1).dayOfWeek());
|
||||
const int res = (QGregorianCalendar::leapTest(year)
|
||||
? (year < 1970 ? forLeapEarly : forLeapLate)
|
||||
: (year < 1970 ? regularEarly : regularLate))[dow == 7 ? 0 : dow];
|
||||
Q_ASSERT(QDate(res, 1, 1).dayOfWeek() == dow);
|
||||
Q_ASSERT(QDate(res, 12, 31).dayOfWeek() == QDate(year, 12, 31).dayOfWeek());
|
||||
return res;
|
||||
}
|
||||
|
||||
// Convert an MSecs Since Epoch into Local Time
|
||||
bool QDateTimePrivate::epochMSecsToLocalTime(qint64 msecs, QDate *localDate, QTime *localTime,
|
||||
@ -2822,18 +2851,14 @@ bool QDateTimePrivate::epochMSecsToLocalTime(qint64 msecs, QDate *localDate, QTi
|
||||
}
|
||||
#endif // timezone
|
||||
// Kludge
|
||||
// Use existing method to fake the conversion (this is deeply flawed
|
||||
// as it may apply the conversion from the wrong day number, e.g. if
|
||||
// rule is last Sunday of month).
|
||||
// Use existing method to fake the conversion.
|
||||
QDate utcDate;
|
||||
QTime utcTime;
|
||||
msecsToTime(msecs, &utcDate, &utcTime);
|
||||
int year, month, day;
|
||||
utcDate.getDate(&year, &month, &day);
|
||||
// No boundary year is a leap year, so make sure date isn't Feb 29
|
||||
if (month == 2 && day == 29)
|
||||
--day;
|
||||
QDate fakeDate(year < 1970 ? firstSystemTimeYear : 2037, month, day);
|
||||
|
||||
QDate fakeDate(systemTimeYearMatching(year), month, day);
|
||||
qint64 fakeMsecs = QDateTime(fakeDate, utcTime, Qt::UTC).toMSecsSinceEpoch();
|
||||
bool res = qt_localtime(fakeMsecs, localDate, localTime, daylightStatus);
|
||||
*localDate = localDate->addDays(fakeDate.daysTo(utcDate));
|
||||
@ -2880,19 +2905,14 @@ qint64 QDateTimePrivate::localMSecsToEpochMSecs(qint64 localMsecs,
|
||||
}
|
||||
#endif // timezone
|
||||
// Kludge
|
||||
// Use existing method to fake the conversion (this is deeply flawed as it
|
||||
// may apply the conversion from the wrong day number, e.g. if rule is last
|
||||
// Sunday of month).
|
||||
// Use existing method to fake the conversion.
|
||||
QDate dt;
|
||||
QTime tm;
|
||||
msecsToTime(localMsecs, &dt, &tm);
|
||||
int year, month, day;
|
||||
dt.getDate(&year, &month, &day);
|
||||
// No boundary year is a leap year, so make sure date isn't Feb 29
|
||||
if (month == 2 && day == 29)
|
||||
--day;
|
||||
bool ok;
|
||||
QDate fakeDate(year < 1970 ? firstSystemTimeYear : 2037, month, day);
|
||||
QDate fakeDate(systemTimeYearMatching(year), month, day);
|
||||
const qint64 fakeDiff = fakeDate.daysTo(dt);
|
||||
const qint64 utcMsecs = qt_mktime(&fakeDate, &tm, daylightStatus, abbreviation, &ok);
|
||||
Q_ASSERT(ok);
|
||||
|
@ -1,6 +1,6 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2020 The Qt Company Ltd.
|
||||
** Copyright (C) 2021 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
@ -140,6 +140,13 @@ bool QGregorianCalendar::julianFromParts(int year, int month, int day, qint64 *j
|
||||
return true;
|
||||
}
|
||||
|
||||
int QGregorianCalendar::yearStartWeekDay(int year)
|
||||
{
|
||||
// Equivalent to weekDayOfJulian(julianForParts({year, 1, 1})
|
||||
const int y = year - (year < 0 ? 800 : 801);
|
||||
return (y + qDiv(y, 4) - qDiv(y, 100) + qDiv(y, 400)) % 7 + 1;
|
||||
}
|
||||
|
||||
QCalendar::YearMonthDay QGregorianCalendar::julianDayToDate(qint64 jd) const
|
||||
{
|
||||
return partsFromJulian(jd);
|
||||
|
@ -1,6 +1,6 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2020 The Qt Company Ltd.
|
||||
** Copyright (C) 2021 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
@ -82,6 +82,8 @@ public:
|
||||
static bool validParts(int year, int month, int day);
|
||||
static QCalendar::YearMonthDay partsFromJulian(qint64 jd);
|
||||
static bool julianFromParts(int year, int month, int day, qint64 *jd);
|
||||
// Used internally:
|
||||
static int yearStartWeekDay(int year);
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
Loading…
Reference in New Issue
Block a user