From 569dc0de50a46b64763921199a2706ab34d151a9 Mon Sep 17 00:00:00 2001 From: Edward Welbourne Date: Mon, 11 Sep 2023 11:41:39 +0200 Subject: [PATCH] Support the TZDIR environment variable On Linux / glibc, this overrides the default system location for the zone info. So check for files there first. Break out a function to manage the trying of (now three) zoneinfo directories when opening a file by name relative to there. Pick-to: 6.6 6.5 Task-number: QTBUG-116017 Change-Id: I1f97107aabd9015c0a5543639870f1d70654ca67 Reviewed-by: Thiago Macieira --- src/corelib/time/qtimezoneprivate_tz.cpp | 71 ++++++++++++++++-------- 1 file changed, 48 insertions(+), 23 deletions(-) diff --git a/src/corelib/time/qtimezoneprivate_tz.cpp b/src/corelib/time/qtimezoneprivate_tz.cpp index e702a5d6b4..2b54cf7a5e 100644 --- a/src/corelib/time/qtimezoneprivate_tz.cpp +++ b/src/corelib/time/qtimezoneprivate_tz.cpp @@ -51,17 +51,41 @@ typedef QHash QTzTimeZoneHash; static bool isTzFile(const QString &name); +// Open a named file under the zone info directory: +static bool openZoneInfo(QString name, QFile *file) +{ + // At least on Linux / glibc (see man 3 tzset), $TZDIR overrides the system + // default location for zone info: + const QString tzdir = qEnvironmentVariable("TZDIR"); + if (!tzdir.isEmpty()) { + file->setFileName(QDir(tzdir).filePath(name)); + if (file->open(QIODevice::ReadOnly)) + return true; + } + // Try modern system path first: + constexpr auto zoneShare = "/usr/share/zoneinfo/"_L1; + if (tzdir != zoneShare && tzdir != zoneShare.chopped(1)) { + file->setFileName(zoneShare + name); + if (file->open(QIODevice::ReadOnly)) + return true; + } + // Fall back to legacy system path: + constexpr auto zoneLib = "/usr/lib/zoneinfo/"_L1; + if (tzdir != zoneLib && tzdir != zoneLib.chopped(1)) { + file->setFileName(zoneShare + name); + if (file->open(QIODevice::ReadOnly)) + return true; + } + return false; +} + // 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"); - if (!QFile::exists(path)) - path = QStringLiteral("/usr/lib/zoneinfo/zone.tab"); - - QFile tzif(path); - if (!tzif.open(QIODevice::ReadOnly)) + QFile tzif; + if (!openZoneInfo("zone.tab"_L1, &tzif)) return QTzTimeZoneHash(); QTzTimeZoneHash zonesHash; @@ -91,6 +115,7 @@ static QTzTimeZoneHash loadTzTimeZones() } } + const QString path = tzif.fileName(); const qsizetype cut = path.lastIndexOf(u'/'); Q_ASSERT(cut > 0); const QDir zoneDir = QDir(path.first(cut)); @@ -772,20 +797,13 @@ QTzTimeZoneCacheEntry QTzTimeZoneCache::findEntry(const QByteArray &ianaId) tzif.setFileName(QStringLiteral("/etc/localtime")); if (!tzif.open(QIODevice::ReadOnly)) return ret; - } else { - // Open named tz, try modern path first, if fails try legacy path - tzif.setFileName("/usr/share/zoneinfo/"_L1 + QString::fromLocal8Bit(ianaId)); - if (!tzif.open(QIODevice::ReadOnly)) { - tzif.setFileName("/usr/lib/zoneinfo/"_L1 + QString::fromLocal8Bit(ianaId)); - if (!tzif.open(QIODevice::ReadOnly)) { - // ianaId may be a POSIX rule, taken from $TZ or /etc/TZ - auto check = validatePosixRule(ianaId); - if (check.isValid) { - ret.m_hasDst = check.hasDst; - ret.m_posixRule = ianaId; - } - return ret; - } + } else if (!openZoneInfo(QString::fromLocal8Bit(ianaId), &tzif)) { + // ianaId may be a POSIX rule, taken from $TZ or /etc/TZ + auto check = validatePosixRule(ianaId); + if (check.isValid) { + ret.m_hasDst = check.hasDst; + ret.m_posixRule = ianaId; + return ret; } } @@ -1332,7 +1350,8 @@ private: { // On most distros /etc/localtime is a symlink to a real file so extract // name from the path - const auto zoneinfo = "/zoneinfo/"_L1; + const QString tzdir = qEnvironmentVariable("TZDIR"); + constexpr auto zoneinfo = "/zoneinfo/"_L1; QString path = QStringLiteral("/etc/localtime"); long iteration = getSymloopMax(); // Symlink may point to another symlink etc. before being under zoneinfo/ @@ -1340,8 +1359,14 @@ private: // symlink, like America/Montreal pointing to America/Toronto do { path = QFile::symLinkTarget(path); - int index = path.indexOf(zoneinfo); - if (index >= 0) // Found zoneinfo file; extract zone name from path: + // If it's a zoneinfo file, extract the zone name from its path: + int index = tzdir.isEmpty() ? -1 : path.indexOf(tzdir); + if (index >= 0) { + const auto tail = QStringView{ path }.sliced(index + tzdir.size()).toUtf8(); + return tail.startsWith(u'/') ? tail.sliced(1) : tail; + } + index = path.indexOf(zoneinfo); + if (index >= 0) return QStringView{ path }.sliced(index + zoneinfo.size()).toUtf8(); } while (!path.isEmpty() && --iteration > 0);