From 13e8609fc957b9bdcc435a93e39aae4211fe4777 Mon Sep 17 00:00:00 2001 From: Edward Welbourne Date: Fri, 24 Mar 2023 13:24:51 +0100 Subject: [PATCH] Include all available IANA DB zones for the TZ backend Previously, QTzTimeZonePrivate::availableTimeZoneIds() only reported the zones listed in the zone.tab file, which maps territories to zones. It thus omitted several zones that are provided by the IANA DB, but not the primary zones for any territory. This meant that it was possible to pass a zone name to the constructor successfully, despite isTimeZoneIdAvailable() claiming it isn't available. Pick-to: 6.5 Change-Id: I9e4eb7f4cfe578204951b995f0ad9ffd7eed5f9c Reviewed-by: Thiago Macieira --- src/corelib/time/qtimezoneprivate_tz.cpp | 36 ++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/src/corelib/time/qtimezoneprivate_tz.cpp b/src/corelib/time/qtimezoneprivate_tz.cpp index 2576fe7972a..e702a5d6b43 100644 --- a/src/corelib/time/qtimezoneprivate_tz.cpp +++ b/src/corelib/time/qtimezoneprivate_tz.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -41,14 +42,18 @@ Q_CONSTINIT static QBasicMutex s_icu_mutex; */ struct QTzTimeZone { - QLocale::Territory territory; + QLocale::Territory territory = QLocale::AnyTerritory; QByteArray comment; }; // Define as a type as Q_GLOBAL_STATIC doesn't like it typedef QHash QTzTimeZoneHash; -// Parse zone.tab table, assume lists all installed zones, if not will need to read directories +static bool isTzFile(const QString &name); + +// Parse zone.tab table for territory information, read directories to ensure we +// find all installed zones (many are omitted from zone.tab; even more from +// zone1970.tab). static QTzTimeZoneHash loadTzTimeZones() { QString path = QStringLiteral("/usr/share/zoneinfo/zone.tab"); @@ -85,6 +90,28 @@ static QTzTimeZoneHash loadTzTimeZones() } } } + + const qsizetype cut = path.lastIndexOf(u'/'); + Q_ASSERT(cut > 0); + const QDir zoneDir = QDir(path.first(cut)); + QDirIterator zoneFiles(zoneDir, QDirIterator::Subdirectories); + while (zoneFiles.hasNext()) { + const QFileInfo info = zoneFiles.nextFileInfo(); + if (!(info.isFile() || info.isSymLink())) + continue; + const QString name = zoneDir.relativeFilePath(info.filePath()); + // Two sub-directories containing (more or less) copies of the zoneinfo tree. + if (info.isDir() ? name == "posix"_L1 || name == "right"_L1 + : name.startsWith("posix/"_L1) || name.startsWith("right/"_L1)) { + continue; + } + // We could filter out *.* and leapseconds instead of doing the + // isTzFile() check; in practice current (2023) zoneinfo/ contains only + // actual zone files and matches to that filter. + const QByteArray id = QFile::encodeName(name); + if (!zonesHash.contains(id) && isTzFile(zoneDir.absoluteFilePath(name))) + zonesHash.insert(id, QTzTimeZone()); + } return zonesHash; } @@ -128,6 +155,11 @@ struct QTzType { }; Q_DECLARE_TYPEINFO(QTzType, Q_PRIMITIVE_TYPE); +static bool isTzFile(const QString &name) +{ + QFile file(name); + return file.open(QFile::ReadOnly) && file.read(strlen(TZ_MAGIC)) == TZ_MAGIC; +} // TZ File parsing