diff --git a/src/corelib/time/qtimezone.cpp b/src/corelib/time/qtimezone.cpp index 4976ea5fcb8..c593af544f0 100644 --- a/src/corelib/time/qtimezone.cpp +++ b/src/corelib/time/qtimezone.cpp @@ -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 set_union(const QList &l1, const QList 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. diff --git a/src/corelib/time/qtimezoneprivate.cpp b/src/corelib/time/qtimezoneprivate.cpp index d9a14ac36be..97f2f85e52a 100644 --- a/src/corelib/time/qtimezoneprivate.cpp +++ b/src/corelib/time/qtimezoneprivate.cpp @@ -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, diff --git a/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp b/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp index c03c7a89e9d..7ae4a8b52e4 100644 --- a/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp +++ b/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp @@ -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()); diff --git a/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp b/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp index 29643b2caca..6b0ce704568 100644 --- a/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp +++ b/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp @@ -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;