1
0
mirror of https://github.com/esp8266/Arduino.git synced 2025-04-19 23:22:16 +03:00
esp8266/tools/format_tzdata.py
Max Prokhorov c96dc26792
Generate TZ.h from zoneinfo (#9084)
* Generate TZ.h from zoneinfo

Using tzdata pypi package that ships zoneinfo blobs
Can't use raw data package from IANA, need it built first
Minor tweaks to Espurna script that generated .md

More data than the original, not limited to ZONE/...
Zoneinfo source can be overwritten using path args, if needed
(but, probably not needed, since it would always be preferable to pull the latest version)

Also, allow to override certain names and tz strings independent
of tzdata values (resolves #9007)
2024-02-11 18:51:35 +01:00

174 lines
4.0 KiB
Python
Executable File

#!/usr/bin/env python3
# this script refreshes world timezone definitions in
# cores/esp8266/TZ.h
#
# use the file output argument or stdout redirect to overwrite the target file
import argparse
import contextlib
import datetime
import mmap
import os
import pathlib
import re
import sys
import pathlib
from importlib import resources
import tzdata # https://tzdata.readthedocs.io/en/latest/
def known_alias(entry):
swaps = {
"Europe/Zaporozhye": "Europe/Zaporizhzhia",
"Europe/Uzhgorod": "Europe/Uzhhorod",
}
return swaps.get(entry)
def fix_name(name):
swaps = [["-", "m"], ["+", "p"], ["/", "_"]]
for lhs, rhs in swaps:
name = name.replace(lhs, rhs)
return name
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(f):
return [zone.strip() for zone in f.readlines()]
def make_zones(args):
out = []
for zone in make_zones_list(args.zones):
if args.root:
target = args.root / zone
else:
target = tzdata_resource_from_name(zone)
with target.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()
tz = tz.decode("ascii")
if alias := known_alias(zone):
out.append([alias, tz])
out.append([zone, tz])
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
rows.append(f"|{name}|`{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(f"*Generated with *{tzdata.IANA_VERSION=} {tzdata.__version__=}*")
def header(zones):
print("// ! ! ! DO NOT EDIT, AUTOMATICALLY GENERATED ! ! !")
print(f"// File created {datetime.datetime.now(tz=datetime.timezone.utc)}")
print(f"// Based on IANA database {tzdata.IANA_VERSION}")
print(f"// Re-run <esp8266 arduino core>/tools/{sys.argv[0]} to update")
print()
print("#pragma once")
print()
for name, value in zones:
print(f'#define TZ_{fix_name(name)}\tPSTR("{value}")')
if __name__ == "__main__":
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument(
"--output",
type=argparse.FileType("w", encoding="utf-8"),
default=sys.stdout,
)
parser.add_argument(
"--format",
default="header",
choices=["header", "markdown"],
)
parser.add_argument(
"--zones",
type=argparse.FileType("r", encoding="utf-8"),
help="Zone names file, 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",
type=pathlib.Path,
)
args = parser.parse_args()
zones = make_zones(args)
with contextlib.redirect_stdout(args.output):
if args.format == "markdown":
markdown(zones)
elif args.format == "header":
header(zones)