1 /* 2 * Copyright 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.internal.telephony.nitz; 18 19 import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.ARBITRARY_DEBUG_INFO; 20 import static com.android.internal.telephony.nitz.TimeZoneLookupHelper.CountryResult.QUALITY_DEFAULT_BOOSTED; 21 import static com.android.internal.telephony.nitz.TimeZoneLookupHelper.CountryResult.QUALITY_MULTIPLE_ZONES_DIFFERENT_OFFSETS; 22 import static com.android.internal.telephony.nitz.TimeZoneLookupHelper.CountryResult.QUALITY_MULTIPLE_ZONES_SAME_OFFSET; 23 import static com.android.internal.telephony.nitz.TimeZoneLookupHelper.CountryResult.QUALITY_SINGLE_ZONE; 24 25 import static org.junit.Assert.assertEquals; 26 import static org.junit.Assert.assertFalse; 27 import static org.junit.Assert.assertNotNull; 28 import static org.junit.Assert.assertNull; 29 import static org.junit.Assert.assertTrue; 30 31 import android.icu.util.GregorianCalendar; 32 import android.icu.util.TimeZone; 33 import android.timezone.CountryTimeZones.OffsetResult; 34 35 import com.android.internal.telephony.NitzData; 36 import com.android.internal.telephony.nitz.TimeZoneLookupHelper.CountryResult; 37 38 import org.junit.After; 39 import org.junit.Before; 40 import org.junit.Test; 41 42 import java.util.Arrays; 43 import java.util.Calendar; 44 import java.util.Date; 45 import java.util.List; 46 import java.util.concurrent.TimeUnit; 47 48 public class TimeZoneLookupHelperTest { 49 // Note: Historical dates are used to avoid the test breaking due to data changes. 50 /* Arbitrary summer date in the Northern hemisphere. */ 51 private static final long NH_SUMMER_TIME_MILLIS = createUnixEpochTime(2015, 6, 20, 1, 2, 3); 52 /* Arbitrary winter date in the Northern hemisphere. */ 53 private static final long NH_WINTER_TIME_MILLIS = createUnixEpochTime(2015, 1, 20, 1, 2, 3); 54 55 private TimeZoneLookupHelper mTimeZoneLookupHelper; 56 57 @Before setUp()58 public void setUp() { 59 mTimeZoneLookupHelper = new TimeZoneLookupHelper(); 60 } 61 62 @After tearDown()63 public void tearDown() { 64 mTimeZoneLookupHelper = null; 65 } 66 67 @Test testLookupByNitzByNitz()68 public void testLookupByNitzByNitz() { 69 // Historical dates are used to avoid the test breaking due to data changes. 70 // However, algorithm updates may change the exact time zone returned, though it shouldn't 71 // ever be a less exact match. 72 long nhSummerTimeMillis = createUnixEpochTime(2015, 6, 20, 1, 2, 3); 73 long nhWinterTimeMillis = createUnixEpochTime(2015, 1, 20, 1, 2, 3); 74 75 String nhSummerTimeString = "15/06/20,01:02:03"; 76 String nhWinterTimeString = "15/01/20,01:02:03"; 77 78 // Tests for London, UK. 79 { 80 String lonSummerTimeString = nhSummerTimeString + "+4"; 81 int lonSummerOffsetMillis = (int) TimeUnit.HOURS.toMillis(1); 82 int lonSummerDstOffsetMillis = (int) TimeUnit.HOURS.toMillis(1); 83 84 String lonWinterTimeString = nhWinterTimeString + "+0"; 85 int lonWinterOffsetMillis = 0; 86 int lonWinterDstOffsetMillis = 0; 87 88 OffsetResult lookupResult; 89 90 // Summer, known DST state (DST == true). 91 NitzData lonSummerNitzDataWithOffset = NitzData.parse(lonSummerTimeString + ",4"); 92 lookupResult = mTimeZoneLookupHelper.lookupByNitz(lonSummerNitzDataWithOffset); 93 assertOffsetResultZoneOffsets(nhSummerTimeMillis, lonSummerOffsetMillis, 94 lonSummerDstOffsetMillis, lookupResult); 95 assertOffsetResultMetadata(false, lookupResult); 96 97 // Winter, known DST state (DST == false). 98 NitzData lonWinterNitzDataWithOffset = NitzData.parse(lonWinterTimeString + ",0"); 99 lookupResult = mTimeZoneLookupHelper.lookupByNitz(lonWinterNitzDataWithOffset); 100 assertOffsetResultZoneOffsets(nhWinterTimeMillis, lonWinterOffsetMillis, 101 lonWinterDstOffsetMillis, lookupResult); 102 assertOffsetResultMetadata(false, lookupResult); 103 104 // Summer, unknown DST state 105 NitzData lonSummerNitzDataWithoutOffset = NitzData.parse(lonSummerTimeString); 106 lookupResult = mTimeZoneLookupHelper.lookupByNitz(lonSummerNitzDataWithoutOffset); 107 assertOffsetResultZoneOffsets(nhSummerTimeMillis, lonSummerOffsetMillis, null, 108 lookupResult); 109 assertOffsetResultMetadata(false, lookupResult); 110 111 // Winter, unknown DST state 112 NitzData lonWinterNitzDataWithoutOffset = NitzData.parse(lonWinterTimeString); 113 lookupResult = mTimeZoneLookupHelper.lookupByNitz(lonWinterNitzDataWithoutOffset); 114 assertOffsetResultZoneOffsets(nhWinterTimeMillis, lonWinterOffsetMillis, null, 115 lookupResult); 116 assertOffsetResultMetadata(false, lookupResult); 117 } 118 119 // Tests for Mountain View, CA, US. 120 { 121 String mtvSummerTimeString = nhSummerTimeString + "-32"; 122 int mtvSummerOffsetMillis = (int) TimeUnit.HOURS.toMillis(-8); 123 int mtvSummerDstOffsetMillis = (int) TimeUnit.HOURS.toMillis(1); 124 125 String mtvWinterTimeString = nhWinterTimeString + "-28"; 126 int mtvWinterOffsetMillis = (int) TimeUnit.HOURS.toMillis(-7); 127 int mtvWinterDstOffsetMillis = 0; 128 129 OffsetResult lookupResult; 130 131 // Summer, known DST state (DST == true). 132 NitzData mtvSummerNitzDataWithOffset = NitzData.parse(mtvSummerTimeString + ",4"); 133 lookupResult = mTimeZoneLookupHelper.lookupByNitz(mtvSummerNitzDataWithOffset); 134 assertOffsetResultZoneOffsets(nhSummerTimeMillis, mtvSummerOffsetMillis, 135 mtvSummerDstOffsetMillis, lookupResult); 136 assertOffsetResultMetadata(false, lookupResult); 137 138 // Winter, known DST state (DST == false). 139 NitzData mtvWinterNitzDataWithOffset = NitzData.parse(mtvWinterTimeString + ",0"); 140 lookupResult = mTimeZoneLookupHelper.lookupByNitz(mtvWinterNitzDataWithOffset); 141 assertOffsetResultZoneOffsets(nhWinterTimeMillis, mtvWinterOffsetMillis, 142 mtvWinterDstOffsetMillis, lookupResult); 143 assertOffsetResultMetadata(false, lookupResult); 144 145 // Summer, unknown DST state 146 NitzData mtvSummerNitzDataWithoutOffset = NitzData.parse(mtvSummerTimeString); 147 lookupResult = mTimeZoneLookupHelper.lookupByNitz(mtvSummerNitzDataWithoutOffset); 148 assertOffsetResultZoneOffsets(nhSummerTimeMillis, mtvSummerOffsetMillis, null, 149 lookupResult); 150 assertOffsetResultMetadata(false, lookupResult); 151 152 // Winter, unknown DST state 153 NitzData mtvWinterNitzDataWithoutOffset = NitzData.parse(mtvWinterTimeString); 154 lookupResult = mTimeZoneLookupHelper.lookupByNitz(mtvWinterNitzDataWithoutOffset); 155 assertOffsetResultZoneOffsets(nhWinterTimeMillis, mtvWinterOffsetMillis, null, 156 lookupResult); 157 assertOffsetResultMetadata(false, lookupResult); 158 } 159 } 160 161 @Test testLookupByNitzCountry_filterByEffectiveDate()162 public void testLookupByNitzCountry_filterByEffectiveDate() { 163 // America/North_Dakota/Beulah was on Mountain Time until 2010-11-07, when it switched to 164 // Central Time. 165 String usIso = "US"; 166 167 // Try MDT / -6 hours in summer before America/North_Dakota/Beulah switched to Central Time. 168 { 169 String nitzString = "10/11/05,00:00:00-24,1"; // 2010-11-05 00:00:00 UTC, UTC-6, DST 170 NitzData nitzData = NitzData.parse(nitzString); 171 // The zone chosen is a side effect of zone ordering in the data files so we just check 172 // the isOnlyMatch value. 173 OffsetResult offsetResult = mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, usIso); 174 assertFalse(offsetResult.isOnlyMatch()); 175 } 176 177 // Try MDT / -6 hours in summer after America/North_Dakota/Beulah switched to central time. 178 { 179 String nitzString = "11/11/05,00:00:00-24,1"; // 2011-11-05 00:00:00 UTC, UTC-6, DST 180 NitzData nitzData = NitzData.parse(nitzString); 181 OffsetResult offsetResult = mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, usIso); 182 assertTrue(offsetResult.isOnlyMatch()); 183 } 184 } 185 186 @Test testLookupByNitzCountry_multipleMatches()187 public void testLookupByNitzCountry_multipleMatches() { 188 // America/Denver & America/Phoenix share the same Mountain Standard Time offset (i.e. 189 // during winter). 190 String usIso = "US"; 191 192 // Try MDT for a recent summer date: No ambiguity here. 193 { 194 String nitzString = "15/06/01,00:00:00-24,1"; // 2015-06-01 00:00:00 UTC, UTC-6, DST 195 NitzData nitzData = NitzData.parse(nitzString); 196 OffsetResult offsetResult = mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, usIso); 197 assertTrue(offsetResult.isOnlyMatch()); 198 } 199 200 // Try MST for a recent summer date: No ambiguity here. 201 { 202 String nitzString = "15/06/01,00:00:00-28,0"; // 2015-06-01 00:00:00 UTC, UTC-7, not DST 203 NitzData nitzData = NitzData.parse(nitzString); 204 OffsetResult offsetResult = mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, usIso); 205 assertTrue(offsetResult.isOnlyMatch()); 206 } 207 208 // Try MST for a recent winter date: There are multiple zones to pick from because of the 209 // America/Denver & America/Phoenix ambiguity. 210 { 211 String nitzString = "15/01/01,00:00:00-28,0"; // 2015-01-01 00:00:00 UTC, UTC-7, not DST 212 NitzData nitzData = NitzData.parse(nitzString); 213 OffsetResult offsetResult = mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, usIso); 214 assertFalse(offsetResult.isOnlyMatch()); 215 } 216 } 217 218 @Test testLookupByNitzCountry_dstKnownAndUnknown()219 public void testLookupByNitzCountry_dstKnownAndUnknown() { 220 // Historical dates are used to avoid the test breaking due to data changes. 221 // However, algorithm updates may change the exact time zone returned, though it shouldn't 222 // ever be a less exact match. 223 long nhSummerTimeMillis = createUnixEpochTime(2015, 6, 20, 1, 2, 3); 224 long nhWinterTimeMillis = createUnixEpochTime(2015, 1, 20, 1, 2, 3); 225 226 // A country in the northern hemisphere with one time zone. 227 String adIso = "AD"; // Andora 228 String summerTimeNitzString = "15/06/20,01:02:03+8"; // 2015-06-20 01:02:03 UTC, UTC+2 229 String winterTimeNitzString = "15/01/20,01:02:03+4"; // 2015-01-20 01:02:03 UTC, UTC+1 230 231 // Summer, known & correct DST state (DST == true). 232 { 233 String summerTimeNitzStringWithDst = summerTimeNitzString + ",1"; 234 NitzData nitzData = NitzData.parse(summerTimeNitzStringWithDst); 235 int expectedUtcOffset = (int) TimeUnit.HOURS.toMillis(2); 236 Integer expectedDstOffset = (int) TimeUnit.HOURS.toMillis(1); 237 assertEquals(expectedUtcOffset, nitzData.getLocalOffsetMillis()); 238 assertEquals(expectedDstOffset, nitzData.getDstAdjustmentMillis()); 239 240 OffsetResult adSummerWithDstResult = 241 mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, adIso); 242 OffsetResult expectedResult = 243 new OffsetResult(zone("Europe/Andorra"), true /* isOnlyMatch */); 244 assertEquals(expectedResult, adSummerWithDstResult); 245 assertOffsetResultZoneOffsets(nhSummerTimeMillis, expectedUtcOffset, expectedDstOffset, 246 adSummerWithDstResult); 247 } 248 249 // Summer, known & incorrect DST state (DST == false) 250 { 251 String summerTimeNitzStringWithNoDst = summerTimeNitzString + ",0"; 252 NitzData nitzData = NitzData.parse(summerTimeNitzStringWithNoDst); 253 254 OffsetResult adSummerWithNoDstResult = 255 mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, adIso); 256 assertNull(adSummerWithNoDstResult); 257 } 258 259 // Winter, known & correct DST state (DST == false) 260 { 261 String winterTimeNitzStringWithNoDst = winterTimeNitzString + ",0"; 262 NitzData nitzData = NitzData.parse(winterTimeNitzStringWithNoDst); 263 int expectedUtcOffset = (int) TimeUnit.HOURS.toMillis(1); 264 Integer expectedDstOffset = 0; 265 assertEquals(expectedUtcOffset, nitzData.getLocalOffsetMillis()); 266 assertEquals(expectedDstOffset, nitzData.getDstAdjustmentMillis()); 267 268 OffsetResult adWinterWithDstResult = 269 mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, adIso); 270 OffsetResult expectedResult = 271 new OffsetResult(zone("Europe/Andorra"), true /* isOnlyMatch */); 272 assertEquals(expectedResult, adWinterWithDstResult); 273 assertOffsetResultZoneOffsets(nhWinterTimeMillis, expectedUtcOffset, expectedDstOffset, 274 adWinterWithDstResult); 275 } 276 277 // Winter, known & incorrect DST state (DST == true) 278 { 279 String winterTimeNitzStringWithDst = winterTimeNitzString + ",1"; 280 NitzData nitzData = NitzData.parse(winterTimeNitzStringWithDst); 281 282 OffsetResult adWinterWithDstResult = 283 mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, adIso); 284 assertNull(adWinterWithDstResult); 285 } 286 287 // Summer, unknown DST state (will match any DST state with the correct offset). 288 { 289 NitzData nitzData = NitzData.parse(summerTimeNitzString); 290 int expectedUtcOffset = (int) TimeUnit.HOURS.toMillis(2); 291 Integer expectedDstOffset = null; // Unknown 292 assertEquals(expectedUtcOffset, nitzData.getLocalOffsetMillis()); 293 assertEquals(expectedDstOffset, nitzData.getDstAdjustmentMillis()); 294 295 OffsetResult adSummerUnknownDstResult = 296 mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, adIso); 297 OffsetResult expectedResult = 298 new OffsetResult(zone("Europe/Andorra"), true /* isOnlyMatch */); 299 assertEquals(expectedResult, adSummerUnknownDstResult); 300 assertOffsetResultZoneOffsets(nhSummerTimeMillis, expectedUtcOffset, expectedDstOffset, 301 adSummerUnknownDstResult); 302 } 303 304 // Winter, unknown DST state (will match any DST state with the correct offset) 305 { 306 NitzData nitzData = NitzData.parse(winterTimeNitzString); 307 int expectedUtcOffset = (int) TimeUnit.HOURS.toMillis(1); 308 Integer expectedDstOffset = null; // Unknown 309 assertEquals(expectedUtcOffset, nitzData.getLocalOffsetMillis()); 310 assertEquals(expectedDstOffset, nitzData.getDstAdjustmentMillis()); 311 312 OffsetResult adWinterUnknownDstResult = 313 mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, adIso); 314 OffsetResult expectedResult = 315 new OffsetResult(zone("Europe/Andorra"), true /* isOnlyMatch */); 316 assertEquals(expectedResult, adWinterUnknownDstResult); 317 assertOffsetResultZoneOffsets(nhWinterTimeMillis, expectedUtcOffset, expectedDstOffset, 318 adWinterUnknownDstResult); 319 } 320 } 321 322 @Test testLookupByCountry_oneZone()323 public void testLookupByCountry_oneZone() { 324 // GB has one time zone. 325 CountryResult expectedResult = 326 new CountryResult("Europe/London", QUALITY_SINGLE_ZONE, ARBITRARY_DEBUG_INFO); 327 assertEquals(expectedResult, 328 mTimeZoneLookupHelper.lookupByCountry("gb", NH_SUMMER_TIME_MILLIS)); 329 assertEquals(expectedResult, 330 mTimeZoneLookupHelper.lookupByCountry("gb", NH_WINTER_TIME_MILLIS)); 331 } 332 333 @Test testLookupByCountry_oneEffectiveZone()334 public void testLookupByCountry_oneEffectiveZone() { 335 // Historical dates are used to avoid the test breaking due to data changes. 336 337 // DE has two time zones according to IANA data: Europe/Berlin and Europe/Busingen, but they 338 // become effectively identical after 338950800000 millis (Sun, 28 Sep 1980 01:00:00 GMT). 339 // Android data tells us that Europe/Berlin the one that was "kept". 340 long nhSummerTimeMillis = createUnixEpochTime(1975, 6, 20, 1, 2, 3); 341 long nhWinterTimeMillis = createUnixEpochTime(1975, 1, 20, 1, 2, 3); 342 343 // Before 1980, quality == QUALITY_MULTIPLE_ZONES_SAME_OFFSET because Europe/Busingen was 344 // relevant. 345 CountryResult expectedResult = new CountryResult( 346 "Europe/Berlin", QUALITY_MULTIPLE_ZONES_SAME_OFFSET, ARBITRARY_DEBUG_INFO); 347 assertEquals(expectedResult, 348 mTimeZoneLookupHelper.lookupByCountry("de", nhSummerTimeMillis)); 349 assertEquals(expectedResult, 350 mTimeZoneLookupHelper.lookupByCountry("de", nhWinterTimeMillis)); 351 352 // And in 2015, quality == QUALITY_SINGLE_ZONE because Europe/Busingen became irrelevant 353 // after 1980. 354 nhSummerTimeMillis = createUnixEpochTime(2015, 6, 20, 1, 2, 3); 355 nhWinterTimeMillis = createUnixEpochTime(2015, 1, 20, 1, 2, 3); 356 357 expectedResult = 358 new CountryResult("Europe/Berlin", QUALITY_SINGLE_ZONE, ARBITRARY_DEBUG_INFO); 359 assertEquals(expectedResult, 360 mTimeZoneLookupHelper.lookupByCountry("de", nhSummerTimeMillis)); 361 assertEquals(expectedResult, 362 mTimeZoneLookupHelper.lookupByCountry("de", nhWinterTimeMillis)); 363 } 364 365 @Test testDefaultBoostBehavior()366 public void testDefaultBoostBehavior() { 367 long timeMillis = createUnixEpochTime(2015, 6, 20, 1, 2, 3); 368 369 // An example known to be explicitly boosted. New Zealand has two zones but the vast 370 // majority of the population use one of them so Android's data file explicitly boosts the 371 // country default. If that changes in future this test will need to be changed to use 372 // another example. 373 String countryIsoCode = "nz"; 374 375 CountryResult expectedResult = new CountryResult( 376 "Pacific/Auckland", QUALITY_DEFAULT_BOOSTED, ARBITRARY_DEBUG_INFO); 377 assertEquals(expectedResult, 378 mTimeZoneLookupHelper.lookupByCountry(countryIsoCode, timeMillis)); 379 380 // Data correct for the North and South Island. 381 int majorityWinterOffset = (int) TimeUnit.HOURS.toMillis(12); 382 NitzData majorityNitzData = NitzData.createForTests( 383 majorityWinterOffset, 0, timeMillis, null /* emulatorTimeZone */); 384 385 // Boost doesn't directly affect lookupByNitzCountry() 386 OffsetResult majorityOffsetResult = 387 mTimeZoneLookupHelper.lookupByNitzCountry(majorityNitzData, countryIsoCode); 388 assertEquals(zone("Pacific/Auckland"), majorityOffsetResult.getTimeZone()); 389 assertTrue(majorityOffsetResult.isOnlyMatch()); 390 391 // Data correct for the Chatham Islands. 392 int chathamWinterOffset = majorityWinterOffset + ((int) TimeUnit.MINUTES.toMillis(45)); 393 NitzData chathamNitzData = NitzData.createForTests( 394 chathamWinterOffset, 0, timeMillis, null /* emulatorTimeZone */); 395 OffsetResult chathamOffsetResult = 396 mTimeZoneLookupHelper.lookupByNitzCountry(chathamNitzData, countryIsoCode); 397 assertEquals(zone("Pacific/Chatham"), chathamOffsetResult.getTimeZone()); 398 assertTrue(chathamOffsetResult.isOnlyMatch()); 399 400 // NITZ data that makes no sense for NZ results in no match. 401 int nonsenseOffset = (int) TimeUnit.HOURS.toMillis(5); 402 NitzData nonsenseNitzData = NitzData.createForTests( 403 nonsenseOffset, 0, timeMillis, null /* emulatorTimeZone */); 404 OffsetResult nonsenseOffsetResult = 405 mTimeZoneLookupHelper.lookupByNitzCountry(nonsenseNitzData, countryIsoCode); 406 assertNull(nonsenseOffsetResult); 407 } 408 409 @Test testNoDefaultBoostBehavior()410 public void testNoDefaultBoostBehavior() { 411 long timeMillis = createUnixEpochTime(2015, 6, 20, 1, 2, 3); 412 413 // An example known to not be explicitly boosted. Micronesia is spread out and there's no 414 // suitable default. 415 String countryIsoCode = "fm"; 416 417 CountryResult expectedResult = new CountryResult( 418 "Pacific/Pohnpei", QUALITY_MULTIPLE_ZONES_DIFFERENT_OFFSETS, ARBITRARY_DEBUG_INFO); 419 assertEquals(expectedResult, 420 mTimeZoneLookupHelper.lookupByCountry(countryIsoCode, timeMillis)); 421 422 // Prove an OffsetResult can be found with the correct offset. 423 int chuukWinterOffset = (int) TimeUnit.HOURS.toMillis(10); 424 NitzData chuukNitzData = NitzData.createForTests( 425 chuukWinterOffset, 0, timeMillis, null /* emulatorTimeZone */); 426 OffsetResult chuukOffsetResult = 427 mTimeZoneLookupHelper.lookupByNitzCountry(chuukNitzData, countryIsoCode); 428 assertEquals(zone("Pacific/Chuuk"), chuukOffsetResult.getTimeZone()); 429 assertTrue(chuukOffsetResult.isOnlyMatch()); 430 431 // NITZ data that makes no sense for FM: no boost means we should get nothing. 432 int nonsenseOffset = (int) TimeUnit.HOURS.toMillis(5); 433 NitzData nonsenseNitzData = NitzData.createForTests( 434 nonsenseOffset, 0, timeMillis, null /* emulatorTimeZone */); 435 OffsetResult nonsenseOffsetResult = 436 mTimeZoneLookupHelper.lookupByNitzCountry(nonsenseNitzData, countryIsoCode); 437 assertNull(nonsenseOffsetResult); 438 } 439 440 @Test testLookupByCountry_multipleZones()441 public void testLookupByCountry_multipleZones() { 442 // US has many time zones that have different offsets. 443 CountryResult expectedResult = new CountryResult( 444 "America/New_York", QUALITY_MULTIPLE_ZONES_DIFFERENT_OFFSETS, ARBITRARY_DEBUG_INFO); 445 assertEquals(expectedResult, 446 mTimeZoneLookupHelper.lookupByCountry("us", NH_SUMMER_TIME_MILLIS)); 447 assertEquals(expectedResult, 448 mTimeZoneLookupHelper.lookupByCountry("us", NH_WINTER_TIME_MILLIS)); 449 } 450 451 @Test testCountryUsesUtc()452 public void testCountryUsesUtc() { 453 assertFalse(mTimeZoneLookupHelper.countryUsesUtc("us", NH_SUMMER_TIME_MILLIS)); 454 assertFalse(mTimeZoneLookupHelper.countryUsesUtc("us", NH_WINTER_TIME_MILLIS)); 455 assertFalse(mTimeZoneLookupHelper.countryUsesUtc("gb", NH_SUMMER_TIME_MILLIS)); 456 assertTrue(mTimeZoneLookupHelper.countryUsesUtc("gb", NH_WINTER_TIME_MILLIS)); 457 } 458 459 @Test regressionTest_Bug167653885()460 public void regressionTest_Bug167653885() { 461 // This NITZ caused an error in Android R because lookupByNitz was returning a time zone 462 // known to android.icu.util.TimeZone but not java.util.TimeZone. 463 NitzData nitzData = NitzData.parse("20/05/08,04:15:48+08,00"); 464 OffsetResult offsetResult = mTimeZoneLookupHelper.lookupByNitz(nitzData); 465 assertNotNull(offsetResult); 466 467 List<String> knownIds = Arrays.asList(java.util.TimeZone.getAvailableIDs()); 468 assertTrue(knownIds.contains(offsetResult.getTimeZone().getID())); 469 } 470 471 /** 472 * Assert the time zone in the OffsetResult has the expected properties at the specified time. 473 */ assertOffsetResultZoneOffsets(long time, int expectedOffsetAtTime, Integer expectedDstAtTime, OffsetResult lookupResult)474 private static void assertOffsetResultZoneOffsets(long time, int expectedOffsetAtTime, 475 Integer expectedDstAtTime, OffsetResult lookupResult) { 476 477 TimeZone timeZone = lookupResult.getTimeZone(); 478 GregorianCalendar calendar = new GregorianCalendar(timeZone); 479 calendar.setTimeInMillis(time); 480 int actualOffsetAtTime = 481 calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET); 482 assertEquals(expectedOffsetAtTime, actualOffsetAtTime); 483 484 if (expectedDstAtTime != null) { 485 Date date = new Date(time); 486 assertEquals(expectedDstAtTime > 0, timeZone.inDaylightTime(date)); 487 488 // The code under test assumes DST means +1 in all cases, 489 // This code makes fewer assumptions. 490 assertEquals(expectedDstAtTime.intValue(), calendar.get(Calendar.DST_OFFSET)); 491 } 492 } 493 assertOffsetResultMetadata(boolean isOnlyMatch, OffsetResult lookupResult)494 private static void assertOffsetResultMetadata(boolean isOnlyMatch, OffsetResult lookupResult) { 495 assertEquals(isOnlyMatch, lookupResult.isOnlyMatch()); 496 } 497 createUnixEpochTime( int year, int monthOfYear, int dayOfMonth, int hourOfDay, int minute, int second)498 private static long createUnixEpochTime( 499 int year, int monthOfYear, int dayOfMonth, int hourOfDay, int minute, int second) { 500 GregorianCalendar calendar = new GregorianCalendar(zone("UTC")); 501 calendar.clear(); // Clear millis, etc. 502 calendar.set(year, monthOfYear - 1, dayOfMonth, hourOfDay, minute, second); 503 return calendar.getTimeInMillis(); 504 } 505 zone(String zoneId)506 private static TimeZone zone(String zoneId) { 507 return TimeZone.getFrozenTimeZone(zoneId); 508 } 509 } 510