1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.libcore.timezone.tzlookup; 18 19 import static com.android.libcore.timezone.countryzones.proto.CountryZonesFile.Country; 20 import static com.android.libcore.timezone.testing.TestUtils.assertAbsent; 21 import static com.android.libcore.timezone.testing.TestUtils.assertContains; 22 import static com.android.libcore.timezone.testing.TestUtils.createFile; 23 import static org.junit.Assert.assertEquals; 24 import static org.junit.Assert.assertFalse; 25 import static org.junit.Assert.assertTrue; 26 27 import com.android.libcore.timezone.countryzones.proto.CountryZonesFile; 28 import com.android.libcore.timezone.testing.TestUtils; 29 import com.android.timezone.tzids.proto.TzIdsProto; 30 import com.google.protobuf.TextFormat; 31 import com.ibm.icu.util.TimeZone; 32 33 import org.junit.After; 34 import org.junit.Before; 35 import org.junit.Test; 36 37 import java.io.IOException; 38 import java.nio.charset.StandardCharsets; 39 import java.nio.file.Files; 40 import java.nio.file.Path; 41 import java.nio.file.Paths; 42 import java.util.ArrayList; 43 import java.util.Arrays; 44 import java.util.HashMap; 45 import java.util.List; 46 import java.util.Map; 47 import java.util.stream.Collectors; 48 49 public class TzLookupGeneratorTest { 50 51 private static final String INVALID_TIME_ZONE_ID = "NOT_A_VALID_ID"; 52 private static final String TZDB_VERSION = TimeZone.getTZDataVersion(); 53 54 private Path tempDir; 55 56 @Before setUp()57 public void setUp() throws Exception { 58 tempDir = Files.createTempDirectory("TzLookupGeneratorTest"); 59 } 60 61 @After tearDown()62 public void tearDown() throws Exception { 63 TestUtils.deleteDir(tempDir); 64 } 65 66 @Test invalidCountryZonesFile()67 public void invalidCountryZonesFile() throws Exception { 68 String countryZonesFile = createFile(tempDir, "THIS IS NOT A VALID FILE"); 69 List<ZoneTabFile.CountryEntry> gbZoneTabEntries = createValidZoneTabEntriesGb(); 70 String zoneTabFile = createZoneTabFile(gbZoneTabEntries); 71 String tzLookupFile = createTempFileName("tzlookup"); 72 String tzIdsFile = createTempFileName("tzids"); 73 74 TzLookupGenerator tzLookupGenerator = new TzLookupGenerator( 75 countryZonesFile, zoneTabFile, tzLookupFile, tzIdsFile); 76 assertFalse(tzLookupGenerator.execute(false /* validateAllIanaIdsAreMapped */)); 77 } 78 79 @Test invalidRulesVersion()80 public void invalidRulesVersion() throws Exception { 81 CountryZonesFile.Country validGb = createValidCountryGb(); 82 83 // The IANA version won't match ICU's IANA version so we should see a failure. 84 CountryZonesFile.CountryZones badIanaVersionCountryZones = 85 createValidCountryZones(validGb).toBuilder().setIanaVersion("2001a").build(); 86 String countryZonesFile = createCountryZonesFile(badIanaVersionCountryZones); 87 88 List<ZoneTabFile.CountryEntry> gbZoneTabEntries = createValidZoneTabEntriesGb(); 89 String zoneTabFile = createZoneTabFile(gbZoneTabEntries); 90 91 String tzLookupFile = createTempFileName("tzlookup"); 92 String tzIdsFile = createTempFileName("tzids"); 93 94 TzLookupGenerator tzLookupGenerator = new TzLookupGenerator( 95 countryZonesFile, zoneTabFile, tzLookupFile, tzIdsFile); 96 assertFalse(tzLookupGenerator.execute(false /* validateAllIanaIdsAreMapped */)); 97 98 assertFileMissing(tzLookupFile); 99 assertFileMissing(tzIdsFile); 100 } 101 102 @Test countryWithNoTimeZoneMappings()103 public void countryWithNoTimeZoneMappings() throws Exception { 104 // No zones found! 105 CountryZonesFile.Country gbWithoutZones = 106 createValidCountryGb().toBuilder().clearTimeZoneMappings().build(); 107 CountryZonesFile.CountryZones countryZones = createValidCountryZones(gbWithoutZones); 108 String countryZonesFile = createCountryZonesFile(countryZones); 109 110 String zoneTabFile = createZoneTabFile(createValidZoneTabEntriesGb()); 111 112 String tzLookupFile = createTempFileName("tzlookup"); 113 String tzIdsFile = createTempFileName("tzids"); 114 115 TzLookupGenerator tzLookupGenerator = new TzLookupGenerator( 116 countryZonesFile, zoneTabFile, tzLookupFile, tzIdsFile); 117 assertFalse(tzLookupGenerator.execute(false /* validateAllIanaIdsAreMapped */)); 118 119 assertFileMissing(tzLookupFile); 120 assertFileMissing(tzIdsFile); 121 } 122 123 @Test countryWithDuplicateTimeZoneMappings()124 public void countryWithDuplicateTimeZoneMappings() throws Exception { 125 // Duplicate zones found! 126 CountryZonesFile.Country validCountryGb = createValidCountryGb(); 127 CountryZonesFile.Country gbWithDuplicateZones = 128 validCountryGb.toBuilder() 129 .setDefaultTimeZoneId(validCountryGb.getTimeZoneMappings(0).getId()) 130 .addAllTimeZoneMappings(validCountryGb.getTimeZoneMappingsList()) 131 .build(); 132 CountryZonesFile.CountryZones countryZones = 133 createValidCountryZones(gbWithDuplicateZones); 134 String countryZonesFile = createCountryZonesFile(countryZones); 135 136 String zoneTabFile = createZoneTabFile(createValidZoneTabEntriesGb()); 137 138 String tzLookupFile = createTempFileName("tzlookup"); 139 String tzIdsFile = createTempFileName("tzids"); 140 141 TzLookupGenerator tzLookupGenerator = new TzLookupGenerator( 142 countryZonesFile, zoneTabFile, tzLookupFile, tzIdsFile); 143 assertFalse(tzLookupGenerator.execute(false /* validateAllIanaIdsAreMapped */)); 144 145 assertFileMissing(tzLookupFile); 146 assertFileMissing(tzIdsFile); 147 } 148 149 @Test badDefaultId()150 public void badDefaultId() throws Exception { 151 // Set an invalid default. 152 CountryZonesFile.Country validGb = 153 createValidCountryGb().toBuilder() 154 .setDefaultTimeZoneId("NOT_A_TIMEZONE_ID") 155 .build(); 156 CountryZonesFile.CountryZones gbCountryZones = createValidCountryZones(validGb); 157 String countryZonesFile = createCountryZonesFile(gbCountryZones); 158 159 List<ZoneTabFile.CountryEntry> gbZoneTabEntries = createValidZoneTabEntriesGb(); 160 String zoneTabFile = createZoneTabFile(gbZoneTabEntries); 161 162 String tzLookupFile = createTempFileName("tzlookup"); 163 String tzIdsFile = createTempFileName("tzids"); 164 165 TzLookupGenerator tzLookupGenerator = new TzLookupGenerator( 166 countryZonesFile, zoneTabFile, tzLookupFile, tzIdsFile); 167 assertFalse(tzLookupGenerator.execute(false /* validateAllIanaIdsAreMapped */)); 168 169 assertFileMissing(tzLookupFile); 170 assertFileMissing(tzIdsFile); 171 } 172 173 @Test shouldFail_whenExplicitDefaultIdBelongsToOtherCountry()174 public void shouldFail_whenExplicitDefaultIdBelongsToOtherCountry() throws Exception { 175 // Set a valid default, but to one that isn't referenced by "gb". 176 CountryZonesFile.Country validGb = createValidCountryGb().toBuilder() 177 .setDefaultTimeZoneId(createValidCountryFr().getTimeZoneMappings(0).getId()) 178 .build(); 179 CountryZonesFile.CountryZones gbCountryZones = createValidCountryZones(validGb); 180 String countryZonesFile = createCountryZonesFile(gbCountryZones); 181 182 List<ZoneTabFile.CountryEntry> gbZoneTabEntries = createValidZoneTabEntriesGb(); 183 String zoneTabFile = createZoneTabFile(gbZoneTabEntries); 184 185 String tzLookupFile = createTempFileName("tzlookup"); 186 String tzIdsFile = createTempFileName("tzids"); 187 188 TzLookupGenerator tzLookupGenerator = new TzLookupGenerator( 189 countryZonesFile, zoneTabFile, tzLookupFile, tzIdsFile); 190 assertFalse(tzLookupGenerator.execute(false /* validateAllIanaIdsAreMapped */)); 191 192 assertFileMissing(tzLookupFile); 193 assertFileMissing(tzIdsFile); 194 } 195 196 @Test calculatedDefaultZone()197 public void calculatedDefaultZone() throws Exception { 198 // Ensure there's no explicit default for "gb" and there's one zone. 199 CountryZonesFile.Country validCountryGb = createValidCountryGb(); 200 assertEquals(1, validCountryGb.getTimeZoneMappingsCount()); 201 202 String gbTimeZoneId = validCountryGb.getTimeZoneMappings(0).getId(); 203 CountryZonesFile.Country gbWithoutDefault = validCountryGb.toBuilder() 204 .clearDefaultTimeZoneId().build(); 205 List<ZoneTabFile.CountryEntry> gbZoneTabEntries = createValidZoneTabEntriesGb(); 206 207 OutputData outputData = 208 generateOutputData(gbWithoutDefault, gbZoneTabEntries); 209 210 // Check gb's time zone was defaulted. 211 assertContains(outputData.tzLookupXml, "code=\"gb\" default=\"" + gbTimeZoneId + "\""); 212 } 213 214 @Test explicitDefaultZone()215 public void explicitDefaultZone() throws Exception { 216 // Ensure there's an explicit default for "gb" and there's one zone. 217 CountryZonesFile.Country validCountryGb = createValidCountryGb(); 218 String gbTimeZoneId = validCountryGb.getTimeZoneMappings(0).getId(); 219 CountryZonesFile.Country gbWithExplicitDefaultTimeZone = 220 validCountryGb.toBuilder() 221 .setDefaultTimeZoneId(gbTimeZoneId) 222 .build(); 223 List<ZoneTabFile.CountryEntry> gbZoneTabEntries = createValidZoneTabEntriesGb(); 224 225 OutputData outputData = generateOutputData( 226 gbWithExplicitDefaultTimeZone, gbZoneTabEntries); 227 228 // Check gb's time zone was defaulted. 229 assertContains(outputData.tzLookupXml, "code=\"gb\" default=\"" + gbTimeZoneId + "\""); 230 } 231 232 @Test countryZonesContainsNonLowercaseIsoCode()233 public void countryZonesContainsNonLowercaseIsoCode() throws Exception { 234 CountryZonesFile.Country validCountry = createValidCountryGb(); 235 CountryZonesFile.Country invalidCountry = 236 createValidCountryGb().toBuilder().setIsoCode("Gb").build(); 237 238 CountryZonesFile.CountryZones countryZones = 239 createValidCountryZones(validCountry, invalidCountry); 240 String countryZonesFile = createCountryZonesFile(countryZones); 241 242 List<ZoneTabFile.CountryEntry> gbZoneTabEntries = createValidZoneTabEntriesGb(); 243 String zoneTabFile = createZoneTabFile(gbZoneTabEntries); 244 245 String tzLookupFile = createTempFileName("tzlookup"); 246 String tzIdsFile = createTempFileName("tzids"); 247 248 TzLookupGenerator tzLookupGenerator = new TzLookupGenerator( 249 countryZonesFile, zoneTabFile, tzLookupFile, tzIdsFile); 250 assertFalse(tzLookupGenerator.execute(false /* validateAllIanaIdsAreMapped */)); 251 252 assertFileMissing(tzLookupFile); 253 assertFileMissing(tzIdsFile); 254 } 255 256 @Test countryZonesContainsDuplicate()257 public void countryZonesContainsDuplicate() throws Exception { 258 CountryZonesFile.Country validGb = createValidCountryGb(); 259 260 // The file contains "gb" twice. 261 CountryZonesFile.CountryZones duplicateGbData = 262 createValidCountryZones(validGb, validGb); 263 String countryZonesFile = createCountryZonesFile(duplicateGbData); 264 265 List<ZoneTabFile.CountryEntry> gbZoneTabEntries = createValidZoneTabEntriesGb(); 266 String zoneTabFile = createZoneTabFile(gbZoneTabEntries); 267 268 String tzLookupFile = createTempFileName("tzlookup"); 269 String tzIdsFile = createTempFileName("tzids"); 270 271 TzLookupGenerator tzLookupGenerator = new TzLookupGenerator( 272 countryZonesFile, zoneTabFile, tzLookupFile, tzIdsFile); 273 assertFalse(tzLookupGenerator.execute(false /* validateAllIanaIdsAreMapped */)); 274 275 assertFileMissing(tzLookupFile); 276 assertFileMissing(tzIdsFile); 277 } 278 279 @Test shouldNotFail_whenCountryZonesAndZoneTabCountryMismatch()280 public void shouldNotFail_whenCountryZonesAndZoneTabCountryMismatch() throws Exception { 281 // The two input files contain non-identical country ISO codes. 282 CountryZonesFile.CountryZones countryZones = 283 createValidCountryZones(createValidCountryGb(), createValidCountryFr()); 284 String countryZonesFile = createCountryZonesFile(countryZones); 285 286 String zoneTabFile = 287 createZoneTabFile(createValidZoneTabEntriesFr(), createValidZoneTabEntriesUs()); 288 String tzLookupFile = createTempFileName("tzlookup"); 289 String tzIdsFile = createTempFileName("tzids"); 290 291 TzLookupGenerator tzLookupGenerator = new TzLookupGenerator( 292 countryZonesFile, zoneTabFile, tzLookupFile, tzIdsFile); 293 assertTrue(tzLookupGenerator.execute(false /* validateAllIanaIdsAreMapped */)); 294 } 295 296 @Test shouldNotFail_whenCountryZonesAndZoneTabDisagreeOnZones()297 public void shouldNotFail_whenCountryZonesAndZoneTabDisagreeOnZones() throws Exception { 298 CountryZonesFile.Country gbWithWrongZones = createValidCountryGb(); 299 CountryZonesFile.CountryZones countryZones = createValidCountryZones(gbWithWrongZones); 300 String countryZonesFile = createCountryZonesFile(countryZones); 301 302 String zoneTabFile = createZoneTabFile( 303 List.of(new ZoneTabFile.CountryEntry("GB", "Europe/Paris"))); 304 String tzLookupFile = createTempFileName("tzlookup"); 305 String tzIdsFile = createTempFileName("tzids"); 306 307 TzLookupGenerator tzLookupGenerator = new TzLookupGenerator( 308 countryZonesFile, zoneTabFile, tzLookupFile, tzIdsFile); 309 310 assertTrue(tzLookupGenerator.execute(false /* validateAllIanaIdsAreMapped */)); 311 } 312 313 @Test duplicateEntriesInZoneTab()314 public void duplicateEntriesInZoneTab() throws Exception { 315 CountryZonesFile.Country validGbCountry = createValidCountryGb(); 316 CountryZonesFile.CountryZones countryZones = createValidCountryZones(validGbCountry); 317 String countryZonesFile = createCountryZonesFile(countryZones); 318 319 String zoneTabFileWithDupes = createZoneTabFile( 320 createValidZoneTabEntriesGb(), createValidZoneTabEntriesGb()); 321 322 String tzLookupFile = createTempFileName("tzlookup"); 323 String tzIdsFile = createTempFileName("tzids"); 324 325 TzLookupGenerator tzLookupGenerator = new TzLookupGenerator( 326 countryZonesFile, zoneTabFileWithDupes, tzLookupFile, tzIdsFile); 327 assertFalse(tzLookupGenerator.execute(false /* validateAllIanaIdsAreMapped */)); 328 329 assertFileMissing(tzLookupFile); 330 assertFileMissing(tzIdsFile); 331 } 332 333 @Test incorrectOffset()334 public void incorrectOffset() throws Exception { 335 CountryZonesFile.Country validGbCountry = createValidCountryGb(); 336 CountryZonesFile.Country.Builder gbWithWrongOffsetBuilder = validGbCountry.toBuilder(); 337 gbWithWrongOffsetBuilder.getTimeZoneMappingsBuilder(0).setUtcOffset("20:00"); 338 CountryZonesFile.Country gbWithWrongOffset = gbWithWrongOffsetBuilder.build(); 339 340 CountryZonesFile.CountryZones countryZones = createValidCountryZones(gbWithWrongOffset); 341 String countryZonesFile = createCountryZonesFile(countryZones); 342 343 String zoneTabFile = createZoneTabFile(createValidZoneTabEntriesGb()); 344 345 String tzLookupFile = createTempFileName("tzlookup"); 346 String tzIdsFile = createTempFileName("tzids"); 347 348 TzLookupGenerator tzLookupGenerator = new TzLookupGenerator( 349 countryZonesFile, zoneTabFile, tzLookupFile, tzIdsFile); 350 assertFalse(tzLookupGenerator.execute(false /* validateAllIanaIdsAreMapped */)); 351 352 assertFileMissing(tzLookupFile); 353 assertFileMissing(tzIdsFile); 354 } 355 356 @Test badTimeZoneMappingId()357 public void badTimeZoneMappingId() throws Exception { 358 CountryZonesFile.Country validGbCountry = createValidCountryGb(); 359 CountryZonesFile.Country.Builder gbWithBadIdBuilder = validGbCountry.toBuilder(); 360 gbWithBadIdBuilder.setDefaultTimeZoneId(validGbCountry.getTimeZoneMappings(0).getId()) 361 .addTimeZoneMappingsBuilder().setId(INVALID_TIME_ZONE_ID).setUtcOffset("00:00"); 362 CountryZonesFile.Country gbWithBadId = gbWithBadIdBuilder.build(); 363 364 CountryZonesFile.CountryZones countryZones = createValidCountryZones(gbWithBadId); 365 String countryZonesFile = createCountryZonesFile(countryZones); 366 367 List<ZoneTabFile.CountryEntry> zoneTabEntriesWithBadId = 368 new ArrayList<>(createValidZoneTabEntriesGb()); 369 zoneTabEntriesWithBadId.add(new ZoneTabFile.CountryEntry("GB", INVALID_TIME_ZONE_ID)); 370 String zoneTabFile = createZoneTabFile(zoneTabEntriesWithBadId); 371 372 String tzLookupFile = createTempFileName("tzlookup"); 373 String tzIdsFile = createTempFileName("tzids"); 374 375 TzLookupGenerator tzLookupGenerator = new TzLookupGenerator( 376 countryZonesFile, zoneTabFile, tzLookupFile, tzIdsFile); 377 assertFalse(tzLookupGenerator.execute(false /* validateAllIanaIdsAreMapped */)); 378 379 assertFileMissing(tzLookupFile); 380 assertFileMissing(tzIdsFile); 381 } 382 383 @Test usingOldIdsInCountryTextIsValid()384 public void usingOldIdsInCountryTextIsValid() throws Exception { 385 // This simulates a case where America/Godthab has been superseded by America/Nuuk in IANA 386 // data, but Android wants to continue using America/Godthab. This is signaled as deliberate 387 // through the use of the alternativeIds in countryzones.txt. 388 String countryZonesWithOldIdText = 389 "isoCode:\"gl\"\n" 390 + "defaultTimeZoneId:\"America/Godthab\"\n" 391 + "timeZoneMappings:<\n" 392 + " utcOffset:\"0:00\"\n" 393 + " id:\"America/Danmarkshavn\"\n" 394 + " priority:1\n" 395 + ">\n" 396 + "\n" 397 + "timeZoneMappings:<\n" 398 + " utcOffset:\"-2:00\"\n" 399 + " id:\"America/Godthab\"\n" 400 + " alternativeIds: \"America/Nuuk\"\n" 401 + " priority: 17\n" 402 + ">\n" 403 + "\n" 404 + "timeZoneMappings:<\n" 405 + " utcOffset:\"-2:00\"\n" 406 + " id:\"America/Scoresbysund\"\n" 407 + " priority:1\n" 408 + ">\n" 409 + "\n" 410 + "timeZoneMappings:<\n" 411 + " utcOffset:\"-4:00\"\n" 412 + " id:\"America/Thule\"\n" 413 + " priority:1\n" 414 + ">\n"; 415 Country country = parseCountry(countryZonesWithOldIdText); 416 List<ZoneTabFile.CountryEntry> zoneTabWithNewIds = Arrays.asList( 417 new ZoneTabFile.CountryEntry("GL", "America/Nuuk"), 418 new ZoneTabFile.CountryEntry("GL", "America/Danmarkshavn"), 419 new ZoneTabFile.CountryEntry("GL", "America/Scoresbysund"), 420 new ZoneTabFile.CountryEntry("GL", "America/Thule") 421 ); 422 423 OutputData outputData = generateOutputData(country, zoneTabWithNewIds); 424 425 String expectedTzLookupOutput = "<id>America/Danmarkshavn</id>\n" 426 + "<id notafter=\"1711846800000\" repl=\"America/Godthab\">" 427 + "America/Scoresbysund</id>\n" 428 + "<id alts=\"America/Nuuk\">America/Godthab</id>\n" 429 + "<id>America/Thule</id>\n"; 430 String[] expectedTzLookupXmlLines = expectedTzLookupOutput.split("\\n"); 431 for (String expectedTzLookupXmlLine : expectedTzLookupXmlLines) { 432 assertContains(outputData.tzLookupXml, expectedTzLookupXmlLine); 433 } 434 435 TzIdsProto.TimeZoneIds.Builder tzIdsBuilder = TzIdsProto.TimeZoneIds 436 .newBuilder() 437 .setIanaVersion(TZDB_VERSION); 438 439 TzIdsProto.CountryMapping.Builder b = TzIdsProto.CountryMapping.newBuilder() 440 .setIsoCode("gl") 441 .addTimeZoneIds("America/Danmarkshavn") 442 .addTimeZoneIds("America/Godthab") 443 .addTimeZoneIds("America/Thule"); 444 445 // Because Android lists America/Nuuk in alternativeIds in countryzones.txt, the link will 446 // be reversed from the usual. 447 addLink(b, "America/Nuuk" /* alternativeId */, "America/Godthab" /* preferredId */); 448 addReplacement(b, 1711846800000L, "America/Godthab", "America/Scoresbysund"); 449 450 tzIdsBuilder.addCountryMappings(b); 451 assertEquals(tzIdsBuilder.build(), outputData.timeZoneIds); 452 } 453 454 @Test alternativeIds_shouldBeUsedToFillAltsSection()455 public void alternativeIds_shouldBeUsedToFillAltsSection() throws Exception { 456 Country country = Country.newBuilder() 457 .setIsoCode("gb") 458 .addTimeZoneMappings(CountryZonesFile.TimeZoneMapping.newBuilder() 459 .setId("Europe/London") 460 .setUtcOffset("0:00") 461 .addAlternativeIds("Europe/Belfast") 462 .addAlternativeIds("GB") 463 .addAlternativeIds("GB-Eire")) 464 .build(); 465 466 List<ZoneTabFile.CountryEntry> zoneTabEntries = List.of( 467 new ZoneTabFile.CountryEntry("GB", "Europe/London")); 468 469 OutputData outputData = generateOutputData(country, zoneTabEntries); 470 471 String expectedTzlookupOutput = "<country code=\"gb\" default=\"Europe/London\" " 472 + "everutc=\"y\">\n" 473 + "<id alts=\"Europe/Belfast,GB,GB-Eire\">Europe/London</id>\n" 474 + "</country>"; 475 476 assertContains(outputData.tzLookupXml, expectedTzlookupOutput.split("\n")); 477 478 TzIdsProto.TimeZoneIds.Builder tzIdsBuilder = TzIdsProto.TimeZoneIds 479 .newBuilder() 480 .setIanaVersion(TZDB_VERSION); 481 482 TzIdsProto.CountryMapping.Builder b = TzIdsProto.CountryMapping.newBuilder() 483 .setIsoCode("gb") 484 .addTimeZoneIds("Europe/London"); 485 486 addLink(b, "Europe/Belfast" /* alternativeId */, "Europe/London" /* preferredId */); 487 addLink(b, "GB" /* alternativeId */, "Europe/London" /* preferredId */); 488 addLink(b, "GB-Eire" /* alternativeId */, "Europe/London" /* preferredId */); 489 490 tzIdsBuilder.addCountryMappings(b); 491 assertEquals(tzIdsBuilder.build(), outputData.timeZoneIds); 492 } 493 addLink(TzIdsProto.CountryMapping.Builder builder, String alternativeId, String preferredId)494 private static void addLink(TzIdsProto.CountryMapping.Builder builder, String alternativeId, 495 String preferredId) { 496 TzIdsProto.TimeZoneLink link = 497 TzIdsProto.TimeZoneLink.newBuilder() 498 .setAlternativeId(alternativeId) 499 .setPreferredId(preferredId) 500 .build(); 501 builder.addTimeZoneLinks(link); 502 } 503 504 @Test everUtc_true()505 public void everUtc_true() throws Exception { 506 CountryZonesFile.Country validCountryGb = createValidCountryGb(); 507 OutputData outputData = generateOutputData( 508 validCountryGb, createValidZoneTabEntriesGb()); 509 510 // Check gb's entry contains everutc="y". 511 assertContains(outputData.tzLookupXml, "everutc=\"y\""); 512 } 513 514 @Test everUtc_false()515 public void everUtc_false() throws Exception { 516 CountryZonesFile.Country validCountryFr = createValidCountryFr(); 517 OutputData outputData = generateOutputData( 518 validCountryFr, createValidZoneTabEntriesFr()); 519 520 // Check fr's entry contains everutc="n". 521 assertContains(outputData.tzLookupXml, "everutc=\"n\""); 522 } 523 524 @Test shownInPicker_false()525 public void shownInPicker_false() throws Exception { 526 CountryZonesFile.Country countryPrototype = createValidCountryFr(); 527 528 CountryZonesFile.TimeZoneMapping.Builder timeZoneMappingBuilder = 529 countryPrototype.getTimeZoneMappings(0).toBuilder(); 530 timeZoneMappingBuilder.setShownInPicker(false); 531 532 CountryZonesFile.Country.Builder countryBuilder = countryPrototype.toBuilder(); 533 countryBuilder.setTimeZoneMappings(0, timeZoneMappingBuilder); 534 CountryZonesFile.Country country = countryBuilder.build(); 535 536 OutputData outputData = generateOutputData( 537 country, createValidZoneTabEntriesFr()); 538 539 assertContains(outputData.tzLookupXml, "picker=\"n\""); 540 } 541 542 @Test shownInPicker_true()543 public void shownInPicker_true() throws Exception { 544 CountryZonesFile.Country countryPrototype = createValidCountryFr(); 545 546 CountryZonesFile.TimeZoneMapping.Builder timeZoneMappingBuilder = 547 countryPrototype.getTimeZoneMappings(0).toBuilder(); 548 timeZoneMappingBuilder.setShownInPicker(true); 549 550 CountryZonesFile.Country.Builder countryBuilder = countryPrototype.toBuilder(); 551 countryBuilder.setTimeZoneMappings(0, timeZoneMappingBuilder); 552 CountryZonesFile.Country country = countryBuilder.build(); 553 554 OutputData outputData = generateOutputData( 555 country, createValidZoneTabEntriesFr()); 556 557 // We should not see anything "picker="y" is the implicit default. 558 assertAbsent(outputData.tzLookupXml, "picker="); 559 } 560 561 @Test notAfter()562 public void notAfter() throws Exception { 563 CountryZonesFile.Country country = createValidCountryUs(); 564 List<ZoneTabFile.CountryEntry> zoneTabEntries = createValidZoneTabEntriesUs(); 565 OutputData outputData = generateOutputData(country, zoneTabEntries); 566 String expectedTzLookupOutput = 567 "<id>America/New_York</id>\n" 568 + "<id notafter=\"167814000000\" repl=\"America/New_York\">America/Detroit</id>\n" 569 + "<id notafter=\"152089200000\" repl=\"America/New_York\">America/Kentucky/Louisville</id>\n" 570 + "<id notafter=\"972802800000\" repl=\"America/New_York\">America/Kentucky/Monticello</id>\n" 571 + "<id notafter=\"1130652000000\" repl=\"America/New_York\">America/Indiana/Indianapolis</id>\n" 572 + "<id notafter=\"1194159600000\" repl=\"America/New_York\">America/Indiana/Vincennes</id>\n" 573 + "<id notafter=\"1173600000000\" repl=\"America/New_York\">America/Indiana/Winamac</id>\n" 574 + "<id notafter=\"183535200000\" repl=\"America/Indiana/Indianapolis\">America/Indiana/Marengo</id>\n" 575 + "<id notafter=\"247042800000\" repl=\"America/Indiana/Vincennes\">America/Indiana/Petersburg</id>\n" 576 + "<id notafter=\"89186400000\" repl=\"America/Indiana/Indianapolis\">America/Indiana/Vevay</id>\n" 577 + "<id>America/Chicago</id>\n" 578 + "<id notafter=\"1143964800000\" repl=\"America/Chicago\">America/Indiana/Tell_City</id>\n" 579 + "<id notafter=\"688546800000\" repl=\"America/Indiana/Tell_City\">America/Indiana/Knox</id>\n" 580 + "<id notafter=\"104918400000\" repl=\"America/Chicago\">America/Menominee</id>\n" 581 + "<id notafter=\"720000000000\" repl=\"America/Chicago\">America/North_Dakota/Center</id>\n" 582 + "<id notafter=\"1067155200000\" repl=\"America/Chicago\">America/North_Dakota/New_Salem</id>\n" 583 + "<id notafter=\"1289116800000\" repl=\"America/Chicago\">America/North_Dakota/Beulah</id>\n" 584 + "<id>America/Denver</id>\n" 585 + "<id notafter=\"129114000000\" repl=\"America/Denver\">America/Boise</id>\n" 586 + "<id>America/Phoenix</id>\n" 587 + "<id>America/Los_Angeles</id>\n" 588 + "<id>America/Anchorage</id>\n" 589 + "<id notafter=\"436359600000\" repl=\"America/Anchorage\">America/Juneau</id>\n" 590 + "<id notafter=\"436356000000\" repl=\"America/Juneau\">America/Yakutat</id>\n" 591 + "<id notafter=\"436363200000\" repl=\"America/Anchorage\">America/Nome</id>\n" 592 + "<id notafter=\"1547978400000\" repl=\"America/Anchorage\">America/Metlakatla</id>\n" 593 + "<id notafter=\"341402400000\" repl=\"America/Juneau\">America/Sitka</id>\n" 594 + "<id>Pacific/Honolulu</id>\n" 595 + "<id>America/Adak</id>\n"; 596 String[] expectedTzLookupXmlLines = expectedTzLookupOutput.split("\\n"); 597 for (String expectedTzLookupXmlLine : expectedTzLookupXmlLines) { 598 assertContains(outputData.tzLookupXml, expectedTzLookupXmlLine); 599 } 600 601 TzIdsProto.TimeZoneIds.Builder tzIdsBuilder = TzIdsProto.TimeZoneIds 602 .newBuilder() 603 .setIanaVersion(TZDB_VERSION); 604 605 TzIdsProto.CountryMapping.Builder b = TzIdsProto.CountryMapping.newBuilder() 606 .setIsoCode("us") 607 .addTimeZoneIds("America/New_York") 608 .addTimeZoneIds("America/Chicago") 609 .addTimeZoneIds("America/Denver") 610 .addTimeZoneIds("America/Phoenix") 611 .addTimeZoneIds("America/Los_Angeles") 612 .addTimeZoneIds("America/Anchorage") 613 .addTimeZoneIds("Pacific/Honolulu") 614 .addTimeZoneIds("America/Adak"); 615 616 addReplacement(b, 167814000000L, "America/New_York", "America/Detroit"); 617 addReplacement(b, 152089200000L, "America/New_York", "America/Kentucky/Louisville"); 618 addReplacement(b, 972802800000L, "America/New_York", "America/Kentucky/Monticello"); 619 addReplacement(b, 1130652000000L, "America/New_York", "America/Indiana/Indianapolis"); 620 addReplacement(b, 1194159600000L, "America/New_York", "America/Indiana/Vincennes"); 621 addReplacement(b, 1173600000000L, "America/New_York", "America/Indiana/Winamac"); 622 addReplacement(b, 183535200000L, "America/Indiana/Indianapolis", "America/Indiana/Marengo"); 623 addReplacement(b, 247042800000L, "America/Indiana/Vincennes", "America/Indiana/Petersburg"); 624 addReplacement(b, 89186400000L, "America/Indiana/Indianapolis", "America/Indiana/Vevay"); 625 addReplacement(b, 1143964800000L, "America/Chicago", "America/Indiana/Tell_City"); 626 addReplacement(b, 688546800000L, "America/Indiana/Tell_City", "America/Indiana/Knox"); 627 addReplacement(b, 104918400000L, "America/Chicago", "America/Menominee"); 628 addReplacement(b, 720000000000L, "America/Chicago", "America/North_Dakota/Center"); 629 addReplacement(b, 1067155200000L, "America/Chicago", "America/North_Dakota/New_Salem"); 630 addReplacement(b, 1289116800000L, "America/Chicago", "America/North_Dakota/Beulah"); 631 addReplacement(b, 129114000000L, "America/Denver", "America/Boise"); 632 addReplacement(b, 436359600000L, "America/Anchorage", "America/Juneau"); 633 addReplacement(b, 436356000000L, "America/Juneau", "America/Yakutat"); 634 addReplacement(b, 436363200000L, "America/Anchorage", "America/Nome"); 635 addReplacement(b, 1547978400000L, "America/Anchorage", "America/Metlakatla"); 636 addReplacement(b, 341402400000L, "America/Juneau", "America/Sitka"); 637 638 tzIdsBuilder.addCountryMappings(b); 639 assertEquals(tzIdsBuilder.build(), outputData.timeZoneIds); 640 } 641 642 @Test eachAlternativeTimeZoneShouldBeAttributedToSingleCountryOnly()643 public void eachAlternativeTimeZoneShouldBeAttributedToSingleCountryOnly() throws Exception { 644 CountryZonesFile.Country gbCountry = 645 CountryZonesFile.Country.newBuilder() 646 .setIsoCode("gb") 647 .addTimeZoneMappings(CountryZonesFile.TimeZoneMapping.newBuilder() 648 .setUtcOffset("00:00") 649 .setId("Europe/London") 650 .addAlternativeIds("Europe/Belfast")) 651 .build(); 652 653 CountryZonesFile.Country franceCountry = 654 CountryZonesFile.Country.newBuilder() 655 .setIsoCode("fr") 656 .addTimeZoneMappings(CountryZonesFile.TimeZoneMapping.newBuilder() 657 .setUtcOffset("1:00") 658 .setId("Europe/Paris") 659 .addAlternativeIds("Europe/Belfast")) 660 .build(); 661 662 663 CountryZonesFile.CountryZones countryZones = 664 createValidCountryZones(gbCountry, franceCountry); 665 String countryZonesFile = createCountryZonesFile(countryZones); 666 667 String zoneTabFile = createZoneTabFile(); 668 669 String tzLookupFile = createTempFileName("tzlookup"); 670 String tzIdsFile = createTempFileName("tzids"); 671 672 TzLookupGenerator tzLookupGenerator = new TzLookupGenerator( 673 countryZonesFile, zoneTabFile, tzLookupFile, tzIdsFile); 674 assertFalse(tzLookupGenerator.execute(false /* validateAllIanaIdsAreMapped */)); 675 676 assertFileMissing(tzLookupFile); 677 assertFileMissing(tzIdsFile); 678 } 679 680 @Test idInMappingsShouldBeAttributedToSingleCountryOnly()681 public void idInMappingsShouldBeAttributedToSingleCountryOnly() throws Exception { 682 CountryZonesFile.Country gbCountry = 683 CountryZonesFile.Country.newBuilder() 684 .setIsoCode("gb") 685 .addTimeZoneMappings(CountryZonesFile.TimeZoneMapping.newBuilder() 686 .setUtcOffset("00:00") 687 .setId("Europe/London")) 688 .build(); 689 690 CountryZonesFile.Country franceCountry = 691 CountryZonesFile.Country.newBuilder() 692 .setIsoCode("fr") 693 .addTimeZoneMappings(CountryZonesFile.TimeZoneMapping.newBuilder() 694 .setUtcOffset("1:00") 695 .setId("Europe/London")) 696 .build(); 697 698 699 CountryZonesFile.CountryZones countryZones = 700 createValidCountryZones(gbCountry, franceCountry); 701 String countryZonesFile = createCountryZonesFile(countryZones); 702 703 String zoneTabFile = createZoneTabFile(); 704 String tzLookupFile = createTempFileName("tzlookup"); 705 String tzIdsFile = createTempFileName("tzids"); 706 707 TzLookupGenerator tzLookupGenerator = new TzLookupGenerator( 708 countryZonesFile, zoneTabFile, tzLookupFile, tzIdsFile); 709 710 assertFalse(tzLookupGenerator.execute(false /* validateAllIanaIdsAreMapped */)); 711 712 assertFileMissing(tzLookupFile); 713 assertFileMissing(tzIdsFile); 714 } 715 716 @Test idInMappingsShouldNotAppearInAlternativeNameIds()717 public void idInMappingsShouldNotAppearInAlternativeNameIds() throws Exception { 718 CountryZonesFile.Country gbCountry = 719 CountryZonesFile.Country.newBuilder() 720 .setIsoCode("gb") 721 .addTimeZoneMappings(CountryZonesFile.TimeZoneMapping.newBuilder() 722 .setUtcOffset("00:00") 723 .setId("Europe/London")) 724 .build(); 725 726 CountryZonesFile.Country franceCountry = 727 CountryZonesFile.Country.newBuilder() 728 .setIsoCode("fr") 729 .addTimeZoneMappings(CountryZonesFile.TimeZoneMapping.newBuilder() 730 .setUtcOffset("1:00") 731 .setId("Europe/Paris") 732 .addAlternativeIds("Europe/London")) 733 .build(); 734 735 736 CountryZonesFile.CountryZones countryZones = 737 createValidCountryZones(gbCountry, franceCountry); 738 String countryZonesFile = createCountryZonesFile(countryZones); 739 740 String zoneTabFile = createZoneTabFile(); 741 String tzLookupFile = createTempFileName("tzlookup"); 742 String tzIdsFile = createTempFileName("tzids"); 743 744 TzLookupGenerator tzLookupGenerator = new TzLookupGenerator( 745 countryZonesFile, zoneTabFile, tzLookupFile, tzIdsFile); 746 747 assertFalse(tzLookupGenerator.execute(false /* validateAllIanaIdsAreMapped */)); 748 749 assertFileMissing(tzLookupFile); 750 assertFileMissing(tzIdsFile); 751 } 752 753 @Test shouldFail_whenIdAppearsInMultipleTimeZoneMappingWithinACountry()754 public void shouldFail_whenIdAppearsInMultipleTimeZoneMappingWithinACountry() throws Exception { 755 CountryZonesFile.Country country = 756 CountryZonesFile.Country.newBuilder() 757 .setIsoCode("ar") 758 .addTimeZoneMappings( 759 CountryZonesFile.TimeZoneMapping.newBuilder() 760 .setUtcOffset("-3:00") 761 .setId("America/Argentina/Buenos_Aires") 762 .addAlternativeIds("America/Buenos_Aires")) 763 .addTimeZoneMappings( 764 CountryZonesFile.TimeZoneMapping.newBuilder() 765 .setUtcOffset("-3:00") 766 .setId("America/Argentina/Cordoba") 767 .addAlternativeIds("America/Cordoba") 768 .addAlternativeIds("America/Buenos_Aires")) 769 .build(); 770 771 CountryZonesFile.CountryZones countryZones = createValidCountryZones(country); 772 String countryZonesFile = createCountryZonesFile(countryZones); 773 774 String zoneTabFile = createZoneTabFile(); 775 String tzLookupFile = createTempFileName("tzlookup"); 776 String tzIdsFile = createTempFileName("tzids"); 777 778 TzLookupGenerator tzLookupGenerator = new TzLookupGenerator( 779 countryZonesFile, zoneTabFile, tzLookupFile, tzIdsFile); 780 781 assertFalse(tzLookupGenerator.execute(false /* validateAllIanaIdsAreMapped */)); 782 } 783 784 @Test shouldFail_whenIdIsMentionedAmongAlternativeIds()785 public void shouldFail_whenIdIsMentionedAmongAlternativeIds() throws Exception { 786 CountryZonesFile.Country country = 787 CountryZonesFile.Country.newBuilder() 788 .setIsoCode("gb") 789 .addTimeZoneMappings( 790 CountryZonesFile.TimeZoneMapping.newBuilder() 791 .setUtcOffset("00:00") 792 .setId("Europe/London") 793 .addAlternativeIds("Europe/London")) 794 .build(); 795 796 CountryZonesFile.CountryZones countryZones = createValidCountryZones(country); 797 String countryZonesFile = createCountryZonesFile(countryZones); 798 799 String zoneTabFile = createZoneTabFile(); 800 String tzlookupFile = createTempFileName("tzlookup"); 801 String tzIdsFile = createTempFileName("tzids"); 802 803 TzLookupGenerator tzLookupGenerator = new TzLookupGenerator( 804 countryZonesFile, zoneTabFile, tzlookupFile, tzIdsFile); 805 806 assertFalse(tzLookupGenerator.execute(false /* validateAllIanaIdsAreMapped */)); 807 } 808 809 @Test shouldFail_whenAlternativeIdsListContainsDuplicates()810 public void shouldFail_whenAlternativeIdsListContainsDuplicates() throws Exception { 811 CountryZonesFile.Country country = 812 CountryZonesFile.Country.newBuilder() 813 .setIsoCode("gb") 814 .addTimeZoneMappings( 815 CountryZonesFile.TimeZoneMapping.newBuilder() 816 .setUtcOffset("00:00") 817 .setId("Europe/London") 818 .addAlternativeIds("Europe/Belfast") 819 .addAlternativeIds("GB") 820 .addAlternativeIds("Europe/Belfast")) 821 .build(); 822 823 CountryZonesFile.CountryZones countryZones = createValidCountryZones(country); 824 String countryZonesFile = createCountryZonesFile(countryZones); 825 826 String zoneTabFile = createZoneTabFile(); 827 String tzlookupFile = createTempFileName("tzlookup"); 828 String tzIdsFile = createTempFileName("tzids"); 829 830 TzLookupGenerator tzLookupGenerator = new TzLookupGenerator( 831 countryZonesFile, zoneTabFile, tzlookupFile, tzIdsFile); 832 833 assertFalse(tzLookupGenerator.execute(false /* validateAllIanaIdsAreMapped */)); 834 } 835 836 @Test shouldFail_whenAlternativeIdsListContainsNonEquivalentTimeZoneId()837 public void shouldFail_whenAlternativeIdsListContainsNonEquivalentTimeZoneId() 838 throws Exception { 839 CountryZonesFile.Country country = 840 CountryZonesFile.Country.newBuilder() 841 .setIsoCode("gb") 842 .addTimeZoneMappings( 843 CountryZonesFile.TimeZoneMapping.newBuilder() 844 .setUtcOffset("00:00") 845 .setId("Europe/London") 846 .addAlternativeIds("Europe/Belfast") 847 .addAlternativeIds("GB") 848 .addAlternativeIds("Europe/Paris")) 849 .build(); 850 851 CountryZonesFile.CountryZones countryZones = createValidCountryZones(country); 852 String countryZonesFile = createCountryZonesFile(countryZones); 853 854 String zoneTabFile = createZoneTabFile(); 855 String tzlookupFile = createTempFileName("tzlookup"); 856 String tzIdsFile = createTempFileName("tzids"); 857 858 TzLookupGenerator tzLookupGenerator = new TzLookupGenerator( 859 countryZonesFile, zoneTabFile, tzlookupFile, tzIdsFile); 860 861 assertFalse(tzLookupGenerator.execute(false /* validateAllIanaIdsAreMapped */)); 862 } 863 864 @Test shouldFail_whenAnAliasComesBeforeOriginalTimeZone()865 public void shouldFail_whenAnAliasComesBeforeOriginalTimeZone() throws Exception { 866 // These time zones relationship is unlikely to change judging by the recent 867 // TZDB updates. 868 CountryZonesFile.Country country = 869 CountryZonesFile.Country.newBuilder() 870 .setIsoCode("de") 871 .setDefaultTimeZoneId("Europe/Berlin") 872 .addTimeZoneMappings( 873 CountryZonesFile.TimeZoneMapping.newBuilder() 874 .setUtcOffset("01:00") 875 .setId("Europe/Busingen")) 876 .addTimeZoneMappings( 877 CountryZonesFile.TimeZoneMapping.newBuilder() 878 .setUtcOffset("01:00") 879 .setId("Europe/Berlin")) 880 .build(); 881 882 CountryZonesFile.CountryZones countryZones = createValidCountryZones(country); 883 String countryZonesFile = createCountryZonesFile(countryZones); 884 885 String zoneTabFile = createZoneTabFile( 886 List.of( 887 new ZoneTabFile.CountryEntry("DE", "Europe/Berlin"), 888 new ZoneTabFile.CountryEntry("DE", "Europe/Busingen"))); 889 String tzlookupFile = createTempFileName("tzlookup"); 890 String tzIdsFile = createTempFileName("tzids"); 891 892 TzLookupGenerator tzLookupGenerator = new TzLookupGenerator( 893 countryZonesFile, zoneTabFile, tzlookupFile, tzIdsFile); 894 895 assertFalse(tzLookupGenerator.execute(false /* validateAllIanaIdsAreMapped */)); 896 } 897 898 @Test shouldFail_whenCountryzonesMissesTimeZone()899 public void shouldFail_whenCountryzonesMissesTimeZone() throws Exception { 900 CountryZonesFile.Country gbCountry = 901 CountryZonesFile.Country.newBuilder() 902 .setIsoCode("gb") 903 .addTimeZoneMappings(CountryZonesFile.TimeZoneMapping.newBuilder() 904 .setUtcOffset("00:00") 905 .setId("Europe/London") 906 .addAlternativeIds("Europe/Belfast")) 907 .build(); 908 909 CountryZonesFile.CountryZones countryZones = 910 createValidCountryZones(gbCountry); 911 String countryZonesFile = createCountryZonesFile(countryZones); 912 913 String zoneTabFile = createZoneTabFile(); 914 915 String tzLookupFile = createTempFileName("tzlookup"); 916 String tzIdsFile = createTempFileName("tzids"); 917 918 TzLookupGenerator tzLookupGenerator = new TzLookupGenerator( 919 countryZonesFile, zoneTabFile, tzLookupFile, tzIdsFile); 920 assertFalse(tzLookupGenerator.execute(true /* validateAllIanaIdsAreMapped */)); 921 } 922 addReplacement(TzIdsProto.CountryMapping.Builder builder, long fromMillis, String replacementId, String replacedId)923 private static void addReplacement(TzIdsProto.CountryMapping.Builder builder, 924 long fromMillis, String replacementId, String replacedId) { 925 TzIdsProto.TimeZoneReplacement replacement = 926 TzIdsProto.TimeZoneReplacement.newBuilder() 927 .setReplacedId(replacedId) 928 .setReplacementId(replacementId) 929 .setFromMillis(fromMillis) 930 .build(); 931 builder.addTimeZoneReplacements(replacement); 932 } 933 934 static class OutputData { 935 final String tzLookupXml; 936 final TzIdsProto.TimeZoneIds timeZoneIds; 937 OutputData(String tzLookupXml, TzIdsProto.TimeZoneIds timeZoneIds)938 OutputData(String tzLookupXml, TzIdsProto.TimeZoneIds timeZoneIds) { 939 this.tzLookupXml = tzLookupXml; 940 this.timeZoneIds = timeZoneIds; 941 } 942 } 943 generateOutputData(CountryZonesFile.Country country, List<ZoneTabFile.CountryEntry> zoneTabEntries)944 private OutputData generateOutputData(CountryZonesFile.Country country, 945 List<ZoneTabFile.CountryEntry> zoneTabEntries) throws Exception { 946 947 CountryZonesFile.CountryZones countryZones = createValidCountryZones(country); 948 String countryZonesFile = createCountryZonesFile(countryZones); 949 950 String zoneTabFile = createZoneTabFile(zoneTabEntries); 951 952 String tzLookupFile = createTempFileName("tzlookup"); 953 String tzIdsFile = createTempFileName("tzids"); 954 955 TzLookupGenerator tzLookupGenerator = new TzLookupGenerator( 956 countryZonesFile, zoneTabFile, tzLookupFile, tzIdsFile); 957 assertTrue(tzLookupGenerator.execute(false /* validateAllIanaIdsAreMapped */)); 958 959 Path tzLookupFilePath = checkFileExists(tzLookupFile); 960 String tzLookupXml = readFileToString(tzLookupFilePath); 961 962 Path tzIdsFilePath = checkFileExists(tzIdsFile); 963 String timeZoneIdsText = readFileToString(tzIdsFilePath); 964 TzIdsProto.TimeZoneIds.Builder timeZoneIdsBuilder = 965 TzIdsProto.TimeZoneIds.newBuilder(); 966 TextFormat.merge(timeZoneIdsText, timeZoneIdsBuilder); 967 968 return new OutputData(tzLookupXml, timeZoneIdsBuilder.build()); 969 } 970 generateTzLookupXmlExpectFailure(CountryZonesFile.Country country, List<ZoneTabFile.CountryEntry> zoneTabEntries)971 private void generateTzLookupXmlExpectFailure(CountryZonesFile.Country country, 972 List<ZoneTabFile.CountryEntry> zoneTabEntries) throws Exception { 973 974 CountryZonesFile.CountryZones countryZones = createValidCountryZones(country); 975 String countryZonesFile = createCountryZonesFile(countryZones); 976 977 String zoneTabFile = createZoneTabFile(zoneTabEntries); 978 979 String tzLookupFile = createTempFileName("tzlookup"); 980 String tzIdsFile = createTempFileName("tzids"); 981 982 TzLookupGenerator tzLookupGenerator = new TzLookupGenerator( 983 countryZonesFile, zoneTabFile, tzLookupFile, tzIdsFile); 984 assertFalse(tzLookupGenerator.execute(false /* validateAllIanaIdsAreMapped */)); 985 } 986 readFileToString(Path file)987 private static String readFileToString(Path file) throws IOException { 988 return new String(Files.readAllBytes(file), StandardCharsets.UTF_8); 989 } 990 createZoneTabFile(List<ZoneTabFile.CountryEntry>.... zoneTabEntriesLists)991 private String createZoneTabFile(List<ZoneTabFile.CountryEntry>... zoneTabEntriesLists) 992 throws Exception { 993 List<List<ZoneTabFile.CountryEntry>> entries = Arrays.asList(zoneTabEntriesLists); 994 List<String> lines = entries.stream() 995 .flatMap(List::stream) 996 .map(country -> country.isoCode + "\tIgnored\t" + country.olsonId) 997 .collect(Collectors.toList()); 998 return TestUtils.createFile(tempDir, lines.toArray(new String[0])); 999 } 1000 createCountryZonesFile(CountryZonesFile.CountryZones countryZones)1001 private String createCountryZonesFile(CountryZonesFile.CountryZones countryZones) throws Exception { 1002 return TestUtils.createFile(tempDir, TextFormat.printToString(countryZones)); 1003 } 1004 createValidCountryZones( CountryZonesFile.Country... countries)1005 private static CountryZonesFile.CountryZones createValidCountryZones( 1006 CountryZonesFile.Country... countries) { 1007 CountryZonesFile.CountryZones.Builder builder = 1008 CountryZonesFile.CountryZones.newBuilder() 1009 .setIanaVersion(TZDB_VERSION); 1010 for (CountryZonesFile.Country country : countries) { 1011 builder.addCountries(country); 1012 } 1013 return builder.build(); 1014 } 1015 createValidCountryGb()1016 private static CountryZonesFile.Country createValidCountryGb() { 1017 return CountryZonesFile.Country.newBuilder() 1018 .setIsoCode("gb") 1019 .addTimeZoneMappings(CountryZonesFile.TimeZoneMapping.newBuilder() 1020 .setUtcOffset("00:00") 1021 .setId("Europe/London")) 1022 .build(); 1023 } 1024 createValidCountryUs()1025 private static CountryZonesFile.Country createValidCountryUs() throws Exception { 1026 // This country demonstrates most interesting algorithm behavior. This is copied verbatim 1027 // from countryzones.txt. 1028 String usText = 1029 " isoCode:\"us\"\n" 1030 + " defaultTimeZoneId:\"America/New_York\"\n" 1031 + " timeZoneMappings:<\n" 1032 + " utcOffset:\"-5:00\"\n" 1033 + " id:\"America/New_York\"\n" 1034 + " priority:10\n" 1035 + " >\n" 1036 + " timeZoneMappings:<\n" 1037 + " utcOffset:\"-5:00\"\n" 1038 + " id:\"America/Detroit\"\n" 1039 + " >\n" 1040 + " timeZoneMappings:<\n" 1041 + " utcOffset:\"-5:00\"\n" 1042 + " id:\"America/Kentucky/Louisville\"\n" 1043 + " >\n" 1044 + " timeZoneMappings:<\n" 1045 + " utcOffset:\"-5:00\"\n" 1046 + " id:\"America/Kentucky/Monticello\"\n" 1047 + " >\n" 1048 + " timeZoneMappings:<\n" 1049 + " utcOffset:\"-5:00\"\n" 1050 + " id:\"America/Indiana/Indianapolis\"\n" 1051 + " priority:9\n" 1052 + " >\n" 1053 + " timeZoneMappings:<\n" 1054 + " utcOffset:\"-5:00\"\n" 1055 + " id:\"America/Indiana/Vincennes\"\n" 1056 + " priority:9\n" 1057 + " >\n" 1058 + " timeZoneMappings:<\n" 1059 + " utcOffset:\"-5:00\"\n" 1060 + " id:\"America/Indiana/Winamac\"\n" 1061 + " >\n" 1062 + " timeZoneMappings:<\n" 1063 + " utcOffset:\"-5:00\"\n" 1064 + " id:\"America/Indiana/Marengo\"\n" 1065 + " >\n" 1066 + " timeZoneMappings:<\n" 1067 + " utcOffset:\"-5:00\"\n" 1068 + " id:\"America/Indiana/Petersburg\"\n" 1069 + " >\n" 1070 + " timeZoneMappings:<\n" 1071 + " utcOffset:\"-5:00\"\n" 1072 + " id:\"America/Indiana/Vevay\"\n" 1073 + " >\n" 1074 + " timeZoneMappings:<\n" 1075 + " utcOffset:\"-6:00\"\n" 1076 + " id:\"America/Chicago\"\n" 1077 + " priority:10\n" 1078 + " >\n" 1079 + " timeZoneMappings:<\n" 1080 + " utcOffset:\"-6:00\"\n" 1081 + " id:\"America/Indiana/Tell_City\"\n" 1082 + " priority:9\n" 1083 + " >\n" 1084 + " timeZoneMappings:<\n" 1085 + " utcOffset:\"-6:00\"\n" 1086 + " id:\"America/Indiana/Knox\"\n" 1087 + " >\n" 1088 + " timeZoneMappings:<\n" 1089 + " utcOffset:\"-6:00\"\n" 1090 + " id:\"America/Menominee\"\n" 1091 + " >\n" 1092 + " timeZoneMappings:<\n" 1093 + " utcOffset:\"-6:00\"\n" 1094 + " id:\"America/North_Dakota/Center\"\n" 1095 + " >\n" 1096 + " timeZoneMappings:<\n" 1097 + " utcOffset:\"-6:00\"\n" 1098 + " id:\"America/North_Dakota/New_Salem\"\n" 1099 + " >\n" 1100 + " timeZoneMappings:<\n" 1101 + " utcOffset:\"-6:00\"\n" 1102 + " id:\"America/North_Dakota/Beulah\"\n" 1103 + " >\n" 1104 + " timeZoneMappings:<\n" 1105 + " utcOffset:\"-7:00\"\n" 1106 + " id:\"America/Denver\"\n" 1107 + " priority:9\n" 1108 + " >\n" 1109 + " timeZoneMappings:<\n" 1110 + " utcOffset:\"-7:00\"\n" 1111 + " id:\"America/Boise\"\n" 1112 + " >\n" 1113 + " timeZoneMappings:<\n" 1114 + " utcOffset:\"-7:00\"\n" 1115 + " id:\"America/Phoenix\"\n" 1116 + " priority:10\n" 1117 + " >\n" 1118 + " timeZoneMappings:<\n" 1119 + " utcOffset:\"-8:00\"\n" 1120 + " id:\"America/Los_Angeles\"\n" 1121 + " >\n" 1122 + " timeZoneMappings:<\n" 1123 + " utcOffset:\"-9:00\"\n" 1124 + " id:\"America/Anchorage\"\n" 1125 + " priority:10\n" 1126 + " >\n" 1127 + " timeZoneMappings:<\n" 1128 + " utcOffset:\"-9:00\"\n" 1129 + " id:\"America/Juneau\"\n" 1130 + " priority:9\n" 1131 + " >\n" 1132 + " timeZoneMappings:<\n" 1133 + " utcOffset:\"-9:00\"\n" 1134 + " id:\"America/Yakutat\"\n" 1135 + " >\n" 1136 + " timeZoneMappings:<\n" 1137 + " utcOffset:\"-9:00\"\n" 1138 + " id:\"America/Nome\"\n" 1139 + " >\n" 1140 + " timeZoneMappings:<\n" 1141 + " utcOffset:\"-9:00\"\n" 1142 + " id:\"America/Metlakatla\"\n" 1143 + " >\n" 1144 + " timeZoneMappings:<\n" 1145 + " utcOffset:\"-9:00\"\n" 1146 + " id:\"America/Sitka\"\n" 1147 + " >\n" 1148 + " timeZoneMappings:<\n" 1149 + " utcOffset:\"-10:00\"\n" 1150 + " id:\"Pacific/Honolulu\"\n" 1151 + " priority:10\n" 1152 + " >\n" 1153 + " timeZoneMappings:<\n" 1154 + " utcOffset:\"-10:00\"\n" 1155 + " id:\"America/Adak\"\n" 1156 + " >\n"; 1157 return parseCountry(usText); 1158 } 1159 createValidCountryFr()1160 private static CountryZonesFile.Country createValidCountryFr() { 1161 return CountryZonesFile.Country.newBuilder() 1162 .setIsoCode("fr") 1163 .addTimeZoneMappings(CountryZonesFile.TimeZoneMapping.newBuilder() 1164 .setUtcOffset("01:00") 1165 .setId("Europe/Paris")) 1166 .build(); 1167 } 1168 createValidZoneTabEntriesGb()1169 private static List<ZoneTabFile.CountryEntry> createValidZoneTabEntriesGb() { 1170 return Arrays.asList(new ZoneTabFile.CountryEntry("GB", "Europe/London")); 1171 } 1172 createValidZoneTabEntriesUs()1173 private static List<ZoneTabFile.CountryEntry> createValidZoneTabEntriesUs() { 1174 return Arrays.asList( 1175 new ZoneTabFile.CountryEntry("US", "America/New_York"), 1176 new ZoneTabFile.CountryEntry("US", "America/Detroit"), 1177 new ZoneTabFile.CountryEntry("US", "America/Kentucky/Louisville"), 1178 new ZoneTabFile.CountryEntry("US", "America/Kentucky/Monticello"), 1179 new ZoneTabFile.CountryEntry("US", "America/Indiana/Indianapolis"), 1180 new ZoneTabFile.CountryEntry("US", "America/Indiana/Vincennes"), 1181 new ZoneTabFile.CountryEntry("US", "America/Indiana/Winamac"), 1182 new ZoneTabFile.CountryEntry("US", "America/Indiana/Marengo"), 1183 new ZoneTabFile.CountryEntry("US", "America/Indiana/Petersburg"), 1184 new ZoneTabFile.CountryEntry("US", "America/Indiana/Vevay"), 1185 new ZoneTabFile.CountryEntry("US", "America/Chicago"), 1186 new ZoneTabFile.CountryEntry("US", "America/Indiana/Tell_City"), 1187 new ZoneTabFile.CountryEntry("US", "America/Indiana/Knox"), 1188 new ZoneTabFile.CountryEntry("US", "America/Menominee"), 1189 new ZoneTabFile.CountryEntry("US", "America/North_Dakota/Center"), 1190 new ZoneTabFile.CountryEntry("US", "America/North_Dakota/New_Salem"), 1191 new ZoneTabFile.CountryEntry("US", "America/North_Dakota/Beulah"), 1192 new ZoneTabFile.CountryEntry("US", "America/Denver"), 1193 new ZoneTabFile.CountryEntry("US", "America/Boise"), 1194 new ZoneTabFile.CountryEntry("US", "America/Phoenix"), 1195 new ZoneTabFile.CountryEntry("US", "America/Los_Angeles"), 1196 new ZoneTabFile.CountryEntry("US", "America/Anchorage"), 1197 new ZoneTabFile.CountryEntry("US", "America/Juneau"), 1198 new ZoneTabFile.CountryEntry("US", "America/Sitka"), 1199 new ZoneTabFile.CountryEntry("US", "America/Metlakatla"), 1200 new ZoneTabFile.CountryEntry("US", "America/Yakutat"), 1201 new ZoneTabFile.CountryEntry("US", "America/Nome"), 1202 new ZoneTabFile.CountryEntry("US", "America/Adak"), 1203 new ZoneTabFile.CountryEntry("US", "Pacific/Honolulu")); 1204 } 1205 createValidZoneTabEntriesFr()1206 private static List<ZoneTabFile.CountryEntry> createValidZoneTabEntriesFr() { 1207 return Arrays.asList(new ZoneTabFile.CountryEntry("FR", "Europe/Paris")); 1208 } 1209 1210 /** Returns a file name for a file that does not exist. */ createTempFileName(String fileNamePrefix)1211 private String createTempFileName(String fileNamePrefix) throws IOException { 1212 Path tempFile = Files.createTempFile(tempDir, fileNamePrefix, null /* suffix */); 1213 Files.delete(tempFile); 1214 return tempFile.toString(); 1215 } 1216 parseCountry(String text)1217 private static Country parseCountry(String text) throws Exception { 1218 Country.Builder builder = Country.newBuilder(); 1219 TextFormat.getParser().merge(text, builder); 1220 return builder.build(); 1221 } 1222 checkFileExists(String fileName)1223 private static Path checkFileExists(String fileName) { 1224 Path filePath = Paths.get(fileName); 1225 assertTrue("File " + filePath + " unexpectedly missing", Files.exists(filePath)); 1226 return filePath; 1227 } 1228 assertFileMissing(String fileName)1229 private static void assertFileMissing(String fileName) throws IOException { 1230 Path filePath = Paths.get(fileName); 1231 assertFalse("File " + filePath + " unexpectedly exists", Files.exists(filePath)); 1232 } 1233 } 1234