blob: 373933f28844dfc6ee7e9b1c3e91792981c777eb [file] [log] [blame]
Victor Chang90c5a0c2021-08-03 17:57:10 +01001#!/usr/bin/python3 -B
Neil Fuller86e72c52017-06-12 15:36:06 +01002
Neil Fuller56166d32017-06-12 12:57:10 +01003# Copyright 2017 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""Generates the timezone data files used by Android."""
Neil Fuller86e72c52017-06-12 15:36:06 +010018
Almaz Mingaleev535075a2021-09-28 11:32:28 +010019import argparse
Neil Fuller86e72c52017-06-12 15:36:06 +010020import glob
Neil Fuller86e72c52017-06-12 15:36:06 +010021import os
Neil Fullercdf56952018-02-20 17:12:37 +000022import re
Neil Fuller86e72c52017-06-12 15:36:06 +010023import subprocess
24import sys
25import tarfile
26import tempfile
27
Neil Fullerad7e81c2017-06-15 16:45:56 +010028sys.path.append('%s/external/icu/tools' % os.environ.get('ANDROID_BUILD_TOP'))
Neil Fuller86e72c52017-06-12 15:36:06 +010029import i18nutil
Neil Fullerfa89b032017-06-14 15:58:26 +010030import icuutil
Neil Fuller56166d32017-06-12 12:57:10 +010031import tzdatautil
Neil Fuller86e72c52017-06-12 15:36:06 +010032
Neil Fuller86e72c52017-06-12 15:36:06 +010033
34# Calculate the paths that are referred to by multiple functions.
35android_build_top = i18nutil.GetAndroidRootOrDie()
Neil Fuller56166d32017-06-12 12:57:10 +010036timezone_dir = os.path.realpath('%s/system/timezone' % android_build_top)
37i18nutil.CheckDirExists(timezone_dir, 'system/timezone')
38
Neil Fuller8cec5612017-10-11 10:38:17 +010039android_host_out = i18nutil.GetAndroidHostOutOrDie()
40
Neil Fuller0480f462019-04-09 19:23:35 +010041zone_compactor_dir = os.path.realpath('%s/system/timezone/input_tools/android' % android_build_top)
42i18nutil.CheckDirExists(zone_compactor_dir, 'system/timezone/input_tools/android')
Neil Fuller56166d32017-06-12 12:57:10 +010043
Neil Fullercdf56952018-02-20 17:12:37 +000044timezone_input_tools_dir = os.path.realpath('%s/input_tools' % timezone_dir)
Neil Fuller56166d32017-06-12 12:57:10 +010045timezone_input_data_dir = os.path.realpath('%s/input_data' % timezone_dir)
46
Neil Fuller35467e12017-06-12 16:55:44 +010047timezone_output_data_dir = '%s/output_data' % timezone_dir
48i18nutil.CheckDirExists(timezone_output_data_dir, 'output_data')
Neil Fuller86e72c52017-06-12 15:36:06 +010049
50tmp_dir = tempfile.mkdtemp('-tzdata')
51
Neil Fullera591fb02018-05-08 17:29:11 +010052def GenerateZicInputFile(extracted_iana_data_dir):
53 # Android APIs assume DST means "summer time" so we follow the rearguard format
54 # introduced in 2018e.
55 zic_input_file_name = 'rearguard.zi'
Neil Fuller86e72c52017-06-12 15:36:06 +010056
Neil Fullera591fb02018-05-08 17:29:11 +010057 # 'NDATA=' is used to remove unnecessary rules files.
58 subprocess.check_call(['make', '-C', extracted_iana_data_dir, 'NDATA=', zic_input_file_name])
59
60 zic_input_file = '%s/%s' % (extracted_iana_data_dir, zic_input_file_name)
61 if not os.path.exists(zic_input_file):
Luca Stefani136579e2019-01-04 17:09:55 +010062 print('Could not find %s' % zic_input_file)
Neil Fullera591fb02018-05-08 17:29:11 +010063 sys.exit(1)
64 return zic_input_file
65
66
67def WriteSetupFile(zic_input_file):
Neil Fuller86e72c52017-06-12 15:36:06 +010068 """Writes the list of zones that ZoneCompactor should process."""
69 links = []
70 zones = []
Neil Fullera591fb02018-05-08 17:29:11 +010071 for line in open(zic_input_file):
72 fields = line.split()
73 if fields:
Neil Fuller4da0af12020-05-19 14:56:15 +010074 line_type = fields[0]
75 if line_type == 'Link':
76 # Each "Link" line requires the creation of a link from an old tz ID to
77 # a new tz ID, and implies the existence of a zone with the old tz ID.
78 #
79 # IANA terminology:
80 # TARGET = the new tz ID, LINK-NAME = the old tz ID
81 target = fields[1]
82 link_name = fields[2]
83 links.append('Link %s %s' % (target, link_name))
84 zones.append('Zone %s' % link_name)
85 elif line_type == 'Zone':
86 # Each "Zone" line indicates the existence of a tz ID.
87 #
88 # IANA terminology:
89 # NAME is the tz ID, other fields like STDOFF, RULES, FORMAT,[UNTIL] are
90 # ignored.
91 name = fields[1]
92 zones.append('Zone %s' % name)
Neil Fuller86e72c52017-06-12 15:36:06 +010093
94 zone_compactor_setup_file = '%s/setup' % tmp_dir
95 setup = open(zone_compactor_setup_file, 'w')
Neil Fuller4da0af12020-05-19 14:56:15 +010096
97 # Ordering requirement from ZoneCompactor: Links must come first.
Neil Fuller86e72c52017-06-12 15:36:06 +010098 for link in sorted(set(links)):
99 setup.write('%s\n' % link)
100 for zone in sorted(set(zones)):
101 setup.write('%s\n' % zone)
102 setup.close()
103 return zone_compactor_setup_file
104
105
Neil Fullercdf56952018-02-20 17:12:37 +0000106def BuildIcuData(iana_data_tar_file):
Neil Fuller86e72c52017-06-12 15:36:06 +0100107 icu_build_dir = '%s/icu' % tmp_dir
108
Neil Fullerfa89b032017-06-14 15:58:26 +0100109 icuutil.PrepareIcuBuild(icu_build_dir)
Neil Fullercdf56952018-02-20 17:12:37 +0000110 icuutil.MakeTzDataFiles(icu_build_dir, iana_data_tar_file)
Neil Fullerfa89b032017-06-14 15:58:26 +0100111
112 # Create ICU system image files.
113 icuutil.MakeAndCopyIcuDataFiles(icu_build_dir)
114
Neil Fuller64bd4112018-11-01 18:14:07 +0000115 icu_overlay_dir = '%s/icu_overlay' % timezone_output_data_dir
116
Neil Fullerfa89b032017-06-14 15:58:26 +0100117 # Create the ICU overlay time zone file.
Neil Fuller64bd4112018-11-01 18:14:07 +0000118 icu_overlay_dat_file = '%s/icu_tzdata.dat' % icu_overlay_dir
Neil Fullerfa89b032017-06-14 15:58:26 +0100119 icuutil.MakeAndCopyOverlayTzIcuData(icu_build_dir, icu_overlay_dat_file)
Neil Fuller86e72c52017-06-12 15:36:06 +0100120
Neil Fuller64bd4112018-11-01 18:14:07 +0000121 # Copy ICU license file(s)
122 icuutil.CopyLicenseFiles(icu_overlay_dir)
123
Neil Fuller86e72c52017-06-12 15:36:06 +0100124
Neil Fullercdf56952018-02-20 17:12:37 +0000125def GetIanaVersion(iana_tar_file):
126 iana_tar_filename = os.path.basename(iana_tar_file)
127 iana_version = re.search('tz(?:data|code)(.+)\\.tar\\.gz', iana_tar_filename).group(1)
Neil Fullerad7e81c2017-06-15 16:45:56 +0100128 return iana_version
129
Neil Fuller8cec5612017-10-11 10:38:17 +0100130
Neil Fullercdf56952018-02-20 17:12:37 +0000131def ExtractTarFile(tar_file, dir):
Luca Stefani136579e2019-01-04 17:09:55 +0100132 print('Extracting %s...' % tar_file)
Neil Fullercdf56952018-02-20 17:12:37 +0000133 if not os.path.exists(dir):
134 os.mkdir(dir)
135 tar = tarfile.open(tar_file, 'r')
136 tar.extractall(dir)
Neil Fuller86e72c52017-06-12 15:36:06 +0100137
Neil Fullercdf56952018-02-20 17:12:37 +0000138
139def BuildZic(iana_tools_dir):
Neil Fuller2aba99c2019-02-26 13:15:34 +0000140 iana_zic_code_tar_file = tzdatautil.GetIanaTarFile(iana_tools_dir, 'tzcode')
Neil Fullercdf56952018-02-20 17:12:37 +0000141 iana_zic_code_version = GetIanaVersion(iana_zic_code_tar_file)
Neil Fuller2aba99c2019-02-26 13:15:34 +0000142 iana_zic_data_tar_file = tzdatautil.GetIanaTarFile(iana_tools_dir, 'tzdata')
Neil Fullercdf56952018-02-20 17:12:37 +0000143 iana_zic_data_version = GetIanaVersion(iana_zic_data_tar_file)
144
Luca Stefani136579e2019-01-04 17:09:55 +0100145 print('Found IANA zic release %s/%s in %s/%s ...' \
Neil Fullered0c59b2020-05-03 21:18:13 +0100146 % (iana_zic_code_version, iana_zic_data_version, iana_zic_code_tar_file,
147 iana_zic_data_tar_file))
Neil Fullercdf56952018-02-20 17:12:37 +0000148
149 zic_build_dir = '%s/zic' % tmp_dir
150 ExtractTarFile(iana_zic_code_tar_file, zic_build_dir)
151 ExtractTarFile(iana_zic_data_tar_file, zic_build_dir)
152
153 # zic
Luca Stefani136579e2019-01-04 17:09:55 +0100154 print('Building zic...')
Neil Fullercdf56952018-02-20 17:12:37 +0000155 # VERSION_DEPS= is to stop the build process looking for files that might not
156 # be present across different versions.
157 subprocess.check_call(['make', '-C', zic_build_dir, 'zic'])
158
159 zic_binary_file = '%s/zic' % zic_build_dir
160 if not os.path.exists(zic_binary_file):
Luca Stefani136579e2019-01-04 17:09:55 +0100161 print('Could not find %s' % zic_binary_file)
Neil Fullera591fb02018-05-08 17:29:11 +0100162 sys.exit(1)
Neil Fullercdf56952018-02-20 17:12:37 +0000163 return zic_binary_file
164
165
166def BuildTzdata(zic_binary_file, extracted_iana_data_dir, iana_data_version):
Luca Stefani136579e2019-01-04 17:09:55 +0100167 print('Generating zic input file...')
Neil Fullera591fb02018-05-08 17:29:11 +0100168 zic_input_file = GenerateZicInputFile(extracted_iana_data_dir)
169
Luca Stefani136579e2019-01-04 17:09:55 +0100170 print('Calling zic...')
Neil Fuller86e72c52017-06-12 15:36:06 +0100171 zic_output_dir = '%s/data' % tmp_dir
172 os.mkdir(zic_output_dir)
Almaz Mingaleev7ce53892021-10-18 16:09:14 +0100173 zic_cmd = [zic_binary_file, '-b', 'fat', '-d', zic_output_dir, zic_input_file]
Neil Fuller86e72c52017-06-12 15:36:06 +0100174 subprocess.check_call(zic_cmd)
175
Neil Fullercdf56952018-02-20 17:12:37 +0000176 # ZoneCompactor
Neil Fullera591fb02018-05-08 17:29:11 +0100177 zone_compactor_setup_file = WriteSetupFile(zic_input_file)
Neil Fuller86e72c52017-06-12 15:36:06 +0100178
Luca Stefani136579e2019-01-04 17:09:55 +0100179 print('Calling ZoneCompactor to update tzdata to %s...' % iana_data_version)
Neil Fullerd322be62019-09-16 16:25:03 +0100180
181 tzdatautil.InvokeSoong(android_build_top, ['zone_compactor'])
Neil Fuller86e72c52017-06-12 15:36:06 +0100182
Neil Fullercdf56952018-02-20 17:12:37 +0000183 # Create args for ZoneCompactor
Neil Fullercdf56952018-02-20 17:12:37 +0000184 header_string = 'tzdata%s' % iana_data_version
Neil Fuller35467e12017-06-12 16:55:44 +0100185
Luca Stefani136579e2019-01-04 17:09:55 +0100186 print('Executing ZoneCompactor...')
Neil Fullerd322be62019-09-16 16:25:03 +0100187 command = '%s/bin/zone_compactor' % android_host_out
Neil Fuller35467e12017-06-12 16:55:44 +0100188 iana_output_data_dir = '%s/iana' % timezone_output_data_dir
Neil Fuller61b7d562020-05-21 14:43:47 +0100189 subprocess.check_call([command, zone_compactor_setup_file, zic_output_dir, iana_output_data_dir,
190 header_string])
Neil Fuller56166d32017-06-12 12:57:10 +0100191
192
Neil Fuller78031492020-08-07 21:17:58 +0100193def BuildTzlookupAndTzIds(iana_data_dir):
Neil Fuller8cec5612017-10-11 10:38:17 +0100194 countryzones_source_file = '%s/android/countryzones.txt' % timezone_input_data_dir
Neil Fuller35467e12017-06-12 16:55:44 +0100195 tzlookup_dest_file = '%s/android/tzlookup.xml' % timezone_output_data_dir
Neil Fuller78031492020-08-07 21:17:58 +0100196 tzids_dest_file = '%s/android/tzids.prototxt' % timezone_output_data_dir
Neil Fuller8cec5612017-10-11 10:38:17 +0100197
Neil Fuller78031492020-08-07 21:17:58 +0100198 print('Calling TzLookupGenerator to create tzlookup.xml / tzids.prototxt...')
Neil Fullerd322be62019-09-16 16:25:03 +0100199 tzdatautil.InvokeSoong(android_build_top, ['tzlookup_generator'])
Neil Fuller8cec5612017-10-11 10:38:17 +0100200
Neil Fuller8cec5612017-10-11 10:38:17 +0100201 zone_tab_file = '%s/zone.tab' % iana_data_dir
Neil Fuller008dfcd2020-05-06 16:43:30 +0100202 backward_file = '%s/backward' % iana_data_dir
Neil Fullerd322be62019-09-16 16:25:03 +0100203 command = '%s/bin/tzlookup_generator' % android_host_out
Neil Fuller008dfcd2020-05-06 16:43:30 +0100204 subprocess.check_call([command, countryzones_source_file, zone_tab_file, backward_file,
Neil Fuller78031492020-08-07 21:17:58 +0100205 tzlookup_dest_file, tzids_dest_file])
Neil Fuller56166d32017-06-12 12:57:10 +0100206
Neil Fuller86e72c52017-06-12 15:36:06 +0100207
Neil Fullerf85e8142019-10-28 17:21:55 +0000208def BuildTelephonylookup():
209 telephonylookup_source_file = '%s/android/telephonylookup.txt' % timezone_input_data_dir
210 telephonylookup_dest_file = '%s/android/telephonylookup.xml' % timezone_output_data_dir
211
212 print('Calling TelephonyLookupGenerator to create telephonylookup.xml...')
213 tzdatautil.InvokeSoong(android_build_top, ['telephonylookup_generator'])
214
215 command = '%s/bin/telephonylookup_generator' % android_host_out
216 subprocess.check_call([command, telephonylookup_source_file, telephonylookup_dest_file])
217
218
Almaz Mingaleev535075a2021-09-28 11:32:28 +0100219def CreateDistroFiles(iana_data_version, android_revision,
220 output_distro_dir, output_version_file):
Neil Fullerad7e81c2017-06-15 16:45:56 +0100221 create_distro_script = '%s/distro/tools/create-distro.py' % timezone_dir
222
223 tzdata_file = '%s/iana/tzdata' % timezone_output_data_dir
Almaz Mingaleeve5a0b062021-06-30 14:48:09 +0100224 icu_dir = '%s/icu_overlay' % timezone_output_data_dir
Neil Fullerad7e81c2017-06-15 16:45:56 +0100225 tzlookup_file = '%s/android/tzlookup.xml' % timezone_output_data_dir
Neil Fullerf85e8142019-10-28 17:21:55 +0000226 telephonylookup_file = '%s/android/telephonylookup.xml' % timezone_output_data_dir
Neil Fullerad7e81c2017-06-15 16:45:56 +0100227
Neil Fullere2d551b2018-10-29 15:32:06 +0000228 distro_file_pattern = '%s/*.zip' % output_distro_dir
229 existing_files = glob.glob(distro_file_pattern)
Neil Fullerad7e81c2017-06-15 16:45:56 +0100230
Luca Stefani136579e2019-01-04 17:09:55 +0100231 print('Removing %s' % existing_files)
Neil Fullerad7e81c2017-06-15 16:45:56 +0100232 for existing_file in existing_files:
233 os.remove(existing_file)
234
235 subprocess.check_call([create_distro_script,
Neil Fullercdf56952018-02-20 17:12:37 +0000236 '-iana_version', iana_data_version,
Almaz Mingaleev535075a2021-09-28 11:32:28 +0100237 '-revision', str(android_revision),
Neil Fullerad7e81c2017-06-15 16:45:56 +0100238 '-tzdata', tzdata_file,
Almaz Mingaleeve5a0b062021-06-30 14:48:09 +0100239 '-icu_dir', icu_dir,
Neil Fullerad7e81c2017-06-15 16:45:56 +0100240 '-tzlookup', tzlookup_file,
Neil Fullerf85e8142019-10-28 17:21:55 +0000241 '-telephonylookup', telephonylookup_file,
Neil Fullere2d551b2018-10-29 15:32:06 +0000242 '-output_distro_dir', output_distro_dir,
243 '-output_version_file', output_version_file])
Neil Fullerad7e81c2017-06-15 16:45:56 +0100244
Neil Fullerad3c8762017-10-20 15:58:57 +0100245def UpdateTestFiles():
246 testing_data_dir = '%s/testing/data' % timezone_dir
247 update_test_files_script = '%s/create-test-data.sh' % testing_data_dir
248 subprocess.check_call([update_test_files_script], cwd=testing_data_dir)
249
Neil Fuller86e72c52017-06-12 15:36:06 +0100250
Almaz Mingaleev535075a2021-09-28 11:32:28 +0100251# Run from any directory, with no special setup required.
252# In the rare case when tzdata has to be updated, but under the same version,
253# pass "-revision" argument.
Neil Fuller86e72c52017-06-12 15:36:06 +0100254# See http://www.iana.org/time-zones/ for more about the source of this data.
255def main():
Almaz Mingaleev535075a2021-09-28 11:32:28 +0100256 parser = argparse.ArgumentParser()
257 parser.add_argument('-revision', type=int, default=1,
258 help='The distro revision for the IANA version, default = 1')
259
260 args = parser.parse_args()
261 android_revision = args.revision
262
Luca Stefani136579e2019-01-04 17:09:55 +0100263 print('Source data file structure: %s' % timezone_input_data_dir)
264 print('Source tools file structure: %s' % timezone_input_tools_dir)
Neil Fullered0c59b2020-05-03 21:18:13 +0100265 print('Intermediate / working dir: %s' % tmp_dir)
Luca Stefani136579e2019-01-04 17:09:55 +0100266 print('Output data file structure: %s' % timezone_output_data_dir)
Neil Fuller56166d32017-06-12 12:57:10 +0100267
Neil Fuller8cec5612017-10-11 10:38:17 +0100268 iana_input_data_dir = '%s/iana' % timezone_input_data_dir
Neil Fuller2aba99c2019-02-26 13:15:34 +0000269 iana_data_tar_file = tzdatautil.GetIanaTarFile(iana_input_data_dir, 'tzdata')
Neil Fullercdf56952018-02-20 17:12:37 +0000270 iana_data_version = GetIanaVersion(iana_data_tar_file)
Luca Stefani136579e2019-01-04 17:09:55 +0100271 print('IANA time zone data release %s in %s ...' % (iana_data_version, iana_data_tar_file))
Neil Fullerfa89b032017-06-14 15:58:26 +0100272
273 icu_dir = icuutil.icuDir()
Luca Stefani136579e2019-01-04 17:09:55 +0100274 print('Found icu in %s ...' % icu_dir)
Neil Fuller86e72c52017-06-12 15:36:06 +0100275
Neil Fullercdf56952018-02-20 17:12:37 +0000276 BuildIcuData(iana_data_tar_file)
277
278 iana_tools_dir = '%s/iana' % timezone_input_tools_dir
279 zic_binary_file = BuildZic(iana_tools_dir)
280
281 iana_data_dir = '%s/iana_data' % tmp_dir
282 ExtractTarFile(iana_data_tar_file, iana_data_dir)
283 BuildTzdata(zic_binary_file, iana_data_dir, iana_data_version)
Neil Fullered0c59b2020-05-03 21:18:13 +0100284
Neil Fuller78031492020-08-07 21:17:58 +0100285 BuildTzlookupAndTzIds(iana_data_dir)
Neil Fullered0c59b2020-05-03 21:18:13 +0100286
Neil Fullerf85e8142019-10-28 17:21:55 +0000287 BuildTelephonylookup()
Neil Fullerad7e81c2017-06-15 16:45:56 +0100288
Neil Fullere2d551b2018-10-29 15:32:06 +0000289 # Create a distro file and version file from the output from prior stages.
290 output_distro_dir = '%s/distro' % timezone_output_data_dir
291 output_version_file = '%s/version/tz_version' % timezone_output_data_dir
Almaz Mingaleev535075a2021-09-28 11:32:28 +0100292 CreateDistroFiles(iana_data_version, android_revision,
293 output_distro_dir, output_version_file)
Neil Fullerad7e81c2017-06-15 16:45:56 +0100294
Neil Fullerad3c8762017-10-20 15:58:57 +0100295 # Update test versions of distro files too.
296 UpdateTestFiles()
297
Luca Stefani136579e2019-01-04 17:09:55 +0100298 print('Look in %s and %s for new files' % (timezone_output_data_dir, icu_dir))
Neil Fuller86e72c52017-06-12 15:36:06 +0100299 sys.exit(0)
300
301
302if __name__ == '__main__':
303 main()