88 lines
2.7 KiB
Python
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()
|