diff options
author | Paul Ganssle <paul@ganssle.io> | 2020-05-18 01:55:11 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-05-18 01:55:11 (GMT) |
commit | e527ec8abe0849e784ce100f53c2736986b670ae (patch) | |
tree | 1b638f564cbb69517ba7d9a5fe5d1cbd225bff40 /Lib/zoneinfo/_tzpath.py | |
parent | 9681953c99b686cf23d1c476a2b26d2ddbec7694 (diff) | |
download | cpython-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.py | 65 |
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.""" |