Files
EMS-ESP32/scripts/generate_tz_zones.py

84 lines
2.8 KiB
Python

#!/usr/bin/env python3
"""Generate src/ESP32React/tz_zones.h from interface/src/app/settings/TZ.tsx.
Run from the repo root:
python3 scripts/generate_tz_zones.py
"""
from __future__ import annotations
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
TZ_TSX = ROOT / "interface" / "src" / "app" / "settings" / "TZ.tsx"
OUT_H = ROOT / "src" / "ESP32React" / "tz_zones.h"
def parse_time_zones(text: str) -> list[tuple[str, str]]:
match = re.search(
r"export const TIME_ZONES: Record<string, string> = \{([\s\S]*?)\n\};",
text,
)
if not match:
raise SystemExit("TIME_ZONES table not found in TZ.tsx")
entries = re.findall(r"'((?:\\'|[^'])*)': '((?:\\'|[^'])*)'", match.group(1))
if not entries:
raise SystemExit("no TIME_ZONES entries parsed")
return sorted(entries, key=lambda item: item[0])
def c_string(value: str) -> str:
return '"' + value.replace("\\", "\\\\").replace('"', '\\"') + '"'
def main() -> int:
entries = parse_time_zones(TZ_TSX.read_text(encoding="utf-8"))
max_format = max(len(fmt) for _, fmt in entries)
lines = [
"/* Auto-generated by scripts/generate_tz_zones.py — do not edit. */",
"#pragma once",
"",
"#include <stdlib.h>",
"#include <string.h>",
"",
"struct EmsespTzZone {",
" const char * label;",
" const char * format;",
"};",
"",
f"// Longest POSIX TZ value in the table is {max_format} chars.",
"static constexpr size_t EMSESP_TZ_ZONE_COUNT = "
f"{len(entries)};",
"static const EmsespTzZone EMSESP_TZ_ZONES[EMSESP_TZ_ZONE_COUNT] = {",
]
for label, fmt in entries:
lines.append(f" {{{c_string(label)}, {c_string(fmt)}}},")
lines += [
"};",
"",
"inline int emsesp_tz_zone_cmp(const void * key, const void * element) {",
" return strcmp(static_cast<const char *>(key), static_cast<const EmsespTzZone *>(element)->label);",
"}",
"",
"// Returns the POSIX TZ string for an IANA label, or nullptr if unknown.",
"inline const char * emsesp_tz_format_for_label(const char * label) {",
" if (label == nullptr || *label == '\\0') {",
" return nullptr;",
" }",
" const auto * found = static_cast<const EmsespTzZone *>(",
" bsearch(label, EMSESP_TZ_ZONES, EMSESP_TZ_ZONE_COUNT, sizeof(EmsespTzZone), emsesp_tz_zone_cmp));",
" return found != nullptr ? found->format : nullptr;",
"}",
"",
]
OUT_H.write_text("\n".join(lines), encoding="utf-8")
print(f"Wrote {OUT_H.relative_to(ROOT)} ({len(entries)} zones, max format {max_format})")
return 0
if __name__ == "__main__":
sys.exit(main())