Fix broken data for time-zones with no transitions

While an invalid time-zone shall have no transitions, so may various
constant zones, like UTC.  The TZ data may include only the POSIX rule
for such a zone, in which case we should use it, even if there are no
transitions.

Broke out a piece of repeated code as a common method, in the process,
since I was complicating it further.

Added test for the case that revealed this; and made sure we see a
warning if any of the checkOffset() tests gets skipped because its
zone is unsupported.

Fixes: QTBUG-74614
Change-Id: Ic8e039a2a9b3f4e0f567585682a94f4b494b558d
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Edward Welbourne 2019-03-21 15:07:48 +01:00 committed by Tony Sarajärvi
parent 1119cd4ece
commit 03fadc26e7
3 changed files with 27 additions and 24 deletions

View File

@ -331,6 +331,7 @@ public:
private: private:
void init(const QByteArray &ianaId); void init(const QByteArray &ianaId);
QVector<QTimeZonePrivate::Data> getPosixTransitions(qint64 msNear) const;
Data dataForTzTransition(QTzTransitionTime tran) const; Data dataForTzTransition(QTzTransitionTime tran) const;
QVector<QTzTransitionTime> m_tranTimes; QVector<QTzTransitionTime> m_tranTimes;

View File

@ -943,19 +943,21 @@ QTimeZonePrivate::Data QTzTimeZonePrivate::dataForTzTransition(QTzTransitionTime
return data; return data;
} }
QVector<QTimeZonePrivate::Data> QTzTimeZonePrivate::getPosixTransitions(qint64 msNear) const
{
const int year = QDateTime::fromMSecsSinceEpoch(msNear, Qt::UTC).date().year();
// The Data::atMSecsSinceEpoch of the single entry if zone is constant:
qint64 atTime = m_tranTimes.isEmpty() ? msNear : m_tranTimes.last().atMSecsSinceEpoch;
return calculatePosixTransitions(m_posixRule, year - 1, year + 1, atTime);
}
QTimeZonePrivate::Data QTzTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const QTimeZonePrivate::Data QTzTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const
{ {
// If we have no rules (so probably an invalid tz), return invalid data: // If the required time is after the last transition (or there were none)
if (!m_tranTimes.size()) // and we have a POSIX rule then use it:
return invalidData(); if ((m_tranTimes.isEmpty() || m_tranTimes.last().atMSecsSinceEpoch < forMSecsSinceEpoch)
// If the required time is after the last transition and we have a POSIX rule then use it
if (m_tranTimes.last().atMSecsSinceEpoch < forMSecsSinceEpoch
&& !m_posixRule.isEmpty() && forMSecsSinceEpoch >= 0) { && !m_posixRule.isEmpty() && forMSecsSinceEpoch >= 0) {
const int year = QDateTime::fromMSecsSinceEpoch(forMSecsSinceEpoch, Qt::UTC).date().year(); QVector<QTimeZonePrivate::Data> posixTrans = getPosixTransitions(forMSecsSinceEpoch);
QVector<QTimeZonePrivate::Data> posixTrans =
calculatePosixTransitions(m_posixRule, year - 1, year + 1,
m_tranTimes.last().atMSecsSinceEpoch);
auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(), auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(),
[forMSecsSinceEpoch] (const QTimeZonePrivate::Data &at) { [forMSecsSinceEpoch] (const QTimeZonePrivate::Data &at) {
return at.atMSecsSinceEpoch <= forMSecsSinceEpoch; return at.atMSecsSinceEpoch <= forMSecsSinceEpoch;
@ -986,13 +988,11 @@ bool QTzTimeZonePrivate::hasTransitions() const
QTimeZonePrivate::Data QTzTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch) const QTimeZonePrivate::Data QTzTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch) const
{ {
// If the required time is after the last transition and we have a POSIX rule then use it // If the required time is after the last transition (or there were none)
if (m_tranTimes.size() > 0 && m_tranTimes.last().atMSecsSinceEpoch < afterMSecsSinceEpoch // and we have a POSIX rule then use it:
if ((m_tranTimes.isEmpty() || m_tranTimes.last().atMSecsSinceEpoch < afterMSecsSinceEpoch)
&& !m_posixRule.isEmpty() && afterMSecsSinceEpoch >= 0) { && !m_posixRule.isEmpty() && afterMSecsSinceEpoch >= 0) {
const int year = QDateTime::fromMSecsSinceEpoch(afterMSecsSinceEpoch, Qt::UTC).date().year(); QVector<QTimeZonePrivate::Data> posixTrans = getPosixTransitions(afterMSecsSinceEpoch);
QVector<QTimeZonePrivate::Data> posixTrans =
calculatePosixTransitions(m_posixRule, year - 1, year + 1,
m_tranTimes.last().atMSecsSinceEpoch);
auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(), auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(),
[afterMSecsSinceEpoch] (const QTimeZonePrivate::Data &at) { [afterMSecsSinceEpoch] (const QTimeZonePrivate::Data &at) {
return at.atMSecsSinceEpoch <= afterMSecsSinceEpoch; return at.atMSecsSinceEpoch <= afterMSecsSinceEpoch;
@ -1011,21 +1011,19 @@ QTimeZonePrivate::Data QTzTimeZonePrivate::nextTransition(qint64 afterMSecsSince
QTimeZonePrivate::Data QTzTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const QTimeZonePrivate::Data QTzTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const
{ {
// If the required time is after the last transition and we have a POSIX rule then use it // If the required time is after the last transition (or there were none)
if (m_tranTimes.size() > 0 && m_tranTimes.last().atMSecsSinceEpoch < beforeMSecsSinceEpoch // and we have a POSIX rule then use it:
if ((m_tranTimes.isEmpty() || m_tranTimes.last().atMSecsSinceEpoch < beforeMSecsSinceEpoch)
&& !m_posixRule.isEmpty() && beforeMSecsSinceEpoch > 0) { && !m_posixRule.isEmpty() && beforeMSecsSinceEpoch > 0) {
const int year = QDateTime::fromMSecsSinceEpoch(beforeMSecsSinceEpoch, Qt::UTC).date().year(); QVector<QTimeZonePrivate::Data> posixTrans = getPosixTransitions(beforeMSecsSinceEpoch);
QVector<QTimeZonePrivate::Data> posixTrans =
calculatePosixTransitions(m_posixRule, year - 1, year + 1,
m_tranTimes.last().atMSecsSinceEpoch);
auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(), auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(),
[beforeMSecsSinceEpoch] (const QTimeZonePrivate::Data &at) { [beforeMSecsSinceEpoch] (const QTimeZonePrivate::Data &at) {
return at.atMSecsSinceEpoch < beforeMSecsSinceEpoch; return at.atMSecsSinceEpoch < beforeMSecsSinceEpoch;
}); });
if (it > posixTrans.cbegin()) if (it > posixTrans.cbegin())
return *--it; return *--it;
// else: it fell between the last transition and the first of the POSIX rule. // It fell between the last transition (if any) and the first of the POSIX rule:
return dataForTzTransition(m_tranTimes.last()); return m_tranTimes.isEmpty() ? invalidData() : dataForTzTransition(m_tranTimes.last());
} }
// Otherwise if we can find a valid tran then use its rule // Otherwise if we can find a valid tran then use its rule

View File

@ -539,6 +539,8 @@ void tst_QTimeZone::checkOffset_data()
int year, month, day, hour, min, sec; int year, month, day, hour, min, sec;
int std, dst; int std, dst;
} table[] = { } table[] = {
// Zone with no transitions (QTBUG-74614, when TZ backend uses minimalist data)
{ "Etc/UTC", "epoch", 1970, 1, 1, 0, 0, 0, 0, 0 },
// Kiev: regression test for QTBUG-64122 (on MS): // Kiev: regression test for QTBUG-64122 (on MS):
{ "Europe/Kiev", "summer", 2017, 10, 27, 12, 0, 0, 2 * 3600, 3600 }, { "Europe/Kiev", "summer", 2017, 10, 27, 12, 0, 0, 2 * 3600, 3600 },
{ "Europe/Kiev", "winter", 2017, 10, 29, 12, 0, 0, 2 * 3600, 0 } { "Europe/Kiev", "winter", 2017, 10, 29, 12, 0, 0, 2 * 3600, 0 }
@ -551,6 +553,8 @@ void tst_QTimeZone::checkOffset_data()
<< QDateTime(QDate(entry.year, entry.month, entry.day), << QDateTime(QDate(entry.year, entry.month, entry.day),
QTime(entry.hour, entry.min, entry.sec), zone) QTime(entry.hour, entry.min, entry.sec), zone)
<< entry.dst + entry.std << entry.std << entry.dst; << entry.dst + entry.std << entry.std << entry.dst;
} else {
qWarning("Skipping %s@%s test as zone is invalid", entry.zone, entry.nick);
} }
} }
} }