Files
sm-plugins/EventNotifier/tools/gen_timezones.py
T

88 lines
2.7 KiB
Python

#!/usr/bin/env python3
"""
Generates configs/eventnotifier_timezones.cfg from the system tzdata.
Each zone lists "<utc timestamp>" "<offset seconds> <abbreviation>" pairs; the
"0" entry is the offset in effect before the first transition in range.
Regenerate before END_YEAR runs out (32-bit timestamps end in 2038 anyway).
"""
import re
import zoneinfo
from datetime import datetime, timedelta, timezone
from pathlib import Path
START_YEAR = 2025
END_YEAR = 2037
OUT = Path(__file__).resolve().parent.parent / "configs" / "eventnotifier_timezones.cfg"
def utc(ts):
return datetime.fromtimestamp(ts, timezone.utc)
def state(zone, ts):
local = utc(ts).astimezone(zone)
return int(local.utcoffset().total_seconds()), local.tzname()
def abbreviation(name, offset):
# tzdata uses numeric names like "+03" / "-0330" for zones without a real abbreviation.
if name and re.fullmatch(r"[A-Za-z]+", name):
return name
sign = "+" if offset >= 0 else "-"
hours, rem = divmod(abs(offset), 3600)
minutes = rem // 60
if hours == 0 and minutes == 0:
return "UTC"
return f"UTC{sign}{hours}:{minutes:02d}" if minutes else f"UTC{sign}{hours}"
def transitions(zone):
start = int(datetime(START_YEAR, 1, 1, tzinfo=timezone.utc).timestamp())
end = int(datetime(END_YEAR, 12, 31, tzinfo=timezone.utc).timestamp())
step = int(timedelta(days=1).total_seconds())
periods = [(0, *state(zone, start))]
prev_ts, prev = start, periods[0][1:]
ts = start + step
while ts <= end:
cur = state(zone, ts)
if cur != prev:
# Bisect to the first second of the new state.
lo, hi = prev_ts, ts
while hi - lo > 1:
mid = (lo + hi) // 2
if state(zone, mid) == prev:
lo = mid
else:
hi = mid
periods.append((hi, *state(zone, hi)))
prev = cur
prev_ts = ts
ts += step
return periods
def main():
lines = ["// Generated by tools/gen_timezones.py, do not edit by hand.", '"Timezones"', "{"]
for name in sorted(zoneinfo.available_timezones()):
if name.startswith(("posix/", "right/")) or name in ("Factory", "localtime"):
continue
zone = zoneinfo.ZoneInfo(name)
lines.append(f'\t"{name}"')
lines.append("\t{")
for ts, offset, tzname in transitions(zone):
lines.append(f'\t\t"{ts}"\t"{offset} {abbreviation(tzname, offset)}"')
lines.append("\t}")
lines.append("}")
OUT.parent.mkdir(parents=True, exist_ok=True)
OUT.write_text("\n".join(lines) + "\n")
print(f"Wrote {OUT}")
if __name__ == "__main__":
main()