1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.i18n.test.timezone; 18 19 import org.junit.Test; 20 21 import android.icu.testsharding.MainTestShard; 22 import android.icu.util.TimeZone; 23 24 import java.time.Instant; 25 import java.util.ArrayList; 26 import java.util.Arrays; 27 import java.util.List; 28 import java.util.Objects; 29 import java.util.function.Function; 30 import java.util.stream.Collectors; 31 import com.android.i18n.timezone.CountryTimeZones; 32 import com.android.i18n.timezone.CountryTimeZones.OffsetResult; 33 import com.android.i18n.timezone.CountryTimeZones.TimeZoneMapping; 34 35 import static org.junit.Assert.assertEquals; 36 import static org.junit.Assert.assertFalse; 37 import static org.junit.Assert.assertNull; 38 import static org.junit.Assert.assertTrue; 39 import static org.junit.Assert.fail; 40 41 @MainTestShard 42 public class CountryTimeZonesTest { 43 44 private static final int HOUR_MILLIS = 60 * 60 * 1000; 45 46 private static final String INVALID_TZ_ID = "Moon/Tranquility_Base"; 47 48 // Zones used in the tests. NY_TZ and LON_TZ chosen because they never overlap but both have 49 // DST. 50 private static final TimeZone NY_TZ = TimeZone.getTimeZone("America/New_York"); 51 private static final TimeZone LON_TZ = TimeZone.getTimeZone("Europe/London"); 52 // A zone that matches LON_TZ for WHEN_NO_DST. It does not have DST so differs for WHEN_DST. 53 private static final TimeZone REYK_TZ = TimeZone.getTimeZone("Atlantic/Reykjavik"); 54 // Another zone that matches LON_TZ for WHEN_NO_DST. It does not have DST so differs for 55 // WHEN_DST. 56 private static final TimeZone UTC_TZ = TimeZone.getTimeZone("Etc/UTC"); 57 58 // 22nd July 2017, 13:14:15 UTC (DST time in all the timezones used in these tests that observe 59 // DST). 60 private static final long WHEN_DST = 1500729255000L; 61 // 22nd January 2018, 13:14:15 UTC (non-DST time in all timezones used in these tests). 62 private static final long WHEN_NO_DST = 1516626855000L; 63 64 // The offset applied to most zones during DST. 65 private static final int NORMAL_DST_ADJUSTMENT = HOUR_MILLIS; 66 67 private static final int LON_NO_DST_TOTAL_OFFSET = 0; 68 private static final int LON_DST_TOTAL_OFFSET = LON_NO_DST_TOTAL_OFFSET 69 + NORMAL_DST_ADJUSTMENT; 70 71 private static final int NY_NO_DST_TOTAL_OFFSET = -5 * HOUR_MILLIS; 72 private static final int NY_DST_TOTAL_OFFSET = NY_NO_DST_TOTAL_OFFSET 73 + NORMAL_DST_ADJUSTMENT; 74 75 @Test createValidated()76 public void createValidated() throws Exception { 77 CountryTimeZones countryTimeZones = CountryTimeZones.createValidated( 78 "gb", "Europe/London", false /* defaultTimeZoneBoost */, 79 true /* everUsesUtc */, timeZoneMappings("Europe/London"), "test"); 80 assertTrue(countryTimeZones.matchesCountryCode("gb")); 81 assertEquals("Europe/London", countryTimeZones.getDefaultTimeZoneId()); 82 assertZoneEquals(zone("Europe/London"), countryTimeZones.getDefaultTimeZone()); 83 assertEquals(timeZoneMappings("Europe/London"), countryTimeZones.getTimeZoneMappings()); 84 assertEquals(timeZoneMappings("Europe/London"), 85 countryTimeZones.getEffectiveTimeZoneMappingsAt(0 /* whenMillis */)); 86 } 87 88 @Test createValidated_nullDefault()89 public void createValidated_nullDefault() throws Exception { 90 CountryTimeZones countryTimeZones = CountryTimeZones.createValidated( 91 "gb", null, false /* defaultTimeZoneBoost */, true /* everUsesUtc */, 92 timeZoneMappings("Europe/London"), "test"); 93 assertNull(countryTimeZones.getDefaultTimeZoneId()); 94 assertNull(countryTimeZones.getDefaultTimeZone()); 95 } 96 97 @Test createValidated_invalidDefault()98 public void createValidated_invalidDefault() throws Exception { 99 CountryTimeZones countryTimeZones = CountryTimeZones.createValidated( 100 "gb", INVALID_TZ_ID, false /* defaultTimeZoneBoost */, true /* everUsesUtc */, 101 timeZoneMappings("Europe/London", INVALID_TZ_ID), "test"); 102 assertNull(countryTimeZones.getDefaultTimeZoneId()); 103 assertNull(countryTimeZones.getDefaultTimeZone()); 104 assertEquals(timeZoneMappings("Europe/London"), countryTimeZones.getTimeZoneMappings()); 105 assertEquals(timeZoneMappings("Europe/London"), 106 countryTimeZones.getEffectiveTimeZoneMappingsAt(0 /* whenMillis */)); 107 } 108 109 @Test createValidated_unknownTimeZoneIdIgnored()110 public void createValidated_unknownTimeZoneIdIgnored() throws Exception { 111 CountryTimeZones countryTimeZones = CountryTimeZones.createValidated( 112 "gb", "Europe/London", false /* defaultTimeZoneBoost */, true /* everUsesUtc */, 113 timeZoneMappings("Unknown_Id", "Europe/London"), "test"); 114 assertEquals(timeZoneMappings("Europe/London"), countryTimeZones.getTimeZoneMappings()); 115 assertEquals(timeZoneMappings("Europe/London"), 116 countryTimeZones.getEffectiveTimeZoneMappingsAt(0 /* whenMillis */)); 117 } 118 119 @Test matchesCountryCode()120 public void matchesCountryCode() throws Exception { 121 CountryTimeZones countryTimeZones = CountryTimeZones.createValidated( 122 "gb", "Europe/London", false /* defaultTimeZoneBoost */, true /* everUsesUtc */, 123 timeZoneMappings("Europe/London"), "test"); 124 assertTrue(countryTimeZones.matchesCountryCode("GB")); 125 assertTrue(countryTimeZones.matchesCountryCode("Gb")); 126 assertTrue(countryTimeZones.matchesCountryCode("gB")); 127 } 128 129 @Test structuresAreImmutable()130 public void structuresAreImmutable() throws Exception { 131 CountryTimeZones countryTimeZones = CountryTimeZones.createValidated( 132 "gb", "Europe/London", false /* defaultTimeZoneBoost */, true /* everUsesUtc */, 133 timeZoneMappings("Europe/London"), "test"); 134 135 assertImmutableTimeZone(countryTimeZones.getDefaultTimeZone()); 136 137 List<TimeZoneMapping> timeZoneMappings = countryTimeZones.getTimeZoneMappings(); 138 assertEquals(1, timeZoneMappings.size()); 139 assertImmutableList(timeZoneMappings); 140 141 List<TimeZoneMapping> effectiveTimeZoneMappings = 142 countryTimeZones.getEffectiveTimeZoneMappingsAt(0 /* whenMillis */); 143 assertEquals(1, effectiveTimeZoneMappings.size()); 144 assertImmutableList(effectiveTimeZoneMappings); 145 } 146 147 @Test lookupByOffsetWithBias_oneCandidate()148 public void lookupByOffsetWithBias_oneCandidate() throws Exception { 149 CountryTimeZones countryTimeZones = CountryTimeZones.createValidated( 150 "gb", "Europe/London", false /* defaultTimeZoneBoost */, true /* everUsesUtc */, 151 timeZoneMappings("Europe/London"), "test"); 152 153 OffsetResult lonMatch = new OffsetResult(LON_TZ, true /* oneMatch */); 154 155 // Placeholder constants to improve test case readability. 156 final Boolean isDst = true; 157 final Boolean notDst = false; 158 final Boolean unkIsDst = null; 159 final TimeZone noBias = null; 160 final OffsetResult noMatch = null; 161 162 Object[][] testCases = new Object[][] { 163 // totalOffsetMillis, isDst, whenMillis, bias, expectedMatch 164 165 // The parameters match the zone: total offset and time. 166 { LON_DST_TOTAL_OFFSET, unkIsDst, WHEN_DST, noBias, lonMatch }, 167 { LON_NO_DST_TOTAL_OFFSET, unkIsDst, WHEN_NO_DST, noBias, lonMatch }, 168 169 // The parameters match the zone: total offset, isDst and time. 170 { LON_DST_TOTAL_OFFSET, isDst, WHEN_DST, noBias, lonMatch }, 171 { LON_NO_DST_TOTAL_OFFSET, notDst, WHEN_NO_DST, noBias, lonMatch }, 172 173 // Some lookup failure cases where the total offset, isDst and time do not match the 174 // zone. 175 { LON_DST_TOTAL_OFFSET, isDst, WHEN_NO_DST, noBias, noMatch }, 176 { LON_DST_TOTAL_OFFSET, notDst, WHEN_NO_DST, noBias, noMatch }, 177 { LON_NO_DST_TOTAL_OFFSET, isDst, WHEN_DST, noBias, noMatch }, 178 { LON_NO_DST_TOTAL_OFFSET, isDst, WHEN_NO_DST, noBias, noMatch }, 179 { LON_DST_TOTAL_OFFSET, notDst, WHEN_DST, noBias, noMatch }, 180 { LON_NO_DST_TOTAL_OFFSET, notDst, WHEN_DST, noBias, noMatch }, 181 182 // Some bias cases below. 183 184 // The bias is irrelevant here: it matches what would be returned anyway. 185 { LON_DST_TOTAL_OFFSET, isDst, WHEN_DST, LON_TZ, lonMatch }, 186 { LON_NO_DST_TOTAL_OFFSET, notDst, WHEN_NO_DST, LON_TZ, lonMatch }, 187 188 // A sample of a non-matching case with bias. 189 { LON_DST_TOTAL_OFFSET, isDst, WHEN_NO_DST, LON_TZ, noMatch }, 190 191 // The bias should be ignored: it doesn't match any of the country's zones. 192 { LON_DST_TOTAL_OFFSET, isDst, WHEN_DST, NY_TZ, lonMatch }, 193 194 // The bias should still be ignored even though it matches the offset information 195 // given it doesn't match any of the country's zones. 196 { NY_DST_TOTAL_OFFSET, isDst, WHEN_DST, NY_TZ, noMatch }, 197 }; 198 executeLookupByOffsetWithBiasTestCases(countryTimeZones, testCases); 199 } 200 201 @Test lookupByOffsetWithBias_multipleNonOverlappingCandidates()202 public void lookupByOffsetWithBias_multipleNonOverlappingCandidates() throws Exception { 203 CountryTimeZones countryTimeZones = CountryTimeZones.createValidated( 204 "xx", "Europe/London", false /* defaultTimeZoneBoost */, true /* everUsesUtc */, 205 timeZoneMappings("America/New_York", "Europe/London"), "test"); 206 207 OffsetResult lonMatch = new OffsetResult(LON_TZ, true /* oneMatch */); 208 OffsetResult nyMatch = new OffsetResult(NY_TZ, true /* oneMatch */); 209 210 // Placeholder constants to improve test case readability. 211 final Boolean isDst = true; 212 final Boolean notDst = false; 213 final Boolean unkIsDst = null; 214 final TimeZone noBias = null; 215 final OffsetResult noMatch = null; 216 217 Object[][] testCases = new Object[][] { 218 // totalOffsetMillis, isDst, dstOffsetMillis, whenMillis, bias, expectedMatch 219 220 // The parameters match the zone: total offset and time. 221 { LON_DST_TOTAL_OFFSET, unkIsDst, WHEN_DST, noBias, lonMatch }, 222 { LON_NO_DST_TOTAL_OFFSET, unkIsDst, WHEN_NO_DST, noBias, lonMatch }, 223 { NY_NO_DST_TOTAL_OFFSET, unkIsDst, WHEN_NO_DST, noBias, nyMatch }, 224 { NY_DST_TOTAL_OFFSET, unkIsDst, WHEN_DST, noBias, nyMatch }, 225 226 // The parameters match the zone: total offset, isDst and time. 227 { LON_DST_TOTAL_OFFSET, isDst, WHEN_DST, noBias, lonMatch }, 228 { LON_NO_DST_TOTAL_OFFSET, notDst, WHEN_NO_DST, noBias, lonMatch }, 229 { NY_DST_TOTAL_OFFSET, isDst, WHEN_DST, noBias, nyMatch }, 230 { NY_NO_DST_TOTAL_OFFSET, notDst, WHEN_NO_DST, noBias, nyMatch }, 231 232 // Some lookup failure cases where the total offset, isDst and time do not match the 233 // zone. This is a sample, not complete. 234 { LON_DST_TOTAL_OFFSET, isDst, WHEN_NO_DST, noBias, noMatch }, 235 { LON_DST_TOTAL_OFFSET, unkIsDst, WHEN_NO_DST, noBias, noMatch }, 236 { LON_DST_TOTAL_OFFSET, notDst, WHEN_DST, noBias, noMatch }, 237 { LON_NO_DST_TOTAL_OFFSET, isDst, WHEN_DST, noBias, noMatch }, 238 { LON_NO_DST_TOTAL_OFFSET, isDst, WHEN_NO_DST, noBias, noMatch }, 239 { LON_NO_DST_TOTAL_OFFSET, unkIsDst, WHEN_DST, noBias, noMatch }, 240 { LON_NO_DST_TOTAL_OFFSET, notDst, WHEN_DST, noBias, noMatch }, 241 242 // Some bias cases below. 243 244 // The bias is irrelevant here: it matches what would be returned anyway. 245 { LON_DST_TOTAL_OFFSET, isDst, WHEN_DST, LON_TZ, lonMatch }, 246 { LON_DST_TOTAL_OFFSET, unkIsDst, WHEN_DST, LON_TZ, lonMatch }, 247 { LON_NO_DST_TOTAL_OFFSET, unkIsDst, WHEN_NO_DST, LON_TZ, lonMatch }, 248 249 // A sample of non-matching cases with bias. 250 { LON_NO_DST_TOTAL_OFFSET, isDst, WHEN_NO_DST, LON_TZ, noMatch }, 251 { LON_DST_TOTAL_OFFSET, isDst, WHEN_NO_DST, LON_TZ, noMatch }, 252 { LON_DST_TOTAL_OFFSET, unkIsDst, WHEN_NO_DST, LON_TZ, noMatch }, 253 254 // The bias should be ignored: it matches a zone, but the offset is wrong so 255 // should not be considered a match. 256 { LON_DST_TOTAL_OFFSET, isDst, WHEN_DST, NY_TZ, lonMatch }, 257 { LON_DST_TOTAL_OFFSET, unkIsDst, WHEN_DST, NY_TZ, lonMatch }, 258 }; 259 executeLookupByOffsetWithBiasTestCases(countryTimeZones, testCases); 260 } 261 262 // This is an artificial case very similar to America/Denver and America/Phoenix in the US: both 263 // have the same offset for 6 months of the year but diverge. Australia/Lord_Howe too. 264 @Test lookupByOffsetWithBias_multipleOverlappingCandidates()265 public void lookupByOffsetWithBias_multipleOverlappingCandidates() throws Exception { 266 // Three zones that have the same offset for some of the year. Europe/London changes 267 // offset WHEN_DST, the others do not. 268 CountryTimeZones countryTimeZones = CountryTimeZones.createValidated( 269 "xx", "Europe/London", false /* defaultTimeZoneBoost */, true /* everUsesUtc */, 270 timeZoneMappings("Atlantic/Reykjavik", "Europe/London", "Etc/UTC"), "test"); 271 272 // Placeholder constants to improve test case readability. 273 final Boolean isDst = true; 274 final Boolean notDst = false; 275 final Boolean unkIsDst = null; 276 final TimeZone noBias = null; 277 final OffsetResult noMatch = null; 278 279 // This is the no-DST offset for LON_TZ, REYK_TZ. UTC_TZ. 280 final int noDstTotalOffset = LON_NO_DST_TOTAL_OFFSET; 281 // This is the DST offset for LON_TZ. 282 final int dstTotalOffset = LON_DST_TOTAL_OFFSET; 283 284 OffsetResult lonOnlyMatch = new OffsetResult(LON_TZ, true /* oneMatch */); 285 OffsetResult lonBestMatch = new OffsetResult(LON_TZ, false /* oneMatch */); 286 OffsetResult reykBestMatch = new OffsetResult(REYK_TZ, false /* oneMatch */); 287 OffsetResult utcBestMatch = new OffsetResult(UTC_TZ, false /* oneMatch */); 288 289 Object[][] testCases = new Object[][] { 290 // totalOffsetMillis, isDst, dstOffsetMillis, whenMillis, bias, expectedMatch 291 292 // The parameters match one zone: total offset and time. 293 { dstTotalOffset, unkIsDst, WHEN_DST, noBias, lonOnlyMatch }, 294 { dstTotalOffset, unkIsDst, WHEN_DST, noBias, lonOnlyMatch }, 295 296 // The parameters match several zones: total offset and time. 297 { noDstTotalOffset, unkIsDst, WHEN_NO_DST, noBias, reykBestMatch }, 298 { noDstTotalOffset, unkIsDst, WHEN_DST, noBias, reykBestMatch }, 299 300 // The parameters match one zone: total offset, isDst and time. 301 { dstTotalOffset, isDst, WHEN_DST, noBias, lonOnlyMatch }, 302 { dstTotalOffset, isDst, WHEN_DST, noBias, lonOnlyMatch }, 303 304 { noDstTotalOffset, notDst, WHEN_NO_DST, noBias, reykBestMatch }, 305 { noDstTotalOffset, notDst, WHEN_DST, noBias, reykBestMatch }, 306 307 // Some lookup failure cases where the total offset, isDst and time do not match any 308 // zone. 309 { dstTotalOffset, isDst, WHEN_NO_DST, noBias, noMatch }, 310 { dstTotalOffset, unkIsDst, WHEN_NO_DST, noBias, noMatch }, 311 { noDstTotalOffset, isDst, WHEN_NO_DST, noBias, noMatch }, 312 { noDstTotalOffset, isDst, WHEN_DST, noBias, noMatch }, 313 314 // Some bias cases below. 315 316 // Multiple zones match but Reykjavik is the bias. 317 { noDstTotalOffset, notDst, WHEN_NO_DST, REYK_TZ, reykBestMatch }, 318 319 // Multiple zones match but London is the bias. 320 { noDstTotalOffset, notDst, WHEN_NO_DST, LON_TZ, lonBestMatch }, 321 322 // Multiple zones match but UTC is the bias. 323 { noDstTotalOffset, notDst, WHEN_NO_DST, UTC_TZ, utcBestMatch }, 324 325 // The bias should be ignored: it matches a zone, but the offset is wrong so 326 // should not be considered a match. 327 { LON_DST_TOTAL_OFFSET, isDst, WHEN_DST, REYK_TZ, lonOnlyMatch }, 328 { LON_DST_TOTAL_OFFSET, unkIsDst, WHEN_DST, REYK_TZ, lonOnlyMatch }, 329 }; 330 executeLookupByOffsetWithBiasTestCases(countryTimeZones, testCases); 331 } 332 executeLookupByOffsetWithBiasTestCases( CountryTimeZones countryTimeZones, Object[][] testCases)333 private static void executeLookupByOffsetWithBiasTestCases( 334 CountryTimeZones countryTimeZones, Object[][] testCases) { 335 336 List<String> failures = new ArrayList<>(); 337 for (int i = 0; i < testCases.length; i++) { 338 Object[] testCase = testCases[i]; 339 int totalOffsetMillis = (int) testCase[0]; 340 Boolean isDst = (Boolean) testCase[1]; 341 long whenMillis = (Long) testCase[2]; 342 TimeZone bias = (TimeZone) testCase[3]; 343 OffsetResult expectedMatch = (OffsetResult) testCase[4]; 344 345 OffsetResult actualMatch; 346 if (isDst == null) { 347 actualMatch = countryTimeZones.lookupByOffsetWithBias( 348 whenMillis, bias, totalOffsetMillis); 349 } else { 350 actualMatch = countryTimeZones.lookupByOffsetWithBias( 351 whenMillis, bias, totalOffsetMillis, isDst); 352 } 353 354 if (!offsetResultEquals(expectedMatch, actualMatch)) { 355 Function<TimeZone, String> timeZoneFormatter = 356 x -> x == null ? "null" : x.getID(); 357 Function<OffsetResult, String> offsetResultFormatter = 358 x -> x == null ? "null" 359 : "{" + x.getTimeZone().getID() + ", " + x.isOnlyMatch() + "}"; 360 failures.add("Fail: case=" + i 361 + ", totalOffsetMillis=" + totalOffsetMillis 362 + ", isDst=" + isDst 363 + ", whenMillis=" + whenMillis 364 + ", bias=" + timeZoneFormatter.apply(bias) 365 + ", expectedMatch=" + offsetResultFormatter.apply(expectedMatch) 366 + ", actualMatch=" + offsetResultFormatter.apply(actualMatch) 367 + "\n"); 368 } 369 } 370 if (!failures.isEmpty()) { 371 fail("Failed:\n" + failures); 372 } 373 } 374 375 @Test getEffectiveTimeZonesAt_noZones()376 public void getEffectiveTimeZonesAt_noZones() { 377 CountryTimeZones countryTimeZones = CountryTimeZones.createValidated( 378 "xx", "Europe/London", false /* defaultTimeZoneBoost */, true /* everUsesUtc */, 379 timeZoneMappings(), "test"); 380 assertEquals(timeZoneMappings(), 381 countryTimeZones.getEffectiveTimeZoneMappingsAt(0 /* whenMillis */)); 382 assertEquals(timeZoneMappings(), 383 countryTimeZones.getEffectiveTimeZoneMappingsAt(Long.MIN_VALUE)); 384 assertEquals(timeZoneMappings(), 385 countryTimeZones.getEffectiveTimeZoneMappingsAt(Long.MAX_VALUE)); 386 } 387 388 @Test getEffectiveTimeZonesAt_oneZone()389 public void getEffectiveTimeZonesAt_oneZone() { 390 CountryTimeZones countryTimeZones = CountryTimeZones.createValidated( 391 "xx", "Europe/London", false /* defaultTimeZoneBoost */, true /* everUsesUtc */, 392 timeZoneMappings("Europe/London"), "test"); 393 assertEquals(timeZoneMappings("Europe/London"), 394 countryTimeZones.getEffectiveTimeZoneMappingsAt(0)); 395 assertEquals(timeZoneMappings("Europe/London"), 396 countryTimeZones.getEffectiveTimeZoneMappingsAt(Long.MIN_VALUE)); 397 assertEquals(timeZoneMappings("Europe/London"), 398 countryTimeZones.getEffectiveTimeZoneMappingsAt(Long.MAX_VALUE)); 399 } 400 401 @Test getEffectiveTimeZonesAt_filtering()402 public void getEffectiveTimeZonesAt_filtering() { 403 TimeZoneMapping alwaysUsed = timeZoneMapping("Europe/London", null /* notUsedAfter */); 404 405 long mappingNotUsedAfterMillis = 0L; 406 TimeZoneMapping notAlwaysUsed = timeZoneMapping("Europe/Paris", 407 mappingNotUsedAfterMillis /* notUsedAfter */); 408 409 List<TimeZoneMapping> timeZoneMappings = list(alwaysUsed, notAlwaysUsed); 410 CountryTimeZones countryTimeZones = CountryTimeZones.createValidated( 411 "xx", "Europe/London", false /* defaultTimeZoneBoost */, true /* everUsesUtc */, 412 timeZoneMappings, "test"); 413 414 // Before and at mappingNotUsedAfterMillis, both mappings are "effective". 415 assertEquals(list(alwaysUsed, notAlwaysUsed), 416 countryTimeZones.getEffectiveTimeZoneMappingsAt(Long.MIN_VALUE)); 417 assertEquals(list(alwaysUsed, notAlwaysUsed), 418 countryTimeZones.getEffectiveTimeZoneMappingsAt(mappingNotUsedAfterMillis)); 419 420 // The following should filter the second mapping because it's not "effective" after 421 // mappingNotUsedAfterMillis. 422 assertEquals(list(alwaysUsed), 423 countryTimeZones.getEffectiveTimeZoneMappingsAt(mappingNotUsedAfterMillis + 1)); 424 assertEquals(list(alwaysUsed), 425 countryTimeZones.getEffectiveTimeZoneMappingsAt(Long.MAX_VALUE)); 426 } 427 428 @Test hasUtcZone_everUseUtcHintOverridesZoneInformation()429 public void hasUtcZone_everUseUtcHintOverridesZoneInformation() { 430 // The country has a single zone. Europe/London uses UTC in Winter. 431 CountryTimeZones countryTimeZones = CountryTimeZones.createValidated( 432 "xx", "Etc/UTC", false /* defaultTimeZoneBoost */, false /* everUsesUtc */, 433 timeZoneMappings("Etc/UTC"), "test"); 434 assertFalse(countryTimeZones.hasUtcZone(WHEN_DST)); 435 assertFalse(countryTimeZones.hasUtcZone(WHEN_NO_DST)); 436 } 437 438 @Test hasUtcZone_singleZone()439 public void hasUtcZone_singleZone() { 440 // The country has a single zone. Europe/London uses UTC in Winter. 441 CountryTimeZones countryTimeZones = CountryTimeZones.createValidated( 442 "xx", "Europe/London", false /* defaultTimeZoneBoost */, true /* everUsesUtc */, 443 timeZoneMappings("Europe/London"), "test"); 444 assertFalse(countryTimeZones.hasUtcZone(WHEN_DST)); 445 assertTrue(countryTimeZones.hasUtcZone(WHEN_NO_DST)); 446 } 447 448 @Test hasUtcZone_multipleZonesWithUtc()449 public void hasUtcZone_multipleZonesWithUtc() { 450 // The country has multiple zones. Europe/London uses UTC in Winter. 451 CountryTimeZones countryTimeZones = CountryTimeZones.createValidated( 452 "xx", "America/Los_Angeles", false /* defaultTimeZoneBoost */, 453 true /* everUsesUtc */, 454 timeZoneMappings("America/Los_Angeles", "America/New_York", "Europe/London"), 455 "test"); 456 assertFalse(countryTimeZones.hasUtcZone(WHEN_DST)); 457 assertTrue(countryTimeZones.hasUtcZone(WHEN_NO_DST)); 458 } 459 460 @Test hasUtcZone_multipleZonesWithoutUtc()461 public void hasUtcZone_multipleZonesWithoutUtc() { 462 // The country has multiple zones, none of which use UTC. 463 CountryTimeZones countryTimeZones = CountryTimeZones.createValidated( 464 "xx", "Europe/Paris", false /* defaultTimeZoneBoost */, false /* everUsesUtc */, 465 timeZoneMappings("America/Los_Angeles", "America/New_York", "Europe/Paris"), 466 "test"); 467 assertFalse(countryTimeZones.hasUtcZone(WHEN_DST)); 468 assertFalse(countryTimeZones.hasUtcZone(WHEN_NO_DST)); 469 } 470 471 @Test hasUtcZone_emptyZones()472 public void hasUtcZone_emptyZones() { 473 // The country has no valid zones. 474 CountryTimeZones countryTimeZones = CountryTimeZones.createValidated( 475 "xx", INVALID_TZ_ID, false /* defaultTimeZoneBoost */, false /* everUsesUtc */, 476 timeZoneMappings(INVALID_TZ_ID), "test"); 477 assertTrue(countryTimeZones.getTimeZoneMappings().isEmpty()); 478 assertFalse(countryTimeZones.hasUtcZone(WHEN_DST)); 479 assertFalse(countryTimeZones.hasUtcZone(WHEN_NO_DST)); 480 } 481 482 @Test timeZoneMapping_getTimeZone_badZoneId()483 public void timeZoneMapping_getTimeZone_badZoneId() { 484 TimeZoneMapping timeZoneMapping = 485 TimeZoneMapping.createForTests("DOES_NOT_EXIST", true, 1234L, list()); 486 try { 487 timeZoneMapping.getTimeZone(); 488 fail(); 489 } catch (RuntimeException expected) { 490 } 491 } 492 493 @Test timeZoneMapping_getTimeZone_validZoneId()494 public void timeZoneMapping_getTimeZone_validZoneId() { 495 TimeZoneMapping timeZoneMapping = 496 TimeZoneMapping.createForTests("Europe/London", true, 1234L, list()); 497 TimeZone timeZone = timeZoneMapping.getTimeZone(); 498 assertTrue(timeZone.isFrozen()); 499 assertEquals("Europe/London", timeZone.getID()); 500 } 501 502 @Test timeZoneMapping_isShownInPickerAfter_notHidden()503 public void timeZoneMapping_isShownInPickerAfter_notHidden() { 504 long notUsedAfter = 1234; 505 TimeZoneMapping timeZoneMapping = 506 TimeZoneMapping.createForTests( 507 "Europe/London", true /* showInPicker */, notUsedAfter, list()); 508 509 assertTrue(timeZoneMapping.isShownInPickerAt( 510 Instant.ofEpochMilli(notUsedAfter - 1_000))); 511 assertTrue(timeZoneMapping.isShownInPickerAt(Instant.ofEpochMilli(notUsedAfter))); 512 assertFalse(timeZoneMapping.isShownInPickerAt( 513 Instant.ofEpochMilli(notUsedAfter + 1_000))); 514 } 515 516 @Test timeZoneMapping_isShownInPickerAfter_hiddenTimeZone()517 public void timeZoneMapping_isShownInPickerAfter_hiddenTimeZone() { 518 long notUsedAfter = 1234; 519 TimeZoneMapping hiddenTimeZoneMapping = 520 TimeZoneMapping.createForTests( 521 "Moon/Secret_base", false /* showInPicker */, notUsedAfter, list()); 522 523 assertFalse(hiddenTimeZoneMapping.isShownInPickerAt( 524 Instant.ofEpochMilli(notUsedAfter - 1_000))); 525 assertFalse(hiddenTimeZoneMapping.isShownInPickerAt(Instant.ofEpochMilli(notUsedAfter))); 526 assertFalse(hiddenTimeZoneMapping.isShownInPickerAt( 527 Instant.ofEpochMilli(notUsedAfter + 1_000))); 528 } 529 assertImmutableTimeZone(TimeZone timeZone)530 private void assertImmutableTimeZone(TimeZone timeZone) { 531 try { 532 timeZone.setRawOffset(1000); 533 fail(); 534 } catch (UnsupportedOperationException expected) { 535 } 536 } 537 assertImmutableList(List<X> list)538 private static <X> void assertImmutableList(List<X> list) { 539 try { 540 list.add(null); 541 fail(); 542 } catch (UnsupportedOperationException expected) { 543 } 544 } 545 assertZoneEquals(TimeZone expected, TimeZone actual)546 private static void assertZoneEquals(TimeZone expected, TimeZone actual) { 547 // TimeZone.equals() only checks the ID, but that's ok for these tests. 548 assertEquals(expected, actual); 549 } 550 offsetResultEquals(OffsetResult expected, OffsetResult actual)551 private static boolean offsetResultEquals(OffsetResult expected, OffsetResult actual) { 552 return expected == actual 553 || (expected != null && actual != null 554 && Objects.equals(expected.getTimeZone().getID(), actual.getTimeZone().getID()) 555 && expected.isOnlyMatch() == actual.isOnlyMatch()); 556 } 557 558 /** 559 * Creates a list of default {@link TimeZoneMapping} objects with the specified time zone IDs. 560 */ timeZoneMapping(String timeZoneId, Long notUsedAfterMillis)561 private static TimeZoneMapping timeZoneMapping(String timeZoneId, Long notUsedAfterMillis) { 562 return TimeZoneMapping.createForTests( 563 timeZoneId, true /* picker */, notUsedAfterMillis, list()); 564 } 565 566 /** 567 * Creates a list of default {@link TimeZoneMapping} objects with the specified time zone IDs. 568 */ timeZoneMappings(String... timeZoneIds)569 private static List<TimeZoneMapping> timeZoneMappings(String... timeZoneIds) { 570 return Arrays.stream(timeZoneIds) 571 .map(x -> timeZoneMapping(x, null /* notUsedAfter */)) 572 .collect(Collectors.toList()); 573 } 574 zone(String id)575 private static TimeZone zone(String id) { 576 return TimeZone.getFrozenTimeZone(id); 577 } 578 list(X... xes)579 private static <X> List<X> list(X... xes) { 580 return Arrays.asList(xes); 581 } 582 } 583