1 #!/usr/bin/python -B 2 3 # 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.""" 18 19 import glob 20 import os 21 import re 22 import subprocess 23 import sys 24 import tarfile 25 import tempfile 26 27 sys.path.append('%s/external/icu/tools' % os.environ.get('ANDROID_BUILD_TOP')) 28 import i18nutil 29 import icuutil 30 import tzdatautil 31 32 33 # Calculate the paths that are referred to by multiple functions. 34 android_build_top = i18nutil.GetAndroidRootOrDie() 35 timezone_dir = os.path.realpath('%s/system/timezone' % android_build_top) 36 i18nutil.CheckDirExists(timezone_dir, 'system/timezone') 37 38 android_host_out = i18nutil.GetAndroidHostOutOrDie() 39 40 zone_compactor_dir = os.path.realpath('%s/system/timezone/zone_compactor' % android_build_top) 41 i18nutil.CheckDirExists(timezone_dir, 'system/timezone/zone_zompactor') 42 43 timezone_input_tools_dir = os.path.realpath('%s/input_tools' % timezone_dir) 44 timezone_input_data_dir = os.path.realpath('%s/input_data' % timezone_dir) 45 46 timezone_output_data_dir = '%s/output_data' % timezone_dir 47 i18nutil.CheckDirExists(timezone_output_data_dir, 'output_data') 48 49 tmp_dir = tempfile.mkdtemp('-tzdata') 50 51 def GenerateZicInputFile(extracted_iana_data_dir): 52 # Android APIs assume DST means "summer time" so we follow the rearguard format 53 # introduced in 2018e. 54 zic_input_file_name = 'rearguard.zi' 55 56 # 'NDATA=' is used to remove unnecessary rules files. 57 subprocess.check_call(['make', '-C', extracted_iana_data_dir, 'NDATA=', zic_input_file_name]) 58 59 zic_input_file = '%s/%s' % (extracted_iana_data_dir, zic_input_file_name) 60 if not os.path.exists(zic_input_file): 61 print 'Could not find %s' % zic_input_file 62 sys.exit(1) 63 return zic_input_file 64 65 66 def WriteSetupFile(zic_input_file): 67 """Writes the list of zones that ZoneCompactor should process.""" 68 links = [] 69 zones = [] 70 for line in open(zic_input_file): 71 fields = line.split() 72 if fields: 73 if fields[0] == 'Link': 74 links.append('%s %s %s' % (fields[0], fields[1], fields[2])) 75 zones.append(fields[2]) 76 elif fields[0] == 'Zone': 77 zones.append(fields[1]) 78 zones.sort() 79 80 zone_compactor_setup_file = '%s/setup' % tmp_dir 81 setup = open(zone_compactor_setup_file, 'w') 82 for link in sorted(set(links)): 83 setup.write('%s\n' % link) 84 for zone in sorted(set(zones)): 85 setup.write('%s\n' % zone) 86 setup.close() 87 return zone_compactor_setup_file 88 89 90 def BuildIcuData(iana_data_tar_file): 91 icu_build_dir = '%s/icu' % tmp_dir 92 93 icuutil.PrepareIcuBuild(icu_build_dir) 94 icuutil.MakeTzDataFiles(icu_build_dir, iana_data_tar_file) 95 96 # Create ICU system image files. 97 icuutil.MakeAndCopyIcuDataFiles(icu_build_dir) 98 99 # Create the ICU overlay time zone file. 100 icu_overlay_dat_file = '%s/icu_overlay/icu_tzdata.dat' % timezone_output_data_dir 101 icuutil.MakeAndCopyOverlayTzIcuData(icu_build_dir, icu_overlay_dat_file) 102 103 104 def GetIanaVersion(iana_tar_file): 105 iana_tar_filename = os.path.basename(iana_tar_file) 106 iana_version = re.search('tz(?:data|code)(.+)\\.tar\\.gz', iana_tar_filename).group(1) 107 return iana_version 108 109 110 def ExtractTarFile(tar_file, dir): 111 print 'Extracting %s...' % tar_file 112 if not os.path.exists(dir): 113 os.mkdir(dir) 114 tar = tarfile.open(tar_file, 'r') 115 tar.extractall(dir) 116 117 118 def BuildZic(iana_tools_dir): 119 iana_zic_code_tar_file = tzdatautil.GetIanaTarFile(iana_tools_dir, 'code') 120 iana_zic_code_version = GetIanaVersion(iana_zic_code_tar_file) 121 iana_zic_data_tar_file = tzdatautil.GetIanaTarFile(iana_tools_dir, 'data') 122 iana_zic_data_version = GetIanaVersion(iana_zic_data_tar_file) 123 124 print 'Found IANA zic release %s/%s in %s/%s ...' \ 125 % (iana_zic_code_version, iana_zic_data_version, iana_zic_code_tar_file, iana_zic_data_tar_file) 126 127 zic_build_dir = '%s/zic' % tmp_dir 128 ExtractTarFile(iana_zic_code_tar_file, zic_build_dir) 129 ExtractTarFile(iana_zic_data_tar_file, zic_build_dir) 130 131 # zic 132 print 'Building zic...' 133 # VERSION_DEPS= is to stop the build process looking for files that might not 134 # be present across different versions. 135 subprocess.check_call(['make', '-C', zic_build_dir, 'zic']) 136 137 zic_binary_file = '%s/zic' % zic_build_dir 138 if not os.path.exists(zic_binary_file): 139 print 'Could not find %s' % zic_binary_file 140 sys.exit(1) 141 return zic_binary_file 142 143 144 def BuildTzdata(zic_binary_file, extracted_iana_data_dir, iana_data_version): 145 print 'Generating zic input file...' 146 zic_input_file = GenerateZicInputFile(extracted_iana_data_dir) 147 148 print 'Calling zic...' 149 zic_output_dir = '%s/data' % tmp_dir 150 os.mkdir(zic_output_dir) 151 zic_cmd = [zic_binary_file, '-d', zic_output_dir, zic_input_file] 152 subprocess.check_call(zic_cmd) 153 154 # ZoneCompactor 155 zone_compactor_setup_file = WriteSetupFile(zic_input_file) 156 157 print 'Calling ZoneCompactor to update tzdata to %s...' % iana_data_version 158 subprocess.check_call(['make', '-C', android_build_top, '-j30', 'zone_compactor']) 159 160 # Create args for ZoneCompactor 161 zone_tab_file = '%s/zone.tab' % extracted_iana_data_dir 162 jar_file = '%s/framework/zone_compactor.jar' % android_host_out 163 header_string = 'tzdata%s' % iana_data_version 164 165 print 'Executing ZoneCompactor...' 166 iana_output_data_dir = '%s/iana' % timezone_output_data_dir 167 subprocess.check_call(['java', '-jar', jar_file, 168 zone_compactor_setup_file, zic_output_dir, zone_tab_file, 169 iana_output_data_dir, header_string]) 170 171 172 def BuildTzlookup(iana_data_dir): 173 countryzones_source_file = '%s/android/countryzones.txt' % timezone_input_data_dir 174 tzlookup_dest_file = '%s/android/tzlookup.xml' % timezone_output_data_dir 175 176 print 'Calling TzLookupGenerator to create tzlookup.xml...' 177 subprocess.check_call(['make', '-C', android_build_top, '-j30', 'tzlookup_generator']) 178 179 jar_file = '%s/framework/tzlookup_generator.jar' % android_host_out 180 zone_tab_file = '%s/zone.tab' % iana_data_dir 181 subprocess.check_call(['java', '-jar', jar_file, 182 countryzones_source_file, zone_tab_file, tzlookup_dest_file]) 183 184 185 def CreateDistroFiles(iana_data_version, output_dir): 186 create_distro_script = '%s/distro/tools/create-distro.py' % timezone_dir 187 188 tzdata_file = '%s/iana/tzdata' % timezone_output_data_dir 189 icu_file = '%s/icu_overlay/icu_tzdata.dat' % timezone_output_data_dir 190 tzlookup_file = '%s/android/tzlookup.xml' % timezone_output_data_dir 191 192 distro_file_pattern = '%s/*.zip' % output_dir 193 existing_distro_files = glob.glob(distro_file_pattern) 194 195 distro_file_metadata_pattern = '%s/*.txt' % output_dir 196 existing_distro_metadata_files = glob.glob(distro_file_metadata_pattern) 197 existing_files = existing_distro_files + existing_distro_metadata_files 198 199 print 'Removing %s' % existing_files 200 for existing_file in existing_files: 201 os.remove(existing_file) 202 203 subprocess.check_call([create_distro_script, 204 '-iana_version', iana_data_version, 205 '-tzdata', tzdata_file, 206 '-icu', icu_file, 207 '-tzlookup', tzlookup_file, 208 '-output', output_dir]) 209 210 def UpdateTestFiles(): 211 testing_data_dir = '%s/testing/data' % timezone_dir 212 update_test_files_script = '%s/create-test-data.sh' % testing_data_dir 213 subprocess.check_call([update_test_files_script], cwd=testing_data_dir) 214 215 216 # Run with no arguments from any directory, with no special setup required. 217 # See http://www.iana.org/time-zones/ for more about the source of this data. 218 def main(): 219 print 'Source data file structure: %s' % timezone_input_data_dir 220 print 'Source tools file structure: %s' % timezone_input_tools_dir 221 print 'Output data file structure: %s' % timezone_output_data_dir 222 223 iana_input_data_dir = '%s/iana' % timezone_input_data_dir 224 iana_data_tar_file = tzdatautil.GetIanaTarFile(iana_input_data_dir, 'data') 225 iana_data_version = GetIanaVersion(iana_data_tar_file) 226 print 'IANA time zone data release %s in %s ...' % (iana_data_version, iana_data_tar_file) 227 228 icu_dir = icuutil.icuDir() 229 print 'Found icu in %s ...' % icu_dir 230 231 BuildIcuData(iana_data_tar_file) 232 233 iana_tools_dir = '%s/iana' % timezone_input_tools_dir 234 zic_binary_file = BuildZic(iana_tools_dir) 235 236 iana_data_dir = '%s/iana_data' % tmp_dir 237 ExtractTarFile(iana_data_tar_file, iana_data_dir) 238 BuildTzdata(zic_binary_file, iana_data_dir, iana_data_version) 239 BuildTzlookup(iana_data_dir) 240 241 # Create a distro file from the output from prior stages. 242 distro_output_dir = '%s/distro' % timezone_output_data_dir 243 CreateDistroFiles(iana_data_version, distro_output_dir) 244 245 # Update test versions of distro files too. 246 UpdateTestFiles() 247 248 print 'Look in %s and %s for new files' % (timezone_output_data_dir, icu_dir) 249 sys.exit(0) 250 251 252 if __name__ == '__main__': 253 main() 254