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