summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorEdward Welbourne <edward.welbourne@qt.io>2021-05-04 16:20:48 +0200
committerEdward Welbourne <edward.welbourne@qt.io>2021-06-16 15:53:23 +0200
commit1f4b237dade9d0d2ed5439e3834ac22985797561 (patch)
tree0be5b2d2e06765959acddc98395ef07e8d4541bd /src
parent9661cde1615e21f5b6bbffe3e687cacba247f514 (diff)
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>
Diffstat (limited to 'src')
-rw-r--r--src/corelib/time/qdatetime.cpp60
-rw-r--r--src/corelib/time/qgregoriancalendar.cpp9
-rw-r--r--src/corelib/time/qgregoriancalendar_p.h4
3 files changed, 51 insertions, 22 deletions
diff --git a/src/corelib/time/qdatetime.cpp b/src/corelib/time/qdatetime.cpp
index c776e3bf69..3b3668ddf3 100644
--- a/src/corelib/time/qdatetime.cpp
+++ b/src/corelib/time/qdatetime.cpp
@@ -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);
diff --git a/src/corelib/time/qgregoriancalendar.cpp b/src/corelib/time/qgregoriancalendar.cpp
index f562413dd1..ca3c19b85c 100644
--- a/src/corelib/time/qgregoriancalendar.cpp
+++ b/src/corelib/time/qgregoriancalendar.cpp
@@ -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);
diff --git a/src/corelib/time/qgregoriancalendar_p.h b/src/corelib/time/qgregoriancalendar_p.h
index 45d51e831d..155d861362 100644
--- a/src/corelib/time/qgregoriancalendar_p.h
+++ b/src/corelib/time/qgregoriancalendar_p.h
@@ -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