Merge "Extend TzLookupGenerator to generate a new file" am: 0a4103ef37 am: d44d5dd375 am: a6af08b4b4 am: 16c2d8bfc7
Original change: https://android-review.googlesource.com/c/platform/system/timezone/+/1370416
Change-Id: I8b099714e8d9f9e449d98567dfd0f3dc6bec5655
diff --git a/input_tools/android/tzlookup_generator/Android.bp b/input_tools/android/tzlookup_generator/Android.bp
index b63324a..7278dc5 100644
--- a/input_tools/android/tzlookup_generator/Android.bp
+++ b/input_tools/android/tzlookup_generator/Android.bp
@@ -21,7 +21,19 @@
include_dirs: ["external/protobuf/src"],
},
- srcs: ["src/main/proto/**/*.proto"],
+ srcs: ["src/main/proto/**/country_zones_file.proto"],
+}
+
+// Proto library
+java_library_host {
+ name: "tzaliasesprotos",
+
+ proto: {
+ type: "full",
+ include_dirs: ["external/protobuf/src"],
+ },
+
+ srcs: ["src/main/proto/**/tz_aliases_file.proto"],
}
java_library_host {
@@ -32,6 +44,7 @@
"countryzonesprotos",
"icu4j",
"libprotobuf-java-full",
+ "tzaliasesprotos",
"tztools_common",
],
}
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
index b010c01..95bbbf2 100644
--- 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
@@ -30,7 +30,7 @@
import java.util.stream.Collectors;
/**
- * A class that knows about the structure of the backward file.
+ * A class that knows about the structure of the IANA tzdb backward file.
*/
final class BackwardFile {
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 dd04d8e..6a24d0f 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.libcore.timezone.tzaliases.proto.TzAliasesFile;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
@@ -170,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) {
@@ -180,8 +181,41 @@
this.everUsesUtc = everUsesUtc;
}
- void addTimeZoneIdentifier(TimeZoneMapping timeZoneId) {
- timeZoneIds.add(timeZoneId);
+ void addTimeZoneMapping(TimeZoneMapping timeZoneMapping) {
+ timeZoneMappings.add(timeZoneMapping);
+ }
+
+ static TzAliasesFile.TimeZoneAliases createTimeZoneAliases(Country country) {
+ TzAliasesFile.TimeZoneAliases.Builder countryAliasesBuilder =
+ TzAliasesFile.TimeZoneAliases.newBuilder();
+ for (TzLookupFile.TimeZoneMapping timeZoneMapping : country.timeZoneMappings) {
+ String mappingTimeZoneId = timeZoneMapping.olsonId;
+ String notUsedReplacementId = timeZoneMapping.notAfterReplacementId;
+ Instant notUsedAfterInstant = timeZoneMapping.notUsedAfterInclusive;
+ if (notUsedReplacementId != null && notUsedAfterInstant != null) {
+ String replacedTimeZoneId = mappingTimeZoneId;
+ TzAliasesFile.TimeZoneReplacement timeZoneReplacement =
+ TzAliasesFile.TimeZoneReplacement.newBuilder()
+ .setReplacedId(replacedTimeZoneId)
+ .setReplacementId(notUsedReplacementId)
+ .setFromMillis(notUsedAfterInstant.toEpochMilli())
+ .build();
+ countryAliasesBuilder.addTimeZoneReplacement(timeZoneReplacement);
+ }
+
+ 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 aliases if they want to.
+ TzAliasesFile.TimeZoneLink timeZoneLink =
+ TzAliasesFile.TimeZoneLink.newBuilder()
+ .setPreferredId(mappingTimeZoneId)
+ .setAlternativeId(alternativeZoneId)
+ .build();
+ countryAliasesBuilder.addTimeZoneLink(timeZoneLink);
+ }
+ }
+ return countryAliasesBuilder.build();
}
static void writeXml(Country country, XMLStreamWriter writer)
@@ -195,8 +229,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();
}
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 75fef71..20ffe96 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,18 +15,23 @@
*/
package com.android.libcore.timezone.tzlookup;
-import com.android.libcore.timezone.tzlookup.proto.CountryZonesFile;
+import com.android.libcore.timezone.tzaliases.proto.TzAliasesFile;
+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.google.protobuf.TextFormat;
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.FileOutputStream;
import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
@@ -35,13 +40,14 @@
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 ICU4J, countryzones.txt, backwards
- * and zones.tab.
+ * Generates Android's tzlookup.xml and zone alias file using ICU4J, Android's countryzones.txt
+ * file, and TZDB's backwards and zones.tab files.
*/
public final class TzLookupGenerator {
@@ -73,31 +79,34 @@
private final String countryZonesFileIn;
private final String zoneTabFileIn;
private final String backwardFileIn;
- private final String tzLookupXmlOut;
+ private final String tzLookupXmlFileOut;
+ private final String timeZoneAliasesFileOut;
/**
* Executes the generator.
*/
public static void main(String[] args) throws Exception {
- if (args.length != 4) {
+ if (args.length != 5) {
System.err.println(
"usage: java com.android.libcore.timezone.tzlookup.TzLookupGenerator"
+ " <[in] countryzones.txt file> <[in] zone.tab file>"
- + " <[in] backward file> <[out] tzlookup.xml file>");
+ + " <[in] backward file>"
+ + " <[out] tzlookup.xml file> <[out] zone alias file>");
System.exit(0);
}
TzLookupGenerator tzLookupGenerator =
- new TzLookupGenerator(args[0], args[1], args[2], args[3]);
+ new TzLookupGenerator(args[0], args[1], args[2], args[3], args[4]);
boolean success = tzLookupGenerator.execute();
System.exit(success ? 0 : 1);
}
TzLookupGenerator(String countryZonesFileIn, String zoneTabFileIn, String backwardFileIn,
- String tzLookupXmlOut) {
+ String tzLookupXmlFileOut, String timeZoneAliasesFileOut) {
this.countryZonesFileIn = countryZonesFileIn;
this.zoneTabFileIn = zoneTabFileIn;
this.backwardFileIn = backwardFileIn;
- this.tzLookupXmlOut = tzLookupXmlOut;
+ this.tzLookupXmlFileOut = tzLookupXmlFileOut;
+ this.timeZoneAliasesFileOut = timeZoneAliasesFileOut;
}
boolean execute() {
@@ -152,12 +161,12 @@
BackwardFile backwardIn = parseAndValidateBackwardFile(backwardFileIn, errors);
errors.throwIfError("Errors accumulated");
- TzLookupFile.TimeZones timeZonesOut = createOutputData(
+ OutputData outputData = createOutputData(
inputIanaVersion, zoneTabMapping, countriesIn, backwardIn, errors);
- errors.throwIfError("Errors accumulated");
// Write the output structure if there wasn't an error.
- writeOutputData(timeZonesOut, tzLookupXmlOut, errors);
+ errors.throwIfError("Errors accumulated");
+ writeOutputData(outputData, tzLookupXmlFileOut, timeZoneAliasesFileOut, errors);
return true;
} catch (HaltExecutionException e) {
logError("Stopping due to fatal condition", e);
@@ -227,29 +236,49 @@
}
}
- private static void writeOutputData(TzLookupFile.TimeZones timeZonesOut,
- String tzLookupXmlFileName, Errors errors) throws HaltExecutionException {
+ private static void writeOutputData(OutputData outputData,
+ String tzLookupXmlFileName, String timeZoneAliasesFileName, 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 " + timeZoneAliasesFileName);
+ try (OutputStreamWriter timeZoneAliasesWriter =
+ new OutputStreamWriter(new FileOutputStream(timeZoneAliasesFileName),
+ StandardCharsets.UTF_8)) {
+ // Write out the tz aliases file used during later stages of the pipeline.
+ logInfo("Writing " + timeZoneAliasesFileName);
+
+ TzAliasesFile.TimeZoneAliases timeZoneAliases = outputData.getTimeZoneAliases();
+ TextFormat.print(timeZoneAliases, timeZoneAliasesWriter);
+ } catch (IOException e) {
+ errors.addFatalAndHalt("Unable to write " + timeZoneAliasesFileName, e);
+ } finally {
+ errors.popScope();
+ }
}
- private static TzLookupFile.TimeZones createOutputData(String inputIanaVersion,
+ private static OutputData createOutputData(String inputIanaVersion,
Map<String, List<String>> zoneTabMapping, List<CountryZonesFile.Country> countriesIn,
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);
+ TzAliasesFile.TimeZoneAliases.Builder timeZoneAliasesBuilder =
+ TzAliasesFile.TimeZoneAliases.newBuilder();
+ timeZoneAliasesBuilder.setIanaVersion(inputIanaVersion);
// The time use when sampling the offsets for a zone.
final long offsetSampleTimeMillis = getSampleOffsetTimeMillisForData(inputIanaVersion);
@@ -269,20 +298,22 @@
continue;
}
- TzLookupFile.Country countryOut = processCountry(
+ CountryOutputData countryOutputData = processCountry(
offsetSampleTimeMillis, everUseUtcStartTimeMillis, countryIn,
zoneTabCountryTimeZoneIds, backwardIn, errors);
- if (countryOut == null) {
+ if (countryOutputData == null) {
// Continue processing countries if there are only errors.
continue;
}
- countryZonesOut.addCountry(countryOut);
+
+ tzLookupCountryZones.addCountry(countryOutputData.getTzLookupCountry());
+ timeZoneAliasesBuilder.mergeFrom(countryOutputData.getTimeZoneAliases());
}
errors.throwIfError("One or more countries failed");
- return timeZonesOut;
+ return new OutputData(timeZonesOut, timeZoneAliasesBuilder.build());
}
- private static TzLookupFile.Country processCountry(long offsetSampleTimeMillis,
+ private static CountryOutputData processCountry(long offsetSampleTimeMillis,
long everUseUtcStartTimeMillis, CountryZonesFile.Country countryIn,
List<String> zoneTabCountryTimeZoneIds, BackwardFile backwardIn,
Errors errors) {
@@ -412,12 +443,37 @@
TzLookupFile.TimeZoneMapping timeZoneIdOut = new TzLookupFile.TimeZoneMapping(
timeZoneInId, shownInPicker, notUsedAfterInstant, notUsedReplacementId,
alternativeZoneIds);
- countryOut.addTimeZoneIdentifier(timeZoneIdOut);
+ countryOut.addTimeZoneMapping(timeZoneIdOut);
} finally {
errors.popScope();
}
}
- return countryOut;
+
+ // TimeZoneAliases contains only information that is available from Country so we can
+ // currently build one from the other.
+ // Note: Because TimeZoneAliases is driven from Android's country mapping data, the
+ // aliases will only include zone IDs that are linked to a country AND where the link
+ // stays within the scope of a ISO 3166 country code.
+ //
+ // For example, Europe/Guernsey is a link in IANA's data to Europe/London in the TZDB
+ // "europe" file. However, this is a cross-ISO code link: Europe/London is associated
+ // with ISO code "GB", Europe/Guernsey is associated with ISO code "GG". So
+ // Europe/Guernsey is not recognized as an alias for Europe/London and it will not
+ // appear in the TimeZoneAliases.
+ //
+ // This is the behavior we want: although the TZDB makes use of links as a shorthand
+ // when countries (often neighbours with complicated shared histories) have followed the
+ // same rules, Android's data almost exclusively uses region-specific time zone IDs on
+ // the assumption it is what users would prefer, e.g. if we didn't do this, it would be
+ // difficult for some users to understand why the exemplar location for their time zone
+ // is in a different region / country from the one they are in. We have had bugs to
+ // prove this for macro-areas like the EU that have harmonized time zone rules but where
+ // users still prefer (say) Europe/Paris over Europe/Berlin, where there has been no
+ // functional difference between their time zones for several decades.
+ TzAliasesFile.TimeZoneAliases timeZoneAliases =
+ TzLookupFile.Country.createTimeZoneAliases(countryOut);
+
+ return new CountryOutputData(countryOut, timeZoneAliases);
} finally{
// End of country processing.
errors.popScope();
@@ -621,4 +677,43 @@
private static void logInfo(String msg) {
System.err.println("I: " + msg);
}
+
+ private static class CountryOutputData {
+ private final TzLookupFile.Country tzLookupCountry;
+ private final TzAliasesFile.TimeZoneAliases timeZoneAliases;
+
+ private CountryOutputData(TzLookupFile.Country tzLookupCountry,
+ TzAliasesFile.TimeZoneAliases timeZoneAliases) {
+ this.tzLookupCountry = Objects.requireNonNull(tzLookupCountry);
+ this.timeZoneAliases = Objects.requireNonNull(timeZoneAliases);
+ }
+
+ private TzLookupFile.Country getTzLookupCountry() {
+ return tzLookupCountry;
+ }
+
+ private TzAliasesFile.TimeZoneAliases getTimeZoneAliases() {
+ return timeZoneAliases;
+ }
+ }
+
+ private static class OutputData {
+
+ private final TzLookupFile.TimeZones tzLookupTimeZones;
+ private final TzAliasesFile.TimeZoneAliases timeZoneAliases;
+
+ private OutputData(TzLookupFile.TimeZones tzLookupTimeZones,
+ TzAliasesFile.TimeZoneAliases timeZoneAliases) {
+ this.tzLookupTimeZones = Objects.requireNonNull(tzLookupTimeZones);
+ this.timeZoneAliases = Objects.requireNonNull(timeZoneAliases);
+ }
+
+ private TzLookupFile.TimeZones getTzLookupTimeZones() {
+ return tzLookupTimeZones;
+ }
+
+ private TzAliasesFile.TimeZoneAliases getTimeZoneAliases() {
+ return timeZoneAliases;
+ }
+ }
}
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 c294cc8..88c9f21 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;
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 9ed5644..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;
diff --git a/input_tools/android/tzlookup_generator/src/main/proto/tz_aliases_file.proto b/input_tools/android/tzlookup_generator/src/main/proto/tz_aliases_file.proto
new file mode 100644
index 0000000..c5ae52c
--- /dev/null
+++ b/input_tools/android/tzlookup_generator/src/main/proto/tz_aliases_file.proto
@@ -0,0 +1,57 @@
+/*
+ * 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.libcore.timezone.tzaliases.proto";
+option java_multiple_files = false;
+
+package com.android.libcore.timezone.tzaliases.proto;
+
+// Information about Olson IDs used by Android.
+message TimeZoneAliases {
+ // The IANA TZDB version the data was generated from.
+ optional string ianaVersion = 1;
+ // Aliases that are always correct, i.e. obsoleted IDs.
+ repeated TimeZoneLink timeZoneLink = 2;
+ // Aliases that are time dependent. i.e. where time zones have become equivalent over time.
+ repeated TimeZoneReplacement timeZoneReplacement = 3;
+}
+
+// An alias / synonym when one time zone ID is just direct synonym for another.
+//
+// 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 because it was added after an Android release
+// launched.
+message TimeZoneLink {
+ // The alternative ID. This will typically be an obsoleted IANA ID.
+ required string alternativeId = 1;
+ // The Android preferred ID. This will typically be a newer / more correct Olson ID.
+ required string preferredId = 2;
+}
+
+// The functional replacement of one time zone 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 the better / to use in place of replacedId.
+ 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/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 e5ae559..373626a 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,9 @@
package com.android.libcore.timezone.tzlookup;
+import com.android.libcore.timezone.countryzones.proto.CountryZonesFile;
+import com.android.libcore.timezone.tzaliases.proto.TzAliasesFile;
import com.android.libcore.timezone.testing.TestUtils;
-import com.android.libcore.timezone.tzlookup.proto.CountryZonesFile;
import com.google.protobuf.TextFormat;
import com.ibm.icu.util.TimeZone;
@@ -37,17 +38,18 @@
import java.util.Map;
import java.util.stream.Collectors;
+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 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;
@@ -66,11 +68,12 @@
String countryZonesFile = createFile(tempDir, "THIS IS NOT A VALID FILE");
List<ZoneTabFile.CountryEntry> gbZoneTabEntries = createValidZoneTabEntriesGb();
String zoneTabFile = createZoneTabFile(gbZoneTabEntries);
- String backwardFile = createBackwardFile(createValidBackwardLinks());
- String outputFile = Files.createTempFile(tempDir, "out", null /* suffix */).toString();
+ String backwardFile = createBackwardFile(createEmptyBackwardLinks());
+ String tzLookupFile = createTempFileName("tzlookup");
+ String tzAliasFile = createTempFileName("tzalias");
- TzLookupGenerator tzLookupGenerator =
- new TzLookupGenerator(countryZonesFile, zoneTabFile, backwardFile, outputFile);
+ TzLookupGenerator tzLookupGenerator = new TzLookupGenerator(
+ countryZonesFile, zoneTabFile, backwardFile, tzLookupFile, tzAliasFile);
assertFalse(tzLookupGenerator.execute());
}
@@ -85,16 +88,17 @@
List<ZoneTabFile.CountryEntry> gbZoneTabEntries = createValidZoneTabEntriesGb();
String zoneTabFile = createZoneTabFile(gbZoneTabEntries);
- String backwardFile = createBackwardFile(createValidBackwardLinks());
+ String backwardFile = createBackwardFile(createEmptyBackwardLinks());
- String outputFile = Files.createTempFile(tempDir, "out", null /* suffix */).toString();
+ String tzLookupFile = createTempFileName("tzlookup");
+ String tzAliasFile = createTempFileName("tzalias");
- TzLookupGenerator tzLookupGenerator =
- new TzLookupGenerator(countryZonesFile, zoneTabFile, backwardFile, outputFile);
+ TzLookupGenerator tzLookupGenerator = new TzLookupGenerator(
+ countryZonesFile, zoneTabFile, backwardFile, tzLookupFile, tzAliasFile);
assertFalse(tzLookupGenerator.execute());
- Path outputFilePath = Paths.get(outputFile);
- assertEquals(0, Files.size(outputFilePath));
+ assertFileMissing(tzLookupFile);
+ assertFileMissing(tzAliasFile);
}
@Test
@@ -104,18 +108,19 @@
createValidCountryGb().toBuilder().clearTimeZoneMappings().build();
CountryZonesFile.CountryZones countryZones = createValidCountryZones(gbWithoutZones);
String countryZonesFile = createCountryZonesFile(countryZones);
- String backwardFile = createBackwardFile(createValidBackwardLinks());
+ String backwardFile = createBackwardFile(createEmptyBackwardLinks());
String zoneTabFile = createZoneTabFile(createValidZoneTabEntriesGb());
- String outputFile = Files.createTempFile(tempDir, "out", null /* suffix */).toString();
+ String tzLookupFile = createTempFileName("tzlookup");
+ String tzAliasFile = createTempFileName("tzalias");
- TzLookupGenerator tzLookupGenerator =
- new TzLookupGenerator(countryZonesFile, zoneTabFile, backwardFile, outputFile);
+ TzLookupGenerator tzLookupGenerator = new TzLookupGenerator(
+ countryZonesFile, zoneTabFile, backwardFile, tzLookupFile, tzAliasFile);
assertFalse(tzLookupGenerator.execute());
- Path outputFilePath = Paths.get(outputFile);
- assertEquals(0, Files.size(outputFilePath));
+ assertFileMissing(tzLookupFile);
+ assertFileMissing(tzAliasFile);
}
@Test
@@ -130,18 +135,19 @@
CountryZonesFile.CountryZones countryZones =
createValidCountryZones(gbWithDuplicateZones);
String countryZonesFile = createCountryZonesFile(countryZones);
- String backwardFile = createBackwardFile(createValidBackwardLinks());
+ String backwardFile = createBackwardFile(createEmptyBackwardLinks());
String zoneTabFile = createZoneTabFile(createValidZoneTabEntriesGb());
- String outputFile = Files.createTempFile(tempDir, "out", null /* suffix */).toString();
+ String tzLookupFile = createTempFileName("tzlookup");
+ String tzAliasFile = createTempFileName("tzalias");
- TzLookupGenerator tzLookupGenerator =
- new TzLookupGenerator(countryZonesFile, zoneTabFile, backwardFile, outputFile);
+ TzLookupGenerator tzLookupGenerator = new TzLookupGenerator(
+ countryZonesFile, zoneTabFile, backwardFile, tzLookupFile, tzAliasFile);
assertFalse(tzLookupGenerator.execute());
- Path outputFilePath = Paths.get(outputFile);
- assertEquals(0, Files.size(outputFilePath));
+ assertFileMissing(tzLookupFile);
+ assertFileMissing(tzAliasFile);
}
@Test
@@ -156,16 +162,17 @@
List<ZoneTabFile.CountryEntry> gbZoneTabEntries = createValidZoneTabEntriesGb();
String zoneTabFile = createZoneTabFile(gbZoneTabEntries);
- String backwardFile = createBackwardFile(createValidBackwardLinks());
+ String backwardFile = createBackwardFile(createEmptyBackwardLinks());
- String outputFile = Files.createTempFile(tempDir, "out", null /* suffix */).toString();
+ String tzLookupFile = createTempFileName("tzlookup");
+ String tzAliasFile = createTempFileName("tzalias");
- TzLookupGenerator tzLookupGenerator =
- new TzLookupGenerator(countryZonesFile, zoneTabFile, backwardFile, outputFile);
+ TzLookupGenerator tzLookupGenerator = new TzLookupGenerator(
+ countryZonesFile, zoneTabFile, backwardFile, tzLookupFile, tzAliasFile);
assertFalse(tzLookupGenerator.execute());
- Path outputFilePath = Paths.get(outputFile);
- assertEquals(0, Files.size(outputFilePath));
+ assertFileMissing(tzLookupFile);
+ assertFileMissing(tzAliasFile);
}
@Test
@@ -179,16 +186,17 @@
List<ZoneTabFile.CountryEntry> gbZoneTabEntries = createValidZoneTabEntriesGb();
String zoneTabFile = createZoneTabFile(gbZoneTabEntries);
- String backwardFile = createBackwardFile(createValidBackwardLinks());
+ String backwardFile = createBackwardFile(createEmptyBackwardLinks());
- String outputFile = Files.createTempFile(tempDir, "out", null /* suffix */).toString();
+ String tzLookupFile = createTempFileName("tzlookup");
+ String tzAliasFile = createTempFileName("tzalias");
- TzLookupGenerator tzLookupGenerator =
- new TzLookupGenerator(countryZonesFile, zoneTabFile, backwardFile, outputFile);
+ TzLookupGenerator tzLookupGenerator = new TzLookupGenerator(
+ countryZonesFile, zoneTabFile, backwardFile, tzLookupFile, tzAliasFile);
assertFalse(tzLookupGenerator.execute());
- Path outputFilePath = Paths.get(outputFile);
- assertEquals(0, Files.size(outputFilePath));
+ assertFileMissing(tzLookupFile);
+ assertFileMissing(tzAliasFile);
}
@Test
@@ -202,11 +210,11 @@
.clearDefaultTimeZoneId().build();
List<ZoneTabFile.CountryEntry> gbZoneTabEntries = createValidZoneTabEntriesGb();
- String tzLookupXml = generateTzLookupXml(gbWithoutDefault, gbZoneTabEntries,
- createValidBackwardLinks());
+ 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
@@ -220,11 +228,11 @@
.build();
List<ZoneTabFile.CountryEntry> gbZoneTabEntries = createValidZoneTabEntriesGb();
- String tzLookupXml = generateTzLookupXml(gbWithExplicitDefaultTimeZone, gbZoneTabEntries,
- createValidBackwardLinks());
+ 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
@@ -239,16 +247,17 @@
List<ZoneTabFile.CountryEntry> gbZoneTabEntries = createValidZoneTabEntriesGb();
String zoneTabFile = createZoneTabFile(gbZoneTabEntries);
- String backwardFile = createBackwardFile(createValidBackwardLinks());
+ String backwardFile = createBackwardFile(createEmptyBackwardLinks());
- String outputFile = Files.createTempFile(tempDir, "out", null /* suffix */).toString();
+ String tzLookupFile = createTempFileName("tzlookup");
+ String tzAliasFile = createTempFileName("tzalias");
- TzLookupGenerator tzLookupGenerator =
- new TzLookupGenerator(countryZonesFile, zoneTabFile, backwardFile, outputFile);
+ TzLookupGenerator tzLookupGenerator = new TzLookupGenerator(
+ countryZonesFile, zoneTabFile, backwardFile, tzLookupFile, tzAliasFile);
assertFalse(tzLookupGenerator.execute());
- Path outputFilePath = Paths.get(outputFile);
- assertEquals(0, Files.size(outputFilePath));
+ assertFileMissing(tzLookupFile);
+ assertFileMissing(tzAliasFile);
}
@Test
@@ -262,16 +271,17 @@
List<ZoneTabFile.CountryEntry> gbZoneTabEntries = createValidZoneTabEntriesGb();
String zoneTabFile = createZoneTabFile(gbZoneTabEntries);
- String backwardFile = createBackwardFile(createValidBackwardLinks());
+ String backwardFile = createBackwardFile(createEmptyBackwardLinks());
- String outputFile = Files.createTempFile(tempDir, "out", null /* suffix */).toString();
+ String tzLookupFile = createTempFileName("tzlookup");
+ String tzAliasFile = createTempFileName("tzalias");
- TzLookupGenerator tzLookupGenerator =
- new TzLookupGenerator(countryZonesFile, zoneTabFile, backwardFile, outputFile);
+ TzLookupGenerator tzLookupGenerator = new TzLookupGenerator(
+ countryZonesFile, zoneTabFile, backwardFile, tzLookupFile, tzAliasFile);
assertFalse(tzLookupGenerator.execute());
- Path outputFilePath = Paths.get(outputFile);
- assertEquals(0, Files.size(outputFilePath));
+ assertFileMissing(tzLookupFile);
+ assertFileMissing(tzAliasFile);
}
@Test
@@ -283,15 +293,16 @@
String zoneTabFile =
createZoneTabFile(createValidZoneTabEntriesFr(), createValidZoneTabEntriesUs());
- String backwardFile = createBackwardFile(createValidBackwardLinks());
- String outputFile = Files.createTempFile(tempDir, "out", null /* suffix */).toString();
+ String backwardFile = createBackwardFile(createEmptyBackwardLinks());
+ String tzLookupFile = createTempFileName("tzlookup");
+ String tzAliasFile = createTempFileName("tzalias");
- TzLookupGenerator tzLookupGenerator =
- new TzLookupGenerator(countryZonesFile, zoneTabFile, backwardFile, outputFile);
+ TzLookupGenerator tzLookupGenerator = new TzLookupGenerator(
+ countryZonesFile, zoneTabFile, backwardFile, tzLookupFile, tzAliasFile);
assertFalse(tzLookupGenerator.execute());
- Path outputFilePath = Paths.get(outputFile);
- assertEquals(0, Files.size(outputFilePath));
+ assertFileMissing(tzLookupFile);
+ assertFileMissing(tzAliasFile);
}
@Test
@@ -305,15 +316,16 @@
String countryZonesFile = createCountryZonesFile(countryZones);
String zoneTabFile = createZoneTabFile(createValidZoneTabEntriesGb());
- String backwardFile = createBackwardFile(createValidBackwardLinks());
- String outputFile = Files.createTempFile(tempDir, "out", null /* suffix */).toString();
+ String backwardFile = createBackwardFile(createEmptyBackwardLinks());
+ String tzLookupFile = createTempFileName("tzlookup");
+ String tzAliasFile = createTempFileName("tzalias");
- TzLookupGenerator tzLookupGenerator =
- new TzLookupGenerator(countryZonesFile, zoneTabFile, backwardFile, outputFile);
+ TzLookupGenerator tzLookupGenerator = new TzLookupGenerator(
+ countryZonesFile, zoneTabFile, backwardFile, tzLookupFile, tzAliasFile);
assertFalse(tzLookupGenerator.execute());
- Path outputFilePath = Paths.get(outputFile);
- assertEquals(0, Files.size(outputFilePath));
+ assertFileMissing(tzLookupFile);
+ assertFileMissing(tzAliasFile);
}
@Test
@@ -325,15 +337,16 @@
String zoneTabFileWithDupes = createZoneTabFile(
createValidZoneTabEntriesGb(), createValidZoneTabEntriesGb());
- String backwardFile = createBackwardFile(createValidBackwardLinks());
- String outputFile = Files.createTempFile(tempDir, "out", null /* suffix */).toString();
+ String backwardFile = createBackwardFile(createEmptyBackwardLinks());
+ String tzLookupFile = createTempFileName("tzlookup");
+ String tzAliasFile = createTempFileName("tzalias");
TzLookupGenerator tzLookupGenerator = new TzLookupGenerator(
- countryZonesFile, zoneTabFileWithDupes, backwardFile, outputFile);
+ countryZonesFile, zoneTabFileWithDupes, backwardFile, tzLookupFile, tzAliasFile);
assertFalse(tzLookupGenerator.execute());
- Path outputFilePath = Paths.get(outputFile);
- assertEquals(0, Files.size(outputFilePath));
+ assertFileMissing(tzLookupFile);
+ assertFileMissing(tzAliasFile);
}
@Test
@@ -347,16 +360,17 @@
String countryZonesFile = createCountryZonesFile(countryZones);
String zoneTabFile = createZoneTabFile(createValidZoneTabEntriesGb());
- String backwardFile = createBackwardFile(createValidBackwardLinks());
+ String backwardFile = createBackwardFile(createEmptyBackwardLinks());
- String outputFile = Files.createTempFile(tempDir, "out", null /* suffix */).toString();
+ String tzLookupFile = createTempFileName("tzlookup");
+ String tzAliasFile = createTempFileName("tzalias");
- TzLookupGenerator tzLookupGenerator =
- new TzLookupGenerator(countryZonesFile, zoneTabFile, backwardFile, outputFile);
+ TzLookupGenerator tzLookupGenerator = new TzLookupGenerator(
+ countryZonesFile, zoneTabFile, backwardFile, tzLookupFile, tzAliasFile);
assertFalse(tzLookupGenerator.execute());
- Path outputFilePath = Paths.get(outputFile);
- assertEquals(0, Files.size(outputFilePath));
+ assertFileMissing(tzLookupFile);
+ assertFileMissing(tzAliasFile);
}
@Test
@@ -374,16 +388,17 @@
new ArrayList<>(createValidZoneTabEntriesGb());
zoneTabEntriesWithBadId.add(new ZoneTabFile.CountryEntry("GB", INVALID_TIME_ZONE_ID));
String zoneTabFile = createZoneTabFile(zoneTabEntriesWithBadId);
- String backwardFile = createBackwardFile(createValidBackwardLinks());
+ String backwardFile = createBackwardFile(createEmptyBackwardLinks());
- String outputFile = Files.createTempFile(tempDir, "out", null /* suffix */).toString();
+ String tzLookupFile = createTempFileName("tzlookup");
+ String tzAliasFile = createTempFileName("tzalias");
- TzLookupGenerator tzLookupGenerator =
- new TzLookupGenerator(countryZonesFile, zoneTabFile, backwardFile, outputFile);
+ TzLookupGenerator tzLookupGenerator = new TzLookupGenerator(
+ countryZonesFile, zoneTabFile, backwardFile, tzLookupFile, tzAliasFile);
assertFalse(tzLookupGenerator.execute());
- Path outputFilePath = Paths.get(outputFile);
- assertEquals(0, Files.size(outputFilePath));
+ assertFileMissing(tzLookupFile);
+ assertFileMissing(tzAliasFile);
}
@Test
@@ -394,20 +409,51 @@
String badBackwardFile = TestUtils.createFile(tempDir, "THIS IS NOT VALID");
- String outputFile = Files.createTempFile(tempDir, "out", null /* suffix */).toString();
+ String tzLookupFile = createTempFileName("tzlookup");
+ String tzAliasFile = createTempFileName("tzalias");
- TzLookupGenerator tzLookupGenerator =
- new TzLookupGenerator(countryZonesFile, zoneTabFile, badBackwardFile, outputFile);
+ TzLookupGenerator tzLookupGenerator = new TzLookupGenerator(
+ countryZonesFile, zoneTabFile, badBackwardFile, tzLookupFile, tzAliasFile);
assertFalse(tzLookupGenerator.execute());
- Path outputFilePath = Paths.get(outputFile);
- assertEquals(0, Files.size(outputFilePath));
+ assertFileMissing(tzLookupFile);
+ assertFileMissing(tzAliasFile);
}
@Test
- public void usingOldLinksValid() throws Exception {
+ 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);
+
+ TzAliasesFile.TimeZoneAliases.Builder b = TzAliasesFile.TimeZoneAliases
+ .newBuilder()
+ .setIanaVersion(TZDB_VERSION);
+ addLink(b, "GB" /* alternativeId */, "Europe/London" /* preferredId */);
+ assertEquals(b.build(), outputData.timeZoneAliases);
+ }
+
+ @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.
+ // 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"
@@ -441,16 +487,34 @@
Map<String, String> backwardLinks = new HashMap<>();
backwardLinks.put("America/Godthab", "America/Nuuk");
- String tzLookupXml = generateTzLookupXml(country, zoneTabWithNewIds, backwardLinks);
+ OutputData outputData = generateOutputData(country, zoneTabWithNewIds, backwardLinks);
- String expectedOutput = "<id>America/Danmarkshavn</id>\n"
+ 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[] expectedLines = expectedOutput.split("\\n");
- for (String expectedLine : expectedLines) {
- assertContains(tzLookupXml, expectedLine);
+ String[] expectedTzLookupXmlLines = expectedTzLookupOutput.split("\\n");
+ for (String expectedTzLookupXmlLine : expectedTzLookupXmlLines) {
+ assertContains(outputData.tzLookupXml, expectedTzLookupXmlLine);
}
+
+ TzAliasesFile.TimeZoneAliases.Builder b = TzAliasesFile.TimeZoneAliases
+ .newBuilder()
+ .setIanaVersion(TZDB_VERSION);
+ // 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 */);
+ assertEquals(b.build(), outputData.timeZoneAliases);
+ }
+
+ private static void addLink(TzAliasesFile.TimeZoneAliases.Builder builder, String alternativeId,
+ String preferredId) {
+ TzAliasesFile.TimeZoneLink link =
+ TzAliasesFile.TimeZoneLink.newBuilder()
+ .setAlternativeId(alternativeId)
+ .setPreferredId(preferredId)
+ .build();
+ builder.addTimeZoneLink(link);
}
@Test
@@ -499,21 +563,21 @@
@Test
public void everUtc_true() throws Exception {
CountryZonesFile.Country validCountryGb = createValidCountryGb();
- String tzLookupXml = generateTzLookupXml(validCountryGb, createValidZoneTabEntriesGb(),
- createValidBackwardLinks());
+ 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(),
- createValidBackwardLinks());
+ OutputData outputData = generateOutputData(
+ validCountryFr, createValidZoneTabEntriesFr(), createEmptyBackwardLinks());
// Check fr's entry contains everutc="n".
- assertContains(tzLookupXml, "everutc=\"n\"");
+ assertContains(outputData.tzLookupXml, "everutc=\"n\"");
}
@Test
@@ -528,10 +592,10 @@
countryBuilder.setTimeZoneMappings(0, timeZoneMappingBuilder);
CountryZonesFile.Country country = countryBuilder.build();
- String tzLookupXml = generateTzLookupXml(country, createValidZoneTabEntriesFr(),
- createValidBackwardLinks());
+ OutputData outputData = generateOutputData(
+ country, createValidZoneTabEntriesFr(), createEmptyBackwardLinks());
- assertContains(tzLookupXml, "picker=\"n\"");
+ assertContains(outputData.tzLookupXml, "picker=\"n\"");
}
@Test
@@ -546,20 +610,20 @@
countryBuilder.setTimeZoneMappings(0, timeZoneMappingBuilder);
CountryZonesFile.Country country = countryBuilder.build();
- String tzLookupXml = generateTzLookupXml(country, createValidZoneTabEntriesFr(),
- createValidBackwardLinks());
+ 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,
- createValidBackwardLinks());
- String expectedOutput =
+ OutputData outputData = generateOutputData(
+ country, zoneTabEntries, createEmptyBackwardLinks());
+ String expectedTzLookupOutput =
"<id>America/New_York</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"
@@ -589,13 +653,60 @@
+ "<id notafter=\"341402400000\" repl=\"America/Anchorage\">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);
+ }
+
+ TzAliasesFile.TimeZoneAliases.Builder b = TzAliasesFile.TimeZoneAliases
+ .newBuilder()
+ .setIanaVersion(TZDB_VERSION);
+ 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/New_York", "America/Indiana/Marengo");
+ addReplacement(b, 247042800000L, "America/New_York", "America/Indiana/Petersburg");
+ addReplacement(b, 89186400000L, "America/New_York", "America/Indiana/Vevay");
+ addReplacement(b, 688546800000L, "America/Chicago", "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/Phoenix", "America/Boise");
+ addReplacement(b, 436359600000L, "America/Anchorage", "America/Juneau");
+ addReplacement(b, 436356000000L, "America/Anchorage", "America/Yakutat");
+ addReplacement(b, 436363200000L, "America/Anchorage", "America/Nome");
+ addReplacement(b, 1547978400000L, "America/Anchorage", "America/Metlakatla");
+ addReplacement(b, 341402400000L, "America/Anchorage", "America/Sitka");;
+ assertEquals(b.build(), outputData.timeZoneAliases);
+ }
+
+ private static void addReplacement(TzAliasesFile.TimeZoneAliases.Builder builder,
+ long fromMillis, String replacementId, String replacedId) {
+ TzAliasesFile.TimeZoneReplacement replacement =
+ TzAliasesFile.TimeZoneReplacement.newBuilder()
+ .setReplacedId(replacedId)
+ .setReplacementId(replacementId)
+ .setFromMillis(fromMillis)
+ .build();
+ builder.addTimeZoneReplacement(replacement);
+ }
+
+ static class OutputData {
+ final String tzLookupXml;
+ final TzAliasesFile.TimeZoneAliases timeZoneAliases;
+
+ OutputData(String tzLookupXml, TzAliasesFile.TimeZoneAliases timeZoneAliases) {
+ this.tzLookupXml = tzLookupXml;
+ this.timeZoneAliases = timeZoneAliases;
}
}
- private String generateTzLookupXml(CountryZonesFile.Country country,
+ private OutputData generateOutputData(CountryZonesFile.Country country,
List<ZoneTabFile.CountryEntry> zoneTabEntries, Map<String, String> backwardLinks)
throws Exception {
@@ -605,16 +716,23 @@
String zoneTabFile = createZoneTabFile(zoneTabEntries);
String backwardFile = createBackwardFile(backwardLinks);
- String outputFile = Files.createTempFile(tempDir, "out", null /* suffix */).toString();
+ String tzLookupFile = createTempFileName("tzlookup");
+ String tzAliasFile = createTempFileName("tzalias");
- TzLookupGenerator tzLookupGenerator =
- new TzLookupGenerator(countryZonesFile, zoneTabFile, backwardFile, outputFile);
+ TzLookupGenerator tzLookupGenerator = new TzLookupGenerator(
+ countryZonesFile, zoneTabFile, backwardFile, tzLookupFile, tzAliasFile);
assertTrue(tzLookupGenerator.execute());
- Path outputFilePath = Paths.get(outputFile);
- assertTrue(Files.exists(outputFilePath));
+ Path tzLookupFilePath = checkFileExists(tzLookupFile);
+ String tzLookupXml = readFileToString(tzLookupFilePath);
- return readFileToString(outputFilePath);
+ Path tzAliasFilePath = checkFileExists(tzAliasFile);
+ String timeZoneAliasesText = readFileToString(tzAliasFilePath);
+ TzAliasesFile.TimeZoneAliases.Builder timeZoneAliasesBuilder =
+ TzAliasesFile.TimeZoneAliases.newBuilder();
+ TextFormat.merge(timeZoneAliasesText, timeZoneAliasesBuilder);
+
+ return new OutputData(tzLookupXml, timeZoneAliasesBuilder.build());
}
private void generateTzLookupXmlExpectFailure(CountryZonesFile.Country country,
@@ -627,10 +745,11 @@
String zoneTabFile = createZoneTabFile(zoneTabEntries);
String backwardFile = createBackwardFile(backwardLinks);
- String outputFile = Files.createTempFile(tempDir, "out", null /* suffix */).toString();
+ String tzLookupFile = createTempFileName("tzlookup");
+ String tzAliasFile = createTempFileName("tzalias");
- TzLookupGenerator tzLookupGenerator =
- new TzLookupGenerator(countryZonesFile, zoneTabFile, backwardFile, outputFile);
+ TzLookupGenerator tzLookupGenerator = new TzLookupGenerator(
+ countryZonesFile, zoneTabFile, backwardFile, tzLookupFile, tzAliasFile);
assertFalse(tzLookupGenerator.execute());
}
@@ -656,7 +775,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);
}
@@ -854,8 +973,14 @@
}
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 {
@@ -865,10 +990,8 @@
return TestUtils.createFile(tempDir, lines.toArray(new String[0]));
}
- private static Map<String, String> createValidBackwardLinks() {
- Map<String, String> map = new HashMap<>();
- map.put("America/Godthab", "America/Nuuk");
- return map;
+ private static Map<String, String> createEmptyBackwardLinks() {
+ return new HashMap<>();
}
private static Country parseCountry(String text) throws Exception {
@@ -877,4 +1000,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/zonetree/CountryZoneTreeTest.java b/input_tools/android/tzlookup_generator/src/test/java/com/android/libcore/timezone/tzlookup/zonetree/CountryZoneTreeTest.java
index e93c380..3986215 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;
diff --git a/update-tzdata.py b/update-tzdata.py
index f914719..f35beca 100755
--- a/update-tzdata.py
+++ b/update-tzdata.py
@@ -144,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)
@@ -190,18 +191,19 @@
header_string])
-def BuildTzlookup(iana_data_dir):
+def BuildTzlookupAndTzAliases(iana_data_dir, tzalias_dest_dir):
countryzones_source_file = '%s/android/countryzones.txt' % timezone_input_data_dir
tzlookup_dest_file = '%s/android/tzlookup.xml' % timezone_output_data_dir
+ tzaliases_dest_file = '%s/tzaliases.txt' % tzalias_dest_dir
- print('Calling TzLookupGenerator to create tzlookup.xml...')
+ print('Calling TzLookupGenerator to create tzlookup.xml / tzaliases.txt...')
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, backward_file,
- tzlookup_dest_file])
+ tzlookup_dest_file, tzaliases_dest_file])
def BuildTelephonylookup():
@@ -250,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
@@ -268,7 +271,11 @@
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)
+
+ tzaliases_out_dir = '%s/android_intermediates' % tmp_dir
+ os.mkdir(tzaliases_out_dir)
+ BuildTzlookupAndTzAliases(iana_data_dir, tzaliases_out_dir)
+
BuildTelephonylookup()
# Create a distro file and version file from the output from prior stages.