-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathupdate-tzdata.py
executable file
·305 lines (231 loc) · 11 KB
/
update-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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
#!/usr/bin/python3 -B
# Copyright 2017 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Generates the timezone data files used by Android."""
import argparse
import glob
import os
import re
import subprocess
import sys
import tarfile
import tempfile
sys.path.append('%s/external/icu/tools' % os.environ.get('ANDROID_BUILD_TOP'))
import i18nutil
import icuutil
import tzdatautil
# Calculate the paths that are referred to by multiple functions.
android_build_top = i18nutil.GetAndroidRootOrDie()
timezone_dir = os.path.realpath('%s/system/timezone' % android_build_top)
i18nutil.CheckDirExists(timezone_dir, 'system/timezone')
android_host_out = i18nutil.GetAndroidHostOutOrDie()
zone_compactor_dir = os.path.realpath('%s/system/timezone/input_tools/android' % android_build_top)
i18nutil.CheckDirExists(zone_compactor_dir, 'system/timezone/input_tools/android')
timezone_input_tools_dir = os.path.realpath('%s/input_tools' % timezone_dir)
timezone_input_data_dir = os.path.realpath('%s/input_data' % timezone_dir)
timezone_output_data_dir = '%s/output_data' % timezone_dir
i18nutil.CheckDirExists(timezone_output_data_dir, 'output_data')
tmp_dir = tempfile.mkdtemp('-tzdata')
def GenerateZicInputFile(extracted_iana_data_dir):
# Android APIs assume DST means "summer time" so we follow the rearguard format
# introduced in 2018e.
zic_input_file_name = 'rearguard.zi'
# 'NDATA=' is used to remove unnecessary rules files.
subprocess.check_call(['make', '-C', extracted_iana_data_dir, 'NDATA=', zic_input_file_name])
zic_input_file = '%s/%s' % (extracted_iana_data_dir, zic_input_file_name)
if not os.path.exists(zic_input_file):
print('Could not find %s' % zic_input_file)
sys.exit(1)
return zic_input_file
def WriteSetupFile(zic_input_file):
"""Writes the list of zones that ZoneCompactor should process."""
links = []
zones = []
for line in open(zic_input_file):
fields = line.split()
if fields:
line_type = fields[0]
if line_type == 'Link':
# Each "Link" line requires the creation of a link from an old tz ID to
# a new tz ID, and implies the existence of a zone with the old tz ID.
#
# IANA terminology:
# TARGET = the new tz ID, LINK-NAME = the old tz ID
target = fields[1]
link_name = fields[2]
links.append('Link %s %s' % (target, link_name))
zones.append('Zone %s' % link_name)
elif line_type == 'Zone':
# Each "Zone" line indicates the existence of a tz ID.
#
# IANA terminology:
# NAME is the tz ID, other fields like STDOFF, RULES, FORMAT,[UNTIL] are
# ignored.
name = fields[1]
zones.append('Zone %s' % name)
zone_compactor_setup_file = '%s/setup' % tmp_dir
setup = open(zone_compactor_setup_file, 'w')
# Ordering requirement from ZoneCompactor: Links must come first.
for link in sorted(set(links)):
setup.write('%s\n' % link)
for zone in sorted(set(zones)):
setup.write('%s\n' % zone)
setup.close()
return zone_compactor_setup_file
def BuildIcuData(iana_data_tar_file):
icu_build_dir = '%s/icu' % tmp_dir
icuutil.PrepareIcuBuild(icu_build_dir)
icuutil.MakeTzDataFiles(icu_build_dir, iana_data_tar_file)
# Create ICU system image files.
icuutil.MakeAndCopyIcuDataFiles(icu_build_dir)
# Create the ICU overlay time zone file.
icu_overlay_dir = '%s/icu_overlay' % timezone_output_data_dir
icu_overlay_dat_file = '%s/icu_tzdata.dat' % icu_overlay_dir
icuutil.MakeAndCopyOverlayTzIcuData(icu_build_dir, icu_overlay_dat_file)
# There are files in ICU which generation depends on ICU itself,
# so multiple builds might be needed.
icuutil.GenerateIcuDataFiles()
# Copy ICU license file(s)
icuutil.CopyLicenseFiles(icu_overlay_dir)
def GetIanaVersion(iana_tar_file):
iana_tar_filename = os.path.basename(iana_tar_file)
iana_version = re.search('tz(?:data|code)(.+)\\.tar\\.gz', iana_tar_filename).group(1)
return iana_version
def ExtractTarFile(tar_file, dir):
print('Extracting %s...' % tar_file)
if not os.path.exists(dir):
os.mkdir(dir)
tar = tarfile.open(tar_file, 'r')
tar.extractall(dir)
def BuildZic(iana_tools_dir):
iana_zic_code_tar_file = tzdatautil.GetIanaTarFile(iana_tools_dir, 'tzcode')
iana_zic_code_version = GetIanaVersion(iana_zic_code_tar_file)
iana_zic_data_tar_file = tzdatautil.GetIanaTarFile(iana_tools_dir, 'tzdata')
iana_zic_data_version = GetIanaVersion(iana_zic_data_tar_file)
print('Found IANA zic release %s/%s in %s/%s ...' \
% (iana_zic_code_version, iana_zic_data_version, iana_zic_code_tar_file,
iana_zic_data_tar_file))
zic_build_dir = '%s/zic' % tmp_dir
ExtractTarFile(iana_zic_code_tar_file, zic_build_dir)
ExtractTarFile(iana_zic_data_tar_file, zic_build_dir)
# zic
print('Building zic...')
# VERSION_DEPS= is to stop the build process looking for files that might not
# be present across different versions.
subprocess.check_call(['make', '-C', zic_build_dir, 'zic'])
zic_binary_file = '%s/zic' % zic_build_dir
if not os.path.exists(zic_binary_file):
print('Could not find %s' % zic_binary_file)
sys.exit(1)
return zic_binary_file
def BuildTzdata(zic_binary_file, extracted_iana_data_dir, iana_data_version):
print('Generating zic input file...')
zic_input_file = GenerateZicInputFile(extracted_iana_data_dir)
print('Calling zic...')
zic_output_dir = '%s/data' % tmp_dir
os.mkdir(zic_output_dir)
zic_cmd = [zic_binary_file, '-b', 'fat', '-d', zic_output_dir, zic_input_file]
subprocess.check_call(zic_cmd)
# ZoneCompactor
zone_compactor_setup_file = WriteSetupFile(zic_input_file)
print('Calling ZoneCompactor to update tzdata to %s...' % iana_data_version)
tzdatautil.InvokeSoong(android_build_top, ['zone_compactor'])
# Create args for ZoneCompactor
header_string = 'tzdata%s' % iana_data_version
print('Executing ZoneCompactor...')
command = '%s/bin/zone_compactor' % android_host_out
iana_output_data_dir = '%s/iana' % timezone_output_data_dir
subprocess.check_call([command, zone_compactor_setup_file, zic_output_dir, iana_output_data_dir,
header_string])
def BuildTzlookupAndTzIds(iana_data_dir):
countryzones_source_file = '%s/android/countryzones.txt' % timezone_input_data_dir
tzlookup_dest_file = '%s/android/tzlookup.xml' % timezone_output_data_dir
tzids_dest_file = '%s/android/tzids.prototxt' % timezone_output_data_dir
print('Calling TzLookupGenerator to create tzlookup.xml / tzids.prototxt...')
tzdatautil.InvokeSoong(android_build_top, ['tzlookup_generator'])
zone_tab_file = '%s/zone.tab' % iana_data_dir
command = '%s/bin/tzlookup_generator' % android_host_out
subprocess.check_call([command, countryzones_source_file, zone_tab_file, tzlookup_dest_file,
tzids_dest_file])
def BuildTelephonylookup():
telephonylookup_source_file = '%s/android/telephonylookup.txt' % timezone_input_data_dir
telephonylookup_dest_file = '%s/android/telephonylookup.xml' % timezone_output_data_dir
print('Calling TelephonyLookupGenerator to create telephonylookup.xml...')
tzdatautil.InvokeSoong(android_build_top, ['telephonylookup_generator'])
command = '%s/bin/telephonylookup_generator' % android_host_out
subprocess.check_call([command, telephonylookup_source_file, telephonylookup_dest_file])
def CreateDistroFiles(iana_data_version, android_revision,
output_distro_dir, output_version_file):
create_distro_script = '%s/distro/tools/create-distro.py' % timezone_dir
tzdata_file = '%s/iana/tzdata' % timezone_output_data_dir
icu_dir = '%s/icu_overlay' % timezone_output_data_dir
tzlookup_file = '%s/android/tzlookup.xml' % timezone_output_data_dir
telephonylookup_file = '%s/android/telephonylookup.xml' % timezone_output_data_dir
distro_file_pattern = '%s/*.zip' % output_distro_dir
existing_files = glob.glob(distro_file_pattern)
print('Removing %s' % existing_files)
for existing_file in existing_files:
os.remove(existing_file)
subprocess.check_call([create_distro_script,
'-iana_version', iana_data_version,
'-revision', str(android_revision),
'-tzdata', tzdata_file,
'-icu_dir', icu_dir,
'-tzlookup', tzlookup_file,
'-telephonylookup', telephonylookup_file,
'-output_distro_dir', output_distro_dir,
'-output_version_file', output_version_file])
def UpdateTestFiles():
testing_data_dir = '%s/testing/data' % timezone_dir
update_test_files_script = '%s/create-test-data.sh' % testing_data_dir
subprocess.check_call([update_test_files_script], cwd=testing_data_dir)
# Run from any directory, with no special setup required.
# In the rare case when tzdata has to be updated, but under the same version,
# pass "-revision" argument.
# See http://www.iana.org/time-zones/ for more about the source of this data.
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-revision', type=int, default=1,
help='The distro revision for the IANA version, default = 1')
args = parser.parse_args()
android_revision = args.revision
print('Source data file structure: %s' % timezone_input_data_dir)
print('Source tools file structure: %s' % timezone_input_tools_dir)
print('Intermediate / working dir: %s' % tmp_dir)
print('Output data file structure: %s' % timezone_output_data_dir)
iana_input_data_dir = '%s/iana' % timezone_input_data_dir
iana_data_tar_file = tzdatautil.GetIanaTarFile(iana_input_data_dir, 'tzdata')
iana_data_version = GetIanaVersion(iana_data_tar_file)
print('IANA time zone data release %s in %s ...' % (iana_data_version, iana_data_tar_file))
icu_dir = icuutil.icuDir()
print('Found icu in %s ...' % icu_dir)
BuildIcuData(iana_data_tar_file)
iana_tools_dir = '%s/iana' % timezone_input_tools_dir
zic_binary_file = BuildZic(iana_tools_dir)
iana_data_dir = '%s/iana_data' % tmp_dir
ExtractTarFile(iana_data_tar_file, iana_data_dir)
BuildTzdata(zic_binary_file, iana_data_dir, iana_data_version)
BuildTzlookupAndTzIds(iana_data_dir)
BuildTelephonylookup()
# Create a distro file and version file from the output from prior stages.
output_distro_dir = '%s/distro' % timezone_output_data_dir
output_version_file = '%s/version/tz_version' % timezone_output_data_dir
CreateDistroFiles(iana_data_version, android_revision,
output_distro_dir, output_version_file)
# Update test versions of distro files too.
UpdateTestFiles()
print('Look in %s and %s for new files' % (timezone_output_data_dir, icu_dir))
sys.exit(0)
if __name__ == '__main__':
main()