Expand range of allowed UTC offsets to 16 hours

It turns out that Alaska and The Philippines had historical offsets
exceeding 15 hours, prior to day-transitions to bring their dates in
sync with their respective sides of the international date line.

Change-Id: I48fdf3aa6d8c0bacb368d08316733a10ee11a281
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
bb10
Edward Welbourne 2023-03-24 12:31:20 +01:00
parent 455abef6c6
commit cb54fa634a
4 changed files with 47 additions and 31 deletions

View File

@ -177,7 +177,7 @@ Q_GLOBAL_STATIC(QTimeZoneSingleton, global_tz);
A default UTC time zone backend is provided which is always available when
feature \c timezone is enabled. This provides a set of generic Offset From
UTC time zones in the range UTC-14:00 to UTC+14:00. These time zones can be
UTC time zones in the range UTC-16:00 to UTC+16:00. These time zones can be
created using either the standard ISO format names, such as "UTC+00:00", as
listed by availableTimeZoneIds(), or using a name of similar form in
combination with the number of offset seconds.
@ -258,13 +258,27 @@ Q_GLOBAL_STATIC(QTimeZoneSingleton, global_tz);
This enumeration provides constants bounding the range of plausible timezone
offsets from UTC, measured in seconds.
Sane UTC offsets range from -14 to +14 hours.
No known zone has offset > 12 hrs West of Greenwich (Baker Island, USA).
No known zone has offset > 14 hrs East of Greenwich (Kiritimati, Christmas Island, Kiribati).
Sane modern zones' UTC offsets range from -14 to +12 hours.
No known modern zone has offset > 12 hrs West of Greenwich (Baker Island, USA).
No known modern zone has offset > 14 hrs East of Greenwich
(Kiritimati, Christmas Island, Kiribati).
Note that there are zones whose offsets differ by more than a day.
\value MinUtcOffsetSecs -14 * 3600,
\value MaxUtcOffsetSecs +14 * 3600
Historically, before 1867, when Russia sold Alaska to America, Alaska used the
same date as Russia, so had offsets over 15 hours East of Greenwich.
Earlier still, until 1844, The Philippines (then controlled by Spain) used the
same date as Spain's American holdings, so had offsets close to 16 hours West
of Greenwich.
Each made its one-day transition to cross the international date line while
using local solar mean time, before adopting a unified time-zone, so the
offsets within their territories were variable.
As a result, The Philippines might have exceeded 16 hours as offset at the
extremities of its territory, but no modern zone's representative location was
at such an extremity.
\value MinUtcOffsetSecs -16 * 3600,
\value MaxUtcOffsetSecs +16 * 3600
*/
#if QT_CONFIG(timezone)
@ -468,7 +482,7 @@ QTimeZone::QTimeZone(const QByteArray &ianaId)
/*!
Creates a time zone instance with the given offset, \a offsetSeconds, from UTC.
The \a offsetSeconds from UTC must be in the range -14 hours to +14 hours
The \a offsetSeconds from UTC must be in the range -16 hours to +16 hours
otherwise an invalid time zone will be returned.
This constructor is only available when feature \c timezone is enabled. The
@ -495,7 +509,7 @@ QTimeZone::QTimeZone(int offsetSeconds)
The \a ianaId must not be one of the available system IDs returned by
availableTimeZoneIds(). The \a offsetSeconds from UTC must be in the range
-14 hours to +14 hours.
-16 hours to +16 hours.
If the custom time zone does not have a specific territory then set it to the
default value of QLocale::AnyTerritory.
@ -605,10 +619,10 @@ QTimeZone QTimeZone::asBackendZone() const
Returns a time representation at a fixed \a offset, in seconds, ahead of
UTC.
The \a offset from UTC must be in the range -14 hours to +14 hours otherwise an
invalid time zone will be returned. The returned QTimeZone is a lightweight
time representation, not a time zone (backed by system-supplied or standard
data).
The \a offset from UTC must be in the range -16 hours to +16 hours otherwise
an invalid time zone will be returned. The returned QTimeZone is a
lightweight time representation, not a time zone (backed by system-supplied
or standard data).
If the offset is 0, the \l timeSpec() of the returned instance will be
Qt::UTC. Otherwise, if \a offset is valid, timeSpec() is

View File

@ -82,12 +82,14 @@ class Q_CORE_EXPORT QTimeZone
QTimeZone(ShortData sd) : d(sd) {}
public:
// Sane UTC offsets range from -14 to +14 hours:
// Sane UTC offsets range from -16 to +16 hours:
enum {
// No known zone > 12 hrs West of Greenwich (Baker Island, USA)
MinUtcOffsetSecs = -14 * 3600,
// No known zone > 14 hrs East of Greenwich (Kiritimati, Christmas Island, Kiribati)
MaxUtcOffsetSecs = +14 * 3600
// No known modern zone > 12 hrs West of Greenwich.
// Until 1844, Asia/Manila (in The Philippines) was at 15:56 West.
MinUtcOffsetSecs = -16 * 3600,
// No known modern zone > 14 hrs East of Greenwich.
// Until 1867, America/Metlakatla (in Alaska) was at 15:13:42 East.
MaxUtcOffsetSecs = +16 * 3600
};
enum Initialization { LocalTime, UTC };

View File

@ -182,28 +182,28 @@ QTimeZonePrivate::Data QTimeZonePrivate::dataForLocalTime(qint64 forLocalMSecs,
/*
We need a UTC time at which to ask for the offset, in order to be able to
add that offset to forLocalMSecs, to get the UTC time we
need. Fortunately, no time-zone offset is more than 14 hours; and DST
transitions happen (much) more than thirty-two hours apart. So sampling
offset sixteen hours each side gives us information we can be sure
add that offset to forLocalMSecs, to get the UTC time we need.
Fortunately, all time-zone offsets have been less than 17 hours; and DST
transitions happen (much) more than thirty-four hours apart. So sampling
offset seventeen hours each side gives us information we can be sure
brackets the correct time and at most one DST transition.
*/
std::integral_constant<qint64, 16 * 3600 * 1000> sixteenHoursInMSecs;
static_assert(-sixteenHoursInMSecs / 1000 < QTimeZone::MinUtcOffsetSecs
&& sixteenHoursInMSecs / 1000 > QTimeZone::MaxUtcOffsetSecs);
std::integral_constant<qint64, 17 * 3600 * 1000> seventeenHoursInMSecs;
static_assert(-seventeenHoursInMSecs / 1000 < QTimeZone::MinUtcOffsetSecs
&& seventeenHoursInMSecs / 1000 > QTimeZone::MaxUtcOffsetSecs);
qint64 millis;
// Clip the bracketing times to the bounds of the supported range. Exclude
// minMSecs(), because at least one backend (Windows) uses it for a
// start-of-time fake transition, that we want previousTransition() to find.
const qint64 recent =
sub_overflow(forLocalMSecs, sixteenHoursInMSecs, &millis) || millis <= minMSecs()
sub_overflow(forLocalMSecs, seventeenHoursInMSecs, &millis) || millis <= minMSecs()
? minMSecs() + 1 : millis; // Necessarily <= forLocalMSecs + 2.
// (Given that minMSecs() is std::numeric_limits<qint64>::min() + 1.)
const qint64 imminent =
add_overflow(forLocalMSecs, sixteenHoursInMSecs, &millis)
add_overflow(forLocalMSecs, seventeenHoursInMSecs, &millis)
? maxMSecs() : millis; // Necessarily >= forLocalMSecs
// At most one of those was clipped to its boundary value:
Q_ASSERT(recent < imminent && sixteenHoursInMSecs < imminent - recent + 2);
Q_ASSERT(recent < imminent && seventeenHoursInMSecs < imminent - recent + 2);
/*
Offsets are Local - UTC, positive to the east of Greenwich, negative to
the west; DST offset always exceeds standard offset, when DST applies.

View File

@ -507,7 +507,7 @@ void tst_QTimeZone::asBackendZone()
void tst_QTimeZone::systemZone()
{
const QTimeZone zone = QTimeZone::systemTimeZone();
QVERIFY(zone.isValid());
QVERIFY2(zone.isValid(), "Invalid system zone setting, tests are doomed.");
QCOMPARE(zone.id(), QTimeZone::systemTimeZoneId());
QCOMPARE(zone, QTimeZone(QTimeZone::systemTimeZoneId()));
// Check it behaves the same as local-time:
@ -1162,9 +1162,9 @@ void tst_QTimeZone::utcTest()
QCOMPARE(tz.standardTimeOffset(now), 36000);
QCOMPARE(tz.daylightTimeOffset(now), 0);
// Test invalid UTC offset, must be in range -14 to +14 hours
int min = -14*60*60;
int max = 14*60*60;
// Test validity range of UTC offsets:
int min = int(QTimeZone::MinUtcOffsetSecs);
int max = int(QTimeZone::MaxUtcOffsetSecs);
QCOMPARE(QTimeZone(min - 1).isValid(), false);
QCOMPARE(QTimeZone(min).isValid(), true);
QCOMPARE(QTimeZone(min + 1).isValid(), true);