13cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller/*
23cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller * Copyright (C) 2017 The Android Open Source Project
33cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller *
43cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller * Licensed under the Apache License, Version 2.0 (the "License");
53cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller * you may not use this file except in compliance with the License.
63cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller * You may obtain a copy of the License at
73cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller *
83cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller *      http://www.apache.org/licenses/LICENSE-2.0
93cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller *
103cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller * Unless required by applicable law or agreed to in writing, software
113cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller * distributed under the License is distributed on an "AS IS" BASIS,
123cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
133cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller * See the License for the specific language governing permissions and
143cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller * limitations under the License.
153cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller */
163cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
173cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fullerpackage libcore.util;
183cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
193cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fullerimport org.junit.After;
203cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fullerimport org.junit.Before;
213cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fullerimport org.junit.Test;
223cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
233cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fullerimport android.icu.util.TimeZone;
243cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
253cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fullerimport java.io.IOException;
263cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fullerimport java.nio.charset.StandardCharsets;
273cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fullerimport java.nio.file.FileVisitResult;
283cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fullerimport java.nio.file.Files;
293cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fullerimport java.nio.file.Path;
303cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fullerimport java.nio.file.SimpleFileVisitor;
313cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fullerimport java.nio.file.attribute.BasicFileAttributes;
323cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fullerimport java.util.Arrays;
333cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fullerimport java.util.HashMap;
343cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fullerimport java.util.HashSet;
353cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fullerimport java.util.List;
363cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fullerimport java.util.Map;
373cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fullerimport java.util.Set;
383cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fullerimport java.util.stream.Collectors;
393cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
403cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fullerimport static org.junit.Assert.assertEquals;
413cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fullerimport static org.junit.Assert.assertNull;
423cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fullerimport static org.junit.Assert.fail;
433cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
443cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fullerpublic class TimeZoneFinderTest {
453cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
463cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    private static final int HOUR_MILLIS = 60 * 60 * 1000;
473cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
483cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    // Zones used in the tests. NEW_YORK_TZ and LONDON_TZ chosen because they never overlap but both
493cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    // have DST.
503cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    private static final TimeZone NEW_YORK_TZ = TimeZone.getTimeZone("America/New_York");
513cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    private static final TimeZone LONDON_TZ = TimeZone.getTimeZone("Europe/London");
523cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    // A zone that matches LONDON_TZ for WHEN_NO_DST. It does not have DST so differs for WHEN_DST.
533cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    private static final TimeZone REYKJAVIK_TZ = TimeZone.getTimeZone("Atlantic/Reykjavik");
543cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    // Another zone that matches LONDON_TZ for WHEN_NO_DST. It does not have DST so differs for
553cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    // WHEN_DST.
563cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    private static final TimeZone UTC_TZ = TimeZone.getTimeZone("Etc/UTC");
573cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
583cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    // 22nd July 2017, 13:14:15 UTC (DST time in all the timezones used in these tests that observe
593cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    // DST).
603cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    private static final long WHEN_DST = 1500729255000L;
613cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    // 22nd January 2018, 13:14:15 UTC (non-DST time in all timezones used in these tests).
623cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    private static final long WHEN_NO_DST = 1516626855000L;
633cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
643cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    private static final int LONDON_DST_OFFSET_MILLIS = HOUR_MILLIS;
653cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    private static final int LONDON_NO_DST_OFFSET_MILLIS = 0;
663cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
673cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    private static final int NEW_YORK_DST_OFFSET_MILLIS = -4 * HOUR_MILLIS;
683cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    private static final int NEW_YORK_NO_DST_OFFSET_MILLIS = -5 * HOUR_MILLIS;
693cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
703cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    private Path testDir;
713cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
723cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    @Before
733cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    public void setUp() throws Exception {
743cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        testDir = Files.createTempDirectory("TimeZoneFinderTest");
753cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    }
763cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
773cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    @After
783cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    public void tearDown() throws Exception {
793cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // Delete the testDir and all contents.
803cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        Files.walkFileTree(testDir, new SimpleFileVisitor<Path>() {
813cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller            @Override
823cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
833cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                    throws IOException {
843cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                Files.delete(file);
853cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                return FileVisitResult.CONTINUE;
863cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller            }
873cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
883cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller            @Override
893cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller            public FileVisitResult postVisitDirectory(Path dir, IOException exc)
903cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                    throws IOException {
913cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                Files.delete(dir);
923cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                return FileVisitResult.CONTINUE;
933cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller            }
943cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        });
953cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    }
963cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
973cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    @Test
983cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    public void createInstanceWithFallback() throws Exception {
993cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        String validXml1 = "<timezones>\n"
1003cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  <countryzones>\n"
1013cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    <country code=\"gb\">\n"
1023cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      <id>Europe/London</id>\n"
1033cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    </country>\n"
1043cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  </countryzones>\n"
1053cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "</timezones>\n";
1063cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        String validXml2 = "<timezones>\n"
1073cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  <countryzones>\n"
1083cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    <country code=\"gb\">\n"
1093cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      <id>Europe/Paris</id>\n"
1103cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    </country>\n"
1113cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  </countryzones>\n"
1123cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "</timezones>\n";
1133cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
1143cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        String invalidXml = "<foo></foo>\n";
1153cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        checkValidateThrowsParserException(invalidXml);
1163cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
1173cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        String validFile1 = createFile(validXml1);
1183cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        String validFile2 = createFile(validXml2);
1193cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        String invalidFile = createFile(invalidXml);
1203cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        String missingFile = createMissingFile();
1213cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
1223cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        TimeZoneFinder file1ThenFile2 =
1233cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                TimeZoneFinder.createInstanceWithFallback(validFile1, validFile2);
1243cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZonesEqual(zones("Europe/London"), file1ThenFile2.lookupTimeZonesByCountry("gb"));
1253cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
1263cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        TimeZoneFinder missingFileThenFile1 =
1273cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                TimeZoneFinder.createInstanceWithFallback(missingFile, validFile1);
1283cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZonesEqual(zones("Europe/London"),
1293cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                missingFileThenFile1.lookupTimeZonesByCountry("gb"));
1303cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
1313cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        TimeZoneFinder file2ThenFile1 =
1323cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                TimeZoneFinder.createInstanceWithFallback(validFile2, validFile1);
1333cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZonesEqual(zones("Europe/Paris"), file2ThenFile1.lookupTimeZonesByCountry("gb"));
1343cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
1353cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // We assume the file has been validated so an invalid file is not checked ahead of time.
1363cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // We will find out when we look something up.
1373cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        TimeZoneFinder invalidThenValid =
1383cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                TimeZoneFinder.createInstanceWithFallback(invalidFile, validFile1);
1393cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertNull(invalidThenValid.lookupTimeZonesByCountry("gb"));
1403cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
1413cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // This is not a normal case: It would imply a define shipped without a file in /system!
1423cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        TimeZoneFinder missingFiles =
1433cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                TimeZoneFinder.createInstanceWithFallback(missingFile, missingFile);
1443cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertNull(missingFiles.lookupTimeZonesByCountry("gb"));
1453cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    }
1463cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
1473cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    @Test
1483cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    public void xmlParsing_emptyFile() throws Exception {
1493cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        checkValidateThrowsParserException("");
1503cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    }
1513cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
1523cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    @Test
1533cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    public void xmlParsing_unexpectedRootElement() throws Exception {
1543cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        checkValidateThrowsParserException("<foo></foo>\n");
1553cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    }
1563cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
1573cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    @Test
1583cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    public void xmlParsing_missingCountryZones() throws Exception {
1593cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        checkValidateThrowsParserException("<timezones></timezones>\n");
1603cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    }
1613cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
1623cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    @Test
1633cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    public void xmlParsing_noCountriesOk() throws Exception {
1643cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        validate("<timezones>\n"
1653cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  <countryzones>\n"
1663cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  </countryzones>\n"
1673cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "</timezones>\n");
1683cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    }
1693cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
1703cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    @Test
1713cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    public void xmlParsing_unexpectedComments() throws Exception {
1723cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        TimeZoneFinder finder = validate("<timezones>\n"
1733cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  <countryzones>\n"
1743cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    <country code=\"gb\">\n"
1753cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      <!-- This is a comment -->"
1763cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      <id>Europe/London</id>\n"
1773cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    </country>\n"
1783cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  </countryzones>\n"
1793cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "</timezones>\n");
1803cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
1813cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
1823cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // This is a crazy comment, but also helps prove that TEXT nodes are coalesced by the
1833cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // parser.
1843cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        finder = validate("<timezones>\n"
1853cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  <countryzones>\n"
1863cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    <country code=\"gb\">\n"
1873cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      <id>Europe/<!-- Don't freak out! -->London</id>\n"
1883cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    </country>\n"
1893cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  </countryzones>\n"
1903cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "</timezones>\n");
1913cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
1923cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    }
1933cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
1943cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    @Test
1953cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    public void xmlParsing_unexpectedElementsIgnored() throws Exception {
1963cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        String unexpectedElement = "<unexpected-element>\n<a /></unexpected-element>\n";
1973cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        TimeZoneFinder finder = validate("<timezones>\n"
1983cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  " + unexpectedElement
1993cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  <countryzones>\n"
2003cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    <country code=\"gb\">\n"
2013cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      <id>Europe/London</id>\n"
2023cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    </country>\n"
2033cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  </countryzones>\n"
2043cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "</timezones>\n");
2053cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
2063cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
2073cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        finder = validate("<timezones>\n"
2083cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  <countryzones>\n"
2093cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    " + unexpectedElement
2103cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    <country code=\"gb\">\n"
2113cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      <id>Europe/London</id>\n"
2123cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    </country>\n"
2133cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  </countryzones>\n"
2143cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "</timezones>\n");
2153cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
2163cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
2173cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        finder = validate("<timezones>\n"
2183cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  <countryzones>\n"
2193cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    <country code=\"gb\">\n"
2203cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      " + unexpectedElement
2213cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      <id>Europe/London</id>\n"
2223cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    </country>\n"
2233cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  </countryzones>\n"
2243cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "</timezones>\n");
2253cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
2263cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
2273cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        finder = validate("<timezones>\n"
2283cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  <countryzones>\n"
2293cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    <country code=\"gb\">\n"
2303cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      <id>Europe/London</id>\n"
2313cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      " + unexpectedElement
2323cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      <id>Europe/Paris</id>\n"
2333cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    </country>\n"
2343cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  </countryzones>\n"
2353cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "</timezones>\n");
2363cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZonesEqual(zones("Europe/London", "Europe/Paris"),
2373cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                finder.lookupTimeZonesByCountry("gb"));
2383cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
2393cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        finder = validate("<timezones>\n"
2403cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  <countryzones>\n"
2413cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    <country code=\"gb\">\n"
2423cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      <id>Europe/London</id>\n"
2433cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    </country>\n"
2443cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    " + unexpectedElement
2453cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  </countryzones>\n"
2463cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "</timezones>\n");
2473cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
2483cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
2493cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // This test is important because it ensures we can extend the format in future with
2503cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // more information.
2513cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        finder = validate("<timezones>\n"
2523cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  <countryzones>\n"
2533cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    <country code=\"gb\">\n"
2543cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      <id>Europe/London</id>\n"
2553cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    </country>\n"
2563cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  </countryzones>\n"
2573cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  " + unexpectedElement
2583cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "</timezones>\n");
2593cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
2603cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    }
2613cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
2623cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    @Test
2633cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    public void xmlParsing_unexpectedTextIgnored() throws Exception {
2643cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        String unexpectedText = "unexpected-text";
2653cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        TimeZoneFinder finder = validate("<timezones>\n"
2663cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  " + unexpectedText
2673cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  <countryzones>\n"
2683cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    <country code=\"gb\">\n"
2693cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      <id>Europe/London</id>\n"
2703cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    </country>\n"
2713cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  </countryzones>\n"
2723cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "</timezones>\n");
2733cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
2743cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
2753cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        finder = validate("<timezones>\n"
2763cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  <countryzones>\n"
2773cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    " + unexpectedText
2783cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    <country code=\"gb\">\n"
2793cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      <id>Europe/London</id>\n"
2803cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    </country>\n"
2813cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  </countryzones>\n"
2823cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "</timezones>\n");
2833cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
2843cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
2853cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        finder = validate("<timezones>\n"
2863cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  <countryzones>\n"
2873cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    <country code=\"gb\">\n"
2883cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      " + unexpectedText
2893cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      <id>Europe/London</id>\n"
2903cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    </country>\n"
2913cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  </countryzones>\n"
2923cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "</timezones>\n");
2933cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
2943cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
2953cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        finder = validate("<timezones>\n"
2963cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  <countryzones>\n"
2973cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    <country code=\"gb\">\n"
2983cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      <id>Europe/London</id>\n"
2993cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      " + unexpectedText
3003cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      <id>Europe/Paris</id>\n"
3013cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    </country>\n"
3023cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  </countryzones>\n"
3033cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "</timezones>\n");
3043cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZonesEqual(zones("Europe/London", "Europe/Paris"),
3053cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                finder.lookupTimeZonesByCountry("gb"));
3063cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    }
3073cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
3083cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    @Test
3093cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    public void xmlParsing_truncatedInput() throws Exception {
3103cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        checkValidateThrowsParserException("<timezones>\n");
3113cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
3123cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        checkValidateThrowsParserException("<timezones>\n"
3133cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  <countryzones>\n");
3143cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
3153cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        checkValidateThrowsParserException("<timezones>\n"
3163cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  <countryzones>\n"
3173cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    <country code=\"gb\">\n");
3183cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
3193cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        checkValidateThrowsParserException("<timezones>\n"
3203cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  <countryzones>\n"
3213cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    <country code=\"gb\">\n"
3223cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      <id>Europe/London</id>\n");
3233cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
3243cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        checkValidateThrowsParserException("<timezones>\n"
3253cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  <countryzones>\n"
3263cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    <country code=\"gb\">\n"
3273cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      <id>Europe/London</id>\n"
3283cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    </country>\n");
3293cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
3303cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        checkValidateThrowsParserException("<timezones>\n"
3313cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  <countryzones>\n"
3323cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    <country code=\"gb\">\n"
3333cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      <id>Europe/London</id>\n"
3343cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    </country>\n"
3353cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  </countryzones>\n");
3363cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    }
3373cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
3383cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    @Test
3393cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    public void xmlParsing_unexpectedChildInTimeZoneIdThrows() throws Exception {
3403cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        checkValidateThrowsParserException("<timezones>\n"
3413cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  <countryzones>\n"
3423cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    <country code=\"gb\">\n"
3433cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      <id><unexpected-element /></id>\n"
3443cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    </country>\n"
3453cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  </countryzones>\n"
3463cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "</timezones>\n");
3473cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    }
3483cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
3493cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    @Test
3503cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    public void xmlParsing_unknownTimeZoneIdIgnored() throws Exception {
3513cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        TimeZoneFinder finder = validate("<timezones>\n"
3523cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  <countryzones>\n"
3533cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    <country code=\"gb\">\n"
3543cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      <id>Unknown_Id</id>\n"
3553cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      <id>Europe/London</id>\n"
3563cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    </country>\n"
3573cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  </countryzones>\n"
3583cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "</timezones>\n");
3593cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
3603cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    }
3613cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
3623cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    @Test
3633cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    public void xmlParsing_missingCountryCode() throws Exception {
3643cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        checkValidateThrowsParserException("<timezones>\n"
3653cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  <countryzones>\n"
3663cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    <country>\n"
3673cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      <id>Europe/London</id>\n"
3683cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    </country>\n"
3693cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  </countryzones>\n"
3703cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "</timezones>\n");
3713cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    }
3723cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
3733cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    @Test
3743cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    public void xmlParsing_unknownCountryReturnsNull() throws Exception {
3753cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        TimeZoneFinder finder = validate("<timezones>\n"
3763cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  <countryzones>\n"
3773cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  </countryzones>\n"
3783cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "</timezones>\n");
3793cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertNull(finder.lookupTimeZonesByCountry("gb"));
3803cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    }
3813cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
3823cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    @Test
3833cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    public void lookupTimeZonesByCountry_structuresAreImmutable() throws Exception {
3843cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        TimeZoneFinder finder = validate("<timezones>\n"
3853cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  <countryzones>\n"
3863cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    <country code=\"gb\">\n"
3873cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      <id>Europe/London</id>\n"
3883cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    </country>\n"
3893cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  </countryzones>\n"
3903cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "</timezones>\n");
3913cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
3923cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        List<TimeZone> gbList = finder.lookupTimeZonesByCountry("gb");
3933cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertEquals(1, gbList.size());
3943cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertImmutableList(gbList);
3953cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertImmutableTimeZone(gbList.get(0));
3963cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
3973cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertNull(finder.lookupTimeZonesByCountry("unknown"));
3983cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    }
3993cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
4003cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    @Test
4013cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    public void lookupTimeZoneByCountryAndOffset_unknownCountry() throws Exception {
4023cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        TimeZoneFinder finder = validate("<timezones>\n"
4033cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  <countryzones>\n"
4043cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    <country code=\"xx\">\n"
4053cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      <id>Europe/London</id>\n"
4063cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    </country>\n"
4073cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  </countryzones>\n"
4083cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "</timezones>\n");
4093cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
4103cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // Demonstrate the arguments work for a known country.
4113cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZoneEquals(LONDON_TZ,
4123cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_DST_OFFSET_MILLIS,
4133cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                        true /* isDst */, WHEN_DST, null /* bias */));
4143cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
4153cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // Test with an unknown country.
4163cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        String unknownCountryCode = "yy";
4173cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertNull(finder.lookupTimeZoneByCountryAndOffset(unknownCountryCode,
4183cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                LONDON_DST_OFFSET_MILLIS, true /* isDst */, WHEN_DST, null /* bias */));
4193cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
4203cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertNull(finder.lookupTimeZoneByCountryAndOffset(unknownCountryCode,
4213cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                LONDON_DST_OFFSET_MILLIS, true /* isDst */, WHEN_DST, LONDON_TZ /* bias */));
4223cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    }
4233cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
4243cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    @Test
4253cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    public void lookupTimeZoneByCountryAndOffset_oneCandidate() throws Exception {
4263cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        TimeZoneFinder finder = validate("<timezones>\n"
4273cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  <countryzones>\n"
4283cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    <country code=\"xx\">\n"
4293cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      <id>Europe/London</id>\n"
4303cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    </country>\n"
4313cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  </countryzones>\n"
4323cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "</timezones>\n");
4333cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
4343cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // The three parameters match the configured zone: offset, isDst and when.
4353cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZoneEquals(LONDON_TZ,
4363cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_DST_OFFSET_MILLIS,
4373cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                        true /* isDst */, WHEN_DST, null /* bias */));
4383cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZoneEquals(LONDON_TZ,
4393cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_NO_DST_OFFSET_MILLIS,
4403cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                        false /* isDst */, WHEN_NO_DST, null /* bias */));
4413cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
4423cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // Some lookup failure cases where the offset, isDst and when do not match the configured
4433cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // zone.
4443cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        TimeZone noDstMatch1 = finder.lookupTimeZoneByCountryAndOffset("xx",
4453cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                LONDON_DST_OFFSET_MILLIS, true /* isDst */, WHEN_NO_DST, null /* bias */);
4463cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertNull(noDstMatch1);
4473cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
4483cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        TimeZone noDstMatch2 = finder.lookupTimeZoneByCountryAndOffset("xx",
4493cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                LONDON_DST_OFFSET_MILLIS, false /* isDst */, WHEN_NO_DST, null /* bias */);
4503cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertNull(noDstMatch2);
4513cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
4523cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        TimeZone noDstMatch3 = finder.lookupTimeZoneByCountryAndOffset("xx",
4533cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                LONDON_NO_DST_OFFSET_MILLIS, true /* isDst */, WHEN_DST, null /* bias */);
4543cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertNull(noDstMatch3);
4553cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
4563cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        TimeZone noDstMatch4 = finder.lookupTimeZoneByCountryAndOffset("xx",
4573cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                LONDON_NO_DST_OFFSET_MILLIS, true /* isDst */, WHEN_NO_DST, null /* bias */);
4583cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertNull(noDstMatch4);
4593cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
4603cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        TimeZone noDstMatch5 = finder.lookupTimeZoneByCountryAndOffset("xx",
4613cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                LONDON_DST_OFFSET_MILLIS, false /* isDst */, WHEN_DST, null /* bias */);
4623cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertNull(noDstMatch5);
4633cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
4643cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        TimeZone noDstMatch6 = finder.lookupTimeZoneByCountryAndOffset("xx",
4653cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                LONDON_NO_DST_OFFSET_MILLIS, false /* isDst */, WHEN_DST, null /* bias */);
4663cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertNull(noDstMatch6);
4673cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
4683cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // Some bias cases below.
4693cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
4703cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // The bias is irrelevant here: it matches what would be returned anyway.
4713cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZoneEquals(LONDON_TZ,
4723cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_DST_OFFSET_MILLIS,
4733cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                        true /* isDst */, WHEN_DST, LONDON_TZ /* bias */));
4743cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZoneEquals(LONDON_TZ,
4753cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_NO_DST_OFFSET_MILLIS,
4763cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                        false /* isDst */, WHEN_NO_DST, LONDON_TZ /* bias */));
4773cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // A sample of a non-matching case with bias.
4783cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertNull(finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_DST_OFFSET_MILLIS,
4793cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                true /* isDst */, WHEN_NO_DST, LONDON_TZ /* bias */));
4803cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
4813cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // The bias should be ignored: it doesn't match any of the country's zones.
4823cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZoneEquals(LONDON_TZ,
4833cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_DST_OFFSET_MILLIS,
4843cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                        true /* isDst */, WHEN_DST, NEW_YORK_TZ /* bias */));
4853cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
4863cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // The bias should still be ignored even though it matches the offset information given:
4873cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // it doesn't match any of the country's configured zones.
4883cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertNull(finder.lookupTimeZoneByCountryAndOffset("xx", NEW_YORK_DST_OFFSET_MILLIS,
4893cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                true /* isDst */, WHEN_DST, NEW_YORK_TZ /* bias */));
4903cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    }
4913cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
4923cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    @Test
4933cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    public void lookupTimeZoneByCountryAndOffset_multipleNonOverlappingCandidates()
4943cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller            throws Exception {
4953cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        TimeZoneFinder finder = validate("<timezones>\n"
4963cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  <countryzones>\n"
4973cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    <country code=\"xx\">\n"
4983cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      <id>America/New_York</id>\n"
4993cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      <id>Europe/London</id>\n"
5003cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    </country>\n"
5013cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  </countryzones>\n"
5023cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "</timezones>\n");
5033cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
5043cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // The three parameters match the configured zone: offset, isDst and when.
5053cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZoneEquals(LONDON_TZ, finder.lookupTimeZoneByCountryAndOffset("xx",
5063cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                LONDON_DST_OFFSET_MILLIS, true /* isDst */, WHEN_DST, null /* bias */));
5073cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZoneEquals(LONDON_TZ, finder.lookupTimeZoneByCountryAndOffset("xx",
5083cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                LONDON_NO_DST_OFFSET_MILLIS, false /* isDst */, WHEN_NO_DST, null /* bias */));
5093cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZoneEquals(NEW_YORK_TZ, finder.lookupTimeZoneByCountryAndOffset("xx",
5103cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                NEW_YORK_DST_OFFSET_MILLIS, true /* isDst */, WHEN_DST, null /* bias */));
5113cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZoneEquals(NEW_YORK_TZ, finder.lookupTimeZoneByCountryAndOffset("xx",
5123cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                NEW_YORK_NO_DST_OFFSET_MILLIS, false /* isDst */, WHEN_NO_DST, null /* bias */));
5133cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
5143cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // Some lookup failure cases where the offset, isDst and when do not match the configured
5153cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // zone. This is a sample, not complete.
5163cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        TimeZone noDstMatch1 = finder.lookupTimeZoneByCountryAndOffset("xx",
5173cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                LONDON_DST_OFFSET_MILLIS, true /* isDst */, WHEN_NO_DST, null /* bias */);
5183cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertNull(noDstMatch1);
5193cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
5203cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        TimeZone noDstMatch2 = finder.lookupTimeZoneByCountryAndOffset("xx",
5213cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                LONDON_DST_OFFSET_MILLIS, false /* isDst */, WHEN_NO_DST, null /* bias */);
5223cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertNull(noDstMatch2);
5233cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
5243cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        TimeZone noDstMatch3 = finder.lookupTimeZoneByCountryAndOffset("xx",
5253cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                NEW_YORK_NO_DST_OFFSET_MILLIS, true /* isDst */, WHEN_DST, null /* bias */);
5263cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertNull(noDstMatch3);
5273cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
5283cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        TimeZone noDstMatch4 = finder.lookupTimeZoneByCountryAndOffset("xx",
5293cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                NEW_YORK_NO_DST_OFFSET_MILLIS, true /* isDst */, WHEN_NO_DST, null /* bias */);
5303cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertNull(noDstMatch4);
5313cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
5323cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        TimeZone noDstMatch5 = finder.lookupTimeZoneByCountryAndOffset("xx",
5333cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                LONDON_DST_OFFSET_MILLIS, false /* isDst */, WHEN_DST, null /* bias */);
5343cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertNull(noDstMatch5);
5353cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
5363cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        TimeZone noDstMatch6 = finder.lookupTimeZoneByCountryAndOffset("xx",
5373cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                LONDON_NO_DST_OFFSET_MILLIS, false /* isDst */, WHEN_DST, null /* bias */);
5383cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertNull(noDstMatch6);
5393cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
5403cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // Some bias cases below.
5413cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
5423cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // The bias is irrelevant here: it matches what would be returned anyway.
5433cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZoneEquals(LONDON_TZ,
5443cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_DST_OFFSET_MILLIS,
5453cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                        true /* isDst */, WHEN_DST, LONDON_TZ /* bias */));
5463cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZoneEquals(LONDON_TZ,
5473cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_NO_DST_OFFSET_MILLIS,
5483cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                        false /* isDst */, WHEN_NO_DST, LONDON_TZ /* bias */));
5493cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // A sample of a non-matching case with bias.
5503cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertNull(finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_DST_OFFSET_MILLIS,
5513cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                true /* isDst */, WHEN_NO_DST, LONDON_TZ /* bias */));
5523cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
5533cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // The bias should be ignored: it matches a configured zone, but the offset is wrong so
5543cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // should not be considered a match.
5553cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZoneEquals(LONDON_TZ,
5563cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_DST_OFFSET_MILLIS,
5573cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                        true /* isDst */, WHEN_DST, NEW_YORK_TZ /* bias */));
5583cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    }
5593cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
5603cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    // This is an artificial case very similar to America/Denver and America/Phoenix in the US: both
5613cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    // have the same offset for 6 months of the year but diverge. Australia/Lord_Howe too.
5623cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    @Test
5633cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    public void lookupTimeZoneByCountryAndOffset_multipleOverlappingCandidates() throws Exception {
5643cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // Three zones that have the same offset for some of the year. Europe/London changes
5653cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // offset WHEN_DST, the others do not.
5663cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        TimeZoneFinder finder = validate("<timezones>\n"
5673cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  <countryzones>\n"
5683cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    <country code=\"xx\">\n"
5693cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      <id>Atlantic/Reykjavik</id>\n"
5703cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      <id>Europe/London</id>\n"
5713cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "      <id>Etc/UTC</id>\n"
5723cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "    </country>\n"
5733cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "  </countryzones>\n"
5743cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                + "</timezones>\n");
5753cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
5763cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // This is the no-DST offset for LONDON_TZ, REYKJAVIK_TZ. UTC_TZ.
5773cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        final int noDstOffset = LONDON_NO_DST_OFFSET_MILLIS;
5783cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // This is the DST offset for LONDON_TZ.
5793cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        final int dstOffset = LONDON_DST_OFFSET_MILLIS;
5803cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
5813cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // The three parameters match the configured zone: offset, isDst and when.
5823cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZoneEquals(LONDON_TZ, finder.lookupTimeZoneByCountryAndOffset("xx", dstOffset,
5833cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                true /* isDst */, WHEN_DST, null /* bias */));
5843cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZoneEquals(REYKJAVIK_TZ, finder.lookupTimeZoneByCountryAndOffset("xx", noDstOffset,
5853cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                false /* isDst */, WHEN_NO_DST, null /* bias */));
5863cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZoneEquals(LONDON_TZ, finder.lookupTimeZoneByCountryAndOffset("xx", dstOffset,
5873cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                true /* isDst */, WHEN_DST, null /* bias */));
5883cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZoneEquals(REYKJAVIK_TZ, finder.lookupTimeZoneByCountryAndOffset("xx", noDstOffset,
5893cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                false /* isDst */, WHEN_NO_DST, null /* bias */));
5903cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZoneEquals(REYKJAVIK_TZ, finder.lookupTimeZoneByCountryAndOffset("xx", noDstOffset,
5913cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                false /* isDst */, WHEN_DST, null /* bias */));
5923cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
5933cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // Some lookup failure cases where the offset, isDst and when do not match the configured
5943cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // zones.
5953cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        TimeZone noDstMatch1 = finder.lookupTimeZoneByCountryAndOffset("xx", dstOffset,
5963cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                true /* isDst */, WHEN_NO_DST, null /* bias */);
5973cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertNull(noDstMatch1);
5983cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
5993cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        TimeZone noDstMatch2 = finder.lookupTimeZoneByCountryAndOffset("xx", noDstOffset,
6003cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                true /* isDst */, WHEN_DST, null /* bias */);
6013cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertNull(noDstMatch2);
6023cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
6033cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        TimeZone noDstMatch3 = finder.lookupTimeZoneByCountryAndOffset("xx", noDstOffset,
6043cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                true /* isDst */, WHEN_NO_DST, null /* bias */);
6053cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertNull(noDstMatch3);
6063cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
6073cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        TimeZone noDstMatch4 = finder.lookupTimeZoneByCountryAndOffset("xx", dstOffset,
6083cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                false /* isDst */, WHEN_DST, null /* bias */);
6093cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertNull(noDstMatch4);
6103cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
6113cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
6123cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // Some bias cases below.
6133cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
6143cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // The bias is relevant here: it overrides what would be returned naturally.
6153cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZoneEquals(REYKJAVIK_TZ, finder.lookupTimeZoneByCountryAndOffset("xx", noDstOffset,
6163cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                false /* isDst */, WHEN_NO_DST, null /* bias */));
6173cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZoneEquals(LONDON_TZ, finder.lookupTimeZoneByCountryAndOffset("xx", noDstOffset,
6183cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                false /* isDst */, WHEN_NO_DST, LONDON_TZ /* bias */));
6193cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZoneEquals(UTC_TZ, finder.lookupTimeZoneByCountryAndOffset("xx", noDstOffset,
6203cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                false /* isDst */, WHEN_NO_DST, UTC_TZ /* bias */));
6213cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
6223cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // The bias should be ignored: it matches a configured zone, but the offset is wrong so
6233cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // should not be considered a match.
6243cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertZoneEquals(LONDON_TZ, finder.lookupTimeZoneByCountryAndOffset("xx",
6253cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                LONDON_DST_OFFSET_MILLIS, true /* isDst */, WHEN_DST, REYKJAVIK_TZ /* bias */));
6263cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    }
6273cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
6283cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    @Test
6293cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    public void consistencyTest() throws Exception {
6303cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // Confirm that no new zones have been added to zones.tab without also adding them to the
6313cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // configuration used to drive TimeZoneFinder.
6323cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
6333cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // zone.tab is a tab separated ASCII file provided by IANA and included in Android's tzdata
6343cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // file. Each line contains a mapping from country code -> zone ID. The ordering used by
6353cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // TimeZoneFinder is Android-specific, but we can use zone.tab to make sure we know about
6363cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // all country zones. Any update to tzdata that adds, renames, or removes zones should be
6373cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // reflected in the file used by TimeZoneFinder.
6383cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        Map<String, Set<String>> zoneTabMappings = new HashMap<>();
6393cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        for (String line : ZoneInfoDB.getInstance().getZoneTab().split("\n")) {
6403cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller            int countryCodeEnd = line.indexOf('\t', 1);
6413cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller            int olsonIdStart = line.indexOf('\t', 4) + 1;
6423cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller            int olsonIdEnd = line.indexOf('\t', olsonIdStart);
6433cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller            if (olsonIdEnd == -1) {
6443cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                olsonIdEnd = line.length(); // Not all zone.tab lines have a comment.
6453cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller            }
6463cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller            String countryCode = line.substring(0, countryCodeEnd);
6473cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller            String olsonId = line.substring(olsonIdStart, olsonIdEnd);
6483cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller            Set<String> zoneIds = zoneTabMappings.get(countryCode);
6493cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller            if (zoneIds == null) {
6503cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                zoneIds = new HashSet<>();
6513cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                zoneTabMappings.put(countryCode, zoneIds);
6523cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller            }
6533cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller            zoneIds.add(olsonId);
6543cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        }
6553cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
6563cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        TimeZoneFinder timeZoneFinder = TimeZoneFinder.getInstance();
6573cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        for (Map.Entry<String, Set<String>> countryEntry : zoneTabMappings.entrySet()) {
6583cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller            String countryCode = countryEntry.getKey();
6593cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller            // Android uses lower case, IANA uses upper.
6603cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller            countryCode = countryCode.toLowerCase();
6613cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
6623cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller            List<String> ianaZoneIds = countryEntry.getValue().stream().sorted()
6633cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                    .collect(Collectors.toList());
6643cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller            List<TimeZone> androidZones = timeZoneFinder.lookupTimeZonesByCountry(countryCode);
6653cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller            List<String> androidZoneIds =
6663cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                    androidZones.stream().map(TimeZone::getID).sorted()
6673cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                            .collect(Collectors.toList());
6683cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
6693cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller            assertEquals("Android zones for " + countryCode + " do not match IANA data",
6703cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller                    ianaZoneIds, androidZoneIds);
6713cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        }
6723cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    }
6733cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
6743cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    private void assertImmutableTimeZone(TimeZone timeZone) {
6753cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        try {
6763cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller            timeZone.setRawOffset(1000);
6773cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller            fail();
6783cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        } catch (UnsupportedOperationException expected) {
6793cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        }
6803cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    }
6813cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
6823cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    private static void assertImmutableList(List<TimeZone> timeZones) {
6833cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        try {
6843cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller            timeZones.add(null);
6853cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller            fail();
6863cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        } catch (UnsupportedOperationException expected) {
6873cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        }
6883cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    }
6893cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
6903cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    private static void assertZoneEquals(TimeZone expected, TimeZone actual) {
6913cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // TimeZone.equals() only checks the ID, but that's ok for these tests.
6923cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertEquals(expected, actual);
6933cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    }
6943cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
6953cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    private static void assertZonesEqual(List<TimeZone> expected, List<TimeZone> actual) {
6963cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        // TimeZone.equals() only checks the ID, but that's ok for these tests.
6973cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        assertEquals(expected, actual);
6983cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    }
6993cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
7003cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    private static void checkValidateThrowsParserException(String xml) throws Exception {
7013cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        try {
7023cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller            validate(xml);
7033cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller            fail();
7043cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        } catch (IOException expected) {
7053cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        }
7063cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    }
7073cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
7083cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    private static TimeZoneFinder validate(String xml) throws IOException {
7093cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        TimeZoneFinder timeZoneFinder = TimeZoneFinder.createInstanceForTests(xml);
7103cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        timeZoneFinder.validate();
7113cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        return timeZoneFinder;
7123cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    }
7133cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
7143cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    private static List<TimeZone> zones(String... ids) {
7153cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        return Arrays.stream(ids).map(TimeZone::getTimeZone).collect(Collectors.toList());
7163cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    }
7173cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
7183cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    private String createFile(String fileContent) throws IOException {
7193cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        Path filePath = Files.createTempFile(testDir, null, null);
7203cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        Files.write(filePath, fileContent.getBytes(StandardCharsets.UTF_8));
7213cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        return filePath.toString();
7223cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    }
7233cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller
7243cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    private String createMissingFile() throws IOException {
7253cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        Path filePath = Files.createTempFile(testDir, null, null);
7263cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        Files.delete(filePath);
7273cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller        return filePath.toString();
7283cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller    }
7293cbfb6e3a3d8c50b6512df250b24dbfffbf6d3a7Neil Fuller}
730