blob: b79860d5de2295123546734d9a45db477e8177d9 [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
115 # Create the ICU overlay time zone file.
Almaz Mingaleev7ed0edf2021-11-04 15:30:02 +0000116 icu_overlay_dir = '%s/icu_overlay' % timezone_output_data_dir
Neil Fuller64bd4112018-11-01 18:14:07 +0000117 icu_overlay_dat_file = '%s/icu_tzdata.dat' % icu_overlay_dir
Neil Fullerfa89b032017-06-14 15:58:26 +0100118 icuutil.MakeAndCopyOverlayTzIcuData(icu_build_dir, icu_overlay_dat_file)
Neil Fuller86e72c52017-06-12 15:36:06 +0100119
Almaz Mingaleev881012b2021-11-08 10:00:23 +0000120 # There are files in ICU which generation depends on ICU itself,
121 # so multiple builds might be needed.
Almaz Mingaleev7ed0edf2021-11-04 15:30:02 +0000122 icuutil.GenerateIcuDataFiles()
123
Neil Fuller64bd4112018-11-01 18:14:07 +0000124 # Copy ICU license file(s)
125 icuutil.CopyLicenseFiles(icu_overlay_dir)
126
Neil Fuller86e72c52017-06-12 15:36:06 +0100127
Neil Fullercdf56952018-02-20 17:12:37 +0000128def GetIanaVersion(iana_tar_file):
129 iana_tar_filename = os.path.basename(iana_tar_file)
130 iana_version = re.search('tz(?:data|code)(.+)\\.tar\\.gz', iana_tar_filename).group(1)
Neil Fullerad7e81c2017-06-15 16:45:56 +0100131 return iana_version
132
Neil Fuller8cec5612017-10-11 10:38:17 +0100133
Neil Fullercdf56952018-02-20 17:12:37 +0000134def ExtractTarFile(tar_file, dir):
Luca Stefani136579e2019-01-04 17:09:55 +0100135 print('Extracting %s...' % tar_file)
Neil Fullercdf56952018-02-20 17:12:37 +0000136 if not os.path.exists(dir):
137 os.mkdir(dir)
138 tar = tarfile.open(tar_file, 'r')
139 tar.extractall(dir)
Neil Fuller86e72c52017-06-12 15:36:06 +0100140
Neil Fullercdf56952018-02-20 17:12:37 +0000141
142def BuildZic(iana_tools_dir):
Neil Fuller2aba99c2019-02-26 13:15:34 +0000143 iana_zic_code_tar_file = tzdatautil.GetIanaTarFile(iana_tools_dir, 'tzcode')
Neil Fullercdf56952018-02-20 17:12:37 +0000144 iana_zic_code_version = GetIanaVersion(iana_zic_code_tar_file)
Neil Fuller2aba99c2019-02-26 13:15:34 +0000145 iana_zic_data_tar_file = tzdatautil.GetIanaTarFile(iana_tools_dir, 'tzdata')
Neil Fullercdf56952018-02-20 17:12:37 +0000146 iana_zic_data_version = GetIanaVersion(iana_zic_data_tar_file)
147
Luca Stefani136579e2019-01-04 17:09:55 +0100148 print('Found IANA zic release %s/%s in %s/%s ...' \
Neil Fullered0c59b2020-05-03 21:18:13 +0100149 % (iana_zic_code_version, iana_zic_data_version, iana_zic_code_tar_file,
150 iana_zic_data_tar_file))
Neil Fullercdf56952018-02-20 17:12:37 +0000151
152 zic_build_dir = '%s/zic' % tmp_dir
153 ExtractTarFile(iana_zic_code_tar_file, zic_build_dir)
154 ExtractTarFile(iana_zic_data_tar_file, zic_build_dir)
155
156 # zic
Luca Stefani136579e2019-01-04 17:09:55 +0100157 print('Building zic...')
Neil Fullercdf56952018-02-20 17:12:37 +0000158 # VERSION_DEPS= is to stop the build process looking for files that might not
159 # be present across different versions.
160 subprocess.check_call(['make', '-C', zic_build_dir, 'zic'])
161
162 zic_binary_file = '%s/zic' % zic_build_dir
163 if not os.path.exists(zic_binary_file):
Luca Stefani136579e2019-01-04 17:09:55 +0100164 print('Could not find %s' % zic_binary_file)
Neil Fullera591fb02018-05-08 17:29:11 +0100165 sys.exit(1)
Neil Fullercdf56952018-02-20 17:12:37 +0000166 return zic_binary_file
167
168
169def BuildTzdata(zic_binary_file, extracted_iana_data_dir, iana_data_version):
Luca Stefani136579e2019-01-04 17:09:55 +0100170 print('Generating zic input file...')
Neil Fullera591fb02018-05-08 17:29:11 +0100171 zic_input_file = GenerateZicInputFile(extracted_iana_data_dir)
172
Luca Stefani136579e2019-01-04 17:09:55 +0100173 print('Calling zic...')
Neil Fuller86e72c52017-06-12 15:36:06 +0100174 zic_output_dir = '%s/data' % tmp_dir
175 os.mkdir(zic_output_dir)
Almaz Mingaleev7ce53892021-10-18 16:09:14 +0100176 zic_cmd = [zic_binary_file, '-b', 'fat', '-d', zic_output_dir, zic_input_file]
Neil Fuller86e72c52017-06-12 15:36:06 +0100177 subprocess.check_call(zic_cmd)
178
Neil Fullercdf56952018-02-20 17:12:37 +0000179 # ZoneCompactor
Neil Fullera591fb02018-05-08 17:29:11 +0100180 zone_compactor_setup_file = WriteSetupFile(zic_input_file)
Neil Fuller86e72c52017-06-12 15:36:06 +0100181
Luca Stefani136579e2019-01-04 17:09:55 +0100182 print('Calling ZoneCompactor to update tzdata to %s...' % iana_data_version)
Neil Fullerd322be62019-09-16 16:25:03 +0100183
184 tzdatautil.InvokeSoong(android_build_top, ['zone_compactor'])
Neil Fuller86e72c52017-06-12 15:36:06 +0100185
Neil Fullercdf56952018-02-20 17:12:37 +0000186 # Create args for ZoneCompactor
Neil Fullercdf56952018-02-20 17:12:37 +0000187 header_string = 'tzdata%s' % iana_data_version
Neil Fuller35467e12017-06-12 16:55:44 +0100188
Luca Stefani136579e2019-01-04 17:09:55 +0100189 print('Executing ZoneCompactor...')
Neil Fullerd322be62019-09-16 16:25:03 +0100190 command = '%s/bin/zone_compactor' % android_host_out
Neil Fuller35467e12017-06-12 16:55:44 +0100191 iana_output_data_dir = '%s/iana' % timezone_output_data_dir
Neil Fuller61b7d562020-05-21 14:43:47 +0100192 subprocess.check_call([command, zone_compactor_setup_file, zic_output_dir, iana_output_data_dir,
193 header_string])
Neil Fuller56166d32017-06-12 12:57:10 +0100194
195
Neil Fuller78031492020-08-07 21:17:58 +0100196def BuildTzlookupAndTzIds(iana_data_dir):
Neil Fuller8cec5612017-10-11 10:38:17 +0100197 countryzones_source_file = '%s/android/countryzones.txt' % timezone_input_data_dir
Neil Fuller35467e12017-06-12 16:55:44 +0100198 tzlookup_dest_file = '%s/android/tzlookup.xml' % timezone_output_data_dir
Neil Fuller78031492020-08-07 21:17:58 +0100199 tzids_dest_file = '%s/android/tzids.prototxt' % timezone_output_data_dir
Neil Fuller8cec5612017-10-11 10:38:17 +0100200
Neil Fuller78031492020-08-07 21:17:58 +0100201 print('Calling TzLookupGenerator to create tzlookup.xml / tzids.prototxt...')
Neil Fullerd322be62019-09-16 16:25:03 +0100202 tzdatautil.InvokeSoong(android_build_top, ['tzlookup_generator'])
Neil Fuller8cec5612017-10-11 10:38:17 +0100203
Neil Fuller8cec5612017-10-11 10:38:17 +0100204 zone_tab_file = '%s/zone.tab' % iana_data_dir
Neil Fullerd322be62019-09-16 16:25:03 +0100205 command = '%s/bin/tzlookup_generator' % android_host_out
Almaz Mingaleevb2090992021-12-03 10:17:52 +0000206 subprocess.check_call([command, countryzones_source_file, zone_tab_file, tzlookup_dest_file,
207 tzids_dest_file])
Neil Fuller56166d32017-06-12 12:57:10 +0100208
Neil Fuller86e72c52017-06-12 15:36:06 +0100209
Neil Fullerf85e8142019-10-28 17:21:55 +0000210def BuildTelephonylookup():
211 telephonylookup_source_file = '%s/android/telephonylookup.txt' % timezone_input_data_dir
212 telephonylookup_dest_file = '%s/android/telephonylookup.xml' % timezone_output_data_dir
213
214 print('Calling TelephonyLookupGenerator to create telephonylookup.xml...')
215 tzdatautil.InvokeSoong(android_build_top, ['telephonylookup_generator'])
216
217 command = '%s/bin/telephonylookup_generator' % android_host_out
218 subprocess.check_call([command, telephonylookup_source_file, telephonylookup_dest_file])
219
220
Almaz Mingaleev535075a2021-09-28 11:32:28 +0100221def CreateDistroFiles(iana_data_version, android_revision,
222 output_distro_dir, output_version_file):
Neil Fullerad7e81c2017-06-15 16:45:56 +0100223 create_distro_script = '%s/distro/tools/create-distro.py' % timezone_dir
224
225 tzdata_file = '%s/iana/tzdata' % timezone_output_data_dir
Almaz Mingaleeve5a0b062021-06-30 14:48:09 +0100226 icu_dir = '%s/icu_overlay' % timezone_output_data_dir
Neil Fullerad7e81c2017-06-15 16:45:56 +0100227 tzlookup_file = '%s/android/tzlookup.xml' % timezone_output_data_dir
Neil Fullerf85e8142019-10-28 17:21:55 +0000228 telephonylookup_file = '%s/android/telephonylookup.xml' % timezone_output_data_dir
Neil Fullerad7e81c2017-06-15 16:45:56 +0100229
Neil Fullere2d551b2018-10-29 15:32:06 +0000230 distro_file_pattern = '%s/*.zip' % output_distro_dir
231 existing_files = glob.glob(distro_file_pattern)
Neil Fullerad7e81c2017-06-15 16:45:56 +0100232
Luca Stefani136579e2019-01-04 17:09:55 +0100233 print('Removing %s' % existing_files)
Neil Fullerad7e81c2017-06-15 16:45:56 +0100234 for existing_file in existing_files:
235 os.remove(existing_file)
236
237 subprocess.check_call([create_distro_script,
Neil Fullercdf56952018-02-20 17:12:37 +0000238 '-iana_version', iana_data_version,
Almaz Mingaleev535075a2021-09-28 11:32:28 +0100239 '-revision', str(android_revision),
Neil Fullerad7e81c2017-06-15 16:45:56 +0100240 '-tzdata', tzdata_file,
Almaz Mingaleeve5a0b062021-06-30 14:48:09 +0100241 '-icu_dir', icu_dir,
Neil Fullerad7e81c2017-06-15 16:45:56 +0100242 '-tzlookup', tzlookup_file,
Neil Fullerf85e8142019-10-28 17:21:55 +0000243 '-telephonylookup', telephonylookup_file,
Neil Fullere2d551b2018-10-29 15:32:06 +0000244 '-output_distro_dir', output_distro_dir,
245 '-output_version_file', output_version_file])
Neil Fullerad7e81c2017-06-15 16:45:56 +0100246
Neil Fullerad3c8762017-10-20 15:58:57 +0100247def UpdateTestFiles():
248 testing_data_dir = '%s/testing/data' % timezone_dir
249 update_test_files_script = '%s/create-test-data.sh' % testing_data_dir
250 subprocess.check_call([update_test_files_script], cwd=testing_data_dir)
251
Neil Fuller86e72c52017-06-12 15:36:06 +0100252
Almaz Mingaleev535075a2021-09-28 11:32:28 +0100253# Run from any directory, with no special setup required.
254# In the rare case when tzdata has to be updated, but under the same version,
255# pass "-revision" argument.
Neil Fuller86e72c52017-06-12 15:36:06 +0100256# See http://www.iana.org/time-zones/ for more about the source of this data.
257def main():
Almaz Mingaleev535075a2021-09-28 11:32:28 +0100258 parser = argparse.ArgumentParser()
259 parser.add_argument('-revision', type=int, default=1,
260 help='The distro revision for the IANA version, default = 1')
261
262 args = parser.parse_args()
263 android_revision = args.revision
264
Luca Stefani136579e2019-01-04 17:09:55 +0100265 print('Source data file structure: %s' % timezone_input_data_dir)
266 print('Source tools file structure: %s' % timezone_input_tools_dir)
Neil Fullered0c59b2020-05-03 21:18:13 +0100267 print('Intermediate / working dir: %s' % tmp_dir)
Luca Stefani136579e2019-01-04 17:09:55 +0100268 print('Output data file structure: %s' % timezone_output_data_dir)
Neil Fuller56166d32017-06-12 12:57:10 +0100269
Neil Fuller8cec5612017-10-11 10:38:17 +0100270 iana_input_data_dir = '%s/iana' % timezone_input_data_dir
Neil Fuller2aba99c2019-02-26 13:15:34 +0000271 iana_data_tar_file = tzdatautil.GetIanaTarFile(iana_input_data_dir, 'tzdata')
Neil Fullercdf56952018-02-20 17:12:37 +0000272 iana_data_version = GetIanaVersion(iana_data_tar_file)
Luca Stefani136579e2019-01-04 17:09:55 +0100273 print('IANA time zone data release %s in %s ...' % (iana_data_version, iana_data_tar_file))
Neil Fullerfa89b032017-06-14 15:58:26 +0100274
275 icu_dir = icuutil.icuDir()
Luca Stefani136579e2019-01-04 17:09:55 +0100276 print('Found icu in %s ...' % icu_dir)
Neil Fuller86e72c52017-06-12 15:36:06 +0100277
Neil Fullercdf56952018-02-20 17:12:37 +0000278 BuildIcuData(iana_data_tar_file)
279
280 iana_tools_dir = '%s/iana' % timezone_input_tools_dir
281 zic_binary_file = BuildZic(iana_tools_dir)
282
283 iana_data_dir = '%s/iana_data' % tmp_dir
284 ExtractTarFile(iana_data_tar_file, iana_data_dir)
285 BuildTzdata(zic_binary_file, iana_data_dir, iana_data_version)
Neil Fullered0c59b2020-05-03 21:18:13 +0100286
Neil Fuller78031492020-08-07 21:17:58 +0100287 BuildTzlookupAndTzIds(iana_data_dir)
Neil Fullered0c59b2020-05-03 21:18:13 +0100288
Neil Fullerf85e8142019-10-28 17:21:55 +0000289 BuildTelephonylookup()
Neil Fullerad7e81c2017-06-15 16:45:56 +0100290
Neil Fullere2d551b2018-10-29 15:32:06 +0000291 # Create a distro file and version file from the output from prior stages.
292 output_distro_dir = '%s/distro' % timezone_output_data_dir
293 output_version_file = '%s/version/tz_version' % timezone_output_data_dir
Almaz Mingaleev535075a2021-09-28 11:32:28 +0100294 CreateDistroFiles(iana_data_version, android_revision,
295 output_distro_dir, output_version_file)
Neil Fullerad7e81c2017-06-15 16:45:56 +0100296
Neil Fullerad3c8762017-10-20 15:58:57 +0100297 # Update test versions of distro files too.
298 UpdateTestFiles()
299
Luca Stefani136579e2019-01-04 17:09:55 +0100300 print('Look in %s and %s for new files' % (timezone_output_data_dir, icu_dir))
Neil Fuller86e72c52017-06-12 15:36:06 +0100301 sys.exit(0)
302
303
304if __name__ == '__main__':
305 main()