[automerger skipped] DO NOT MERGE Create a boot-looping version of the tzdata apex. [Q] am: 69104e82ed -s ours am: 242abb6496 -s ours am: 1341191f8a -s ours am: cad31ac2c3 -s ours
am skip reason: Change-Id Ia4d0a0ff8e3d31ff49d692584d25674b9af31ebc with SHA-1 42d819d0ed is in history
Original change: https://googleplex-android-review.googlesource.com/c/platform/system/timezone/+/13229448
MUST ONLY BE SUBMITTED BY AUTOMERGER
Change-Id: If32d33490267604837f436330ebc5d83189f4bb5
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..d97975c
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,3 @@
+third_party {
+ license_type: NOTICE
+}
diff --git a/README.android b/README.android
index 490de9b..72652f4 100644
--- a/README.android
+++ b/README.android
@@ -33,9 +33,10 @@
testing/data/README for details.
tzdatacheck
- - Source code for a binary executed during boot and used to ensure that a
+ - This is for the "time zone updates via APK" feature.
+ Source code for a binary executed during boot and used to ensure that a
device doesn't boot with incompatible/outdated time zone data installed
- /data (as could happen if the device has just received an OTA upgrade).
+ in /data (as could happen if the device has just received an OTA upgrade).
It is also responsible for committing staged install/uninstalls.
@@ -142,7 +143,7 @@
Major version increment:
- A non-backwards compatible change to the tzdata or tzlookup.xml files used
- by bionic / libcore.
+ by bionic / com/android/i18n/timezone code.
- Removal of an existing file from the distro.
Minor version increment:
@@ -155,18 +156,16 @@
Changing the Time Zone Data Set Version
---------------------------------------
-1) Modify libcore/luni/src/main/java/libcore/timezone/TzDataSetVersion.java
+1) Modify android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TzDataSetVersion.java
- CURRENT_FORMAT_MAJOR_VERSION, CURRENT_FORMAT_MINOR_VERSION
2) Run update-tzdata.py to regenerate the system/timezone/output_data,
system/timezone/testing/data, external/icu runtime files and testing equivalents.
3) Build/flash a device image with the changes and run CTS tests:
atest CtsHostTzDataTests
- atest CtsLibcoreTestCases
+ atest CtsIcuTestCases
4) Run non-CTS test cases:
atest FrameworksServicesTests:com.android.server.timezone
-5) Upload, review, submit the changes from system/timezone and libcore.
- Note: you may have changes in external/icu/ but usually there is no need to update
- external/icu as only a timestamp in zoneinfo64.txt should have changed.
+5) Upload, review, submit the changes from system/timezone and external/icu/.
REMINDER: Any prebuilt apks / apex files (i.e. ones that that contain time zone files)
will also need to be regenerated after this change.
diff --git a/apex/Android.bp b/apex/Android.bp
index 1ff0743..9fece58 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -16,7 +16,7 @@
apex_defaults {
name: "com.android.tzdata-defaults",
updatable: true,
- min_sdk_version: "R",
+ min_sdk_version: "30",
// Use a custom AndroidManifest.xml used for API targeting.
androidManifest: ":com.android.tzdata-androidManifest",
diff --git a/apex/tests/Android.bp b/apex/tests/Android.bp
index d5c524b..ef35edd 100644
--- a/apex/tests/Android.bp
+++ b/apex/tests/Android.bp
@@ -28,7 +28,7 @@
// Tag this module as an mts test artifact
test_suites: [
"general-tests",
- "mts",
+ "mts-tzdata",
],
min_sdk_version: "29",
}
diff --git a/apex/tests/src/java/android/tzdata/mts/TimeZoneRulesTest.java b/apex/tests/src/java/android/tzdata/mts/TimeZoneRulesTest.java
index 0fe9f1d..96c91d3 100644
--- a/apex/tests/src/java/android/tzdata/mts/TimeZoneRulesTest.java
+++ b/apex/tests/src/java/android/tzdata/mts/TimeZoneRulesTest.java
@@ -144,7 +144,7 @@
}
}
- // Sanity check that whenever a display name is just a GMT string that it's the
+ // Confidence check that whenever a display name is just a GMT string that it's the
// right GMT string.
String gmtDst = formatGmtString(tz, true);
String gmtStd = formatGmtString(tz, false);
diff --git a/debug_tools/host/Android.bp b/debug_tools/host/Android.bp
index c07e062..d144633 100644
--- a/debug_tools/host/Android.bp
+++ b/debug_tools/host/Android.bp
@@ -18,3 +18,20 @@
static_libs: ["guava"],
}
+java_binary_host {
+ name: "zone_splitter",
+ main_class: "ZoneSplitter",
+ static_libs: ["timezone_host_debug_tools"],
+}
+
+java_binary_host {
+ name: "tz_file_dumper",
+ main_class: "TzFileDumper",
+ static_libs: ["timezone_host_debug_tools"],
+}
+
+java_binary_host {
+ name: "unique_zones_visualizer",
+ main_class: "com.android.libcore.timezone.tzlookup.zonetree.UniqueZonesVisualizer",
+ static_libs: ["tzlookup"],
+}
diff --git a/debug_tools/host/README.android b/debug_tools/host/README.android
index 7ea52ef..4926677 100644
--- a/debug_tools/host/README.android
+++ b/debug_tools/host/README.android
@@ -9,3 +9,17 @@
It also dumps human-readable CSV files of the (v1) content currently used by
Android's ZoneInfo class. These can be inspected and compared with dumps from
other tzdata files easily using your favourite text diffing tool.
+
+visualize_zone_trees.py
+ - A tool that visualizes time zones, specifically when time zones "sync up"
+ (i.e. stop being distinct). Time zone IDs are created in the tzdb whenever
+ there is a distinction between time zones, e.g. if a sub-region moved
+ from being the same as one major region to be the same as a different major
+ region, then a new time zone ID is required to represent the sub-region's
+ behavior because neither of the IDs for the major region describe the time
+ zone behavior of the sub-region throughout time. Once created, zone IDs
+ persist in the tzdb forever. Zone trees are used to determine if zone IDs
+ are still needed in order to represent relevant distinctions, e.g. does
+ Android still need to offer a choice between two zones that will be the
+ same from time X until the end of time? If the answer is no, one of the
+ zones can be used / shown to users and the other need not be.
diff --git a/debug_tools/host/dump-tzdata.py b/debug_tools/host/dump-tzdata.py
index 30cc9ac..9b36a2c 100755
--- a/debug_tools/host/dump-tzdata.py
+++ b/debug_tools/host/dump-tzdata.py
@@ -26,6 +26,9 @@
sys.path.append('%s/external/icu/tools' % os.environ.get('ANDROID_BUILD_TOP'))
import i18nutil
+sys.path.append('%s/system/timezone' % os.environ.get('ANDROID_BUILD_TOP'))
+import tzdatautil
+
# Calculate the paths that are referred to by multiple functions.
android_build_top = i18nutil.GetAndroidRootOrDie()
@@ -39,17 +42,17 @@
def BuildDebugTools():
- subprocess.check_call(['make', '-C', android_build_top, '-j30', 'timezone_host_debug_tools'])
+ tzdatautil.InvokeSoong(android_build_top, ['zone_splitter', 'tz_file_dumper'])
def SplitTzData(tzdata_file, output_dir):
- jar_file = '%s/framework/timezone_host_debug_tools.jar' % android_host_out
- subprocess.check_call(['java', '-cp', jar_file, 'ZoneSplitter', tzdata_file, output_dir])
+ jar_file = '%s/framework/zone_splitter.jar' % android_host_out
+ subprocess.check_call(['java', '-jar', jar_file, tzdata_file, output_dir])
def CreateCsvFiles(zones_dir, csvs_dir):
- jar_file = '%s/framework/timezone_host_debug_tools.jar' % android_host_out
- subprocess.check_call(['java', '-cp', jar_file, 'TzFileDumper', zones_dir, csvs_dir])
+ jar_file = '%s/framework/tz_file_dumper.jar' % android_host_out
+ subprocess.check_call(['java', '-jar', jar_file, zones_dir, csvs_dir])
def CheckFileExists(file, filename):
diff --git a/debug_tools/host/main/java/TzFileDumper.java b/debug_tools/host/main/java/TzFileDumper.java
index d06a66c..5979328 100644
--- a/debug_tools/host/main/java/TzFileDumper.java
+++ b/debug_tools/host/main/java/TzFileDumper.java
@@ -285,8 +285,8 @@
Object[] row = new Object[] {
type.gmtOffsetSeconds,
type.isDst,
- type.ttisgmt,
- type.ttisstd,
+ nullToEmptyString(type.ttisgmt),
+ nullToEmptyString(type.ttisstd),
formatDurationSeconds(type.gmtOffsetSeconds),
formatIsDst(type.isDst),
};
@@ -299,6 +299,10 @@
"[gmtOffset ISO]", "[DST?]");
}
+ private static Object nullToEmptyString(Object object) {
+ return object == null ? "" : object;
+ }
+
private static void fillIntArray(MappedByteBuffer mappedByteBuffer, int[] toFill) {
for (int i = 0; i < toFill.length; i++) {
toFill[i] = mappedByteBuffer.getInt();
diff --git a/debug_tools/host/main/java/ZoneSplitter.java b/debug_tools/host/main/java/ZoneSplitter.java
index a8b72e6..3840df6 100644
--- a/debug_tools/host/main/java/ZoneSplitter.java
+++ b/debug_tools/host/main/java/ZoneSplitter.java
@@ -62,7 +62,7 @@
// byte[12] tzdata_version -- "tzdata2012f\0"
// int index_offset
// int data_offset
- // int zonetab_offset
+ // int final_offset
writeVersionFile(mappedFile, outputDir);
final int fileSize = (int) tzData.length();
@@ -70,12 +70,13 @@
validateOffset(index_offset, fileSize);
int data_offset = mappedFile.getInt();
validateOffset(data_offset, fileSize);
- int zonetab_offset = mappedFile.getInt();
- validateOffset(zonetab_offset, fileSize);
+ int final_offset = mappedFile.getInt();
- if (index_offset >= data_offset || data_offset >= zonetab_offset) {
+ if (index_offset >= data_offset
+ || data_offset >= final_offset
+ || final_offset > fileSize) {
throw new IOException("Invalid offset: index_offset=" + index_offset
- + ", data_offset=" + data_offset + ", zonetab_offset=" + zonetab_offset
+ + ", data_offset=" + data_offset + ", final_offset=" + final_offset
+ ", fileSize=" + fileSize);
}
@@ -83,7 +84,12 @@
zicFilesDir.mkdir();
extractZicFiles(mappedFile, index_offset, data_offset, zicFilesDir);
- writeZoneTabFile(mappedFile, zonetab_offset, fileSize - zonetab_offset, outputDir);
+ if (final_offset != fileSize) {
+ // This isn't an error, but it's worth noting: it suggests the file may be in a newer
+ // format than the current branch.
+ System.out.println(
+ "final_offset (" + final_offset + ") != fileSize (" + fileSize + ")");
+ }
}
static MappedByteBuffer createMappedByteBuffer(File tzData) throws IOException {
@@ -174,7 +180,8 @@
if (ids[i].compareTo(ids[i - 1]) <= 0) {
throw new IOException(
"Index not sorted or contains multiple entries with the same ID"
- + ", index=" + i + ", ids[i]=" + ids[i] + ", ids[i - 1]=" + ids[i - 1]);
+ + ", index=" + i + ", ids[i]=" + ids[i]
+ + ", ids[i - 1]=" + ids[i - 1]);
}
}
}
@@ -192,14 +199,6 @@
}
}
- private static void writeZoneTabFile(MappedByteBuffer mappedFile,
- int zoneTabOffset, int zoneTabSize, File outputDir) throws IOException {
- byte[] bytes = new byte[zoneTabSize];
- mappedFile.position(zoneTabOffset);
- mappedFile.get(bytes, 0, bytes.length);
- writeBytesToFile(new File(outputDir, "zone.tab"), bytes);
- }
-
private static void writeStringUtf8ToFile(File file, String string) throws IOException {
writeBytesToFile(file, string.getBytes(StandardCharsets.UTF_8));
}
diff --git a/debug_tools/host/visualize_zone_trees.py b/debug_tools/host/visualize_zone_trees.py
new file mode 100755
index 0000000..bd6654d
--- /dev/null
+++ b/debug_tools/host/visualize_zone_trees.py
@@ -0,0 +1,92 @@
+#!/usr/bin/python -B
+
+# Copyright 2020 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.
+
+"""Allows visualization of zone trees (the thing that works out if zones are distinct)."""
+
+from __future__ import print_function
+
+import argparse
+import glob
+import os
+import subprocess
+import sys
+
+sys.path.append('%s/external/icu/tools' % os.environ.get('ANDROID_BUILD_TOP'))
+import i18nutil
+
+sys.path.append('%s/system/timezone' % os.environ.get('ANDROID_BUILD_TOP'))
+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()
+
+debug_tools_dir = os.path.realpath('%s/system/timezone/debug_tools/host' % android_build_top)
+i18nutil.CheckDirExists(debug_tools_dir, 'system/timezone/debug_tools/host')
+
+
+def BuildAndRunTool(country_zones_txt, country_code, output_dir):
+ tzdatautil.InvokeSoong(android_build_top, ['unique_zones_visualizer'])
+ jar_file = '%s/framework/unique_zones_visualizer.jar' % android_host_out
+ subprocess.check_call(['java', '-jar', jar_file, country_zones_txt, country_code, output_dir])
+
+def CreatePngs(output_dir):
+ gv_files = glob.glob('%s/*.gv' % output_dir)
+ for gv_file in gv_files:
+ png_file = gv_file.replace('.gv', '.png')
+ print('Generating %s...' % png_file)
+ with open(png_file, 'w') as out_file:
+ subprocess.check_call(['dot', '-Tpng', gv_file], stdout=out_file)
+
+def CheckFileExists(file, filename):
+ if not os.path.isfile(file):
+ print("Couldn't find %s (%s)!" % (filename, file))
+ sys.exit(1)
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument('-input', required=True,
+ help='The country_zones.txt file to process')
+ parser.add_argument('-country_code', required=True,
+ help='The country code (e.g. "us") to process')
+ parser.add_argument('-output', required=True,
+ help='The output directory for the dump')
+ args = parser.parse_args()
+
+ country_zones_txt = args.input
+ country_code = args.country_code
+ output_dir = args.output
+
+ CheckFileExists(country_zones_txt, '-input')
+ if not os.path.exists(output_dir):
+ print('Creating dir: %s' % output_dir)
+ os.mkdir(output_dir)
+ i18nutil.CheckDirExists(output_dir, '-output')
+
+ BuildAndRunTool(country_zones_txt, country_code, output_dir)
+ CreatePngs(output_dir)
+
+ print('Look in %s for all generated files' % output_dir)
+ sys.exit(0)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/distro/core/src/test/com/android/timezone/distro/TimeZoneDistroTest.java b/distro/core/src/test/com/android/timezone/distro/TimeZoneDistroTest.java
index ac6d825..f36b615 100644
--- a/distro/core/src/test/com/android/timezone/distro/TimeZoneDistroTest.java
+++ b/distro/core/src/test/com/android/timezone/distro/TimeZoneDistroTest.java
@@ -15,10 +15,8 @@
*/
package com.android.timezone.distro;
-import junit.framework.TestCase;
-
+import com.android.i18n.timezone.TzDataSetVersion;
import libcore.testing.io.TestIoUtils;
-import libcore.timezone.TzDataSetVersion;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -32,6 +30,7 @@
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
+import junit.framework.TestCase;
/**
* Tests for {@link TimeZoneDistro}.
diff --git a/distro/installer/src/main/com/android/timezone/distro/installer/TimeZoneDistroInstaller.java b/distro/installer/src/main/com/android/timezone/distro/installer/TimeZoneDistroInstaller.java
index aa3b377..dbeeb07 100644
--- a/distro/installer/src/main/com/android/timezone/distro/installer/TimeZoneDistroInstaller.java
+++ b/distro/installer/src/main/com/android/timezone/distro/installer/TimeZoneDistroInstaller.java
@@ -15,6 +15,9 @@
*/
package com.android.timezone.distro.installer;
+import com.android.i18n.timezone.TzDataSetVersion;
+import com.android.i18n.timezone.TzDataSetVersion.TzDataSetException;
+import com.android.i18n.timezone.ZoneInfoDb;
import com.android.timezone.distro.DistroException;
import com.android.timezone.distro.DistroVersion;
import com.android.timezone.distro.FileUtils;
@@ -30,11 +33,8 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import libcore.timezone.TelephonyLookup;
-import libcore.timezone.TzDataSetVersion;
-import libcore.timezone.TzDataSetVersion.TzDataSetException;
-import libcore.timezone.TimeZoneFinder;
-import libcore.timezone.ZoneInfoDb;
+import com.android.i18n.timezone.TelephonyLookup;
+import com.android.i18n.timezone.TimeZoneFinder;
/**
* A distro-validation / extraction class. Separate from the services code that uses it for easier
@@ -209,18 +209,11 @@
// Validate the tzdata file.
File zoneInfoFile = new File(workingDir, TimeZoneDistro.TZDATA_FILE_NAME);
- ZoneInfoDb tzData = ZoneInfoDb.loadTzData(zoneInfoFile.getPath());
- if (tzData == null) {
- Slog.i(logTag, "Update not applied: " + zoneInfoFile + " could not be loaded");
- return INSTALL_FAIL_VALIDATION_ERROR;
- }
try {
- tzData.validate();
+ ZoneInfoDb.validateTzData(zoneInfoFile.getPath());
} catch (IOException e) {
Slog.i(logTag, "Update not applied: " + zoneInfoFile + " failed validation", e);
return INSTALL_FAIL_VALIDATION_ERROR;
- } finally {
- tzData.close();
}
// Validate the tzlookup.xml file.
diff --git a/distro/installer/src/test/com/android/timezone/distro/installer/TimeZoneDistroInstallerTest.java b/distro/installer/src/test/com/android/timezone/distro/installer/TimeZoneDistroInstallerTest.java
index 70ccb6d..770e384 100644
--- a/distro/installer/src/test/com/android/timezone/distro/installer/TimeZoneDistroInstallerTest.java
+++ b/distro/installer/src/test/com/android/timezone/distro/installer/TimeZoneDistroInstallerTest.java
@@ -15,6 +15,7 @@
*/
package com.android.timezone.distro.installer;
+import com.android.i18n.timezone.TzDataSetVersion;
import com.android.timezone.distro.DistroVersion;
import com.android.timezone.distro.FileUtils;
import com.android.timezone.distro.StagedDistroOperation;
@@ -38,7 +39,6 @@
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import libcore.io.IoUtils;
-import libcore.timezone.TzDataSetVersion;
import libcore.timezone.testing.ZoneInfoTestHelper;
import static org.junit.Assert.assertArrayEquals;
diff --git a/distro/tools/src/main/com/android/timezone/distro/tools/CreateTimeZoneDistro.java b/distro/tools/src/main/com/android/timezone/distro/tools/CreateTimeZoneDistro.java
index e518437..e91acc1 100644
--- a/distro/tools/src/main/com/android/timezone/distro/tools/CreateTimeZoneDistro.java
+++ b/distro/tools/src/main/com/android/timezone/distro/tools/CreateTimeZoneDistro.java
@@ -15,7 +15,7 @@
*/
package com.android.timezone.distro.tools;
-import libcore.timezone.TzDataSetVersion;
+import com.android.i18n.timezone.TzDataSetVersion;
import com.android.timezone.distro.DistroVersion;
import com.android.timezone.distro.TimeZoneDistro;
diff --git a/input_data/android/countryzones.txt b/input_data/android/countryzones.txt
index 8491e38..f23401e 100644
--- a/input_data/android/countryzones.txt
+++ b/input_data/android/countryzones.txt
@@ -69,10 +69,11 @@
# timeZoneMappings:
# Time zones associated with the country and associated metadata.
#
-# The ordering of TimeZoneMapping elements is important because it influence
+# The ordering of TimeZoneMapping elements is important because it influences
# the order that time zones in a country are considered when the device is
-# trying to detect a time zone and has a known local time, offset from UTC,
-# and whether the local zone is currently observing DST.
+# trying to detect a time zone from incomplete / ambiguous information, i.e.
+# when the device doesn't know time zone ID but knows local time, offset from
+# UTC, and whether the local zone is currently observing DST.
#
# There will often be several time zones within a country that could match
# at any given instant, but the first zone that matches a user's country,
@@ -80,18 +81,36 @@
# that the time zones with the highest likelihood of matching the user's
# needs are first.
#
-# For example, a sensible ordering for TimeZoneMapping entries for a country
-# would be by population of users that observe the offset/DST to maximize
-# the probability of matching the truth for an arbitrary user in that country.
+# For example, a sensible ordering for TimeZoneMapping entries within a
+# country's data for a given set of criteria would be by the population of
+# users that observe the offset/DST. Matching the entries with the highest
+# population first maximizes the probability of getting the right answer for
+# an arbitrarily chosen user in that country.
#
-# Currently the TimeZoneMapping entries are mostly ordered by raw (non-DST)
-# offset and then an ill-defined "desirability". This ordering is an historical
-# artifact that is likely to change in future.
+# The TimeZoneMapping entries for a country are primarily ordered by raw
+# (non-DST) offset.
+#
+# Generally, the secondary ordering is such that "in use" zones for a country
+# appear before legacy zones, and then legacy zones appear in reverse priority
+# order (see priority below). This is for easier maintenance and validation
+# of the generated tzlookup.xml, i.e. it helps if an unused mapping always
+# references a zone declared above it in a file, and if all zones that are have
+# been superseded by the same zone are listed together in reverse priority
+# order.
+#
# TimeZoneMapping:
#
# id:
-# The ID of the time zone.
+# The ID of the time zone to use on device. See also aliasId.
+#
+# aliasId:
+# (Optional) Used to identify the modern Olson ID when the id property is
+# using an obsoleted time zone ID. A legacy time zone ID may be used on device
+# to avoid problems if the zone ID is widely used. This is intentionally
+# explicit to make it clear the use of an old ID is intentional rather than an
+# accident. The id must also link to the aliasId in IANA's data (see the IANA
+# "backward" file).
#
# utcOffset:
# The expected non-DST offset for the time zone. Used as a form of
@@ -115,7 +134,8 @@
# where it makes sense. Different criteria can be used for different
# countries but within a country it should be consistent.
# Often the exact priority assigned doesn't matter and zone priorities are
-# only used when there is otherwise a tie. See comments below for details.
+# only used when there is otherwise a tie. See comments for each country below
+# for details.
# ANDORRA
countries:<
@@ -276,15 +296,9 @@
>
timeZoneMappings:<
utcOffset:"-3:00"
- id:"America/Argentina/Salta"
- # 1.21M
- priority:1210
- >
- timeZoneMappings:<
- utcOffset:"-3:00"
- id:"America/Argentina/Jujuy"
- # 673K
- priority:673
+ id:"America/Argentina/Mendoza"
+ # 1.74M
+ priority:1740
>
timeZoneMappings:<
utcOffset:"-3:00"
@@ -294,6 +308,24 @@
>
timeZoneMappings:<
utcOffset:"-3:00"
+ id:"America/Argentina/Salta"
+ # 1.21M
+ priority:1210
+ >
+ timeZoneMappings:<
+ utcOffset:"-3:00"
+ id:"America/Argentina/San_Juan"
+ # 681K
+ priority:681
+ >
+ timeZoneMappings:<
+ utcOffset:"-3:00"
+ id:"America/Argentina/Jujuy"
+ # 673K
+ priority:673
+ >
+ timeZoneMappings:<
+ utcOffset:"-3:00"
id:"America/Argentina/Catamarca"
# 368K
priority:368
@@ -306,24 +338,6 @@
>
timeZoneMappings:<
utcOffset:"-3:00"
- id:"America/Argentina/San_Juan"
- # 681K
- priority:681
- >
- timeZoneMappings:<
- utcOffset:"-3:00"
- id:"America/Argentina/Mendoza"
- # 1.74M
- priority:1740
- >
- timeZoneMappings:<
- utcOffset:"-3:00"
- id:"America/Argentina/San_Luis"
- # 432K
- priority:432
- >
- timeZoneMappings:<
- utcOffset:"-3:00"
id:"America/Argentina/Rio_Gallegos"
# 247K (Santa Cruz Province)
priority:247
@@ -334,6 +348,12 @@
# 127K (Tierra del Fuego)
priority:127
>
+ timeZoneMappings:<
+ utcOffset:"-3:00"
+ id:"America/Argentina/San_Luis"
+ # 432K
+ priority:432
+ >
>
# AMERICAN SAMOA
@@ -376,12 +396,6 @@
>
timeZoneMappings:<
utcOffset:"10:00"
- id:"Australia/Brisbane"
- # 2.36M
- priority:2360
- >
- timeZoneMappings:<
- utcOffset:"10:00"
id:"Australia/Hobart"
# 224K
priority:224
@@ -394,6 +408,12 @@
>
timeZoneMappings:<
utcOffset:"10:00"
+ id:"Australia/Brisbane"
+ # 2.36M
+ priority:2360
+ >
+ timeZoneMappings:<
+ utcOffset:"10:00"
id:"Australia/Lindeman"
# Unknown
priority:1
@@ -623,36 +643,6 @@
>
timeZoneMappings:<
utcOffset:"-3:00"
- id:"America/Belem"
- # City: 1.44M, State: 7.78M
- priority:7780
- >
- timeZoneMappings:<
- utcOffset:"-3:00"
- id:"America/Fortaleza"
- # City: 2.61M, State: 8.61M
- priority:8610
- >
- timeZoneMappings:<
- utcOffset:"-3:00"
- id:"America/Recife"
- # City: 1.63M, State: 8.93M
- priority:8930
- >
- timeZoneMappings:<
- utcOffset:"-3:00"
- id:"America/Araguaina"
- # City: 173K, State: 1.50M
- priority:1500
- >
- timeZoneMappings:<
- utcOffset:"-3:00"
- id:"America/Maceio"
- # City: 1.02M, State:3.17M
- priority:3170
- >
- timeZoneMappings:<
- utcOffset:"-3:00"
id:"America/Bahia"
# City (Salvador): 2.94M, State: 15.2M
priority:15200
@@ -663,6 +653,36 @@
# City: 294K, State:7.79M (Same state as Belem)
priority:7789
>
+ timeZoneMappings:<
+ utcOffset:"-3:00"
+ id:"America/Recife"
+ # City: 1.63M, State: 8.93M
+ priority:8930
+ >
+ timeZoneMappings:<
+ utcOffset:"-3:00"
+ id:"America/Fortaleza"
+ # City: 2.61M, State: 8.61M
+ priority:8610
+ >
+ timeZoneMappings:<
+ utcOffset:"-3:00"
+ id:"America/Belem"
+ # City: 1.44M, State: 7.78M
+ priority:7780
+ >
+ timeZoneMappings:<
+ utcOffset:"-3:00"
+ id:"America/Maceio"
+ # City: 1.02M, State:3.17M
+ priority:3170
+ >
+ timeZoneMappings:<
+ utcOffset:"-3:00"
+ id:"America/Araguaina"
+ # City: 173K, State: 1.50M
+ priority:1500
+ >
timeZoneMappings:<
utcOffset:"-4:00"
@@ -672,18 +692,18 @@
>
timeZoneMappings:<
utcOffset:"-4:00"
- id:"America/Campo_Grande"
- # City: 854K, State: 2.51M
- priority:2510
- >
- timeZoneMappings:<
- utcOffset:"-4:00"
id:"America/Cuiaba"
# City: 585K, State: 3.34M
priority:3340
>
timeZoneMappings:<
utcOffset:"-4:00"
+ id:"America/Campo_Grande"
+ # City: 854K, State: 2.51M
+ priority:2510
+ >
+ timeZoneMappings:<
+ utcOffset:"-4:00"
id:"America/Porto_Velho"
# City: 511K, State: 1.59M
priority:1590
@@ -697,18 +717,18 @@
timeZoneMappings:<
utcOffset:"-5:00"
+ id:"America/Rio_Branco"
+ # City: 320K, State: 759K
+ priority:759
+ >
+ timeZoneMappings:<
+ utcOffset:"-5:00"
id:"America/Eirunepe"
# City: 30.9K, State: 4.00M
# This is the same state as Manaus, most of which uses a different zone, so the
# priority is set to let Rio_Branco "win".
priority:758
>
- timeZoneMappings:<
- utcOffset:"-5:00"
- id:"America/Rio_Branco"
- # City: 320K, State: 759K
- priority:759
- >
>
# BAHAMAS
@@ -809,18 +829,18 @@
timeZoneMappings:<
utcOffset:"-4:00"
- id:"America/Glace_Bay"
- # 19.0K
- priority:19
- >
- timeZoneMappings:<
- utcOffset:"-4:00"
id:"America/Moncton"
# 71.9K
priority:72
>
timeZoneMappings:<
utcOffset:"-4:00"
+ id:"America/Glace_Bay"
+ # 19.0K
+ priority:19
+ >
+ timeZoneMappings:<
+ utcOffset:"-4:00"
id:"America/Goose_Bay"
# 8.12K
priority:8
@@ -834,12 +854,6 @@
timeZoneMappings:<
utcOffset:"-5:00"
- id:"America/Nipigon"
- # 1.63K
- priority:1
- >
- timeZoneMappings:<
- utcOffset:"-5:00"
id:"America/Thunder_Bay"
# 108K
priority:108
@@ -852,6 +866,12 @@
>
timeZoneMappings:<
utcOffset:"-5:00"
+ id:"America/Nipigon"
+ # 1.63K
+ priority:1
+ >
+ timeZoneMappings:<
+ utcOffset:"-5:00"
id:"America/Pangnirtung"
# 1.48K
priority:1
@@ -871,6 +891,12 @@
>
timeZoneMappings:<
utcOffset:"-6:00"
+ id:"America/Swift_Current"
+ # 16.6K
+ priority:16
+ >
+ timeZoneMappings:<
+ utcOffset:"-6:00"
id:"America/Rankin_Inlet"
# 2.84K
priority:3
@@ -883,12 +909,6 @@
>
timeZoneMappings:<
utcOffset:"-6:00"
- id:"America/Swift_Current"
- # 16.6K
- priority:16
- >
- timeZoneMappings:<
- utcOffset:"-6:00"
id:"America/Resolute"
# 0.20K
priority:1
@@ -896,30 +916,12 @@
timeZoneMappings:<
utcOffset:"-7:00"
- id:"America/Cambridge_Bay"
- # 1.77K
- priority:2
- >
- timeZoneMappings:<
- utcOffset:"-7:00"
id:"America/Yellowknife"
# 19.6K
priority:20
>
timeZoneMappings:<
utcOffset:"-7:00"
- id:"America/Inuvik"
- # 3.24K
- priority:3
- >
- timeZoneMappings:<
- utcOffset:"-7:00"
- id:"America/Whitehorse"
- # 25.1K
- priority:251
- >
- timeZoneMappings:<
- utcOffset:"-7:00"
id:"America/Dawson_Creek"
# 13.0K
priority:13
@@ -938,10 +940,28 @@
>
timeZoneMappings:<
utcOffset:"-7:00"
+ id:"America/Inuvik"
+ # 3.24K
+ priority:3
+ >
+ timeZoneMappings:<
+ utcOffset:"-7:00"
+ id:"America/Cambridge_Bay"
+ # 1.77K
+ priority:2
+ >
+ timeZoneMappings:<
+ utcOffset:"-7:00"
id:"America/Dawson"
# 1.38K
priority:1
>
+ timeZoneMappings:<
+ utcOffset:"-7:00"
+ id:"America/Whitehorse"
+ # 25.1K
+ priority:251
+ >
>
# COCOS (KEELING) ISLANDS
@@ -1861,12 +1881,13 @@
timeZoneMappings:<
utcOffset:"5:00"
- id:"Asia/Aqtau"
+ id:"Asia/Oral"
+ priority:2
>
timeZoneMappings:<
utcOffset:"5:00"
- id:"Asia/Oral"
- priority:2
+ id:"Asia/Aqtau"
+ priority:1
>
timeZoneMappings:<
utcOffset:"5:00"
@@ -2212,16 +2233,16 @@
>
timeZoneMappings:<
utcOffset:"-6:00"
- id:"America/Matamoros"
- # 520K
- priority:520
- >
- timeZoneMappings:<
- utcOffset:"-6:00"
id:"America/Bahia_Banderas"
# 83.7K
priority:84
>
+ timeZoneMappings:<
+ utcOffset:"-6:00"
+ id:"America/Matamoros"
+ # 520K
+ priority:520
+ >
timeZoneMappings:<
utcOffset:"-5:00"
@@ -2238,18 +2259,18 @@
>
timeZoneMappings:<
utcOffset:"-7:00"
- id:"America/Hermosillo"
- # 812K
- priority:812
- >
- timeZoneMappings:<
- utcOffset:"-7:00"
id:"America/Mazatlan"
# 658K
priority:658
>
timeZoneMappings:<
utcOffset:"-7:00"
+ id:"America/Hermosillo"
+ # 812K
+ priority:812
+ >
+ timeZoneMappings:<
+ utcOffset:"-7:00"
id:"America/Ojinaga"
# 28.0K
priority:28
@@ -2538,16 +2559,16 @@
defaultTimeZoneId:"Asia/Gaza"
timeZoneMappings:<
utcOffset:"2:00"
- id:"Asia/Gaza"
- # Territory: 1.85M
- priority:1850
- >
- timeZoneMappings:<
- utcOffset:"2:00"
id:"Asia/Hebron"
# Territory: 3.28M
priority:3280
>
+ timeZoneMappings:<
+ utcOffset:"2:00"
+ id:"Asia/Gaza"
+ # Territory: 1.85M
+ priority:1850
+ >
>
# PORTUGAL
@@ -2687,18 +2708,18 @@
timeZoneMappings:<
utcOffset:"9:00"
- id:"Asia/Yakutsk"
- # 959K (Sakha Republic)
- priority:959
- >
- timeZoneMappings:<
- utcOffset:"9:00"
id:"Asia/Chita"
# 1.11M
priority:1110
>
timeZoneMappings:<
utcOffset:"9:00"
+ id:"Asia/Yakutsk"
+ # 959K (Sakha Republic)
+ priority:959
+ >
+ timeZoneMappings:<
+ utcOffset:"9:00"
id:"Asia/Khandyga"
# 959K (Sakha Republic). Priority set just below Yakutsk.
priority:958
@@ -2719,6 +2740,12 @@
>
timeZoneMappings:<
utcOffset:"7:00"
+ id:"Asia/Novokuznetsk"
+ # 2.76M
+ priority:2760
+ >
+ timeZoneMappings:<
+ utcOffset:"7:00"
id:"Asia/Novosibirsk"
# 2.67M
priority:2670
@@ -2731,12 +2758,6 @@
>
timeZoneMappings:<
utcOffset:"7:00"
- id:"Asia/Novokuznetsk"
- # 2.76M
- priority:2760
- >
- timeZoneMappings:<
- utcOffset:"7:00"
id:"Asia/Tomsk"
# 1.05M
priority:1050
@@ -2764,9 +2785,9 @@
>
timeZoneMappings:<
utcOffset:"4:00"
- id:"Europe/Astrakhan"
- # 1.01M
- priority:1010
+ id:"Europe/Saratov"
+ # 2.52M
+ priority:2520
>
timeZoneMappings:<
utcOffset:"4:00"
@@ -2776,9 +2797,9 @@
>
timeZoneMappings:<
utcOffset:"4:00"
- id:"Europe/Saratov"
- # 2.52M
- priority:2520
+ id:"Europe/Astrakhan"
+ # 1.01M
+ priority:1010
>
timeZoneMappings:<
utcOffset:"4:00"
@@ -3163,13 +3184,13 @@
>
timeZoneMappings:<
utcOffset:"2:00"
- id:"Europe/Uzhgorod"
- priority:1
+ id:"Europe/Zaporozhye"
+ priority:2
>
timeZoneMappings:<
utcOffset:"2:00"
- id:"Europe/Zaporozhye"
- priority:2
+ id:"Europe/Uzhgorod"
+ priority:1
>
timeZoneMappings:<
utcOffset:"3:00"
@@ -3223,21 +3244,15 @@
>
timeZoneMappings:<
utcOffset:"-5:00"
- id:"America/Detroit"
- # 673K
- priority:673
- >
- timeZoneMappings:<
- utcOffset:"-5:00"
id:"America/Kentucky/Louisville"
# 760K
priority:760
>
timeZoneMappings:<
utcOffset:"-5:00"
- id:"America/Kentucky/Monticello"
- # 6.19K
- priority:6
+ id:"America/Detroit"
+ # 673K
+ priority:673
>
timeZoneMappings:<
utcOffset:"-5:00"
@@ -3253,15 +3268,9 @@
>
timeZoneMappings:<
utcOffset:"-5:00"
- id:"America/Indiana/Winamac"
- # 2.49K
- priority:2
- >
- timeZoneMappings:<
- utcOffset:"-5:00"
- id:"America/Indiana/Marengo"
- # 0.83K
- priority:1
+ id:"America/Kentucky/Monticello"
+ # 6.19K
+ priority:6
>
timeZoneMappings:<
utcOffset:"-5:00"
@@ -3271,10 +3280,22 @@
>
timeZoneMappings:<
utcOffset:"-5:00"
+ id:"America/Indiana/Winamac"
+ # 2.49K
+ priority:2
+ >
+ timeZoneMappings:<
+ utcOffset:"-5:00"
id:"America/Indiana/Vevay"
# 1.68K
priority:2
>
+ timeZoneMappings:<
+ utcOffset:"-5:00"
+ id:"America/Indiana/Marengo"
+ # 0.83K
+ priority:1
+ >
timeZoneMappings:<
utcOffset:"-6:00"
@@ -3284,40 +3305,40 @@
>
timeZoneMappings:<
utcOffset:"-6:00"
- id:"America/Indiana/Knox"
- # 3.70K
- priority:4
- >
- timeZoneMappings:<
- utcOffset:"-6:00"
id:"America/Menominee"
# 8.60K
priority:9
>
timeZoneMappings:<
utcOffset:"-6:00"
- id:"America/North_Dakota/Center"
- # 0.57K
- priority:1
- >
- timeZoneMappings:<
- utcOffset:"-6:00"
- id:"America/North_Dakota/New_Salem"
- # 0.95K
- priority:1
- >
- timeZoneMappings:<
- utcOffset:"-6:00"
id:"America/Indiana/Tell_City"
# 7.27K
priority:7
>
timeZoneMappings:<
utcOffset:"-6:00"
+ id:"America/Indiana/Knox"
+ # 3.70K
+ priority:4
+ >
+ timeZoneMappings:<
+ utcOffset:"-6:00"
id:"America/North_Dakota/Beulah"
# 3.12K
priority:3
>
+ timeZoneMappings:<
+ utcOffset:"-6:00"
+ id:"America/North_Dakota/New_Salem"
+ # 0.95K
+ priority:1
+ >
+ timeZoneMappings:<
+ utcOffset:"-6:00"
+ id:"America/North_Dakota/Center"
+ # 0.57K
+ priority:1
+ >
timeZoneMappings:<
utcOffset:"-7:00"
@@ -3327,16 +3348,16 @@
>
timeZoneMappings:<
utcOffset:"-7:00"
- id:"America/Boise"
- # 206K
- priority:206
- >
- timeZoneMappings:<
- utcOffset:"-7:00"
id:"America/Phoenix"
# 1.45M
priority:1450
>
+ timeZoneMappings:<
+ utcOffset:"-7:00"
+ id:"America/Boise"
+ # 206K
+ priority:206
+ >
timeZoneMappings:<
utcOffset:"-8:00"
@@ -3359,9 +3380,9 @@
>
timeZoneMappings:<
utcOffset:"-9:00"
- id:"America/Yakutat"
- # 0.66K
- priority:1
+ id:"America/Sitka"
+ # 8.88K
+ priority:9
>
timeZoneMappings:<
utcOffset:"-9:00"
@@ -3377,9 +3398,9 @@
>
timeZoneMappings:<
utcOffset:"-9:00"
- id:"America/Sitka"
- # 8.88K
- priority:9
+ id:"America/Yakutat"
+ # 0.66K
+ priority:1
>
timeZoneMappings:<
diff --git a/input_tools/android/common/Android.bp b/input_tools/android/common/Android.bp
index 4baf656..2ecd9b8 100644
--- a/input_tools/android/common/Android.bp
+++ b/input_tools/android/common/Android.bp
@@ -40,4 +40,5 @@
"tztools_common",
"tztools_common_testing",
],
+ test_suites: ["general-tests"],
}
diff --git a/input_tools/android/common/TEST_MAPPING b/input_tools/android/common/TEST_MAPPING
new file mode 100644
index 0000000..042a54f
--- /dev/null
+++ b/input_tools/android/common/TEST_MAPPING
@@ -0,0 +1,8 @@
+{
+ "presubmit": [
+ {
+ "name" : "tztools_common_tests",
+ "host" : true
+ }
+ ]
+}
diff --git a/input_tools/android/common/src/main/java/com/android/libcore/timezone/util/Errors.java b/input_tools/android/common/src/main/java/com/android/libcore/timezone/util/Errors.java
index 24c608c..e1e48f9 100644
--- a/input_tools/android/common/src/main/java/com/android/libcore/timezone/util/Errors.java
+++ b/input_tools/android/common/src/main/java/com/android/libcore/timezone/util/Errors.java
@@ -16,12 +16,17 @@
package com.android.libcore.timezone.util;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
/**
- * Stores context, errors and error severity for logging and flow control.
+ * Stores context, errors and error severity for logging and flow control. This class distinguishes
+ * between warnings (just info), errors (may not be immediately fatal) and fatal (immediately
+ * fatal).
*/
public final class Errors {
@@ -45,19 +50,29 @@
return scopes.removeLast();
}
- public void addFatal(String msg) {
- level = Math.max(level, LEVEL_FATAL);
- add(msg);
+ /** Adds a fatal error, and immediately throws {@link HaltExecutionException}. */
+ public HaltExecutionException addFatalAndHalt(String msg) throws HaltExecutionException {
+ addInternal(msg, null, LEVEL_FATAL);
+ throw new HaltExecutionException("Fatal error");
+ }
+
+ /** Adds a fatal error, and immediately throws {@link HaltExecutionException}. */
+ public HaltExecutionException addFatalAndHalt(String msg, Throwable t)
+ throws HaltExecutionException {
+ addInternal(msg, t, LEVEL_FATAL);
+ throw new HaltExecutionException("Fatal error");
}
public void addError(String msg) {
- level = Math.max(level, LEVEL_ERROR);
- add(msg);
+ addInternal(msg, null, LEVEL_ERROR);
+ }
+
+ public void addError(String msg, Throwable t) {
+ addInternal(msg, t, LEVEL_ERROR);
}
public void addWarning(String msg) {
- level = Math.max(level, LEVEL_WARNING);
- add(msg);
+ addInternal(msg, null, LEVEL_WARNING);
}
public String asString() {
@@ -73,15 +88,46 @@
return messages.isEmpty();
}
+ /** True if there are error or fatal messages. */
public boolean hasError() {
return level >= LEVEL_ERROR;
}
+ /** True if there are fatal messages. */
public boolean hasFatal() {
return level >= LEVEL_FATAL;
}
- private void add(String msg) {
+ private void addInternal(String msg, Throwable t, int level) {
+ this.level = Math.max(this.level, level);
+ addMessage(msg);
+ if (t != null) {
+ try (StringWriter out = new StringWriter();
+ PrintWriter printWriter = new PrintWriter(out)) {
+ t.printStackTrace(printWriter);
+ addMessage(out.toString());
+ } catch (IOException e) {
+ // Impossible - this is actually a compiler bug. Nothing throws IOException above.
+ throw new AssertionError("Impossible exception thrown", e);
+ }
+ }
+ }
+
+ private void addMessage(String msg) {
messages.add(scopes.toString() + ": " + msg);
}
+
+ /** Throws a {@link HaltExecutionException} if there are any error or fatal messages. */
+ public void throwIfError(String why) throws HaltExecutionException {
+ if (hasError()) {
+ throw new HaltExecutionException(why);
+ }
+ }
+
+ /** Thrown to halt execution. */
+ public static class HaltExecutionException extends Exception {
+ HaltExecutionException(String why) {
+ super(why);
+ }
+ }
}
diff --git a/input_tools/android/common/src/test/java/com/android/libcore/timezone/util/ErrorsTest.java b/input_tools/android/common/src/test/java/com/android/libcore/timezone/util/ErrorsTest.java
index 5a6e717..1d568e5 100644
--- a/input_tools/android/common/src/test/java/com/android/libcore/timezone/util/ErrorsTest.java
+++ b/input_tools/android/common/src/test/java/com/android/libcore/timezone/util/ErrorsTest.java
@@ -20,8 +20,10 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import com.android.libcore.timezone.testing.TestUtils;
+import com.android.libcore.timezone.util.Errors.HaltExecutionException;
public class ErrorsTest {
@@ -62,7 +64,10 @@
assertFalse(errors.hasError());
assertFalse(errors.hasFatal());
- errors.addFatal("Hello");
+ try {
+ throw errors.addFatalAndHalt("Hello");
+ } catch (HaltExecutionException expected) {}
+
assertFalse(errors.isEmpty());
assertTrue(errors.hasError());
assertTrue(errors.hasFatal());
@@ -80,13 +85,19 @@
errors.addError("John Cleese");
errors.pushScope("Holy grail");
- errors.addFatal("Silly place");
+ try {
+ errors.addFatalAndHalt("Silly place");
+ fail();
+ } catch (HaltExecutionException expected) {}
errors.popScope();
errors.addError("Michael Palin");
errors.pushScope("Parrot sketch");
- errors.addFatal("Fjords");
+ try {
+ errors.addFatalAndHalt("Fjords");
+ fail();
+ } catch (HaltExecutionException expected) {}
errors.popScope();
String[] lines = errors.asString().split("\n");
diff --git a/input_tools/android/telephonylookup_generator/Android.bp b/input_tools/android/telephonylookup_generator/Android.bp
index a596992..00dcc8c 100644
--- a/input_tools/android/telephonylookup_generator/Android.bp
+++ b/input_tools/android/telephonylookup_generator/Android.bp
@@ -47,4 +47,5 @@
"telephonylookup_generator",
"tztools_common_testing",
],
+ test_suites: ["general-tests"],
}
diff --git a/input_tools/android/telephonylookup_generator/TEST_MAPPING b/input_tools/android/telephonylookup_generator/TEST_MAPPING
new file mode 100644
index 0000000..9916c9c
--- /dev/null
+++ b/input_tools/android/telephonylookup_generator/TEST_MAPPING
@@ -0,0 +1,8 @@
+{
+ "presubmit": [
+ {
+ "name" : "telephonylookup_generator_tests",
+ "host" : true
+ }
+ ]
+}
diff --git a/input_tools/android/telephonylookup_generator/src/main/java/com/android/libcore/timezone/telephonylookup/TelephonyLookupGenerator.java b/input_tools/android/telephonylookup_generator/src/main/java/com/android/libcore/timezone/telephonylookup/TelephonyLookupGenerator.java
index 978d264..4626d8d 100644
--- a/input_tools/android/telephonylookup_generator/src/main/java/com/android/libcore/timezone/telephonylookup/TelephonyLookupGenerator.java
+++ b/input_tools/android/telephonylookup_generator/src/main/java/com/android/libcore/timezone/telephonylookup/TelephonyLookupGenerator.java
@@ -19,6 +19,7 @@
import com.android.libcore.timezone.telephonylookup.proto.TelephonyLookupProtoFile;
import com.android.libcore.timezone.util.Errors;
+import com.android.libcore.timezone.util.Errors.HaltExecutionException;
import com.ibm.icu.util.ULocale;
@@ -68,78 +69,75 @@
}
boolean execute() throws IOException {
- // Parse the countryzones input file.
- TelephonyLookupProtoFile.TelephonyLookup telephonyLookupIn;
+ Errors errors = new Errors();
try {
- telephonyLookupIn = parseTelephonyLookupTextFile(telephonyLookupProtoFile);
- } catch (ParseException e) {
- logError("Unable to parse " + telephonyLookupProtoFile, e);
- return false;
- }
+ // Parse the countryzones input file.
+ TelephonyLookupProtoFile.TelephonyLookup telephonyLookupIn;
+ try {
+ telephonyLookupIn = parseTelephonyLookupTextFile(telephonyLookupProtoFile);
+ } catch (ParseException e) {
+ throw errors.addFatalAndHalt("Unable to parse " + telephonyLookupProtoFile, e);
+ }
- List<TelephonyLookupProtoFile.Network> networksIn = telephonyLookupIn.getNetworksList();
+ List<TelephonyLookupProtoFile.Network> networksIn = telephonyLookupIn.getNetworksList();
- Errors processingErrors = new Errors();
- processingErrors.pushScope("Validation");
- validateNetworks(networksIn, processingErrors);
- processingErrors.popScope();
+ validateNetworks(networksIn, errors);
+ errors.throwIfError("One or more validation errors encountered");
- // Validation failed, so stop.
- if (processingErrors.hasFatal()) {
- logInfo("Issues:\n" + processingErrors.asString());
- return false;
- }
-
- TelephonyLookupXmlFile.TelephonyLookup telephonyLookupOut =
- createOutputTelephonyLookup(networksIn);
- if (!processingErrors.hasError()) {
- // Write the output structure if there wasn't an error.
+ TelephonyLookupXmlFile.TelephonyLookup telephonyLookupOut =
+ createOutputTelephonyLookup(networksIn);
logInfo("Writing " + outputFile);
try {
TelephonyLookupXmlFile.write(telephonyLookupOut, outputFile);
} catch (XMLStreamException e) {
- e.printStackTrace(System.err);
- processingErrors.addFatal("Unable to write output file");
+ throw errors.addFatalAndHalt("Unable to write output file", e);
+ }
+ } catch (HaltExecutionException e) {
+ e.printStackTrace();
+ logError("Stopping due to fatal error: " + e.getMessage());
+ } finally {
+ // Report all warnings / errors
+ if (!errors.isEmpty()) {
+ logInfo("Issues:\n" + errors.asString());
}
}
-
- // Report all warnings / errors
- if (!processingErrors.isEmpty()) {
- logInfo("Issues:\n" + processingErrors.asString());
- }
-
- return !processingErrors.hasError();
+ return !errors.hasError();
}
- private static void validateNetworks(
- List<TelephonyLookupProtoFile.Network> networksIn, Errors processingErrors) {
- Set<String> knownIsoCountries = getLowerCaseCountryIsoCodes();
- Set<String> mccMncSet = new HashSet<>();
- for (TelephonyLookupProtoFile.Network networkIn : networksIn) {
- String mcc = networkIn.getMcc();
- if (mcc.length() != 3 || !isAsciiNumeric(mcc)) {
- processingErrors.addFatal("mcc=" + mcc + " must have 3 decimal digits");
- }
+ private static void validateNetworks(List<TelephonyLookupProtoFile.Network> networksIn,
+ Errors errors) {
+ errors.pushScope("validateNetworks");
+ try {
+ Set<String> knownIsoCountries = getLowerCaseCountryIsoCodes();
+ Set<String> mccMncSet = new HashSet<>();
+ for (TelephonyLookupProtoFile.Network networkIn : networksIn) {
+ String mcc = networkIn.getMcc();
+ if (mcc.length() != 3 || !isAsciiNumeric(mcc)) {
+ errors.addError("mcc=" + mcc + " must have 3 decimal digits");
+ }
- String mnc = networkIn.getMnc();
- if (!(mnc.length() == 2 || mnc.length() == 3) || !isAsciiNumeric(mnc)) {
- processingErrors.addFatal("mnc=" + mnc + " must have 2 or 3 decimal digits");
- }
+ String mnc = networkIn.getMnc();
+ if (!(mnc.length() == 2 || mnc.length() == 3) || !isAsciiNumeric(mnc)) {
+ errors.addError("mnc=" + mnc + " must have 2 or 3 decimal digits");
+ }
- String mccMnc = "" + mcc + mnc;
- if (!mccMncSet.add(mccMnc)) {
- processingErrors.addFatal("Duplicate entry for mcc=" + mcc + ", mnc=" + mnc);
- }
+ String mccMnc = "" + mcc + mnc;
+ if (!mccMncSet.add(mccMnc)) {
+ errors.addError("Duplicate entry for mcc=" + mcc + ", mnc=" + mnc);
+ }
- String countryIsoCode = networkIn.getCountryIsoCode();
- String countryIsoCodeLower = countryIsoCode.toLowerCase(Locale.ROOT);
- if (!countryIsoCodeLower.equals(countryIsoCode)) {
- processingErrors.addFatal("Country code not lower case: " + countryIsoCode);
- }
+ String countryIsoCode = networkIn.getCountryIsoCode();
+ String countryIsoCodeLower = countryIsoCode.toLowerCase(Locale.ROOT);
+ if (!countryIsoCodeLower.equals(countryIsoCode)) {
+ errors.addError("Country code not lower case: " + countryIsoCode);
+ }
- if (!knownIsoCountries.contains(countryIsoCodeLower)) {
- processingErrors.addFatal("Country code not known: " + countryIsoCode);
+ if (!knownIsoCountries.contains(countryIsoCodeLower)) {
+ errors.addError("Country code not known: " + countryIsoCode);
+ }
}
+ } finally {
+ errors.popScope();
}
}
diff --git a/input_tools/android/tzids/Android.bp b/input_tools/android/tzids/Android.bp
new file mode 100644
index 0000000..c4750c8
--- /dev/null
+++ b/input_tools/android/tzids/Android.bp
@@ -0,0 +1,42 @@
+// Copyright (C) 2020 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.
+
+// Proto library
+java_library_host {
+ name: "tzids_protos",
+
+ proto: {
+ type: "full",
+ include_dirs: ["external/protobuf/src"],
+ },
+
+ srcs: ["src/main/proto/**/*.proto"],
+}
+
+java_library_host {
+ name: "tzids",
+ srcs: ["src/main/java/**/*.java"],
+ static_libs: ["tzids_protos"],
+}
+
+java_test_host {
+ name: "tzids_tests",
+
+ srcs: ["src/test/java/**/*.java"],
+ static_libs: [
+ "junit",
+ "tzids",
+ ],
+ test_suites: ["general-tests"],
+}
diff --git a/input_tools/android/tzids/TEST_MAPPING b/input_tools/android/tzids/TEST_MAPPING
new file mode 100644
index 0000000..89921f9
--- /dev/null
+++ b/input_tools/android/tzids/TEST_MAPPING
@@ -0,0 +1,8 @@
+{
+ "presubmit": [
+ {
+ "name" : "tzids_tests",
+ "host" : true
+ }
+ ]
+}
diff --git a/input_tools/android/tzids/src/main/java/com/android/timezone/tzids/TimeZoneIds.java b/input_tools/android/tzids/src/main/java/com/android/timezone/tzids/TimeZoneIds.java
new file mode 100644
index 0000000..2e68057
--- /dev/null
+++ b/input_tools/android/tzids/src/main/java/com/android/timezone/tzids/TimeZoneIds.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+package com.android.timezone.tzids;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.stream.Collectors.toMap;
+
+import com.android.timezone.tzids.proto.TzIdsProto;
+import com.google.protobuf.TextFormat;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * A representation of Android's country ISO code mapping for TZDB time zone IDs, and information
+ * about time zone ID equivalence, for use in host tooling.
+ *
+ * <p>The Android team maintain the mapping based on information from IANA's TZDB and other
+ * sources. Strictly speaking, TZDB no longer supports the idea of a time zone ID being associated
+ * with a single country (see zone1970.tab Vs zone.tab), but Android requires most zones be mapped
+ * this way: Android has a region-based time zone picker in settings (for selecting the device's
+ * time zone manually), and it uses a country-based algorithm for telephony-based time zone
+ * detection. Most time zone IDs in TZDB have an exemplar location, and a time zone ID will be
+ * mapped to the country of that exemplar location.
+ *
+ * <p>Besides the country mapping, only some IDs for a country are actually "useful". The others are
+ * associated with equivalent alternatives that are preferred. Mappings between different time zone
+ * IDs exist in two main situations:
+ * <ol>
+ * <li>"Links" : When TZDB has a direct link / synonym. e.g. "GB-Eire", an old ID, maps to
+ * "Europe/London", the modern / canonical equivalent. "GB-Eire" and "Europe/London" are
+ * identical in TZDB. Note: Other libraries with additional metadata like ICU could still treat
+ * "GB-Eire" differently from "Europe/London".
+ * These will typically be as you'd expect from looking at the IANA tzdb backward file. However,
+ * the IDs preferred by Android are determined by countryzones.txt, which could choose to
+ * continue using an old ID, in which case the link may be reversed. This reversal is expected
+ * when an older version of ICU doesn't have strings for a new ID.</li>
+ * <li>"Replacements" : When zones have coalesced over time within a country. TZDB has many
+ * "extra" IDs for time zones because of historical differences that haven't been relevant for
+ * years. Replacements have a "from" time which indicates the point in time after which the two
+ * zones can be considered equivalent. e.g. "America/Boise" has the same rules as
+ * "America/Phoenix" after Sun, 03 Feb 1974, so the two can be considered equivalent today, but
+ * not for dates before then.</li>
+ * </ol>
+ *
+ * <p>Other notes:
+ * <ul>
+ * <li>There are entries in TZDB that are not mapped to a country and so are not included.
+ * e.g. Etc/GMT+5.</li>
+ * <li>Time zone IDs must be mapped to at most one country.</li>
+ * <li>There are many time zone IDs in TZDB that have identical rules, but are associated with
+ * different countries. These are deliberately not recorded as links or replacements.
+ * i.e. Links/replacements do not link across ISO country codes.</li>
+ * </ul>
+ */
+public class TimeZoneIds {
+
+ private final TzIdsProto.TimeZoneIds mTimeZoneIdsProto;
+
+ /** Creates a {@link TimeZoneIds} from the proto definition. */
+ public TimeZoneIds(TzIdsProto.TimeZoneIds timeZoneIdsProto) {
+ mTimeZoneIdsProto = Objects.requireNonNull(timeZoneIdsProto);
+ }
+
+ /** Loads the {@code inputFile} as a {@link TimeZoneIds}. */
+ public static TimeZoneIds load(File inputFile) throws IOException {
+ TzIdsProto.TimeZoneIds.Builder builder = TzIdsProto.TimeZoneIds.newBuilder();
+ try (InputStreamReader reader =
+ new InputStreamReader(new FileInputStream(inputFile), UTF_8)) {
+ TextFormat.getParser().merge(reader, builder);
+ }
+ return new TimeZoneIds(builder.build());
+ }
+
+ /** Store this in {@code file}. */
+ public void store(File file) throws IOException {
+ try (OutputStreamWriter writer =
+ new OutputStreamWriter(new FileOutputStream(file), UTF_8)) {
+ writer.append("# Autogenerated file - DO NOT EDIT.\n");
+ TextFormat.print(mTimeZoneIdsProto, writer);
+ }
+ }
+
+ /**
+ * Returns the country code associated with the supplied {@code zoneId} or {@code null} if it
+ * is not found.
+ */
+ public String getCountryCodeForZoneId(String zoneId) {
+ for (TzIdsProto.CountryMapping countryMapping
+ : mTimeZoneIdsProto.getCountryMappingsList()) {
+ if (countryMapping.getTimeZoneIdsList().contains(zoneId)) {
+ return countryMapping.getIsoCode();
+ }
+
+ for (TzIdsProto.TimeZoneLink timeZoneLink : countryMapping.getTimeZoneLinksList()) {
+ if (timeZoneLink.getAlternativeId().equals(zoneId)) {
+ return countryMapping.getIsoCode();
+ }
+ }
+
+ for (TzIdsProto.TimeZoneReplacement timeZoneReplacement
+ : countryMapping.getTimeZoneReplacementsList()) {
+ if (timeZoneReplacement.getReplacedId().equals(zoneId)) {
+ return countryMapping.getIsoCode();
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns a country mapping for TZDB time zone IDs on or after the specified time.
+ * The {@link Map} returned contains entries that link Olson IDs to themselves, or to preferred
+ * Olson IDs. The {@code replacementThreshold} is used to identify which "replacements" should
+ * be considered (see {@link TimeZoneIds} for details).
+ *
+ * <p>This mapping is useful when interpreting data from third party sources that could use any
+ * of the many equivalent zone IDs from TZDB and need to interact with Android devices.
+ *
+ * <p>If no map entry is present for an ID, then the IDs is either not valid or is not mapped to
+ * the ISO country code by Android's mapping data.
+ *
+ * @param isoCode the ISO 3166 alpha-2 country code for the country
+ * @param replacementThreshold the point in time to use when interpreting replacements
+ * @throws IllegalArgumentException if the country code is not recognized
+ */
+ public Map<String, String> getCountryIdMap(String isoCode, Instant replacementThreshold) {
+ for (TzIdsProto.CountryMapping countryMapping
+ : mTimeZoneIdsProto.getCountryMappingsList()) {
+ if (countryMapping.getIsoCode().equalsIgnoreCase(isoCode)) {
+ return createCountryIdMap(countryMapping, replacementThreshold);
+ }
+ }
+ throw new IllegalArgumentException("Unknown country code: " + isoCode);
+ }
+
+ private Map<String, String> createCountryIdMap(
+ TzIdsProto.CountryMapping countryMapping, Instant replacementThreshold) {
+ Map<String, String> tzIdMap = new HashMap<>();
+
+ // Add identity entries for the preferred IDs.
+ putAllSafely(tzIdMap, countryMapping.getTimeZoneIdsList()
+ .stream()
+ .collect(toMap(Function.identity(), Function.identity())));
+
+ // Handle links: just add straightforward map entries.
+ putAllSafely(tzIdMap, countryMapping.getTimeZoneLinksList()
+ .stream()
+ .collect(toMap(x -> x.getAlternativeId(), x -> x.getPreferredId())));
+
+ // Handle replacements: replacements are zone IDs that are recognized but potentially no
+ // longer used. Each replacement has a time when it comes into effect, and it may link to
+ // another replacement or a "top level" time zone ID. Below, the replacementThreshold is
+ // used to work out which replacements are in use and map each replacedId to a zone ID. It
+ // is ok if a zone maps to itself, i.e. when a replacement is in the future.
+
+ // Create a lookup of replacements by replacedId to make it easier to traverse the linked
+ // list formed by replacements referring to other replacements.
+ Map<String, TzIdsProto.TimeZoneReplacement> replacementLookupMap =
+ countryMapping.getTimeZoneReplacementsList()
+ .stream()
+ .collect(toMap(x -> x.getReplacedId(), Function.identity()));
+
+ // For each replacement in the replacement list, work out what the replacedId should map to
+ // before the replacementThreshold.
+ Map<String, String> replacementsAtThreshold = countryMapping.getTimeZoneReplacementsList()
+ .stream()
+ .collect(toMap(x -> x.getReplacedId(),
+ x -> traverseReplacementList(x, replacementLookupMap, replacementThreshold)
+ ));
+ putAllSafely(tzIdMap, replacementsAtThreshold);
+ return tzIdMap;
+ }
+
+ /**
+ * Returns the replacement ID for {@code start} by following the replacement links until there
+ * are no more replacements or the replacement happens after {@code replacementThreshold}.
+ */
+ private static String traverseReplacementList(TzIdsProto.TimeZoneReplacement start,
+ Map<String, TzIdsProto.TimeZoneReplacement> replacementLookupMap,
+ Instant replacementThreshold) {
+ TzIdsProto.TimeZoneReplacement current = start;
+ while(current.getFromMillis() <= replacementThreshold.toEpochMilli()) {
+ TzIdsProto.TimeZoneReplacement next =
+ replacementLookupMap.get(current.getReplacementId());
+ if (next == null) {
+ // No more links to follow - use the replacement ID.
+ return current.getReplacementId();
+ }
+ current = next;
+ }
+ // There were more links that could be followed, but we stopped following
+ // because of the replacementThreshold.
+ return current.getReplacedId();
+ }
+
+ /**
+ * Like {@link Map#putAll(Map)} but throws an exception if {@code sourceMap} contains a key
+ * already present in {@code targetMap}.
+ */
+ private static void putAllSafely(Map<String, String> targetMap, Map<String, String> sourceMap) {
+ for (Map.Entry<String, String> entry : sourceMap.entrySet()) {
+ String oldMapping = targetMap.put(entry.getKey(), entry.getValue());
+ if (oldMapping != null) {
+ throw new IllegalArgumentException("Key is not unique: "
+ + entry.getKey() + " maps to both "
+ + entry.getValue() + " and " + oldMapping);
+ }
+ }
+ }
+}
diff --git a/input_tools/android/tzids/src/main/proto/tz_ids_proto.proto b/input_tools/android/tzids/src/main/proto/tz_ids_proto.proto
new file mode 100644
index 0000000..7aa3b1a
--- /dev/null
+++ b/input_tools/android/tzids/src/main/proto/tz_ids_proto.proto
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+syntax = "proto2";
+
+option java_package = "com.android.timezone.tzids.proto";
+option java_multiple_files = false;
+
+package com.android.timezone.tzids.proto;
+
+// Information about Olson IDs used / preferred by Android.
+message TimeZoneIds {
+ // The IANA TZDB version the data was generated from.
+ optional string ianaVersion = 1;
+
+ // Information about IDs that are mapped to ISO 3166 Alpha-2 country codes.
+ repeated CountryMapping countryMappings = 2;
+}
+
+// Information about Olson IDs recognized by Android as being related to a country.
+message CountryMapping {
+ // The ISO 3166 Alpha-2 country code.
+ required string isoCode = 1;
+
+ // The IANA TZDB Olson IDs preferred by Android for the country.
+ repeated string timeZoneIds = 2;
+
+ // Links for time zones that are recognized as being for the country, but are not preferred.
+ // These links are for time zones that have always been equivalent.
+ // e.g. "GB-Eire" is linked to "Europe/London" because "GB-Eire"" is just an obsoleted synonym.
+ repeated TimeZoneLink timeZoneLinks = 3;
+
+ // Replacements for time zones where the replaced time zone is not identical to the replacement
+ // before some point in time. After that point in time, the two zones have been judged as
+ // equivalent. e.g. "America/Boise" has the same rules as "America/Denver" after Sun, 03 Feb
+ // 1974, so the two can be considered equivalent today, but not for dates before that.
+ repeated TimeZoneReplacement timeZoneReplacements = 4;
+}
+
+// An ID replacement when one time zone Olson ID is just direct synonym for another.
+message TimeZoneLink {
+ // The alternative Olson ID. This will typically be an obsoleted Olson ID.
+ required string alternativeId = 1;
+ // The Android preferred Olson ID. This will typically be a newer / more correct Olson ID.
+ required string preferredId = 2;
+}
+
+// The functional replacement of one time zone ID by another after a point in time.
+// Computed by looking at offset behavior / zone metadata.
+message TimeZoneReplacement {
+ // The Olson ID that was replaced / ceased to be distinct.
+ required string replacedId = 1;
+ // The Olson ID that is better / to use in place of replacedId on Android after fromMillis.
+ required string replacementId = 2;
+ // When replacementId replaced replacedId. Milliseconds from the start of the Unix epoch.
+ required int64 fromMillis = 3;
+}
diff --git a/input_tools/android/tzids/src/test/java/com/android/timezone/tzids/TimeZoneIdsTest.java b/input_tools/android/tzids/src/test/java/com/android/timezone/tzids/TimeZoneIdsTest.java
new file mode 100644
index 0000000..95c095c
--- /dev/null
+++ b/input_tools/android/tzids/src/test/java/com/android/timezone/tzids/TimeZoneIdsTest.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+package com.android.timezone.tzids;
+
+import static java.time.ZoneOffset.UTC;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import com.android.timezone.tzids.proto.TzIdsProto;
+
+import org.junit.Test;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.util.HashMap;
+import java.util.Map;
+
+/** Tests for {@link TimeZoneIds}. */
+public final class TimeZoneIdsTest {
+
+ @Test
+ public void getCountryIdMap_links() throws Exception {
+ TzIdsProto.TimeZoneIds.Builder tzIdsBuilder = TzIdsProto.TimeZoneIds.newBuilder();
+
+ TzIdsProto.CountryMapping gb = TzIdsProto.CountryMapping.newBuilder()
+ .setIsoCode("gb")
+ .addTimeZoneIds("Europe/London")
+ .addTimeZoneLinks(createLink("GB", "Europe/London"))
+ .build();
+ tzIdsBuilder.addCountryMappings(gb);
+
+ TimeZoneIds tzIds = new TimeZoneIds(tzIdsBuilder.build());
+
+ Map<String, String> expectedMap = new HashMap<>();
+ expectedMap.put("Europe/London", "Europe/London");
+ expectedMap.put("GB", "Europe/London");
+
+ assertEquals(expectedMap, tzIds.getCountryIdMap("Gb", Instant.EPOCH));
+ assertEquals(expectedMap, tzIds.getCountryIdMap("GB", Instant.EPOCH));
+ assertEquals(expectedMap, tzIds.getCountryIdMap("gB", Instant.EPOCH));
+
+ assertEquals(expectedMap, tzIds.getCountryIdMap("gb", Instant.MIN));
+ assertEquals(expectedMap, tzIds.getCountryIdMap("gb", Instant.EPOCH));
+ assertEquals(expectedMap, tzIds.getCountryIdMap("gb", Instant.MAX));
+ }
+
+ @Test
+ public void getCountryIdMap_replacements() throws Exception {
+ TzIdsProto.TimeZoneIds.Builder tzIdsBuilder = TzIdsProto.TimeZoneIds.newBuilder();
+
+ // A much-simplified version of the US time zone IDs with a chain of replacements.
+ Instant knoxFrom = LocalDateTime.of(1991, Month.OCTOBER, 27, 7, 0).toInstant(UTC);
+ Instant tellCityFrom = LocalDateTime.of(2006, Month.APRIL, 2, 8, 0).toInstant(UTC);
+ TzIdsProto.CountryMapping us = TzIdsProto.CountryMapping.newBuilder()
+ .setIsoCode("us")
+ .addTimeZoneIds("America/Chicago")
+ .addTimeZoneReplacements(
+ createReplacement(
+ "America/Indiana/Tell_City", "America/Chicago", tellCityFrom))
+ .addTimeZoneReplacements(
+ createReplacement(
+ "America/Indiana/Knox", "America/Indiana/Tell_City", knoxFrom))
+ .build();
+ tzIdsBuilder.addCountryMappings(us);
+
+ TimeZoneIds tzIds = new TimeZoneIds(tzIdsBuilder.build());
+
+ Map<String, String> baseExpectedMap = new HashMap<>();
+ baseExpectedMap.put("America/Chicago", "America/Chicago");
+
+ // Before all replacements are in effect.
+ {
+ Map<String, String> expectedMap = new HashMap<>(baseExpectedMap);
+ expectedMap.put("America/Indiana/Tell_City", "America/Indiana/Tell_City");
+ expectedMap.put("America/Indiana/Knox", "America/Indiana/Knox");
+
+ assertEquals(expectedMap, tzIds.getCountryIdMap("us", Instant.EPOCH));
+ assertEquals(expectedMap, tzIds.getCountryIdMap("us", knoxFrom.minusMillis(1)));
+ }
+
+ // One replacement is in effect.
+ {
+ Map<String, String> expectedMap = new HashMap<>(baseExpectedMap);
+ expectedMap.put("America/Indiana/Knox", "America/Indiana/Tell_City");
+ expectedMap.put("America/Indiana/Tell_City", "America/Indiana/Tell_City");
+
+ assertEquals(expectedMap, tzIds.getCountryIdMap("us", knoxFrom));
+ assertEquals(expectedMap, tzIds.getCountryIdMap("us", tellCityFrom.minusMillis(1)));
+ }
+
+ // All replacements are in effect.
+ {
+ Map<String, String> expectedMap = new HashMap<>(baseExpectedMap);
+ expectedMap.put("America/Indiana/Knox", "America/Chicago");
+ expectedMap.put("America/Indiana/Tell_City", "America/Chicago");
+
+ assertEquals(expectedMap, tzIds.getCountryIdMap("us", tellCityFrom));
+ assertEquals(expectedMap,
+ tzIds.getCountryIdMap("us", Instant.ofEpochMilli(Long.MAX_VALUE)));
+ }
+ }
+
+ @Test
+ public void getCountryCodeForZoneId() {
+ TzIdsProto.TimeZoneIds.Builder tzIdsBuilder = TzIdsProto.TimeZoneIds.newBuilder();
+
+ TzIdsProto.CountryMapping gb = TzIdsProto.CountryMapping.newBuilder()
+ .setIsoCode("gb")
+ .addTimeZoneIds("Europe/London")
+ .addTimeZoneLinks(createLink("GB", "Europe/London"))
+ .build();
+ tzIdsBuilder.addCountryMappings(gb);
+
+ Instant boiseFrom = LocalDateTime.of(1974, Month.FEBRUARY, 3, 9, 0).toInstant(UTC);
+ TzIdsProto.CountryMapping us = TzIdsProto.CountryMapping.newBuilder()
+ .setIsoCode("us")
+ .addTimeZoneIds("America/Phoenix")
+ .addTimeZoneLinks(createLink("US/Arizona", "America/Phoenix"))
+ .addTimeZoneReplacements(
+ createReplacement("America/Boise", "America/Phoenix", boiseFrom))
+ .build();
+ tzIdsBuilder.addCountryMappings(us);
+
+ TimeZoneIds tzIds = new TimeZoneIds(tzIdsBuilder.build());
+ assertNull(tzIds.getCountryCodeForZoneId("FooBar"));
+ assertEquals("gb", tzIds.getCountryCodeForZoneId("GB"));
+ assertEquals("gb", tzIds.getCountryCodeForZoneId("Europe/London"));
+ assertEquals("us", tzIds.getCountryCodeForZoneId("America/Phoenix"));
+ assertEquals("us", tzIds.getCountryCodeForZoneId("US/Arizona"));
+ assertEquals("us", tzIds.getCountryCodeForZoneId("America/Boise"));
+ }
+
+ private static TzIdsProto.TimeZoneLink createLink(
+ String alternativeId, String preferredId) {
+ return TzIdsProto.TimeZoneLink.newBuilder()
+ .setAlternativeId(alternativeId)
+ .setPreferredId(preferredId)
+ .build();
+ }
+
+ private static TzIdsProto.TimeZoneReplacement createReplacement(String replacedId,
+ String replacementId, Instant from) {
+ return TzIdsProto.TimeZoneReplacement.newBuilder()
+ .setReplacedId(replacedId)
+ .setReplacementId(replacementId)
+ .setFromMillis(from.toEpochMilli())
+ .build();
+ }
+}
diff --git a/input_tools/android/tzlookup_generator/Android.bp b/input_tools/android/tzlookup_generator/Android.bp
index 610fb44..cc15ada 100644
--- a/input_tools/android/tzlookup_generator/Android.bp
+++ b/input_tools/android/tzlookup_generator/Android.bp
@@ -14,7 +14,8 @@
// Proto library
java_library_host {
- name: "countryzonesprotos",
+ name: "countryzones_protos",
+
proto: {
type: "full",
include_dirs: ["external/protobuf/src"],
@@ -23,6 +24,18 @@
srcs: ["src/main/proto/**/*.proto"],
}
+java_library_host {
+ name: "tzlookup",
+
+ srcs: ["src/main/java/**/*.java"],
+ static_libs: [
+ "countryzones_protos",
+ "icu4j",
+ "tzids",
+ "tztools_common",
+ ],
+}
+
// The tzlookup_generator host tool.
java_binary_host {
name: "tzlookup_generator",
@@ -30,21 +43,18 @@
main_class: "com.android.libcore.timezone.tzlookup.TzLookupGenerator",
srcs: ["src/main/java/**/*.java"],
static_libs: [
- "icu4j",
- "countryzonesprotos",
- "libprotobuf-java-full",
- "tztools_common",
+ "tzlookup",
],
}
-// The tzlookup_generator host tool.
java_test_host {
- name: "tzlookup_generator_tests",
+ name: "tzlookup_tests",
srcs: ["src/test/java/**/*.java"],
static_libs: [
"junit",
- "tzlookup_generator",
+ "tzlookup",
"tztools_common_testing",
],
+ test_suites: ["general-tests"],
}
diff --git a/input_tools/android/tzlookup_generator/README.android b/input_tools/android/tzlookup_generator/README.android
index 4c993d4..fbb4ac8 100644
--- a/input_tools/android/tzlookup_generator/README.android
+++ b/input_tools/android/tzlookup_generator/README.android
@@ -1,2 +1,12 @@
-This tool generates the tzlookup.xml file from the countryzones.txt (proto text) file.
-The tool also uses ICU4J and IANA data to synthesize some time zone metadata.
+This tool generates the tzlookup.xml file from the countryzones.txt (proto
+text) file. tzlookup.xml is used to associate zones with regions/countries. The
+tool uses ICU4J and IANA data to synthesize some time zone metadata.
+
+Besides tzlookup.xml, the tool also generates a tzids.prototxt file
+containing information about zone IDs, id links and "functionally identical"
+zones[1] within each known country. These can be used to map time zone IDs to
+Android's preferred set. This duplicates information from the tzlookup.xml, but
+it is easier to use from host tools than Android's tzlookup.xml's format.
+
+[1] i.e. zones that differed in the past but were "folded in" to other zones
+and are not distinct for future times according to IANA or ICU/CLDR.
diff --git a/input_tools/android/tzlookup_generator/TEST_MAPPING b/input_tools/android/tzlookup_generator/TEST_MAPPING
new file mode 100644
index 0000000..a66d9c7
--- /dev/null
+++ b/input_tools/android/tzlookup_generator/TEST_MAPPING
@@ -0,0 +1,8 @@
+{
+ "presubmit": [
+ {
+ "name" : "tzlookup_tests",
+ "host" : true
+ }
+ ]
+}
diff --git a/input_tools/android/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/BackwardFile.java b/input_tools/android/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/BackwardFile.java
new file mode 100644
index 0000000..95bbbf2
--- /dev/null
+++ b/input_tools/android/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/BackwardFile.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+package com.android.libcore.timezone.tzlookup;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.text.ParseException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * A class that knows about the structure of the IANA tzdb backward file.
+ */
+final class BackwardFile {
+
+ private final Map<String, String> links = new HashMap<>();
+ private Map<String, String> directLinks;
+
+ private BackwardFile() {}
+
+ static BackwardFile parse(String backwardFile) throws IOException, ParseException {
+ BackwardFile backward = new BackwardFile();
+
+ List<String> lines = Files
+ .readAllLines(Paths.get(backwardFile), StandardCharsets.US_ASCII);
+
+ // Remove comments
+ List<String> linkLines =
+ lines.stream()
+ .filter(s -> !(s.startsWith("#") || s.isEmpty()))
+ .collect(Collectors.toList());
+
+ for (String linkLine : linkLines) {
+ String[] fields = linkLine.split("\t+");
+ if (fields.length < 3 || !fields[0].equals("Link")) {
+ throw new ParseException("Line is malformed: " + linkLine, 0);
+ }
+ backward.addLink(fields[1], fields[2]);
+ }
+ return backward;
+ }
+
+ /**
+ * Add a link entry.
+ *
+ * @param target the new tz ID
+ * @param linkName the old tz ID
+ */
+ private void addLink(String target, String linkName) {
+ String oldValue = links.put(linkName, target);
+ if (oldValue != null) {
+ throw new IllegalStateException("Duplicate link from " + linkName);
+ }
+ }
+
+ /**
+ * Returns a set of IDs linked to the supplied ID, even intermediate ones in a chain of links.
+ */
+ Set<String> getAllAlternativeIds(String zoneId) {
+ Set<String> knownAlternativeIds = new HashSet<>();
+ // Add the ID we're searching for. We don't need to look for it.
+ knownAlternativeIds.add(zoneId);
+
+ LinkedList<String> searchIdQueue = new LinkedList<>();
+ searchIdQueue.add(zoneId);
+ while (!searchIdQueue.isEmpty()) {
+ String searchId = searchIdQueue.removeLast();
+ for (Map.Entry<String, String> entry : links.entrySet()) {
+ String fromId = entry.getKey();
+ String toId = entry.getValue();
+ if (fromId.equals(searchId)) {
+ if (knownAlternativeIds.add(toId)) {
+ searchIdQueue.add(toId);
+ }
+ } else if (toId.equals(searchId)) {
+ if (knownAlternativeIds.add(fromId)) {
+ searchIdQueue.add(fromId);
+ }
+ }
+ }
+ }
+
+ // Remove the zone we were searching for - it's not an alternative for itself.
+ knownAlternativeIds.remove(zoneId);
+ return Collections.unmodifiableSet(knownAlternativeIds);
+ }
+
+ Map<String, String> getLinks() {
+ return Collections.unmodifiableMap(links);
+ }
+
+ /** Returns a mapping from linkName (old tz ID) to target (new tz ID). */
+ Map<String, String> getDirectLinks() {
+ if (directLinks == null) {
+ // Validate links for cycles and collapse the links to remove intermediates if there are
+ // links to links. There's a simple check to confirm that no chain is longer than a
+ // fixed length, to guard against cycles.
+ final int maxChainLength = 2;
+ Map<String, String> collapsedLinks = new HashMap<>();
+ for (String fromId : links.keySet()) {
+ int chainLength = 0;
+ String currentId = fromId;
+ String lastId = null;
+ while ((currentId = links.get(currentId)) != null) {
+ chainLength++;
+ lastId = currentId;
+ if (chainLength > maxChainLength) {
+ throw new IllegalStateException(
+ "Chain from " + fromId + " is longer than " + maxChainLength);
+ }
+ }
+ if (chainLength == 0) {
+ throw new IllegalStateException("Null Link targetId for " + fromId);
+ }
+ collapsedLinks.put(fromId, lastId);
+ }
+ directLinks = Collections.unmodifiableMap(collapsedLinks);
+ }
+ return directLinks;
+ }
+}
diff --git a/input_tools/android/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/CountryZonesFileSupport.java b/input_tools/android/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/CountryZonesFileSupport.java
index 9f9ad77..2c82cb4 100644
--- a/input_tools/android/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/CountryZonesFileSupport.java
+++ b/input_tools/android/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/CountryZonesFileSupport.java
@@ -15,7 +15,7 @@
*/
package com.android.libcore.timezone.tzlookup;
-import com.android.libcore.timezone.tzlookup.proto.CountryZonesFile;
+import com.android.libcore.timezone.countryzones.proto.CountryZonesFile;
import com.google.protobuf.TextFormat;
import java.io.BufferedReader;
diff --git a/input_tools/android/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/TzLookupFile.java b/input_tools/android/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/TzLookupFile.java
index d29de56..446b857 100644
--- a/input_tools/android/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/TzLookupFile.java
+++ b/input_tools/android/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/TzLookupFile.java
@@ -15,6 +15,7 @@
*/
package com.android.libcore.timezone.tzlookup;
+import com.android.timezone.tzids.proto.TzIdsProto;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
@@ -25,6 +26,8 @@
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
+
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
@@ -60,6 +63,10 @@
private static final String ZONE_SHOW_IN_PICKER_ATTRIBUTE = "picker";
// The time when the zone stops being distinct from another of the country's zones (inclusive).
private static final String ZONE_NOT_USED_AFTER_ATTRIBUTE = "notafter";
+ // The zone ID used in place of this one starting from the "notafter" time (when present).
+ private static final String ZONE_NOT_USED_REPLACEMENT_ID_ATTRIBUTE = "repl";
+ // Other IDs associated with this ID, e.g. legacy or more modern alternatives.
+ private static final String ZONE_ALTERNATIVE_IDS_ATTRIBUTE = "alts";
// Short encodings for boolean attributes.
@@ -71,20 +78,17 @@
/*
* The required XML structure is:
* <timezones ianaversion="2017b">
- * <countryzones>
- * <country code="us" default="America/New_York" everutc="n">
- * <!-- -5:00 -->
- * <id notafter="1234">America/New_York"</id>
- * ...
- * <!-- -8:00 -->
- * <id picker="n">America/Los_Angeles</id>
- * ...
- * </country>
- * <country code="gb" default="Europe/London" defaultBoost="y" everutc="y">
- * <!-- 0:00 -->
- * <id>Europe/London</id>
- * </country>
- * </countryzones>
+ * <countryzones>
+ * <country code="us" default="America/New_York" everutc="n">
+ * <id>America/New_York"</id>
+ * <id notafter="1234" repl="America/New_York" alts="US/Michigan">America/Detroit"</id>
+ * ...
+ * <id picker="n">America/Los_Angeles</id>
+ * </country>
+ * <country code="gb" default="Europe/London" defaultBoost="y" everutc="y">
+ * <id alts="Europe/Belfast,GB,GB-Eire">Europe/London</id>
+ * </country>
+ * </countryzones>
* </timezones>
*/
@@ -167,7 +171,7 @@
private final String defaultTimeZoneId;
private final boolean defaultTimeZoneBoost;
private final boolean everUsesUtc;
- private final List<TimeZoneMapping> timeZoneIds = new ArrayList<>();
+ private final List<TimeZoneMapping> timeZoneMappings = new ArrayList<>();
Country(String isoCode, String defaultTimeZoneId, boolean defaultTimeZoneBoost,
boolean everUsesUtc) {
@@ -177,8 +181,47 @@
this.everUsesUtc = everUsesUtc;
}
- void addTimeZoneIdentifier(TimeZoneMapping timeZoneId) {
- timeZoneIds.add(timeZoneId);
+ void addTimeZoneMapping(TimeZoneMapping timeZoneMapping) {
+ timeZoneMappings.add(timeZoneMapping);
+ }
+
+ static TzIdsProto.CountryMapping createCountryMappingProto(Country country) {
+ TzIdsProto.CountryMapping.Builder countryMappingBuilder =
+ TzIdsProto.CountryMapping.newBuilder()
+ .setIsoCode(country.isoCode);
+ for (TzLookupFile.TimeZoneMapping timeZoneMapping : country.timeZoneMappings) {
+ String mappingTimeZoneId = timeZoneMapping.olsonId;
+ String notUsedReplacementId = timeZoneMapping.notAfterReplacementId;
+ Instant notUsedAfterInstant = timeZoneMapping.notUsedAfterInclusive;
+ if (notUsedReplacementId == null && notUsedAfterInstant == null) {
+ countryMappingBuilder.addTimeZoneIds(mappingTimeZoneId);
+ } else if (notUsedReplacementId != null && notUsedAfterInstant != null) {
+ String replacedTimeZoneId = mappingTimeZoneId;
+ TzIdsProto.TimeZoneReplacement timeZoneReplacement =
+ TzIdsProto.TimeZoneReplacement.newBuilder()
+ .setReplacedId(replacedTimeZoneId)
+ .setReplacementId(notUsedReplacementId)
+ .setFromMillis(notUsedAfterInstant.toEpochMilli())
+ .build();
+ countryMappingBuilder.addTimeZoneReplacements(timeZoneReplacement);
+ } else {
+ throw new IllegalArgumentException(
+ "Malformed TimeZoneMapping:" + timeZoneMapping);
+ }
+
+ for (String alternativeZoneId : timeZoneMapping.alternativeZoneIds) {
+ // We could collapse links when notUsedReplacementId != null by using it instead
+ // of mappingTimeZoneId below, but that would potentially lose information.
+ // Leave it to the downstream components to collapse links if they want to.
+ TzIdsProto.TimeZoneLink timeZoneLink =
+ TzIdsProto.TimeZoneLink.newBuilder()
+ .setPreferredId(mappingTimeZoneId)
+ .setAlternativeId(alternativeZoneId)
+ .build();
+ countryMappingBuilder.addTimeZoneLinks(timeZoneLink);
+ }
+ }
+ return countryMappingBuilder.build();
}
static void writeXml(Country country, XMLStreamWriter writer)
@@ -192,8 +235,8 @@
}
writer.writeAttribute(EVER_USES_UTC_ATTRIBUTE, encodeBooleanAttribute(
country.everUsesUtc));
- for (TimeZoneMapping timeZoneId : country.timeZoneIds) {
- TimeZoneMapping.writeXml(timeZoneId, writer);
+ for (TimeZoneMapping timeZoneMapping : country.timeZoneMappings) {
+ TimeZoneMapping.writeXml(timeZoneMapping, writer);
}
writer.writeEndElement();
}
@@ -212,24 +255,40 @@
private final String olsonId;
private final boolean showInPicker;
private final Instant notUsedAfterInclusive;
+ private final String notAfterReplacementId;
+ private final List<String> alternativeZoneIds;
- TimeZoneMapping(String olsonId, boolean showInPicker, Instant notUsedAfterInclusive) {
- this.olsonId = olsonId;
+ TimeZoneMapping(String olsonId, boolean showInPicker, Instant notUsedAfterInclusive,
+ String notAfterReplacementId, List<String> alternativeZoneIds) {
+ this.olsonId = Objects.requireNonNull(olsonId);
this.showInPicker = showInPicker;
+ if ((notUsedAfterInclusive == null) != (notAfterReplacementId == null)) {
+ throw new IllegalArgumentException(
+ "Supply both notUsedAfterInclusive and notAfterReplacementId or neither");
+ }
this.notUsedAfterInclusive = notUsedAfterInclusive;
+ this.notAfterReplacementId = notAfterReplacementId;
+ this.alternativeZoneIds = Objects.requireNonNull(alternativeZoneIds);
}
- static void writeXml(TimeZoneMapping timeZoneId, XMLStreamWriter writer)
+ static void writeXml(TimeZoneMapping timeZoneMapping, XMLStreamWriter writer)
throws XMLStreamException {
writer.writeStartElement(ZONE_ID_ELEMENT);
- if (!timeZoneId.showInPicker) {
+ if (!timeZoneMapping.showInPicker) {
writer.writeAttribute(ZONE_SHOW_IN_PICKER_ATTRIBUTE, encodeBooleanAttribute(false));
}
- if (timeZoneId.notUsedAfterInclusive != null) {
+ if (timeZoneMapping.notUsedAfterInclusive != null) {
writer.writeAttribute(ZONE_NOT_USED_AFTER_ATTRIBUTE,
- encodeLongAttribute(timeZoneId.notUsedAfterInclusive.toEpochMilli()));
+ encodeLongAttribute(timeZoneMapping.notUsedAfterInclusive.toEpochMilli()));
+ writer.writeAttribute(ZONE_NOT_USED_REPLACEMENT_ID_ATTRIBUTE,
+ timeZoneMapping.notAfterReplacementId);
}
- writer.writeCharacters(timeZoneId.olsonId);
+ if (!timeZoneMapping.alternativeZoneIds.isEmpty()) {
+ String alternativeZoneIdsString =
+ String.join(",", timeZoneMapping.alternativeZoneIds);
+ writer.writeAttribute(ZONE_ALTERNATIVE_IDS_ATTRIBUTE, alternativeZoneIdsString);
+ }
+ writer.writeCharacters(timeZoneMapping.olsonId);
writer.writeEndElement();
}
}
diff --git a/input_tools/android/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/TzLookupGenerator.java b/input_tools/android/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/TzLookupGenerator.java
index ac3684a..9bf86e0 100644
--- a/input_tools/android/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/TzLookupGenerator.java
+++ b/input_tools/android/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/TzLookupGenerator.java
@@ -15,31 +15,38 @@
*/
package com.android.libcore.timezone.tzlookup;
-import com.android.libcore.timezone.tzlookup.proto.CountryZonesFile;
+import com.android.libcore.timezone.countryzones.proto.CountryZonesFile;
import com.android.libcore.timezone.tzlookup.zonetree.CountryZoneTree;
import com.android.libcore.timezone.tzlookup.zonetree.CountryZoneUsage;
import com.android.libcore.timezone.util.Errors;
+import com.android.libcore.timezone.util.Errors.HaltExecutionException;
+import com.android.timezone.tzids.TimeZoneIds;
+import com.android.timezone.tzids.proto.TzIdsProto;
import com.ibm.icu.util.BasicTimeZone;
import com.ibm.icu.util.Calendar;
import com.ibm.icu.util.GregorianCalendar;
import com.ibm.icu.util.TimeZone;
import com.ibm.icu.util.TimeZoneRule;
+import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
+
import javax.xml.stream.XMLStreamException;
/**
- * Generates the tzlookup.xml file using the information from countryzones.txt and zones.tab.
- *
- * See {@link #main(String[])} for commandline information.
+ * Generates Android's tzlookup.xml and tzids.prototxt file using ICU4J, Android's countryzones.txt
+ * file, and TZDB's backwards and zones.tab files.
*/
public final class TzLookupGenerator {
@@ -51,7 +58,7 @@
/**
* The end time (exclusive) for generating country zone usage. 20380119 03:14:07 UTC. Any times
- * after this will be considered "infinity" for the "notAfter" value and not included. Chosen
+ * after this will be considered "infinity" for the "notafter" value and not included. Chosen
* because this is a "nice round number" and has historical significance for people that deal
* with computer time. There is no particular reason to choose this over another time; any
* future time after the last time we expect the code to reasonably encounter will do.
@@ -68,118 +75,204 @@
public static final Instant ZONE_USAGE_CALCS_END =
ZONE_USAGE_NOT_AFTER_CUT_OFF.plus(2 * 365, ChronoUnit.DAYS);
- private final String countryZonesFile;
- private final String zoneTabFile;
- private final String outputFile;
+ private final String countryZonesFileIn;
+ private final String zoneTabFileIn;
+ private final String backwardFileIn;
+ private final String tzLookupXmlFileOut;
+ private final String timeZoneIdsFileOut;
/**
* Executes the generator.
- *
- * Positional arguments:
- * 1: The countryzones.txt file
- * 2: the zone.tab file
- * 3: the file to generate
*/
public static void main(String[] args) throws Exception {
- if (args.length != 3) {
+ if (args.length != 5) {
System.err.println(
"usage: java com.android.libcore.timezone.tzlookup.TzLookupGenerator"
- + " <input proto file> <zone.tab file> <output xml file>");
+ + " <[in] countryzones.txt file> <[in] zone.tab file>"
+ + " <[in] backward file>"
+ + " <[out] tzlookup.xml file> <[out] zone IDs file>");
System.exit(0);
}
- boolean success = new TzLookupGenerator(args[0], args[1], args[2]).execute();
+ TzLookupGenerator tzLookupGenerator =
+ new TzLookupGenerator(args[0], args[1], args[2], args[3], args[4]);
+ boolean success = tzLookupGenerator.execute();
System.exit(success ? 0 : 1);
}
- TzLookupGenerator(String countryZonesFile, String zoneTabFile, String outputFile) {
- this.countryZonesFile = countryZonesFile;
- this.zoneTabFile = zoneTabFile;
- this.outputFile = outputFile;
+ TzLookupGenerator(String countryZonesFileIn, String zoneTabFileIn, String backwardFileIn,
+ String tzLookupXmlFileOut, String timeZoneIdsFileOut) {
+ this.countryZonesFileIn = countryZonesFileIn;
+ this.zoneTabFileIn = zoneTabFileIn;
+ this.backwardFileIn = backwardFileIn;
+ this.tzLookupXmlFileOut = tzLookupXmlFileOut;
+ this.timeZoneIdsFileOut = timeZoneIdsFileOut;
}
- boolean execute() throws IOException {
- // Parse the countryzones input file.
- CountryZonesFile.CountryZones countryZonesIn;
+ boolean execute() {
+ Errors errors = new Errors();
try {
- countryZonesIn = CountryZonesFileSupport.parseCountryZonesTextFile(countryZonesFile);
- } catch (ParseException e) {
- logError("Unable to parse " + countryZonesFile, e);
- return false;
- }
+ // Parse the countryzones input file.
+ CountryZonesFile.CountryZones countryZonesIn =
+ parseAndValidateCountryZones(countryZonesFileIn, errors);
- // Check the countryzones rules version matches the version that ICU is using.
- String icuTzDataVersion = TimeZone.getTZDataVersion();
- String inputIanaVersion = countryZonesIn.getIanaVersion();
- if (!icuTzDataVersion.equals(inputIanaVersion)) {
- logError("Input data is for " + inputIanaVersion + " but the ICU you have is for "
- + icuTzDataVersion);
- return false;
- }
+ // Check the countryzones.txt rules version matches the version that ICU is using.
+ String icuTzDataVersion = TimeZone.getTZDataVersion();
+ String inputIanaVersion = countryZonesIn.getIanaVersion();
+ if (!icuTzDataVersion.equals(inputIanaVersion)) {
+ throw errors.addFatalAndHalt("Input data (countryzones.txt) is for "
+ + inputIanaVersion + " but the ICU you have is for " + icuTzDataVersion);
+ }
- // Pull out information we want to validate against from zone.tab (which we have to assume
- // matches the ICU version since it doesn't contain its own version info).
- ZoneTabFile zoneTabIn = ZoneTabFile.parse(zoneTabFile);
- Map<String, List<String>> zoneTabMapping =
- ZoneTabFile.createCountryToOlsonIdsMap(zoneTabIn);
- List<CountryZonesFile.Country> countriesIn = countryZonesIn.getCountriesList();
- List<String> countriesInIsos = CountryZonesFileSupport.extractIsoCodes(countriesIn);
+ // Pull out information we want to validate against from zone.tab (which we have to
+ // assume matches the ICU version since it doesn't contain its own version info).
+ Map<String, List<String>> zoneTabMapping = parseZoneTabFile(zoneTabFileIn, errors);
- // Sanity check the countryzones file only contains lower-case country codes. The output
- // file uses them and the on-device code assumes lower case.
- if (!Utils.allLowerCaseAscii(countriesInIsos)) {
- logError("Non-lowercase country ISO codes found in: " + countriesInIsos);
- return false;
- }
- // Sanity check the countryzones file doesn't contain duplicate country entries.
- if (!Utils.allUnique(countriesInIsos)) {
- logError("Duplicate input country entries found: " + countriesInIsos);
- return false;
- }
+ List<CountryZonesFile.Country> countriesIn = countryZonesIn.getCountriesList();
+ List<String> countriesInIsos = CountryZonesFileSupport.extractIsoCodes(countriesIn);
- // Validate the country iso codes found in the countryzones against those in zone.tab.
- // zone.tab uses upper case, countryzones uses lower case.
- List<String> upperCaseCountriesInIsos = Utils.toUpperCase(countriesInIsos);
- Set<String> timezonesCountryIsos = new HashSet<>(upperCaseCountriesInIsos);
- Set<String> zoneTabCountryIsos = zoneTabMapping.keySet();
- if (!zoneTabCountryIsos.equals(timezonesCountryIsos)) {
- logError(zoneTabFile + " contains "
- + Utils.subtract(zoneTabCountryIsos, timezonesCountryIsos)
- + " not present in countryzones, "
- + countryZonesFile + " contains "
- + Utils.subtract(timezonesCountryIsos, zoneTabCountryIsos)
- + " not present in zonetab.");
- return false;
- }
+ // Confidence check the countryzones file only contains lower-case country codes. The
+ // output file uses them and the on-device code assumes lower case.
+ if (!Utils.allLowerCaseAscii(countriesInIsos)) {
+ throw errors.addFatalAndHalt(
+ "Non-lowercase country ISO codes found in: " + countriesInIsos);
+ }
+ // Confidence check the countryzones file doesn't contain duplicate country entries.
+ if (!Utils.allUnique(countriesInIsos)) {
+ throw errors.addFatalAndHalt(
+ "Duplicate input country entries found: " + countriesInIsos);
+ }
- Errors processingErrors = new Errors();
- TzLookupFile.TimeZones timeZonesOut = createOutputTimeZones(
- inputIanaVersion, zoneTabMapping, countriesIn, processingErrors);
- if (!processingErrors.hasError()) {
+ // Validate the country iso codes found in the countryzones.txt against those in
+ // zone.tab. zone.tab uses upper case, countryzones uses lower case.
+ List<String> upperCaseCountriesInIsos = Utils.toUpperCase(countriesInIsos);
+ Set<String> timezonesCountryIsos = new HashSet<>(upperCaseCountriesInIsos);
+ Set<String> zoneTabCountryIsos = zoneTabMapping.keySet();
+ if (!zoneTabCountryIsos.equals(timezonesCountryIsos)) {
+ throw errors.addFatalAndHalt(zoneTabFileIn + " contains "
+ + Utils.subtract(zoneTabCountryIsos, timezonesCountryIsos)
+ + " not present in countryzones, "
+ + countryZonesFileIn + " contains "
+ + Utils.subtract(timezonesCountryIsos, zoneTabCountryIsos)
+ + " not present in zonetab.");
+ }
+
+ // Obtain and validate a mapping from old IDs to new IDs.
+ BackwardFile backwardIn = parseAndValidateBackwardFile(backwardFileIn, errors);
+ errors.throwIfError("Errors accumulated");
+
+ OutputData outputData = createOutputData(
+ inputIanaVersion, zoneTabMapping, countriesIn, backwardIn, errors);
+
// Write the output structure if there wasn't an error.
- logInfo("Writing " + outputFile);
- try {
- TzLookupFile.write(timeZonesOut, outputFile);
- } catch (XMLStreamException e) {
- e.printStackTrace(System.err);
- processingErrors.addFatal("Unable to write output file");
+ errors.throwIfError("Errors accumulated");
+ writeOutputData(outputData, tzLookupXmlFileOut, timeZoneIdsFileOut, errors);
+ return true;
+ } catch (HaltExecutionException e) {
+ logError("Stopping due to fatal condition", e);
+ return false;
+ } finally {
+ // Report all warnings / errors
+ if (!errors.isEmpty()) {
+ logInfo("Issues:\n" + errors.asString());
}
}
-
- // Report all warnings / errors
- if (!processingErrors.isEmpty()) {
- logInfo("Issues:\n" + processingErrors.asString());
- }
-
- return !processingErrors.hasError();
}
- private static TzLookupFile.TimeZones createOutputTimeZones(String inputIanaVersion,
+ private Map<String, List<String>> parseZoneTabFile(String zoneTabFile, Errors errors)
+ throws HaltExecutionException {
+ errors.pushScope("Parsing " + zoneTabFile);
+ try {
+ ZoneTabFile zoneTabIn;
+ zoneTabIn = ZoneTabFile.parse(zoneTabFile);
+ return ZoneTabFile.createCountryToOlsonIdsMap(zoneTabIn);
+ } catch (ParseException | IOException e) {
+ throw errors.addFatalAndHalt("Unable to parse " + zoneTabFile, e);
+ } finally {
+ errors.popScope();
+ }
+ }
+
+ /**
+ * Load the backward file and return the links contained within. This is used as the source of
+ * equivalent time zone IDs.
+ */
+ private static BackwardFile parseAndValidateBackwardFile(String backwardFile, Errors errors) {
+ errors.pushScope("Parsing " + backwardFile);
+ try {
+ BackwardFile backward = BackwardFile.parse(backwardFile);
+
+ // Validate the links.
+ Map<String, String> zoneIdLinks = backward.getLinks();
+ zoneIdLinks.forEach(
+ (k, v) -> {
+ if (invalidTimeZoneId(k)) {
+ errors.addError("Bad 'from' link: " + k + "->" + v);
+ }
+ if (invalidTimeZoneId(v)) {
+ errors.addError("Bad 'to' link: " + k + "->" + v);
+ }
+ });
+ return backward;
+ } catch (ParseException | IOException e) {
+ errors.addError("Unable to parse " + backwardFile, e);
+ return null;
+ } finally {
+ errors.popScope();
+ }
+ }
+
+ private static CountryZonesFile.CountryZones parseAndValidateCountryZones(
+ String countryZonesFile, Errors errors) throws HaltExecutionException {
+ errors.pushScope("Parsing " + countryZonesFile);
+ try {
+ CountryZonesFile.CountryZones countryZonesIn;
+ countryZonesIn = CountryZonesFileSupport.parseCountryZonesTextFile(countryZonesFile);
+ return countryZonesIn;
+ } catch (ParseException | IOException e) {
+ throw errors.addFatalAndHalt("Unable to parse " + countryZonesFile, e);
+ } finally {
+ errors.popScope();
+ }
+ }
+
+ private static void writeOutputData(OutputData outputData,
+ String tzLookupXmlFileName, String timeZoneIdsFileName, Errors errors)
+ throws HaltExecutionException {
+ errors.pushScope("write " + tzLookupXmlFileName);
+ try {
+ // Write out the file used on device.
+ logInfo("Writing " + tzLookupXmlFileName);
+
+ TzLookupFile.TimeZones timeZonesOut = outputData.getTzLookupTimeZones();
+ TzLookupFile.write(timeZonesOut, tzLookupXmlFileName);
+ } catch (IOException | XMLStreamException e) {
+ errors.addFatalAndHalt("Unable to write " + tzLookupXmlFileName, e);
+ } finally {
+ errors.popScope();
+ }
+
+ errors.pushScope("write " + timeZoneIdsFileName);
+ try {
+ // Write out the tz IDs file used during later stages of the pipeline.
+ logInfo("Writing " + timeZoneIdsFileName);
+
+ TimeZoneIds timeZoneIds = outputData.getTimeZoneIds();
+ timeZoneIds.store(new File(timeZoneIdsFileName));
+ } catch (IOException e) {
+ errors.addFatalAndHalt("Unable to write " + timeZoneIdsFileName, e);
+ } finally {
+ errors.popScope();
+ }
+ }
+
+ private static OutputData createOutputData(String inputIanaVersion,
Map<String, List<String>> zoneTabMapping, List<CountryZonesFile.Country> countriesIn,
- Errors processingErrors) {
+ BackwardFile backwardIn, Errors errors) throws HaltExecutionException {
+
// Start constructing the output structure.
TzLookupFile.TimeZones timeZonesOut = new TzLookupFile.TimeZones(inputIanaVersion);
- TzLookupFile.CountryZones countryZonesOut = new TzLookupFile.CountryZones();
- timeZonesOut.setCountryZones(countryZonesOut);
+ TzLookupFile.CountryZones tzLookupCountryZones = new TzLookupFile.CountryZones();
+ timeZonesOut.setCountryZones(tzLookupCountryZones);
// The time use when sampling the offsets for a zone.
final long offsetSampleTimeMillis = getSampleOffsetTimeMillisForData(inputIanaVersion);
@@ -189,68 +282,72 @@
// to WW2) so we start looking at the beginning of "this year".
long everUseUtcStartTimeMillis = getYearStartTimeMillisForData(inputIanaVersion);
+ TzIdsProto.TimeZoneIds.Builder tzIdsBuilder = TzIdsProto.TimeZoneIds.newBuilder()
+ .setIanaVersion(inputIanaVersion);
+
// Process each Country.
for (CountryZonesFile.Country countryIn : countriesIn) {
String isoCode = countryIn.getIsoCode();
List<String> zoneTabCountryTimeZoneIds = zoneTabMapping.get(isoCode.toUpperCase());
if (zoneTabCountryTimeZoneIds == null) {
- processingErrors.addError("Country=" + isoCode + " missing from zone.tab");
+ errors.addError("Country=" + isoCode + " missing from zone.tab");
// No point in continuing.
continue;
}
- TzLookupFile.Country countryOut = processCountry(
+ CountryOutputData countryOutputData = processCountry(
offsetSampleTimeMillis, everUseUtcStartTimeMillis, countryIn,
- zoneTabCountryTimeZoneIds, processingErrors);
- if (processingErrors.hasFatal()) {
- // Stop if there's a fatal error, continue processing countries if there are just
- // errors.
- break;
- } else if (countryOut == null) {
+ zoneTabCountryTimeZoneIds, backwardIn, errors);
+ if (countryOutputData == null) {
+ // Continue processing countries if there are only errors.
continue;
}
- countryZonesOut.addCountry(countryOut);
+
+ tzLookupCountryZones.addCountry(countryOutputData.getTzLookupCountry());
+ tzIdsBuilder.addCountryMappings(countryOutputData.getTimeZoneIdsCountryMapping());
}
- return timeZonesOut;
+ errors.throwIfError("One or more countries failed");
+ TimeZoneIds timeZoneIds = new TimeZoneIds(tzIdsBuilder.build());
+ return new OutputData(timeZonesOut, timeZoneIds);
}
- private static TzLookupFile.Country processCountry(long offsetSampleTimeMillis,
+ private static CountryOutputData processCountry(long offsetSampleTimeMillis,
long everUseUtcStartTimeMillis, CountryZonesFile.Country countryIn,
- List<String> zoneTabCountryTimeZoneIds,
- Errors processingErrors) {
+ List<String> zoneTabCountryTimeZoneIds, BackwardFile backwardIn,
+ Errors errors) {
String isoCode = countryIn.getIsoCode();
- processingErrors.pushScope("country=" + isoCode);
+ errors.pushScope("country=" + isoCode);
try {
// Each Country must have >= 1 time zone.
List<CountryZonesFile.TimeZoneMapping> timeZonesIn =
countryIn.getTimeZoneMappingsList();
if (timeZonesIn.isEmpty()) {
- processingErrors.addError("No time zones");
+ errors.addError("No time zones");
// No point in continuing.
return null;
}
- // Look for duplicate time zone IDs.
List<String> countryTimeZoneIds = CountryZonesFileSupport.extractIds(timeZonesIn);
+
+ // Look for duplicate time zone IDs.
if (!Utils.allUnique(countryTimeZoneIds)) {
- processingErrors.addError("country's zones=" + countryTimeZoneIds
- + " contains duplicates");
+ errors.addError("country's zones=" + countryTimeZoneIds + " contains duplicates");
// No point in continuing.
return null;
}
// Each Country needs a default time zone ID (but we can guess in some cases).
- String defaultTimeZoneId = determineCountryDefaultZoneId(countryIn, processingErrors);
+ String defaultTimeZoneId = determineCountryDefaultZoneId(countryIn, errors);
if (defaultTimeZoneId == null) {
// No point in continuing.
return null;
}
boolean defaultTimeZoneBoost =
- determineCountryDefaultTimeZoneBoost(countryIn, processingErrors);
+ determineCountryDefaultTimeZoneBoost(countryIn, errors);
// Validate the default.
if (!countryTimeZoneIds.contains(defaultTimeZoneId)) {
- processingErrors.addError("defaultTimeZoneId=" + defaultTimeZoneId
+ errors.addError("defaultTimeZoneId=" + defaultTimeZoneId
+ " is not one of the country's zones=" + countryTimeZoneIds);
// No point in continuing.
return null;
@@ -258,98 +355,138 @@
// Validate the other zone IDs.
try {
- processingErrors.pushScope("validate country zone ids");
- boolean errors = false;
+ errors.pushScope("validate country zone ids");
for (String countryTimeZoneId : countryTimeZoneIds) {
if (invalidTimeZoneId(countryTimeZoneId)) {
- processingErrors.addError("countryTimeZoneId=" + countryTimeZoneId
+ errors.addError("countryTimeZoneId=" + countryTimeZoneId
+ " is not a valid zone ID");
- errors = true;
}
}
- if (errors) {
+ if (errors.hasError()) {
// No point in continuing.
return null;
}
} finally {
- processingErrors.popScope();
+ errors.popScope();
}
// Work out the hint for whether the country uses a zero offset from UTC.
boolean everUsesUtc = anyZonesUseUtc(countryTimeZoneIds, everUseUtcStartTimeMillis);
// Validate the country information against the equivalent information in zone.tab.
- processingErrors.pushScope("zone.tab comparison");
+ errors.pushScope("zone.tab comparison");
try {
// Look for unexpected duplicate time zone IDs in zone.tab
if (!Utils.allUnique(zoneTabCountryTimeZoneIds)) {
- processingErrors.addError(
- "Duplicate time zone IDs found:" + zoneTabCountryTimeZoneIds);
+ errors.addError("Duplicate time zone IDs found:" + zoneTabCountryTimeZoneIds);
// No point in continuing.
return null;
-
}
- if (!Utils.setEquals(zoneTabCountryTimeZoneIds, countryTimeZoneIds)) {
- processingErrors.addError("IANA lists " + isoCode
- + " as having zones: " + zoneTabCountryTimeZoneIds
- + ", but countryzones has " + countryTimeZoneIds);
+ // Validate the IDs being used against the IANA data for the country. If it fails
+ // the countryzones.txt needs to be updated with new IDs (or an alias can be added
+ // if there's some reason to keep using the old ID).
+ validateCountryZonesTzIdsAgainstIana(isoCode, zoneTabCountryTimeZoneIds,
+ timeZonesIn, backwardIn.getDirectLinks(), errors);
+ if (errors.hasError()) {
// No point in continuing.
return null;
}
} finally {
- processingErrors.popScope();
+ errors.popScope();
}
// Calculate countryZoneUsage.
- CountryZoneUsage countryZoneUsage =
- calculateCountryZoneUsage(countryIn, processingErrors);
+ CountryZoneUsage countryZoneUsage = calculateCountryZoneUsage(countryIn, errors);
if (countryZoneUsage == null) {
// No point in continuing with this country.
return null;
}
- // Add the country to the output structure.
+ // Create the tzlookup country structure.
TzLookupFile.Country countryOut = new TzLookupFile.Country(
isoCode, defaultTimeZoneId, defaultTimeZoneBoost, everUsesUtc);
// Process each input time zone.
for (CountryZonesFile.TimeZoneMapping timeZoneIn : timeZonesIn) {
- processingErrors.pushScope(
+ errors.pushScope(
"id=" + timeZoneIn.getId() + ", offset=" + timeZoneIn.getUtcOffset()
+ ", shownInPicker=" + timeZoneIn.getShownInPicker());
try {
- // Validate the offset information in countryIn.
- validateNonDstOffset(offsetSampleTimeMillis, countryIn, timeZoneIn,
- processingErrors);
-
String timeZoneInId = timeZoneIn.getId();
- boolean shownInPicker = timeZoneIn.getShownInPicker();
- if (!countryZoneUsage.hasEntry(timeZoneInId)) {
- // This implies a programming error.
- processingErrors.addFatal(
- "No entry in CountryZoneUsage for " + timeZoneInId);
- return null;
- }
// The notUsedAfterInstant can be null if the zone is used until at least
// ZONE_CALCS_END_INSTANT. That's what we want.
Instant notUsedAfterInstant =
countryZoneUsage.getNotUsedAfterInstant(timeZoneInId);
+ String notUsedReplacementId =
+ countryZoneUsage.getNotUsedReplacementId(timeZoneInId);
+
+ // Validate the offset information in countryIn.
+ validateNonDstOffset(offsetSampleTimeMillis, countryIn, timeZoneIn, errors);
+
+ boolean shownInPicker = timeZoneIn.getShownInPicker();
+ if (!countryZoneUsage.hasEntry(timeZoneInId)) {
+ // This implies a programming error.
+ errors.addError("No entry in CountryZoneUsage for " + timeZoneInId);
+ return null;
+ }
+
+ // Find all the alternative zone IDs for the chosen zone ID.
+ List<String> alternativeZoneIds =
+ new ArrayList<>(backwardIn.getAllAlternativeIds(timeZoneInId));
+ Collections.sort(alternativeZoneIds);
// Add the id mapping and associated metadata.
- TzLookupFile.TimeZoneMapping timeZoneIdOut =
- new TzLookupFile.TimeZoneMapping(
- timeZoneInId, shownInPicker, notUsedAfterInstant);
- countryOut.addTimeZoneIdentifier(timeZoneIdOut);
+ TzLookupFile.TimeZoneMapping timeZoneIdOut = new TzLookupFile.TimeZoneMapping(
+ timeZoneInId, shownInPicker, notUsedAfterInstant, notUsedReplacementId,
+ alternativeZoneIds);
+ countryOut.addTimeZoneMapping(timeZoneIdOut);
} finally {
- processingErrors.popScope();
+ errors.popScope();
}
}
- return countryOut;
+
+ // CountryMapping contains only information that is available from Country so we can
+ // currently build one from the other.
+ TzIdsProto.CountryMapping countryMappingProto =
+ TzLookupFile.Country.createCountryMappingProto(countryOut);
+
+ return new CountryOutputData(countryOut, countryMappingProto);
} finally{
// End of country processing.
- processingErrors.popScope();
+ errors.popScope();
+ }
+ }
+
+ private static void validateCountryZonesTzIdsAgainstIana(String isoCode,
+ List<String> zoneTabCountryTimeZoneIds,
+ List<CountryZonesFile.TimeZoneMapping> timeZoneMappings,
+ Map<String, String> zoneIdLinks, Errors errors) {
+
+ List<String> expectedIanaTimeZoneIds = new ArrayList<>();
+ for (CountryZonesFile.TimeZoneMapping mapping : timeZoneMappings) {
+ String timeZoneId = mapping.getId();
+ String expectedIanaTimeZoneId;
+ if (!mapping.hasAliasId()) {
+ expectedIanaTimeZoneId = timeZoneId;
+ } else {
+ String aliasTimeZoneId = mapping.getAliasId();
+
+ // Confirm the alias is valid.
+ if (!aliasTimeZoneId.equals(zoneIdLinks.get(timeZoneId))) {
+ errors.addError(timeZoneId + " does not link to " + aliasTimeZoneId);
+ return;
+ }
+ expectedIanaTimeZoneId = aliasTimeZoneId;
+ }
+ expectedIanaTimeZoneIds.add(expectedIanaTimeZoneId);
+ }
+
+ if (!Utils.setEquals(zoneTabCountryTimeZoneIds, expectedIanaTimeZoneIds)) {
+ errors.addError("IANA lists " + isoCode
+ + " as having zones: " + zoneTabCountryTimeZoneIds
+ + ", but countryzones has " + expectedIanaTimeZoneIds);
}
}
@@ -357,20 +494,20 @@
* Determines the default zone ID for the country.
*/
private static String determineCountryDefaultZoneId(
- CountryZonesFile.Country countryIn, Errors processingErrorsOut) {
+ CountryZonesFile.Country countryIn, Errors errors) {
List<CountryZonesFile.TimeZoneMapping> timeZonesIn = countryIn.getTimeZoneMappingsList();
String defaultTimeZoneId;
if (countryIn.hasDefaultTimeZoneId()) {
defaultTimeZoneId = countryIn.getDefaultTimeZoneId();
if (invalidTimeZoneId(defaultTimeZoneId)) {
- processingErrorsOut.addError(
+ errors.addError(
"Default time zone ID " + defaultTimeZoneId + " is not valid");
// No point in continuing.
return null;
}
} else {
if (timeZonesIn.size() > 1) {
- processingErrorsOut.addError(
+ errors.addError(
"To pick a default time zone there must be a single offset group");
// No point in continuing.
return null;
@@ -384,14 +521,14 @@
* Determines the defaultTimeZoneBoost value for the country.
*/
private static boolean determineCountryDefaultTimeZoneBoost(
- CountryZonesFile.Country countryIn, Errors processingErrorsOut) {
+ CountryZonesFile.Country countryIn, Errors errors) {
if (!countryIn.hasDefaultTimeZoneBoost()) {
return false;
}
boolean defaultTimeZoneBoost = countryIn.getDefaultTimeZoneBoost();
if (!countryIn.hasDefaultTimeZoneId() && defaultTimeZoneBoost) {
- processingErrorsOut.addError(
+ errors.addError(
"defaultTimeZoneBoost is specified but defaultTimeZoneId is not explicit");
}
@@ -459,7 +596,7 @@
try {
utcOffsetMillis = Utils.parseUtcOffsetToMillis(utcOffsetString);
} catch (ParseException e) {
- errors.addFatal("Bad offset string: " + utcOffsetString);
+ errors.addError("Bad offset string: " + utcOffsetString);
return;
}
@@ -471,7 +608,7 @@
String timeZoneIdIn = timeZoneIn.getId();
if (invalidTimeZoneId(timeZoneIdIn)) {
- errors.addFatal("Time zone ID=" + timeZoneIdIn + " is not valid");
+ errors.addError("Time zone ID=" + timeZoneIdIn + " is not valid");
return;
}
@@ -481,7 +618,7 @@
timeZone.getOffset(offsetSampleTimeMillis, false /* local */, offsets);
int actualOffsetMillis = offsets[0];
if (actualOffsetMillis != utcOffsetMillis) {
- errors.addFatal("Offset mismatch: You will want to confirm the ordering for "
+ errors.addError("Offset mismatch: You will want to confirm the ordering for "
+ country.getIsoCode() + " still makes sense. Raw offset for "
+ timeZoneIdIn + " is " + Utils.toUtcOffsetString(actualOffsetMillis)
+ " and not " + Utils.toUtcOffsetString(utcOffsetMillis)
@@ -490,21 +627,20 @@
}
private static CountryZoneUsage calculateCountryZoneUsage(
- CountryZonesFile.Country countryIn, Errors processingErrors) {
- processingErrors.pushScope("Building zone tree");
+ CountryZonesFile.Country countryIn, Errors errors) {
+ errors.pushScope("Building zone tree");
try {
CountryZoneTree countryZoneTree = CountryZoneTree.create(
countryIn, ZONE_USAGE_CALCS_START, ZONE_USAGE_CALCS_END);
List<String> countryIssues = countryZoneTree.validateNoPriorityClashes();
if (!countryIssues.isEmpty()) {
- processingErrors
- .addError("Issues validating country zone trees. Adjust priorities:");
- countryIssues.forEach(processingErrors::addError);
+ errors.addError("Issues validating country zone trees. Adjust priorities:");
+ countryIssues.forEach(errors::addError);
return null;
}
return countryZoneTree.calculateCountryZoneUsage(ZONE_USAGE_NOT_AFTER_CUT_OFF);
} finally {
- processingErrors.popScope();
+ errors.popScope();
}
}
@@ -520,4 +656,42 @@
private static void logInfo(String msg) {
System.err.println("I: " + msg);
}
+
+ private static class CountryOutputData {
+ private final TzLookupFile.Country tzLookupCountry;
+ private final TzIdsProto.CountryMapping timeZoneIdsCountryMapping;
+
+ private CountryOutputData(TzLookupFile.Country tzLookupCountry,
+ TzIdsProto.CountryMapping timeZoneIdsCountryMapping) {
+ this.tzLookupCountry = Objects.requireNonNull(tzLookupCountry);
+ this.timeZoneIdsCountryMapping = Objects.requireNonNull(timeZoneIdsCountryMapping);
+ }
+
+ private TzLookupFile.Country getTzLookupCountry() {
+ return tzLookupCountry;
+ }
+
+ private TzIdsProto.CountryMapping getTimeZoneIdsCountryMapping() {
+ return timeZoneIdsCountryMapping;
+ }
+ }
+
+ private static class OutputData {
+
+ private final TzLookupFile.TimeZones tzLookupTimeZones;
+ private final TimeZoneIds timeZoneIds;
+
+ private OutputData(TzLookupFile.TimeZones tzLookupTimeZones, TimeZoneIds timeZoneIds) {
+ this.tzLookupTimeZones = Objects.requireNonNull(tzLookupTimeZones);
+ this.timeZoneIds = Objects.requireNonNull(timeZoneIds);
+ }
+
+ private TzLookupFile.TimeZones getTzLookupTimeZones() {
+ return tzLookupTimeZones;
+ }
+
+ private TimeZoneIds getTimeZoneIds() {
+ return timeZoneIds;
+ }
+ }
}
diff --git a/input_tools/android/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/ZoneTabFile.java b/input_tools/android/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/ZoneTabFile.java
index a939ca5..6b960f9 100644
--- a/input_tools/android/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/ZoneTabFile.java
+++ b/input_tools/android/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/ZoneTabFile.java
@@ -19,6 +19,7 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
+import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -36,7 +37,7 @@
private ZoneTabFile() {}
- static ZoneTabFile parse(String zoneTabFile) throws IOException {
+ static ZoneTabFile parse(String zoneTabFile) throws IOException, ParseException {
ZoneTabFile zoneTab = new ZoneTabFile();
List<String> lines = Files
@@ -49,9 +50,9 @@
.collect(Collectors.toList());
for (String mappingLine : mappingLines) {
- String[] fields = mappingLine.split("\t");
+ String[] fields = mappingLine.split("\t+");
if (fields.length < 3) {
- throw new IOException("Line is malformed: " + mappingLine);
+ throw new ParseException("Line is malformed: " + mappingLine, 0);
}
CountryEntry countryEntry = new CountryEntry(fields[0], fields[2]);
zoneTab.addCountryEntry(countryEntry);
@@ -74,7 +75,10 @@
countryEntry.isoCode, k -> new ArrayList<>());
olsonIds.add(countryEntry.olsonId);
}
- return countryIsoToOlsonIdsMap;
+ // Replace each list value with an immutable one.
+ countryIsoToOlsonIdsMap.forEach(
+ (k, v) -> countryIsoToOlsonIdsMap.put(k, Collections.unmodifiableList(v)));
+ return Collections.unmodifiableMap(countryIsoToOlsonIdsMap);
}
static class CountryEntry {
diff --git a/input_tools/android/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/zonetree/CountryZoneTree.java b/input_tools/android/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/zonetree/CountryZoneTree.java
index 23c2b0a..43ace04 100644
--- a/input_tools/android/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/zonetree/CountryZoneTree.java
+++ b/input_tools/android/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/zonetree/CountryZoneTree.java
@@ -15,8 +15,8 @@
*/
package com.android.libcore.timezone.tzlookup.zonetree;
-import com.android.libcore.timezone.tzlookup.proto.CountryZonesFile;
-import com.android.libcore.timezone.tzlookup.proto.CountryZonesFile.Country;
+import com.android.libcore.timezone.countryzones.proto.CountryZonesFile;
+import com.android.libcore.timezone.countryzones.proto.CountryZonesFile.Country;
import com.android.libcore.timezone.tzlookup.zonetree.ZoneOffsetPeriod.ZonePeriodsKey;
import com.ibm.icu.text.TimeZoneNames;
import com.ibm.icu.util.BasicTimeZone;
@@ -31,6 +31,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import static java.util.stream.Collectors.toList;
@@ -510,27 +511,56 @@
}
Instant endInstant = node.getEndInstant();
+ String replacementTimeZoneId = getReplacementTimeZoneIdOrNull(node);
if (!node.isLeaf()) {
ZoneInfo primaryZone = node.getPrimaryZoneInfo();
- addZoneEntryIfMissing(endInstant, primaryZone);
+ addZoneEntryIfMissing(endInstant, replacementTimeZoneId, primaryZone);
} else {
// In some rare cases (e.g. Canada: Swift_Current and Creston) zones have agreed
// completely since 1970 so some leaves may have multiple zones. So, attempt to
// add all zones for leaves, not just the primary.
for (ZoneInfo zoneInfo : node.getZoneInfos()) {
- addZoneEntryIfMissing(endInstant, zoneInfo);
+ addZoneEntryIfMissing(endInstant, replacementTimeZoneId, zoneInfo);
}
}
}
- private void addZoneEntryIfMissing(Instant endInstant, ZoneInfo zoneInfo) {
+ /**
+ * Takes a {@link ZoneNode} and if the parent node has a different primary zone ID, then
+ * this method returns that zone ID. {@code null} is returned otherwise.
+ */
+ private String getReplacementTimeZoneIdOrNull(ZoneNode node) {
+ if (node.isRoot()) {
+ // There is no parent node, so there can be no replacement ID.
+ return null;
+ }
+
+ String zoneId = node.primaryZoneInfo.getZoneId();
+ String replacementId = node.getParent().primaryZoneInfo.getZoneId();
+ if (Objects.equals(zoneId, replacementId)) {
+ // Often, the parent node will have the same primary zone ID. A zone ID cannot
+ // replace itself. Since we're traversing the tree "preorder" this is fine - if
+ // there is a replacement later in time it will already have been found.
+ return null;
+ }
+ return replacementId;
+ }
+
+ private void addZoneEntryIfMissing(
+ Instant endInstant, String replacementTimeZoneId, ZoneInfo zoneInfo) {
String zoneId = zoneInfo.getZoneId();
+
+ if (Objects.equals(zoneId, replacementTimeZoneId)) {
+ throw new IllegalStateException(zoneId + " cannot replace itself. Cycle!");
+ }
+
if (!notAfterCutOff.isAfter(endInstant)) {
// notAfterCutOff <= endInstant
endInstant = null;
+ replacementTimeZoneId = null;
}
if (!zoneUsage.hasEntry(zoneId)) {
- zoneUsage.addEntry(zoneId, endInstant);
+ zoneUsage.addEntry(zoneId, endInstant, replacementTimeZoneId);
}
}
diff --git a/input_tools/android/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/zonetree/CountryZoneUsage.java b/input_tools/android/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/zonetree/CountryZoneUsage.java
index 813d741..334c66b 100644
--- a/input_tools/android/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/zonetree/CountryZoneUsage.java
+++ b/input_tools/android/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/zonetree/CountryZoneUsage.java
@@ -18,6 +18,7 @@
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
+import java.util.Objects;
/**
* A record for a country of when zones stopped being (effectively) used.
@@ -34,12 +35,12 @@
return isoCode;
}
- void addEntry(String zoneId, Instant notUsedAfterInstant) {
+ void addEntry(String zoneId, Instant notUsedAfterInstant, String notUsedReplacementId) {
if (zoneIdEntryMap.containsKey(zoneId)) {
throw new IllegalArgumentException(
"Entry exists for " + zoneId + " for isoCode=" + isoCode);
}
- zoneIdEntryMap.put(zoneId, new Entry(zoneId, notUsedAfterInstant));
+ zoneIdEntryMap.put(zoneId, new Entry(zoneId, notUsedAfterInstant, notUsedReplacementId));
}
public boolean hasEntry(String zoneId) {
@@ -50,18 +51,34 @@
Entry entry = zoneIdEntryMap.get(zoneId);
if (entry == null) {
throw new IllegalArgumentException(
- "No entry for " + zoneId+ " for isoCode=" + isoCode);
+ "No entry for " + zoneId + " for isoCode=" + isoCode);
}
return entry.notUsedAfter;
}
+ public String getNotUsedReplacementId(String zoneId) {
+ Entry entry = zoneIdEntryMap.get(zoneId);
+ if (entry == null) {
+ throw new IllegalArgumentException(
+ "No entry for " + zoneId + " for isoCode=" + isoCode);
+ }
+ return entry.notUsedReplacementId;
+ }
+
private static class Entry {
final String zoneId;
final Instant notUsedAfter;
+ final String notUsedReplacementId;
- Entry(String zoneId, Instant notUsedAfter) {
- this.zoneId = zoneId;
+ Entry(String zoneId, Instant notUsedAfter, String notUsedReplacementId) {
+ this.zoneId = Objects.requireNonNull(zoneId);
+
+ if ((notUsedAfter == null) != (notUsedReplacementId == null)) {
+ throw new IllegalArgumentException(
+ "Both notUsedAfter and notUsedReplacement, or neither required");
+ }
this.notUsedAfter = notUsedAfter;
+ this.notUsedReplacementId = notUsedReplacementId;
}
}
}
diff --git a/input_tools/android/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/zonetree/UniqueZonesVisualizer.java b/input_tools/android/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/zonetree/UniqueZonesVisualizer.java
index 8d6f6c7..258872f 100644
--- a/input_tools/android/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/zonetree/UniqueZonesVisualizer.java
+++ b/input_tools/android/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/zonetree/UniqueZonesVisualizer.java
@@ -15,10 +15,10 @@
*/
package com.android.libcore.timezone.tzlookup.zonetree;
+import com.android.libcore.timezone.countryzones.proto.CountryZonesFile.Country;
+import com.android.libcore.timezone.countryzones.proto.CountryZonesFile.CountryZones;
import com.android.libcore.timezone.tzlookup.CountryZonesFileSupport;
import com.android.libcore.timezone.tzlookup.TzLookupGenerator;
-import com.android.libcore.timezone.tzlookup.proto.CountryZonesFile.Country;
-import com.android.libcore.timezone.tzlookup.proto.CountryZonesFile.CountryZones;
import java.io.IOException;
import java.time.Instant;
diff --git a/input_tools/android/tzlookup_generator/src/main/proto/country_zones_file.proto b/input_tools/android/tzlookup_generator/src/main/proto/country_zones_file.proto
index 6da1c29..de3085b 100644
--- a/input_tools/android/tzlookup_generator/src/main/proto/country_zones_file.proto
+++ b/input_tools/android/tzlookup_generator/src/main/proto/country_zones_file.proto
@@ -16,10 +16,10 @@
syntax = "proto2";
-option java_package = "com.android.libcore.timezone.tzlookup.proto";
+option java_package = "com.android.libcore.timezone.countryzones.proto";
option java_multiple_files = false;
-package com.android.libcore.timezone.tzlookup.proto;
+package com.android.libcore.timezone.countryzones.proto;
message CountryZones {
required string ianaVersion = 1;
@@ -35,7 +35,8 @@
message TimeZoneMapping {
required string id = 1;
- required string utcOffset = 2;
- optional bool shownInPicker = 3 [default = true];
- optional uint32 priority = 4 [default = 1];
+ optional string aliasId = 2;
+ required string utcOffset = 3;
+ optional bool shownInPicker = 4 [default = true];
+ optional uint32 priority = 5 [default = 1];
}
diff --git a/input_tools/android/tzlookup_generator/src/test/java/com/android/libcore/timezone/tzlookup/BackwardFileTest.java b/input_tools/android/tzlookup_generator/src/test/java/com/android/libcore/timezone/tzlookup/BackwardFileTest.java
new file mode 100644
index 0000000..bed94e5
--- /dev/null
+++ b/input_tools/android/tzlookup_generator/src/test/java/com/android/libcore/timezone/tzlookup/BackwardFileTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+package com.android.libcore.timezone.tzlookup;
+
+import static junit.framework.TestCase.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.libcore.timezone.testing.TestUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.text.ParseException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class BackwardFileTest {
+
+ private Path tempDir;
+
+ @Before
+ public void setUp() throws Exception {
+ tempDir = Files.createTempDirectory("BackwardFileTest");
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ TestUtils.deleteDir(tempDir);
+ }
+
+ @Test
+ public void parseEmpty() throws Exception {
+ String file = createFile("");
+ BackwardFile backward = BackwardFile.parse(file);
+ assertTrue(backward.getDirectLinks().isEmpty());
+ }
+
+ @Test
+ public void parseIgnoresCommentsAndEmptyLines() throws Exception {
+ String file = createFile(
+ "# This is a comment",
+ "",
+ "# And another",
+ "Link\tAmerica/Nuuk\t\tAmerica/Godthab"
+ );
+ BackwardFile backward = BackwardFile.parse(file);
+
+ Map<String, String> expectedLinks = new HashMap<>();
+ expectedLinks.put("America/Godthab", "America/Nuuk");
+ assertEquals(expectedLinks, backward.getDirectLinks());
+ }
+
+ @Test
+ public void parse() throws Exception {
+ String file = createFile(
+ "# This is a comment",
+ "Link\tAmerica/Nuuk\t\tAmerica/Godthab",
+ "# This is a comment",
+ "Link\tAfrica/Nairobi\t\tAfrica/Asmera",
+ "# This is a comment",
+ "Link\tAfrica/Abidjan\t\tAfrica/Timbuktu",
+ "# This is a comment",
+ "Link\tAfrica/Timbuktu\t\tAfrica/Timbuktu2"
+ );
+ BackwardFile backward = BackwardFile.parse(file);
+ Map<String, String> expectedLinks = new HashMap<>();
+ expectedLinks.put("America/Godthab", "America/Nuuk");
+ expectedLinks.put("Africa/Asmera", "Africa/Nairobi");
+ expectedLinks.put("Africa/Timbuktu", "Africa/Abidjan");
+ expectedLinks.put("Africa/Timbuktu2", "Africa/Timbuktu");
+ assertEquals(expectedLinks, backward.getLinks());
+
+ Map<String, String> expectedDirectLinks = new HashMap<>();
+ expectedDirectLinks.put("America/Godthab", "America/Nuuk");
+ expectedDirectLinks.put("Africa/Asmera", "Africa/Nairobi");
+ expectedDirectLinks.put("Africa/Timbuktu", "Africa/Abidjan");
+ expectedDirectLinks.put("Africa/Timbuktu2", "Africa/Abidjan");
+ assertEquals(expectedDirectLinks, backward.getDirectLinks());
+
+ assertEquals(set("Africa/Abidjan", "Africa/Timbuktu2"),
+ backward.getAllAlternativeIds("Africa/Timbuktu"));
+ assertEquals(set("Africa/Abidjan", "Africa/Timbuktu"),
+ backward.getAllAlternativeIds("Africa/Timbuktu2"));
+ assertEquals(set("Africa/Timbuktu", "Africa/Timbuktu2"),
+ backward.getAllAlternativeIds("Africa/Abidjan"));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void getDirectLinksWithLoop() throws Exception {
+ String file = createFile(
+ "Link\tAmerica/New_York\t\tAmerica/Los_Angeles",
+ "Link\tAmerica/Los_Angeles\t\tAmerica/Phoenix",
+ "Link\tAmerica/Phoenix\t\tAmerica/New_York"
+ );
+ BackwardFile backward = BackwardFile.parse(file);
+ backward.getDirectLinks();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void parseWithDupes() throws Exception {
+ String file = createFile(
+ "Link\tAmerica/New_York\t\tAmerica/Los_Angeles",
+ "Link\tAmerica/Phoenix\t\tAmerica/Los_Angeles"
+ );
+ BackwardFile.parse(file);
+ }
+
+ @Test(expected = ParseException.class)
+ public void parseMalformedFile() throws Exception {
+ // Mapping lines are expected to have at least three tab-separated columns.
+ String file = createFile("NotLink\tBooHoo");
+ BackwardFile.parse(file);
+ }
+
+ private String createFile(String... lines) throws IOException {
+ return TestUtils.createFile(tempDir, lines);
+ }
+
+ private static <T> Set<T> set(T... values) {
+ return new HashSet<>(Arrays.asList(values));
+ }
+}
diff --git a/input_tools/android/tzlookup_generator/src/test/java/com/android/libcore/timezone/tzlookup/TzLookupGeneratorTest.java b/input_tools/android/tzlookup_generator/src/test/java/com/android/libcore/timezone/tzlookup/TzLookupGeneratorTest.java
index 6a4843f..75c5ae9 100644
--- a/input_tools/android/tzlookup_generator/src/test/java/com/android/libcore/timezone/tzlookup/TzLookupGeneratorTest.java
+++ b/input_tools/android/tzlookup_generator/src/test/java/com/android/libcore/timezone/tzlookup/TzLookupGeneratorTest.java
@@ -16,8 +16,17 @@
package com.android.libcore.timezone.tzlookup;
+import static com.android.libcore.timezone.countryzones.proto.CountryZonesFile.Country;
+import static com.android.libcore.timezone.testing.TestUtils.assertAbsent;
+import static com.android.libcore.timezone.testing.TestUtils.assertContains;
+import static com.android.libcore.timezone.testing.TestUtils.createFile;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.libcore.timezone.countryzones.proto.CountryZonesFile;
import com.android.libcore.timezone.testing.TestUtils;
-import com.android.libcore.timezone.tzlookup.proto.CountryZonesFile;
+import com.android.timezone.tzids.proto.TzIdsProto;
import com.google.protobuf.TextFormat;
import com.ibm.icu.util.TimeZone;
@@ -32,20 +41,15 @@
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.stream.Collectors;
-import static com.android.libcore.timezone.testing.TestUtils.assertAbsent;
-import static com.android.libcore.timezone.testing.TestUtils.assertContains;
-import static com.android.libcore.timezone.testing.TestUtils.createFile;
-import static com.android.libcore.timezone.tzlookup.proto.CountryZonesFile.Country;
-import static junit.framework.TestCase.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
public class TzLookupGeneratorTest {
- public static final String INVALID_TIME_ZONE_ID = "NOT_A_VALID_ID";
+ private static final String INVALID_TIME_ZONE_ID = "NOT_A_VALID_ID";
+ private static final String TZDB_VERSION = TimeZone.getTZDataVersion();
private Path tempDir;
@@ -64,10 +68,12 @@
String countryZonesFile = createFile(tempDir, "THIS IS NOT A VALID FILE");
List<ZoneTabFile.CountryEntry> gbZoneTabEntries = createValidZoneTabEntriesGb();
String zoneTabFile = createZoneTabFile(gbZoneTabEntries);
- String outputFile = Files.createTempFile(tempDir, "out", null /* suffix */).toString();
+ String backwardFile = createBackwardFile(createEmptyBackwardLinks());
+ String tzLookupFile = createTempFileName("tzlookup");
+ String tzIdsFile = createTempFileName("tzids");
- TzLookupGenerator tzLookupGenerator =
- new TzLookupGenerator(countryZonesFile, zoneTabFile, outputFile);
+ TzLookupGenerator tzLookupGenerator = new TzLookupGenerator(
+ countryZonesFile, zoneTabFile, backwardFile, tzLookupFile, tzIdsFile);
assertFalse(tzLookupGenerator.execute());
}
@@ -82,15 +88,17 @@
List<ZoneTabFile.CountryEntry> gbZoneTabEntries = createValidZoneTabEntriesGb();
String zoneTabFile = createZoneTabFile(gbZoneTabEntries);
+ String backwardFile = createBackwardFile(createEmptyBackwardLinks());
- String outputFile = Files.createTempFile(tempDir, "out", null /* suffix */).toString();
+ String tzLookupFile = createTempFileName("tzlookup");
+ String tzIdsFile = createTempFileName("tzids");
- TzLookupGenerator tzLookupGenerator =
- new TzLookupGenerator(countryZonesFile, zoneTabFile, outputFile);
+ TzLookupGenerator tzLookupGenerator = new TzLookupGenerator(
+ countryZonesFile, zoneTabFile, backwardFile, tzLookupFile, tzIdsFile);
assertFalse(tzLookupGenerator.execute());
- Path outputFilePath = Paths.get(outputFile);
- assertEquals(0, Files.size(outputFilePath));
+ assertFileMissing(tzLookupFile);
+ assertFileMissing(tzIdsFile);
}
@Test
@@ -100,17 +108,19 @@
createValidCountryGb().toBuilder().clearTimeZoneMappings().build();
CountryZonesFile.CountryZones countryZones = createValidCountryZones(gbWithoutZones);
String countryZonesFile = createCountryZonesFile(countryZones);
+ String backwardFile = createBackwardFile(createEmptyBackwardLinks());
String zoneTabFile = createZoneTabFile(createValidZoneTabEntriesGb());
- String outputFile = Files.createTempFile(tempDir, "out", null /* suffix */).toString();
+ String tzLookupFile = createTempFileName("tzlookup");
+ String tzIdsFile = createTempFileName("tzids");
- TzLookupGenerator tzLookupGenerator =
- new TzLookupGenerator(countryZonesFile, zoneTabFile, outputFile);
+ TzLookupGenerator tzLookupGenerator = new TzLookupGenerator(
+ countryZonesFile, zoneTabFile, backwardFile, tzLookupFile, tzIdsFile);
assertFalse(tzLookupGenerator.execute());
- Path outputFilePath = Paths.get(outputFile);
- assertEquals(0, Files.size(outputFilePath));
+ assertFileMissing(tzLookupFile);
+ assertFileMissing(tzIdsFile);
}
@Test
@@ -125,17 +135,19 @@
CountryZonesFile.CountryZones countryZones =
createValidCountryZones(gbWithDuplicateZones);
String countryZonesFile = createCountryZonesFile(countryZones);
+ String backwardFile = createBackwardFile(createEmptyBackwardLinks());
String zoneTabFile = createZoneTabFile(createValidZoneTabEntriesGb());
- String outputFile = Files.createTempFile(tempDir, "out", null /* suffix */).toString();
+ String tzLookupFile = createTempFileName("tzlookup");
+ String tzIdsFile = createTempFileName("tzids");
- TzLookupGenerator tzLookupGenerator =
- new TzLookupGenerator(countryZonesFile, zoneTabFile, outputFile);
+ TzLookupGenerator tzLookupGenerator = new TzLookupGenerator(
+ countryZonesFile, zoneTabFile, backwardFile, tzLookupFile, tzIdsFile);
assertFalse(tzLookupGenerator.execute());
- Path outputFilePath = Paths.get(outputFile);
- assertEquals(0, Files.size(outputFilePath));
+ assertFileMissing(tzLookupFile);
+ assertFileMissing(tzIdsFile);
}
@Test
@@ -150,15 +162,17 @@
List<ZoneTabFile.CountryEntry> gbZoneTabEntries = createValidZoneTabEntriesGb();
String zoneTabFile = createZoneTabFile(gbZoneTabEntries);
+ String backwardFile = createBackwardFile(createEmptyBackwardLinks());
- String outputFile = Files.createTempFile(tempDir, "out", null /* suffix */).toString();
+ String tzLookupFile = createTempFileName("tzlookup");
+ String tzIdsFile = createTempFileName("tzids");
- TzLookupGenerator tzLookupGenerator =
- new TzLookupGenerator(countryZonesFile, zoneTabFile, outputFile);
+ TzLookupGenerator tzLookupGenerator = new TzLookupGenerator(
+ countryZonesFile, zoneTabFile, backwardFile, tzLookupFile, tzIdsFile);
assertFalse(tzLookupGenerator.execute());
- Path outputFilePath = Paths.get(outputFile);
- assertEquals(0, Files.size(outputFilePath));
+ assertFileMissing(tzLookupFile);
+ assertFileMissing(tzIdsFile);
}
@Test
@@ -172,15 +186,17 @@
List<ZoneTabFile.CountryEntry> gbZoneTabEntries = createValidZoneTabEntriesGb();
String zoneTabFile = createZoneTabFile(gbZoneTabEntries);
+ String backwardFile = createBackwardFile(createEmptyBackwardLinks());
- String outputFile = Files.createTempFile(tempDir, "out", null /* suffix */).toString();
+ String tzLookupFile = createTempFileName("tzlookup");
+ String tzIdsFile = createTempFileName("tzids");
- TzLookupGenerator tzLookupGenerator =
- new TzLookupGenerator(countryZonesFile, zoneTabFile, outputFile);
+ TzLookupGenerator tzLookupGenerator = new TzLookupGenerator(
+ countryZonesFile, zoneTabFile, backwardFile, tzLookupFile, tzIdsFile);
assertFalse(tzLookupGenerator.execute());
- Path outputFilePath = Paths.get(outputFile);
- assertEquals(0, Files.size(outputFilePath));
+ assertFileMissing(tzLookupFile);
+ assertFileMissing(tzIdsFile);
}
@Test
@@ -194,10 +210,11 @@
.clearDefaultTimeZoneId().build();
List<ZoneTabFile.CountryEntry> gbZoneTabEntries = createValidZoneTabEntriesGb();
- String tzLookupXml = generateTzLookupXml(gbWithoutDefault, gbZoneTabEntries);
+ OutputData outputData =
+ generateOutputData(gbWithoutDefault, gbZoneTabEntries, createEmptyBackwardLinks());
// Check gb's time zone was defaulted.
- assertContains(tzLookupXml, "code=\"gb\" default=\"" + gbTimeZoneId + "\"");
+ assertContains(outputData.tzLookupXml, "code=\"gb\" default=\"" + gbTimeZoneId + "\"");
}
@Test
@@ -211,10 +228,11 @@
.build();
List<ZoneTabFile.CountryEntry> gbZoneTabEntries = createValidZoneTabEntriesGb();
- String tzLookupXml = generateTzLookupXml(gbWithExplicitDefaultTimeZone, gbZoneTabEntries);
+ OutputData outputData = generateOutputData(
+ gbWithExplicitDefaultTimeZone, gbZoneTabEntries, createEmptyBackwardLinks());
// Check gb's time zone was defaulted.
- assertContains(tzLookupXml, "code=\"gb\" default=\"" + gbTimeZoneId + "\"");
+ assertContains(outputData.tzLookupXml, "code=\"gb\" default=\"" + gbTimeZoneId + "\"");
}
@Test
@@ -229,15 +247,17 @@
List<ZoneTabFile.CountryEntry> gbZoneTabEntries = createValidZoneTabEntriesGb();
String zoneTabFile = createZoneTabFile(gbZoneTabEntries);
+ String backwardFile = createBackwardFile(createEmptyBackwardLinks());
- String outputFile = Files.createTempFile(tempDir, "out", null /* suffix */).toString();
+ String tzLookupFile = createTempFileName("tzlookup");
+ String tzIdsFile = createTempFileName("tzids");
- TzLookupGenerator tzLookupGenerator =
- new TzLookupGenerator(countryZonesFile, zoneTabFile, outputFile);
+ TzLookupGenerator tzLookupGenerator = new TzLookupGenerator(
+ countryZonesFile, zoneTabFile, backwardFile, tzLookupFile, tzIdsFile);
assertFalse(tzLookupGenerator.execute());
- Path outputFilePath = Paths.get(outputFile);
- assertEquals(0, Files.size(outputFilePath));
+ assertFileMissing(tzLookupFile);
+ assertFileMissing(tzIdsFile);
}
@Test
@@ -251,15 +271,17 @@
List<ZoneTabFile.CountryEntry> gbZoneTabEntries = createValidZoneTabEntriesGb();
String zoneTabFile = createZoneTabFile(gbZoneTabEntries);
+ String backwardFile = createBackwardFile(createEmptyBackwardLinks());
- String outputFile = Files.createTempFile(tempDir, "out", null /* suffix */).toString();
+ String tzLookupFile = createTempFileName("tzlookup");
+ String tzIdsFile = createTempFileName("tzids");
- TzLookupGenerator tzLookupGenerator =
- new TzLookupGenerator(countryZonesFile, zoneTabFile, outputFile);
+ TzLookupGenerator tzLookupGenerator = new TzLookupGenerator(
+ countryZonesFile, zoneTabFile, backwardFile, tzLookupFile, tzIdsFile);
assertFalse(tzLookupGenerator.execute());
- Path outputFilePath = Paths.get(outputFile);
- assertEquals(0, Files.size(outputFilePath));
+ assertFileMissing(tzLookupFile);
+ assertFileMissing(tzIdsFile);
}
@Test
@@ -271,15 +293,16 @@
String zoneTabFile =
createZoneTabFile(createValidZoneTabEntriesFr(), createValidZoneTabEntriesUs());
+ String backwardFile = createBackwardFile(createEmptyBackwardLinks());
+ String tzLookupFile = createTempFileName("tzlookup");
+ String tzIdsFile = createTempFileName("tzids");
- String outputFile = Files.createTempFile(tempDir, "out", null /* suffix */).toString();
-
- TzLookupGenerator tzLookupGenerator =
- new TzLookupGenerator(countryZonesFile, zoneTabFile, outputFile);
+ TzLookupGenerator tzLookupGenerator = new TzLookupGenerator(
+ countryZonesFile, zoneTabFile, backwardFile, tzLookupFile, tzIdsFile);
assertFalse(tzLookupGenerator.execute());
- Path outputFilePath = Paths.get(outputFile);
- assertEquals(0, Files.size(outputFilePath));
+ assertFileMissing(tzLookupFile);
+ assertFileMissing(tzIdsFile);
}
@Test
@@ -293,15 +316,16 @@
String countryZonesFile = createCountryZonesFile(countryZones);
String zoneTabFile = createZoneTabFile(createValidZoneTabEntriesGb());
+ String backwardFile = createBackwardFile(createEmptyBackwardLinks());
+ String tzLookupFile = createTempFileName("tzlookup");
+ String tzIdsFile = createTempFileName("tzids");
- String outputFile = Files.createTempFile(tempDir, "out", null /* suffix */).toString();
-
- TzLookupGenerator tzLookupGenerator =
- new TzLookupGenerator(countryZonesFile, zoneTabFile, outputFile);
+ TzLookupGenerator tzLookupGenerator = new TzLookupGenerator(
+ countryZonesFile, zoneTabFile, backwardFile, tzLookupFile, tzIdsFile);
assertFalse(tzLookupGenerator.execute());
- Path outputFilePath = Paths.get(outputFile);
- assertEquals(0, Files.size(outputFilePath));
+ assertFileMissing(tzLookupFile);
+ assertFileMissing(tzIdsFile);
}
@Test
@@ -313,14 +337,16 @@
String zoneTabFileWithDupes = createZoneTabFile(
createValidZoneTabEntriesGb(), createValidZoneTabEntriesGb());
- String outputFile = Files.createTempFile(tempDir, "out", null /* suffix */).toString();
+ String backwardFile = createBackwardFile(createEmptyBackwardLinks());
+ String tzLookupFile = createTempFileName("tzlookup");
+ String tzIdsFile = createTempFileName("tzids");
- TzLookupGenerator tzLookupGenerator =
- new TzLookupGenerator(countryZonesFile, zoneTabFileWithDupes, outputFile);
+ TzLookupGenerator tzLookupGenerator = new TzLookupGenerator(
+ countryZonesFile, zoneTabFileWithDupes, backwardFile, tzLookupFile, tzIdsFile);
assertFalse(tzLookupGenerator.execute());
- Path outputFilePath = Paths.get(outputFile);
- assertEquals(0, Files.size(outputFilePath));
+ assertFileMissing(tzLookupFile);
+ assertFileMissing(tzIdsFile);
}
@Test
@@ -334,15 +360,17 @@
String countryZonesFile = createCountryZonesFile(countryZones);
String zoneTabFile = createZoneTabFile(createValidZoneTabEntriesGb());
+ String backwardFile = createBackwardFile(createEmptyBackwardLinks());
- String outputFile = Files.createTempFile(tempDir, "out", null /* suffix */).toString();
+ String tzLookupFile = createTempFileName("tzlookup");
+ String tzIdsFile = createTempFileName("tzids");
- TzLookupGenerator tzLookupGenerator =
- new TzLookupGenerator(countryZonesFile, zoneTabFile, outputFile);
+ TzLookupGenerator tzLookupGenerator = new TzLookupGenerator(
+ countryZonesFile, zoneTabFile, backwardFile, tzLookupFile, tzIdsFile);
assertFalse(tzLookupGenerator.execute());
- Path outputFilePath = Paths.get(outputFile);
- assertEquals(0, Files.size(outputFilePath));
+ assertFileMissing(tzLookupFile);
+ assertFileMissing(tzIdsFile);
}
@Test
@@ -360,33 +388,210 @@
new ArrayList<>(createValidZoneTabEntriesGb());
zoneTabEntriesWithBadId.add(new ZoneTabFile.CountryEntry("GB", INVALID_TIME_ZONE_ID));
String zoneTabFile = createZoneTabFile(zoneTabEntriesWithBadId);
+ String backwardFile = createBackwardFile(createEmptyBackwardLinks());
- String outputFile = Files.createTempFile(tempDir, "out", null /* suffix */).toString();
+ String tzLookupFile = createTempFileName("tzlookup");
+ String tzIdsFile = createTempFileName("tzids");
- TzLookupGenerator tzLookupGenerator =
- new TzLookupGenerator(countryZonesFile, zoneTabFile, outputFile);
+ TzLookupGenerator tzLookupGenerator = new TzLookupGenerator(
+ countryZonesFile, zoneTabFile, backwardFile, tzLookupFile, tzIdsFile);
assertFalse(tzLookupGenerator.execute());
- Path outputFilePath = Paths.get(outputFile);
- assertEquals(0, Files.size(outputFilePath));
+ assertFileMissing(tzLookupFile);
+ assertFileMissing(tzIdsFile);
+ }
+
+ @Test
+ public void badBackwardFile() throws Exception {
+ CountryZonesFile.CountryZones countryZones = createValidCountryZones(createValidCountryGb());
+ String countryZonesFile = createCountryZonesFile(countryZones);
+ String zoneTabFile = createZoneTabFile(createValidZoneTabEntriesGb());
+
+ String badBackwardFile = TestUtils.createFile(tempDir, "THIS IS NOT VALID");
+
+ String tzLookupFile = createTempFileName("tzlookup");
+ String tzIdsFile = createTempFileName("tzids");
+
+ TzLookupGenerator tzLookupGenerator = new TzLookupGenerator(
+ countryZonesFile, zoneTabFile, badBackwardFile, tzLookupFile, tzIdsFile);
+ assertFalse(tzLookupGenerator.execute());
+
+ assertFileMissing(tzLookupFile);
+ assertFileMissing(tzIdsFile);
+ }
+
+ @Test
+ public void checkNormalLinks() throws Exception {
+ String countryZonesText = "isoCode:\"gb\"\n"
+ + "timeZoneMappings:<\n"
+ + " utcOffset:\"0:00\"\n"
+ + " id:\"Europe/London\"\n"
+ + ">\n";
+
+ Country country = parseCountry(countryZonesText);
+ List<ZoneTabFile.CountryEntry> zoneTab = Arrays.asList(
+ new ZoneTabFile.CountryEntry("GB", "Europe/London"));
+ Map<String, String> backwardLinks = new HashMap<>();
+ // GB is an obsoleted ID for Europe/London.
+ backwardLinks.put("GB", "Europe/London");
+
+ OutputData outputData = generateOutputData(country, zoneTab, backwardLinks);
+
+ // GB will be listed as an alternative for Europe/London.
+ String expectedTzLookupXmlLine = "<id alts=\"GB\">Europe/London</id>\n";
+ assertContains(outputData.tzLookupXml, expectedTzLookupXmlLine);
+
+ TzIdsProto.TimeZoneIds.Builder tzIdsBuilder = TzIdsProto.TimeZoneIds
+ .newBuilder()
+ .setIanaVersion(TZDB_VERSION);
+ TzIdsProto.CountryMapping.Builder b = TzIdsProto.CountryMapping.newBuilder()
+ .setIsoCode("gb")
+ .addTimeZoneIds("Europe/London");
+ addLink(b, "GB" /* alternativeId */, "Europe/London" /* preferredId */);
+ tzIdsBuilder.addCountryMappings(b);
+ assertEquals(tzIdsBuilder.build(), outputData.timeZoneIds);
+ }
+
+ @Test
+ public void usingOldIdsInCountryTextIsValid() throws Exception {
+ // This simulates a case where America/Godthab has been superseded by America/Nuuk in IANA
+ // data, but Android wants to continue using America/Godthab. This is signaled as deliberate
+ // through the use of the aliasId in countryzones.txt (otherwise the tooling will complain,
+ // see next test).
+ String countryZonesWithOldIdText =
+ "isoCode:\"gl\"\n"
+ + "defaultTimeZoneId:\"America/Godthab\"\n"
+ + "timeZoneMappings:<\n"
+ + " utcOffset:\"0:00\"\n"
+ + " id:\"America/Danmarkshavn\"\n"
+ + ">\n"
+ + "\n"
+ + "timeZoneMappings:<\n"
+ + " utcOffset:\"-1:00\"\n"
+ + " id:\"America/Scoresbysund\"\n"
+ + ">\n"
+ + "\n"
+ + "timeZoneMappings:<\n"
+ + " utcOffset:\"-3:00\"\n"
+ + " id:\"America/Godthab\"\n"
+ + " aliasId:\"America/Nuuk\"\n"
+ + ">\n"
+ + "\n"
+ + "timeZoneMappings:<\n"
+ + " utcOffset:\"-4:00\"\n"
+ + " id:\"America/Thule\"\n"
+ + ">\n";
+ Country country = parseCountry(countryZonesWithOldIdText);
+ List<ZoneTabFile.CountryEntry> zoneTabWithNewIds = Arrays.asList(
+ new ZoneTabFile.CountryEntry("GL", "America/Nuuk"),
+ new ZoneTabFile.CountryEntry("GL", "America/Danmarkshavn"),
+ new ZoneTabFile.CountryEntry("GL", "America/Scoresbysund"),
+ new ZoneTabFile.CountryEntry("GL", "America/Thule")
+ );
+ Map<String, String> backwardLinks = new HashMap<>();
+ backwardLinks.put("America/Godthab", "America/Nuuk");
+
+ OutputData outputData = generateOutputData(country, zoneTabWithNewIds, backwardLinks);
+
+ String expectedTzLookupOutput = "<id>America/Danmarkshavn</id>\n"
+ + "<id>America/Scoresbysund</id>\n"
+ + "<id alts=\"America/Nuuk\">America/Godthab</id>\n"
+ + "<id>America/Thule</id>\n";
+ String[] expectedTzLookupXmlLines = expectedTzLookupOutput.split("\\n");
+ for (String expectedTzLookupXmlLine : expectedTzLookupXmlLines) {
+ assertContains(outputData.tzLookupXml, expectedTzLookupXmlLine);
+ }
+
+ TzIdsProto.TimeZoneIds.Builder tzIdsBuilder = TzIdsProto.TimeZoneIds
+ .newBuilder()
+ .setIanaVersion(TZDB_VERSION);
+
+ TzIdsProto.CountryMapping.Builder b = TzIdsProto.CountryMapping.newBuilder()
+ .setIsoCode("gl")
+ .addTimeZoneIds("America/Danmarkshavn")
+ .addTimeZoneIds("America/Scoresbysund")
+ .addTimeZoneIds("America/Godthab")
+ .addTimeZoneIds("America/Thule");
+
+ // Because Android lists America/Nuuk as the aliasId in countryzones.txt, the link will
+ // be reversed from the usual.
+ addLink(b, "America/Nuuk" /* alternativeId */, "America/Godthab" /* preferredId */);
+
+ tzIdsBuilder.addCountryMappings(b);
+ assertEquals(tzIdsBuilder.build(), outputData.timeZoneIds);
+ }
+
+ private static void addLink(TzIdsProto.CountryMapping.Builder builder, String alternativeId,
+ String preferredId) {
+ TzIdsProto.TimeZoneLink link =
+ TzIdsProto.TimeZoneLink.newBuilder()
+ .setAlternativeId(alternativeId)
+ .setPreferredId(preferredId)
+ .build();
+ builder.addTimeZoneLinks(link);
+ }
+
+ @Test
+ public void usingOldLinksMissingAlias() throws Exception {
+ // This simulates a case where America/Godthab has been superseded by America/Nuuk in IANA
+ // data, but the Android file hasn't been updated properly.
+ String countryZonesWithOldIdText =
+ "isoCode:\"gl\"\n"
+ + "defaultTimeZoneId:\"America/Godthab\"\n"
+ + "timeZoneMappings:<\n"
+ + " utcOffset:\"0:00\"\n"
+ + " id:\"America/Danmarkshavn\"\n"
+ + ">\n"
+ + "\n"
+ + "timeZoneMappings:<\n"
+ + " utcOffset:\"-1:00\"\n"
+ + " id:\"America/Scoresbysund\"\n"
+ + ">\n"
+ + "\n"
+ + "timeZoneMappings:<\n"
+ + " utcOffset:\"-3:00\"\n"
+ + " id:\"America/Godthab\"\n"
+
+ // Exclude the crucial line that tells the generator we meant to use an old ID...
+ /* + " aliasId:\"America/Nuuk\"\n" */
+
+ + ">\n"
+ + "\n"
+ + "timeZoneMappings:<\n"
+ + " utcOffset:\"-4:00\"\n"
+ + " id:\"America/Thule\"\n"
+ + ">\n";
+ Country country = parseCountry(countryZonesWithOldIdText);
+ List<ZoneTabFile.CountryEntry> zoneTabWithNewIds = Arrays.asList(
+ new ZoneTabFile.CountryEntry("GL", "America/Nuuk"),
+ new ZoneTabFile.CountryEntry("GL", "America/Danmarkshavn"),
+ new ZoneTabFile.CountryEntry("GL", "America/Scoresbysund"),
+ new ZoneTabFile.CountryEntry("GL", "America/Thule")
+ );
+ Map<String, String> links = new HashMap<>();
+ links.put("America/Godthab", "America/Nuuk");
+
+ generateTzLookupXmlExpectFailure(country, zoneTabWithNewIds, links);
}
@Test
public void everUtc_true() throws Exception {
CountryZonesFile.Country validCountryGb = createValidCountryGb();
- String tzLookupXml = generateTzLookupXml(validCountryGb, createValidZoneTabEntriesGb());
+ OutputData outputData = generateOutputData(
+ validCountryGb, createValidZoneTabEntriesGb(), createEmptyBackwardLinks());
// Check gb's entry contains everutc="y".
- assertContains(tzLookupXml, "everutc=\"y\"");
+ assertContains(outputData.tzLookupXml, "everutc=\"y\"");
}
@Test
public void everUtc_false() throws Exception {
CountryZonesFile.Country validCountryFr = createValidCountryFr();
- String tzLookupXml = generateTzLookupXml(validCountryFr, createValidZoneTabEntriesFr());
+ OutputData outputData = generateOutputData(
+ validCountryFr, createValidZoneTabEntriesFr(), createEmptyBackwardLinks());
// Check fr's entry contains everutc="n".
- assertContains(tzLookupXml, "everutc=\"n\"");
+ assertContains(outputData.tzLookupXml, "everutc=\"n\"");
}
@Test
@@ -401,9 +606,10 @@
countryBuilder.setTimeZoneMappings(0, timeZoneMappingBuilder);
CountryZonesFile.Country country = countryBuilder.build();
- String tzLookupXml = generateTzLookupXml(country, createValidZoneTabEntriesFr());
+ OutputData outputData = generateOutputData(
+ country, createValidZoneTabEntriesFr(), createEmptyBackwardLinks());
- assertContains(tzLookupXml, "picker=\"n\"");
+ assertContains(outputData.tzLookupXml, "picker=\"n\"");
}
@Test
@@ -418,71 +624,161 @@
countryBuilder.setTimeZoneMappings(0, timeZoneMappingBuilder);
CountryZonesFile.Country country = countryBuilder.build();
- String tzLookupXml = generateTzLookupXml(country, createValidZoneTabEntriesFr());
+ OutputData outputData = generateOutputData(
+ country, createValidZoneTabEntriesFr(), createEmptyBackwardLinks());
// We should not see anything "picker="y" is the implicit default.
- assertAbsent(tzLookupXml, "picker=");
+ assertAbsent(outputData.tzLookupXml, "picker=");
}
@Test
public void notAfter() throws Exception {
CountryZonesFile.Country country = createValidCountryUs();
List<ZoneTabFile.CountryEntry> zoneTabEntries = createValidZoneTabEntriesUs();
- String tzLookupXml = generateTzLookupXml(country, zoneTabEntries);
- String expectedOutput =
+ OutputData outputData = generateOutputData(
+ country, zoneTabEntries, createEmptyBackwardLinks());
+ String expectedTzLookupOutput =
"<id>America/New_York</id>\n"
- + "<id notafter=\"167814000000\">America/Detroit</id>\n"
- + "<id notafter=\"152089200000\">America/Kentucky/Louisville</id>\n"
- + "<id notafter=\"972802800000\">America/Kentucky/Monticello</id>\n"
- + "<id notafter=\"1130652000000\">America/Indiana/Indianapolis</id>\n"
- + "<id notafter=\"1194159600000\">America/Indiana/Vincennes</id>\n"
- + "<id notafter=\"1173600000000\">America/Indiana/Winamac</id>\n"
- + "<id notafter=\"183535200000\">America/Indiana/Marengo</id>\n"
- + "<id notafter=\"247042800000\">America/Indiana/Petersburg</id>\n"
- + "<id notafter=\"89186400000\">America/Indiana/Vevay</id>\n"
+ + "<id notafter=\"167814000000\" repl=\"America/New_York\">America/Detroit</id>\n"
+ + "<id notafter=\"152089200000\" repl=\"America/New_York\">America/Kentucky/Louisville</id>\n"
+ + "<id notafter=\"972802800000\" repl=\"America/New_York\">America/Kentucky/Monticello</id>\n"
+ + "<id notafter=\"1130652000000\" repl=\"America/New_York\">America/Indiana/Indianapolis</id>\n"
+ + "<id notafter=\"1194159600000\" repl=\"America/New_York\">America/Indiana/Vincennes</id>\n"
+ + "<id notafter=\"1173600000000\" repl=\"America/New_York\">America/Indiana/Winamac</id>\n"
+ + "<id notafter=\"183535200000\" repl=\"America/Indiana/Indianapolis\">America/Indiana/Marengo</id>\n"
+ + "<id notafter=\"247042800000\" repl=\"America/Indiana/Vincennes\">America/Indiana/Petersburg</id>\n"
+ + "<id notafter=\"89186400000\" repl=\"America/Indiana/Indianapolis\">America/Indiana/Vevay</id>\n"
+ "<id>America/Chicago</id>\n"
- + "<id notafter=\"688546800000\">America/Indiana/Knox</id>\n"
- + "<id notafter=\"104918400000\">America/Menominee</id>\n"
- + "<id notafter=\"720000000000\">America/North_Dakota/Center</id>\n"
- + "<id notafter=\"1067155200000\">America/North_Dakota/New_Salem</id>\n"
- + "<id notafter=\"1143964800000\">America/Indiana/Tell_City</id>\n"
- + "<id notafter=\"1289116800000\">America/North_Dakota/Beulah</id>\n"
+ + "<id notafter=\"688546800000\" repl=\"America/Indiana/Tell_City\">America/Indiana/Knox</id>\n"
+ + "<id notafter=\"104918400000\" repl=\"America/Chicago\">America/Menominee</id>\n"
+ + "<id notafter=\"720000000000\" repl=\"America/Chicago\">America/North_Dakota/Center</id>\n"
+ + "<id notafter=\"1067155200000\" repl=\"America/Chicago\">America/North_Dakota/New_Salem</id>\n"
+ + "<id notafter=\"1143964800000\" repl=\"America/Chicago\">America/Indiana/Tell_City</id>\n"
+ + "<id notafter=\"1289116800000\" repl=\"America/Chicago\">America/North_Dakota/Beulah</id>\n"
+ "<id>America/Denver</id>\n"
- + "<id notafter=\"129114000000\">America/Boise</id>\n"
+ + "<id notafter=\"129114000000\" repl=\"America/Denver\">America/Boise</id>\n"
+ "<id>America/Phoenix</id>\n"
+ "<id>America/Los_Angeles</id>\n"
+ "<id>America/Anchorage</id>\n"
- + "<id notafter=\"436359600000\">America/Juneau</id>\n"
- + "<id notafter=\"436356000000\">America/Yakutat</id>\n"
- + "<id notafter=\"436363200000\">America/Nome</id>\n"
- + "<id notafter=\"1547978400000\">America/Metlakatla</id>\n"
- + "<id notafter=\"341402400000\">America/Sitka</id>\n"
+ + "<id notafter=\"436359600000\" repl=\"America/Anchorage\">America/Juneau</id>\n"
+ + "<id notafter=\"436356000000\" repl=\"America/Juneau\">America/Yakutat</id>\n"
+ + "<id notafter=\"436363200000\" repl=\"America/Anchorage\">America/Nome</id>\n"
+ + "<id notafter=\"1547978400000\" repl=\"America/Anchorage\">America/Metlakatla</id>\n"
+ + "<id notafter=\"341402400000\" repl=\"America/Juneau\">America/Sitka</id>\n"
+ "<id>Pacific/Honolulu</id>\n"
+ "<id>America/Adak</id>\n";
- String[] expectedLines = expectedOutput.split("\\n");
- for (String expectedLine : expectedLines) {
- assertContains(tzLookupXml, expectedLine);
+ String[] expectedTzLookupXmlLines = expectedTzLookupOutput.split("\\n");
+ for (String expectedTzLookupXmlLine : expectedTzLookupXmlLines) {
+ assertContains(outputData.tzLookupXml, expectedTzLookupXmlLine);
+ }
+
+ TzIdsProto.TimeZoneIds.Builder tzIdsBuilder = TzIdsProto.TimeZoneIds
+ .newBuilder()
+ .setIanaVersion(TZDB_VERSION);
+
+ TzIdsProto.CountryMapping.Builder b = TzIdsProto.CountryMapping.newBuilder()
+ .setIsoCode("us")
+ .addTimeZoneIds("America/New_York")
+ .addTimeZoneIds("America/Chicago")
+ .addTimeZoneIds("America/Denver")
+ .addTimeZoneIds("America/Phoenix")
+ .addTimeZoneIds("America/Los_Angeles")
+ .addTimeZoneIds("America/Anchorage")
+ .addTimeZoneIds("Pacific/Honolulu")
+ .addTimeZoneIds("America/Adak");
+
+ addReplacement(b, 167814000000L, "America/New_York", "America/Detroit");
+ addReplacement(b, 152089200000L, "America/New_York", "America/Kentucky/Louisville");
+ addReplacement(b, 972802800000L, "America/New_York", "America/Kentucky/Monticello");
+ addReplacement(b, 1130652000000L, "America/New_York", "America/Indiana/Indianapolis");
+ addReplacement(b, 1194159600000L, "America/New_York", "America/Indiana/Vincennes");
+ addReplacement(b, 1173600000000L, "America/New_York", "America/Indiana/Winamac");
+ addReplacement(b, 183535200000L, "America/Indiana/Indianapolis", "America/Indiana/Marengo");
+ addReplacement(b, 247042800000L, "America/Indiana/Vincennes", "America/Indiana/Petersburg");
+ addReplacement(b, 89186400000L, "America/Indiana/Indianapolis", "America/Indiana/Vevay");
+ addReplacement(b, 688546800000L, "America/Indiana/Tell_City", "America/Indiana/Knox");
+ addReplacement(b, 104918400000L, "America/Chicago", "America/Menominee");
+ addReplacement(b, 720000000000L, "America/Chicago", "America/North_Dakota/Center");
+ addReplacement(b, 1067155200000L, "America/Chicago", "America/North_Dakota/New_Salem");
+ addReplacement(b, 1143964800000L, "America/Chicago", "America/Indiana/Tell_City");
+ addReplacement(b, 1289116800000L, "America/Chicago", "America/North_Dakota/Beulah");
+ addReplacement(b, 129114000000L, "America/Denver", "America/Boise");
+ addReplacement(b, 436359600000L, "America/Anchorage", "America/Juneau");
+ addReplacement(b, 436356000000L, "America/Juneau", "America/Yakutat");
+ addReplacement(b, 436363200000L, "America/Anchorage", "America/Nome");
+ addReplacement(b, 1547978400000L, "America/Anchorage", "America/Metlakatla");
+ addReplacement(b, 341402400000L, "America/Juneau", "America/Sitka");;
+
+ tzIdsBuilder.addCountryMappings(b);
+ assertEquals(tzIdsBuilder.build(), outputData.timeZoneIds);
+ }
+
+ private static void addReplacement(TzIdsProto.CountryMapping.Builder builder,
+ long fromMillis, String replacementId, String replacedId) {
+ TzIdsProto.TimeZoneReplacement replacement =
+ TzIdsProto.TimeZoneReplacement.newBuilder()
+ .setReplacedId(replacedId)
+ .setReplacementId(replacementId)
+ .setFromMillis(fromMillis)
+ .build();
+ builder.addTimeZoneReplacements(replacement);
+ }
+
+ static class OutputData {
+ final String tzLookupXml;
+ final TzIdsProto.TimeZoneIds timeZoneIds;
+
+ OutputData(String tzLookupXml, TzIdsProto.TimeZoneIds timeZoneIds) {
+ this.tzLookupXml = tzLookupXml;
+ this.timeZoneIds = timeZoneIds;
}
}
- private String generateTzLookupXml(CountryZonesFile.Country country,
- List<ZoneTabFile.CountryEntry> zoneTabEntries) throws Exception {
+ private OutputData generateOutputData(CountryZonesFile.Country country,
+ List<ZoneTabFile.CountryEntry> zoneTabEntries, Map<String, String> backwardLinks)
+ throws Exception {
CountryZonesFile.CountryZones countryZones = createValidCountryZones(country);
String countryZonesFile = createCountryZonesFile(countryZones);
String zoneTabFile = createZoneTabFile(zoneTabEntries);
+ String backwardFile = createBackwardFile(backwardLinks);
- String outputFile = Files.createTempFile(tempDir, "out", null /* suffix */).toString();
+ String tzLookupFile = createTempFileName("tzlookup");
+ String tzIdsFile = createTempFileName("tzids");
- TzLookupGenerator tzLookupGenerator =
- new TzLookupGenerator(countryZonesFile, zoneTabFile, outputFile);
+ TzLookupGenerator tzLookupGenerator = new TzLookupGenerator(
+ countryZonesFile, zoneTabFile, backwardFile, tzLookupFile, tzIdsFile);
assertTrue(tzLookupGenerator.execute());
- Path outputFilePath = Paths.get(outputFile);
- assertTrue(Files.exists(outputFilePath));
+ Path tzLookupFilePath = checkFileExists(tzLookupFile);
+ String tzLookupXml = readFileToString(tzLookupFilePath);
- return readFileToString(outputFilePath);
+ Path tzIdsFilePath = checkFileExists(tzIdsFile);
+ String timeZoneIdsText = readFileToString(tzIdsFilePath);
+ TzIdsProto.TimeZoneIds.Builder timeZoneIdsBuilder =
+ TzIdsProto.TimeZoneIds.newBuilder();
+ TextFormat.merge(timeZoneIdsText, timeZoneIdsBuilder);
+
+ return new OutputData(tzLookupXml, timeZoneIdsBuilder.build());
+ }
+
+ private void generateTzLookupXmlExpectFailure(CountryZonesFile.Country country,
+ List<ZoneTabFile.CountryEntry> zoneTabEntries, Map<String, String> backwardLinks)
+ throws Exception {
+
+ CountryZonesFile.CountryZones countryZones = createValidCountryZones(country);
+ String countryZonesFile = createCountryZonesFile(countryZones);
+
+ String zoneTabFile = createZoneTabFile(zoneTabEntries);
+ String backwardFile = createBackwardFile(backwardLinks);
+
+ String tzLookupFile = createTempFileName("tzlookup");
+ String tzIdsFile = createTempFileName("tzids");
+
+ TzLookupGenerator tzLookupGenerator = new TzLookupGenerator(
+ countryZonesFile, zoneTabFile, backwardFile, tzLookupFile, tzIdsFile);
+ assertFalse(tzLookupGenerator.execute());
}
private static String readFileToString(Path file) throws IOException {
@@ -507,7 +803,7 @@
CountryZonesFile.Country... countries) {
CountryZonesFile.CountryZones.Builder builder =
CountryZonesFile.CountryZones.newBuilder()
- .setIanaVersion(TimeZone.getTZDataVersion());
+ .setIanaVersion(TZDB_VERSION);
for (CountryZonesFile.Country country : countries) {
builder.addCountries(country);
}
@@ -705,8 +1001,25 @@
}
private static List<ZoneTabFile.CountryEntry> createValidZoneTabEntriesFr() {
- return Arrays.asList(
- new ZoneTabFile.CountryEntry("FR", "Europe/Paris"));
+ return Arrays.asList(new ZoneTabFile.CountryEntry("FR", "Europe/Paris"));
+ }
+
+ /** Returns a file name for a file that does not exist. */
+ private String createTempFileName(String fileNamePrefix) throws IOException {
+ Path tempFile = Files.createTempFile(tempDir, fileNamePrefix, null /* suffix */);
+ Files.delete(tempFile);
+ return tempFile.toString();
+ }
+
+ private String createBackwardFile(Map<String, String> links) throws Exception {
+ List<String> lines = links.entrySet().stream()
+ .map(x -> "Link\t" + x.getValue() + "\t\t" + x.getKey())
+ .collect(Collectors.toList());
+ return TestUtils.createFile(tempDir, lines.toArray(new String[0]));
+ }
+
+ private static Map<String, String> createEmptyBackwardLinks() {
+ return new HashMap<>();
}
private static Country parseCountry(String text) throws Exception {
@@ -715,4 +1028,14 @@
return builder.build();
}
+ private static Path checkFileExists(String fileName) {
+ Path filePath = Paths.get(fileName);
+ assertTrue("File " + filePath + " unexpectedly missing", Files.exists(filePath));
+ return filePath;
+ }
+
+ private static void assertFileMissing(String fileName) throws IOException {
+ Path filePath = Paths.get(fileName);
+ assertFalse("File " + filePath + " unexpectedly exists", Files.exists(filePath));
+ }
}
diff --git a/input_tools/android/tzlookup_generator/src/test/java/com/android/libcore/timezone/tzlookup/ZoneTabFileTest.java b/input_tools/android/tzlookup_generator/src/test/java/com/android/libcore/timezone/tzlookup/ZoneTabFileTest.java
index b581ffd..108b902 100644
--- a/input_tools/android/tzlookup_generator/src/test/java/com/android/libcore/timezone/tzlookup/ZoneTabFileTest.java
+++ b/input_tools/android/tzlookup_generator/src/test/java/com/android/libcore/timezone/tzlookup/ZoneTabFileTest.java
@@ -25,6 +25,7 @@
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.text.ParseException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
@@ -32,7 +33,6 @@
import static junit.framework.TestCase.assertEquals;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
public class ZoneTabFileTest {
@@ -75,7 +75,7 @@
"# This is a comment",
"GB\tStuff\tEurope/London\tStuff",
"# This is a comment",
- "US\tStuff\tAmerica/New_York\tStuff",
+ "US\tStuff\t\tAmerica/New_York\tStuff",
"# This is a comment",
"US\tStuff\tAmerica/Los_Angeles",
"# This is a comment"
@@ -89,14 +89,11 @@
zoneTab.getCountryEntries());
}
- @Test
+ @Test(expected = ParseException.class)
public void parseMalformedFile() throws Exception {
// Mapping lines are expected to have at least three tab-separated columns.
String file = createFile("GB\tStuff");
- try {
- ZoneTabFile.parse(file);
- fail();
- } catch (IOException expected) {}
+ ZoneTabFile.parse(file);
}
@Test
diff --git a/input_tools/android/tzlookup_generator/src/test/java/com/android/libcore/timezone/tzlookup/zonetree/CountryZoneTreeTest.java b/input_tools/android/tzlookup_generator/src/test/java/com/android/libcore/timezone/tzlookup/zonetree/CountryZoneTreeTest.java
index e93c380..f3ff299 100644
--- a/input_tools/android/tzlookup_generator/src/test/java/com/android/libcore/timezone/tzlookup/zonetree/CountryZoneTreeTest.java
+++ b/input_tools/android/tzlookup_generator/src/test/java/com/android/libcore/timezone/tzlookup/zonetree/CountryZoneTreeTest.java
@@ -23,7 +23,7 @@
import java.time.Instant;
import java.time.temporal.ChronoUnit;
-import static com.android.libcore.timezone.tzlookup.proto.CountryZonesFile.Country;
+import static com.android.libcore.timezone.countryzones.proto.CountryZonesFile.Country;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
@@ -108,6 +108,8 @@
Instant.ofEpochSecond(338950800); /* 1980-09-28T01:00:00Z */
assertEquals(expectedNotUsedAfterInstant,
countryZoneUsage.getNotUsedAfterInstant("Europe/Busingen"));
+ assertEquals("Europe/Berlin",
+ countryZoneUsage.getNotUsedReplacementId("Europe/Busingen"));
}
@Test
@@ -154,7 +156,11 @@
Instant.ofEpochSecond(762883200); /* 1994-03-05T16:00:00Z */
assertEquals(expectedNotUsedAfterInstant,
countryZoneUsage.getNotUsedAfterInstant("Australia/Lindeman"));
+ assertEquals("Australia/Brisbane",
+ countryZoneUsage.getNotUsedReplacementId("Australia/Lindeman"));
+
assertNull(countryZoneUsage.getNotUsedAfterInstant("Australia/Brisbane"));
+ assertNull(countryZoneUsage.getNotUsedReplacementId("Australia/Brisbane"));
}
private static Country parseCountry(String text) throws Exception {
diff --git a/input_tools/android/tzlookup_generator/src/test/java/com/android/libcore/timezone/tzlookup/zonetree/CountryZoneUsageTest.java b/input_tools/android/tzlookup_generator/src/test/java/com/android/libcore/timezone/tzlookup/zonetree/CountryZoneUsageTest.java
index b1a13b9..eae3ec1 100644
--- a/input_tools/android/tzlookup_generator/src/test/java/com/android/libcore/timezone/tzlookup/zonetree/CountryZoneUsageTest.java
+++ b/input_tools/android/tzlookup_generator/src/test/java/com/android/libcore/timezone/tzlookup/zonetree/CountryZoneUsageTest.java
@@ -22,13 +22,14 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class CountryZoneUsageTest {
@Test
- public void testGetIsCode() {
+ public void testGetIsoCode() {
CountryZoneUsage countryZoneUsage = new CountryZoneUsage("us");
assertEquals("us", countryZoneUsage.getIsoCode());
}
@@ -48,15 +49,23 @@
@Test
public void testWithEntry() {
CountryZoneUsage countryZoneUsage = new CountryZoneUsage("us");
- String usZoneId = "America/Boise";
+
+ String usZoneId1 = "America/Boise";
+ countryZoneUsage.addEntry(usZoneId1, null /* notUsedAfterInstant */, null /* altTzId */);
+ assertTrue(countryZoneUsage.hasEntry(usZoneId1));
+ assertNull(countryZoneUsage.getNotUsedAfterInstant(usZoneId1));
+ assertNull(null, countryZoneUsage.getNotUsedReplacementId(usZoneId1));
+
+ String usZoneId2 = "America/Los_Angeles";
Instant instant = Instant.ofEpochSecond(1234);
- countryZoneUsage.addEntry(usZoneId, instant);
+ countryZoneUsage.addEntry(usZoneId2, instant, usZoneId1 /* notUsedReplacementId */);
+ assertTrue(countryZoneUsage.hasEntry(usZoneId2));
+ assertEquals(instant, countryZoneUsage.getNotUsedAfterInstant(usZoneId2));
+ assertEquals(usZoneId1, countryZoneUsage.getNotUsedReplacementId(usZoneId2));
- assertTrue(countryZoneUsage.hasEntry(usZoneId));
- assertEquals(instant, countryZoneUsage.getNotUsedAfterInstant(usZoneId));
-
+ // Duplicate IDs are not allowed.
try {
- countryZoneUsage.addEntry(usZoneId, instant);
+ countryZoneUsage.addEntry(usZoneId1, instant, "" /* notUsedReplacementId */);
fail();
} catch (IllegalArgumentException expected) {
}
diff --git a/input_tools/android/zone_compactor/main/java/ZoneCompactor.java b/input_tools/android/zone_compactor/main/java/ZoneCompactor.java
index c3c1089..5cc076a 100644
--- a/input_tools/android/zone_compactor/main/java/ZoneCompactor.java
+++ b/input_tools/android/zone_compactor/main/java/ZoneCompactor.java
@@ -17,7 +17,7 @@
import java.io.*;
import java.util.*;
-// usage: java ZoneCompiler <setup file> <data directory> <output directory> <tzdata version>
+// usage: java ZoneCompactor <setup file> <data directory> <output directory> <tzdata version>
//
// Compile a set of tzfile-formatted files into a single file containing an index.
//
@@ -43,13 +43,13 @@
private static final int MAXNAME = 40;
// Zone name synonyms.
- private Map<String,String> links = new HashMap<String,String>();
+ private Map<String,String> links = new HashMap<>();
// File offsets by zone name.
- private Map<String,Integer> offsets = new HashMap<String,Integer>();
+ private Map<String,Integer> offsets = new HashMap<>();
// File lengths by zone name.
- private Map<String,Integer> lengths = new HashMap<String,Integer>();
+ private Map<String,Integer> lengths = new HashMap<>();
// Concatenate the contents of 'inFile' onto 'out'.
private static void copyFile(File inFile, OutputStream out) throws Exception {
@@ -72,7 +72,8 @@
out.flush();
}
- public ZoneCompactor(String setupFile, String dataDirectory, String zoneTabFile, String outputDirectory, String version) throws Exception {
+ public ZoneCompactor(String setupFile, String dataDirectory, String outputDirectory,
+ String version) throws Exception {
// Read the setup file and concatenate all the data.
ByteArrayOutputStream allData = new ByteArrayOutputStream();
BufferedReader reader = new BufferedReader(new FileReader(setupFile));
@@ -80,19 +81,20 @@
int offset = 0;
while ((s = reader.readLine()) != null) {
s = s.trim();
- if (s.startsWith("Link")) {
- StringTokenizer st = new StringTokenizer(s);
- st.nextToken();
+ StringTokenizer st = new StringTokenizer(s);
+ String lineType = st.nextToken();
+ if (lineType.startsWith("Link")) {
String to = st.nextToken();
String from = st.nextToken();
links.put(from, to);
- } else {
- String link = links.get(s);
+ } else if (lineType.startsWith("Zone")) {
+ String zoneId = st.nextToken();
+ String link = links.get(zoneId);
if (link == null) {
- File sourceFile = new File(dataDirectory, s);
+ File sourceFile = new File(dataDirectory, zoneId);
long length = sourceFile.length();
- offsets.put(s, offset);
- lengths.put(s, (int) length);
+ offsets.put(zoneId, offset);
+ lengths.put(zoneId, (int) length);
offset += length;
copyFile(sourceFile, allData);
@@ -102,9 +104,7 @@
reader.close();
// Fill in fields for links.
- Iterator<String> it = links.keySet().iterator();
- while (it.hasNext()) {
- String from = it.next();
+ for (String from : links.keySet()) {
String to = links.get(from);
offsets.put(from, offsets.get(to));
@@ -120,18 +120,22 @@
// byte[12] tzdata_version -- 'tzdata2012f\0'
// int index_offset -- so we can slip in extra header fields in a backwards-compatible way
// int data_offset
- // int zonetab_offset
+ // int final_offset
// tzdata_version
f.write(toAscii(new byte[12], version));
- // Write dummy values for the three offsets, and remember where we need to seek back to later
+ // Write placeholder values for the offsets, and remember where we need to seek back to later
// when we have the real values.
int index_offset_offset = (int) f.getFilePointer();
f.writeInt(0);
int data_offset_offset = (int) f.getFilePointer();
f.writeInt(0);
- int zonetab_offset_offset = (int) f.getFilePointer();
+ // The final offset serves as a placeholder for sections that might be added in future and
+ // ensures we know the size of the final "real" section. Relying on the last section ending at
+ // EOF would make it harder to append sections to the end of the file in a backward compatible
+ // way.
+ int final_offset_offset = (int) f.getFilePointer();
f.writeInt(0);
int index_offset = (int) f.getFilePointer();
@@ -140,9 +144,7 @@
ArrayList<String> sortedOlsonIds = new ArrayList<String>();
sortedOlsonIds.addAll(offsets.keySet());
Collections.sort(sortedOlsonIds);
- it = sortedOlsonIds.iterator();
- while (it.hasNext()) {
- String zoneName = it.next();
+ for (String zoneName : sortedOlsonIds) {
if (zoneName.length() >= MAXNAME) {
throw new RuntimeException("zone filename too long: " + zoneName.length());
}
@@ -164,25 +166,15 @@
// Write the data.
f.write(allData.toByteArray());
- int zonetab_offset = (int) f.getFilePointer();
-
- // Copy the zone.tab.
- reader = new BufferedReader(new FileReader(zoneTabFile));
- while ((s = reader.readLine()) != null) {
- if (!s.startsWith("#")) {
- f.writeBytes(s);
- f.write('\n');
- }
- }
- reader.close();
+ int final_offset = (int) f.getFilePointer();
// Go back and fix up the offsets in the header.
f.seek(index_offset_offset);
f.writeInt(index_offset);
f.seek(data_offset_offset);
f.writeInt(data_offset);
- f.seek(zonetab_offset_offset);
- f.writeInt(zonetab_offset);
+ f.seek(final_offset_offset);
+ f.writeInt(final_offset);
f.close();
}
@@ -198,10 +190,11 @@
}
public static void main(String[] args) throws Exception {
- if (args.length != 5) {
- System.err.println("usage: java ZoneCompactor <setup file> <data directory> <zone.tab file> <output directory> <tzdata version>");
- System.exit(0);
+ if (args.length != 4) {
+ System.err.println("usage: java ZoneCompactor <setup file> <data directory>"
+ + " <output directory> <tzdata version>");
+ System.exit(1);
}
- new ZoneCompactor(args[0], args[1], args[2], args[3], args[4]);
+ new ZoneCompactor(args[0], args[1], args[2], args[3]);
}
}
diff --git a/output_data/Android.bp b/output_data/Android.bp
index e56a078..abe8e36 100644
--- a/output_data/Android.bp
+++ b/output_data/Android.bp
@@ -55,7 +55,7 @@
}
// tzdata packaged into a jar for use in robolectric
-java_genrule {
+java_genrule_host {
name: "robolectric_tzdata",
out: ["robolectric_tzdata.jar"],
tools: ["soong_zip"],
diff --git a/output_data/android/tzids.prototxt b/output_data/android/tzids.prototxt
new file mode 100644
index 0000000..9030977
--- /dev/null
+++ b/output_data/android/tzids.prototxt
@@ -0,0 +1,1984 @@
+# Autogenerated file - DO NOT EDIT.
+ianaVersion: "2020d"
+countryMappings {
+ isoCode: "ad"
+ timeZoneIds: "Europe/Andorra"
+}
+countryMappings {
+ isoCode: "ae"
+ timeZoneIds: "Asia/Dubai"
+}
+countryMappings {
+ isoCode: "af"
+ timeZoneIds: "Asia/Kabul"
+}
+countryMappings {
+ isoCode: "ag"
+ timeZoneIds: "America/Antigua"
+}
+countryMappings {
+ isoCode: "ai"
+ timeZoneIds: "America/Anguilla"
+}
+countryMappings {
+ isoCode: "al"
+ timeZoneIds: "Europe/Tirane"
+}
+countryMappings {
+ isoCode: "am"
+ timeZoneIds: "Asia/Yerevan"
+}
+countryMappings {
+ isoCode: "ao"
+ timeZoneIds: "Africa/Luanda"
+}
+countryMappings {
+ isoCode: "aq"
+ timeZoneIds: "Antarctica/McMurdo"
+ timeZoneIds: "Antarctica/DumontDUrville"
+ timeZoneIds: "Antarctica/Casey"
+ timeZoneIds: "Antarctica/Davis"
+ timeZoneIds: "Antarctica/Mawson"
+ timeZoneIds: "Antarctica/Vostok"
+ timeZoneIds: "Antarctica/Syowa"
+ timeZoneIds: "Antarctica/Troll"
+ timeZoneIds: "Antarctica/Rothera"
+ timeZoneIds: "Antarctica/Palmer"
+}
+countryMappings {
+ isoCode: "ar"
+ timeZoneIds: "America/Argentina/Buenos_Aires"
+ timeZoneIds: "America/Argentina/San_Luis"
+ timeZoneLinks {
+ alternativeId: "America/Buenos_Aires"
+ preferredId: "America/Argentina/Buenos_Aires"
+ }
+ timeZoneLinks {
+ alternativeId: "America/Cordoba"
+ preferredId: "America/Argentina/Cordoba"
+ }
+ timeZoneLinks {
+ alternativeId: "America/Rosario"
+ preferredId: "America/Argentina/Cordoba"
+ }
+ timeZoneLinks {
+ alternativeId: "America/Mendoza"
+ preferredId: "America/Argentina/Mendoza"
+ }
+ timeZoneLinks {
+ alternativeId: "America/Jujuy"
+ preferredId: "America/Argentina/Jujuy"
+ }
+ timeZoneLinks {
+ alternativeId: "America/Argentina/ComodRivadavia"
+ preferredId: "America/Argentina/Catamarca"
+ }
+ timeZoneLinks {
+ alternativeId: "America/Catamarca"
+ preferredId: "America/Argentina/Catamarca"
+ }
+ timeZoneReplacements {
+ replacedId: "America/Argentina/Cordoba"
+ replacementId: "America/Argentina/Buenos_Aires"
+ fromMillis: 687931200000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Argentina/Mendoza"
+ replacementId: "America/Argentina/Buenos_Aires"
+ fromMillis: 1237082400000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Argentina/Tucuman"
+ replacementId: "America/Argentina/Buenos_Aires"
+ fromMillis: 1087099200000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Argentina/Salta"
+ replacementId: "America/Argentina/Mendoza"
+ fromMillis: 1096171200000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Argentina/San_Juan"
+ replacementId: "America/Argentina/Salta"
+ fromMillis: 1090728000000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Argentina/Jujuy"
+ replacementId: "America/Argentina/Salta"
+ fromMillis: 687931200000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Argentina/Catamarca"
+ replacementId: "America/Argentina/Salta"
+ fromMillis: 1087704000000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Argentina/La_Rioja"
+ replacementId: "America/Argentina/Catamarca"
+ fromMillis: 687931200000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Argentina/Rio_Gallegos"
+ replacementId: "America/Argentina/La_Rioja"
+ fromMillis: 673588800000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Argentina/Ushuaia"
+ replacementId: "America/Argentina/Salta"
+ fromMillis: 1087704000000
+ }
+}
+countryMappings {
+ isoCode: "as"
+ timeZoneIds: "Pacific/Pago_Pago"
+ timeZoneLinks {
+ alternativeId: "Pacific/Samoa"
+ preferredId: "Pacific/Pago_Pago"
+ }
+ timeZoneLinks {
+ alternativeId: "US/Samoa"
+ preferredId: "Pacific/Pago_Pago"
+ }
+}
+countryMappings {
+ isoCode: "at"
+ timeZoneIds: "Europe/Vienna"
+}
+countryMappings {
+ isoCode: "au"
+ timeZoneIds: "Australia/Sydney"
+ timeZoneIds: "Australia/Brisbane"
+ timeZoneIds: "Australia/Lord_Howe"
+ timeZoneIds: "Australia/Adelaide"
+ timeZoneIds: "Australia/Darwin"
+ timeZoneIds: "Australia/Perth"
+ timeZoneIds: "Australia/Eucla"
+ timeZoneLinks {
+ alternativeId: "Australia/ACT"
+ preferredId: "Australia/Sydney"
+ }
+ timeZoneLinks {
+ alternativeId: "Australia/Canberra"
+ preferredId: "Australia/Sydney"
+ }
+ timeZoneLinks {
+ alternativeId: "Australia/NSW"
+ preferredId: "Australia/Sydney"
+ }
+ timeZoneLinks {
+ alternativeId: "Australia/Victoria"
+ preferredId: "Australia/Melbourne"
+ }
+ timeZoneLinks {
+ alternativeId: "Australia/Tasmania"
+ preferredId: "Australia/Hobart"
+ }
+ timeZoneLinks {
+ alternativeId: "Australia/Queensland"
+ preferredId: "Australia/Brisbane"
+ }
+ timeZoneLinks {
+ alternativeId: "Australia/LHI"
+ preferredId: "Australia/Lord_Howe"
+ }
+ timeZoneLinks {
+ alternativeId: "Australia/South"
+ preferredId: "Australia/Adelaide"
+ }
+ timeZoneLinks {
+ alternativeId: "Australia/Yancowinna"
+ preferredId: "Australia/Broken_Hill"
+ }
+ timeZoneLinks {
+ alternativeId: "Australia/North"
+ preferredId: "Australia/Darwin"
+ }
+ timeZoneLinks {
+ alternativeId: "Australia/West"
+ preferredId: "Australia/Perth"
+ }
+ timeZoneReplacements {
+ replacedId: "Australia/Melbourne"
+ replacementId: "Australia/Sydney"
+ fromMillis: 796147200000
+ }
+ timeZoneReplacements {
+ replacedId: "Australia/Hobart"
+ replacementId: "Australia/Sydney"
+ fromMillis: 1193500800000
+ }
+ timeZoneReplacements {
+ replacedId: "Australia/Currie"
+ replacementId: "Australia/Hobart"
+ fromMillis: 37728000000
+ }
+ timeZoneReplacements {
+ replacedId: "Australia/Lindeman"
+ replacementId: "Australia/Brisbane"
+ fromMillis: 762883200000
+ }
+ timeZoneReplacements {
+ replacedId: "Antarctica/Macquarie"
+ replacementId: "Australia/Sydney"
+ fromMillis: 1286035200000
+ }
+ timeZoneReplacements {
+ replacedId: "Australia/Broken_Hill"
+ replacementId: "Australia/Adelaide"
+ fromMillis: 796149000000
+ }
+}
+countryMappings {
+ isoCode: "aw"
+ timeZoneIds: "America/Aruba"
+}
+countryMappings {
+ isoCode: "ax"
+ timeZoneIds: "Europe/Mariehamn"
+}
+countryMappings {
+ isoCode: "az"
+ timeZoneIds: "Asia/Baku"
+}
+countryMappings {
+ isoCode: "ba"
+ timeZoneIds: "Europe/Sarajevo"
+}
+countryMappings {
+ isoCode: "bb"
+ timeZoneIds: "America/Barbados"
+}
+countryMappings {
+ isoCode: "bd"
+ timeZoneIds: "Asia/Dhaka"
+ timeZoneLinks {
+ alternativeId: "Asia/Dacca"
+ preferredId: "Asia/Dhaka"
+ }
+}
+countryMappings {
+ isoCode: "be"
+ timeZoneIds: "Europe/Brussels"
+}
+countryMappings {
+ isoCode: "bf"
+ timeZoneIds: "Africa/Ouagadougou"
+}
+countryMappings {
+ isoCode: "bg"
+ timeZoneIds: "Europe/Sofia"
+}
+countryMappings {
+ isoCode: "bh"
+ timeZoneIds: "Asia/Bahrain"
+}
+countryMappings {
+ isoCode: "bi"
+ timeZoneIds: "Africa/Bujumbura"
+}
+countryMappings {
+ isoCode: "bj"
+ timeZoneIds: "Africa/Porto-Novo"
+}
+countryMappings {
+ isoCode: "bl"
+ timeZoneIds: "America/St_Barthelemy"
+}
+countryMappings {
+ isoCode: "bm"
+ timeZoneIds: "Atlantic/Bermuda"
+}
+countryMappings {
+ isoCode: "bn"
+ timeZoneIds: "Asia/Brunei"
+}
+countryMappings {
+ isoCode: "bo"
+ timeZoneIds: "America/La_Paz"
+}
+countryMappings {
+ isoCode: "bq"
+ timeZoneIds: "America/Kralendijk"
+}
+countryMappings {
+ isoCode: "br"
+ timeZoneIds: "America/Noronha"
+ timeZoneIds: "America/Sao_Paulo"
+ timeZoneIds: "America/Manaus"
+ timeZoneIds: "America/Rio_Branco"
+ timeZoneLinks {
+ alternativeId: "Brazil/DeNoronha"
+ preferredId: "America/Noronha"
+ }
+ timeZoneLinks {
+ alternativeId: "Brazil/East"
+ preferredId: "America/Sao_Paulo"
+ }
+ timeZoneLinks {
+ alternativeId: "Brazil/West"
+ preferredId: "America/Manaus"
+ }
+ timeZoneLinks {
+ alternativeId: "America/Porto_Acre"
+ preferredId: "America/Rio_Branco"
+ }
+ timeZoneLinks {
+ alternativeId: "Brazil/Acre"
+ preferredId: "America/Rio_Branco"
+ }
+ timeZoneReplacements {
+ replacedId: "America/Bahia"
+ replacementId: "America/Sao_Paulo"
+ fromMillis: 1550368800000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Santarem"
+ replacementId: "America/Recife"
+ fromMillis: 1214280000000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Recife"
+ replacementId: "America/Bahia"
+ fromMillis: 1330221600000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Fortaleza"
+ replacementId: "America/Recife"
+ fromMillis: 972180000000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Belem"
+ replacementId: "America/Recife"
+ fromMillis: 1013911200000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Maceio"
+ replacementId: "America/Fortaleza"
+ fromMillis: 824004000000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Araguaina"
+ replacementId: "America/Bahia"
+ fromMillis: 1361066400000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Cuiaba"
+ replacementId: "America/Manaus"
+ fromMillis: 1550372400000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Campo_Grande"
+ replacementId: "America/Cuiaba"
+ fromMillis: 1076814000000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Porto_Velho"
+ replacementId: "America/Manaus"
+ fromMillis: 761713200000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Boa_Vista"
+ replacementId: "America/Manaus"
+ fromMillis: 971578800000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Eirunepe"
+ replacementId: "America/Rio_Branco"
+ fromMillis: 761716800000
+ }
+}
+countryMappings {
+ isoCode: "bs"
+ timeZoneIds: "America/Nassau"
+}
+countryMappings {
+ isoCode: "bt"
+ timeZoneIds: "Asia/Thimphu"
+ timeZoneLinks {
+ alternativeId: "Asia/Thimbu"
+ preferredId: "Asia/Thimphu"
+ }
+}
+countryMappings {
+ isoCode: "bw"
+ timeZoneIds: "Africa/Gaborone"
+}
+countryMappings {
+ isoCode: "by"
+ timeZoneIds: "Europe/Minsk"
+}
+countryMappings {
+ isoCode: "bz"
+ timeZoneIds: "America/Belize"
+}
+countryMappings {
+ isoCode: "ca"
+ timeZoneIds: "America/Toronto"
+ timeZoneIds: "America/Vancouver"
+ timeZoneIds: "America/Edmonton"
+ timeZoneIds: "America/Winnipeg"
+ timeZoneIds: "America/Halifax"
+ timeZoneIds: "America/St_Johns"
+ timeZoneIds: "America/Blanc-Sablon"
+ timeZoneIds: "America/Atikokan"
+ timeZoneIds: "America/Regina"
+ timeZoneIds: "America/Whitehorse"
+ timeZoneLinks {
+ alternativeId: "America/Montreal"
+ preferredId: "America/Toronto"
+ }
+ timeZoneLinks {
+ alternativeId: "Canada/Eastern"
+ preferredId: "America/Toronto"
+ }
+ timeZoneLinks {
+ alternativeId: "Canada/Pacific"
+ preferredId: "America/Vancouver"
+ }
+ timeZoneLinks {
+ alternativeId: "Canada/Mountain"
+ preferredId: "America/Edmonton"
+ }
+ timeZoneLinks {
+ alternativeId: "Canada/Central"
+ preferredId: "America/Winnipeg"
+ }
+ timeZoneLinks {
+ alternativeId: "Canada/Atlantic"
+ preferredId: "America/Halifax"
+ }
+ timeZoneLinks {
+ alternativeId: "Canada/Newfoundland"
+ preferredId: "America/St_Johns"
+ }
+ timeZoneLinks {
+ alternativeId: "America/Coral_Harbour"
+ preferredId: "America/Atikokan"
+ }
+ timeZoneLinks {
+ alternativeId: "Canada/Saskatchewan"
+ preferredId: "America/Regina"
+ }
+ timeZoneLinks {
+ alternativeId: "Canada/Yukon"
+ preferredId: "America/Whitehorse"
+ }
+ timeZoneReplacements {
+ replacedId: "America/Moncton"
+ replacementId: "America/Halifax"
+ fromMillis: 1162098000000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Glace_Bay"
+ replacementId: "America/Halifax"
+ fromMillis: 57733200000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Goose_Bay"
+ replacementId: "America/Halifax"
+ fromMillis: 1299996000000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Thunder_Bay"
+ replacementId: "America/Toronto"
+ fromMillis: 120636000000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Iqaluit"
+ replacementId: "America/Toronto"
+ fromMillis: 972802800000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Nipigon"
+ replacementId: "America/Thunder_Bay"
+ fromMillis: 89186400000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Pangnirtung"
+ replacementId: "America/Iqaluit"
+ fromMillis: 796806000000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Swift_Current"
+ replacementId: "America/Regina"
+ fromMillis: 73472400000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Rankin_Inlet"
+ replacementId: "America/Winnipeg"
+ fromMillis: 1130659200000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Rainy_River"
+ replacementId: "America/Rankin_Inlet"
+ fromMillis: 986112000000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Resolute"
+ replacementId: "America/Winnipeg"
+ fromMillis: 1173600000000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Yellowknife"
+ replacementId: "America/Edmonton"
+ fromMillis: 309945600000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Dawson_Creek"
+ replacementId: "America/Whitehorse"
+ fromMillis: 1604214000000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Creston"
+ replacementId: "America/Dawson_Creek"
+ fromMillis: 84013200000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Fort_Nelson"
+ replacementId: "America/Dawson_Creek"
+ fromMillis: 1425808800000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Inuvik"
+ replacementId: "America/Yellowknife"
+ fromMillis: 294228000000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Cambridge_Bay"
+ replacementId: "America/Edmonton"
+ fromMillis: 986115600000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Dawson"
+ replacementId: "America/Whitehorse"
+ fromMillis: 120646800000
+ }
+}
+countryMappings {
+ isoCode: "cc"
+ timeZoneIds: "Indian/Cocos"
+}
+countryMappings {
+ isoCode: "cd"
+ timeZoneIds: "Africa/Lubumbashi"
+ timeZoneIds: "Africa/Kinshasa"
+}
+countryMappings {
+ isoCode: "cf"
+ timeZoneIds: "Africa/Bangui"
+}
+countryMappings {
+ isoCode: "cg"
+ timeZoneIds: "Africa/Brazzaville"
+}
+countryMappings {
+ isoCode: "ch"
+ timeZoneIds: "Europe/Zurich"
+}
+countryMappings {
+ isoCode: "ci"
+ timeZoneIds: "Africa/Abidjan"
+ timeZoneLinks {
+ alternativeId: "Africa/Timbuktu"
+ preferredId: "Africa/Abidjan"
+ }
+}
+countryMappings {
+ isoCode: "ck"
+ timeZoneIds: "Pacific/Rarotonga"
+}
+countryMappings {
+ isoCode: "cl"
+ timeZoneIds: "America/Punta_Arenas"
+ timeZoneIds: "America/Santiago"
+ timeZoneIds: "Pacific/Easter"
+ timeZoneLinks {
+ alternativeId: "Chile/Continental"
+ preferredId: "America/Santiago"
+ }
+ timeZoneLinks {
+ alternativeId: "Chile/EasterIsland"
+ preferredId: "Pacific/Easter"
+ }
+}
+countryMappings {
+ isoCode: "cm"
+ timeZoneIds: "Africa/Douala"
+}
+countryMappings {
+ isoCode: "cn"
+ timeZoneIds: "Asia/Shanghai"
+ timeZoneIds: "Asia/Urumqi"
+ timeZoneLinks {
+ alternativeId: "Asia/Chongqing"
+ preferredId: "Asia/Shanghai"
+ }
+ timeZoneLinks {
+ alternativeId: "Asia/Chungking"
+ preferredId: "Asia/Shanghai"
+ }
+ timeZoneLinks {
+ alternativeId: "Asia/Harbin"
+ preferredId: "Asia/Shanghai"
+ }
+ timeZoneLinks {
+ alternativeId: "PRC"
+ preferredId: "Asia/Shanghai"
+ }
+ timeZoneLinks {
+ alternativeId: "Asia/Kashgar"
+ preferredId: "Asia/Urumqi"
+ }
+}
+countryMappings {
+ isoCode: "co"
+ timeZoneIds: "America/Bogota"
+}
+countryMappings {
+ isoCode: "cr"
+ timeZoneIds: "America/Costa_Rica"
+}
+countryMappings {
+ isoCode: "cu"
+ timeZoneIds: "America/Havana"
+ timeZoneLinks {
+ alternativeId: "Cuba"
+ preferredId: "America/Havana"
+ }
+}
+countryMappings {
+ isoCode: "cv"
+ timeZoneIds: "Atlantic/Cape_Verde"
+}
+countryMappings {
+ isoCode: "cw"
+ timeZoneIds: "America/Curacao"
+}
+countryMappings {
+ isoCode: "cx"
+ timeZoneIds: "Indian/Christmas"
+}
+countryMappings {
+ isoCode: "cy"
+ timeZoneIds: "Asia/Nicosia"
+ timeZoneIds: "Asia/Famagusta"
+}
+countryMappings {
+ isoCode: "cz"
+ timeZoneIds: "Europe/Prague"
+}
+countryMappings {
+ isoCode: "de"
+ timeZoneIds: "Europe/Berlin"
+ timeZoneReplacements {
+ replacedId: "Europe/Busingen"
+ replacementId: "Europe/Berlin"
+ fromMillis: 338950800000
+ }
+}
+countryMappings {
+ isoCode: "dj"
+ timeZoneIds: "Africa/Djibouti"
+}
+countryMappings {
+ isoCode: "dk"
+ timeZoneIds: "Europe/Copenhagen"
+}
+countryMappings {
+ isoCode: "dm"
+ timeZoneIds: "America/Dominica"
+}
+countryMappings {
+ isoCode: "do"
+ timeZoneIds: "America/Santo_Domingo"
+}
+countryMappings {
+ isoCode: "dz"
+ timeZoneIds: "Africa/Algiers"
+}
+countryMappings {
+ isoCode: "ec"
+ timeZoneIds: "America/Guayaquil"
+ timeZoneIds: "Pacific/Galapagos"
+}
+countryMappings {
+ isoCode: "ee"
+ timeZoneIds: "Europe/Tallinn"
+}
+countryMappings {
+ isoCode: "eg"
+ timeZoneIds: "Africa/Cairo"
+ timeZoneLinks {
+ alternativeId: "Egypt"
+ preferredId: "Africa/Cairo"
+ }
+}
+countryMappings {
+ isoCode: "eh"
+ timeZoneIds: "Africa/El_Aaiun"
+}
+countryMappings {
+ isoCode: "er"
+ timeZoneIds: "Africa/Asmara"
+}
+countryMappings {
+ isoCode: "es"
+ timeZoneIds: "Europe/Madrid"
+ timeZoneIds: "Atlantic/Canary"
+ timeZoneReplacements {
+ replacedId: "Africa/Ceuta"
+ replacementId: "Europe/Madrid"
+ fromMillis: 496803600000
+ }
+}
+countryMappings {
+ isoCode: "et"
+ timeZoneIds: "Africa/Addis_Ababa"
+}
+countryMappings {
+ isoCode: "fi"
+ timeZoneIds: "Europe/Helsinki"
+}
+countryMappings {
+ isoCode: "fj"
+ timeZoneIds: "Pacific/Fiji"
+}
+countryMappings {
+ isoCode: "fk"
+ timeZoneIds: "Atlantic/Stanley"
+}
+countryMappings {
+ isoCode: "fm"
+ timeZoneIds: "Pacific/Pohnpei"
+ timeZoneIds: "Pacific/Kosrae"
+ timeZoneIds: "Pacific/Chuuk"
+ timeZoneLinks {
+ alternativeId: "Pacific/Ponape"
+ preferredId: "Pacific/Pohnpei"
+ }
+ timeZoneLinks {
+ alternativeId: "Pacific/Truk"
+ preferredId: "Pacific/Chuuk"
+ }
+ timeZoneLinks {
+ alternativeId: "Pacific/Yap"
+ preferredId: "Pacific/Chuuk"
+ }
+}
+countryMappings {
+ isoCode: "fo"
+ timeZoneIds: "Atlantic/Faroe"
+ timeZoneLinks {
+ alternativeId: "Atlantic/Faeroe"
+ preferredId: "Atlantic/Faroe"
+ }
+}
+countryMappings {
+ isoCode: "fr"
+ timeZoneIds: "Europe/Paris"
+}
+countryMappings {
+ isoCode: "ga"
+ timeZoneIds: "Africa/Libreville"
+}
+countryMappings {
+ isoCode: "gb"
+ timeZoneIds: "Europe/London"
+ timeZoneLinks {
+ alternativeId: "Europe/Belfast"
+ preferredId: "Europe/London"
+ }
+ timeZoneLinks {
+ alternativeId: "GB"
+ preferredId: "Europe/London"
+ }
+ timeZoneLinks {
+ alternativeId: "GB-Eire"
+ preferredId: "Europe/London"
+ }
+}
+countryMappings {
+ isoCode: "gd"
+ timeZoneIds: "America/Grenada"
+}
+countryMappings {
+ isoCode: "ge"
+ timeZoneIds: "Asia/Tbilisi"
+}
+countryMappings {
+ isoCode: "gf"
+ timeZoneIds: "America/Cayenne"
+}
+countryMappings {
+ isoCode: "gg"
+ timeZoneIds: "Europe/Guernsey"
+}
+countryMappings {
+ isoCode: "gh"
+ timeZoneIds: "Africa/Accra"
+}
+countryMappings {
+ isoCode: "gi"
+ timeZoneIds: "Europe/Gibraltar"
+}
+countryMappings {
+ isoCode: "gl"
+ timeZoneIds: "America/Danmarkshavn"
+ timeZoneIds: "America/Scoresbysund"
+ timeZoneIds: "America/Nuuk"
+ timeZoneIds: "America/Thule"
+ timeZoneLinks {
+ alternativeId: "America/Godthab"
+ preferredId: "America/Nuuk"
+ }
+}
+countryMappings {
+ isoCode: "gm"
+ timeZoneIds: "Africa/Banjul"
+}
+countryMappings {
+ isoCode: "gn"
+ timeZoneIds: "Africa/Conakry"
+}
+countryMappings {
+ isoCode: "gp"
+ timeZoneIds: "America/Guadeloupe"
+}
+countryMappings {
+ isoCode: "gq"
+ timeZoneIds: "Africa/Malabo"
+}
+countryMappings {
+ isoCode: "gr"
+ timeZoneIds: "Europe/Athens"
+}
+countryMappings {
+ isoCode: "gs"
+ timeZoneIds: "Atlantic/South_Georgia"
+}
+countryMappings {
+ isoCode: "gt"
+ timeZoneIds: "America/Guatemala"
+}
+countryMappings {
+ isoCode: "gu"
+ timeZoneIds: "Pacific/Guam"
+}
+countryMappings {
+ isoCode: "gw"
+ timeZoneIds: "Africa/Bissau"
+}
+countryMappings {
+ isoCode: "gy"
+ timeZoneIds: "America/Guyana"
+}
+countryMappings {
+ isoCode: "hk"
+ timeZoneIds: "Asia/Hong_Kong"
+ timeZoneLinks {
+ alternativeId: "Hongkong"
+ preferredId: "Asia/Hong_Kong"
+ }
+}
+countryMappings {
+ isoCode: "hn"
+ timeZoneIds: "America/Tegucigalpa"
+}
+countryMappings {
+ isoCode: "hr"
+ timeZoneIds: "Europe/Zagreb"
+}
+countryMappings {
+ isoCode: "ht"
+ timeZoneIds: "America/Port-au-Prince"
+}
+countryMappings {
+ isoCode: "hu"
+ timeZoneIds: "Europe/Budapest"
+}
+countryMappings {
+ isoCode: "id"
+ timeZoneIds: "Asia/Jayapura"
+ timeZoneIds: "Asia/Makassar"
+ timeZoneIds: "Asia/Jakarta"
+ timeZoneLinks {
+ alternativeId: "Asia/Ujung_Pandang"
+ preferredId: "Asia/Makassar"
+ }
+ timeZoneReplacements {
+ replacedId: "Asia/Pontianak"
+ replacementId: "Asia/Jakarta"
+ fromMillis: 567964800000
+ }
+}
+countryMappings {
+ isoCode: "ie"
+ timeZoneIds: "Europe/Dublin"
+ timeZoneLinks {
+ alternativeId: "Eire"
+ preferredId: "Europe/Dublin"
+ }
+}
+countryMappings {
+ isoCode: "il"
+ timeZoneIds: "Asia/Jerusalem"
+ timeZoneLinks {
+ alternativeId: "Asia/Tel_Aviv"
+ preferredId: "Asia/Jerusalem"
+ }
+ timeZoneLinks {
+ alternativeId: "Israel"
+ preferredId: "Asia/Jerusalem"
+ }
+}
+countryMappings {
+ isoCode: "im"
+ timeZoneIds: "Europe/Isle_of_Man"
+}
+countryMappings {
+ isoCode: "in"
+ timeZoneIds: "Asia/Kolkata"
+ timeZoneLinks {
+ alternativeId: "Asia/Calcutta"
+ preferredId: "Asia/Kolkata"
+ }
+}
+countryMappings {
+ isoCode: "io"
+ timeZoneIds: "Indian/Chagos"
+}
+countryMappings {
+ isoCode: "iq"
+ timeZoneIds: "Asia/Baghdad"
+}
+countryMappings {
+ isoCode: "ir"
+ timeZoneIds: "Asia/Tehran"
+ timeZoneLinks {
+ alternativeId: "Iran"
+ preferredId: "Asia/Tehran"
+ }
+}
+countryMappings {
+ isoCode: "is"
+ timeZoneIds: "Atlantic/Reykjavik"
+ timeZoneLinks {
+ alternativeId: "Iceland"
+ preferredId: "Atlantic/Reykjavik"
+ }
+}
+countryMappings {
+ isoCode: "it"
+ timeZoneIds: "Europe/Rome"
+}
+countryMappings {
+ isoCode: "je"
+ timeZoneIds: "Europe/Jersey"
+}
+countryMappings {
+ isoCode: "jm"
+ timeZoneIds: "America/Jamaica"
+ timeZoneLinks {
+ alternativeId: "Jamaica"
+ preferredId: "America/Jamaica"
+ }
+}
+countryMappings {
+ isoCode: "jo"
+ timeZoneIds: "Asia/Amman"
+}
+countryMappings {
+ isoCode: "jp"
+ timeZoneIds: "Asia/Tokyo"
+ timeZoneLinks {
+ alternativeId: "Japan"
+ preferredId: "Asia/Tokyo"
+ }
+}
+countryMappings {
+ isoCode: "ke"
+ timeZoneIds: "Africa/Nairobi"
+ timeZoneLinks {
+ alternativeId: "Africa/Asmera"
+ preferredId: "Africa/Nairobi"
+ }
+}
+countryMappings {
+ isoCode: "kg"
+ timeZoneIds: "Asia/Bishkek"
+}
+countryMappings {
+ isoCode: "kh"
+ timeZoneIds: "Asia/Phnom_Penh"
+}
+countryMappings {
+ isoCode: "ki"
+ timeZoneIds: "Pacific/Kiritimati"
+ timeZoneIds: "Pacific/Enderbury"
+ timeZoneIds: "Pacific/Tarawa"
+}
+countryMappings {
+ isoCode: "km"
+ timeZoneIds: "Indian/Comoro"
+}
+countryMappings {
+ isoCode: "kn"
+ timeZoneIds: "America/St_Kitts"
+}
+countryMappings {
+ isoCode: "kp"
+ timeZoneIds: "Asia/Pyongyang"
+}
+countryMappings {
+ isoCode: "kr"
+ timeZoneIds: "Asia/Seoul"
+ timeZoneLinks {
+ alternativeId: "ROK"
+ preferredId: "Asia/Seoul"
+ }
+}
+countryMappings {
+ isoCode: "kw"
+ timeZoneIds: "Asia/Kuwait"
+}
+countryMappings {
+ isoCode: "ky"
+ timeZoneIds: "America/Cayman"
+}
+countryMappings {
+ isoCode: "kz"
+ timeZoneIds: "Asia/Almaty"
+ timeZoneIds: "Asia/Oral"
+ timeZoneReplacements {
+ replacedId: "Asia/Qostanay"
+ replacementId: "Asia/Almaty"
+ fromMillis: 1099170000000
+ }
+ timeZoneReplacements {
+ replacedId: "Asia/Aqtau"
+ replacementId: "Asia/Oral"
+ fromMillis: 1099173600000
+ }
+ timeZoneReplacements {
+ replacedId: "Asia/Qyzylorda"
+ replacementId: "Asia/Oral"
+ fromMillis: 1545328800000
+ }
+ timeZoneReplacements {
+ replacedId: "Asia/Aqtobe"
+ replacementId: "Asia/Oral"
+ fromMillis: 1545328800000
+ }
+ timeZoneReplacements {
+ replacedId: "Asia/Atyrau"
+ replacementId: "Asia/Oral"
+ fromMillis: 922572000000
+ }
+}
+countryMappings {
+ isoCode: "la"
+ timeZoneIds: "Asia/Vientiane"
+}
+countryMappings {
+ isoCode: "lb"
+ timeZoneIds: "Asia/Beirut"
+}
+countryMappings {
+ isoCode: "lc"
+ timeZoneIds: "America/St_Lucia"
+}
+countryMappings {
+ isoCode: "li"
+ timeZoneIds: "Europe/Vaduz"
+}
+countryMappings {
+ isoCode: "lk"
+ timeZoneIds: "Asia/Colombo"
+}
+countryMappings {
+ isoCode: "lr"
+ timeZoneIds: "Africa/Monrovia"
+}
+countryMappings {
+ isoCode: "ls"
+ timeZoneIds: "Africa/Maseru"
+}
+countryMappings {
+ isoCode: "lt"
+ timeZoneIds: "Europe/Vilnius"
+}
+countryMappings {
+ isoCode: "lu"
+ timeZoneIds: "Europe/Luxembourg"
+}
+countryMappings {
+ isoCode: "lv"
+ timeZoneIds: "Europe/Riga"
+}
+countryMappings {
+ isoCode: "ly"
+ timeZoneIds: "Africa/Tripoli"
+ timeZoneLinks {
+ alternativeId: "Libya"
+ preferredId: "Africa/Tripoli"
+ }
+}
+countryMappings {
+ isoCode: "ma"
+ timeZoneIds: "Africa/Casablanca"
+}
+countryMappings {
+ isoCode: "mc"
+ timeZoneIds: "Europe/Monaco"
+}
+countryMappings {
+ isoCode: "md"
+ timeZoneIds: "Europe/Chisinau"
+ timeZoneLinks {
+ alternativeId: "Europe/Tiraspol"
+ preferredId: "Europe/Chisinau"
+ }
+}
+countryMappings {
+ isoCode: "me"
+ timeZoneIds: "Europe/Podgorica"
+}
+countryMappings {
+ isoCode: "mf"
+ timeZoneIds: "America/Marigot"
+}
+countryMappings {
+ isoCode: "mg"
+ timeZoneIds: "Indian/Antananarivo"
+}
+countryMappings {
+ isoCode: "mh"
+ timeZoneIds: "Pacific/Majuro"
+ timeZoneLinks {
+ alternativeId: "Kwajalein"
+ preferredId: "Pacific/Kwajalein"
+ }
+ timeZoneReplacements {
+ replacedId: "Pacific/Kwajalein"
+ replacementId: "Pacific/Majuro"
+ fromMillis: 745934400000
+ }
+}
+countryMappings {
+ isoCode: "mk"
+ timeZoneIds: "Europe/Skopje"
+}
+countryMappings {
+ isoCode: "ml"
+ timeZoneIds: "Africa/Bamako"
+}
+countryMappings {
+ isoCode: "mm"
+ timeZoneIds: "Asia/Yangon"
+ timeZoneLinks {
+ alternativeId: "Asia/Rangoon"
+ preferredId: "Asia/Yangon"
+ }
+}
+countryMappings {
+ isoCode: "mn"
+ timeZoneIds: "Asia/Choibalsan"
+ timeZoneIds: "Asia/Ulaanbaatar"
+ timeZoneIds: "Asia/Hovd"
+ timeZoneLinks {
+ alternativeId: "Asia/Ulan_Bator"
+ preferredId: "Asia/Ulaanbaatar"
+ }
+}
+countryMappings {
+ isoCode: "mo"
+ timeZoneIds: "Asia/Macau"
+ timeZoneLinks {
+ alternativeId: "Asia/Macao"
+ preferredId: "Asia/Macau"
+ }
+}
+countryMappings {
+ isoCode: "mp"
+ timeZoneIds: "Pacific/Saipan"
+}
+countryMappings {
+ isoCode: "mq"
+ timeZoneIds: "America/Martinique"
+}
+countryMappings {
+ isoCode: "mr"
+ timeZoneIds: "Africa/Nouakchott"
+}
+countryMappings {
+ isoCode: "ms"
+ timeZoneIds: "America/Montserrat"
+}
+countryMappings {
+ isoCode: "mt"
+ timeZoneIds: "Europe/Malta"
+}
+countryMappings {
+ isoCode: "mu"
+ timeZoneIds: "Indian/Mauritius"
+}
+countryMappings {
+ isoCode: "mv"
+ timeZoneIds: "Indian/Maldives"
+}
+countryMappings {
+ isoCode: "mw"
+ timeZoneIds: "Africa/Blantyre"
+}
+countryMappings {
+ isoCode: "mx"
+ timeZoneIds: "America/Mexico_City"
+ timeZoneIds: "America/Matamoros"
+ timeZoneIds: "America/Cancun"
+ timeZoneIds: "America/Chihuahua"
+ timeZoneIds: "America/Hermosillo"
+ timeZoneIds: "America/Ojinaga"
+ timeZoneIds: "America/Tijuana"
+ timeZoneLinks {
+ alternativeId: "Mexico/General"
+ preferredId: "America/Mexico_City"
+ }
+ timeZoneLinks {
+ alternativeId: "Mexico/BajaSur"
+ preferredId: "America/Mazatlan"
+ }
+ timeZoneLinks {
+ alternativeId: "America/Ensenada"
+ preferredId: "America/Tijuana"
+ }
+ timeZoneLinks {
+ alternativeId: "America/Santa_Isabel"
+ preferredId: "America/Tijuana"
+ }
+ timeZoneLinks {
+ alternativeId: "Mexico/BajaNorte"
+ preferredId: "America/Tijuana"
+ }
+ timeZoneReplacements {
+ replacedId: "America/Merida"
+ replacementId: "America/Mexico_City"
+ fromMillis: 407653200000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Monterrey"
+ replacementId: "America/Mexico_City"
+ fromMillis: 594198000000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Bahia_Banderas"
+ replacementId: "America/Mexico_City"
+ fromMillis: 1270371600000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Mazatlan"
+ replacementId: "America/Chihuahua"
+ fromMillis: 891766800000
+ }
+}
+countryMappings {
+ isoCode: "my"
+ timeZoneIds: "Asia/Kuala_Lumpur"
+ timeZoneReplacements {
+ replacedId: "Asia/Kuching"
+ replacementId: "Asia/Kuala_Lumpur"
+ fromMillis: 378664200000
+ }
+}
+countryMappings {
+ isoCode: "mz"
+ timeZoneIds: "Africa/Maputo"
+}
+countryMappings {
+ isoCode: "na"
+ timeZoneIds: "Africa/Windhoek"
+}
+countryMappings {
+ isoCode: "nc"
+ timeZoneIds: "Pacific/Noumea"
+}
+countryMappings {
+ isoCode: "ne"
+ timeZoneIds: "Africa/Niamey"
+}
+countryMappings {
+ isoCode: "nf"
+ timeZoneIds: "Pacific/Norfolk"
+}
+countryMappings {
+ isoCode: "ng"
+ timeZoneIds: "Africa/Lagos"
+}
+countryMappings {
+ isoCode: "ni"
+ timeZoneIds: "America/Managua"
+}
+countryMappings {
+ isoCode: "nl"
+ timeZoneIds: "Europe/Amsterdam"
+}
+countryMappings {
+ isoCode: "no"
+ timeZoneIds: "Europe/Oslo"
+ timeZoneLinks {
+ alternativeId: "Atlantic/Jan_Mayen"
+ preferredId: "Europe/Oslo"
+ }
+}
+countryMappings {
+ isoCode: "np"
+ timeZoneIds: "Asia/Kathmandu"
+ timeZoneLinks {
+ alternativeId: "Asia/Katmandu"
+ preferredId: "Asia/Kathmandu"
+ }
+}
+countryMappings {
+ isoCode: "nr"
+ timeZoneIds: "Pacific/Nauru"
+}
+countryMappings {
+ isoCode: "nu"
+ timeZoneIds: "Pacific/Niue"
+}
+countryMappings {
+ isoCode: "nz"
+ timeZoneIds: "Pacific/Auckland"
+ timeZoneIds: "Pacific/Chatham"
+ timeZoneLinks {
+ alternativeId: "Antarctica/South_Pole"
+ preferredId: "Pacific/Auckland"
+ }
+ timeZoneLinks {
+ alternativeId: "NZ"
+ preferredId: "Pacific/Auckland"
+ }
+ timeZoneLinks {
+ alternativeId: "NZ-CHAT"
+ preferredId: "Pacific/Chatham"
+ }
+}
+countryMappings {
+ isoCode: "om"
+ timeZoneIds: "Asia/Muscat"
+}
+countryMappings {
+ isoCode: "pa"
+ timeZoneIds: "America/Panama"
+}
+countryMappings {
+ isoCode: "pe"
+ timeZoneIds: "America/Lima"
+}
+countryMappings {
+ isoCode: "pf"
+ timeZoneIds: "Pacific/Gambier"
+ timeZoneIds: "Pacific/Marquesas"
+ timeZoneIds: "Pacific/Tahiti"
+}
+countryMappings {
+ isoCode: "pg"
+ timeZoneIds: "Pacific/Port_Moresby"
+ timeZoneIds: "Pacific/Bougainville"
+}
+countryMappings {
+ isoCode: "ph"
+ timeZoneIds: "Asia/Manila"
+}
+countryMappings {
+ isoCode: "pk"
+ timeZoneIds: "Asia/Karachi"
+}
+countryMappings {
+ isoCode: "pl"
+ timeZoneIds: "Europe/Warsaw"
+ timeZoneLinks {
+ alternativeId: "Poland"
+ preferredId: "Europe/Warsaw"
+ }
+}
+countryMappings {
+ isoCode: "pm"
+ timeZoneIds: "America/Miquelon"
+}
+countryMappings {
+ isoCode: "pn"
+ timeZoneIds: "Pacific/Pitcairn"
+}
+countryMappings {
+ isoCode: "pr"
+ timeZoneIds: "America/Puerto_Rico"
+}
+countryMappings {
+ isoCode: "ps"
+ timeZoneIds: "Asia/Hebron"
+ timeZoneReplacements {
+ replacedId: "Asia/Gaza"
+ replacementId: "Asia/Hebron"
+ fromMillis: 1317330000000
+ }
+}
+countryMappings {
+ isoCode: "pt"
+ timeZoneIds: "Europe/Lisbon"
+ timeZoneIds: "Atlantic/Azores"
+ timeZoneLinks {
+ alternativeId: "Portugal"
+ preferredId: "Europe/Lisbon"
+ }
+ timeZoneReplacements {
+ replacedId: "Atlantic/Madeira"
+ replacementId: "Europe/Lisbon"
+ fromMillis: 828234000000
+ }
+}
+countryMappings {
+ isoCode: "pw"
+ timeZoneIds: "Pacific/Palau"
+}
+countryMappings {
+ isoCode: "py"
+ timeZoneIds: "America/Asuncion"
+}
+countryMappings {
+ isoCode: "qa"
+ timeZoneIds: "Asia/Qatar"
+}
+countryMappings {
+ isoCode: "re"
+ timeZoneIds: "Indian/Reunion"
+}
+countryMappings {
+ isoCode: "ro"
+ timeZoneIds: "Europe/Bucharest"
+}
+countryMappings {
+ isoCode: "rs"
+ timeZoneIds: "Europe/Belgrade"
+}
+countryMappings {
+ isoCode: "ru"
+ timeZoneIds: "Asia/Kamchatka"
+ timeZoneIds: "Asia/Anadyr"
+ timeZoneIds: "Asia/Magadan"
+ timeZoneIds: "Asia/Sakhalin"
+ timeZoneIds: "Asia/Srednekolymsk"
+ timeZoneIds: "Asia/Vladivostok"
+ timeZoneIds: "Asia/Chita"
+ timeZoneIds: "Asia/Irkutsk"
+ timeZoneIds: "Asia/Krasnoyarsk"
+ timeZoneIds: "Asia/Novosibirsk"
+ timeZoneIds: "Asia/Barnaul"
+ timeZoneIds: "Asia/Omsk"
+ timeZoneIds: "Asia/Yekaterinburg"
+ timeZoneIds: "Europe/Samara"
+ timeZoneIds: "Europe/Saratov"
+ timeZoneIds: "Europe/Volgograd"
+ timeZoneIds: "Europe/Moscow"
+ timeZoneIds: "Europe/Kirov"
+ timeZoneIds: "Europe/Kaliningrad"
+ timeZoneLinks {
+ alternativeId: "W-SU"
+ preferredId: "Europe/Moscow"
+ }
+ timeZoneReplacements {
+ replacedId: "Asia/Ust-Nera"
+ replacementId: "Asia/Vladivostok"
+ fromMillis: 1315828800000
+ }
+ timeZoneReplacements {
+ replacedId: "Asia/Yakutsk"
+ replacementId: "Asia/Chita"
+ fromMillis: 1459015200000
+ }
+ timeZoneReplacements {
+ replacedId: "Asia/Khandyga"
+ replacementId: "Asia/Yakutsk"
+ fromMillis: 1315832400000
+ }
+ timeZoneReplacements {
+ replacedId: "Asia/Novokuznetsk"
+ replacementId: "Asia/Krasnoyarsk"
+ fromMillis: 1459022400000
+ }
+ timeZoneReplacements {
+ replacedId: "Asia/Tomsk"
+ replacementId: "Asia/Barnaul"
+ fromMillis: 1464465600000
+ }
+ timeZoneReplacements {
+ replacedId: "Europe/Ulyanovsk"
+ replacementId: "Europe/Saratov"
+ fromMillis: 1480806000000
+ }
+ timeZoneReplacements {
+ replacedId: "Europe/Astrakhan"
+ replacementId: "Europe/Ulyanovsk"
+ fromMillis: 701823600000
+ }
+}
+countryMappings {
+ isoCode: "rw"
+ timeZoneIds: "Africa/Kigali"
+}
+countryMappings {
+ isoCode: "sa"
+ timeZoneIds: "Asia/Riyadh"
+}
+countryMappings {
+ isoCode: "sb"
+ timeZoneIds: "Pacific/Guadalcanal"
+}
+countryMappings {
+ isoCode: "sc"
+ timeZoneIds: "Indian/Mahe"
+}
+countryMappings {
+ isoCode: "sd"
+ timeZoneIds: "Africa/Khartoum"
+}
+countryMappings {
+ isoCode: "se"
+ timeZoneIds: "Europe/Stockholm"
+}
+countryMappings {
+ isoCode: "sg"
+ timeZoneIds: "Asia/Singapore"
+ timeZoneLinks {
+ alternativeId: "Singapore"
+ preferredId: "Asia/Singapore"
+ }
+}
+countryMappings {
+ isoCode: "sh"
+ timeZoneIds: "Atlantic/St_Helena"
+}
+countryMappings {
+ isoCode: "si"
+ timeZoneIds: "Europe/Ljubljana"
+}
+countryMappings {
+ isoCode: "sj"
+ timeZoneIds: "Arctic/Longyearbyen"
+}
+countryMappings {
+ isoCode: "sk"
+ timeZoneIds: "Europe/Bratislava"
+}
+countryMappings {
+ isoCode: "sl"
+ timeZoneIds: "Africa/Freetown"
+}
+countryMappings {
+ isoCode: "sm"
+ timeZoneIds: "Europe/San_Marino"
+}
+countryMappings {
+ isoCode: "sn"
+ timeZoneIds: "Africa/Dakar"
+}
+countryMappings {
+ isoCode: "so"
+ timeZoneIds: "Africa/Mogadishu"
+}
+countryMappings {
+ isoCode: "sr"
+ timeZoneIds: "America/Paramaribo"
+}
+countryMappings {
+ isoCode: "ss"
+ timeZoneIds: "Africa/Juba"
+}
+countryMappings {
+ isoCode: "st"
+ timeZoneIds: "Africa/Sao_Tome"
+}
+countryMappings {
+ isoCode: "sv"
+ timeZoneIds: "America/El_Salvador"
+}
+countryMappings {
+ isoCode: "sx"
+ timeZoneIds: "America/Lower_Princes"
+}
+countryMappings {
+ isoCode: "sy"
+ timeZoneIds: "Asia/Damascus"
+}
+countryMappings {
+ isoCode: "sz"
+ timeZoneIds: "Africa/Mbabane"
+}
+countryMappings {
+ isoCode: "tc"
+ timeZoneIds: "America/Grand_Turk"
+}
+countryMappings {
+ isoCode: "td"
+ timeZoneIds: "Africa/Ndjamena"
+}
+countryMappings {
+ isoCode: "tf"
+ timeZoneIds: "Indian/Kerguelen"
+}
+countryMappings {
+ isoCode: "tg"
+ timeZoneIds: "Africa/Lome"
+}
+countryMappings {
+ isoCode: "th"
+ timeZoneIds: "Asia/Bangkok"
+}
+countryMappings {
+ isoCode: "tj"
+ timeZoneIds: "Asia/Dushanbe"
+}
+countryMappings {
+ isoCode: "tk"
+ timeZoneIds: "Pacific/Fakaofo"
+}
+countryMappings {
+ isoCode: "tl"
+ timeZoneIds: "Asia/Dili"
+}
+countryMappings {
+ isoCode: "tm"
+ timeZoneIds: "Asia/Ashgabat"
+ timeZoneLinks {
+ alternativeId: "Asia/Ashkhabad"
+ preferredId: "Asia/Ashgabat"
+ }
+}
+countryMappings {
+ isoCode: "tn"
+ timeZoneIds: "Africa/Tunis"
+}
+countryMappings {
+ isoCode: "to"
+ timeZoneIds: "Pacific/Tongatapu"
+}
+countryMappings {
+ isoCode: "tr"
+ timeZoneIds: "Europe/Istanbul"
+ timeZoneLinks {
+ alternativeId: "Turkey"
+ preferredId: "Europe/Istanbul"
+ }
+}
+countryMappings {
+ isoCode: "tt"
+ timeZoneIds: "America/Port_of_Spain"
+ timeZoneLinks {
+ alternativeId: "America/Virgin"
+ preferredId: "America/Port_of_Spain"
+ }
+}
+countryMappings {
+ isoCode: "tv"
+ timeZoneIds: "Pacific/Funafuti"
+}
+countryMappings {
+ isoCode: "tw"
+ timeZoneIds: "Asia/Taipei"
+ timeZoneLinks {
+ alternativeId: "ROC"
+ preferredId: "Asia/Taipei"
+ }
+}
+countryMappings {
+ isoCode: "tz"
+ timeZoneIds: "Africa/Dar_es_Salaam"
+}
+countryMappings {
+ isoCode: "ua"
+ timeZoneIds: "Europe/Kiev"
+ timeZoneIds: "Europe/Simferopol"
+ timeZoneReplacements {
+ replacedId: "Europe/Zaporozhye"
+ replacementId: "Europe/Kiev"
+ fromMillis: 686102400000
+ }
+ timeZoneReplacements {
+ replacedId: "Europe/Uzhgorod"
+ replacementId: "Europe/Zaporozhye"
+ fromMillis: 686091600000
+ }
+}
+countryMappings {
+ isoCode: "ug"
+ timeZoneIds: "Africa/Kampala"
+}
+countryMappings {
+ isoCode: "um"
+ timeZoneIds: "Pacific/Wake"
+ timeZoneIds: "Pacific/Midway"
+}
+countryMappings {
+ isoCode: "us"
+ timeZoneIds: "America/New_York"
+ timeZoneIds: "America/Chicago"
+ timeZoneIds: "America/Denver"
+ timeZoneIds: "America/Phoenix"
+ timeZoneIds: "America/Los_Angeles"
+ timeZoneIds: "America/Anchorage"
+ timeZoneIds: "Pacific/Honolulu"
+ timeZoneIds: "America/Adak"
+ timeZoneLinks {
+ alternativeId: "US/Eastern"
+ preferredId: "America/New_York"
+ }
+ timeZoneLinks {
+ alternativeId: "America/Louisville"
+ preferredId: "America/Kentucky/Louisville"
+ }
+ timeZoneLinks {
+ alternativeId: "US/Michigan"
+ preferredId: "America/Detroit"
+ }
+ timeZoneLinks {
+ alternativeId: "America/Fort_Wayne"
+ preferredId: "America/Indiana/Indianapolis"
+ }
+ timeZoneLinks {
+ alternativeId: "America/Indianapolis"
+ preferredId: "America/Indiana/Indianapolis"
+ }
+ timeZoneLinks {
+ alternativeId: "US/East-Indiana"
+ preferredId: "America/Indiana/Indianapolis"
+ }
+ timeZoneLinks {
+ alternativeId: "US/Central"
+ preferredId: "America/Chicago"
+ }
+ timeZoneLinks {
+ alternativeId: "America/Knox_IN"
+ preferredId: "America/Indiana/Knox"
+ }
+ timeZoneLinks {
+ alternativeId: "US/Indiana-Starke"
+ preferredId: "America/Indiana/Knox"
+ }
+ timeZoneLinks {
+ alternativeId: "America/Shiprock"
+ preferredId: "America/Denver"
+ }
+ timeZoneLinks {
+ alternativeId: "Navajo"
+ preferredId: "America/Denver"
+ }
+ timeZoneLinks {
+ alternativeId: "US/Mountain"
+ preferredId: "America/Denver"
+ }
+ timeZoneLinks {
+ alternativeId: "US/Arizona"
+ preferredId: "America/Phoenix"
+ }
+ timeZoneLinks {
+ alternativeId: "US/Pacific"
+ preferredId: "America/Los_Angeles"
+ }
+ timeZoneLinks {
+ alternativeId: "US/Alaska"
+ preferredId: "America/Anchorage"
+ }
+ timeZoneLinks {
+ alternativeId: "Pacific/Johnston"
+ preferredId: "Pacific/Honolulu"
+ }
+ timeZoneLinks {
+ alternativeId: "US/Hawaii"
+ preferredId: "Pacific/Honolulu"
+ }
+ timeZoneLinks {
+ alternativeId: "America/Atka"
+ preferredId: "America/Adak"
+ }
+ timeZoneLinks {
+ alternativeId: "US/Aleutian"
+ preferredId: "America/Adak"
+ }
+ timeZoneReplacements {
+ replacedId: "America/Kentucky/Louisville"
+ replacementId: "America/New_York"
+ fromMillis: 152089200000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Detroit"
+ replacementId: "America/New_York"
+ fromMillis: 167814000000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Indiana/Indianapolis"
+ replacementId: "America/New_York"
+ fromMillis: 1130652000000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Indiana/Vincennes"
+ replacementId: "America/New_York"
+ fromMillis: 1194159600000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Kentucky/Monticello"
+ replacementId: "America/New_York"
+ fromMillis: 972802800000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Indiana/Petersburg"
+ replacementId: "America/Indiana/Vincennes"
+ fromMillis: 247042800000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Indiana/Winamac"
+ replacementId: "America/New_York"
+ fromMillis: 1173600000000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Indiana/Vevay"
+ replacementId: "America/Indiana/Indianapolis"
+ fromMillis: 89186400000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Indiana/Marengo"
+ replacementId: "America/Indiana/Indianapolis"
+ fromMillis: 183535200000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Menominee"
+ replacementId: "America/Chicago"
+ fromMillis: 104918400000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Indiana/Tell_City"
+ replacementId: "America/Chicago"
+ fromMillis: 1143964800000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Indiana/Knox"
+ replacementId: "America/Indiana/Tell_City"
+ fromMillis: 688546800000
+ }
+ timeZoneReplacements {
+ replacedId: "America/North_Dakota/Beulah"
+ replacementId: "America/Chicago"
+ fromMillis: 1289116800000
+ }
+ timeZoneReplacements {
+ replacedId: "America/North_Dakota/New_Salem"
+ replacementId: "America/Chicago"
+ fromMillis: 1067155200000
+ }
+ timeZoneReplacements {
+ replacedId: "America/North_Dakota/Center"
+ replacementId: "America/Chicago"
+ fromMillis: 720000000000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Boise"
+ replacementId: "America/Denver"
+ fromMillis: 129114000000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Juneau"
+ replacementId: "America/Anchorage"
+ fromMillis: 436359600000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Sitka"
+ replacementId: "America/Juneau"
+ fromMillis: 341402400000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Nome"
+ replacementId: "America/Anchorage"
+ fromMillis: 436363200000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Metlakatla"
+ replacementId: "America/Anchorage"
+ fromMillis: 1547978400000
+ }
+ timeZoneReplacements {
+ replacedId: "America/Yakutat"
+ replacementId: "America/Juneau"
+ fromMillis: 436356000000
+ }
+}
+countryMappings {
+ isoCode: "uy"
+ timeZoneIds: "America/Montevideo"
+}
+countryMappings {
+ isoCode: "uz"
+ timeZoneIds: "Asia/Tashkent"
+ timeZoneReplacements {
+ replacedId: "Asia/Samarkand"
+ replacementId: "Asia/Tashkent"
+ fromMillis: 670366800000
+ }
+}
+countryMappings {
+ isoCode: "va"
+ timeZoneIds: "Europe/Vatican"
+}
+countryMappings {
+ isoCode: "vc"
+ timeZoneIds: "America/St_Vincent"
+}
+countryMappings {
+ isoCode: "ve"
+ timeZoneIds: "America/Caracas"
+}
+countryMappings {
+ isoCode: "vg"
+ timeZoneIds: "America/Tortola"
+}
+countryMappings {
+ isoCode: "vi"
+ timeZoneIds: "America/St_Thomas"
+}
+countryMappings {
+ isoCode: "vn"
+ timeZoneIds: "Asia/Ho_Chi_Minh"
+ timeZoneLinks {
+ alternativeId: "Asia/Saigon"
+ preferredId: "Asia/Ho_Chi_Minh"
+ }
+}
+countryMappings {
+ isoCode: "vu"
+ timeZoneIds: "Pacific/Efate"
+}
+countryMappings {
+ isoCode: "wf"
+ timeZoneIds: "Pacific/Wallis"
+}
+countryMappings {
+ isoCode: "ws"
+ timeZoneIds: "Pacific/Apia"
+}
+countryMappings {
+ isoCode: "ye"
+ timeZoneIds: "Asia/Aden"
+}
+countryMappings {
+ isoCode: "yt"
+ timeZoneIds: "Indian/Mayotte"
+}
+countryMappings {
+ isoCode: "za"
+ timeZoneIds: "Africa/Johannesburg"
+}
+countryMappings {
+ isoCode: "zm"
+ timeZoneIds: "Africa/Lusaka"
+}
+countryMappings {
+ isoCode: "zw"
+ timeZoneIds: "Africa/Harare"
+}
diff --git a/output_data/android/tzlookup.xml b/output_data/android/tzlookup.xml
index ad1bf62..a41aee4 100644
--- a/output_data/android/tzlookup.xml
+++ b/output_data/android/tzlookup.xml
@@ -41,38 +41,38 @@
<id>Antarctica/Palmer</id>
</country>
<country code="ar" default="America/Argentina/Buenos_Aires" defaultBoost="y" everutc="n">
- <id>America/Argentina/Buenos_Aires</id>
- <id notafter="687931200000">America/Argentina/Cordoba</id>
- <id notafter="1096171200000">America/Argentina/Salta</id>
- <id notafter="687931200000">America/Argentina/Jujuy</id>
- <id notafter="1087099200000">America/Argentina/Tucuman</id>
- <id notafter="1087704000000">America/Argentina/Catamarca</id>
- <id notafter="687931200000">America/Argentina/La_Rioja</id>
- <id notafter="1090728000000">America/Argentina/San_Juan</id>
- <id notafter="1237082400000">America/Argentina/Mendoza</id>
+ <id alts="America/Buenos_Aires">America/Argentina/Buenos_Aires</id>
+ <id notafter="687931200000" repl="America/Argentina/Buenos_Aires" alts="America/Cordoba,America/Rosario">America/Argentina/Cordoba</id>
+ <id notafter="1237082400000" repl="America/Argentina/Buenos_Aires" alts="America/Mendoza">America/Argentina/Mendoza</id>
+ <id notafter="1087099200000" repl="America/Argentina/Buenos_Aires">America/Argentina/Tucuman</id>
+ <id notafter="1096171200000" repl="America/Argentina/Mendoza">America/Argentina/Salta</id>
+ <id notafter="1090728000000" repl="America/Argentina/Salta">America/Argentina/San_Juan</id>
+ <id notafter="687931200000" repl="America/Argentina/Salta" alts="America/Jujuy">America/Argentina/Jujuy</id>
+ <id notafter="1087704000000" repl="America/Argentina/Salta" alts="America/Argentina/ComodRivadavia,America/Catamarca">America/Argentina/Catamarca</id>
+ <id notafter="687931200000" repl="America/Argentina/Catamarca">America/Argentina/La_Rioja</id>
+ <id notafter="673588800000" repl="America/Argentina/La_Rioja">America/Argentina/Rio_Gallegos</id>
+ <id notafter="1087704000000" repl="America/Argentina/Salta">America/Argentina/Ushuaia</id>
<id>America/Argentina/San_Luis</id>
- <id notafter="673588800000">America/Argentina/Rio_Gallegos</id>
- <id notafter="1087704000000">America/Argentina/Ushuaia</id>
</country>
<country code="as" default="Pacific/Pago_Pago" everutc="n">
- <id>Pacific/Pago_Pago</id>
+ <id alts="Pacific/Samoa,US/Samoa">Pacific/Pago_Pago</id>
</country>
<country code="at" default="Europe/Vienna" everutc="n">
<id>Europe/Vienna</id>
</country>
<country code="au" default="Australia/Sydney" everutc="n">
- <id>Australia/Sydney</id>
- <id notafter="796147200000">Australia/Melbourne</id>
- <id>Australia/Brisbane</id>
- <id notafter="1193500800000">Australia/Hobart</id>
- <id notafter="37728000000">Australia/Currie</id>
- <id notafter="762883200000">Australia/Lindeman</id>
- <id notafter="1286035200000">Antarctica/Macquarie</id>
- <id>Australia/Lord_Howe</id>
- <id>Australia/Adelaide</id>
- <id notafter="796149000000">Australia/Broken_Hill</id>
- <id>Australia/Darwin</id>
- <id>Australia/Perth</id>
+ <id alts="Australia/ACT,Australia/Canberra,Australia/NSW">Australia/Sydney</id>
+ <id notafter="796147200000" repl="Australia/Sydney" alts="Australia/Victoria">Australia/Melbourne</id>
+ <id notafter="1193500800000" repl="Australia/Sydney" alts="Australia/Tasmania">Australia/Hobart</id>
+ <id notafter="37728000000" repl="Australia/Hobart">Australia/Currie</id>
+ <id alts="Australia/Queensland">Australia/Brisbane</id>
+ <id notafter="762883200000" repl="Australia/Brisbane">Australia/Lindeman</id>
+ <id notafter="1286035200000" repl="Australia/Sydney">Antarctica/Macquarie</id>
+ <id alts="Australia/LHI">Australia/Lord_Howe</id>
+ <id alts="Australia/South">Australia/Adelaide</id>
+ <id notafter="796149000000" repl="Australia/Adelaide" alts="Australia/Yancowinna">Australia/Broken_Hill</id>
+ <id alts="Australia/North">Australia/Darwin</id>
+ <id alts="Australia/West">Australia/Perth</id>
<id>Australia/Eucla</id>
</country>
<country code="aw" default="America/Aruba" everutc="n">
@@ -91,7 +91,7 @@
<id>America/Barbados</id>
</country>
<country code="bd" default="Asia/Dhaka" everutc="n">
- <id>Asia/Dhaka</id>
+ <id alts="Asia/Dacca">Asia/Dhaka</id>
</country>
<country code="be" default="Europe/Brussels" everutc="n">
<id>Europe/Brussels</id>
@@ -127,28 +127,28 @@
<id>America/Kralendijk</id>
</country>
<country code="br" default="America/Noronha" everutc="n">
- <id>America/Noronha</id>
- <id>America/Sao_Paulo</id>
- <id notafter="1013911200000">America/Belem</id>
- <id notafter="972180000000">America/Fortaleza</id>
- <id notafter="1330221600000">America/Recife</id>
- <id notafter="1361066400000">America/Araguaina</id>
- <id notafter="824004000000">America/Maceio</id>
- <id notafter="1550368800000">America/Bahia</id>
- <id notafter="1214280000000">America/Santarem</id>
- <id>America/Manaus</id>
- <id notafter="1076814000000">America/Campo_Grande</id>
- <id notafter="1550372400000">America/Cuiaba</id>
- <id notafter="761713200000">America/Porto_Velho</id>
- <id notafter="971578800000">America/Boa_Vista</id>
- <id notafter="761716800000">America/Eirunepe</id>
- <id>America/Rio_Branco</id>
+ <id alts="Brazil/DeNoronha">America/Noronha</id>
+ <id alts="Brazil/East">America/Sao_Paulo</id>
+ <id notafter="1550368800000" repl="America/Sao_Paulo">America/Bahia</id>
+ <id notafter="1214280000000" repl="America/Recife">America/Santarem</id>
+ <id notafter="1330221600000" repl="America/Bahia">America/Recife</id>
+ <id notafter="972180000000" repl="America/Recife">America/Fortaleza</id>
+ <id notafter="1013911200000" repl="America/Recife">America/Belem</id>
+ <id notafter="824004000000" repl="America/Fortaleza">America/Maceio</id>
+ <id notafter="1361066400000" repl="America/Bahia">America/Araguaina</id>
+ <id alts="Brazil/West">America/Manaus</id>
+ <id notafter="1550372400000" repl="America/Manaus">America/Cuiaba</id>
+ <id notafter="1076814000000" repl="America/Cuiaba">America/Campo_Grande</id>
+ <id notafter="761713200000" repl="America/Manaus">America/Porto_Velho</id>
+ <id notafter="971578800000" repl="America/Manaus">America/Boa_Vista</id>
+ <id alts="America/Porto_Acre,Brazil/Acre">America/Rio_Branco</id>
+ <id notafter="761716800000" repl="America/Rio_Branco">America/Eirunepe</id>
</country>
<country code="bs" default="America/Nassau" everutc="n">
<id>America/Nassau</id>
</country>
<country code="bt" default="Asia/Thimphu" everutc="n">
- <id>Asia/Thimphu</id>
+ <id alts="Asia/Thimbu">Asia/Thimphu</id>
</country>
<country code="bw" default="Africa/Gaborone" everutc="n">
<id>Africa/Gaborone</id>
@@ -160,34 +160,34 @@
<id>America/Belize</id>
</country>
<country code="ca" default="America/Toronto" everutc="n">
- <id>America/Toronto</id>
- <id>America/Vancouver</id>
- <id>America/Edmonton</id>
- <id>America/Winnipeg</id>
- <id>America/Halifax</id>
- <id>America/St_Johns</id>
- <id notafter="57733200000">America/Glace_Bay</id>
- <id notafter="1162098000000">America/Moncton</id>
- <id notafter="1299996000000">America/Goose_Bay</id>
+ <id alts="America/Montreal,Canada/Eastern">America/Toronto</id>
+ <id alts="Canada/Pacific">America/Vancouver</id>
+ <id alts="Canada/Mountain">America/Edmonton</id>
+ <id alts="Canada/Central">America/Winnipeg</id>
+ <id alts="Canada/Atlantic">America/Halifax</id>
+ <id alts="Canada/Newfoundland">America/St_Johns</id>
+ <id notafter="1162098000000" repl="America/Halifax">America/Moncton</id>
+ <id notafter="57733200000" repl="America/Halifax">America/Glace_Bay</id>
+ <id notafter="1299996000000" repl="America/Halifax">America/Goose_Bay</id>
<id>America/Blanc-Sablon</id>
- <id notafter="89186400000">America/Nipigon</id>
- <id notafter="120636000000">America/Thunder_Bay</id>
- <id notafter="972802800000">America/Iqaluit</id>
- <id notafter="796806000000">America/Pangnirtung</id>
- <id>America/Atikokan</id>
- <id>America/Regina</id>
- <id notafter="1130659200000">America/Rankin_Inlet</id>
- <id notafter="986112000000">America/Rainy_River</id>
- <id notafter="73472400000">America/Swift_Current</id>
- <id notafter="1173600000000">America/Resolute</id>
- <id notafter="986115600000">America/Cambridge_Bay</id>
- <id notafter="309945600000">America/Yellowknife</id>
- <id notafter="294228000000">America/Inuvik</id>
- <id>America/Whitehorse</id>
- <id notafter="1604214000000">America/Dawson_Creek</id>
- <id notafter="84013200000">America/Creston</id>
- <id notafter="1425808800000">America/Fort_Nelson</id>
- <id notafter="120646800000">America/Dawson</id>
+ <id notafter="120636000000" repl="America/Toronto">America/Thunder_Bay</id>
+ <id notafter="972802800000" repl="America/Toronto">America/Iqaluit</id>
+ <id notafter="89186400000" repl="America/Thunder_Bay">America/Nipigon</id>
+ <id notafter="796806000000" repl="America/Iqaluit">America/Pangnirtung</id>
+ <id alts="America/Coral_Harbour">America/Atikokan</id>
+ <id alts="Canada/Saskatchewan">America/Regina</id>
+ <id notafter="73472400000" repl="America/Regina">America/Swift_Current</id>
+ <id notafter="1130659200000" repl="America/Winnipeg">America/Rankin_Inlet</id>
+ <id notafter="986112000000" repl="America/Rankin_Inlet">America/Rainy_River</id>
+ <id notafter="1173600000000" repl="America/Winnipeg">America/Resolute</id>
+ <id notafter="309945600000" repl="America/Edmonton">America/Yellowknife</id>
+ <id notafter="1604214000000" repl="America/Whitehorse">America/Dawson_Creek</id>
+ <id notafter="84013200000" repl="America/Dawson_Creek">America/Creston</id>
+ <id notafter="1425808800000" repl="America/Dawson_Creek">America/Fort_Nelson</id>
+ <id notafter="294228000000" repl="America/Yellowknife">America/Inuvik</id>
+ <id notafter="986115600000" repl="America/Edmonton">America/Cambridge_Bay</id>
+ <id notafter="120646800000" repl="America/Whitehorse">America/Dawson</id>
+ <id alts="Canada/Yukon">America/Whitehorse</id>
</country>
<country code="cc" default="Indian/Cocos" everutc="n">
<id>Indian/Cocos</id>
@@ -206,22 +206,22 @@
<id>Europe/Zurich</id>
</country>
<country code="ci" default="Africa/Abidjan" everutc="y">
- <id>Africa/Abidjan</id>
+ <id alts="Africa/Timbuktu">Africa/Abidjan</id>
</country>
<country code="ck" default="Pacific/Rarotonga" everutc="n">
<id>Pacific/Rarotonga</id>
</country>
<country code="cl" default="America/Santiago" everutc="n">
<id>America/Punta_Arenas</id>
- <id>America/Santiago</id>
- <id>Pacific/Easter</id>
+ <id alts="Chile/Continental">America/Santiago</id>
+ <id alts="Chile/EasterIsland">Pacific/Easter</id>
</country>
<country code="cm" default="Africa/Douala" everutc="n">
<id>Africa/Douala</id>
</country>
<country code="cn" default="Asia/Shanghai" defaultBoost="y" everutc="n">
- <id>Asia/Shanghai</id>
- <id>Asia/Urumqi</id>
+ <id alts="Asia/Chongqing,Asia/Chungking,Asia/Harbin,PRC">Asia/Shanghai</id>
+ <id alts="Asia/Kashgar">Asia/Urumqi</id>
</country>
<country code="co" default="America/Bogota" everutc="n">
<id>America/Bogota</id>
@@ -230,7 +230,7 @@
<id>America/Costa_Rica</id>
</country>
<country code="cu" default="America/Havana" everutc="n">
- <id>America/Havana</id>
+ <id alts="Cuba">America/Havana</id>
</country>
<country code="cv" default="Atlantic/Cape_Verde" everutc="n">
<id>Atlantic/Cape_Verde</id>
@@ -250,7 +250,7 @@
</country>
<country code="de" default="Europe/Berlin" everutc="n">
<id>Europe/Berlin</id>
- <id notafter="338950800000">Europe/Busingen</id>
+ <id notafter="338950800000" repl="Europe/Berlin">Europe/Busingen</id>
</country>
<country code="dj" default="Africa/Djibouti" everutc="n">
<id>Africa/Djibouti</id>
@@ -275,7 +275,7 @@
<id>Europe/Tallinn</id>
</country>
<country code="eg" default="Africa/Cairo" everutc="n">
- <id>Africa/Cairo</id>
+ <id alts="Egypt">Africa/Cairo</id>
</country>
<country code="eh" default="Africa/El_Aaiun" everutc="y">
<id>Africa/El_Aaiun</id>
@@ -285,7 +285,7 @@
</country>
<country code="es" default="Europe/Madrid" everutc="y">
<id>Europe/Madrid</id>
- <id notafter="496803600000">Africa/Ceuta</id>
+ <id notafter="496803600000" repl="Europe/Madrid">Africa/Ceuta</id>
<id>Atlantic/Canary</id>
</country>
<country code="et" default="Africa/Addis_Ababa" everutc="n">
@@ -301,12 +301,12 @@
<id>Atlantic/Stanley</id>
</country>
<country code="fm" default="Pacific/Pohnpei" everutc="n">
- <id>Pacific/Pohnpei</id>
+ <id alts="Pacific/Ponape">Pacific/Pohnpei</id>
<id>Pacific/Kosrae</id>
- <id>Pacific/Chuuk</id>
+ <id alts="Pacific/Truk,Pacific/Yap">Pacific/Chuuk</id>
</country>
<country code="fo" default="Atlantic/Faroe" everutc="y">
- <id>Atlantic/Faroe</id>
+ <id alts="Atlantic/Faeroe">Atlantic/Faroe</id>
</country>
<country code="fr" default="Europe/Paris" everutc="n">
<id>Europe/Paris</id>
@@ -315,7 +315,7 @@
<id>Africa/Libreville</id>
</country>
<country code="gb" default="Europe/London" everutc="y">
- <id>Europe/London</id>
+ <id alts="Europe/Belfast,GB,GB-Eire">Europe/London</id>
</country>
<country code="gd" default="America/Grenada" everutc="n">
<id>America/Grenada</id>
@@ -338,7 +338,7 @@
<country code="gl" default="America/Nuuk" everutc="y">
<id>America/Danmarkshavn</id>
<id>America/Scoresbysund</id>
- <id>America/Nuuk</id>
+ <id alts="America/Godthab">America/Nuuk</id>
<id>America/Thule</id>
</country>
<country code="gm" default="Africa/Banjul" everutc="y">
@@ -372,7 +372,7 @@
<id>America/Guyana</id>
</country>
<country code="hk" default="Asia/Hong_Kong" everutc="n">
- <id>Asia/Hong_Kong</id>
+ <id alts="Hongkong">Asia/Hong_Kong</id>
</country>
<country code="hn" default="America/Tegucigalpa" everutc="n">
<id>America/Tegucigalpa</id>
@@ -388,21 +388,21 @@
</country>
<country code="id" default="Asia/Jakarta" everutc="n">
<id>Asia/Jayapura</id>
- <id>Asia/Makassar</id>
+ <id alts="Asia/Ujung_Pandang">Asia/Makassar</id>
<id>Asia/Jakarta</id>
- <id notafter="567964800000">Asia/Pontianak</id>
+ <id notafter="567964800000" repl="Asia/Jakarta">Asia/Pontianak</id>
</country>
<country code="ie" default="Europe/Dublin" everutc="y">
- <id>Europe/Dublin</id>
+ <id alts="Eire">Europe/Dublin</id>
</country>
<country code="il" default="Asia/Jerusalem" everutc="n">
- <id>Asia/Jerusalem</id>
+ <id alts="Asia/Tel_Aviv,Israel">Asia/Jerusalem</id>
</country>
<country code="im" default="Europe/Isle_of_Man" everutc="y">
<id>Europe/Isle_of_Man</id>
</country>
<country code="in" default="Asia/Kolkata" everutc="n">
- <id>Asia/Kolkata</id>
+ <id alts="Asia/Calcutta">Asia/Kolkata</id>
</country>
<country code="io" default="Indian/Chagos" everutc="n">
<id>Indian/Chagos</id>
@@ -411,10 +411,10 @@
<id>Asia/Baghdad</id>
</country>
<country code="ir" default="Asia/Tehran" everutc="n">
- <id>Asia/Tehran</id>
+ <id alts="Iran">Asia/Tehran</id>
</country>
<country code="is" default="Atlantic/Reykjavik" everutc="y">
- <id>Atlantic/Reykjavik</id>
+ <id alts="Iceland">Atlantic/Reykjavik</id>
</country>
<country code="it" default="Europe/Rome" everutc="n">
<id>Europe/Rome</id>
@@ -423,16 +423,16 @@
<id>Europe/Jersey</id>
</country>
<country code="jm" default="America/Jamaica" everutc="n">
- <id>America/Jamaica</id>
+ <id alts="Jamaica">America/Jamaica</id>
</country>
<country code="jo" default="Asia/Amman" everutc="n">
<id>Asia/Amman</id>
</country>
<country code="jp" default="Asia/Tokyo" everutc="n">
- <id>Asia/Tokyo</id>
+ <id alts="Japan">Asia/Tokyo</id>
</country>
<country code="ke" default="Africa/Nairobi" everutc="n">
- <id>Africa/Nairobi</id>
+ <id alts="Africa/Asmera">Africa/Nairobi</id>
</country>
<country code="kg" default="Asia/Bishkek" everutc="n">
<id>Asia/Bishkek</id>
@@ -455,7 +455,7 @@
<id>Asia/Pyongyang</id>
</country>
<country code="kr" default="Asia/Seoul" everutc="n">
- <id>Asia/Seoul</id>
+ <id alts="ROK">Asia/Seoul</id>
</country>
<country code="kw" default="Asia/Kuwait" everutc="n">
<id>Asia/Kuwait</id>
@@ -465,12 +465,12 @@
</country>
<country code="kz" default="Asia/Almaty" everutc="n">
<id>Asia/Almaty</id>
- <id notafter="1099170000000">Asia/Qostanay</id>
- <id notafter="1099173600000">Asia/Aqtau</id>
+ <id notafter="1099170000000" repl="Asia/Almaty">Asia/Qostanay</id>
<id>Asia/Oral</id>
- <id notafter="1545328800000">Asia/Qyzylorda</id>
- <id notafter="1545328800000">Asia/Aqtobe</id>
- <id notafter="922572000000">Asia/Atyrau</id>
+ <id notafter="1099173600000" repl="Asia/Oral">Asia/Aqtau</id>
+ <id notafter="1545328800000" repl="Asia/Oral">Asia/Qyzylorda</id>
+ <id notafter="1545328800000" repl="Asia/Oral">Asia/Aqtobe</id>
+ <id notafter="922572000000" repl="Asia/Oral">Asia/Atyrau</id>
</country>
<country code="la" default="Asia/Vientiane" everutc="n">
<id>Asia/Vientiane</id>
@@ -503,7 +503,7 @@
<id>Europe/Riga</id>
</country>
<country code="ly" default="Africa/Tripoli" everutc="n">
- <id>Africa/Tripoli</id>
+ <id alts="Libya">Africa/Tripoli</id>
</country>
<country code="ma" default="Africa/Casablanca" everutc="y">
<id>Africa/Casablanca</id>
@@ -512,7 +512,7 @@
<id>Europe/Monaco</id>
</country>
<country code="md" default="Europe/Chisinau" everutc="n">
- <id>Europe/Chisinau</id>
+ <id alts="Europe/Tiraspol">Europe/Chisinau</id>
</country>
<country code="me" default="Europe/Podgorica" everutc="n">
<id>Europe/Podgorica</id>
@@ -525,7 +525,7 @@
</country>
<country code="mh" default="Pacific/Majuro" everutc="n">
<id>Pacific/Majuro</id>
- <id notafter="745934400000">Pacific/Kwajalein</id>
+ <id notafter="745934400000" repl="Pacific/Majuro" alts="Kwajalein">Pacific/Kwajalein</id>
</country>
<country code="mk" default="Europe/Skopje" everutc="n">
<id>Europe/Skopje</id>
@@ -534,15 +534,15 @@
<id>Africa/Bamako</id>
</country>
<country code="mm" default="Asia/Yangon" everutc="n">
- <id>Asia/Yangon</id>
+ <id alts="Asia/Rangoon">Asia/Yangon</id>
</country>
<country code="mn" default="Asia/Ulaanbaatar" everutc="n">
<id>Asia/Choibalsan</id>
- <id>Asia/Ulaanbaatar</id>
+ <id alts="Asia/Ulan_Bator">Asia/Ulaanbaatar</id>
<id>Asia/Hovd</id>
</country>
<country code="mo" default="Asia/Macau" everutc="n">
- <id>Asia/Macau</id>
+ <id alts="Asia/Macao">Asia/Macau</id>
</country>
<country code="mp" default="Pacific/Saipan" everutc="n">
<id>Pacific/Saipan</id>
@@ -569,21 +569,21 @@
<id>Africa/Blantyre</id>
</country>
<country code="mx" default="America/Mexico_City" everutc="n">
- <id>America/Mexico_City</id>
- <id notafter="407653200000">America/Merida</id>
- <id notafter="594198000000">America/Monterrey</id>
+ <id alts="Mexico/General">America/Mexico_City</id>
+ <id notafter="407653200000" repl="America/Mexico_City">America/Merida</id>
+ <id notafter="594198000000" repl="America/Mexico_City">America/Monterrey</id>
+ <id notafter="1270371600000" repl="America/Mexico_City">America/Bahia_Banderas</id>
<id>America/Matamoros</id>
- <id notafter="1270371600000">America/Bahia_Banderas</id>
<id>America/Cancun</id>
<id>America/Chihuahua</id>
+ <id notafter="891766800000" repl="America/Chihuahua" alts="Mexico/BajaSur">America/Mazatlan</id>
<id>America/Hermosillo</id>
- <id notafter="891766800000">America/Mazatlan</id>
<id>America/Ojinaga</id>
- <id>America/Tijuana</id>
+ <id alts="America/Ensenada,America/Santa_Isabel,Mexico/BajaNorte">America/Tijuana</id>
</country>
<country code="my" default="Asia/Kuala_Lumpur" everutc="n">
<id>Asia/Kuala_Lumpur</id>
- <id notafter="378664200000">Asia/Kuching</id>
+ <id notafter="378664200000" repl="Asia/Kuala_Lumpur">Asia/Kuching</id>
</country>
<country code="mz" default="Africa/Maputo" everutc="n">
<id>Africa/Maputo</id>
@@ -610,10 +610,10 @@
<id>Europe/Amsterdam</id>
</country>
<country code="no" default="Europe/Oslo" everutc="n">
- <id>Europe/Oslo</id>
+ <id alts="Atlantic/Jan_Mayen">Europe/Oslo</id>
</country>
<country code="np" default="Asia/Kathmandu" everutc="n">
- <id>Asia/Kathmandu</id>
+ <id alts="Asia/Katmandu">Asia/Kathmandu</id>
</country>
<country code="nr" default="Pacific/Nauru" everutc="n">
<id>Pacific/Nauru</id>
@@ -622,8 +622,8 @@
<id>Pacific/Niue</id>
</country>
<country code="nz" default="Pacific/Auckland" defaultBoost="y" everutc="n">
- <id>Pacific/Auckland</id>
- <id>Pacific/Chatham</id>
+ <id alts="Antarctica/South_Pole,NZ">Pacific/Auckland</id>
+ <id alts="NZ-CHAT">Pacific/Chatham</id>
</country>
<country code="om" default="Asia/Muscat" everutc="n">
<id>Asia/Muscat</id>
@@ -650,7 +650,7 @@
<id>Asia/Karachi</id>
</country>
<country code="pl" default="Europe/Warsaw" everutc="n">
- <id>Europe/Warsaw</id>
+ <id alts="Poland">Europe/Warsaw</id>
</country>
<country code="pm" default="America/Miquelon" everutc="n">
<id>America/Miquelon</id>
@@ -662,12 +662,12 @@
<id>America/Puerto_Rico</id>
</country>
<country code="ps" default="Asia/Gaza" everutc="n">
- <id notafter="1317330000000">Asia/Gaza</id>
<id>Asia/Hebron</id>
+ <id notafter="1317330000000" repl="Asia/Hebron">Asia/Gaza</id>
</country>
<country code="pt" default="Europe/Lisbon" everutc="y">
- <id>Europe/Lisbon</id>
- <id notafter="828234000000">Atlantic/Madeira</id>
+ <id alts="Portugal">Europe/Lisbon</id>
+ <id notafter="828234000000" repl="Europe/Lisbon">Atlantic/Madeira</id>
<id>Atlantic/Azores</id>
</country>
<country code="pw" default="Pacific/Palau" everutc="n">
@@ -695,24 +695,24 @@
<id>Asia/Sakhalin</id>
<id>Asia/Srednekolymsk</id>
<id>Asia/Vladivostok</id>
- <id notafter="1315828800000">Asia/Ust-Nera</id>
- <id notafter="1459015200000">Asia/Yakutsk</id>
+ <id notafter="1315828800000" repl="Asia/Vladivostok">Asia/Ust-Nera</id>
<id>Asia/Chita</id>
- <id notafter="1315832400000">Asia/Khandyga</id>
+ <id notafter="1459015200000" repl="Asia/Chita">Asia/Yakutsk</id>
+ <id notafter="1315832400000" repl="Asia/Yakutsk">Asia/Khandyga</id>
<id>Asia/Irkutsk</id>
<id>Asia/Krasnoyarsk</id>
+ <id notafter="1459022400000" repl="Asia/Krasnoyarsk">Asia/Novokuznetsk</id>
<id>Asia/Novosibirsk</id>
<id>Asia/Barnaul</id>
- <id notafter="1459022400000">Asia/Novokuznetsk</id>
- <id notafter="1464465600000">Asia/Tomsk</id>
+ <id notafter="1464465600000" repl="Asia/Barnaul">Asia/Tomsk</id>
<id>Asia/Omsk</id>
<id>Asia/Yekaterinburg</id>
<id>Europe/Samara</id>
- <id notafter="701823600000">Europe/Astrakhan</id>
- <id notafter="1480806000000">Europe/Ulyanovsk</id>
<id>Europe/Saratov</id>
+ <id notafter="1480806000000" repl="Europe/Saratov">Europe/Ulyanovsk</id>
+ <id notafter="701823600000" repl="Europe/Ulyanovsk">Europe/Astrakhan</id>
<id>Europe/Volgograd</id>
- <id>Europe/Moscow</id>
+ <id alts="W-SU">Europe/Moscow</id>
<id>Europe/Kirov</id>
<id>Europe/Kaliningrad</id>
</country>
@@ -735,7 +735,7 @@
<id>Europe/Stockholm</id>
</country>
<country code="sg" default="Asia/Singapore" everutc="n">
- <id>Asia/Singapore</id>
+ <id alts="Singapore">Asia/Singapore</id>
</country>
<country code="sh" default="Atlantic/St_Helena" everutc="y">
<id>Atlantic/St_Helena</id>
@@ -807,7 +807,7 @@
<id>Asia/Dili</id>
</country>
<country code="tm" default="Asia/Ashgabat" everutc="n">
- <id>Asia/Ashgabat</id>
+ <id alts="Asia/Ashkhabad">Asia/Ashgabat</id>
</country>
<country code="tn" default="Africa/Tunis" everutc="n">
<id>Africa/Tunis</id>
@@ -816,24 +816,24 @@
<id>Pacific/Tongatapu</id>
</country>
<country code="tr" default="Europe/Istanbul" everutc="n">
- <id>Europe/Istanbul</id>
+ <id alts="Turkey">Europe/Istanbul</id>
</country>
<country code="tt" default="America/Port_of_Spain" everutc="n">
- <id>America/Port_of_Spain</id>
+ <id alts="America/Virgin">America/Port_of_Spain</id>
</country>
<country code="tv" default="Pacific/Funafuti" everutc="n">
<id>Pacific/Funafuti</id>
</country>
<country code="tw" default="Asia/Taipei" everutc="n">
- <id>Asia/Taipei</id>
+ <id alts="ROC">Asia/Taipei</id>
</country>
<country code="tz" default="Africa/Dar_es_Salaam" everutc="n">
<id>Africa/Dar_es_Salaam</id>
</country>
<country code="ua" default="Europe/Kiev" everutc="n">
<id>Europe/Kiev</id>
- <id notafter="686091600000">Europe/Uzhgorod</id>
- <id notafter="686102400000">Europe/Zaporozhye</id>
+ <id notafter="686102400000" repl="Europe/Kiev">Europe/Zaporozhye</id>
+ <id notafter="686091600000" repl="Europe/Zaporozhye">Europe/Uzhgorod</id>
<id picker="n">Europe/Simferopol</id>
</country>
<country code="ug" default="Africa/Kampala" everutc="n">
@@ -844,42 +844,42 @@
<id>Pacific/Midway</id>
</country>
<country code="us" default="America/New_York" everutc="n">
- <id>America/New_York</id>
- <id notafter="167814000000">America/Detroit</id>
- <id notafter="152089200000">America/Kentucky/Louisville</id>
- <id notafter="972802800000">America/Kentucky/Monticello</id>
- <id notafter="1130652000000">America/Indiana/Indianapolis</id>
- <id notafter="1194159600000">America/Indiana/Vincennes</id>
- <id notafter="1173600000000">America/Indiana/Winamac</id>
- <id notafter="183535200000">America/Indiana/Marengo</id>
- <id notafter="247042800000">America/Indiana/Petersburg</id>
- <id notafter="89186400000">America/Indiana/Vevay</id>
- <id>America/Chicago</id>
- <id notafter="688546800000">America/Indiana/Knox</id>
- <id notafter="104918400000">America/Menominee</id>
- <id notafter="720000000000">America/North_Dakota/Center</id>
- <id notafter="1067155200000">America/North_Dakota/New_Salem</id>
- <id notafter="1143964800000">America/Indiana/Tell_City</id>
- <id notafter="1289116800000">America/North_Dakota/Beulah</id>
- <id>America/Denver</id>
- <id notafter="129114000000">America/Boise</id>
- <id>America/Phoenix</id>
- <id>America/Los_Angeles</id>
- <id>America/Anchorage</id>
- <id notafter="436359600000">America/Juneau</id>
- <id notafter="436356000000">America/Yakutat</id>
- <id notafter="436363200000">America/Nome</id>
- <id notafter="1547978400000">America/Metlakatla</id>
- <id notafter="341402400000">America/Sitka</id>
- <id>Pacific/Honolulu</id>
- <id>America/Adak</id>
+ <id alts="US/Eastern">America/New_York</id>
+ <id notafter="152089200000" repl="America/New_York" alts="America/Louisville">America/Kentucky/Louisville</id>
+ <id notafter="167814000000" repl="America/New_York" alts="US/Michigan">America/Detroit</id>
+ <id notafter="1130652000000" repl="America/New_York" alts="America/Fort_Wayne,America/Indianapolis,US/East-Indiana">America/Indiana/Indianapolis</id>
+ <id notafter="1194159600000" repl="America/New_York">America/Indiana/Vincennes</id>
+ <id notafter="972802800000" repl="America/New_York">America/Kentucky/Monticello</id>
+ <id notafter="247042800000" repl="America/Indiana/Vincennes">America/Indiana/Petersburg</id>
+ <id notafter="1173600000000" repl="America/New_York">America/Indiana/Winamac</id>
+ <id notafter="89186400000" repl="America/Indiana/Indianapolis">America/Indiana/Vevay</id>
+ <id notafter="183535200000" repl="America/Indiana/Indianapolis">America/Indiana/Marengo</id>
+ <id alts="US/Central">America/Chicago</id>
+ <id notafter="104918400000" repl="America/Chicago">America/Menominee</id>
+ <id notafter="1143964800000" repl="America/Chicago">America/Indiana/Tell_City</id>
+ <id notafter="688546800000" repl="America/Indiana/Tell_City" alts="America/Knox_IN,US/Indiana-Starke">America/Indiana/Knox</id>
+ <id notafter="1289116800000" repl="America/Chicago">America/North_Dakota/Beulah</id>
+ <id notafter="1067155200000" repl="America/Chicago">America/North_Dakota/New_Salem</id>
+ <id notafter="720000000000" repl="America/Chicago">America/North_Dakota/Center</id>
+ <id alts="America/Shiprock,Navajo,US/Mountain">America/Denver</id>
+ <id alts="US/Arizona">America/Phoenix</id>
+ <id notafter="129114000000" repl="America/Denver">America/Boise</id>
+ <id alts="US/Pacific">America/Los_Angeles</id>
+ <id alts="US/Alaska">America/Anchorage</id>
+ <id notafter="436359600000" repl="America/Anchorage">America/Juneau</id>
+ <id notafter="341402400000" repl="America/Juneau">America/Sitka</id>
+ <id notafter="436363200000" repl="America/Anchorage">America/Nome</id>
+ <id notafter="1547978400000" repl="America/Anchorage">America/Metlakatla</id>
+ <id notafter="436356000000" repl="America/Juneau">America/Yakutat</id>
+ <id alts="Pacific/Johnston,US/Hawaii">Pacific/Honolulu</id>
+ <id alts="America/Atka,US/Aleutian">America/Adak</id>
</country>
<country code="uy" default="America/Montevideo" everutc="n">
<id>America/Montevideo</id>
</country>
<country code="uz" default="Asia/Tashkent" everutc="n">
<id>Asia/Tashkent</id>
- <id notafter="670366800000">Asia/Samarkand</id>
+ <id notafter="670366800000" repl="Asia/Tashkent">Asia/Samarkand</id>
</country>
<country code="va" default="Europe/Vatican" everutc="n">
<id>Europe/Vatican</id>
@@ -897,7 +897,7 @@
<id>America/St_Thomas</id>
</country>
<country code="vn" default="Asia/Ho_Chi_Minh" everutc="n">
- <id>Asia/Ho_Chi_Minh</id>
+ <id alts="Asia/Saigon">Asia/Ho_Chi_Minh</id>
</country>
<country code="vu" default="Pacific/Efate" everutc="n">
<id>Pacific/Efate</id>
diff --git a/output_data/distro/distro.zip b/output_data/distro/distro.zip
index 2bd2144..f6ec8fb 100644
--- a/output_data/distro/distro.zip
+++ b/output_data/distro/distro.zip
Binary files differ
diff --git a/output_data/iana/Android.bp b/output_data/iana/Android.bp
index 650549a..2dd51b9 100644
--- a/output_data/iana/Android.bp
+++ b/output_data/iana/Android.bp
@@ -12,16 +12,25 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+// Build rules related to the tzdata file.
+// tzdata is a binary file used by bionic functions like mktime / localtime and
+// Android's implementation of java.util.TimeZone, ZoneInfoDb. Both these users
+// look for the file in multiple locations. Also see the com.android.tzdata
+// APEX definition.
+
+// The on-device version of the tzdata file in /system/usr/share/zoneinfo/
+// This is the ultimate fallback for bionic for situations where the
+// tzdata APEX is not mounted. For example, bionic in initd. It is also
+// referenced by old copies of bionic that may be statically linked
+// via the NDK. It is no longer used by ZoneInfoDb.
prebuilt_usr_share {
name: "tzdata",
src: "tzdata",
sub_dir: "zoneinfo",
}
-// A host version of the tzdata module for use by
-// hostdex rules.
-
-// Simulate /system
+// The host version of the tzdata file equivalent to the definition above.
+// Used by bionic on host; bionic is being increasingly used on host for tooling.
prebuilt_usr_share_host {
name: "tzdata_host",
src: "tzdata",
diff --git a/output_data/iana/tzdata b/output_data/iana/tzdata
index cb45b2f..075fb62 100644
--- a/output_data/iana/tzdata
+++ b/output_data/iana/tzdata
Binary files differ
diff --git a/output_data/version/tz_version b/output_data/version/tz_version
index d503456..33807c5 100644
--- a/output_data/version/tz_version
+++ b/output_data/version/tz_version
@@ -1 +1 @@
-004.001|2020d|001
\ No newline at end of file
+005.001|2020d|001
\ No newline at end of file
diff --git a/testing/data/test1/output_data/android/tzlookup.xml b/testing/data/test1/output_data/android/tzlookup.xml
index 951c87b..328ff79 100644
--- a/testing/data/test1/output_data/android/tzlookup.xml
+++ b/testing/data/test1/output_data/android/tzlookup.xml
@@ -41,38 +41,38 @@
<id>Antarctica/Palmer</id>
</country>
<country code="ar" default="America/Argentina/Buenos_Aires" defaultBoost="y" everutc="n">
- <id>America/Argentina/Buenos_Aires</id>
- <id notafter="687931200000">America/Argentina/Cordoba</id>
- <id notafter="1096171200000">America/Argentina/Salta</id>
- <id notafter="687931200000">America/Argentina/Jujuy</id>
- <id notafter="1087099200000">America/Argentina/Tucuman</id>
- <id notafter="1087704000000">America/Argentina/Catamarca</id>
- <id notafter="687931200000">America/Argentina/La_Rioja</id>
- <id notafter="1090728000000">America/Argentina/San_Juan</id>
- <id notafter="1237082400000">America/Argentina/Mendoza</id>
+ <id alts="America/Buenos_Aires">America/Argentina/Buenos_Aires</id>
+ <id notafter="687931200000" repl="America/Argentina/Buenos_Aires" alts="America/Cordoba,America/Rosario">America/Argentina/Cordoba</id>
+ <id notafter="1237082400000" repl="America/Argentina/Buenos_Aires" alts="America/Mendoza">America/Argentina/Mendoza</id>
+ <id notafter="1087099200000" repl="America/Argentina/Buenos_Aires">America/Argentina/Tucuman</id>
+ <id notafter="1096171200000" repl="America/Argentina/Mendoza">America/Argentina/Salta</id>
+ <id notafter="1090728000000" repl="America/Argentina/Salta">America/Argentina/San_Juan</id>
+ <id notafter="687931200000" repl="America/Argentina/Salta" alts="America/Jujuy">America/Argentina/Jujuy</id>
+ <id notafter="1087704000000" repl="America/Argentina/Salta" alts="America/Argentina/ComodRivadavia,America/Catamarca">America/Argentina/Catamarca</id>
+ <id notafter="687931200000" repl="America/Argentina/Catamarca">America/Argentina/La_Rioja</id>
+ <id notafter="673588800000" repl="America/Argentina/La_Rioja">America/Argentina/Rio_Gallegos</id>
+ <id notafter="1087704000000" repl="America/Argentina/Salta">America/Argentina/Ushuaia</id>
<id>America/Argentina/San_Luis</id>
- <id notafter="673588800000">America/Argentina/Rio_Gallegos</id>
- <id notafter="1087704000000">America/Argentina/Ushuaia</id>
</country>
<country code="as" default="Pacific/Pago_Pago" everutc="n">
- <id>Pacific/Pago_Pago</id>
+ <id alts="Pacific/Samoa,US/Samoa">Pacific/Pago_Pago</id>
</country>
<country code="at" default="Europe/Vienna" everutc="n">
<id>Europe/Vienna</id>
</country>
<country code="au" default="Australia/Sydney" everutc="n">
- <id>Australia/Sydney</id>
- <id notafter="796147200000">Australia/Melbourne</id>
- <id>Australia/Brisbane</id>
- <id notafter="1193500800000">Australia/Hobart</id>
- <id notafter="37728000000">Australia/Currie</id>
- <id notafter="762883200000">Australia/Lindeman</id>
- <id notafter="1286035200000">Antarctica/Macquarie</id>
- <id>Australia/Lord_Howe</id>
- <id>Australia/Adelaide</id>
- <id notafter="796149000000">Australia/Broken_Hill</id>
- <id>Australia/Darwin</id>
- <id>Australia/Perth</id>
+ <id alts="Australia/ACT,Australia/Canberra,Australia/NSW">Australia/Sydney</id>
+ <id notafter="796147200000" repl="Australia/Sydney" alts="Australia/Victoria">Australia/Melbourne</id>
+ <id notafter="1193500800000" repl="Australia/Sydney" alts="Australia/Tasmania">Australia/Hobart</id>
+ <id notafter="37728000000" repl="Australia/Hobart">Australia/Currie</id>
+ <id alts="Australia/Queensland">Australia/Brisbane</id>
+ <id notafter="762883200000" repl="Australia/Brisbane">Australia/Lindeman</id>
+ <id notafter="1286035200000" repl="Australia/Sydney">Antarctica/Macquarie</id>
+ <id alts="Australia/LHI">Australia/Lord_Howe</id>
+ <id alts="Australia/South">Australia/Adelaide</id>
+ <id notafter="796149000000" repl="Australia/Adelaide" alts="Australia/Yancowinna">Australia/Broken_Hill</id>
+ <id alts="Australia/North">Australia/Darwin</id>
+ <id alts="Australia/West">Australia/Perth</id>
<id>Australia/Eucla</id>
</country>
<country code="aw" default="America/Aruba" everutc="n">
@@ -91,7 +91,7 @@
<id>America/Barbados</id>
</country>
<country code="bd" default="Asia/Dhaka" everutc="n">
- <id>Asia/Dhaka</id>
+ <id alts="Asia/Dacca">Asia/Dhaka</id>
</country>
<country code="be" default="Europe/Brussels" everutc="n">
<id>Europe/Brussels</id>
@@ -127,28 +127,28 @@
<id>America/Kralendijk</id>
</country>
<country code="br" default="America/Noronha" everutc="n">
- <id>America/Noronha</id>
- <id>America/Sao_Paulo</id>
- <id notafter="1013911200000">America/Belem</id>
- <id notafter="972180000000">America/Fortaleza</id>
- <id notafter="1330221600000">America/Recife</id>
- <id notafter="1361066400000">America/Araguaina</id>
- <id notafter="824004000000">America/Maceio</id>
- <id notafter="1550368800000">America/Bahia</id>
- <id notafter="1214280000000">America/Santarem</id>
- <id>America/Manaus</id>
- <id notafter="1076814000000">America/Campo_Grande</id>
- <id notafter="1550372400000">America/Cuiaba</id>
- <id notafter="761713200000">America/Porto_Velho</id>
- <id notafter="971578800000">America/Boa_Vista</id>
- <id notafter="761716800000">America/Eirunepe</id>
- <id>America/Rio_Branco</id>
+ <id alts="Brazil/DeNoronha">America/Noronha</id>
+ <id alts="Brazil/East">America/Sao_Paulo</id>
+ <id notafter="1550368800000" repl="America/Sao_Paulo">America/Bahia</id>
+ <id notafter="1214280000000" repl="America/Recife">America/Santarem</id>
+ <id notafter="1330221600000" repl="America/Bahia">America/Recife</id>
+ <id notafter="972180000000" repl="America/Recife">America/Fortaleza</id>
+ <id notafter="1013911200000" repl="America/Recife">America/Belem</id>
+ <id notafter="824004000000" repl="America/Fortaleza">America/Maceio</id>
+ <id notafter="1361066400000" repl="America/Bahia">America/Araguaina</id>
+ <id alts="Brazil/West">America/Manaus</id>
+ <id notafter="1550372400000" repl="America/Manaus">America/Cuiaba</id>
+ <id notafter="1076814000000" repl="America/Cuiaba">America/Campo_Grande</id>
+ <id notafter="761713200000" repl="America/Manaus">America/Porto_Velho</id>
+ <id notafter="971578800000" repl="America/Manaus">America/Boa_Vista</id>
+ <id alts="America/Porto_Acre,Brazil/Acre">America/Rio_Branco</id>
+ <id notafter="761716800000" repl="America/Rio_Branco">America/Eirunepe</id>
</country>
<country code="bs" default="America/Nassau" everutc="n">
<id>America/Nassau</id>
</country>
<country code="bt" default="Asia/Thimphu" everutc="n">
- <id>Asia/Thimphu</id>
+ <id alts="Asia/Thimbu">Asia/Thimphu</id>
</country>
<country code="bw" default="Africa/Gaborone" everutc="n">
<id>Africa/Gaborone</id>
@@ -160,34 +160,34 @@
<id>America/Belize</id>
</country>
<country code="ca" default="America/Toronto" everutc="n">
- <id>America/Toronto</id>
- <id>America/Vancouver</id>
- <id>America/Edmonton</id>
- <id>America/Winnipeg</id>
- <id>America/Halifax</id>
- <id>America/St_Johns</id>
- <id notafter="57733200000">America/Glace_Bay</id>
- <id notafter="1162098000000">America/Moncton</id>
- <id notafter="1299996000000">America/Goose_Bay</id>
+ <id alts="America/Montreal,Canada/Eastern">America/Toronto</id>
+ <id alts="Canada/Pacific">America/Vancouver</id>
+ <id alts="Canada/Mountain">America/Edmonton</id>
+ <id alts="Canada/Central">America/Winnipeg</id>
+ <id alts="Canada/Atlantic">America/Halifax</id>
+ <id alts="Canada/Newfoundland">America/St_Johns</id>
+ <id notafter="1162098000000" repl="America/Halifax">America/Moncton</id>
+ <id notafter="57733200000" repl="America/Halifax">America/Glace_Bay</id>
+ <id notafter="1299996000000" repl="America/Halifax">America/Goose_Bay</id>
<id>America/Blanc-Sablon</id>
- <id notafter="89186400000">America/Nipigon</id>
- <id notafter="120636000000">America/Thunder_Bay</id>
- <id notafter="972802800000">America/Iqaluit</id>
- <id notafter="796806000000">America/Pangnirtung</id>
- <id>America/Atikokan</id>
- <id>America/Regina</id>
- <id notafter="1130659200000">America/Rankin_Inlet</id>
- <id notafter="986112000000">America/Rainy_River</id>
- <id notafter="73472400000">America/Swift_Current</id>
- <id notafter="1173600000000">America/Resolute</id>
- <id notafter="986115600000">America/Cambridge_Bay</id>
- <id notafter="309945600000">America/Yellowknife</id>
- <id notafter="294228000000">America/Inuvik</id>
- <id>America/Whitehorse</id>
- <id notafter="1604214000000">America/Dawson_Creek</id>
- <id notafter="84013200000">America/Creston</id>
- <id notafter="1425808800000">America/Fort_Nelson</id>
- <id notafter="120646800000">America/Dawson</id>
+ <id notafter="120636000000" repl="America/Toronto">America/Thunder_Bay</id>
+ <id notafter="972802800000" repl="America/Toronto">America/Iqaluit</id>
+ <id notafter="89186400000" repl="America/Thunder_Bay">America/Nipigon</id>
+ <id notafter="796806000000" repl="America/Iqaluit">America/Pangnirtung</id>
+ <id alts="America/Coral_Harbour">America/Atikokan</id>
+ <id alts="Canada/Saskatchewan">America/Regina</id>
+ <id notafter="73472400000" repl="America/Regina">America/Swift_Current</id>
+ <id notafter="1130659200000" repl="America/Winnipeg">America/Rankin_Inlet</id>
+ <id notafter="986112000000" repl="America/Rankin_Inlet">America/Rainy_River</id>
+ <id notafter="1173600000000" repl="America/Winnipeg">America/Resolute</id>
+ <id notafter="309945600000" repl="America/Edmonton">America/Yellowknife</id>
+ <id notafter="1604214000000" repl="America/Whitehorse">America/Dawson_Creek</id>
+ <id notafter="84013200000" repl="America/Dawson_Creek">America/Creston</id>
+ <id notafter="1425808800000" repl="America/Dawson_Creek">America/Fort_Nelson</id>
+ <id notafter="294228000000" repl="America/Yellowknife">America/Inuvik</id>
+ <id notafter="986115600000" repl="America/Edmonton">America/Cambridge_Bay</id>
+ <id notafter="120646800000" repl="America/Whitehorse">America/Dawson</id>
+ <id alts="Canada/Yukon">America/Whitehorse</id>
</country>
<country code="cc" default="Indian/Cocos" everutc="n">
<id>Indian/Cocos</id>
@@ -206,22 +206,22 @@
<id>Europe/Zurich</id>
</country>
<country code="ci" default="Africa/Abidjan" everutc="y">
- <id>Africa/Abidjan</id>
+ <id alts="Africa/Timbuktu">Africa/Abidjan</id>
</country>
<country code="ck" default="Pacific/Rarotonga" everutc="n">
<id>Pacific/Rarotonga</id>
</country>
<country code="cl" default="America/Santiago" everutc="n">
<id>America/Punta_Arenas</id>
- <id>America/Santiago</id>
- <id>Pacific/Easter</id>
+ <id alts="Chile/Continental">America/Santiago</id>
+ <id alts="Chile/EasterIsland">Pacific/Easter</id>
</country>
<country code="cm" default="Africa/Douala" everutc="n">
<id>Africa/Douala</id>
</country>
<country code="cn" default="Asia/Shanghai" defaultBoost="y" everutc="n">
- <id>Asia/Shanghai</id>
- <id>Asia/Urumqi</id>
+ <id alts="Asia/Chongqing,Asia/Chungking,Asia/Harbin,PRC">Asia/Shanghai</id>
+ <id alts="Asia/Kashgar">Asia/Urumqi</id>
</country>
<country code="co" default="America/Bogota" everutc="n">
<id>America/Bogota</id>
@@ -230,7 +230,7 @@
<id>America/Costa_Rica</id>
</country>
<country code="cu" default="America/Havana" everutc="n">
- <id>America/Havana</id>
+ <id alts="Cuba">America/Havana</id>
</country>
<country code="cv" default="Atlantic/Cape_Verde" everutc="n">
<id>Atlantic/Cape_Verde</id>
@@ -250,7 +250,7 @@
</country>
<country code="de" default="Europe/Berlin" everutc="n">
<id>Europe/Berlin</id>
- <id notafter="338950800000">Europe/Busingen</id>
+ <id notafter="338950800000" repl="Europe/Berlin">Europe/Busingen</id>
</country>
<country code="dj" default="Africa/Djibouti" everutc="n">
<id>Africa/Djibouti</id>
@@ -275,7 +275,7 @@
<id>Europe/Tallinn</id>
</country>
<country code="eg" default="Africa/Cairo" everutc="n">
- <id>Africa/Cairo</id>
+ <id alts="Egypt">Africa/Cairo</id>
</country>
<country code="eh" default="Africa/El_Aaiun" everutc="y">
<id>Africa/El_Aaiun</id>
@@ -285,7 +285,7 @@
</country>
<country code="es" default="Europe/Madrid" everutc="y">
<id>Europe/Madrid</id>
- <id notafter="496803600000">Africa/Ceuta</id>
+ <id notafter="496803600000" repl="Europe/Madrid">Africa/Ceuta</id>
<id>Atlantic/Canary</id>
</country>
<country code="et" default="Africa/Addis_Ababa" everutc="n">
@@ -301,12 +301,12 @@
<id>Atlantic/Stanley</id>
</country>
<country code="fm" default="Pacific/Pohnpei" everutc="n">
- <id>Pacific/Pohnpei</id>
+ <id alts="Pacific/Ponape">Pacific/Pohnpei</id>
<id>Pacific/Kosrae</id>
- <id>Pacific/Chuuk</id>
+ <id alts="Pacific/Truk,Pacific/Yap">Pacific/Chuuk</id>
</country>
<country code="fo" default="Atlantic/Faroe" everutc="y">
- <id>Atlantic/Faroe</id>
+ <id alts="Atlantic/Faeroe">Atlantic/Faroe</id>
</country>
<country code="fr" default="Europe/Paris" everutc="n">
<id>Europe/Paris</id>
@@ -315,7 +315,7 @@
<id>Africa/Libreville</id>
</country>
<country code="gb" default="Europe/London" everutc="y">
- <id>Europe/London</id>
+ <id alts="Europe/Belfast,GB,GB-Eire">Europe/London</id>
</country>
<country code="gd" default="America/Grenada" everutc="n">
<id>America/Grenada</id>
@@ -338,7 +338,7 @@
<country code="gl" default="America/Nuuk" everutc="y">
<id>America/Danmarkshavn</id>
<id>America/Scoresbysund</id>
- <id>America/Nuuk</id>
+ <id alts="America/Godthab">America/Nuuk</id>
<id>America/Thule</id>
</country>
<country code="gm" default="Africa/Banjul" everutc="y">
@@ -372,7 +372,7 @@
<id>America/Guyana</id>
</country>
<country code="hk" default="Asia/Hong_Kong" everutc="n">
- <id>Asia/Hong_Kong</id>
+ <id alts="Hongkong">Asia/Hong_Kong</id>
</country>
<country code="hn" default="America/Tegucigalpa" everutc="n">
<id>America/Tegucigalpa</id>
@@ -388,21 +388,21 @@
</country>
<country code="id" default="Asia/Jakarta" everutc="n">
<id>Asia/Jayapura</id>
- <id>Asia/Makassar</id>
+ <id alts="Asia/Ujung_Pandang">Asia/Makassar</id>
<id>Asia/Jakarta</id>
- <id notafter="567964800000">Asia/Pontianak</id>
+ <id notafter="567964800000" repl="Asia/Jakarta">Asia/Pontianak</id>
</country>
<country code="ie" default="Europe/Dublin" everutc="y">
- <id>Europe/Dublin</id>
+ <id alts="Eire">Europe/Dublin</id>
</country>
<country code="il" default="Asia/Jerusalem" everutc="n">
- <id>Asia/Jerusalem</id>
+ <id alts="Asia/Tel_Aviv,Israel">Asia/Jerusalem</id>
</country>
<country code="im" default="Europe/Isle_of_Man" everutc="y">
<id>Europe/Isle_of_Man</id>
</country>
<country code="in" default="Asia/Kolkata" everutc="n">
- <id>Asia/Kolkata</id>
+ <id alts="Asia/Calcutta">Asia/Kolkata</id>
</country>
<country code="io" default="Indian/Chagos" everutc="n">
<id>Indian/Chagos</id>
@@ -411,10 +411,10 @@
<id>Asia/Baghdad</id>
</country>
<country code="ir" default="Asia/Tehran" everutc="n">
- <id>Asia/Tehran</id>
+ <id alts="Iran">Asia/Tehran</id>
</country>
<country code="is" default="Atlantic/Reykjavik" everutc="y">
- <id>Atlantic/Reykjavik</id>
+ <id alts="Iceland">Atlantic/Reykjavik</id>
</country>
<country code="it" default="Europe/Rome" everutc="n">
<id>Europe/Rome</id>
@@ -423,16 +423,16 @@
<id>Europe/Jersey</id>
</country>
<country code="jm" default="America/Jamaica" everutc="n">
- <id>America/Jamaica</id>
+ <id alts="Jamaica">America/Jamaica</id>
</country>
<country code="jo" default="Asia/Amman" everutc="n">
<id>Asia/Amman</id>
</country>
<country code="jp" default="Asia/Tokyo" everutc="n">
- <id>Asia/Tokyo</id>
+ <id alts="Japan">Asia/Tokyo</id>
</country>
<country code="ke" default="Africa/Nairobi" everutc="n">
- <id>Africa/Nairobi</id>
+ <id alts="Africa/Asmera">Africa/Nairobi</id>
</country>
<country code="kg" default="Asia/Bishkek" everutc="n">
<id>Asia/Bishkek</id>
@@ -455,7 +455,7 @@
<id>Asia/Pyongyang</id>
</country>
<country code="kr" default="Asia/Seoul" everutc="n">
- <id>Asia/Seoul</id>
+ <id alts="ROK">Asia/Seoul</id>
</country>
<country code="kw" default="Asia/Kuwait" everutc="n">
<id>Asia/Kuwait</id>
@@ -465,12 +465,12 @@
</country>
<country code="kz" default="Asia/Almaty" everutc="n">
<id>Asia/Almaty</id>
- <id notafter="1099170000000">Asia/Qostanay</id>
- <id notafter="1099173600000">Asia/Aqtau</id>
+ <id notafter="1099170000000" repl="Asia/Almaty">Asia/Qostanay</id>
<id>Asia/Oral</id>
- <id notafter="1545328800000">Asia/Qyzylorda</id>
- <id notafter="1545328800000">Asia/Aqtobe</id>
- <id notafter="922572000000">Asia/Atyrau</id>
+ <id notafter="1099173600000" repl="Asia/Oral">Asia/Aqtau</id>
+ <id notafter="1545328800000" repl="Asia/Oral">Asia/Qyzylorda</id>
+ <id notafter="1545328800000" repl="Asia/Oral">Asia/Aqtobe</id>
+ <id notafter="922572000000" repl="Asia/Oral">Asia/Atyrau</id>
</country>
<country code="la" default="Asia/Vientiane" everutc="n">
<id>Asia/Vientiane</id>
@@ -503,7 +503,7 @@
<id>Europe/Riga</id>
</country>
<country code="ly" default="Africa/Tripoli" everutc="n">
- <id>Africa/Tripoli</id>
+ <id alts="Libya">Africa/Tripoli</id>
</country>
<country code="ma" default="Africa/Casablanca" everutc="y">
<id>Africa/Casablanca</id>
@@ -512,7 +512,7 @@
<id>Europe/Monaco</id>
</country>
<country code="md" default="Europe/Chisinau" everutc="n">
- <id>Europe/Chisinau</id>
+ <id alts="Europe/Tiraspol">Europe/Chisinau</id>
</country>
<country code="me" default="Europe/Podgorica" everutc="n">
<id>Europe/Podgorica</id>
@@ -525,7 +525,7 @@
</country>
<country code="mh" default="Pacific/Majuro" everutc="n">
<id>Pacific/Majuro</id>
- <id notafter="745934400000">Pacific/Kwajalein</id>
+ <id notafter="745934400000" repl="Pacific/Majuro" alts="Kwajalein">Pacific/Kwajalein</id>
</country>
<country code="mk" default="Europe/Skopje" everutc="n">
<id>Europe/Skopje</id>
@@ -534,15 +534,15 @@
<id>Africa/Bamako</id>
</country>
<country code="mm" default="Asia/Yangon" everutc="n">
- <id>Asia/Yangon</id>
+ <id alts="Asia/Rangoon">Asia/Yangon</id>
</country>
<country code="mn" default="Asia/Ulaanbaatar" everutc="n">
<id>Asia/Choibalsan</id>
- <id>Asia/Ulaanbaatar</id>
+ <id alts="Asia/Ulan_Bator">Asia/Ulaanbaatar</id>
<id>Asia/Hovd</id>
</country>
<country code="mo" default="Asia/Macau" everutc="n">
- <id>Asia/Macau</id>
+ <id alts="Asia/Macao">Asia/Macau</id>
</country>
<country code="mp" default="Pacific/Saipan" everutc="n">
<id>Pacific/Saipan</id>
@@ -569,21 +569,21 @@
<id>Africa/Blantyre</id>
</country>
<country code="mx" default="America/Mexico_City" everutc="n">
- <id>America/Mexico_City</id>
- <id notafter="407653200000">America/Merida</id>
- <id notafter="594198000000">America/Monterrey</id>
+ <id alts="Mexico/General">America/Mexico_City</id>
+ <id notafter="407653200000" repl="America/Mexico_City">America/Merida</id>
+ <id notafter="594198000000" repl="America/Mexico_City">America/Monterrey</id>
+ <id notafter="1270371600000" repl="America/Mexico_City">America/Bahia_Banderas</id>
<id>America/Matamoros</id>
- <id notafter="1270371600000">America/Bahia_Banderas</id>
<id>America/Cancun</id>
<id>America/Chihuahua</id>
+ <id notafter="891766800000" repl="America/Chihuahua" alts="Mexico/BajaSur">America/Mazatlan</id>
<id>America/Hermosillo</id>
- <id notafter="891766800000">America/Mazatlan</id>
<id>America/Ojinaga</id>
- <id>America/Tijuana</id>
+ <id alts="America/Ensenada,America/Santa_Isabel,Mexico/BajaNorte">America/Tijuana</id>
</country>
<country code="my" default="Asia/Kuala_Lumpur" everutc="n">
<id>Asia/Kuala_Lumpur</id>
- <id notafter="378664200000">Asia/Kuching</id>
+ <id notafter="378664200000" repl="Asia/Kuala_Lumpur">Asia/Kuching</id>
</country>
<country code="mz" default="Africa/Maputo" everutc="n">
<id>Africa/Maputo</id>
@@ -610,10 +610,10 @@
<id>Europe/Amsterdam</id>
</country>
<country code="no" default="Europe/Oslo" everutc="n">
- <id>Europe/Oslo</id>
+ <id alts="Atlantic/Jan_Mayen">Europe/Oslo</id>
</country>
<country code="np" default="Asia/Kathmandu" everutc="n">
- <id>Asia/Kathmandu</id>
+ <id alts="Asia/Katmandu">Asia/Kathmandu</id>
</country>
<country code="nr" default="Pacific/Nauru" everutc="n">
<id>Pacific/Nauru</id>
@@ -622,8 +622,8 @@
<id>Pacific/Niue</id>
</country>
<country code="nz" default="Pacific/Auckland" defaultBoost="y" everutc="n">
- <id>Pacific/Auckland</id>
- <id>Pacific/Chatham</id>
+ <id alts="Antarctica/South_Pole,NZ">Pacific/Auckland</id>
+ <id alts="NZ-CHAT">Pacific/Chatham</id>
</country>
<country code="om" default="Asia/Muscat" everutc="n">
<id>Asia/Muscat</id>
@@ -650,7 +650,7 @@
<id>Asia/Karachi</id>
</country>
<country code="pl" default="Europe/Warsaw" everutc="n">
- <id>Europe/Warsaw</id>
+ <id alts="Poland">Europe/Warsaw</id>
</country>
<country code="pm" default="America/Miquelon" everutc="n">
<id>America/Miquelon</id>
@@ -662,12 +662,12 @@
<id>America/Puerto_Rico</id>
</country>
<country code="ps" default="Asia/Gaza" everutc="n">
- <id notafter="1317330000000">Asia/Gaza</id>
<id>Asia/Hebron</id>
+ <id notafter="1317330000000" repl="Asia/Hebron">Asia/Gaza</id>
</country>
<country code="pt" default="Europe/Lisbon" everutc="y">
- <id>Europe/Lisbon</id>
- <id notafter="828234000000">Atlantic/Madeira</id>
+ <id alts="Portugal">Europe/Lisbon</id>
+ <id notafter="828234000000" repl="Europe/Lisbon">Atlantic/Madeira</id>
<id>Atlantic/Azores</id>
</country>
<country code="pw" default="Pacific/Palau" everutc="n">
@@ -695,24 +695,24 @@
<id>Asia/Sakhalin</id>
<id>Asia/Srednekolymsk</id>
<id>Asia/Vladivostok</id>
- <id notafter="1315828800000">Asia/Ust-Nera</id>
- <id notafter="1459015200000">Asia/Yakutsk</id>
+ <id notafter="1315828800000" repl="Asia/Vladivostok">Asia/Ust-Nera</id>
<id>Asia/Chita</id>
- <id notafter="1315832400000">Asia/Khandyga</id>
+ <id notafter="1459015200000" repl="Asia/Chita">Asia/Yakutsk</id>
+ <id notafter="1315832400000" repl="Asia/Yakutsk">Asia/Khandyga</id>
<id>Asia/Irkutsk</id>
<id>Asia/Krasnoyarsk</id>
+ <id notafter="1459022400000" repl="Asia/Krasnoyarsk">Asia/Novokuznetsk</id>
<id>Asia/Novosibirsk</id>
<id>Asia/Barnaul</id>
- <id notafter="1459022400000">Asia/Novokuznetsk</id>
- <id notafter="1464465600000">Asia/Tomsk</id>
+ <id notafter="1464465600000" repl="Asia/Barnaul">Asia/Tomsk</id>
<id>Asia/Omsk</id>
<id>Asia/Yekaterinburg</id>
<id>Europe/Samara</id>
- <id notafter="701823600000">Europe/Astrakhan</id>
- <id notafter="1480806000000">Europe/Ulyanovsk</id>
<id>Europe/Saratov</id>
+ <id notafter="1480806000000" repl="Europe/Saratov">Europe/Ulyanovsk</id>
+ <id notafter="701823600000" repl="Europe/Ulyanovsk">Europe/Astrakhan</id>
<id>Europe/Volgograd</id>
- <id>Europe/Moscow</id>
+ <id alts="W-SU">Europe/Moscow</id>
<id>Europe/Kirov</id>
<id>Europe/Kaliningrad</id>
</country>
@@ -735,7 +735,7 @@
<id>Europe/Stockholm</id>
</country>
<country code="sg" default="Asia/Singapore" everutc="n">
- <id>Asia/Singapore</id>
+ <id alts="Singapore">Asia/Singapore</id>
</country>
<country code="sh" default="Atlantic/St_Helena" everutc="y">
<id>Atlantic/St_Helena</id>
@@ -807,7 +807,7 @@
<id>Asia/Dili</id>
</country>
<country code="tm" default="Asia/Ashgabat" everutc="n">
- <id>Asia/Ashgabat</id>
+ <id alts="Asia/Ashkhabad">Asia/Ashgabat</id>
</country>
<country code="tn" default="Africa/Tunis" everutc="n">
<id>Africa/Tunis</id>
@@ -816,24 +816,24 @@
<id>Pacific/Tongatapu</id>
</country>
<country code="tr" default="Europe/Istanbul" everutc="n">
- <id>Europe/Istanbul</id>
+ <id alts="Turkey">Europe/Istanbul</id>
</country>
<country code="tt" default="America/Port_of_Spain" everutc="n">
- <id>America/Port_of_Spain</id>
+ <id alts="America/Virgin">America/Port_of_Spain</id>
</country>
<country code="tv" default="Pacific/Funafuti" everutc="n">
<id>Pacific/Funafuti</id>
</country>
<country code="tw" default="Asia/Taipei" everutc="n">
- <id>Asia/Taipei</id>
+ <id alts="ROC">Asia/Taipei</id>
</country>
<country code="tz" default="Africa/Dar_es_Salaam" everutc="n">
<id>Africa/Dar_es_Salaam</id>
</country>
<country code="ua" default="Europe/Kiev" everutc="n">
<id>Europe/Kiev</id>
- <id notafter="686091600000">Europe/Uzhgorod</id>
- <id notafter="686102400000">Europe/Zaporozhye</id>
+ <id notafter="686102400000" repl="Europe/Kiev">Europe/Zaporozhye</id>
+ <id notafter="686091600000" repl="Europe/Zaporozhye">Europe/Uzhgorod</id>
<id picker="n">Europe/Simferopol</id>
</country>
<country code="ug" default="Africa/Kampala" everutc="n">
@@ -844,42 +844,42 @@
<id>Pacific/Midway</id>
</country>
<country code="us" default="America/New_York" everutc="n">
- <id>America/New_York</id>
- <id notafter="167814000000">America/Detroit</id>
- <id notafter="152089200000">America/Kentucky/Louisville</id>
- <id notafter="972802800000">America/Kentucky/Monticello</id>
- <id notafter="1130652000000">America/Indiana/Indianapolis</id>
- <id notafter="1194159600000">America/Indiana/Vincennes</id>
- <id notafter="1173600000000">America/Indiana/Winamac</id>
- <id notafter="183535200000">America/Indiana/Marengo</id>
- <id notafter="247042800000">America/Indiana/Petersburg</id>
- <id notafter="89186400000">America/Indiana/Vevay</id>
- <id>America/Chicago</id>
- <id notafter="688546800000">America/Indiana/Knox</id>
- <id notafter="104918400000">America/Menominee</id>
- <id notafter="720000000000">America/North_Dakota/Center</id>
- <id notafter="1067155200000">America/North_Dakota/New_Salem</id>
- <id notafter="1143964800000">America/Indiana/Tell_City</id>
- <id notafter="1289116800000">America/North_Dakota/Beulah</id>
- <id>America/Denver</id>
- <id notafter="129114000000">America/Boise</id>
- <id>America/Phoenix</id>
- <id>America/Los_Angeles</id>
- <id>America/Anchorage</id>
- <id notafter="436359600000">America/Juneau</id>
- <id notafter="436356000000">America/Yakutat</id>
- <id notafter="436363200000">America/Nome</id>
- <id notafter="1547978400000">America/Metlakatla</id>
- <id notafter="341402400000">America/Sitka</id>
- <id>Pacific/Honolulu</id>
- <id>America/Adak</id>
+ <id alts="US/Eastern">America/New_York</id>
+ <id notafter="152089200000" repl="America/New_York" alts="America/Louisville">America/Kentucky/Louisville</id>
+ <id notafter="167814000000" repl="America/New_York" alts="US/Michigan">America/Detroit</id>
+ <id notafter="1130652000000" repl="America/New_York" alts="America/Fort_Wayne,America/Indianapolis,US/East-Indiana">America/Indiana/Indianapolis</id>
+ <id notafter="1194159600000" repl="America/New_York">America/Indiana/Vincennes</id>
+ <id notafter="972802800000" repl="America/New_York">America/Kentucky/Monticello</id>
+ <id notafter="247042800000" repl="America/Indiana/Vincennes">America/Indiana/Petersburg</id>
+ <id notafter="1173600000000" repl="America/New_York">America/Indiana/Winamac</id>
+ <id notafter="89186400000" repl="America/Indiana/Indianapolis">America/Indiana/Vevay</id>
+ <id notafter="183535200000" repl="America/Indiana/Indianapolis">America/Indiana/Marengo</id>
+ <id alts="US/Central">America/Chicago</id>
+ <id notafter="104918400000" repl="America/Chicago">America/Menominee</id>
+ <id notafter="1143964800000" repl="America/Chicago">America/Indiana/Tell_City</id>
+ <id notafter="688546800000" repl="America/Indiana/Tell_City" alts="America/Knox_IN,US/Indiana-Starke">America/Indiana/Knox</id>
+ <id notafter="1289116800000" repl="America/Chicago">America/North_Dakota/Beulah</id>
+ <id notafter="1067155200000" repl="America/Chicago">America/North_Dakota/New_Salem</id>
+ <id notafter="720000000000" repl="America/Chicago">America/North_Dakota/Center</id>
+ <id alts="America/Shiprock,Navajo,US/Mountain">America/Denver</id>
+ <id alts="US/Arizona">America/Phoenix</id>
+ <id notafter="129114000000" repl="America/Denver">America/Boise</id>
+ <id alts="US/Pacific">America/Los_Angeles</id>
+ <id alts="US/Alaska">America/Anchorage</id>
+ <id notafter="436359600000" repl="America/Anchorage">America/Juneau</id>
+ <id notafter="341402400000" repl="America/Juneau">America/Sitka</id>
+ <id notafter="436363200000" repl="America/Anchorage">America/Nome</id>
+ <id notafter="1547978400000" repl="America/Anchorage">America/Metlakatla</id>
+ <id notafter="436356000000" repl="America/Juneau">America/Yakutat</id>
+ <id alts="Pacific/Johnston,US/Hawaii">Pacific/Honolulu</id>
+ <id alts="America/Atka,US/Aleutian">America/Adak</id>
</country>
<country code="uy" default="America/Montevideo" everutc="n">
<id>America/Montevideo</id>
</country>
<country code="uz" default="Asia/Tashkent" everutc="n">
<id>Asia/Tashkent</id>
- <id notafter="670366800000">Asia/Samarkand</id>
+ <id notafter="670366800000" repl="Asia/Tashkent">Asia/Samarkand</id>
</country>
<country code="va" default="Europe/Vatican" everutc="n">
<id>Europe/Vatican</id>
@@ -897,7 +897,7 @@
<id>America/St_Thomas</id>
</country>
<country code="vn" default="Asia/Ho_Chi_Minh" everutc="n">
- <id>Asia/Ho_Chi_Minh</id>
+ <id alts="Asia/Saigon">Asia/Ho_Chi_Minh</id>
</country>
<country code="vu" default="Pacific/Efate" everutc="n">
<id>Pacific/Efate</id>
diff --git a/testing/data/test1/output_data/distro/distro.zip b/testing/data/test1/output_data/distro/distro.zip
index 0201aa2..a88c2c2 100644
--- a/testing/data/test1/output_data/distro/distro.zip
+++ b/testing/data/test1/output_data/distro/distro.zip
Binary files differ
diff --git a/testing/data/test1/output_data/iana/tzdata b/testing/data/test1/output_data/iana/tzdata
index 231ab4a..4d317d3 100644
--- a/testing/data/test1/output_data/iana/tzdata
+++ b/testing/data/test1/output_data/iana/tzdata
Binary files differ
diff --git a/testing/data/test1/output_data/version/tz_version b/testing/data/test1/output_data/version/tz_version
index e1e8f45..b843d8a 100644
--- a/testing/data/test1/output_data/version/tz_version
+++ b/testing/data/test1/output_data/version/tz_version
@@ -1 +1 @@
-004.001|2030a|001
\ No newline at end of file
+005.001|2030a|001
\ No newline at end of file
diff --git a/testing/data/test2/output_data/android/tzlookup.xml b/testing/data/test2/output_data/android/tzlookup.xml
index 841f35b..f997f3f 100644
--- a/testing/data/test2/output_data/android/tzlookup.xml
+++ b/testing/data/test2/output_data/android/tzlookup.xml
@@ -41,38 +41,38 @@
<id>Antarctica/Palmer</id>
</country>
<country code="ar" default="America/Argentina/Buenos_Aires" defaultBoost="y" everutc="n">
- <id>America/Argentina/Buenos_Aires</id>
- <id notafter="687931200000">America/Argentina/Cordoba</id>
- <id notafter="1096171200000">America/Argentina/Salta</id>
- <id notafter="687931200000">America/Argentina/Jujuy</id>
- <id notafter="1087099200000">America/Argentina/Tucuman</id>
- <id notafter="1087704000000">America/Argentina/Catamarca</id>
- <id notafter="687931200000">America/Argentina/La_Rioja</id>
- <id notafter="1090728000000">America/Argentina/San_Juan</id>
- <id notafter="1237082400000">America/Argentina/Mendoza</id>
+ <id alts="America/Buenos_Aires">America/Argentina/Buenos_Aires</id>
+ <id notafter="687931200000" repl="America/Argentina/Buenos_Aires" alts="America/Cordoba,America/Rosario">America/Argentina/Cordoba</id>
+ <id notafter="1237082400000" repl="America/Argentina/Buenos_Aires" alts="America/Mendoza">America/Argentina/Mendoza</id>
+ <id notafter="1087099200000" repl="America/Argentina/Buenos_Aires">America/Argentina/Tucuman</id>
+ <id notafter="1096171200000" repl="America/Argentina/Mendoza">America/Argentina/Salta</id>
+ <id notafter="1090728000000" repl="America/Argentina/Salta">America/Argentina/San_Juan</id>
+ <id notafter="687931200000" repl="America/Argentina/Salta" alts="America/Jujuy">America/Argentina/Jujuy</id>
+ <id notafter="1087704000000" repl="America/Argentina/Salta" alts="America/Argentina/ComodRivadavia,America/Catamarca">America/Argentina/Catamarca</id>
+ <id notafter="687931200000" repl="America/Argentina/Catamarca">America/Argentina/La_Rioja</id>
+ <id notafter="673588800000" repl="America/Argentina/La_Rioja">America/Argentina/Rio_Gallegos</id>
+ <id notafter="1087704000000" repl="America/Argentina/Salta">America/Argentina/Ushuaia</id>
<id>America/Argentina/San_Luis</id>
- <id notafter="673588800000">America/Argentina/Rio_Gallegos</id>
- <id notafter="1087704000000">America/Argentina/Ushuaia</id>
</country>
<country code="as" default="Pacific/Pago_Pago" everutc="n">
- <id>Pacific/Pago_Pago</id>
+ <id alts="Pacific/Samoa,US/Samoa">Pacific/Pago_Pago</id>
</country>
<country code="at" default="Europe/Vienna" everutc="n">
<id>Europe/Vienna</id>
</country>
<country code="au" default="Australia/Sydney" everutc="n">
- <id>Australia/Sydney</id>
- <id notafter="796147200000">Australia/Melbourne</id>
- <id>Australia/Brisbane</id>
- <id notafter="1193500800000">Australia/Hobart</id>
- <id notafter="37728000000">Australia/Currie</id>
- <id notafter="762883200000">Australia/Lindeman</id>
- <id notafter="1286035200000">Antarctica/Macquarie</id>
- <id>Australia/Lord_Howe</id>
- <id>Australia/Adelaide</id>
- <id notafter="796149000000">Australia/Broken_Hill</id>
- <id>Australia/Darwin</id>
- <id>Australia/Perth</id>
+ <id alts="Australia/ACT,Australia/Canberra,Australia/NSW">Australia/Sydney</id>
+ <id notafter="796147200000" repl="Australia/Sydney" alts="Australia/Victoria">Australia/Melbourne</id>
+ <id notafter="1193500800000" repl="Australia/Sydney" alts="Australia/Tasmania">Australia/Hobart</id>
+ <id notafter="37728000000" repl="Australia/Hobart">Australia/Currie</id>
+ <id alts="Australia/Queensland">Australia/Brisbane</id>
+ <id notafter="762883200000" repl="Australia/Brisbane">Australia/Lindeman</id>
+ <id notafter="1286035200000" repl="Australia/Sydney">Antarctica/Macquarie</id>
+ <id alts="Australia/LHI">Australia/Lord_Howe</id>
+ <id alts="Australia/South">Australia/Adelaide</id>
+ <id notafter="796149000000" repl="Australia/Adelaide" alts="Australia/Yancowinna">Australia/Broken_Hill</id>
+ <id alts="Australia/North">Australia/Darwin</id>
+ <id alts="Australia/West">Australia/Perth</id>
<id>Australia/Eucla</id>
</country>
<country code="aw" default="America/Aruba" everutc="n">
@@ -91,7 +91,7 @@
<id>America/Barbados</id>
</country>
<country code="bd" default="Asia/Dhaka" everutc="n">
- <id>Asia/Dhaka</id>
+ <id alts="Asia/Dacca">Asia/Dhaka</id>
</country>
<country code="be" default="Europe/Brussels" everutc="n">
<id>Europe/Brussels</id>
@@ -127,28 +127,28 @@
<id>America/Kralendijk</id>
</country>
<country code="br" default="America/Noronha" everutc="n">
- <id>America/Noronha</id>
- <id>America/Sao_Paulo</id>
- <id notafter="1013911200000">America/Belem</id>
- <id notafter="972180000000">America/Fortaleza</id>
- <id notafter="1330221600000">America/Recife</id>
- <id notafter="1361066400000">America/Araguaina</id>
- <id notafter="824004000000">America/Maceio</id>
- <id notafter="1550368800000">America/Bahia</id>
- <id notafter="1214280000000">America/Santarem</id>
- <id>America/Manaus</id>
- <id notafter="1076814000000">America/Campo_Grande</id>
- <id notafter="1550372400000">America/Cuiaba</id>
- <id notafter="761713200000">America/Porto_Velho</id>
- <id notafter="971578800000">America/Boa_Vista</id>
- <id notafter="761716800000">America/Eirunepe</id>
- <id>America/Rio_Branco</id>
+ <id alts="Brazil/DeNoronha">America/Noronha</id>
+ <id alts="Brazil/East">America/Sao_Paulo</id>
+ <id notafter="1550368800000" repl="America/Sao_Paulo">America/Bahia</id>
+ <id notafter="1214280000000" repl="America/Recife">America/Santarem</id>
+ <id notafter="1330221600000" repl="America/Bahia">America/Recife</id>
+ <id notafter="972180000000" repl="America/Recife">America/Fortaleza</id>
+ <id notafter="1013911200000" repl="America/Recife">America/Belem</id>
+ <id notafter="824004000000" repl="America/Fortaleza">America/Maceio</id>
+ <id notafter="1361066400000" repl="America/Bahia">America/Araguaina</id>
+ <id alts="Brazil/West">America/Manaus</id>
+ <id notafter="1550372400000" repl="America/Manaus">America/Cuiaba</id>
+ <id notafter="1076814000000" repl="America/Cuiaba">America/Campo_Grande</id>
+ <id notafter="761713200000" repl="America/Manaus">America/Porto_Velho</id>
+ <id notafter="971578800000" repl="America/Manaus">America/Boa_Vista</id>
+ <id alts="America/Porto_Acre,Brazil/Acre">America/Rio_Branco</id>
+ <id notafter="761716800000" repl="America/Rio_Branco">America/Eirunepe</id>
</country>
<country code="bs" default="America/Nassau" everutc="n">
<id>America/Nassau</id>
</country>
<country code="bt" default="Asia/Thimphu" everutc="n">
- <id>Asia/Thimphu</id>
+ <id alts="Asia/Thimbu">Asia/Thimphu</id>
</country>
<country code="bw" default="Africa/Gaborone" everutc="n">
<id>Africa/Gaborone</id>
@@ -160,34 +160,34 @@
<id>America/Belize</id>
</country>
<country code="ca" default="America/Toronto" everutc="n">
- <id>America/Toronto</id>
- <id>America/Vancouver</id>
- <id>America/Edmonton</id>
- <id>America/Winnipeg</id>
- <id>America/Halifax</id>
- <id>America/St_Johns</id>
- <id notafter="57733200000">America/Glace_Bay</id>
- <id notafter="1162098000000">America/Moncton</id>
- <id notafter="1299996000000">America/Goose_Bay</id>
+ <id alts="America/Montreal,Canada/Eastern">America/Toronto</id>
+ <id alts="Canada/Pacific">America/Vancouver</id>
+ <id alts="Canada/Mountain">America/Edmonton</id>
+ <id alts="Canada/Central">America/Winnipeg</id>
+ <id alts="Canada/Atlantic">America/Halifax</id>
+ <id alts="Canada/Newfoundland">America/St_Johns</id>
+ <id notafter="1162098000000" repl="America/Halifax">America/Moncton</id>
+ <id notafter="57733200000" repl="America/Halifax">America/Glace_Bay</id>
+ <id notafter="1299996000000" repl="America/Halifax">America/Goose_Bay</id>
<id>America/Blanc-Sablon</id>
- <id notafter="89186400000">America/Nipigon</id>
- <id notafter="120636000000">America/Thunder_Bay</id>
- <id notafter="972802800000">America/Iqaluit</id>
- <id notafter="796806000000">America/Pangnirtung</id>
- <id>America/Atikokan</id>
- <id>America/Regina</id>
- <id notafter="1130659200000">America/Rankin_Inlet</id>
- <id notafter="986112000000">America/Rainy_River</id>
- <id notafter="73472400000">America/Swift_Current</id>
- <id notafter="1173600000000">America/Resolute</id>
- <id notafter="986115600000">America/Cambridge_Bay</id>
- <id notafter="309945600000">America/Yellowknife</id>
- <id notafter="294228000000">America/Inuvik</id>
- <id>America/Whitehorse</id>
- <id notafter="1604214000000">America/Dawson_Creek</id>
- <id notafter="84013200000">America/Creston</id>
- <id notafter="1425808800000">America/Fort_Nelson</id>
- <id notafter="120646800000">America/Dawson</id>
+ <id notafter="120636000000" repl="America/Toronto">America/Thunder_Bay</id>
+ <id notafter="972802800000" repl="America/Toronto">America/Iqaluit</id>
+ <id notafter="89186400000" repl="America/Thunder_Bay">America/Nipigon</id>
+ <id notafter="796806000000" repl="America/Iqaluit">America/Pangnirtung</id>
+ <id alts="America/Coral_Harbour">America/Atikokan</id>
+ <id alts="Canada/Saskatchewan">America/Regina</id>
+ <id notafter="73472400000" repl="America/Regina">America/Swift_Current</id>
+ <id notafter="1130659200000" repl="America/Winnipeg">America/Rankin_Inlet</id>
+ <id notafter="986112000000" repl="America/Rankin_Inlet">America/Rainy_River</id>
+ <id notafter="1173600000000" repl="America/Winnipeg">America/Resolute</id>
+ <id notafter="309945600000" repl="America/Edmonton">America/Yellowknife</id>
+ <id notafter="1604214000000" repl="America/Whitehorse">America/Dawson_Creek</id>
+ <id notafter="84013200000" repl="America/Dawson_Creek">America/Creston</id>
+ <id notafter="1425808800000" repl="America/Dawson_Creek">America/Fort_Nelson</id>
+ <id notafter="294228000000" repl="America/Yellowknife">America/Inuvik</id>
+ <id notafter="986115600000" repl="America/Edmonton">America/Cambridge_Bay</id>
+ <id notafter="120646800000" repl="America/Whitehorse">America/Dawson</id>
+ <id alts="Canada/Yukon">America/Whitehorse</id>
</country>
<country code="cc" default="Indian/Cocos" everutc="n">
<id>Indian/Cocos</id>
@@ -206,22 +206,22 @@
<id>Europe/Zurich</id>
</country>
<country code="ci" default="Africa/Abidjan" everutc="y">
- <id>Africa/Abidjan</id>
+ <id alts="Africa/Timbuktu">Africa/Abidjan</id>
</country>
<country code="ck" default="Pacific/Rarotonga" everutc="n">
<id>Pacific/Rarotonga</id>
</country>
<country code="cl" default="America/Santiago" everutc="n">
<id>America/Punta_Arenas</id>
- <id>America/Santiago</id>
- <id>Pacific/Easter</id>
+ <id alts="Chile/Continental">America/Santiago</id>
+ <id alts="Chile/EasterIsland">Pacific/Easter</id>
</country>
<country code="cm" default="Africa/Douala" everutc="n">
<id>Africa/Douala</id>
</country>
<country code="cn" default="Asia/Shanghai" defaultBoost="y" everutc="n">
- <id>Asia/Shanghai</id>
- <id>Asia/Urumqi</id>
+ <id alts="Asia/Chongqing,Asia/Chungking,Asia/Harbin,PRC">Asia/Shanghai</id>
+ <id alts="Asia/Kashgar">Asia/Urumqi</id>
</country>
<country code="co" default="America/Bogota" everutc="n">
<id>America/Bogota</id>
@@ -230,7 +230,7 @@
<id>America/Costa_Rica</id>
</country>
<country code="cu" default="America/Havana" everutc="n">
- <id>America/Havana</id>
+ <id alts="Cuba">America/Havana</id>
</country>
<country code="cv" default="Atlantic/Cape_Verde" everutc="n">
<id>Atlantic/Cape_Verde</id>
@@ -250,7 +250,7 @@
</country>
<country code="de" default="Europe/Berlin" everutc="n">
<id>Europe/Berlin</id>
- <id notafter="338950800000">Europe/Busingen</id>
+ <id notafter="338950800000" repl="Europe/Berlin">Europe/Busingen</id>
</country>
<country code="dj" default="Africa/Djibouti" everutc="n">
<id>Africa/Djibouti</id>
@@ -275,7 +275,7 @@
<id>Europe/Tallinn</id>
</country>
<country code="eg" default="Africa/Cairo" everutc="n">
- <id>Africa/Cairo</id>
+ <id alts="Egypt">Africa/Cairo</id>
</country>
<country code="eh" default="Africa/El_Aaiun" everutc="y">
<id>Africa/El_Aaiun</id>
@@ -285,7 +285,7 @@
</country>
<country code="es" default="Europe/Madrid" everutc="y">
<id>Europe/Madrid</id>
- <id notafter="496803600000">Africa/Ceuta</id>
+ <id notafter="496803600000" repl="Europe/Madrid">Africa/Ceuta</id>
<id>Atlantic/Canary</id>
</country>
<country code="et" default="Africa/Addis_Ababa" everutc="n">
@@ -301,12 +301,12 @@
<id>Atlantic/Stanley</id>
</country>
<country code="fm" default="Pacific/Pohnpei" everutc="n">
- <id>Pacific/Pohnpei</id>
+ <id alts="Pacific/Ponape">Pacific/Pohnpei</id>
<id>Pacific/Kosrae</id>
- <id>Pacific/Chuuk</id>
+ <id alts="Pacific/Truk,Pacific/Yap">Pacific/Chuuk</id>
</country>
<country code="fo" default="Atlantic/Faroe" everutc="y">
- <id>Atlantic/Faroe</id>
+ <id alts="Atlantic/Faeroe">Atlantic/Faroe</id>
</country>
<country code="fr" default="Europe/Paris" everutc="n">
<id>Europe/Paris</id>
@@ -315,7 +315,7 @@
<id>Africa/Libreville</id>
</country>
<country code="gb" default="Europe/London" everutc="y">
- <id>Europe/London</id>
+ <id alts="Europe/Belfast,GB,GB-Eire">Europe/London</id>
</country>
<country code="gd" default="America/Grenada" everutc="n">
<id>America/Grenada</id>
@@ -338,7 +338,7 @@
<country code="gl" default="America/Nuuk" everutc="y">
<id>America/Danmarkshavn</id>
<id>America/Scoresbysund</id>
- <id>America/Nuuk</id>
+ <id alts="America/Godthab">America/Nuuk</id>
<id>America/Thule</id>
</country>
<country code="gm" default="Africa/Banjul" everutc="y">
@@ -372,7 +372,7 @@
<id>America/Guyana</id>
</country>
<country code="hk" default="Asia/Hong_Kong" everutc="n">
- <id>Asia/Hong_Kong</id>
+ <id alts="Hongkong">Asia/Hong_Kong</id>
</country>
<country code="hn" default="America/Tegucigalpa" everutc="n">
<id>America/Tegucigalpa</id>
@@ -388,21 +388,21 @@
</country>
<country code="id" default="Asia/Jakarta" everutc="n">
<id>Asia/Jayapura</id>
- <id>Asia/Makassar</id>
+ <id alts="Asia/Ujung_Pandang">Asia/Makassar</id>
<id>Asia/Jakarta</id>
- <id notafter="567964800000">Asia/Pontianak</id>
+ <id notafter="567964800000" repl="Asia/Jakarta">Asia/Pontianak</id>
</country>
<country code="ie" default="Europe/Dublin" everutc="y">
- <id>Europe/Dublin</id>
+ <id alts="Eire">Europe/Dublin</id>
</country>
<country code="il" default="Asia/Jerusalem" everutc="n">
- <id>Asia/Jerusalem</id>
+ <id alts="Asia/Tel_Aviv,Israel">Asia/Jerusalem</id>
</country>
<country code="im" default="Europe/Isle_of_Man" everutc="y">
<id>Europe/Isle_of_Man</id>
</country>
<country code="in" default="Asia/Kolkata" everutc="n">
- <id>Asia/Kolkata</id>
+ <id alts="Asia/Calcutta">Asia/Kolkata</id>
</country>
<country code="io" default="Indian/Chagos" everutc="n">
<id>Indian/Chagos</id>
@@ -411,10 +411,10 @@
<id>Asia/Baghdad</id>
</country>
<country code="ir" default="Asia/Tehran" everutc="n">
- <id>Asia/Tehran</id>
+ <id alts="Iran">Asia/Tehran</id>
</country>
<country code="is" default="Atlantic/Reykjavik" everutc="y">
- <id>Atlantic/Reykjavik</id>
+ <id alts="Iceland">Atlantic/Reykjavik</id>
</country>
<country code="it" default="Europe/Rome" everutc="n">
<id>Europe/Rome</id>
@@ -423,16 +423,16 @@
<id>Europe/Jersey</id>
</country>
<country code="jm" default="America/Jamaica" everutc="n">
- <id>America/Jamaica</id>
+ <id alts="Jamaica">America/Jamaica</id>
</country>
<country code="jo" default="Asia/Amman" everutc="n">
<id>Asia/Amman</id>
</country>
<country code="jp" default="Asia/Tokyo" everutc="n">
- <id>Asia/Tokyo</id>
+ <id alts="Japan">Asia/Tokyo</id>
</country>
<country code="ke" default="Africa/Nairobi" everutc="n">
- <id>Africa/Nairobi</id>
+ <id alts="Africa/Asmera">Africa/Nairobi</id>
</country>
<country code="kg" default="Asia/Bishkek" everutc="n">
<id>Asia/Bishkek</id>
@@ -455,7 +455,7 @@
<id>Asia/Pyongyang</id>
</country>
<country code="kr" default="Asia/Seoul" everutc="n">
- <id>Asia/Seoul</id>
+ <id alts="ROK">Asia/Seoul</id>
</country>
<country code="kw" default="Asia/Kuwait" everutc="n">
<id>Asia/Kuwait</id>
@@ -465,12 +465,12 @@
</country>
<country code="kz" default="Asia/Almaty" everutc="n">
<id>Asia/Almaty</id>
- <id notafter="1099170000000">Asia/Qostanay</id>
- <id notafter="1099173600000">Asia/Aqtau</id>
+ <id notafter="1099170000000" repl="Asia/Almaty">Asia/Qostanay</id>
<id>Asia/Oral</id>
- <id notafter="1545328800000">Asia/Qyzylorda</id>
- <id notafter="1545328800000">Asia/Aqtobe</id>
- <id notafter="922572000000">Asia/Atyrau</id>
+ <id notafter="1099173600000" repl="Asia/Oral">Asia/Aqtau</id>
+ <id notafter="1545328800000" repl="Asia/Oral">Asia/Qyzylorda</id>
+ <id notafter="1545328800000" repl="Asia/Oral">Asia/Aqtobe</id>
+ <id notafter="922572000000" repl="Asia/Oral">Asia/Atyrau</id>
</country>
<country code="la" default="Asia/Vientiane" everutc="n">
<id>Asia/Vientiane</id>
@@ -503,7 +503,7 @@
<id>Europe/Riga</id>
</country>
<country code="ly" default="Africa/Tripoli" everutc="n">
- <id>Africa/Tripoli</id>
+ <id alts="Libya">Africa/Tripoli</id>
</country>
<country code="ma" default="Africa/Casablanca" everutc="y">
<id>Africa/Casablanca</id>
@@ -512,7 +512,7 @@
<id>Europe/Monaco</id>
</country>
<country code="md" default="Europe/Chisinau" everutc="n">
- <id>Europe/Chisinau</id>
+ <id alts="Europe/Tiraspol">Europe/Chisinau</id>
</country>
<country code="me" default="Europe/Podgorica" everutc="n">
<id>Europe/Podgorica</id>
@@ -525,7 +525,7 @@
</country>
<country code="mh" default="Pacific/Majuro" everutc="n">
<id>Pacific/Majuro</id>
- <id notafter="745934400000">Pacific/Kwajalein</id>
+ <id notafter="745934400000" repl="Pacific/Majuro" alts="Kwajalein">Pacific/Kwajalein</id>
</country>
<country code="mk" default="Europe/Skopje" everutc="n">
<id>Europe/Skopje</id>
@@ -534,15 +534,15 @@
<id>Africa/Bamako</id>
</country>
<country code="mm" default="Asia/Yangon" everutc="n">
- <id>Asia/Yangon</id>
+ <id alts="Asia/Rangoon">Asia/Yangon</id>
</country>
<country code="mn" default="Asia/Ulaanbaatar" everutc="n">
<id>Asia/Choibalsan</id>
- <id>Asia/Ulaanbaatar</id>
+ <id alts="Asia/Ulan_Bator">Asia/Ulaanbaatar</id>
<id>Asia/Hovd</id>
</country>
<country code="mo" default="Asia/Macau" everutc="n">
- <id>Asia/Macau</id>
+ <id alts="Asia/Macao">Asia/Macau</id>
</country>
<country code="mp" default="Pacific/Saipan" everutc="n">
<id>Pacific/Saipan</id>
@@ -569,21 +569,21 @@
<id>Africa/Blantyre</id>
</country>
<country code="mx" default="America/Mexico_City" everutc="n">
- <id>America/Mexico_City</id>
- <id notafter="407653200000">America/Merida</id>
- <id notafter="594198000000">America/Monterrey</id>
+ <id alts="Mexico/General">America/Mexico_City</id>
+ <id notafter="407653200000" repl="America/Mexico_City">America/Merida</id>
+ <id notafter="594198000000" repl="America/Mexico_City">America/Monterrey</id>
+ <id notafter="1270371600000" repl="America/Mexico_City">America/Bahia_Banderas</id>
<id>America/Matamoros</id>
- <id notafter="1270371600000">America/Bahia_Banderas</id>
<id>America/Cancun</id>
<id>America/Chihuahua</id>
+ <id notafter="891766800000" repl="America/Chihuahua" alts="Mexico/BajaSur">America/Mazatlan</id>
<id>America/Hermosillo</id>
- <id notafter="891766800000">America/Mazatlan</id>
<id>America/Ojinaga</id>
- <id>America/Tijuana</id>
+ <id alts="America/Ensenada,America/Santa_Isabel,Mexico/BajaNorte">America/Tijuana</id>
</country>
<country code="my" default="Asia/Kuala_Lumpur" everutc="n">
<id>Asia/Kuala_Lumpur</id>
- <id notafter="378664200000">Asia/Kuching</id>
+ <id notafter="378664200000" repl="Asia/Kuala_Lumpur">Asia/Kuching</id>
</country>
<country code="mz" default="Africa/Maputo" everutc="n">
<id>Africa/Maputo</id>
@@ -610,10 +610,10 @@
<id>Europe/Amsterdam</id>
</country>
<country code="no" default="Europe/Oslo" everutc="n">
- <id>Europe/Oslo</id>
+ <id alts="Atlantic/Jan_Mayen">Europe/Oslo</id>
</country>
<country code="np" default="Asia/Kathmandu" everutc="n">
- <id>Asia/Kathmandu</id>
+ <id alts="Asia/Katmandu">Asia/Kathmandu</id>
</country>
<country code="nr" default="Pacific/Nauru" everutc="n">
<id>Pacific/Nauru</id>
@@ -622,8 +622,8 @@
<id>Pacific/Niue</id>
</country>
<country code="nz" default="Pacific/Auckland" defaultBoost="y" everutc="n">
- <id>Pacific/Auckland</id>
- <id>Pacific/Chatham</id>
+ <id alts="Antarctica/South_Pole,NZ">Pacific/Auckland</id>
+ <id alts="NZ-CHAT">Pacific/Chatham</id>
</country>
<country code="om" default="Asia/Muscat" everutc="n">
<id>Asia/Muscat</id>
@@ -650,7 +650,7 @@
<id>Asia/Karachi</id>
</country>
<country code="pl" default="Europe/Warsaw" everutc="n">
- <id>Europe/Warsaw</id>
+ <id alts="Poland">Europe/Warsaw</id>
</country>
<country code="pm" default="America/Miquelon" everutc="n">
<id>America/Miquelon</id>
@@ -662,12 +662,12 @@
<id>America/Puerto_Rico</id>
</country>
<country code="ps" default="Asia/Gaza" everutc="n">
- <id notafter="1317330000000">Asia/Gaza</id>
<id>Asia/Hebron</id>
+ <id notafter="1317330000000" repl="Asia/Hebron">Asia/Gaza</id>
</country>
<country code="pt" default="Europe/Lisbon" everutc="y">
- <id>Europe/Lisbon</id>
- <id notafter="828234000000">Atlantic/Madeira</id>
+ <id alts="Portugal">Europe/Lisbon</id>
+ <id notafter="828234000000" repl="Europe/Lisbon">Atlantic/Madeira</id>
<id>Atlantic/Azores</id>
</country>
<country code="pw" default="Pacific/Palau" everutc="n">
@@ -695,24 +695,24 @@
<id>Asia/Sakhalin</id>
<id>Asia/Srednekolymsk</id>
<id>Asia/Vladivostok</id>
- <id notafter="1315828800000">Asia/Ust-Nera</id>
- <id notafter="1459015200000">Asia/Yakutsk</id>
+ <id notafter="1315828800000" repl="Asia/Vladivostok">Asia/Ust-Nera</id>
<id>Asia/Chita</id>
- <id notafter="1315832400000">Asia/Khandyga</id>
+ <id notafter="1459015200000" repl="Asia/Chita">Asia/Yakutsk</id>
+ <id notafter="1315832400000" repl="Asia/Yakutsk">Asia/Khandyga</id>
<id>Asia/Irkutsk</id>
<id>Asia/Krasnoyarsk</id>
+ <id notafter="1459022400000" repl="Asia/Krasnoyarsk">Asia/Novokuznetsk</id>
<id>Asia/Novosibirsk</id>
<id>Asia/Barnaul</id>
- <id notafter="1459022400000">Asia/Novokuznetsk</id>
- <id notafter="1464465600000">Asia/Tomsk</id>
+ <id notafter="1464465600000" repl="Asia/Barnaul">Asia/Tomsk</id>
<id>Asia/Omsk</id>
<id>Asia/Yekaterinburg</id>
<id>Europe/Samara</id>
- <id notafter="701823600000">Europe/Astrakhan</id>
- <id notafter="1480806000000">Europe/Ulyanovsk</id>
<id>Europe/Saratov</id>
+ <id notafter="1480806000000" repl="Europe/Saratov">Europe/Ulyanovsk</id>
+ <id notafter="701823600000" repl="Europe/Ulyanovsk">Europe/Astrakhan</id>
<id>Europe/Volgograd</id>
- <id>Europe/Moscow</id>
+ <id alts="W-SU">Europe/Moscow</id>
<id>Europe/Kirov</id>
<id>Europe/Kaliningrad</id>
</country>
@@ -735,7 +735,7 @@
<id>Europe/Stockholm</id>
</country>
<country code="sg" default="Asia/Singapore" everutc="n">
- <id>Asia/Singapore</id>
+ <id alts="Singapore">Asia/Singapore</id>
</country>
<country code="sh" default="Atlantic/St_Helena" everutc="y">
<id>Atlantic/St_Helena</id>
@@ -807,7 +807,7 @@
<id>Asia/Dili</id>
</country>
<country code="tm" default="Asia/Ashgabat" everutc="n">
- <id>Asia/Ashgabat</id>
+ <id alts="Asia/Ashkhabad">Asia/Ashgabat</id>
</country>
<country code="tn" default="Africa/Tunis" everutc="n">
<id>Africa/Tunis</id>
@@ -816,24 +816,24 @@
<id>Pacific/Tongatapu</id>
</country>
<country code="tr" default="Europe/Istanbul" everutc="n">
- <id>Europe/Istanbul</id>
+ <id alts="Turkey">Europe/Istanbul</id>
</country>
<country code="tt" default="America/Port_of_Spain" everutc="n">
- <id>America/Port_of_Spain</id>
+ <id alts="America/Virgin">America/Port_of_Spain</id>
</country>
<country code="tv" default="Pacific/Funafuti" everutc="n">
<id>Pacific/Funafuti</id>
</country>
<country code="tw" default="Asia/Taipei" everutc="n">
- <id>Asia/Taipei</id>
+ <id alts="ROC">Asia/Taipei</id>
</country>
<country code="tz" default="Africa/Dar_es_Salaam" everutc="n">
<id>Africa/Dar_es_Salaam</id>
</country>
<country code="ua" default="Europe/Kiev" everutc="n">
<id>Europe/Kiev</id>
- <id notafter="686091600000">Europe/Uzhgorod</id>
- <id notafter="686102400000">Europe/Zaporozhye</id>
+ <id notafter="686102400000" repl="Europe/Kiev">Europe/Zaporozhye</id>
+ <id notafter="686091600000" repl="Europe/Zaporozhye">Europe/Uzhgorod</id>
<id picker="n">Europe/Simferopol</id>
</country>
<country code="ug" default="Africa/Kampala" everutc="n">
@@ -844,42 +844,42 @@
<id>Pacific/Midway</id>
</country>
<country code="us" default="America/New_York" everutc="n">
- <id>America/New_York</id>
- <id notafter="167814000000">America/Detroit</id>
- <id notafter="152089200000">America/Kentucky/Louisville</id>
- <id notafter="972802800000">America/Kentucky/Monticello</id>
- <id notafter="1130652000000">America/Indiana/Indianapolis</id>
- <id notafter="1194159600000">America/Indiana/Vincennes</id>
- <id notafter="1173600000000">America/Indiana/Winamac</id>
- <id notafter="183535200000">America/Indiana/Marengo</id>
- <id notafter="247042800000">America/Indiana/Petersburg</id>
- <id notafter="89186400000">America/Indiana/Vevay</id>
- <id>America/Chicago</id>
- <id notafter="688546800000">America/Indiana/Knox</id>
- <id notafter="104918400000">America/Menominee</id>
- <id notafter="720000000000">America/North_Dakota/Center</id>
- <id notafter="1067155200000">America/North_Dakota/New_Salem</id>
- <id notafter="1143964800000">America/Indiana/Tell_City</id>
- <id notafter="1289116800000">America/North_Dakota/Beulah</id>
- <id>America/Denver</id>
- <id notafter="129114000000">America/Boise</id>
- <id>America/Phoenix</id>
- <id>America/Los_Angeles</id>
- <id>America/Anchorage</id>
- <id notafter="436359600000">America/Juneau</id>
- <id notafter="436356000000">America/Yakutat</id>
- <id notafter="436363200000">America/Nome</id>
- <id notafter="1547978400000">America/Metlakatla</id>
- <id notafter="341402400000">America/Sitka</id>
- <id>Pacific/Honolulu</id>
- <id>America/Adak</id>
+ <id alts="US/Eastern">America/New_York</id>
+ <id notafter="152089200000" repl="America/New_York" alts="America/Louisville">America/Kentucky/Louisville</id>
+ <id notafter="167814000000" repl="America/New_York" alts="US/Michigan">America/Detroit</id>
+ <id notafter="1130652000000" repl="America/New_York" alts="America/Fort_Wayne,America/Indianapolis,US/East-Indiana">America/Indiana/Indianapolis</id>
+ <id notafter="1194159600000" repl="America/New_York">America/Indiana/Vincennes</id>
+ <id notafter="972802800000" repl="America/New_York">America/Kentucky/Monticello</id>
+ <id notafter="247042800000" repl="America/Indiana/Vincennes">America/Indiana/Petersburg</id>
+ <id notafter="1173600000000" repl="America/New_York">America/Indiana/Winamac</id>
+ <id notafter="89186400000" repl="America/Indiana/Indianapolis">America/Indiana/Vevay</id>
+ <id notafter="183535200000" repl="America/Indiana/Indianapolis">America/Indiana/Marengo</id>
+ <id alts="US/Central">America/Chicago</id>
+ <id notafter="104918400000" repl="America/Chicago">America/Menominee</id>
+ <id notafter="1143964800000" repl="America/Chicago">America/Indiana/Tell_City</id>
+ <id notafter="688546800000" repl="America/Indiana/Tell_City" alts="America/Knox_IN,US/Indiana-Starke">America/Indiana/Knox</id>
+ <id notafter="1289116800000" repl="America/Chicago">America/North_Dakota/Beulah</id>
+ <id notafter="1067155200000" repl="America/Chicago">America/North_Dakota/New_Salem</id>
+ <id notafter="720000000000" repl="America/Chicago">America/North_Dakota/Center</id>
+ <id alts="America/Shiprock,Navajo,US/Mountain">America/Denver</id>
+ <id alts="US/Arizona">America/Phoenix</id>
+ <id notafter="129114000000" repl="America/Denver">America/Boise</id>
+ <id alts="US/Pacific">America/Los_Angeles</id>
+ <id alts="US/Alaska">America/Anchorage</id>
+ <id notafter="436359600000" repl="America/Anchorage">America/Juneau</id>
+ <id notafter="341402400000" repl="America/Juneau">America/Sitka</id>
+ <id notafter="436363200000" repl="America/Anchorage">America/Nome</id>
+ <id notafter="1547978400000" repl="America/Anchorage">America/Metlakatla</id>
+ <id notafter="436356000000" repl="America/Juneau">America/Yakutat</id>
+ <id alts="Pacific/Johnston,US/Hawaii">Pacific/Honolulu</id>
+ <id alts="America/Atka,US/Aleutian">America/Adak</id>
</country>
<country code="uy" default="America/Montevideo" everutc="n">
<id>America/Montevideo</id>
</country>
<country code="uz" default="Asia/Tashkent" everutc="n">
<id>Asia/Tashkent</id>
- <id notafter="670366800000">Asia/Samarkand</id>
+ <id notafter="670366800000" repl="Asia/Tashkent">Asia/Samarkand</id>
</country>
<country code="va" default="Europe/Vatican" everutc="n">
<id>Europe/Vatican</id>
@@ -897,7 +897,7 @@
<id>America/St_Thomas</id>
</country>
<country code="vn" default="Asia/Ho_Chi_Minh" everutc="n">
- <id>Asia/Ho_Chi_Minh</id>
+ <id alts="Asia/Saigon">Asia/Ho_Chi_Minh</id>
</country>
<country code="vu" default="Pacific/Efate" everutc="n">
<id>Pacific/Efate</id>
diff --git a/testing/data/test2/output_data/distro/distro.zip b/testing/data/test2/output_data/distro/distro.zip
index 9ac93a2..fdc0f4c 100644
--- a/testing/data/test2/output_data/distro/distro.zip
+++ b/testing/data/test2/output_data/distro/distro.zip
Binary files differ
diff --git a/testing/data/test2/output_data/iana/tzdata b/testing/data/test2/output_data/iana/tzdata
index 6140cf2..e8d2bd6 100644
--- a/testing/data/test2/output_data/iana/tzdata
+++ b/testing/data/test2/output_data/iana/tzdata
Binary files differ
diff --git a/testing/data/test2/output_data/version/tz_version b/testing/data/test2/output_data/version/tz_version
index cd062bc..63882f0 100644
--- a/testing/data/test2/output_data/version/tz_version
+++ b/testing/data/test2/output_data/version/tz_version
@@ -1 +1 @@
-004.001|2016a|001
\ No newline at end of file
+005.001|2016a|001
\ No newline at end of file
diff --git a/testing/data/test3/output_data/android/tzlookup.xml b/testing/data/test3/output_data/android/tzlookup.xml
index 951c87b..328ff79 100644
--- a/testing/data/test3/output_data/android/tzlookup.xml
+++ b/testing/data/test3/output_data/android/tzlookup.xml
@@ -41,38 +41,38 @@
<id>Antarctica/Palmer</id>
</country>
<country code="ar" default="America/Argentina/Buenos_Aires" defaultBoost="y" everutc="n">
- <id>America/Argentina/Buenos_Aires</id>
- <id notafter="687931200000">America/Argentina/Cordoba</id>
- <id notafter="1096171200000">America/Argentina/Salta</id>
- <id notafter="687931200000">America/Argentina/Jujuy</id>
- <id notafter="1087099200000">America/Argentina/Tucuman</id>
- <id notafter="1087704000000">America/Argentina/Catamarca</id>
- <id notafter="687931200000">America/Argentina/La_Rioja</id>
- <id notafter="1090728000000">America/Argentina/San_Juan</id>
- <id notafter="1237082400000">America/Argentina/Mendoza</id>
+ <id alts="America/Buenos_Aires">America/Argentina/Buenos_Aires</id>
+ <id notafter="687931200000" repl="America/Argentina/Buenos_Aires" alts="America/Cordoba,America/Rosario">America/Argentina/Cordoba</id>
+ <id notafter="1237082400000" repl="America/Argentina/Buenos_Aires" alts="America/Mendoza">America/Argentina/Mendoza</id>
+ <id notafter="1087099200000" repl="America/Argentina/Buenos_Aires">America/Argentina/Tucuman</id>
+ <id notafter="1096171200000" repl="America/Argentina/Mendoza">America/Argentina/Salta</id>
+ <id notafter="1090728000000" repl="America/Argentina/Salta">America/Argentina/San_Juan</id>
+ <id notafter="687931200000" repl="America/Argentina/Salta" alts="America/Jujuy">America/Argentina/Jujuy</id>
+ <id notafter="1087704000000" repl="America/Argentina/Salta" alts="America/Argentina/ComodRivadavia,America/Catamarca">America/Argentina/Catamarca</id>
+ <id notafter="687931200000" repl="America/Argentina/Catamarca">America/Argentina/La_Rioja</id>
+ <id notafter="673588800000" repl="America/Argentina/La_Rioja">America/Argentina/Rio_Gallegos</id>
+ <id notafter="1087704000000" repl="America/Argentina/Salta">America/Argentina/Ushuaia</id>
<id>America/Argentina/San_Luis</id>
- <id notafter="673588800000">America/Argentina/Rio_Gallegos</id>
- <id notafter="1087704000000">America/Argentina/Ushuaia</id>
</country>
<country code="as" default="Pacific/Pago_Pago" everutc="n">
- <id>Pacific/Pago_Pago</id>
+ <id alts="Pacific/Samoa,US/Samoa">Pacific/Pago_Pago</id>
</country>
<country code="at" default="Europe/Vienna" everutc="n">
<id>Europe/Vienna</id>
</country>
<country code="au" default="Australia/Sydney" everutc="n">
- <id>Australia/Sydney</id>
- <id notafter="796147200000">Australia/Melbourne</id>
- <id>Australia/Brisbane</id>
- <id notafter="1193500800000">Australia/Hobart</id>
- <id notafter="37728000000">Australia/Currie</id>
- <id notafter="762883200000">Australia/Lindeman</id>
- <id notafter="1286035200000">Antarctica/Macquarie</id>
- <id>Australia/Lord_Howe</id>
- <id>Australia/Adelaide</id>
- <id notafter="796149000000">Australia/Broken_Hill</id>
- <id>Australia/Darwin</id>
- <id>Australia/Perth</id>
+ <id alts="Australia/ACT,Australia/Canberra,Australia/NSW">Australia/Sydney</id>
+ <id notafter="796147200000" repl="Australia/Sydney" alts="Australia/Victoria">Australia/Melbourne</id>
+ <id notafter="1193500800000" repl="Australia/Sydney" alts="Australia/Tasmania">Australia/Hobart</id>
+ <id notafter="37728000000" repl="Australia/Hobart">Australia/Currie</id>
+ <id alts="Australia/Queensland">Australia/Brisbane</id>
+ <id notafter="762883200000" repl="Australia/Brisbane">Australia/Lindeman</id>
+ <id notafter="1286035200000" repl="Australia/Sydney">Antarctica/Macquarie</id>
+ <id alts="Australia/LHI">Australia/Lord_Howe</id>
+ <id alts="Australia/South">Australia/Adelaide</id>
+ <id notafter="796149000000" repl="Australia/Adelaide" alts="Australia/Yancowinna">Australia/Broken_Hill</id>
+ <id alts="Australia/North">Australia/Darwin</id>
+ <id alts="Australia/West">Australia/Perth</id>
<id>Australia/Eucla</id>
</country>
<country code="aw" default="America/Aruba" everutc="n">
@@ -91,7 +91,7 @@
<id>America/Barbados</id>
</country>
<country code="bd" default="Asia/Dhaka" everutc="n">
- <id>Asia/Dhaka</id>
+ <id alts="Asia/Dacca">Asia/Dhaka</id>
</country>
<country code="be" default="Europe/Brussels" everutc="n">
<id>Europe/Brussels</id>
@@ -127,28 +127,28 @@
<id>America/Kralendijk</id>
</country>
<country code="br" default="America/Noronha" everutc="n">
- <id>America/Noronha</id>
- <id>America/Sao_Paulo</id>
- <id notafter="1013911200000">America/Belem</id>
- <id notafter="972180000000">America/Fortaleza</id>
- <id notafter="1330221600000">America/Recife</id>
- <id notafter="1361066400000">America/Araguaina</id>
- <id notafter="824004000000">America/Maceio</id>
- <id notafter="1550368800000">America/Bahia</id>
- <id notafter="1214280000000">America/Santarem</id>
- <id>America/Manaus</id>
- <id notafter="1076814000000">America/Campo_Grande</id>
- <id notafter="1550372400000">America/Cuiaba</id>
- <id notafter="761713200000">America/Porto_Velho</id>
- <id notafter="971578800000">America/Boa_Vista</id>
- <id notafter="761716800000">America/Eirunepe</id>
- <id>America/Rio_Branco</id>
+ <id alts="Brazil/DeNoronha">America/Noronha</id>
+ <id alts="Brazil/East">America/Sao_Paulo</id>
+ <id notafter="1550368800000" repl="America/Sao_Paulo">America/Bahia</id>
+ <id notafter="1214280000000" repl="America/Recife">America/Santarem</id>
+ <id notafter="1330221600000" repl="America/Bahia">America/Recife</id>
+ <id notafter="972180000000" repl="America/Recife">America/Fortaleza</id>
+ <id notafter="1013911200000" repl="America/Recife">America/Belem</id>
+ <id notafter="824004000000" repl="America/Fortaleza">America/Maceio</id>
+ <id notafter="1361066400000" repl="America/Bahia">America/Araguaina</id>
+ <id alts="Brazil/West">America/Manaus</id>
+ <id notafter="1550372400000" repl="America/Manaus">America/Cuiaba</id>
+ <id notafter="1076814000000" repl="America/Cuiaba">America/Campo_Grande</id>
+ <id notafter="761713200000" repl="America/Manaus">America/Porto_Velho</id>
+ <id notafter="971578800000" repl="America/Manaus">America/Boa_Vista</id>
+ <id alts="America/Porto_Acre,Brazil/Acre">America/Rio_Branco</id>
+ <id notafter="761716800000" repl="America/Rio_Branco">America/Eirunepe</id>
</country>
<country code="bs" default="America/Nassau" everutc="n">
<id>America/Nassau</id>
</country>
<country code="bt" default="Asia/Thimphu" everutc="n">
- <id>Asia/Thimphu</id>
+ <id alts="Asia/Thimbu">Asia/Thimphu</id>
</country>
<country code="bw" default="Africa/Gaborone" everutc="n">
<id>Africa/Gaborone</id>
@@ -160,34 +160,34 @@
<id>America/Belize</id>
</country>
<country code="ca" default="America/Toronto" everutc="n">
- <id>America/Toronto</id>
- <id>America/Vancouver</id>
- <id>America/Edmonton</id>
- <id>America/Winnipeg</id>
- <id>America/Halifax</id>
- <id>America/St_Johns</id>
- <id notafter="57733200000">America/Glace_Bay</id>
- <id notafter="1162098000000">America/Moncton</id>
- <id notafter="1299996000000">America/Goose_Bay</id>
+ <id alts="America/Montreal,Canada/Eastern">America/Toronto</id>
+ <id alts="Canada/Pacific">America/Vancouver</id>
+ <id alts="Canada/Mountain">America/Edmonton</id>
+ <id alts="Canada/Central">America/Winnipeg</id>
+ <id alts="Canada/Atlantic">America/Halifax</id>
+ <id alts="Canada/Newfoundland">America/St_Johns</id>
+ <id notafter="1162098000000" repl="America/Halifax">America/Moncton</id>
+ <id notafter="57733200000" repl="America/Halifax">America/Glace_Bay</id>
+ <id notafter="1299996000000" repl="America/Halifax">America/Goose_Bay</id>
<id>America/Blanc-Sablon</id>
- <id notafter="89186400000">America/Nipigon</id>
- <id notafter="120636000000">America/Thunder_Bay</id>
- <id notafter="972802800000">America/Iqaluit</id>
- <id notafter="796806000000">America/Pangnirtung</id>
- <id>America/Atikokan</id>
- <id>America/Regina</id>
- <id notafter="1130659200000">America/Rankin_Inlet</id>
- <id notafter="986112000000">America/Rainy_River</id>
- <id notafter="73472400000">America/Swift_Current</id>
- <id notafter="1173600000000">America/Resolute</id>
- <id notafter="986115600000">America/Cambridge_Bay</id>
- <id notafter="309945600000">America/Yellowknife</id>
- <id notafter="294228000000">America/Inuvik</id>
- <id>America/Whitehorse</id>
- <id notafter="1604214000000">America/Dawson_Creek</id>
- <id notafter="84013200000">America/Creston</id>
- <id notafter="1425808800000">America/Fort_Nelson</id>
- <id notafter="120646800000">America/Dawson</id>
+ <id notafter="120636000000" repl="America/Toronto">America/Thunder_Bay</id>
+ <id notafter="972802800000" repl="America/Toronto">America/Iqaluit</id>
+ <id notafter="89186400000" repl="America/Thunder_Bay">America/Nipigon</id>
+ <id notafter="796806000000" repl="America/Iqaluit">America/Pangnirtung</id>
+ <id alts="America/Coral_Harbour">America/Atikokan</id>
+ <id alts="Canada/Saskatchewan">America/Regina</id>
+ <id notafter="73472400000" repl="America/Regina">America/Swift_Current</id>
+ <id notafter="1130659200000" repl="America/Winnipeg">America/Rankin_Inlet</id>
+ <id notafter="986112000000" repl="America/Rankin_Inlet">America/Rainy_River</id>
+ <id notafter="1173600000000" repl="America/Winnipeg">America/Resolute</id>
+ <id notafter="309945600000" repl="America/Edmonton">America/Yellowknife</id>
+ <id notafter="1604214000000" repl="America/Whitehorse">America/Dawson_Creek</id>
+ <id notafter="84013200000" repl="America/Dawson_Creek">America/Creston</id>
+ <id notafter="1425808800000" repl="America/Dawson_Creek">America/Fort_Nelson</id>
+ <id notafter="294228000000" repl="America/Yellowknife">America/Inuvik</id>
+ <id notafter="986115600000" repl="America/Edmonton">America/Cambridge_Bay</id>
+ <id notafter="120646800000" repl="America/Whitehorse">America/Dawson</id>
+ <id alts="Canada/Yukon">America/Whitehorse</id>
</country>
<country code="cc" default="Indian/Cocos" everutc="n">
<id>Indian/Cocos</id>
@@ -206,22 +206,22 @@
<id>Europe/Zurich</id>
</country>
<country code="ci" default="Africa/Abidjan" everutc="y">
- <id>Africa/Abidjan</id>
+ <id alts="Africa/Timbuktu">Africa/Abidjan</id>
</country>
<country code="ck" default="Pacific/Rarotonga" everutc="n">
<id>Pacific/Rarotonga</id>
</country>
<country code="cl" default="America/Santiago" everutc="n">
<id>America/Punta_Arenas</id>
- <id>America/Santiago</id>
- <id>Pacific/Easter</id>
+ <id alts="Chile/Continental">America/Santiago</id>
+ <id alts="Chile/EasterIsland">Pacific/Easter</id>
</country>
<country code="cm" default="Africa/Douala" everutc="n">
<id>Africa/Douala</id>
</country>
<country code="cn" default="Asia/Shanghai" defaultBoost="y" everutc="n">
- <id>Asia/Shanghai</id>
- <id>Asia/Urumqi</id>
+ <id alts="Asia/Chongqing,Asia/Chungking,Asia/Harbin,PRC">Asia/Shanghai</id>
+ <id alts="Asia/Kashgar">Asia/Urumqi</id>
</country>
<country code="co" default="America/Bogota" everutc="n">
<id>America/Bogota</id>
@@ -230,7 +230,7 @@
<id>America/Costa_Rica</id>
</country>
<country code="cu" default="America/Havana" everutc="n">
- <id>America/Havana</id>
+ <id alts="Cuba">America/Havana</id>
</country>
<country code="cv" default="Atlantic/Cape_Verde" everutc="n">
<id>Atlantic/Cape_Verde</id>
@@ -250,7 +250,7 @@
</country>
<country code="de" default="Europe/Berlin" everutc="n">
<id>Europe/Berlin</id>
- <id notafter="338950800000">Europe/Busingen</id>
+ <id notafter="338950800000" repl="Europe/Berlin">Europe/Busingen</id>
</country>
<country code="dj" default="Africa/Djibouti" everutc="n">
<id>Africa/Djibouti</id>
@@ -275,7 +275,7 @@
<id>Europe/Tallinn</id>
</country>
<country code="eg" default="Africa/Cairo" everutc="n">
- <id>Africa/Cairo</id>
+ <id alts="Egypt">Africa/Cairo</id>
</country>
<country code="eh" default="Africa/El_Aaiun" everutc="y">
<id>Africa/El_Aaiun</id>
@@ -285,7 +285,7 @@
</country>
<country code="es" default="Europe/Madrid" everutc="y">
<id>Europe/Madrid</id>
- <id notafter="496803600000">Africa/Ceuta</id>
+ <id notafter="496803600000" repl="Europe/Madrid">Africa/Ceuta</id>
<id>Atlantic/Canary</id>
</country>
<country code="et" default="Africa/Addis_Ababa" everutc="n">
@@ -301,12 +301,12 @@
<id>Atlantic/Stanley</id>
</country>
<country code="fm" default="Pacific/Pohnpei" everutc="n">
- <id>Pacific/Pohnpei</id>
+ <id alts="Pacific/Ponape">Pacific/Pohnpei</id>
<id>Pacific/Kosrae</id>
- <id>Pacific/Chuuk</id>
+ <id alts="Pacific/Truk,Pacific/Yap">Pacific/Chuuk</id>
</country>
<country code="fo" default="Atlantic/Faroe" everutc="y">
- <id>Atlantic/Faroe</id>
+ <id alts="Atlantic/Faeroe">Atlantic/Faroe</id>
</country>
<country code="fr" default="Europe/Paris" everutc="n">
<id>Europe/Paris</id>
@@ -315,7 +315,7 @@
<id>Africa/Libreville</id>
</country>
<country code="gb" default="Europe/London" everutc="y">
- <id>Europe/London</id>
+ <id alts="Europe/Belfast,GB,GB-Eire">Europe/London</id>
</country>
<country code="gd" default="America/Grenada" everutc="n">
<id>America/Grenada</id>
@@ -338,7 +338,7 @@
<country code="gl" default="America/Nuuk" everutc="y">
<id>America/Danmarkshavn</id>
<id>America/Scoresbysund</id>
- <id>America/Nuuk</id>
+ <id alts="America/Godthab">America/Nuuk</id>
<id>America/Thule</id>
</country>
<country code="gm" default="Africa/Banjul" everutc="y">
@@ -372,7 +372,7 @@
<id>America/Guyana</id>
</country>
<country code="hk" default="Asia/Hong_Kong" everutc="n">
- <id>Asia/Hong_Kong</id>
+ <id alts="Hongkong">Asia/Hong_Kong</id>
</country>
<country code="hn" default="America/Tegucigalpa" everutc="n">
<id>America/Tegucigalpa</id>
@@ -388,21 +388,21 @@
</country>
<country code="id" default="Asia/Jakarta" everutc="n">
<id>Asia/Jayapura</id>
- <id>Asia/Makassar</id>
+ <id alts="Asia/Ujung_Pandang">Asia/Makassar</id>
<id>Asia/Jakarta</id>
- <id notafter="567964800000">Asia/Pontianak</id>
+ <id notafter="567964800000" repl="Asia/Jakarta">Asia/Pontianak</id>
</country>
<country code="ie" default="Europe/Dublin" everutc="y">
- <id>Europe/Dublin</id>
+ <id alts="Eire">Europe/Dublin</id>
</country>
<country code="il" default="Asia/Jerusalem" everutc="n">
- <id>Asia/Jerusalem</id>
+ <id alts="Asia/Tel_Aviv,Israel">Asia/Jerusalem</id>
</country>
<country code="im" default="Europe/Isle_of_Man" everutc="y">
<id>Europe/Isle_of_Man</id>
</country>
<country code="in" default="Asia/Kolkata" everutc="n">
- <id>Asia/Kolkata</id>
+ <id alts="Asia/Calcutta">Asia/Kolkata</id>
</country>
<country code="io" default="Indian/Chagos" everutc="n">
<id>Indian/Chagos</id>
@@ -411,10 +411,10 @@
<id>Asia/Baghdad</id>
</country>
<country code="ir" default="Asia/Tehran" everutc="n">
- <id>Asia/Tehran</id>
+ <id alts="Iran">Asia/Tehran</id>
</country>
<country code="is" default="Atlantic/Reykjavik" everutc="y">
- <id>Atlantic/Reykjavik</id>
+ <id alts="Iceland">Atlantic/Reykjavik</id>
</country>
<country code="it" default="Europe/Rome" everutc="n">
<id>Europe/Rome</id>
@@ -423,16 +423,16 @@
<id>Europe/Jersey</id>
</country>
<country code="jm" default="America/Jamaica" everutc="n">
- <id>America/Jamaica</id>
+ <id alts="Jamaica">America/Jamaica</id>
</country>
<country code="jo" default="Asia/Amman" everutc="n">
<id>Asia/Amman</id>
</country>
<country code="jp" default="Asia/Tokyo" everutc="n">
- <id>Asia/Tokyo</id>
+ <id alts="Japan">Asia/Tokyo</id>
</country>
<country code="ke" default="Africa/Nairobi" everutc="n">
- <id>Africa/Nairobi</id>
+ <id alts="Africa/Asmera">Africa/Nairobi</id>
</country>
<country code="kg" default="Asia/Bishkek" everutc="n">
<id>Asia/Bishkek</id>
@@ -455,7 +455,7 @@
<id>Asia/Pyongyang</id>
</country>
<country code="kr" default="Asia/Seoul" everutc="n">
- <id>Asia/Seoul</id>
+ <id alts="ROK">Asia/Seoul</id>
</country>
<country code="kw" default="Asia/Kuwait" everutc="n">
<id>Asia/Kuwait</id>
@@ -465,12 +465,12 @@
</country>
<country code="kz" default="Asia/Almaty" everutc="n">
<id>Asia/Almaty</id>
- <id notafter="1099170000000">Asia/Qostanay</id>
- <id notafter="1099173600000">Asia/Aqtau</id>
+ <id notafter="1099170000000" repl="Asia/Almaty">Asia/Qostanay</id>
<id>Asia/Oral</id>
- <id notafter="1545328800000">Asia/Qyzylorda</id>
- <id notafter="1545328800000">Asia/Aqtobe</id>
- <id notafter="922572000000">Asia/Atyrau</id>
+ <id notafter="1099173600000" repl="Asia/Oral">Asia/Aqtau</id>
+ <id notafter="1545328800000" repl="Asia/Oral">Asia/Qyzylorda</id>
+ <id notafter="1545328800000" repl="Asia/Oral">Asia/Aqtobe</id>
+ <id notafter="922572000000" repl="Asia/Oral">Asia/Atyrau</id>
</country>
<country code="la" default="Asia/Vientiane" everutc="n">
<id>Asia/Vientiane</id>
@@ -503,7 +503,7 @@
<id>Europe/Riga</id>
</country>
<country code="ly" default="Africa/Tripoli" everutc="n">
- <id>Africa/Tripoli</id>
+ <id alts="Libya">Africa/Tripoli</id>
</country>
<country code="ma" default="Africa/Casablanca" everutc="y">
<id>Africa/Casablanca</id>
@@ -512,7 +512,7 @@
<id>Europe/Monaco</id>
</country>
<country code="md" default="Europe/Chisinau" everutc="n">
- <id>Europe/Chisinau</id>
+ <id alts="Europe/Tiraspol">Europe/Chisinau</id>
</country>
<country code="me" default="Europe/Podgorica" everutc="n">
<id>Europe/Podgorica</id>
@@ -525,7 +525,7 @@
</country>
<country code="mh" default="Pacific/Majuro" everutc="n">
<id>Pacific/Majuro</id>
- <id notafter="745934400000">Pacific/Kwajalein</id>
+ <id notafter="745934400000" repl="Pacific/Majuro" alts="Kwajalein">Pacific/Kwajalein</id>
</country>
<country code="mk" default="Europe/Skopje" everutc="n">
<id>Europe/Skopje</id>
@@ -534,15 +534,15 @@
<id>Africa/Bamako</id>
</country>
<country code="mm" default="Asia/Yangon" everutc="n">
- <id>Asia/Yangon</id>
+ <id alts="Asia/Rangoon">Asia/Yangon</id>
</country>
<country code="mn" default="Asia/Ulaanbaatar" everutc="n">
<id>Asia/Choibalsan</id>
- <id>Asia/Ulaanbaatar</id>
+ <id alts="Asia/Ulan_Bator">Asia/Ulaanbaatar</id>
<id>Asia/Hovd</id>
</country>
<country code="mo" default="Asia/Macau" everutc="n">
- <id>Asia/Macau</id>
+ <id alts="Asia/Macao">Asia/Macau</id>
</country>
<country code="mp" default="Pacific/Saipan" everutc="n">
<id>Pacific/Saipan</id>
@@ -569,21 +569,21 @@
<id>Africa/Blantyre</id>
</country>
<country code="mx" default="America/Mexico_City" everutc="n">
- <id>America/Mexico_City</id>
- <id notafter="407653200000">America/Merida</id>
- <id notafter="594198000000">America/Monterrey</id>
+ <id alts="Mexico/General">America/Mexico_City</id>
+ <id notafter="407653200000" repl="America/Mexico_City">America/Merida</id>
+ <id notafter="594198000000" repl="America/Mexico_City">America/Monterrey</id>
+ <id notafter="1270371600000" repl="America/Mexico_City">America/Bahia_Banderas</id>
<id>America/Matamoros</id>
- <id notafter="1270371600000">America/Bahia_Banderas</id>
<id>America/Cancun</id>
<id>America/Chihuahua</id>
+ <id notafter="891766800000" repl="America/Chihuahua" alts="Mexico/BajaSur">America/Mazatlan</id>
<id>America/Hermosillo</id>
- <id notafter="891766800000">America/Mazatlan</id>
<id>America/Ojinaga</id>
- <id>America/Tijuana</id>
+ <id alts="America/Ensenada,America/Santa_Isabel,Mexico/BajaNorte">America/Tijuana</id>
</country>
<country code="my" default="Asia/Kuala_Lumpur" everutc="n">
<id>Asia/Kuala_Lumpur</id>
- <id notafter="378664200000">Asia/Kuching</id>
+ <id notafter="378664200000" repl="Asia/Kuala_Lumpur">Asia/Kuching</id>
</country>
<country code="mz" default="Africa/Maputo" everutc="n">
<id>Africa/Maputo</id>
@@ -610,10 +610,10 @@
<id>Europe/Amsterdam</id>
</country>
<country code="no" default="Europe/Oslo" everutc="n">
- <id>Europe/Oslo</id>
+ <id alts="Atlantic/Jan_Mayen">Europe/Oslo</id>
</country>
<country code="np" default="Asia/Kathmandu" everutc="n">
- <id>Asia/Kathmandu</id>
+ <id alts="Asia/Katmandu">Asia/Kathmandu</id>
</country>
<country code="nr" default="Pacific/Nauru" everutc="n">
<id>Pacific/Nauru</id>
@@ -622,8 +622,8 @@
<id>Pacific/Niue</id>
</country>
<country code="nz" default="Pacific/Auckland" defaultBoost="y" everutc="n">
- <id>Pacific/Auckland</id>
- <id>Pacific/Chatham</id>
+ <id alts="Antarctica/South_Pole,NZ">Pacific/Auckland</id>
+ <id alts="NZ-CHAT">Pacific/Chatham</id>
</country>
<country code="om" default="Asia/Muscat" everutc="n">
<id>Asia/Muscat</id>
@@ -650,7 +650,7 @@
<id>Asia/Karachi</id>
</country>
<country code="pl" default="Europe/Warsaw" everutc="n">
- <id>Europe/Warsaw</id>
+ <id alts="Poland">Europe/Warsaw</id>
</country>
<country code="pm" default="America/Miquelon" everutc="n">
<id>America/Miquelon</id>
@@ -662,12 +662,12 @@
<id>America/Puerto_Rico</id>
</country>
<country code="ps" default="Asia/Gaza" everutc="n">
- <id notafter="1317330000000">Asia/Gaza</id>
<id>Asia/Hebron</id>
+ <id notafter="1317330000000" repl="Asia/Hebron">Asia/Gaza</id>
</country>
<country code="pt" default="Europe/Lisbon" everutc="y">
- <id>Europe/Lisbon</id>
- <id notafter="828234000000">Atlantic/Madeira</id>
+ <id alts="Portugal">Europe/Lisbon</id>
+ <id notafter="828234000000" repl="Europe/Lisbon">Atlantic/Madeira</id>
<id>Atlantic/Azores</id>
</country>
<country code="pw" default="Pacific/Palau" everutc="n">
@@ -695,24 +695,24 @@
<id>Asia/Sakhalin</id>
<id>Asia/Srednekolymsk</id>
<id>Asia/Vladivostok</id>
- <id notafter="1315828800000">Asia/Ust-Nera</id>
- <id notafter="1459015200000">Asia/Yakutsk</id>
+ <id notafter="1315828800000" repl="Asia/Vladivostok">Asia/Ust-Nera</id>
<id>Asia/Chita</id>
- <id notafter="1315832400000">Asia/Khandyga</id>
+ <id notafter="1459015200000" repl="Asia/Chita">Asia/Yakutsk</id>
+ <id notafter="1315832400000" repl="Asia/Yakutsk">Asia/Khandyga</id>
<id>Asia/Irkutsk</id>
<id>Asia/Krasnoyarsk</id>
+ <id notafter="1459022400000" repl="Asia/Krasnoyarsk">Asia/Novokuznetsk</id>
<id>Asia/Novosibirsk</id>
<id>Asia/Barnaul</id>
- <id notafter="1459022400000">Asia/Novokuznetsk</id>
- <id notafter="1464465600000">Asia/Tomsk</id>
+ <id notafter="1464465600000" repl="Asia/Barnaul">Asia/Tomsk</id>
<id>Asia/Omsk</id>
<id>Asia/Yekaterinburg</id>
<id>Europe/Samara</id>
- <id notafter="701823600000">Europe/Astrakhan</id>
- <id notafter="1480806000000">Europe/Ulyanovsk</id>
<id>Europe/Saratov</id>
+ <id notafter="1480806000000" repl="Europe/Saratov">Europe/Ulyanovsk</id>
+ <id notafter="701823600000" repl="Europe/Ulyanovsk">Europe/Astrakhan</id>
<id>Europe/Volgograd</id>
- <id>Europe/Moscow</id>
+ <id alts="W-SU">Europe/Moscow</id>
<id>Europe/Kirov</id>
<id>Europe/Kaliningrad</id>
</country>
@@ -735,7 +735,7 @@
<id>Europe/Stockholm</id>
</country>
<country code="sg" default="Asia/Singapore" everutc="n">
- <id>Asia/Singapore</id>
+ <id alts="Singapore">Asia/Singapore</id>
</country>
<country code="sh" default="Atlantic/St_Helena" everutc="y">
<id>Atlantic/St_Helena</id>
@@ -807,7 +807,7 @@
<id>Asia/Dili</id>
</country>
<country code="tm" default="Asia/Ashgabat" everutc="n">
- <id>Asia/Ashgabat</id>
+ <id alts="Asia/Ashkhabad">Asia/Ashgabat</id>
</country>
<country code="tn" default="Africa/Tunis" everutc="n">
<id>Africa/Tunis</id>
@@ -816,24 +816,24 @@
<id>Pacific/Tongatapu</id>
</country>
<country code="tr" default="Europe/Istanbul" everutc="n">
- <id>Europe/Istanbul</id>
+ <id alts="Turkey">Europe/Istanbul</id>
</country>
<country code="tt" default="America/Port_of_Spain" everutc="n">
- <id>America/Port_of_Spain</id>
+ <id alts="America/Virgin">America/Port_of_Spain</id>
</country>
<country code="tv" default="Pacific/Funafuti" everutc="n">
<id>Pacific/Funafuti</id>
</country>
<country code="tw" default="Asia/Taipei" everutc="n">
- <id>Asia/Taipei</id>
+ <id alts="ROC">Asia/Taipei</id>
</country>
<country code="tz" default="Africa/Dar_es_Salaam" everutc="n">
<id>Africa/Dar_es_Salaam</id>
</country>
<country code="ua" default="Europe/Kiev" everutc="n">
<id>Europe/Kiev</id>
- <id notafter="686091600000">Europe/Uzhgorod</id>
- <id notafter="686102400000">Europe/Zaporozhye</id>
+ <id notafter="686102400000" repl="Europe/Kiev">Europe/Zaporozhye</id>
+ <id notafter="686091600000" repl="Europe/Zaporozhye">Europe/Uzhgorod</id>
<id picker="n">Europe/Simferopol</id>
</country>
<country code="ug" default="Africa/Kampala" everutc="n">
@@ -844,42 +844,42 @@
<id>Pacific/Midway</id>
</country>
<country code="us" default="America/New_York" everutc="n">
- <id>America/New_York</id>
- <id notafter="167814000000">America/Detroit</id>
- <id notafter="152089200000">America/Kentucky/Louisville</id>
- <id notafter="972802800000">America/Kentucky/Monticello</id>
- <id notafter="1130652000000">America/Indiana/Indianapolis</id>
- <id notafter="1194159600000">America/Indiana/Vincennes</id>
- <id notafter="1173600000000">America/Indiana/Winamac</id>
- <id notafter="183535200000">America/Indiana/Marengo</id>
- <id notafter="247042800000">America/Indiana/Petersburg</id>
- <id notafter="89186400000">America/Indiana/Vevay</id>
- <id>America/Chicago</id>
- <id notafter="688546800000">America/Indiana/Knox</id>
- <id notafter="104918400000">America/Menominee</id>
- <id notafter="720000000000">America/North_Dakota/Center</id>
- <id notafter="1067155200000">America/North_Dakota/New_Salem</id>
- <id notafter="1143964800000">America/Indiana/Tell_City</id>
- <id notafter="1289116800000">America/North_Dakota/Beulah</id>
- <id>America/Denver</id>
- <id notafter="129114000000">America/Boise</id>
- <id>America/Phoenix</id>
- <id>America/Los_Angeles</id>
- <id>America/Anchorage</id>
- <id notafter="436359600000">America/Juneau</id>
- <id notafter="436356000000">America/Yakutat</id>
- <id notafter="436363200000">America/Nome</id>
- <id notafter="1547978400000">America/Metlakatla</id>
- <id notafter="341402400000">America/Sitka</id>
- <id>Pacific/Honolulu</id>
- <id>America/Adak</id>
+ <id alts="US/Eastern">America/New_York</id>
+ <id notafter="152089200000" repl="America/New_York" alts="America/Louisville">America/Kentucky/Louisville</id>
+ <id notafter="167814000000" repl="America/New_York" alts="US/Michigan">America/Detroit</id>
+ <id notafter="1130652000000" repl="America/New_York" alts="America/Fort_Wayne,America/Indianapolis,US/East-Indiana">America/Indiana/Indianapolis</id>
+ <id notafter="1194159600000" repl="America/New_York">America/Indiana/Vincennes</id>
+ <id notafter="972802800000" repl="America/New_York">America/Kentucky/Monticello</id>
+ <id notafter="247042800000" repl="America/Indiana/Vincennes">America/Indiana/Petersburg</id>
+ <id notafter="1173600000000" repl="America/New_York">America/Indiana/Winamac</id>
+ <id notafter="89186400000" repl="America/Indiana/Indianapolis">America/Indiana/Vevay</id>
+ <id notafter="183535200000" repl="America/Indiana/Indianapolis">America/Indiana/Marengo</id>
+ <id alts="US/Central">America/Chicago</id>
+ <id notafter="104918400000" repl="America/Chicago">America/Menominee</id>
+ <id notafter="1143964800000" repl="America/Chicago">America/Indiana/Tell_City</id>
+ <id notafter="688546800000" repl="America/Indiana/Tell_City" alts="America/Knox_IN,US/Indiana-Starke">America/Indiana/Knox</id>
+ <id notafter="1289116800000" repl="America/Chicago">America/North_Dakota/Beulah</id>
+ <id notafter="1067155200000" repl="America/Chicago">America/North_Dakota/New_Salem</id>
+ <id notafter="720000000000" repl="America/Chicago">America/North_Dakota/Center</id>
+ <id alts="America/Shiprock,Navajo,US/Mountain">America/Denver</id>
+ <id alts="US/Arizona">America/Phoenix</id>
+ <id notafter="129114000000" repl="America/Denver">America/Boise</id>
+ <id alts="US/Pacific">America/Los_Angeles</id>
+ <id alts="US/Alaska">America/Anchorage</id>
+ <id notafter="436359600000" repl="America/Anchorage">America/Juneau</id>
+ <id notafter="341402400000" repl="America/Juneau">America/Sitka</id>
+ <id notafter="436363200000" repl="America/Anchorage">America/Nome</id>
+ <id notafter="1547978400000" repl="America/Anchorage">America/Metlakatla</id>
+ <id notafter="436356000000" repl="America/Juneau">America/Yakutat</id>
+ <id alts="Pacific/Johnston,US/Hawaii">Pacific/Honolulu</id>
+ <id alts="America/Atka,US/Aleutian">America/Adak</id>
</country>
<country code="uy" default="America/Montevideo" everutc="n">
<id>America/Montevideo</id>
</country>
<country code="uz" default="Asia/Tashkent" everutc="n">
<id>Asia/Tashkent</id>
- <id notafter="670366800000">Asia/Samarkand</id>
+ <id notafter="670366800000" repl="Asia/Tashkent">Asia/Samarkand</id>
</country>
<country code="va" default="Europe/Vatican" everutc="n">
<id>Europe/Vatican</id>
@@ -897,7 +897,7 @@
<id>America/St_Thomas</id>
</country>
<country code="vn" default="Asia/Ho_Chi_Minh" everutc="n">
- <id>Asia/Ho_Chi_Minh</id>
+ <id alts="Asia/Saigon">Asia/Ho_Chi_Minh</id>
</country>
<country code="vu" default="Pacific/Efate" everutc="n">
<id>Pacific/Efate</id>
diff --git a/testing/data/test3/output_data/distro/distro.zip b/testing/data/test3/output_data/distro/distro.zip
index 2d369c1..ad5caad 100644
--- a/testing/data/test3/output_data/distro/distro.zip
+++ b/testing/data/test3/output_data/distro/distro.zip
Binary files differ
diff --git a/testing/data/test3/output_data/iana/tzdata b/testing/data/test3/output_data/iana/tzdata
index 231ab4a..4d317d3 100644
--- a/testing/data/test3/output_data/iana/tzdata
+++ b/testing/data/test3/output_data/iana/tzdata
Binary files differ
diff --git a/testing/data/test3/output_data/version/tz_version b/testing/data/test3/output_data/version/tz_version
index e1e8f45..b843d8a 100644
--- a/testing/data/test3/output_data/version/tz_version
+++ b/testing/data/test3/output_data/version/tz_version
@@ -1 +1 @@
-004.001|2030a|001
\ No newline at end of file
+005.001|2030a|001
\ No newline at end of file
diff --git a/testing/src/main/java/libcore/timezone/testing/ZoneInfoTestHelper.java b/testing/src/main/java/libcore/timezone/testing/ZoneInfoTestHelper.java
index 224b193..aa1c253 100644
--- a/testing/src/main/java/libcore/timezone/testing/ZoneInfoTestHelper.java
+++ b/testing/src/main/java/libcore/timezone/testing/ZoneInfoTestHelper.java
@@ -222,10 +222,9 @@
// A list is used in preference to a Map to allow simulation of badly ordered / duplicate
// IDs.
private List<ZicDatum> zicData = new ArrayList<>();
- private String zoneTab;
private Integer indexOffsetOverride;
private Integer dataOffsetOverride;
- private Integer zoneTabOffsetOverride;
+ private Integer finalOffsetOverride;
public TzDataBuilder() {}
@@ -245,8 +244,8 @@
return this;
}
- public TzDataBuilder setZoneTabOffsetOverride(int zoneTabOffset) {
- this.zoneTabOffsetOverride = zoneTabOffset;
+ public TzDataBuilder setFinalOffsetOverride(int finalOffset) {
+ this.finalOffsetOverride = finalOffset;
return this;
}
@@ -259,15 +258,9 @@
return this;
}
- public TzDataBuilder setZoneTab(String zoneTab) {
- this.zoneTab = zoneTab;
- return this;
- }
-
public TzDataBuilder initializeToValid() {
setHeaderMagic("tzdata9999a");
addZicData("Europe/Elbonia", new ZicDataBuilder().initializeToValid().build());
- setZoneTab("ZoneTab data");
return this;
}
@@ -288,7 +281,7 @@
writeInt(baos, 0);
int dataOffsetOffset = baos.size();
writeInt(baos, 0);
- int zoneTabOffsetOffset = baos.size();
+ int finalOffsetOffset = baos.size();
writeInt(baos, 0);
// Construct the data section in advance, so we know the offsets.
@@ -323,18 +316,15 @@
int dataOffset = baos.size();
writeByteArray(baos, dataBytes.toByteArray());
- // Write the zoneTab section.
- int zoneTabOffset = baos.size();
- byte[] zoneTabBytes = zoneTab.getBytes(StandardCharsets.US_ASCII);
- writeByteArray(baos, zoneTabBytes);
+ int finalOffset = baos.size();
byte[] bytes = baos.toByteArray();
setInt(bytes, indexOffsetOffset,
indexOffsetOverride != null ? indexOffsetOverride : indexOffset);
setInt(bytes, dataOffsetOffset,
dataOffsetOverride != null ? dataOffsetOverride : dataOffset);
- setInt(bytes, zoneTabOffsetOffset,
- zoneTabOffsetOverride != null ? zoneTabOffsetOverride : zoneTabOffset);
+ setInt(bytes, finalOffsetOffset,
+ finalOffsetOverride != null ? finalOffsetOverride : finalOffset);
return bytes;
}
diff --git a/update-tzdata.py b/update-tzdata.py
index 61ab30e..9cf565b 100755
--- a/update-tzdata.py
+++ b/update-tzdata.py
@@ -72,15 +72,30 @@
for line in open(zic_input_file):
fields = line.split()
if fields:
- if fields[0] == 'Link':
- links.append('%s %s %s' % (fields[0], fields[1], fields[2]))
- zones.append(fields[2])
- elif fields[0] == 'Zone':
- zones.append(fields[1])
- zones.sort()
+ 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)):
@@ -129,7 +144,8 @@
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))
+ % (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)
@@ -166,26 +182,28 @@
tzdatautil.InvokeSoong(android_build_top, ['zone_compactor'])
# Create args for ZoneCompactor
- zone_tab_file = '%s/zone.tab' % extracted_iana_data_dir
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, zone_tab_file,
- iana_output_data_dir, header_string])
+ subprocess.check_call([command, zone_compactor_setup_file, zic_output_dir, iana_output_data_dir,
+ header_string])
-def BuildTzlookup(iana_data_dir):
+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...')
+ 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
+ backward_file = '%s/backward' % iana_data_dir
command = '%s/bin/tzlookup_generator' % android_host_out
- subprocess.check_call([command, countryzones_source_file, zone_tab_file, tzlookup_dest_file])
+ subprocess.check_call([command, countryzones_source_file, zone_tab_file, backward_file,
+ tzlookup_dest_file, tzids_dest_file])
def BuildTelephonylookup():
@@ -234,6 +252,7 @@
def main():
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
@@ -252,7 +271,9 @@
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)
- BuildTzlookup(iana_data_dir)
+
+ BuildTzlookupAndTzIds(iana_data_dir)
+
BuildTelephonylookup()
# Create a distro file and version file from the output from prior stages.