QDTP: match local-time by preference to its zone

When parsing a string whose time-zone part matches local time's name,
use local time in preference to the QTimeZone with that name. The case
is ambiguous, and the bug was already fixed (by something else) in
dev, but this caused a failure in 6.2 through 6.5; and using local
time is more natural to QDateTime in any case. The fix incidentally
makes the the logic of the zone-resolution code more straightforward
and a closer match to how findTimeZone() found the match.

The issue was hidden from 6.6 by a change [*] to the handling of POSIX
rules, that lead to plain abbreviations such as CEST and BST - for
which the IANA DB has no entry - no longer being considered "valid"
zones, despite being technically valid POSIX zone descriptors
(effectively as aliases for UTC).

[*] commit 41c561ddde6210651c60c0789d592f79d7b3e4d5

Fixes: QTBUG-114575
Change-Id: I4369901afd26961d038e382f4c4a7beb83659ad7
Reviewed-by: Konrad Kujawa <konrad.kujawa@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
(cherry picked from commit ca6a0fd63fdd5209f2cc1ff59e6b31c0bed14597)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Edward Welbourne 2023-06-15 13:45:58 +02:00 committed by Qt Cherry-pick Bot
parent 89db6ceef0
commit 47f219c798
3 changed files with 44 additions and 11 deletions

View File

@ -1281,18 +1281,14 @@ QDateTimeParser::scanString(const QDateTime &defaultValue, bool fixup) const
if (isUtc || isUtcOffset) {
timeZone = QTimeZone::fromSecondsAheadOfUtc(sect.value);
} else {
#if QT_CONFIG(timezone)
} else if (startsWithLocalTimeZone(zoneName, usedDateTime) != sect.used) {
QTimeZone namedZone = QTimeZone(zoneName.toLatin1());
if (namedZone.isValid()) {
timeZone = namedZone;
} else {
Q_ASSERT(startsWithLocalTimeZone(zoneName, usedDateTime));
timeZone = QTimeZone::LocalTime;
}
#else
timeZone = QTimeZone::LocalTime;
Q_ASSERT(namedZone.isValid());
timeZone = namedZone;
#endif
} else {
timeZone = QTimeZone::LocalTime;
}
}
break;

View File

@ -7,7 +7,7 @@
#include <QTimeZone>
#include <private/qdatetime_p.h>
#include <private/qtenvironmentvariables_p.h> // for qTzSet()
#include <private/qtenvironmentvariables_p.h> // for qTzSet(), qTzName()
#ifdef Q_OS_WIN
# include <qt_windows.h>
@ -3255,10 +3255,15 @@ void tst_QDateTime::fromStringStringFormat_localTimeZone_data()
QTimeZone gmt("GMT");
if (gmt.isValid()) {
lacksRows = false;
const bool fullyLocal = ([]() {
TimeZoneRollback useZone("GMT");
return qTzName(0) == u"GMT"_s;
})();
QTest::newRow("local-timezone-with-offset:GMT")
<< QByteArrayLiteral("GMT")
<< QString("2008-10-13 GMT 11.50") << QString("yyyy-MM-dd t hh.mm")
<< QDateTime(QDate(2008, 10, 13), QTime(11, 50), gmt);
<< QDateTime(QDate(2008, 10, 13), QTime(11, 50),
fullyLocal ? QTimeZone(QTimeZone::LocalTime) : gmt);
}
QTimeZone helsinki("Europe/Helsinki");
if (helsinki.isValid()) {

View File

@ -46,6 +46,7 @@ class tst_QDateTimeParser : public QObject
Q_OBJECT
private Q_SLOTS:
void reparse();
void parseSection_data();
void parseSection();
@ -53,6 +54,37 @@ private Q_SLOTS:
void intermediateYear();
};
void tst_QDateTimeParser::reparse()
{
const QDateTime when = QDate(2023, 6, 15).startOfDay();
// QTBUG-114575: 6.2 through 6.5 got back a bogus Qt::TimeZone (with zero offset):
const Qt::TimeSpec spec = ([](QStringView name) {
// When local time is UTC or a fixed offset from it, the parser prefers
// to interpret a UTC or offset suffix as such, rather than as local
// time (thereby avoiding DST-ness checks). We have to match that here.
if (name == QLatin1StringView("UTC"))
return Qt::UTC;
if (name.startsWith(u'+') || name.startsWith(u'-')) {
if (std::all_of(name.begin() + 1, name.end(), [](QChar ch) { return ch == u'0'; }))
return Qt::UTC;
if (std::all_of(name.begin() + 1, name.end(), [](QChar ch) { return ch.isDigit(); }))
return Qt::OffsetFromUTC;
// Potential hh:mm offset ? Not yet seen as local tzname[] entry.
}
return Qt::LocalTime;
})(when.timeZoneAbbreviation());
const QStringView format = u"dd/MM/yyyy HH:mm t";
QDateTimeParser who(QMetaType::QDateTime, QDateTimeParser::DateTimeEdit);
QVERIFY(who.parseFormat(format));
const auto state = who.parse(when.toString(format), -1, when, false);
QCOMPARE(state.state, QDateTimeParser::Acceptable);
QVERIFY(!state.conflicts);
QCOMPARE(state.padded, 0);
QCOMPARE(state.value.timeSpec(), spec);
QCOMPARE(state.value, when);
}
void tst_QDateTimeParser::parseSection_data()
{
QTest::addColumn<QString>("format");