Skip to content

Commit 1dee5a4

Browse files
committed
Generate TZ.h from zoneinfo
Using tzdata pypi package that ships zoneinfo blobs (can be overwritten using path args, if needed) Can't use raw data package from IANA, need it built first More data than the original, not limited to ZONE/... Also, allow to override certain names and tz strings independent of tzdata values (resolve esp8266#9007)
1 parent de1029f commit 1dee5a4

File tree

2 files changed

+187
-2
lines changed

2 files changed

+187
-2
lines changed

package/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ The following points assume work in a direct clone of the repository, and not in
111111
112112
* [platform.txt](https://github.com/esp8266/Arduino/blob/master/platform.txt) and [package.json](https://github.com/esp8266/Arduino/blob/master/package.json): update `version` to the release E.g. `3.0.0`,
113113
114-
* [cores/esp8266/TZ.h](https://github.com/esp8266/Arduino/blob/master/cores/esp8266/TZ.h): import the latest database with the following shell command:\
115-
`$ cd tools; sh TZupdate.sh`
114+
* [cores/esp8266/TZ.h](https://github.com/esp8266/Arduino/blob/master/cores/esp8266/TZ.h): import the latest database:\
115+
`$ cd tools; pip install -U tzdata; python format_tzdata.py --format header --output ../cores/esp8266/TZ.h`
116116
117117
* Update SSL/TLS certificates and public keys in examples:\
118118
`$ cd tools; sh certsUpdate.sh`

tools/format_tzdata.py

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
# this script refreshes world timezone definitions in
2+
# cores/esp8266/TZ.h
3+
#
4+
# use the file output argument or stdout redirect to overwrite the target file
5+
6+
import argparse
7+
import contextlib
8+
import datetime
9+
import mmap
10+
import os
11+
import pathlib
12+
import re
13+
import sys
14+
15+
from importlib import resources
16+
17+
import tzdata # https://tzdata.readthedocs.io/en/latest/
18+
19+
20+
def tzdata_version():
21+
return f"Generated with *{tzdata.IANA_VERSION=} {tzdata.__version__=}*"
22+
23+
24+
def known_tz(entry):
25+
swaps = {
26+
"Europe/Simferopol": "EET-2EEST,M3.5.0/3,M10.5.0/4",
27+
}
28+
29+
return swaps.get(entry, None)
30+
31+
32+
def known_alias(entry):
33+
swaps = {
34+
"Europe/Zaporozhye": "Europe/Zaporizhzhia",
35+
"Europe/Uzhgorod": "Europe/Uzhhorod",
36+
}
37+
38+
return swaps.get(entry)
39+
40+
41+
def fix_name(name):
42+
swaps = [["-", "m"], ["+", "p"], ["/", "_"]]
43+
44+
for lhs, rhs in swaps:
45+
name = name.replace(lhs, rhs)
46+
47+
return name
48+
49+
50+
def fix_value(value):
51+
return re.sub(r"<[^>]*>", "UNK", value)
52+
53+
54+
def utc_alias(zone):
55+
return zone in (
56+
"Universal",
57+
"UTC",
58+
"UCT",
59+
"Zulu",
60+
"GMT",
61+
"GMT+0",
62+
"GMT-0",
63+
"GMT0",
64+
"Greenwich",
65+
)
66+
67+
68+
def tzdata_resource_from_name(name):
69+
pair = name.rsplit("/", 1)
70+
if len(pair) == 1:
71+
return resources.files("tzdata.zoneinfo") / pair[0]
72+
73+
return resources.files(f'tzdata.zoneinfo.{pair[0].replace("/", ".")}') / pair[1]
74+
75+
76+
def make_zones_list(zones):
77+
with open(zones, "r") as f:
78+
zones = [zone.strip() for zone in f.readlines()]
79+
80+
return zones
81+
82+
83+
def make_zones(args):
84+
out = []
85+
86+
for zone in make_zones_list(args.zones):
87+
target = tzdata_resource_from_name(zone)
88+
with tzdata_resource_from_name(zone).open("rb") as f:
89+
magic = f.read(4)
90+
if magic != b"TZif":
91+
continue
92+
93+
m = mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ)
94+
newline = m.rfind(b"\n", 0, len(m) - 1)
95+
if newline < 0:
96+
continue
97+
98+
m.seek(newline + 1)
99+
tz = m.readline().strip()
100+
tz = tz.decode("ascii")
101+
102+
if alias := known_alias(zone):
103+
out.append([alias, tz])
104+
105+
if replacement := known_tz(zone):
106+
out.append([zone, replacement])
107+
else:
108+
out.append([zone, tz])
109+
110+
out.sort(key=lambda x: x[0])
111+
return out
112+
113+
114+
def markdown(zones):
115+
utcs = []
116+
rows = []
117+
118+
for name, value in zones:
119+
if utc_alias(name):
120+
utcs.append(name)
121+
continue
122+
123+
rows.append(f"|{name}|{fix_value(value)}|")
124+
125+
print("|Name|Value|")
126+
print("|---|---|")
127+
for name in utcs:
128+
print(f"|{name}|UTC0|")
129+
130+
last = ""
131+
for row in rows:
132+
prefix, _, _ = row.partition("/")
133+
if last != prefix:
134+
last = prefix
135+
print("|||")
136+
print(row)
137+
print()
138+
print("---")
139+
print()
140+
print(tzdata_version())
141+
142+
143+
def header(zones):
144+
print(f"// ! ! ! DO NOT EDIT, AUTOMATICALLY GENERATED ! ! !")
145+
print(f"// File created {datetime.datetime.now()}")
146+
print(f"// Based on IANA database {tzdata.IANA_VERSION}")
147+
print(f"// Re-run <esp8266 arduino core>/tools/{sys.argv[0]} to update")
148+
print()
149+
print("#pragma once")
150+
print()
151+
for name, value in zones:
152+
print(f'#define\tTZ_{fix_name(name)}\tPSTR("{value}")')
153+
154+
155+
if __name__ == "__main__":
156+
parser = argparse.ArgumentParser()
157+
parser.add_argument(
158+
"--output",
159+
type=argparse.FileType("w"),
160+
default=sys.stdout,
161+
)
162+
parser.add_argument(
163+
"--format",
164+
default="header",
165+
choices=["markdown", "header"],
166+
)
167+
parser.add_argument(
168+
"--zones",
169+
help="Zone names, one per line",
170+
default=os.path.join(os.path.dirname(tzdata.__file__), "zones"),
171+
)
172+
parser.add_argument(
173+
"--root",
174+
help="Where do we get raw zoneinfo files from",
175+
default=os.path.join(os.path.dirname(tzdata.__file__), "zoneinfo"),
176+
)
177+
178+
args = parser.parse_args()
179+
zones = make_zones(args)
180+
181+
with contextlib.redirect_stdout(args.output):
182+
if args.format == "markdown":
183+
markdown(zones)
184+
elif args.format == "header":
185+
header(zones)

0 commit comments

Comments
 (0)