import argparse import mmap import os import pathlib import re import sys from importlib import resources import tzdata # https://tzdata.readthedocs.io/en/latest/ def tzdata_version(): return f"Generated with *{tzdata.IANA_VERSION=} {tzdata.__version__=}*" def maybe_fix_value(value): return re.sub(r"<[^>]*>", "UNK", value) def maybe_skip_zone(zone): return zone.startswith("Etc/") or zone in ("GB-Eire", "W-SU") def utc_alias(zone): return zone in ( "Universal", "UTC", "UCT", "Zulu", "GMT", "GMT+0", "GMT-0", "GMT0", "Greenwich", ) def tzdata_resource_from_name(name): pair = name.rsplit("/", 1) if len(pair) == 1: return resources.files("tzdata.zoneinfo") / pair[0] return resources.files(f'tzdata.zoneinfo.{pair[0].replace("/", ".")}') / pair[1] def make_zones_list(zones): with open(zones, "r") as f: zones = [zone.strip() for zone in f.readlines()] return zones def make_zones(args): out = [] for zone in make_zones_list(args.zones): target = tzdata_resource_from_name(zone) with tzdata_resource_from_name(zone).open("rb") as f: magic = f.read(4) if magic != b"TZif": continue m = mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ) newline = m.rfind(b"\n", 0, len(m) - 1) if newline < 0: continue m.seek(newline + 1) tz = m.readline().strip() out.append([zone, tz.decode("ascii")]) out.sort(key=lambda x: x[0]) return out def markdown(zones): utcs = [] rows = [] for name, value in zones: if utc_alias(name): utcs.append(name) continue if maybe_skip_zone(name): continue rows.append(f"|{name}|{maybe_fix_value(value)}|") print("|Name|Value|") print("|---|---|") for name in utcs: print(f"|{name}|UTC0|") last = "" for row in rows: prefix, _, _ = row.partition("/") if last != prefix: last = prefix print("|||") print(row) print() print("---") print() print(tzdata_version()) def header(zones): print(f"// ! ! ! DO NOT EDIT, AUTOMATICALLY GENERATED ! ! !") print(f"// {tzdata_version()}") print() print("#pragma once") print() for name, value in zones: print(f'#define\tTZ_{name.replace("/", "_")}\tPSTR("{value}")') if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument( "--format", default="markdown", choices=["markdown", "header"], ) parser.add_argument( "--zones", help="Zone names, one per line", default=os.path.join(os.path.dirname(tzdata.__file__), "zones"), ) parser.add_argument( "--root", help="Where do we get raw zoneinfo files from", default=os.path.join(os.path.dirname(tzdata.__file__), "zoneinfo"), ) args = parser.parse_args() zones = make_zones(args) if args.format == "markdown": markdown(zones) elif args.format == "header": header(zones)