Clarify the behavior of QDateTime around 24-hour transitions
For those that simply repeat or skip a whole calendar day, life is fairly simple. However, Alaska's 24-hour transition at 15:30 LMT Sitka (incidentally combined with a change of calendar) is a bit trickier. Also fix a typo I noticed in passing. Write tests to determine what the actual behavior is and document enough to make the actual behavior seem unsurprising once encountered, without trying to go into all the excruciating details. Naturally, MS time-zone data lacks the data on the historic transitions involved in these tests, so MS (when not using ICU's time-zone data) is excluded. It seems Cupertino believes Alaska was always in the USA, too. Change-Id: Ia638c04d2ffc3a956a70a2a85badb7bbfdbb791c Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
parent
a49ccc08c3
commit
e72a898c50
@ -3533,7 +3533,7 @@ QDateTime::Data QDateTimePrivate::create(QDate toDate, QTime toTime, const QTime
|
||||
possible. On Windows, where the system doesn't support historical timezone
|
||||
data, historical accuracy is not maintained with respect to timezone
|
||||
transitions, notably including DST. However, building Qt with the ICU
|
||||
library will equipe QTimeZone with the same timezone database as is used on
|
||||
library will equip QTimeZone with the same timezone database as is used on
|
||||
Unix.
|
||||
|
||||
\section2 Timezone transitions
|
||||
@ -3598,6 +3598,25 @@ QDateTime::Data QDateTimePrivate::create(QDate toDate, QTime toTime, const QTime
|
||||
inaccurate. Furthermore, for future dates, the local time zone's offsets and
|
||||
DST rules may change before that date comes around.
|
||||
|
||||
\section3 Whole day transitions
|
||||
|
||||
A small number of zones have skipped or repeated entire days as part of
|
||||
moving The International Date Line across themselves. For these, daysTo()
|
||||
will be unaware of the duplication or gap, simply using the difference in
|
||||
calendar date; in contrast, msecsTo() and secsTo() know the true time
|
||||
interval. Likewise, addMSecs() and addSecs() correspond directly to elapsed
|
||||
time, where addDays(), addMonths() and addYears() follow the nominal
|
||||
calendar, aside from where landing in a gap or duplication requires
|
||||
resolving an ambiguity or invalidity due to a duplication or omission.
|
||||
|
||||
\note Days "lost" during a change of calendar, such as from Julian to
|
||||
Gregorian, do not affect QDateTime. Although the two calendars describe
|
||||
dates differently, the successive days across the change are described by
|
||||
consecutive QDate instances, each one day later than the previous, as
|
||||
described by either calendar or by their toJulianDay() values. In contrast,
|
||||
a zone skipping or duplicating a day is changing its description of \e time,
|
||||
not date, for all that it does so by a whole 24 hours.
|
||||
|
||||
\section2 Offsets From UTC
|
||||
|
||||
Offsets from UTC are measured in seconds east of Greenwich. The moment
|
||||
|
@ -11,6 +11,13 @@
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
# include <qt_windows.h>
|
||||
# if !QT_CONFIG(icu)
|
||||
// The native MS back-end for time-zones lacks info about historic transitions:
|
||||
# define INADEQUATE_TZ_DATA
|
||||
# endif
|
||||
#endif
|
||||
#ifdef Q_OS_ANDROID // Also seems to lack full-day zone transitions:
|
||||
# define INADEQUATE_TZ_DATA
|
||||
#endif
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
@ -1289,7 +1296,50 @@ void tst_QDateTime::addDays()
|
||||
QCOMPARE(dt2.timeSpec(), Qt::TimeZone);
|
||||
QCOMPARE(dt2.timeZone(), cet);
|
||||
}
|
||||
#endif
|
||||
# ifndef INADEQUATE_TZ_DATA
|
||||
if (const QTimeZone lint("Pacific/Kiritimati"); lint.isValid()) {
|
||||
// Line Islands Time skipped Sat 1994-12-31:
|
||||
dt1 = QDateTime(QDate(1994, 12, 30), QTime(12, 0), lint);
|
||||
dt2 = QDateTime(QDate(1995, 1, 1), QTime(12, 0), lint);
|
||||
// Trying to step into the hole gets the other side:
|
||||
QCOMPARE(dt1.addDays(1), dt2);
|
||||
QCOMPARE(dt2.addDays(-1), dt1);
|
||||
// But the other side is in fact two days away:
|
||||
QCOMPARE(dt1.addDays(2), dt2);
|
||||
QCOMPARE(dt2.addDays(-2), dt1);
|
||||
QCOMPARE(dt1.daysTo(dt2), 2);
|
||||
}
|
||||
# ifndef Q_OS_DARWIN
|
||||
if (const QTimeZone alaska("America/Anchorage"); alaska.isValid()) {
|
||||
// On Julian date 1867, Sat Oct 7 (at 14:31 local solar mean time for
|
||||
// Anchorage, 15:30 LMT in Sitka, which hosted the transfer ceremony)
|
||||
// Russia sold Alaska to the USA, which changed the calendar to
|
||||
// Gregorian, hence the date to Fri Oct 18. Compare addSecs:Alaska-Day.
|
||||
// Friday evening and Saturday morning were repeated, with different dates.
|
||||
// Friday noon, as described by the Russians:
|
||||
dt1 = QDateTime(QDate(1867, 10, 6, QCalendar(QCalendar::System::Julian)),
|
||||
QTime(12, 0), alaska);
|
||||
// Sunday noon, as described by the Americans:
|
||||
dt2 = QDateTime(QDate(1867, 10, 20), QTime(12, 0), alaska);
|
||||
// Three elapsed days, but daysTo() and addDays only see two:
|
||||
QCOMPARE(dt1.addDays(2), dt2);
|
||||
QCOMPARE(dt2.addDays(-2), dt1);
|
||||
QCOMPARE(dt1.daysTo(dt2), 2);
|
||||
// Stepping into the duplicated day (Julian 7th, Gregorian 19th) gets
|
||||
// the nearer side, with the same nominal date (and time):
|
||||
QCOMPARE(dt1.addDays(1).date(), dt2.addDays(-1).date());
|
||||
QCOMPARE(dt1.addDays(1).time(), dt2.addDays(-1).time());
|
||||
QCOMPARE(dt1.addDays(1).daysTo(dt2.addDays(-1)), 0);
|
||||
// Yet they differ by a day:
|
||||
QCOMPARE_NE(dt1.addDays(1), dt2.addDays(-1));
|
||||
QCOMPARE(dt1.addDays(1).secsTo(dt2.addDays(-1)), 24 * 60 * 60);
|
||||
// Stepping from one duplicate one day towards the other jumps it:
|
||||
QCOMPARE(dt1, dt2.addDays(-1).addDays(-1));
|
||||
QCOMPARE(dt1.addDays(1).addDays(1), dt2);
|
||||
}
|
||||
# endif // Darwin
|
||||
# endif // inadequate zone data
|
||||
#endif // timezone
|
||||
|
||||
// Baja Mexico has a transition at the epoch, see fromStringDateFormat_data().
|
||||
if (QDateTime(QDate(1969, 12, 30), QTime(0, 0)).secsTo(
|
||||
@ -1578,6 +1628,43 @@ void tst_QDateTime::addMSecs_data()
|
||||
QTest::newRow("to-first")
|
||||
<< QDateTime::fromSecsSinceEpoch(1 - maxSeconds, UTC) << qint64(-1)
|
||||
<< QDateTime::fromSecsSinceEpoch(-maxSeconds, UTC);
|
||||
|
||||
#if QT_CONFIG(timezone)
|
||||
if (const QTimeZone cet("Europe/Oslo"); cet.isValid()) {
|
||||
QTest::newRow("CET-spring-forward")
|
||||
<< QDateTime(QDate(2023, 3, 26), QTime(1, 30), cet) << qint64(60 * 60)
|
||||
<< QDateTime(QDate(2023, 3, 26), QTime(3, 30), cet);
|
||||
QTest::newRow("CET-fall-back")
|
||||
<< QDateTime(QDate(2023, 10, 29), QTime(1, 30), cet) << qint64(3 * 60 * 60)
|
||||
<< QDateTime(QDate(2023, 10, 29), QTime(3, 30), cet);
|
||||
}
|
||||
# ifndef INADEQUATE_TZ_DATA
|
||||
const QTimeZone lint("Pacific/Kiritimati");
|
||||
if (lint.isValid()) {
|
||||
// Line Islands Time skipped Sat 1994-12-31:
|
||||
QTest::newRow("Kiritimati-day-off")
|
||||
<< QDateTime(QDate(1994, 12, 30), QTime(23, 30), lint) << qint64(60 * 60)
|
||||
<< QDateTime(QDate(1995, 1, 1), QTime(0, 30), lint);
|
||||
}
|
||||
# ifndef Q_OS_DARWIN
|
||||
if (const QTimeZone alaska("America/Anchorage"); alaska.isValid()) {
|
||||
// On Julian date 1867, Sat Oct 7 (at 14:31 local solar mean time for
|
||||
// Anchorage, 15:30 LMT in Sitka, which hosted the transfer ceremony)
|
||||
// Russia sold Alaska to the USA, which changed the calendar to
|
||||
// Gregorian, hence the date to Fri Oct 18. Contrast addDays().
|
||||
const QDate sat(1867, 10, 19);
|
||||
Q_ASSERT(sat == QDate(1867, 10, 7, QCalendar(QCalendar::System::Julian)));
|
||||
// At the start of the day, it was Sat 7th; by evening it was Fri 18th;
|
||||
// then the next day was Sat 19th.
|
||||
QTest::newRow("Alaska-Day")
|
||||
// The actual morning of the hand-over:
|
||||
<< QDateTime(sat, QTime(6, 0), alaska) << qint64(12 * 60 * 60)
|
||||
// The evening of the same day.
|
||||
<< QDateTime(sat, QTime(18, 0), alaska).addDays(-1);
|
||||
}
|
||||
# endif // Darwin
|
||||
# endif // inadequate zone data
|
||||
#endif // timezone
|
||||
}
|
||||
|
||||
void tst_QDateTime::addSecs_data()
|
||||
@ -1621,6 +1708,7 @@ void tst_QDateTime::addSecs()
|
||||
QCOMPARE(result - std::chrono::seconds(nsecs), dt);
|
||||
test3 -= std::chrono::seconds(nsecs);
|
||||
QCOMPARE(test3, dt);
|
||||
QCOMPARE(dt.secsTo(result), nsecs);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user