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