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);
|
&& (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):
|
\internal
|
||||||
#ifdef Q_OS_WIN
|
Returns a year, in the system range, with the same day-of-week pattern
|
||||||
constexpr int firstSystemTimeYear = 1970;
|
|
||||||
#else // First year fully in 32-bit time_t range:
|
Returns the number of a year, in the range supported by system time_t
|
||||||
constexpr int firstSystemTimeYear = 1902;
|
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
|
#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
|
// Convert an MSecs Since Epoch into Local Time
|
||||||
bool QDateTimePrivate::epochMSecsToLocalTime(qint64 msecs, QDate *localDate, QTime *localTime,
|
bool QDateTimePrivate::epochMSecsToLocalTime(qint64 msecs, QDate *localDate, QTime *localTime,
|
||||||
@ -2822,18 +2851,14 @@ bool QDateTimePrivate::epochMSecsToLocalTime(qint64 msecs, QDate *localDate, QTi
|
|||||||
}
|
}
|
||||||
#endif // timezone
|
#endif // timezone
|
||||||
// Kludge
|
// Kludge
|
||||||
// Use existing method to fake the conversion (this is deeply flawed
|
// Use existing method to fake the conversion.
|
||||||
// as it may apply the conversion from the wrong day number, e.g. if
|
|
||||||
// rule is last Sunday of month).
|
|
||||||
QDate utcDate;
|
QDate utcDate;
|
||||||
QTime utcTime;
|
QTime utcTime;
|
||||||
msecsToTime(msecs, &utcDate, &utcTime);
|
msecsToTime(msecs, &utcDate, &utcTime);
|
||||||
int year, month, day;
|
int year, month, day;
|
||||||
utcDate.getDate(&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)
|
QDate fakeDate(systemTimeYearMatching(year), month, day);
|
||||||
--day;
|
|
||||||
QDate fakeDate(year < 1970 ? firstSystemTimeYear : 2037, month, day);
|
|
||||||
qint64 fakeMsecs = QDateTime(fakeDate, utcTime, Qt::UTC).toMSecsSinceEpoch();
|
qint64 fakeMsecs = QDateTime(fakeDate, utcTime, Qt::UTC).toMSecsSinceEpoch();
|
||||||
bool res = qt_localtime(fakeMsecs, localDate, localTime, daylightStatus);
|
bool res = qt_localtime(fakeMsecs, localDate, localTime, daylightStatus);
|
||||||
*localDate = localDate->addDays(fakeDate.daysTo(utcDate));
|
*localDate = localDate->addDays(fakeDate.daysTo(utcDate));
|
||||||
@ -2880,19 +2905,14 @@ qint64 QDateTimePrivate::localMSecsToEpochMSecs(qint64 localMsecs,
|
|||||||
}
|
}
|
||||||
#endif // timezone
|
#endif // timezone
|
||||||
// Kludge
|
// Kludge
|
||||||
// Use existing method to fake the conversion (this is deeply flawed as it
|
// Use existing method to fake the conversion.
|
||||||
// may apply the conversion from the wrong day number, e.g. if rule is last
|
|
||||||
// Sunday of month).
|
|
||||||
QDate dt;
|
QDate dt;
|
||||||
QTime tm;
|
QTime tm;
|
||||||
msecsToTime(localMsecs, &dt, &tm);
|
msecsToTime(localMsecs, &dt, &tm);
|
||||||
int year, month, day;
|
int year, month, day;
|
||||||
dt.getDate(&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;
|
bool ok;
|
||||||
QDate fakeDate(year < 1970 ? firstSystemTimeYear : 2037, month, day);
|
QDate fakeDate(systemTimeYearMatching(year), month, day);
|
||||||
const qint64 fakeDiff = fakeDate.daysTo(dt);
|
const qint64 fakeDiff = fakeDate.daysTo(dt);
|
||||||
const qint64 utcMsecs = qt_mktime(&fakeDate, &tm, daylightStatus, abbreviation, &ok);
|
const qint64 utcMsecs = qt_mktime(&fakeDate, &tm, daylightStatus, abbreviation, &ok);
|
||||||
Q_ASSERT(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/
|
** Contact: https://www.qt.io/licensing/
|
||||||
**
|
**
|
||||||
** This file is part of the QtCore module of the Qt Toolkit.
|
** 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;
|
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
|
QCalendar::YearMonthDay QGregorianCalendar::julianDayToDate(qint64 jd) const
|
||||||
{
|
{
|
||||||
return partsFromJulian(jd);
|
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/
|
** Contact: https://www.qt.io/licensing/
|
||||||
**
|
**
|
||||||
** This file is part of the QtCore module of the Qt Toolkit.
|
** 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 bool validParts(int year, int month, int day);
|
||||||
static QCalendar::YearMonthDay partsFromJulian(qint64 jd);
|
static QCalendar::YearMonthDay partsFromJulian(qint64 jd);
|
||||||
static bool julianFromParts(int year, int month, int day, qint64 *jd);
|
static bool julianFromParts(int year, int month, int day, qint64 *jd);
|
||||||
|
// Used internally:
|
||||||
|
static int yearStartWeekDay(int year);
|
||||||
};
|
};
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
Loading…
x
Reference in New Issue
Block a user