Fix QTimeZone::offsetData() for the case without transitions

A zone without transitions, such as any UTC-based one, would
previously return invalid data for the offset data at a given
time. The method was documented to be "the equivalent of calling
offsetFromUtc(), abbreviation(), etc" but these methods do return
sensible data for a zone with no transitions. Furthermore, the backend
data() method on which it depends is implemented by all backends,
including the UTC one, with no transitions.

Fix offsetData() to also return data when no transitions are
available. Improve docs.

Adapt the checkOffset() test to test offsetData() as well as the
various functions to get parts of it. In the process, change that test
to use a QTimeZone row instead of its name as a QByteArray, so that we
can also have rows for lightweight time representations.

Change-Id: I241ecf02a26a228cca972bca5e2db687fe41feb4
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
Reviewed-by: Ivan Solovev <ivan.solovev@qt.io>
This commit is contained in:
Edward Welbourne 2023-04-27 14:39:50 +02:00
parent cded6afa35
commit 9f1252da28
2 changed files with 32 additions and 15 deletions

View File

@ -1141,9 +1141,12 @@ bool QTimeZone::isDaylightTime(const QDateTime &atDateTime) const
}
/*!
Returns the effective offset details at the given \a forDateTime. This is
the equivalent of calling offsetFromUtc(), abbreviation(), etc individually but is
more efficient.
Returns the effective offset details at the given \a forDateTime.
This is the equivalent of calling abbreviation() and all three offset
functions individually but is more efficient. If this data is not available
for the given datetime, an invalid OffsetData will be returned with an
invalid QDateTime as its \c atUtc.
This method is only available when feature \c timezone is enabled.
@ -1163,9 +1166,9 @@ QTimeZone::OffsetData QTimeZone::offsetData(const QDateTime &forDateTime) const
Q_UNREACHABLE();
break;
}
} else if (hasTransitions()) {
return QTimeZonePrivate::toOffsetData(d->data(forDateTime.toMSecsSinceEpoch()));
}
if (isValid())
return QTimeZonePrivate::toOffsetData(d->data(forDateTime.toMSecsSinceEpoch()));
return QTimeZonePrivate::invalidOffsetData();
}
@ -1206,7 +1209,7 @@ bool QTimeZone::hasTransitions() const
Transition after it.
If there is no transition after the given \a afterDateTime then an invalid
OffsetData will be returned with an invalid QDateTime.
OffsetData will be returned with an invalid QDateTime as its \c atUtc.
The given \a afterDateTime is exclusive.
@ -1241,7 +1244,7 @@ QTimeZone::OffsetData QTimeZone::nextTransition(const QDateTime &afterDateTime)
Transition before it.
If there is no transition before the given \a beforeDateTime then an invalid
OffsetData will be returned with an invalid QDateTime.
OffsetData will be returned with an invalid QDateTime as its \c atUtc.
The given \a beforeDateTime is exclusive.

View File

@ -777,17 +777,29 @@ void tst_QTimeZone::transitionEachZone()
void tst_QTimeZone::checkOffset_data()
{
QTest::addColumn<QByteArray>("zoneName");
QTest::addColumn<QTimeZone>("zone");
QTest::addColumn<QDateTime>("when");
QTest::addColumn<int>("netOffset");
QTest::addColumn<int>("stdOffset");
QTest::addColumn<int>("dstOffset");
const QTimeZone UTC = QTimeZone::UTC;
QTest::addRow("UTC")
<< UTC << QDate(1970, 1, 1).startOfDay(UTC) << 0 << 0 << 0;
const auto east = QTimeZone::fromSecondsAheadOfUtc(28'800); // 8 hours
QTest::addRow("UTC+8")
<< east << QDate(2000, 2, 29).startOfDay(east) << 28'800 << 28'800 << 0;
const auto west = QTimeZone::fromDurationAheadOfUtc(std::chrono::hours{-8});
QTest::addRow("UTC-8")
<< west << QDate(2100, 2, 28).startOfDay(west) << -28'800 << -28'800 << 0;
struct {
const char *zone, *nick;
int year, month, day, hour, min, sec;
int std, dst;
} table[] = {
// Exercise the UTC-backend:
{ "UTC", "epoch", 1970, 1, 1, 0, 0, 0, 0, 0 },
// Zone with no transitions (QTBUG-74614, QTBUG-74666, when TZ backend uses minimal data)
{ "Etc/UTC", "epoch", 1970, 1, 1, 0, 0, 0, 0, 0 },
{ "Etc/UTC", "pre_int32", 1901, 12, 13, 20, 45, 51, 0, 0 },
@ -799,38 +811,40 @@ void tst_QTimeZone::checkOffset_data()
{ "Europe/Kiev", "summer", 2017, 10, 27, 12, 0, 0, 2 * 3600, 3600 },
{ "Europe/Kiev", "winter", 2017, 10, 29, 12, 0, 0, 2 * 3600, 0 }
};
bool lacksRows = true;
for (const auto &entry : table) {
QTimeZone zone(entry.zone);
if (zone.isValid()) {
QTest::addRow("%s@%s", entry.zone, entry.nick)
<< QByteArray(entry.zone)
<< zone
<< QDateTime(QDate(entry.year, entry.month, entry.day),
QTime(entry.hour, entry.min, entry.sec), zone)
<< entry.dst + entry.std << entry.std << entry.dst;
lacksRows = false;
} else {
qWarning("Skipping %s@%s test as zone is invalid", entry.zone, entry.nick);
}
}
if (lacksRows)
QSKIP("No valid zone info found, skipping test");
}
void tst_QTimeZone::checkOffset()
{
QFETCH(QByteArray, zoneName);
QFETCH(QTimeZone, zone);
QFETCH(QDateTime, when);
QFETCH(int, netOffset);
QFETCH(int, stdOffset);
QFETCH(int, dstOffset);
QTimeZone zone(zoneName);
QVERIFY(zone.isValid()); // It was when _data() added the row !
QCOMPARE(zone.offsetFromUtc(when), netOffset);
QCOMPARE(zone.standardTimeOffset(when), stdOffset);
QCOMPARE(zone.daylightTimeOffset(when), dstOffset);
QCOMPARE(zone.isDaylightTime(when), dstOffset != 0);
// Also test offsetData(), which gets all this data in one go:
const auto data = zone.offsetData(when);
QCOMPARE(data.atUtc, when);
QCOMPARE(data.offsetFromUtc, netOffset);
QCOMPARE(data.standardTimeOffset, stdOffset);
QCOMPARE(data.daylightTimeOffset, dstOffset);
}
void tst_QTimeZone::availableTimeZoneIds()