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