Refactoring to support an upcoming change
Making various changes to support a large
upcoming commit.
Most of the changes are in TzLookupGenerator
to split up a large method that is only
going to get larger.
Bug: 72142943
Test: Ran unit tests (see tzlookup_generator/README.android)
Test: Ran update-tzdata.py, tzlookup.xml had not changed
Change-Id: Ia10992c8f83eee9d619f77e4a18ecf8612f2e384
diff --git a/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/CountryZonesFileSupport.java b/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/CountryZonesFileSupport.java
index d4b0a41..2266c92 100644
--- a/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/CountryZonesFileSupport.java
+++ b/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/CountryZonesFileSupport.java
@@ -28,11 +28,11 @@
/**
* A class containing utility methods for details with CountryZonesFile proto objects.
*/
-final class CountryZonesFileSupport {
+public final class CountryZonesFileSupport {
private CountryZonesFileSupport() {}
- static CountryZonesFile.CountryZones parseCountryZonesTextFile(String file)
+ public static CountryZonesFile.CountryZones parseCountryZonesTextFile(String file)
throws IOException, ParseException {
try (BufferedReader fileReader = new BufferedReader(new FileReader(file))) {
CountryZonesFile.CountryZones.Builder builder =
diff --git a/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/Errors.java b/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/Errors.java
index caf2955..2d6ede9 100644
--- a/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/Errors.java
+++ b/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/Errors.java
@@ -25,7 +25,12 @@
*/
final class Errors {
- private boolean isFatal;
+ private final static int LEVEL_WARNING = 1;
+ private final static int LEVEL_ERROR = 2;
+ private final static int LEVEL_FATAL = 3;
+
+ private int level = 0;
+
private final LinkedList<String> scopes = new LinkedList<>();
private final List<String> messages = new ArrayList<>();
@@ -45,11 +50,23 @@
}
void addFatal(String msg) {
- isFatal = true;
+ if (level < LEVEL_FATAL) {
+ level = LEVEL_FATAL;
+ }
+ add(msg);
+ }
+
+ void addError(String msg) {
+ if (level < LEVEL_ERROR) {
+ level = LEVEL_ERROR;
+ }
add(msg);
}
void addWarning(String msg) {
+ if (level < LEVEL_WARNING) {
+ level = LEVEL_WARNING;
+ }
add(msg);
}
@@ -62,8 +79,12 @@
return sb.toString();
}
- boolean isFatal() {
- return isFatal;
+ boolean hasError() {
+ return level >= LEVEL_ERROR;
+ }
+
+ boolean hasFatal() {
+ return level >= LEVEL_FATAL;
}
private void add(String msg) {
diff --git a/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/TzLookupGenerator.java b/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/TzLookupGenerator.java
index ce604fe..bfed095 100644
--- a/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/TzLookupGenerator.java
+++ b/tzlookup_generator/src/main/java/com/android/libcore/timezone/tzlookup/TzLookupGenerator.java
@@ -121,124 +121,11 @@
return false;
}
- // Start constructing the output structure.
- TzLookupFile.TimeZones timeZonesOut = new TzLookupFile.TimeZones(inputIanaVersion);
- TzLookupFile.CountryZones countryZonesOut = new TzLookupFile.CountryZones();
- timeZonesOut.setCountryZones(countryZonesOut);
-
- final long offsetSampleTimeMillis = getSampleOffsetTimeMillisForData(inputIanaVersion);
-
Errors processingErrors = new Errors();
-
- // Process each Country.
- for (CountryZonesFile.Country countryIn : countriesIn) {
- String isoCode = countryIn.getIsoCode();
- processingErrors.pushScope("country=" + isoCode);
- try {
- // Each Country must have >= 1 time zone.
- List<CountryZonesFile.TimeZoneMapping> timeZonesIn =
- countryIn.getTimeZoneMappingsList();
- if (timeZonesIn.isEmpty()) {
- processingErrors.addFatal("No time zones");
- continue;
- }
-
- // Look for duplicate time zone IDs.
- List<String> countryTimeZoneIds = CountryZonesFileSupport.extractIds(timeZonesIn);
- if (!Utils.allUnique(countryTimeZoneIds)) {
- processingErrors.addFatal("country's zones=" + countryTimeZoneIds
- + " contains duplicates");
- }
-
- // Each Country needs a default time zone ID (but we can guess in some cases).
- String defaultTimeZoneId;
- if (countryIn.hasDefaultTimeZoneId()) {
- defaultTimeZoneId = countryIn.getDefaultTimeZoneId();
- if (!validTimeZoneId(defaultTimeZoneId)) {
- processingErrors.addFatal(
- "Default time zone ID " + defaultTimeZoneId + " is not valid");
- continue;
- }
- } else {
- if (timeZonesIn.size() > 1) {
- processingErrors.addFatal(
- "To pick a default time zone there must be a single offset group");
- continue;
- }
- defaultTimeZoneId = timeZonesIn.get(0).getId();
- }
-
- // Validate the default.
- if (!countryTimeZoneIds.contains(defaultTimeZoneId)) {
- processingErrors.addFatal("defaultTimeZoneId=" + defaultTimeZoneId
- + " is not one of the country's zones=" + countryTimeZoneIds);
- }
-
- // Work out the hint for whether the country uses a zero offset from UTC.
- // We don't care about historical use of UTC (e.g. parts of Europe like France prior
- // to WW2) so we start looking at the beginning of "this year".
- long startTimeMillis = getYearStartTimeMillisForData(inputIanaVersion);
- boolean everUsesUtc = anyZonesUseUtc(countryTimeZoneIds, startTimeMillis);
-
- // Add the country to the output structure.
- TzLookupFile.Country countryOut =
- new TzLookupFile.Country(isoCode, defaultTimeZoneId, everUsesUtc);
- countryZonesOut.addCountry(countryOut);
-
- // Validate the country information against the equivalent information in zone.tab.
- processingErrors.pushScope("zone.tab comparison");
- try {
- List<String> zoneTabCountryTimeZoneIds =
- zoneTabMapping.get(isoCode.toUpperCase());
- if (zoneTabCountryTimeZoneIds == null) {
- processingErrors.addFatal("Unknown country=" + isoCode);
- continue;
- }
-
- // Look for unexpected duplicate time zone IDs in zone.tab
- if (!Utils.allUnique(zoneTabCountryTimeZoneIds)) {
- processingErrors.addFatal(
- "Duplicate time zone IDs found:" + zoneTabCountryTimeZoneIds);
- }
-
- if (!Utils.setEquals(zoneTabCountryTimeZoneIds, countryTimeZoneIds)) {
- processingErrors.addFatal("IANA lists " + isoCode
- + " as having zones: " + zoneTabCountryTimeZoneIds
- + ", but countryzones has " + countryTimeZoneIds);
- continue;
- }
- } finally {
- processingErrors.popScope();
- }
-
- // Process each input time zone.
- for (CountryZonesFile.TimeZoneMapping timeZoneIn : timeZonesIn) {
- processingErrors.pushScope(
- "id=" + timeZoneIn.getId() + ", offset=" + timeZoneIn.getUtcOffset()
- + ", shownInPicker=" + timeZoneIn.getShownInPicker());
- try {
- // Validate the offset information in countryzones.
- validateNonDstOffset(offsetSampleTimeMillis, countryIn, timeZoneIn,
- processingErrors);
-
- String timeZoneInId = timeZoneIn.getId();
- boolean shownInPicker = timeZoneIn.getShownInPicker();
- // Add the id mapping and associated metadata.
- TzLookupFile.TimeZoneMapping timeZoneIdOut =
- new TzLookupFile.TimeZoneMapping(timeZoneInId, shownInPicker);
- countryOut.addTimeZoneIdentifier(timeZoneIdOut);
- } finally {
- processingErrors.popScope();
- }
- }
- } finally{
- // End of country processing.
- processingErrors.popScope();
- }
- }
-
- if (!processingErrors.isFatal()) {
- // Write the output structure if there wasn't a fatal error.
+ TzLookupFile.TimeZones timeZonesOut = createOutputTimeZones(
+ inputIanaVersion, zoneTabMapping, countriesIn, processingErrors);
+ if (!processingErrors.hasError()) {
+ // Write the output structure if there wasn't an error.
logInfo("Writing " + outputFile);
try {
TzLookupFile.write(timeZonesOut, outputFile);
@@ -253,12 +140,193 @@
logInfo("Issues:\n" + processingErrors.asString());
}
- return !processingErrors.isFatal();
+ return !processingErrors.hasError();
}
- private boolean anyZonesUseUtc(List<String> countryTimeZoneIds, long startTimeMillis) {
- for (String countryTimeZoneId : countryTimeZoneIds) {
- BasicTimeZone timeZone = (BasicTimeZone) TimeZone.getTimeZone(countryTimeZoneId);
+ private static TzLookupFile.TimeZones createOutputTimeZones(String inputIanaVersion,
+ Map<String, List<String>> zoneTabMapping, List<CountryZonesFile.Country> countriesIn,
+ Errors processingErrors) {
+ // Start constructing the output structure.
+ TzLookupFile.TimeZones timeZonesOut = new TzLookupFile.TimeZones(inputIanaVersion);
+ TzLookupFile.CountryZones countryZonesOut = new TzLookupFile.CountryZones();
+ timeZonesOut.setCountryZones(countryZonesOut);
+
+ // The time use when sampling the offsets for a zone.
+ final long offsetSampleTimeMillis = getSampleOffsetTimeMillisForData(inputIanaVersion);
+
+ // The start time to use when working out whether a zone has used UTC.
+ // We don't care about historical use of UTC (e.g. parts of Europe like France prior
+ // to WW2) so we start looking at the beginning of "this year".
+ long everUseUtcStartTimeMillis = getYearStartTimeMillisForData(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");
+ // No point in continuing.
+ continue;
+ }
+
+ TzLookupFile.Country countryOut = 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) {
+ continue;
+ }
+ countryZonesOut.addCountry(countryOut);
+ }
+ return timeZonesOut;
+ }
+
+ private static TzLookupFile.Country processCountry(long offsetSampleTimeMillis,
+ long everUseUtcStartTimeMillis, CountryZonesFile.Country countryIn,
+ List<String> zoneTabCountryTimeZoneIds,
+ Errors processingErrors) {
+ String isoCode = countryIn.getIsoCode();
+ processingErrors.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");
+ // No point in continuing.
+ return null;
+ }
+
+ // Look for duplicate time zone IDs.
+ List<String> countryTimeZoneIds = CountryZonesFileSupport.extractIds(timeZonesIn);
+ if (!Utils.allUnique(countryTimeZoneIds)) {
+ processingErrors.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);
+ if (processingErrors.hasError()) {
+ // No point in continuing.
+ return null;
+ }
+
+ // Validate the default.
+ if (!countryTimeZoneIds.contains(defaultTimeZoneId)) {
+ processingErrors.addError("defaultTimeZoneId=" + defaultTimeZoneId
+ + " is not one of the country's zones=" + countryTimeZoneIds);
+ // No point in continuing.
+ return null;
+ }
+
+ // Validate the other zone IDs.
+ for (String countryTimeZoneId : countryTimeZoneIds) {
+ if (invalidTimeZoneId(countryTimeZoneId)) {
+ processingErrors.addError("countryTimeZoneId=" + countryTimeZoneId
+ + " is not a valid zone ID");
+ }
+ if (processingErrors.hasError()) {
+ // No point in continuing.
+ return null;
+ }
+ }
+
+ // 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");
+ try {
+ // Look for unexpected duplicate time zone IDs in zone.tab
+ if (!Utils.allUnique(zoneTabCountryTimeZoneIds)) {
+ processingErrors.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);
+ // No point in continuing.
+ return null;
+ }
+ } finally {
+ processingErrors.popScope();
+ }
+
+ // Add the country to the output structure.
+ TzLookupFile.Country countryOut =
+ new TzLookupFile.Country(isoCode, defaultTimeZoneId, everUsesUtc);
+
+ // Process each input time zone.
+ for (CountryZonesFile.TimeZoneMapping timeZoneIn : timeZonesIn) {
+ processingErrors.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();
+
+ // Add the id mapping and associated metadata.
+ TzLookupFile.TimeZoneMapping timeZoneIdOut =
+ new TzLookupFile.TimeZoneMapping(timeZoneInId, shownInPicker);
+ countryOut.addTimeZoneIdentifier(timeZoneIdOut);
+ } finally {
+ processingErrors.popScope();
+ }
+ }
+ return countryOut;
+ } finally{
+ // End of country processing.
+ processingErrors.popScope();
+ }
+ }
+
+ /**
+ * Determines the default zone ID for the country.
+ */
+ private static String determineCountryDefaultZoneId(
+ CountryZonesFile.Country countryIn, Errors processingErrorsOut) {
+ List<CountryZonesFile.TimeZoneMapping> timeZonesIn = countryIn.getTimeZoneMappingsList();
+ String defaultTimeZoneId;
+ if (countryIn.hasDefaultTimeZoneId()) {
+ defaultTimeZoneId = countryIn.getDefaultTimeZoneId();
+ if (invalidTimeZoneId(defaultTimeZoneId)) {
+ processingErrorsOut.addError(
+ "Default time zone ID " + defaultTimeZoneId + " is not valid");
+ // No point in continuing.
+ return null;
+ }
+ } else {
+ if (timeZonesIn.size() > 1) {
+ processingErrorsOut.addError(
+ "To pick a default time zone there must be a single offset group");
+ // No point in continuing.
+ return null;
+ }
+ defaultTimeZoneId = timeZonesIn.get(0).getId();
+ }
+ return defaultTimeZoneId;
+ }
+
+ /**
+ * Returns true if any of the zones use UTC after the time specified.
+ */
+ private static boolean anyZonesUseUtc(List<String> timeZoneIds, long startTimeMillis) {
+ for (String timeZoneId : timeZoneIds) {
+ BasicTimeZone timeZone = (BasicTimeZone) TimeZone.getTimeZone(timeZoneId);
TimeZoneRule[] rules = timeZone.getTimeZoneRules(startTimeMillis);
for (TimeZoneRule rule : rules) {
int utcOffset = rule.getRawOffset() + rule.getDSTSavings();
@@ -301,9 +369,9 @@
return calendar;
}
- private static boolean validTimeZoneId(String timeZoneId) {
+ private static boolean invalidTimeZoneId(String timeZoneId) {
TimeZone zone = TimeZone.getTimeZone(timeZoneId);
- return !zone.getID().equals(TimeZone.UNKNOWN_ZONE_ID);
+ return !(zone instanceof BasicTimeZone) || zone.getID().equals(TimeZone.UNKNOWN_ZONE_ID);
}
private static void validateNonDstOffset(long offsetSampleTimeMillis,
@@ -325,7 +393,7 @@
}
String timeZoneIdIn = timeZoneIn.getId();
- if (!validTimeZoneId(timeZoneIdIn)) {
+ if (invalidTimeZoneId(timeZoneIdIn)) {
errors.addFatal("Time zone ID=" + timeZoneIdIn + " is not valid");
return;
}
diff --git a/tzlookup_generator/src/test/java/com/android/libcore/timezone/tzlookup/ErrorsTest.java b/tzlookup_generator/src/test/java/com/android/libcore/timezone/tzlookup/ErrorsTest.java
index 0b5240b..a7874b9 100644
--- a/tzlookup_generator/src/test/java/com/android/libcore/timezone/tzlookup/ErrorsTest.java
+++ b/tzlookup_generator/src/test/java/com/android/libcore/timezone/tzlookup/ErrorsTest.java
@@ -27,11 +27,28 @@
public void warnings() {
Errors errors = new Errors();
assertTrue(errors.isEmpty());
- assertFalse(errors.isFatal());
+ assertFalse(errors.hasError());
+ assertFalse(errors.hasFatal());
errors.addWarning("Hello");
assertFalse(errors.isEmpty());
- assertFalse(errors.isFatal());
+ assertFalse(errors.hasError());
+ assertFalse(errors.hasFatal());
+
+ TestUtils.assertContains(errors.asString(), "Hello");
+ }
+
+ @Test
+ public void error() {
+ Errors errors = new Errors();
+ assertTrue(errors.isEmpty());
+ assertFalse(errors.hasError());
+ assertFalse(errors.hasFatal());
+
+ errors.addError("Hello");
+ assertFalse(errors.isEmpty());
+ assertTrue(errors.hasError());
+ assertFalse(errors.hasFatal());
TestUtils.assertContains(errors.asString(), "Hello");
}
@@ -40,11 +57,13 @@
public void fatal() {
Errors errors = new Errors();
assertTrue(errors.isEmpty());
- assertFalse(errors.isFatal());
+ assertFalse(errors.hasError());
+ assertFalse(errors.hasFatal());
errors.addFatal("Hello");
assertFalse(errors.isEmpty());
- assertTrue(errors.isFatal());
+ assertTrue(errors.hasError());
+ assertTrue(errors.hasFatal());
TestUtils.assertContains(errors.asString(), "Hello");
}
@@ -53,16 +72,16 @@
public void scope() {
Errors errors = new Errors();
- errors.addFatal("Hello");
+ errors.addWarning("Hello");
errors.pushScope("Monty Python");
- errors.addFatal("John Cleese");
+ errors.addError("John Cleese");
errors.pushScope("Holy grail");
errors.addFatal("Silly place");
errors.popScope();
- errors.addFatal("Michael Palin");
+ errors.addError("Michael Palin");
errors.pushScope("Parrot sketch");
errors.addFatal("Fjords");