#!/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 = \{([\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 ", "#include ", "", "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(key), static_cast(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(", " 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())