QTimeZone(qint32 offsetSeconds): use IANA ID when one is available
Principle of least surprise: prefer IANA IDs over synthesized ones. This also aligns what id() returns more nearly with what availableTimeZoneIds() reports. Amend some tests to match the new behavior, extend one test to verify id-round-tripping (also for the IANA zones) and another to verify single-digit offset IDs get zero-padded. Document the complications in how id() relates to what is passed to the constructor. (It was already complicated; the present change just aligns it better with IANA IDs, where possible.) Mention, in availableTimeZoneIds(), that (and why) it only includes IANA's offset IDs. Drive-by: fix a typo in another availableTimeZoneIds() overload's doc. [ChangeLog][QtCore][QTimeZone] When created from (only) a UTC offset, or from (only) a non-IANA UTC-offset ID, a QTimeZone instance now uses an IANA UTC-offset ID, where one is available with a matching offset. Previously it used a synthesized UTC±hh[:mm[:ss]] one which would omit trailing :00 for minutes or seconds, which the IANA ID may well include. Task-number: QTBUG-118586 Change-Id: Ifc4976f36361c830c88a8bef0e8b963fe5a2ab43 Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
parent
e45d05dfc0
commit
df73672f97
@ -452,11 +452,13 @@ QTimeZone::Data &QTimeZone::Data::operator=(QTimeZonePrivate *dptr) noexcept
|
||||
Creates a time zone instance with the requested IANA ID \a ianaId.
|
||||
|
||||
The ID must be one of the available system IDs or a valid UTC-with-offset
|
||||
ID, otherwise an invalid time zone will be returned.
|
||||
ID, otherwise an invalid time zone will be returned. For UTC-with-offset
|
||||
IDs, when they are not in fact IANA IDs, the \c{id()} of the resulting
|
||||
instance may differ from the ID passed to the constructor.
|
||||
|
||||
This constructor is only available when feature \c timezone is enabled.
|
||||
|
||||
\sa availableTimeZoneIds()
|
||||
\sa availableTimeZoneIds(), id()
|
||||
*/
|
||||
|
||||
QTimeZone::QTimeZone(const QByteArray &ianaId)
|
||||
@ -498,7 +500,7 @@ QTimeZone::QTimeZone(const QByteArray &ianaId)
|
||||
\c{QTimeZone::fromSecondsAfterUtc(offsetSeconds)}, albeit implemented as a
|
||||
time zone.
|
||||
|
||||
\sa MinUtcOffsetSecs, MaxUtcOffsetSecs
|
||||
\sa MinUtcOffsetSecs, MaxUtcOffsetSecs, id()
|
||||
*/
|
||||
|
||||
QTimeZone::QTimeZone(int offsetSeconds)
|
||||
@ -790,6 +792,28 @@ bool QTimeZone::isValid() const
|
||||
IANA IDs are used on all platforms. On Windows these are translated from
|
||||
the Windows ID into the best match IANA ID for the time zone and territory.
|
||||
|
||||
If this timezone instance was not constructed from an IANA ID, its ID is
|
||||
determined by how it was constructed. In most cases, the ID passed when
|
||||
constructing the instance is used. (The constructor for a custom zone uses
|
||||
the ID it is passed, which must not be an IANA ID.) There are two
|
||||
exceptions.
|
||||
\list
|
||||
\li Instances constructed by passing only a UTC offset in seconds have no ID
|
||||
passed when constructing.
|
||||
\li The constructor taking only an IANA ID will also accept some UTC-offset
|
||||
IDs that are not in fact IANA IDs: its handling of these is equivalent
|
||||
to passing the corresponding offset in seconds, as for the first
|
||||
exception.
|
||||
\endlist
|
||||
|
||||
In the two exceptional cases, if there is an IANA UTC-offset zone with the
|
||||
specified offset, the instance constructed uses that IANA zone's ID, even
|
||||
though this may differ from the (non-IANA) UTC-offset ID passed to the
|
||||
constructor. Otherwise, the instance uses an ID synthesized from its offset,
|
||||
with the form UTC±hh:mm:ss, omitting any trailing :00 for zero seconds or
|
||||
minutes. Again, this may differ from the UTC-offset ID passed to the
|
||||
constructor.
|
||||
|
||||
This method is only available when feature \c timezone is enabled.
|
||||
*/
|
||||
|
||||
@ -1404,6 +1428,9 @@ QTimeZone QTimeZone::utc()
|
||||
/*!
|
||||
Returns \c true if a given time zone \a ianaId is available on this system.
|
||||
|
||||
This may include some non-IANA IDs, notably UTC-offset IDs, that are not
|
||||
listed in \l availableTimeZoneIds().
|
||||
|
||||
This method is only available when feature \c timezone is enabled.
|
||||
|
||||
\sa availableTimeZoneIds()
|
||||
@ -1441,6 +1468,10 @@ static QList<QByteArray> set_union(const QList<QByteArray> &l1, const QList<QByt
|
||||
|
||||
This method is only available when feature \c timezone is enabled.
|
||||
|
||||
\note the QTimeZone constructor will also accept some UTC-offset IDs that
|
||||
are not in the list returned - it would be impractical to list all possible
|
||||
UTC-offset IDs.
|
||||
|
||||
\sa isTimeZoneIdAvailable()
|
||||
*/
|
||||
|
||||
@ -1454,7 +1485,7 @@ QList<QByteArray> QTimeZone::availableTimeZoneIds()
|
||||
Returns a list of all available IANA time zone IDs for a given \a territory.
|
||||
|
||||
As a special case, a \a territory of \l {QLocale::}{AnyTerritory} selects
|
||||
those time zones that have no kown territorial association, such as UTC. If
|
||||
those time zones that have no known territorial association, such as UTC. If
|
||||
you require a list of all time zone IDs for all territories then use the
|
||||
standard availableTimeZoneIds() method.
|
||||
|
||||
|
@ -813,11 +813,25 @@ qint64 QUtcTimeZonePrivate::offsetFromUtcString(const QByteArray &id)
|
||||
return seconds * sign;
|
||||
}
|
||||
|
||||
// Create offset from UTC
|
||||
// Create from UTC offset:
|
||||
QUtcTimeZonePrivate::QUtcTimeZonePrivate(qint32 offsetSeconds)
|
||||
{
|
||||
QString utcId = isoOffsetFormat(offsetSeconds, QTimeZone::ShortName);
|
||||
init(utcId.toUtf8(), offsetSeconds, utcId, utcId, QLocale::AnyTerritory, utcId);
|
||||
QString name;
|
||||
QByteArray id;
|
||||
// If there's an IANA ID for this offset, use it:
|
||||
const auto data = std::lower_bound(std::begin(utcDataTable), std::end(utcDataTable),
|
||||
offsetSeconds, atLowerUtcOffset);
|
||||
if (data != std::end(utcDataTable) && data->offsetFromUtc == offsetSeconds) {
|
||||
QByteArrayView ianaId = data->id();
|
||||
qsizetype cut = ianaId.indexOf(' ');
|
||||
id = (cut < 0 ? ianaId : ianaId.first(cut)).toByteArray();
|
||||
name = QString::fromUtf8(id);
|
||||
Q_ASSERT(!name.isEmpty());
|
||||
} else { // Fall back to a UTC-offset name:
|
||||
name = isoOffsetFormat(offsetSeconds, QTimeZone::ShortName);
|
||||
id = name.toUtf8();
|
||||
}
|
||||
init(id, offsetSeconds, name, name, QLocale::AnyTerritory, name);
|
||||
}
|
||||
|
||||
QUtcTimeZonePrivate::QUtcTimeZonePrivate(const QByteArray &zoneId, int offsetSeconds,
|
||||
|
@ -4197,7 +4197,7 @@ void tst_QDateTime::timeZones() const
|
||||
QCOMPARE(nzStdOffset.date(), QDate(2012, 6, 1));
|
||||
QCOMPARE(nzStdOffset.time(), QTime(12, 0));
|
||||
QVERIFY(nzStdOffset.timeZone() == nzTzOffset);
|
||||
QCOMPARE(nzStdOffset.timeZone().id(), QByteArray("UTC+12"));
|
||||
QCOMPARE(nzStdOffset.timeZone().id(), QByteArray("UTC+12:00"));
|
||||
QCOMPARE(nzStdOffset.offsetFromUtc(), 43200);
|
||||
QVERIFY(!nzStdOffset.isDaylightTime());
|
||||
QCOMPARE(nzStdOffset.toMSecsSinceEpoch(), utcStd.toMSecsSinceEpoch());
|
||||
|
@ -537,12 +537,14 @@ void tst_QTimeZone::isTimeZoneIdAvailable()
|
||||
for (const QByteArray &id : available) {
|
||||
QVERIFY2(QTimeZone::isTimeZoneIdAvailable(id), id);
|
||||
QVERIFY2(QTimeZone(id).isValid(), id);
|
||||
QCOMPARE(QTimeZone(id).id(), id);
|
||||
}
|
||||
for (qint32 offset = QTimeZone::MinUtcOffsetSecs;
|
||||
offset <= QTimeZone::MinUtcOffsetSecs; ++offset) {
|
||||
const QByteArray id = QTimeZone(offset).id();
|
||||
QVERIFY2(QTimeZone::isTimeZoneIdAvailable(id), id);
|
||||
QVERIFY2(QTimeZone(id).isValid(), id);
|
||||
QCOMPARE(QTimeZone(id).id(), id);
|
||||
}
|
||||
}
|
||||
|
||||
@ -607,7 +609,11 @@ void tst_QTimeZone::utcOffsetId_data()
|
||||
ROW("UTC-11", true, -39600);
|
||||
ROW("UTC-09", true, -32400);
|
||||
ROW("UTC-08", true, -28800);
|
||||
ROW("UTC-8", true, -28800);
|
||||
ROW("UTC-2:5", true, -7500);
|
||||
ROW("UTC-02", true, -7200);
|
||||
ROW("UTC+2", true, 7200);
|
||||
ROW("UTC+2:5", true, 7500);
|
||||
ROW("UTC+12", true, 43200);
|
||||
ROW("UTC+13", true, 46800);
|
||||
// Encountered in bug reports:
|
||||
@ -655,6 +661,19 @@ void tst_QTimeZone::utcOffsetId()
|
||||
QFETCH(int, offset);
|
||||
QCOMPARE(zone.offsetFromUtc(epoch), offset);
|
||||
QVERIFY(!zone.hasDaylightTime());
|
||||
|
||||
// zone.id() will be an IANA ID with zero minutes field if original was
|
||||
// a UTC offset by a whole number of hours. It will also zero-pad a
|
||||
// single-digit hour or minute to two digits.
|
||||
if (const qsizetype cut = id.indexOf(':'); cut >= 0) {
|
||||
if (id.size() == cut + 2) // "...:m" -> "...:0m"
|
||||
id.insert(cut + 1, '0');
|
||||
} else if (zone.id().contains(':')) {
|
||||
id += ":00";
|
||||
}
|
||||
if (id.indexOf(':') == 5) // UTC±h:mm -> UTC±0h:mm
|
||||
id.insert(4, '0');
|
||||
|
||||
QCOMPARE(zone.id(), id);
|
||||
}
|
||||
}
|
||||
@ -1175,15 +1194,22 @@ void tst_QTimeZone::utcTest()
|
||||
QCOMPARE(tzp.hasDaylightTime(), false);
|
||||
QCOMPARE(tzp.hasTransitions(), false);
|
||||
|
||||
// Test create from UTC Offset (uses minimal id, skipping minutes if 0)
|
||||
// Test create from UTC Offset:
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
QTimeZone tz(36000);
|
||||
QVERIFY(tz.isValid());
|
||||
QCOMPARE(tz.id(), QByteArray("UTC+10"));
|
||||
QCOMPARE(tz.id(), QByteArray("UTC+10:00"));
|
||||
QCOMPARE(tz.offsetFromUtc(now), 36000);
|
||||
QCOMPARE(tz.standardTimeOffset(now), 36000);
|
||||
QCOMPARE(tz.daylightTimeOffset(now), 0);
|
||||
|
||||
tz = QTimeZone(15 * 3600); // no IANA ID, so uses minimal id, skipping :00 minutes
|
||||
QVERIFY(tz.isValid());
|
||||
QCOMPARE(tz.id(), QByteArray("UTC+15"));
|
||||
QCOMPARE(tz.offsetFromUtc(now), 15 * 3600);
|
||||
QCOMPARE(tz.standardTimeOffset(now), 15 * 3600);
|
||||
QCOMPARE(tz.daylightTimeOffset(now), 0);
|
||||
|
||||
// Test validity range of UTC offsets:
|
||||
int min = QTimeZone::MinUtcOffsetSecs;
|
||||
int max = QTimeZone::MaxUtcOffsetSecs;
|
||||
|
Loading…
Reference in New Issue
Block a user