Fix various uncommon cases in QTzTimeZonePrivate backend
Includes a fixup for 03fadc26e7
, which
removed the check on empty transition list, needed when no data are
available. Ensured that such a data-free zone would in fact be
noticed as invalid during init().
Fixed handling of times before the epoch (we still want to consult a
POSIX rule, if that's all that's available) while ensuring we (as
documented) ignore DST for such times.
Fixed handling of large times (milliseconds since epoch outside int
range) when looking up POSIX rules. Gave QTimeZonePrivate a YearRange
enum (to be moved to QTimeZone once this merges up to dev) so as to
eliminate a magic number (and avoid adding another). Moved
year-munging in POSIX rules after the one early return, which doesn't
need the year range.
Added test-cases for the distant past/future (just checking UTC's
offsets; SLES has a minimal version of the UTC data-file that triggers
the bugs fixed here for them).
Fixes: QTBUG-74666
Fixes: QTBUG-74550
Change-Id: Ief7b7e55c62cf11064700934f404b2fc283614e1
Reviewed-by: Tony Sarajärvi <tony.sarajarvi@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
parent
f4c41b9797
commit
82ad4be4a2
@ -140,6 +140,10 @@ public:
|
||||
// Inlined for its one caller in qdatetime.cpp
|
||||
inline void setUtcOffsetByTZ(qint64 atMSecsSinceEpoch);
|
||||
#endif // timezone
|
||||
|
||||
// ### Qt 5.14: expose publicly in QDateTime
|
||||
// The first and last years of which QDateTime can represent some part:
|
||||
enum class YearRange : qint32 { First = -292275056, Last = +292278994 };
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
@ -39,6 +39,7 @@
|
||||
|
||||
#include "qtimezone.h"
|
||||
#include "qtimezoneprivate_p.h"
|
||||
#include "qdatetime_p.h" // ### Qt 5.14: remove once YearRange is on QDateTime
|
||||
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QHash>
|
||||
@ -520,19 +521,14 @@ PosixZone PosixZone::parse(const char *&pos, const char *end)
|
||||
|
||||
static QVector<QTimeZonePrivate::Data> calculatePosixTransitions(const QByteArray &posixRule,
|
||||
int startYear, int endYear,
|
||||
int lastTranMSecs)
|
||||
qint64 lastTranMSecs)
|
||||
{
|
||||
QVector<QTimeZonePrivate::Data> result;
|
||||
|
||||
// Limit year by qint64 max size for msecs
|
||||
if (startYear > 292278994)
|
||||
startYear = 292278994;
|
||||
if (endYear > 292278994)
|
||||
endYear = 292278994;
|
||||
|
||||
// POSIX Format is like "TZ=CST6CDT,M3.2.0/2:00:00,M11.1.0/2:00:00"
|
||||
// i.e. "std offset dst [offset],start[/time],end[/time]"
|
||||
// See the section about TZ at http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html
|
||||
// See the section about TZ at
|
||||
// http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html
|
||||
QList<QByteArray> parts = posixRule.split(',');
|
||||
|
||||
PosixZone stdZone, dstZone = PosixZone::invalid();
|
||||
@ -583,6 +579,13 @@ static QVector<QTimeZonePrivate::Data> calculatePosixTransitions(const QByteArra
|
||||
else
|
||||
stdTime = QTime(2, 0, 0);
|
||||
|
||||
// Limit year to the range QDateTime can represent:
|
||||
const int minYear = int(QDateTimePrivate::YearRange::First);
|
||||
const int maxYear = int(QDateTimePrivate::YearRange::Last);
|
||||
startYear = qBound(minYear, startYear, maxYear);
|
||||
endYear = qBound(minYear, endYear, maxYear);
|
||||
Q_ASSERT(startYear <= endYear);
|
||||
|
||||
for (int year = startYear; year <= endYear; ++year) {
|
||||
QTimeZonePrivate::Data dstData;
|
||||
QDateTime dst(calculatePosixDate(dstDateRule, year), dstTime, Qt::UTC);
|
||||
@ -598,13 +601,16 @@ static QVector<QTimeZonePrivate::Data> calculatePosixTransitions(const QByteArra
|
||||
stdData.standardTimeOffset = stdZone.offset;
|
||||
stdData.daylightTimeOffset = 0;
|
||||
stdData.abbreviation = stdZone.name;
|
||||
// Part of the high year will overflow
|
||||
if (year == 292278994 && (dstData.atMSecsSinceEpoch < 0 || stdData.atMSecsSinceEpoch < 0)) {
|
||||
// Part of maxYear will overflow (likewise for minYear, below):
|
||||
if (year == maxYear && (dstData.atMSecsSinceEpoch < 0 || stdData.atMSecsSinceEpoch < 0)) {
|
||||
if (dstData.atMSecsSinceEpoch > 0) {
|
||||
result << dstData;
|
||||
} else if (stdData.atMSecsSinceEpoch > 0) {
|
||||
result << stdData;
|
||||
}
|
||||
} else if (year < 1970) { // We ignore DST before the epoch.
|
||||
if (year > minYear || stdData.atMSecsSinceEpoch != QTimeZonePrivate::invalidMSecs())
|
||||
result << stdData;
|
||||
} else if (dst < std) {
|
||||
result << dstData << stdData;
|
||||
} else {
|
||||
@ -794,6 +800,8 @@ void QTzTimeZonePrivate::init(const QByteArray &ianaId)
|
||||
tran.atMSecsSinceEpoch = tz_tran.tz_time * 1000;
|
||||
m_tranTimes.append(tran);
|
||||
}
|
||||
if (m_tranTimes.isEmpty() && m_posixRule.isEmpty())
|
||||
return; // Invalid after all !
|
||||
|
||||
if (ianaId.isEmpty())
|
||||
m_id = systemTimeZoneId();
|
||||
@ -954,22 +962,25 @@ QVector<QTimeZonePrivate::Data> QTzTimeZonePrivate::getPosixTransitions(qint64 m
|
||||
QTimeZonePrivate::Data QTzTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const
|
||||
{
|
||||
// If the required time is after the last transition (or there were none)
|
||||
// and we have a POSIX rule then use it:
|
||||
if ((m_tranTimes.isEmpty() || m_tranTimes.last().atMSecsSinceEpoch < forMSecsSinceEpoch)
|
||||
&& !m_posixRule.isEmpty() && forMSecsSinceEpoch >= 0) {
|
||||
// and we have a POSIX rule, then use it:
|
||||
if (!m_posixRule.isEmpty()
|
||||
&& (m_tranTimes.isEmpty() || m_tranTimes.last().atMSecsSinceEpoch < forMSecsSinceEpoch)) {
|
||||
QVector<QTimeZonePrivate::Data> posixTrans = getPosixTransitions(forMSecsSinceEpoch);
|
||||
auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(),
|
||||
[forMSecsSinceEpoch] (const QTimeZonePrivate::Data &at) {
|
||||
return at.atMSecsSinceEpoch <= forMSecsSinceEpoch;
|
||||
});
|
||||
if (it > posixTrans.cbegin()) {
|
||||
QTimeZonePrivate::Data data = *--it;
|
||||
// Use most recent, if any in the past; or the first if we have no other rules:
|
||||
if (it > posixTrans.cbegin() || (m_tranTimes.isEmpty() && it < posixTrans.cend())) {
|
||||
QTimeZonePrivate::Data data = *(it > posixTrans.cbegin() ? it - 1 : it);
|
||||
data.atMSecsSinceEpoch = forMSecsSinceEpoch;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
if (m_tranTimes.isEmpty()) // Only possible if !isValid()
|
||||
return invalidData();
|
||||
|
||||
// Otherwise, if we can find a valid tran, then use its rule:
|
||||
// Otherwise, use the rule for the most recent or first transition:
|
||||
auto last = std::partition_point(m_tranTimes.cbegin(), m_tranTimes.cend(),
|
||||
[forMSecsSinceEpoch] (const QTzTransitionTime &at) {
|
||||
return at.atMSecsSinceEpoch <= forMSecsSinceEpoch;
|
||||
@ -989,9 +1000,9 @@ bool QTzTimeZonePrivate::hasTransitions() const
|
||||
QTimeZonePrivate::Data QTzTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch) const
|
||||
{
|
||||
// If the required time is after the last transition (or there were none)
|
||||
// and we have a POSIX rule then use it:
|
||||
if ((m_tranTimes.isEmpty() || m_tranTimes.last().atMSecsSinceEpoch < afterMSecsSinceEpoch)
|
||||
&& !m_posixRule.isEmpty() && afterMSecsSinceEpoch >= 0) {
|
||||
// and we have a POSIX rule, then use it:
|
||||
if (!m_posixRule.isEmpty()
|
||||
&& (m_tranTimes.isEmpty() || m_tranTimes.last().atMSecsSinceEpoch < afterMSecsSinceEpoch)) {
|
||||
QVector<QTimeZonePrivate::Data> posixTrans = getPosixTransitions(afterMSecsSinceEpoch);
|
||||
auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(),
|
||||
[afterMSecsSinceEpoch] (const QTimeZonePrivate::Data &at) {
|
||||
@ -1012,9 +1023,9 @@ QTimeZonePrivate::Data QTzTimeZonePrivate::nextTransition(qint64 afterMSecsSince
|
||||
QTimeZonePrivate::Data QTzTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const
|
||||
{
|
||||
// If the required time is after the last transition (or there were none)
|
||||
// and we have a POSIX rule then use it:
|
||||
if ((m_tranTimes.isEmpty() || m_tranTimes.last().atMSecsSinceEpoch < beforeMSecsSinceEpoch)
|
||||
&& !m_posixRule.isEmpty() && beforeMSecsSinceEpoch > 0) {
|
||||
// and we have a POSIX rule, then use it:
|
||||
if (!m_posixRule.isEmpty()
|
||||
&& (m_tranTimes.isEmpty() || m_tranTimes.last().atMSecsSinceEpoch < beforeMSecsSinceEpoch)) {
|
||||
QVector<QTimeZonePrivate::Data> posixTrans = getPosixTransitions(beforeMSecsSinceEpoch);
|
||||
auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(),
|
||||
[beforeMSecsSinceEpoch] (const QTimeZonePrivate::Data &at) {
|
||||
|
@ -539,8 +539,13 @@ void tst_QTimeZone::checkOffset_data()
|
||||
int year, month, day, hour, min, sec;
|
||||
int std, dst;
|
||||
} table[] = {
|
||||
// Zone with no transitions (QTBUG-74614, when TZ backend uses minimalist data)
|
||||
// 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 },
|
||||
{ "Etc/UTC", "post_int32", 2038, 1, 19, 3, 14, 9, 0, 0 },
|
||||
{ "Etc/UTC", "post_uint32", 2106, 2, 7, 6, 28, 17, 0, 0 },
|
||||
{ "Etc/UTC", "initial", -292275056, 5, 16, 16, 47, 5, 0, 0 },
|
||||
{ "Etc/UTC", "final", 292278994, 8, 17, 7, 12, 55, 0, 0 },
|
||||
// Kiev: regression test for QTBUG-64122 (on MS):
|
||||
{ "Europe/Kiev", "summer", 2017, 10, 27, 12, 0, 0, 2 * 3600, 3600 },
|
||||
{ "Europe/Kiev", "winter", 2017, 10, 29, 12, 0, 0, 2 * 3600, 0 }
|
||||
|
Loading…
Reference in New Issue
Block a user