Home | History | Annotate | Download | only in util
      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 libcore.libcore.util;
     18 
     19 import org.junit.Test;
     20 
     21 import android.icu.util.TimeZone;
     22 
     23 import java.util.Arrays;
     24 import java.util.List;
     25 import java.util.stream.Collectors;
     26 import libcore.util.CountryTimeZones;
     27 import libcore.util.CountryTimeZones.OffsetResult;
     28 import libcore.util.CountryTimeZones.TimeZoneMapping;
     29 
     30 import static org.junit.Assert.assertEquals;
     31 import static org.junit.Assert.assertFalse;
     32 import static org.junit.Assert.assertNull;
     33 import static org.junit.Assert.assertTrue;
     34 import static org.junit.Assert.fail;
     35 
     36 public class CountryTimeZonesTest {
     37 
     38     private static final int HOUR_MILLIS = 60 * 60 * 1000;
     39 
     40     private static final String INVALID_TZ_ID = "Moon/Tranquility_Base";
     41 
     42     // Zones used in the tests. NEW_YORK_TZ and LONDON_TZ chosen because they never overlap but both
     43     // have DST.
     44     private static final TimeZone NEW_YORK_TZ = TimeZone.getTimeZone("America/New_York");
     45     private static final TimeZone LONDON_TZ = TimeZone.getTimeZone("Europe/London");
     46     // A zone that matches LONDON_TZ for WHEN_NO_DST. It does not have DST so differs for WHEN_DST.
     47     private static final TimeZone REYKJAVIK_TZ = TimeZone.getTimeZone("Atlantic/Reykjavik");
     48     // Another zone that matches LONDON_TZ for WHEN_NO_DST. It does not have DST so differs for
     49     // WHEN_DST.
     50     private static final TimeZone UTC_TZ = TimeZone.getTimeZone("Etc/UTC");
     51 
     52     // 22nd July 2017, 13:14:15 UTC (DST time in all the timezones used in these tests that observe
     53     // DST).
     54     private static final long WHEN_DST = 1500729255000L;
     55     // 22nd January 2018, 13:14:15 UTC (non-DST time in all timezones used in these tests).
     56     private static final long WHEN_NO_DST = 1516626855000L;
     57 
     58     // The offset applied to most zones during DST.
     59     private static final int NORMAL_DST_ADJUSTMENT = HOUR_MILLIS;
     60 
     61     private static final int LONDON_NO_DST_OFFSET_MILLIS = 0;
     62     private static final int LONDON_DST_OFFSET_MILLIS = LONDON_NO_DST_OFFSET_MILLIS
     63             + NORMAL_DST_ADJUSTMENT;
     64 
     65     private static final int NEW_YORK_NO_DST_OFFSET_MILLIS = -5 * HOUR_MILLIS;
     66     private static final int NEW_YORK_DST_OFFSET_MILLIS = NEW_YORK_NO_DST_OFFSET_MILLIS
     67             + NORMAL_DST_ADJUSTMENT;
     68 
     69     @Test
     70     public void createValidated() throws Exception {
     71         CountryTimeZones countryTimeZones = CountryTimeZones.createValidated(
     72                 "gb", "Europe/London", true /* everUsesUtc */, timeZoneMappings("Europe/London"),
     73                 "test");
     74         assertTrue(countryTimeZones.isForCountryCode("gb"));
     75         assertEquals("Europe/London", countryTimeZones.getDefaultTimeZoneId());
     76         assertZoneEquals(zone("Europe/London"), countryTimeZones.getDefaultTimeZone());
     77         assertEquals(timeZoneMappings("Europe/London"), countryTimeZones.getTimeZoneMappings());
     78         assertZonesEqual(zones("Europe/London"), countryTimeZones.getIcuTimeZones());
     79     }
     80 
     81     @Test
     82     public void createValidated_nullDefault() throws Exception {
     83         CountryTimeZones countryTimeZones = CountryTimeZones.createValidated(
     84                 "gb", null, true /* everUsesUtc */, timeZoneMappings("Europe/London"), "test");
     85         assertNull(countryTimeZones.getDefaultTimeZoneId());
     86     }
     87 
     88     @Test
     89     public void createValidated_invalidDefault() throws Exception {
     90         CountryTimeZones countryTimeZones = CountryTimeZones.createValidated(
     91                 "gb", INVALID_TZ_ID, true /* everUsesUtc */,
     92                 timeZoneMappings("Europe/London", INVALID_TZ_ID), "test");
     93         assertNull(countryTimeZones.getDefaultTimeZoneId());
     94         assertEquals(timeZoneMappings("Europe/London"), countryTimeZones.getTimeZoneMappings());
     95         assertZonesEqual(zones("Europe/London"), countryTimeZones.getIcuTimeZones());
     96     }
     97 
     98     @Test
     99     public void createValidated_unknownTimeZoneIdIgnored() throws Exception {
    100         CountryTimeZones countryTimeZones = CountryTimeZones.createValidated(
    101                 "gb", "Europe/London", true /* everUsesUtc */,
    102                 timeZoneMappings("Unknown_Id", "Europe/London"), "test");
    103         assertEquals(timeZoneMappings("Europe/London"), countryTimeZones.getTimeZoneMappings());
    104         assertZonesEqual(zones("Europe/London"), countryTimeZones.getIcuTimeZones());
    105     }
    106 
    107     @Test
    108     public void isForCountryCode() throws Exception {
    109         CountryTimeZones countryTimeZones = CountryTimeZones.createValidated(
    110                 "gb", "Europe/London", true /* everUsesUtc */, timeZoneMappings("Europe/London"),
    111                 "test");
    112         assertTrue(countryTimeZones.isForCountryCode("GB"));
    113         assertTrue(countryTimeZones.isForCountryCode("Gb"));
    114         assertTrue(countryTimeZones.isForCountryCode("gB"));
    115     }
    116 
    117     @Test
    118     public void structuresAreImmutable() throws Exception {
    119         CountryTimeZones countryTimeZones = CountryTimeZones.createValidated(
    120                 "gb", "Europe/London", true /* everUsesUtc */, timeZoneMappings("Europe/London"),
    121                 "test");
    122 
    123         assertImmutableTimeZone(countryTimeZones.getDefaultTimeZone());
    124 
    125         List<TimeZone> tzList = countryTimeZones.getIcuTimeZones();
    126         assertEquals(1, tzList.size());
    127         assertImmutableList(tzList);
    128         assertImmutableTimeZone(tzList.get(0));
    129 
    130         List<TimeZoneMapping> timeZoneMappings = countryTimeZones.getTimeZoneMappings();
    131         assertEquals(1, timeZoneMappings.size());
    132         assertImmutableList(timeZoneMappings);
    133     }
    134 
    135     @Test
    136     public void lookupByOffsetWithBiasDeprecated_oneCandidate() throws Exception {
    137         CountryTimeZones countryTimeZones = CountryTimeZones.createValidated(
    138                 "gb", "Europe/London", true /* everUsesUtc */, timeZoneMappings("Europe/London"), "test");
    139 
    140         OffsetResult expectedResult = new OffsetResult(LONDON_TZ, true /* oneMatch */);
    141 
    142         // The three parameters match the configured zone: offset, isDst and time.
    143         assertOffsetResultEquals(expectedResult,
    144                 countryTimeZones.lookupByOffsetWithBias(LONDON_DST_OFFSET_MILLIS,
    145                         true /* isDst */, WHEN_DST, null /* bias */));
    146         assertOffsetResultEquals(expectedResult,
    147                 countryTimeZones.lookupByOffsetWithBias(LONDON_NO_DST_OFFSET_MILLIS,
    148                         false /* isDst */, WHEN_NO_DST, null /* bias */));
    149 
    150         // Some lookup failure cases where the offset, isDst and time do not match the configured
    151         // zone.
    152         OffsetResult noDstMatch1 = countryTimeZones.lookupByOffsetWithBias(
    153                 LONDON_DST_OFFSET_MILLIS, true /* isDst */, WHEN_NO_DST, null /* bias */);
    154         assertNull(noDstMatch1);
    155 
    156         OffsetResult noDstMatch2 = countryTimeZones.lookupByOffsetWithBias(
    157                 LONDON_DST_OFFSET_MILLIS, false /* isDst */, WHEN_NO_DST, null /* bias */);
    158         assertNull(noDstMatch2);
    159 
    160         OffsetResult noDstMatch3 = countryTimeZones.lookupByOffsetWithBias(
    161                 LONDON_NO_DST_OFFSET_MILLIS, true /* isDst */, WHEN_DST, null /* bias */);
    162         assertNull(noDstMatch3);
    163 
    164         OffsetResult noDstMatch4 = countryTimeZones.lookupByOffsetWithBias(
    165                 LONDON_NO_DST_OFFSET_MILLIS, true /* isDst */, WHEN_NO_DST, null /* bias */);
    166         assertNull(noDstMatch4);
    167 
    168         OffsetResult noDstMatch5 = countryTimeZones.lookupByOffsetWithBias(
    169                 LONDON_DST_OFFSET_MILLIS, false /* isDst */, WHEN_DST, null /* bias */);
    170         assertNull(noDstMatch5);
    171 
    172         OffsetResult noDstMatch6 = countryTimeZones.lookupByOffsetWithBias(
    173                 LONDON_NO_DST_OFFSET_MILLIS, false /* isDst */, WHEN_DST, null /* bias */);
    174         assertNull(noDstMatch6);
    175 
    176         // Some bias cases below.
    177 
    178         // The bias is irrelevant here: it matches what would be returned anyway.
    179         assertOffsetResultEquals(expectedResult,
    180                 countryTimeZones.lookupByOffsetWithBias(LONDON_DST_OFFSET_MILLIS,
    181                         true /* isDst */, WHEN_DST, LONDON_TZ /* bias */));
    182         assertOffsetResultEquals(expectedResult,
    183                 countryTimeZones.lookupByOffsetWithBias(LONDON_NO_DST_OFFSET_MILLIS,
    184                         false /* isDst */, WHEN_NO_DST, LONDON_TZ /* bias */));
    185         // A sample of a non-matching case with bias.
    186         assertNull(countryTimeZones.lookupByOffsetWithBias(LONDON_DST_OFFSET_MILLIS,
    187                 true /* isDst */, WHEN_NO_DST, LONDON_TZ /* bias */));
    188 
    189         // The bias should be ignored: it doesn't match any of the country's zones.
    190         assertOffsetResultEquals(expectedResult,
    191                 countryTimeZones.lookupByOffsetWithBias(LONDON_DST_OFFSET_MILLIS,
    192                         true /* isDst */, WHEN_DST, NEW_YORK_TZ /* bias */));
    193 
    194         // The bias should still be ignored even though it matches the offset information given:
    195         // it doesn't match any of the country's configured zones.
    196         assertNull(countryTimeZones.lookupByOffsetWithBias(NEW_YORK_DST_OFFSET_MILLIS,
    197                 true /* isDst */, WHEN_DST, NEW_YORK_TZ /* bias */));
    198     }
    199 
    200     @Test
    201     public void lookupByOffsetWithBiasDeprecated_multipleNonOverlappingCandidates()
    202             throws Exception {
    203         CountryTimeZones countryTimeZones = CountryTimeZones.createValidated(
    204                 "xx", "Europe/London", true /* everUsesUtc */,
    205                 timeZoneMappings("America/New_York", "Europe/London"), "test");
    206 
    207         OffsetResult expectedLondonResult = new OffsetResult(LONDON_TZ, true /* oneMatch */);
    208         OffsetResult expectedNewYorkResult = new OffsetResult(NEW_YORK_TZ, true /* oneMatch */);
    209 
    210         // The three parameters match the configured zone: offset, isDst and time.
    211         assertOffsetResultEquals(expectedLondonResult, countryTimeZones.lookupByOffsetWithBias(
    212                 LONDON_DST_OFFSET_MILLIS, true /* isDst */, WHEN_DST, null /* bias */));
    213         assertOffsetResultEquals(expectedLondonResult, countryTimeZones.lookupByOffsetWithBias(
    214                 LONDON_NO_DST_OFFSET_MILLIS, false /* isDst */, WHEN_NO_DST, null /* bias */));
    215         assertOffsetResultEquals(expectedNewYorkResult, countryTimeZones.lookupByOffsetWithBias(
    216                 NEW_YORK_DST_OFFSET_MILLIS, true /* isDst */, WHEN_DST, null /* bias */));
    217         assertOffsetResultEquals(expectedNewYorkResult, countryTimeZones.lookupByOffsetWithBias(
    218                 NEW_YORK_NO_DST_OFFSET_MILLIS, false /* isDst */, WHEN_NO_DST, null /* bias */));
    219 
    220         // Some lookup failure cases where the offset, isDst and time do not match the configured
    221         // zone. This is a sample, not complete.
    222         OffsetResult noDstMatch1 = countryTimeZones.lookupByOffsetWithBias(
    223                 LONDON_DST_OFFSET_MILLIS, true /* isDst */, WHEN_NO_DST, null /* bias */);
    224         assertNull(noDstMatch1);
    225 
    226         OffsetResult noDstMatch2 = countryTimeZones.lookupByOffsetWithBias(
    227                 LONDON_DST_OFFSET_MILLIS, false /* isDst */, WHEN_NO_DST, null /* bias */);
    228         assertNull(noDstMatch2);
    229 
    230         OffsetResult noDstMatch3 = countryTimeZones.lookupByOffsetWithBias(
    231                 NEW_YORK_NO_DST_OFFSET_MILLIS, true /* isDst */, WHEN_DST, null /* bias */);
    232         assertNull(noDstMatch3);
    233 
    234         OffsetResult noDstMatch4 = countryTimeZones.lookupByOffsetWithBias(
    235                 NEW_YORK_NO_DST_OFFSET_MILLIS, true /* isDst */, WHEN_NO_DST, null /* bias */);
    236         assertNull(noDstMatch4);
    237 
    238         OffsetResult noDstMatch5 = countryTimeZones.lookupByOffsetWithBias(
    239                 LONDON_DST_OFFSET_MILLIS, false /* isDst */, WHEN_DST, null /* bias */);
    240         assertNull(noDstMatch5);
    241 
    242         OffsetResult noDstMatch6 = countryTimeZones.lookupByOffsetWithBias(
    243                 LONDON_NO_DST_OFFSET_MILLIS, false /* isDst */, WHEN_DST, null /* bias */);
    244         assertNull(noDstMatch6);
    245 
    246         // Some bias cases below.
    247 
    248         // The bias is irrelevant here: it matches what would be returned anyway.
    249         assertOffsetResultEquals(expectedLondonResult, countryTimeZones.lookupByOffsetWithBias(
    250                 LONDON_DST_OFFSET_MILLIS, true /* isDst */, WHEN_DST, LONDON_TZ /* bias */));
    251         assertOffsetResultEquals(expectedLondonResult, countryTimeZones.lookupByOffsetWithBias(
    252                 LONDON_NO_DST_OFFSET_MILLIS, false /* isDst */, WHEN_NO_DST, LONDON_TZ /* bias */));
    253         // A sample of a non-matching case with bias.
    254         assertNull(countryTimeZones.lookupByOffsetWithBias(
    255                 LONDON_DST_OFFSET_MILLIS, true /* isDst */, WHEN_NO_DST, LONDON_TZ /* bias */));
    256 
    257         // The bias should be ignored: it matches a configured zone, but the offset is wrong so
    258         // should not be considered a match.
    259         assertOffsetResultEquals(expectedLondonResult, countryTimeZones.lookupByOffsetWithBias(
    260                 LONDON_DST_OFFSET_MILLIS, true /* isDst */, WHEN_DST, NEW_YORK_TZ /* bias */));
    261     }
    262 
    263     // This is an artificial case very similar to America/Denver and America/Phoenix in the US: both
    264     // have the same offset for 6 months of the year but diverge. Australia/Lord_Howe too.
    265     @Test
    266     public void lookupByOffsetWithBiasDeprecated_multipleOverlappingCandidates() throws Exception {
    267         // Three zones that have the same offset for some of the year. Europe/London changes
    268         // offset WHEN_DST, the others do not.
    269         CountryTimeZones countryTimeZones = CountryTimeZones.createValidated(
    270                 "xx", "Europe/London", true /* everUsesUtc */,
    271                 timeZoneMappings("Atlantic/Reykjavik", "Europe/London", "Etc/UTC"), "test");
    272 
    273         // This is the no-DST offset for LONDON_TZ, REYKJAVIK_TZ. UTC_TZ.
    274         final int noDstOffset = LONDON_NO_DST_OFFSET_MILLIS;
    275         // This is the DST offset for LONDON_TZ.
    276         final int dstOffset = LONDON_DST_OFFSET_MILLIS;
    277 
    278         OffsetResult expectedLondonOnlyMatch = new OffsetResult(LONDON_TZ, true /* oneMatch */);
    279         OffsetResult expectedReykjavikBestMatch =
    280                 new OffsetResult(REYKJAVIK_TZ, false /* oneMatch */);
    281 
    282         // The three parameters match the configured zone: offset, isDst and when.
    283         assertOffsetResultEquals(expectedLondonOnlyMatch,
    284                 countryTimeZones.lookupByOffsetWithBias(dstOffset, true /* isDst */, WHEN_DST,
    285                         null /* bias */));
    286         assertOffsetResultEquals(expectedReykjavikBestMatch,
    287                 countryTimeZones.lookupByOffsetWithBias(noDstOffset, false /* isDst */, WHEN_NO_DST,
    288                         null /* bias */));
    289         assertOffsetResultEquals(expectedLondonOnlyMatch,
    290                 countryTimeZones.lookupByOffsetWithBias(dstOffset, true /* isDst */, WHEN_DST,
    291                         null /* bias */));
    292         assertOffsetResultEquals(expectedReykjavikBestMatch,
    293                 countryTimeZones.lookupByOffsetWithBias(noDstOffset, false /* isDst */, WHEN_NO_DST,
    294                         null /* bias */));
    295         assertOffsetResultEquals(expectedReykjavikBestMatch,
    296                 countryTimeZones.lookupByOffsetWithBias(noDstOffset, false /* isDst */, WHEN_DST,
    297                         null /* bias */));
    298 
    299         // Some lookup failure cases where the offset, isDst and time do not match the configured
    300         // zones.
    301         OffsetResult noDstMatch1 = countryTimeZones.lookupByOffsetWithBias(dstOffset,
    302                 true /* isDst */, WHEN_NO_DST, null /* bias */);
    303         assertNull(noDstMatch1);
    304 
    305         OffsetResult noDstMatch2 = countryTimeZones.lookupByOffsetWithBias(noDstOffset,
    306                 true /* isDst */, WHEN_DST, null /* bias */);
    307         assertNull(noDstMatch2);
    308 
    309         OffsetResult noDstMatch3 = countryTimeZones.lookupByOffsetWithBias(noDstOffset,
    310                 true /* isDst */, WHEN_NO_DST, null /* bias */);
    311         assertNull(noDstMatch3);
    312 
    313         OffsetResult noDstMatch4 = countryTimeZones.lookupByOffsetWithBias(dstOffset,
    314                 false /* isDst */, WHEN_DST, null /* bias */);
    315         assertNull(noDstMatch4);
    316 
    317 
    318         // Some bias cases below.
    319 
    320         // Multiple zones match but Reykjavik is the bias.
    321         assertOffsetResultEquals(expectedReykjavikBestMatch,
    322                 countryTimeZones.lookupByOffsetWithBias(noDstOffset, false /* isDst */, WHEN_NO_DST,
    323                         REYKJAVIK_TZ /* bias */));
    324 
    325         // Multiple zones match but London is the bias.
    326         OffsetResult expectedLondonBestMatch = new OffsetResult(LONDON_TZ, false /* oneMatch */);
    327         assertOffsetResultEquals(expectedLondonBestMatch,
    328                 countryTimeZones.lookupByOffsetWithBias(noDstOffset, false /* isDst */, WHEN_NO_DST,
    329                         LONDON_TZ /* bias */));
    330 
    331         // Multiple zones match but UTC is the bias.
    332         OffsetResult expectedUtcResult = new OffsetResult(UTC_TZ, false /* oneMatch */);
    333         assertOffsetResultEquals(expectedUtcResult,
    334                 countryTimeZones.lookupByOffsetWithBias(noDstOffset, false /* isDst */, WHEN_NO_DST,
    335                         UTC_TZ /* bias */));
    336 
    337         // The bias should be ignored: it matches a configured zone, but the offset is wrong so
    338         // should not be considered a match.
    339         assertOffsetResultEquals(expectedLondonOnlyMatch,
    340                 countryTimeZones.lookupByOffsetWithBias(LONDON_DST_OFFSET_MILLIS, true /* isDst */,
    341                         WHEN_DST, REYKJAVIK_TZ /* bias */));
    342     }
    343 
    344     @Test
    345     public void lookupByOffsetWithBias_oneCandidate() throws Exception {
    346         CountryTimeZones countryTimeZones = CountryTimeZones.createValidated(
    347                 "gb", "Europe/London", true /* uses UTC */, timeZoneMappings("Europe/London"),
    348                 "test");
    349 
    350         OffsetResult expectedResult = new OffsetResult(LONDON_TZ, true /* oneMatch */);
    351 
    352         // The three parameters match the configured zone: offset, isDst and time.
    353         assertOffsetResultEquals(expectedResult,
    354                 countryTimeZones.lookupByOffsetWithBias(LONDON_DST_OFFSET_MILLIS,
    355                         NORMAL_DST_ADJUSTMENT, WHEN_DST, null /* bias */));
    356         assertOffsetResultEquals(expectedResult,
    357                 countryTimeZones.lookupByOffsetWithBias(LONDON_NO_DST_OFFSET_MILLIS,
    358                         0 /* no DST */, WHEN_NO_DST, null /* bias */));
    359         assertOffsetResultEquals(expectedResult,
    360                 countryTimeZones.lookupByOffsetWithBias(LONDON_DST_OFFSET_MILLIS,
    361                         null /* unknown DST */, WHEN_DST, null /* bias */));
    362         assertOffsetResultEquals(expectedResult,
    363                 countryTimeZones.lookupByOffsetWithBias(LONDON_NO_DST_OFFSET_MILLIS,
    364                         null /* unknown DST */, WHEN_NO_DST, null /* bias */));
    365 
    366         // Some lookup failure cases where the offset, DST offset and time do not match the
    367         // configured zone.
    368         OffsetResult noDstMatch1 = countryTimeZones.lookupByOffsetWithBias(
    369                 LONDON_DST_OFFSET_MILLIS, NORMAL_DST_ADJUSTMENT, WHEN_NO_DST, null /* bias */);
    370         assertNull(noDstMatch1);
    371 
    372         OffsetResult noDstMatch2 = countryTimeZones.lookupByOffsetWithBias(
    373                 LONDON_DST_OFFSET_MILLIS, 0 /* no DST */, WHEN_NO_DST, null /* bias */);
    374         assertNull(noDstMatch2);
    375 
    376         OffsetResult noDstMatch3 = countryTimeZones.lookupByOffsetWithBias(
    377                 LONDON_NO_DST_OFFSET_MILLIS, NORMAL_DST_ADJUSTMENT, WHEN_DST, null /* bias */);
    378         assertNull(noDstMatch3);
    379 
    380         OffsetResult noDstMatch4 = countryTimeZones.lookupByOffsetWithBias(
    381                 LONDON_NO_DST_OFFSET_MILLIS, NORMAL_DST_ADJUSTMENT, WHEN_NO_DST, null /* bias */);
    382         assertNull(noDstMatch4);
    383 
    384         OffsetResult noDstMatch5 = countryTimeZones.lookupByOffsetWithBias(
    385                 LONDON_DST_OFFSET_MILLIS, 0 /* no DST */, WHEN_DST, null /* bias */);
    386         assertNull(noDstMatch5);
    387 
    388         OffsetResult noDstMatch6 = countryTimeZones.lookupByOffsetWithBias(
    389                 LONDON_NO_DST_OFFSET_MILLIS, 0 /* no DST */, WHEN_DST, null /* bias */);
    390         assertNull(noDstMatch6);
    391 
    392         // Some bias cases below.
    393 
    394         // The bias is irrelevant here: it matches what would be returned anyway.
    395         assertOffsetResultEquals(expectedResult,
    396                 countryTimeZones.lookupByOffsetWithBias(LONDON_DST_OFFSET_MILLIS,
    397                         NORMAL_DST_ADJUSTMENT, WHEN_DST, LONDON_TZ /* bias */));
    398         assertOffsetResultEquals(expectedResult,
    399                 countryTimeZones.lookupByOffsetWithBias(LONDON_NO_DST_OFFSET_MILLIS,
    400                         0 /* no DST */, WHEN_NO_DST, LONDON_TZ /* bias */));
    401         assertOffsetResultEquals(expectedResult,
    402                 countryTimeZones.lookupByOffsetWithBias(LONDON_NO_DST_OFFSET_MILLIS,
    403                         null /* unknown DST */, WHEN_NO_DST, LONDON_TZ /* bias */));
    404         // A sample of a non-matching case with bias.
    405         assertNull(countryTimeZones.lookupByOffsetWithBias(LONDON_DST_OFFSET_MILLIS,
    406                 NORMAL_DST_ADJUSTMENT, WHEN_NO_DST, LONDON_TZ /* bias */));
    407 
    408         // The bias should be ignored: it doesn't match any of the country's zones.
    409         assertOffsetResultEquals(expectedResult,
    410                 countryTimeZones.lookupByOffsetWithBias(LONDON_DST_OFFSET_MILLIS,
    411                         NORMAL_DST_ADJUSTMENT, WHEN_DST, NEW_YORK_TZ /* bias */));
    412 
    413         // The bias should still be ignored even though it matches the offset information given:
    414         // it doesn't match any of the country's configured zones.
    415         assertNull(countryTimeZones.lookupByOffsetWithBias(NEW_YORK_DST_OFFSET_MILLIS,
    416                 NORMAL_DST_ADJUSTMENT, WHEN_DST, NEW_YORK_TZ /* bias */));
    417     }
    418 
    419     @Test
    420     public void lookupByOffsetWithBias_multipleNonOverlappingCandidates()
    421             throws Exception {
    422         CountryTimeZones countryTimeZones = CountryTimeZones.createValidated(
    423                 "xx", "Europe/London", true /* uses UTC */,
    424                 timeZoneMappings("America/New_York", "Europe/London"), "test");
    425 
    426         OffsetResult expectedLondonResult = new OffsetResult(LONDON_TZ, true /* oneMatch */);
    427         OffsetResult expectedNewYorkResult = new OffsetResult(NEW_YORK_TZ, true /* oneMatch */);
    428 
    429         // The three parameters match the configured zone: offset, DST offset and time.
    430         assertOffsetResultEquals(expectedLondonResult, countryTimeZones.lookupByOffsetWithBias(
    431                 LONDON_DST_OFFSET_MILLIS, NORMAL_DST_ADJUSTMENT, WHEN_DST, null /* bias */));
    432         assertOffsetResultEquals(expectedLondonResult, countryTimeZones.lookupByOffsetWithBias(
    433                 LONDON_NO_DST_OFFSET_MILLIS, 0 /* no DST */, WHEN_NO_DST, null /* bias */));
    434         assertOffsetResultEquals(expectedLondonResult, countryTimeZones.lookupByOffsetWithBias(
    435                 LONDON_NO_DST_OFFSET_MILLIS, null /* unknown DST */, WHEN_NO_DST, null /* bias */));
    436         assertOffsetResultEquals(expectedNewYorkResult, countryTimeZones.lookupByOffsetWithBias(
    437                 NEW_YORK_DST_OFFSET_MILLIS, NORMAL_DST_ADJUSTMENT, WHEN_DST, null /* bias */));
    438         assertOffsetResultEquals(expectedNewYorkResult, countryTimeZones.lookupByOffsetWithBias(
    439                 NEW_YORK_NO_DST_OFFSET_MILLIS, 0 /* no DST */, WHEN_NO_DST, null /* bias */));
    440         assertOffsetResultEquals(expectedNewYorkResult, countryTimeZones.lookupByOffsetWithBias(
    441                 NEW_YORK_NO_DST_OFFSET_MILLIS, null /* unknown DST */, WHEN_NO_DST,
    442                 null /* bias */));
    443 
    444         // Some lookup failure cases where the offset, DST offset and time do not match the
    445         // configured zone. This is a sample, not complete.
    446         OffsetResult noDstMatch1 = countryTimeZones.lookupByOffsetWithBias(
    447                 LONDON_DST_OFFSET_MILLIS, NORMAL_DST_ADJUSTMENT, WHEN_NO_DST, null /* bias */);
    448         assertNull(noDstMatch1);
    449 
    450         OffsetResult noDstMatch2 = countryTimeZones.lookupByOffsetWithBias(
    451                 LONDON_DST_OFFSET_MILLIS, 0 /* no DST */, WHEN_NO_DST, null /* bias */);
    452         assertNull(noDstMatch2);
    453 
    454         OffsetResult noDstMatch3 = countryTimeZones.lookupByOffsetWithBias(
    455                 NEW_YORK_NO_DST_OFFSET_MILLIS, NORMAL_DST_ADJUSTMENT, WHEN_DST, null /* bias */);
    456         assertNull(noDstMatch3);
    457 
    458         OffsetResult noDstMatch4 = countryTimeZones.lookupByOffsetWithBias(
    459                 NEW_YORK_NO_DST_OFFSET_MILLIS, NORMAL_DST_ADJUSTMENT, WHEN_NO_DST, null /* bias */);
    460         assertNull(noDstMatch4);
    461 
    462         OffsetResult noDstMatch5 = countryTimeZones.lookupByOffsetWithBias(
    463                 LONDON_DST_OFFSET_MILLIS, 0 /* no DST */, WHEN_DST, null /* bias */);
    464         assertNull(noDstMatch5);
    465 
    466         OffsetResult noDstMatch6 = countryTimeZones.lookupByOffsetWithBias(
    467                 LONDON_NO_DST_OFFSET_MILLIS, 0 /* no DST */, WHEN_DST, null /* bias */);
    468         assertNull(noDstMatch6);
    469 
    470         // Some bias cases below.
    471 
    472         // The bias is irrelevant here: it matches what would be returned anyway.
    473         assertOffsetResultEquals(expectedLondonResult, countryTimeZones.lookupByOffsetWithBias(
    474                 LONDON_DST_OFFSET_MILLIS, NORMAL_DST_ADJUSTMENT, WHEN_DST, LONDON_TZ /* bias */));
    475         assertOffsetResultEquals(expectedLondonResult, countryTimeZones.lookupByOffsetWithBias(
    476                 LONDON_NO_DST_OFFSET_MILLIS, 0 /* no DST */, WHEN_NO_DST, LONDON_TZ /* bias */));
    477         assertOffsetResultEquals(expectedLondonResult, countryTimeZones.lookupByOffsetWithBias(
    478                 LONDON_NO_DST_OFFSET_MILLIS, null /* unknown DST */, WHEN_NO_DST,
    479                 LONDON_TZ /* bias */));
    480 
    481         // A sample of a non-matching case with bias.
    482         assertNull(countryTimeZones.lookupByOffsetWithBias(
    483                 LONDON_DST_OFFSET_MILLIS, NORMAL_DST_ADJUSTMENT, WHEN_NO_DST, LONDON_TZ /* bias */));
    484 
    485         // The bias should be ignored: it matches a configured zone, but the offset is wrong so
    486         // should not be considered a match.
    487         assertOffsetResultEquals(expectedLondonResult, countryTimeZones.lookupByOffsetWithBias(
    488                 LONDON_DST_OFFSET_MILLIS, NORMAL_DST_ADJUSTMENT, WHEN_DST, NEW_YORK_TZ /* bias */));
    489     }
    490 
    491     // This is an artificial case very similar to America/Denver and America/Phoenix in the US: both
    492     // have the same offset for 6 months of the year but diverge. Australia/Lord_Howe too.
    493     @Test
    494     public void lookupByOffsetWithBias_multipleOverlappingCandidates() throws Exception {
    495         // Three zones that have the same offset for some of the year. Europe/London changes
    496         // offset WHEN_DST, the others do not.
    497         CountryTimeZones countryTimeZones = CountryTimeZones.createValidated(
    498                 "xx", "Europe/London", true /* uses UTC */,
    499                 timeZoneMappings("Atlantic/Reykjavik", "Europe/London", "Etc/UTC"), "test");
    500 
    501         // This is the no-DST offset for LONDON_TZ, REYKJAVIK_TZ. UTC_TZ.
    502         final int noDstOffset = LONDON_NO_DST_OFFSET_MILLIS;
    503         // This is the DST offset for LONDON_TZ.
    504         final int dstOffset = LONDON_DST_OFFSET_MILLIS;
    505 
    506         OffsetResult expectedLondonOnlyMatch = new OffsetResult(LONDON_TZ, true /* oneMatch */);
    507         OffsetResult expectedReykjavikBestMatch =
    508                 new OffsetResult(REYKJAVIK_TZ, false /* oneMatch */);
    509 
    510         // The three parameters match the configured zone: offset, DST offset and time.
    511         assertOffsetResultEquals(expectedLondonOnlyMatch,
    512                 countryTimeZones.lookupByOffsetWithBias(dstOffset, NORMAL_DST_ADJUSTMENT, WHEN_DST,
    513                         null /* bias */));
    514         assertOffsetResultEquals(expectedReykjavikBestMatch,
    515                 countryTimeZones.lookupByOffsetWithBias(noDstOffset, 0 /* no DST */, WHEN_NO_DST,
    516                         null /* bias */));
    517         assertOffsetResultEquals(expectedLondonOnlyMatch,
    518                 countryTimeZones.lookupByOffsetWithBias(dstOffset, NORMAL_DST_ADJUSTMENT, WHEN_DST,
    519                         null /* bias */));
    520         assertOffsetResultEquals(expectedReykjavikBestMatch,
    521                 countryTimeZones.lookupByOffsetWithBias(noDstOffset, 0 /* no DST */, WHEN_NO_DST,
    522                         null /* bias */));
    523         assertOffsetResultEquals(expectedReykjavikBestMatch,
    524                 countryTimeZones.lookupByOffsetWithBias(noDstOffset, 0 /* no DST */, WHEN_DST,
    525                         null /* bias */));
    526 
    527         // Unknown DST cases
    528         assertOffsetResultEquals(expectedReykjavikBestMatch,
    529                 countryTimeZones.lookupByOffsetWithBias(noDstOffset, null, WHEN_NO_DST,
    530                         null /* bias */));
    531         assertOffsetResultEquals(expectedReykjavikBestMatch,
    532                 countryTimeZones.lookupByOffsetWithBias(noDstOffset, null, WHEN_DST,
    533                         null /* bias */));
    534         assertNull(countryTimeZones.lookupByOffsetWithBias(dstOffset, null, WHEN_NO_DST,
    535                         null /* bias */));
    536         assertOffsetResultEquals(expectedLondonOnlyMatch,
    537                 countryTimeZones.lookupByOffsetWithBias(dstOffset, null, WHEN_DST,
    538                         null /* bias */));
    539 
    540         // Some lookup failure cases where the offset, DST offset and time do not match the
    541         // configured zones.
    542         OffsetResult noDstMatch1 = countryTimeZones.lookupByOffsetWithBias(dstOffset,
    543                 NORMAL_DST_ADJUSTMENT, WHEN_NO_DST, null /* bias */);
    544         assertNull(noDstMatch1);
    545 
    546         OffsetResult noDstMatch2 = countryTimeZones.lookupByOffsetWithBias(noDstOffset,
    547                 NORMAL_DST_ADJUSTMENT, WHEN_DST, null /* bias */);
    548         assertNull(noDstMatch2);
    549 
    550         OffsetResult noDstMatch3 = countryTimeZones.lookupByOffsetWithBias(noDstOffset,
    551                 NORMAL_DST_ADJUSTMENT, WHEN_NO_DST, null /* bias */);
    552         assertNull(noDstMatch3);
    553 
    554         OffsetResult noDstMatch4 = countryTimeZones.lookupByOffsetWithBias(dstOffset,
    555                 0 /* no DST */, WHEN_DST, null /* bias */);
    556         assertNull(noDstMatch4);
    557 
    558 
    559         // Some bias cases below.
    560 
    561         // Multiple zones match but Reykjavik is the bias.
    562         assertOffsetResultEquals(expectedReykjavikBestMatch,
    563                 countryTimeZones.lookupByOffsetWithBias(noDstOffset, 0 /* no DST */, WHEN_NO_DST,
    564                         REYKJAVIK_TZ /* bias */));
    565         assertOffsetResultEquals(expectedReykjavikBestMatch,
    566                 countryTimeZones.lookupByOffsetWithBias(noDstOffset, null /* unknown DST */,
    567                         WHEN_NO_DST, REYKJAVIK_TZ /* bias */));
    568 
    569         // Multiple zones match but London is the bias.
    570         OffsetResult expectedLondonBestMatch = new OffsetResult(LONDON_TZ, false /* oneMatch */);
    571         assertOffsetResultEquals(expectedLondonBestMatch,
    572                 countryTimeZones.lookupByOffsetWithBias(noDstOffset, 0 /* no DST */, WHEN_NO_DST,
    573                         LONDON_TZ /* bias */));
    574         assertOffsetResultEquals(expectedLondonBestMatch,
    575                 countryTimeZones.lookupByOffsetWithBias(noDstOffset, null /* unknown DST */,
    576                         WHEN_NO_DST, LONDON_TZ /* bias */));
    577 
    578         // Multiple zones match but UTC is the bias.
    579         OffsetResult expectedUtcResult = new OffsetResult(UTC_TZ, false /* oneMatch */);
    580         assertOffsetResultEquals(expectedUtcResult,
    581                 countryTimeZones.lookupByOffsetWithBias(noDstOffset, 0 /* no DST */, WHEN_NO_DST,
    582                         UTC_TZ /* bias */));
    583         assertOffsetResultEquals(expectedUtcResult,
    584                 countryTimeZones.lookupByOffsetWithBias(noDstOffset, null /* unknown DST */,
    585                         WHEN_NO_DST, UTC_TZ /* bias */));
    586 
    587         // The bias should be ignored: it matches a configured zone, but the offset is wrong so
    588         // should not be considered a match.
    589         assertOffsetResultEquals(expectedLondonOnlyMatch,
    590                 countryTimeZones.lookupByOffsetWithBias(LONDON_DST_OFFSET_MILLIS,
    591                         NORMAL_DST_ADJUSTMENT, WHEN_DST, REYKJAVIK_TZ /* bias */));
    592     }
    593 
    594     @Test
    595     public void isDefaultOkForCountryTimeZoneDetection_noZones() {
    596         CountryTimeZones countryTimeZones = CountryTimeZones.createValidated(
    597                 "xx", "Europe/London", true /* everUsesUtc */, timeZoneMappings(), "test");
    598         assertFalse(countryTimeZones.isDefaultOkForCountryTimeZoneDetection(WHEN_DST));
    599         assertFalse(countryTimeZones.isDefaultOkForCountryTimeZoneDetection(WHEN_NO_DST));
    600     }
    601 
    602     @Test
    603     public void isDefaultOkForCountryTimeZoneDetection_oneZone() {
    604         CountryTimeZones countryTimeZones = CountryTimeZones.createValidated(
    605                 "xx", "Europe/London", true /* everUsesUtc */, timeZoneMappings("Europe/London"),
    606                 "test");
    607         assertTrue(countryTimeZones.isDefaultOkForCountryTimeZoneDetection(WHEN_DST));
    608         assertTrue(countryTimeZones.isDefaultOkForCountryTimeZoneDetection(WHEN_NO_DST));
    609     }
    610 
    611     @Test
    612     public void isDefaultOkForCountryTimeZoneDetection_twoZones_overlap() {
    613         CountryTimeZones countryTimeZones = CountryTimeZones.createValidated(
    614                 "xx", "Europe/London", true /* everUsesUtc */,
    615                 timeZoneMappings("Europe/London", "Etc/UTC"), "test");
    616         // Europe/London is the same as UTC in the Winter, so all the zones have the same offset
    617         // in Winter, but not in Summer.
    618         assertFalse(countryTimeZones.isDefaultOkForCountryTimeZoneDetection(WHEN_DST));
    619         assertTrue(countryTimeZones.isDefaultOkForCountryTimeZoneDetection(WHEN_NO_DST));
    620     }
    621 
    622     @Test
    623     public void isDefaultOkForCountryTimeZoneDetection_twoZones_noOverlap() {
    624         CountryTimeZones countryTimeZones = CountryTimeZones.createValidated(
    625                 "xx", "Europe/London", true /* everUsesUtc */,
    626                 timeZoneMappings("Europe/London", "America/New_York"), "test");
    627         // The zones have different offsets all year, so it would never be ok to use the default
    628         // zone for the country of "xx".
    629         assertFalse(countryTimeZones.isDefaultOkForCountryTimeZoneDetection(WHEN_DST));
    630         assertFalse(countryTimeZones.isDefaultOkForCountryTimeZoneDetection(WHEN_NO_DST));
    631     }
    632 
    633     @Test
    634     public void hasUtcZone_everUseUtcHintOverridesZoneInformation() {
    635         // The country has a single zone. Europe/London uses UTC in Winter.
    636         CountryTimeZones countryTimeZones = CountryTimeZones.createValidated(
    637                 "xx", "Etc/UTC", false /* everUsesUtc */, timeZoneMappings("Etc/UTC"), "test");
    638         assertFalse(countryTimeZones.hasUtcZone(WHEN_DST));
    639         assertFalse(countryTimeZones.hasUtcZone(WHEN_NO_DST));
    640     }
    641 
    642     @Test
    643     public void hasUtcZone_singleZone() {
    644         // The country has a single zone. Europe/London uses UTC in Winter.
    645         CountryTimeZones countryTimeZones = CountryTimeZones.createValidated(
    646                 "xx", "Europe/London", true /* everUsesUtc */, timeZoneMappings("Europe/London"),
    647                 "test");
    648         assertFalse(countryTimeZones.hasUtcZone(WHEN_DST));
    649         assertTrue(countryTimeZones.hasUtcZone(WHEN_NO_DST));
    650     }
    651 
    652     @Test
    653     public void hasUtcZone_multipleZonesWithUtc() {
    654         // The country has multiple zones. Europe/London uses UTC in Winter.
    655         CountryTimeZones countryTimeZones = CountryTimeZones.createValidated(
    656                 "xx", "America/Los_Angeles", true /* everUsesUtc */,
    657                 timeZoneMappings("America/Los_Angeles", "America/New_York", "Europe/London"),
    658                 "test");
    659         assertFalse(countryTimeZones.hasUtcZone(WHEN_DST));
    660         assertTrue(countryTimeZones.hasUtcZone(WHEN_NO_DST));
    661     }
    662 
    663     @Test
    664     public void hasUtcZone_multipleZonesWithoutUtc() {
    665         // The country has multiple zones, none of which use UTC.
    666         CountryTimeZones countryTimeZones = CountryTimeZones.createValidated(
    667                 "xx", "Europe/Paris", false /* everUsesUtc */,
    668                 timeZoneMappings("America/Los_Angeles", "America/New_York", "Europe/Paris"),
    669                 "test");
    670         assertFalse(countryTimeZones.hasUtcZone(WHEN_DST));
    671         assertFalse(countryTimeZones.hasUtcZone(WHEN_NO_DST));
    672     }
    673 
    674     @Test
    675     public void hasUtcZone_emptyZones() {
    676         // The country has no valid zones.
    677         CountryTimeZones countryTimeZones = CountryTimeZones.createValidated(
    678                 "xx", INVALID_TZ_ID, false /* everUsesUtc */, timeZoneMappings(INVALID_TZ_ID),
    679                 "test");
    680         assertTrue(countryTimeZones.getTimeZoneMappings().isEmpty());
    681         assertFalse(countryTimeZones.hasUtcZone(WHEN_DST));
    682         assertFalse(countryTimeZones.hasUtcZone(WHEN_NO_DST));
    683     }
    684 
    685     private void assertImmutableTimeZone(TimeZone timeZone) {
    686         try {
    687             timeZone.setRawOffset(1000);
    688             fail();
    689         } catch (UnsupportedOperationException expected) {
    690         }
    691     }
    692 
    693     private static <X> void assertImmutableList(List<X> list) {
    694         try {
    695             list.add(null);
    696             fail();
    697         } catch (UnsupportedOperationException expected) {
    698         }
    699     }
    700 
    701     private static void assertZoneEquals(TimeZone expected, TimeZone actual) {
    702         // TimeZone.equals() only checks the ID, but that's ok for these tests.
    703         assertEquals(expected, actual);
    704     }
    705 
    706     private static void assertOffsetResultEquals(OffsetResult expected, OffsetResult actual) {
    707         assertEquals(expected.mTimeZone.getID(), actual.mTimeZone.getID());
    708         assertEquals(expected.mOneMatch, actual.mOneMatch);
    709     }
    710 
    711     private static void assertZonesEqual(List<TimeZone> expected, List<TimeZone> actual) {
    712         // TimeZone.equals() only checks the ID, but that's ok for these tests.
    713         assertEquals(expected, actual);
    714     }
    715 
    716     private static TimeZone zone(String id) {
    717         return TimeZone.getTimeZone(id);
    718     }
    719 
    720     /**
    721      * Creates a list of default {@link TimeZoneMapping} objects with the specified time zone IDs.
    722      */
    723     private static List<TimeZoneMapping> timeZoneMappings(String... timeZoneIds) {
    724         return Arrays.stream(timeZoneIds)
    725                 .map(x -> TimeZoneMapping.createForTests(
    726                         x, true /* picker */, null /* notUsedAfter */))
    727                 .collect(Collectors.toList());
    728     }
    729 
    730     private static List<TimeZone> zones(String... ids) {
    731         return Arrays.stream(ids).map(TimeZone::getTimeZone).collect(Collectors.toList());
    732     }
    733 }
    734