summaryrefslogtreecommitdiffstats
path: root/Lib/zoneinfo/_tzpath.py
diff options
context:
space:
mode:
authorPaul Ganssle <paul@ganssle.io>2020-05-18 01:55:11 (GMT)
committerGitHub <noreply@github.com>2020-05-18 01:55:11 (GMT)
commite527ec8abe0849e784ce100f53c2736986b670ae (patch)
tree1b638f564cbb69517ba7d9a5fe5d1cbd225bff40 /Lib/zoneinfo/_tzpath.py
parent9681953c99b686cf23d1c476a2b26d2ddbec7694 (diff)
downloadcpython-e527ec8abe0849e784ce100f53c2736986b670ae.zip
cpython-e527ec8abe0849e784ce100f53c2736986b670ae.tar.gz
cpython-e527ec8abe0849e784ce100f53c2736986b670ae.tar.bz2
bpo-40536: Add zoneinfo.available_timezones (GH-20158)
This was not specified in the PEP, but it will likely be a frequently requested feature if it's not included. This includes only the "canonical" zones, not a simple listing of every valid value of `key` that can be passed to `Zoneinfo`, because it seems likely that that's what people will want.
Diffstat (limited to 'Lib/zoneinfo/_tzpath.py')
-rw-r--r--Lib/zoneinfo/_tzpath.py65
1 files changed, 65 insertions, 0 deletions
diff --git a/Lib/zoneinfo/_tzpath.py b/Lib/zoneinfo/_tzpath.py
index 8cff0b1..c4c671d 100644
--- a/Lib/zoneinfo/_tzpath.py
+++ b/Lib/zoneinfo/_tzpath.py
@@ -102,6 +102,71 @@ def _validate_tzfile_path(path, _base=_TEST_PATH):
del _TEST_PATH
+def available_timezones():
+ """Returns a set containing all available time zones.
+
+ .. caution::
+
+ This may attempt to open a large number of files, since the best way to
+ determine if a given file on the time zone search path is to open it
+ and check for the "magic string" at the beginning.
+ """
+ from importlib import resources
+
+ valid_zones = set()
+
+ # Start with loading from the tzdata package if it exists: this has a
+ # pre-assembled list of zones that only requires opening one file.
+ try:
+ with resources.open_text("tzdata", "zones") as f:
+ for zone in f:
+ zone = zone.strip()
+ if zone:
+ valid_zones.add(zone)
+ except (ImportError, FileNotFoundError):
+ pass
+
+ def valid_key(fpath):
+ try:
+ with open(fpath, "rb") as f:
+ return f.read(4) == b"TZif"
+ except Exception: # pragma: nocover
+ return False
+
+ for tz_root in TZPATH:
+ if not os.path.exists(tz_root):
+ continue
+
+ for root, dirnames, files in os.walk(tz_root):
+ if root == tz_root:
+ # right/ and posix/ are special directories and shouldn't be
+ # included in the output of available zones
+ if "right" in dirnames:
+ dirnames.remove("right")
+ if "posix" in dirnames:
+ dirnames.remove("posix")
+
+ for file in files:
+ fpath = os.path.join(root, file)
+
+ key = os.path.relpath(fpath, start=tz_root)
+ if os.sep != "/": # pragma: nocover
+ key = key.replace(os.sep, "/")
+
+ if not key or key in valid_zones:
+ continue
+
+ if valid_key(fpath):
+ valid_zones.add(key)
+
+ if "posixrules" in valid_zones:
+ # posixrules is a special symlink-only time zone where it exists, it
+ # should not be included in the output
+ valid_zones.remove("posixrules")
+
+ return valid_zones
+
+
class InvalidTZPathWarning(RuntimeWarning):
"""Warning raised if an invalid path is specified in PYTHONTZPATH."""