1 /* 2 * Copyright (C) 2018 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.icu; 18 19 import org.junit.Test; 20 21 import android.icu.text.TimeZoneNames; 22 import android.icu.util.VersionInfo; 23 import android.system.Os; 24 25 import java.io.File; 26 import java.util.ArrayList; 27 import java.util.Arrays; 28 import java.util.Collections; 29 import java.util.Date; 30 import java.util.List; 31 import java.util.Locale; 32 import java.util.Set; 33 import java.util.concurrent.BrokenBarrierException; 34 import java.util.concurrent.CyclicBarrier; 35 import java.util.concurrent.TimeUnit; 36 import java.util.concurrent.atomic.AtomicInteger; 37 import java.util.function.Function; 38 import java.util.stream.Collectors; 39 40 import libcore.icu.ICU; 41 import libcore.timezone.TimeZoneDataFiles; 42 import libcore.timezone.TimeZoneFinder; 43 import libcore.timezone.TzDataSetVersion; 44 import libcore.timezone.ZoneInfoDB; 45 import libcore.util.CoreLibraryDebug; 46 import libcore.util.DebugInfo; 47 48 import static org.junit.Assert.assertEquals; 49 import static org.junit.Assert.assertFalse; 50 import static org.junit.Assert.assertTrue; 51 import static org.junit.Assert.fail; 52 53 /** 54 * Tests that compare ICU and libcore time zone behavior and similar cross-cutting concerns. 55 */ 56 public class TimeZoneIntegrationTest { 57 58 // http://b/28949992 59 @Test testJavaSetDefaultAppliesToIcuTimezone()60 public void testJavaSetDefaultAppliesToIcuTimezone() { 61 java.util.TimeZone origTz = java.util.TimeZone.getDefault(); 62 try { 63 android.icu.util.TimeZone origIcuTz = android.icu.util.TimeZone.getDefault(); 64 assertEquals(origTz.getID(), origIcuTz.getID()); 65 66 java.util.TimeZone tz = java.util.TimeZone.getTimeZone("GMT-05:00"); 67 java.util.TimeZone.setDefault(tz); 68 android.icu.util.TimeZone icuTz = android.icu.util.TimeZone.getDefault(); 69 assertEquals(tz.getID(), icuTz.getID()); 70 } finally { 71 java.util.TimeZone.setDefault(origTz); 72 } 73 } 74 75 // http://b/30937209 76 @Test testSetDefaultDeadlock()77 public void testSetDefaultDeadlock() throws InterruptedException, BrokenBarrierException { 78 // Since this tests a deadlock, the test has two fundamental problems: 79 // - it is probabilistic: it's not guaranteed to fail if the problem exists 80 // - if it fails, it will effectively hang the current runtime, as no other thread will 81 // be able to call TimeZone.getDefault()/setDefault() successfully any more. 82 83 // 10 was too low to be reliable, 100 failed more than half the time (on a bullhead). 84 final int iterations = 100; 85 java.util.TimeZone otherTimeZone = java.util.TimeZone.getTimeZone("Europe/London"); 86 AtomicInteger setterCount = new AtomicInteger(); 87 CyclicBarrier startBarrier = new CyclicBarrier(2); 88 Thread setter = new Thread(() -> { 89 waitFor(startBarrier); 90 for (int i = 0; i < iterations; i++) { 91 java.util.TimeZone.setDefault(otherTimeZone); 92 java.util.TimeZone.setDefault(null); 93 setterCount.set(i+1); 94 } 95 }); 96 setter.setName("testSetDefaultDeadlock setter"); 97 98 AtomicInteger getterCount = new AtomicInteger(); 99 Thread getter = new Thread(() -> { 100 waitFor(startBarrier); 101 for (int i = 0; i < iterations; i++) { 102 android.icu.util.TimeZone.getDefault(); 103 getterCount.set(i+1); 104 } 105 }); 106 getter.setName("testSetDefaultDeadlock getter"); 107 108 setter.start(); 109 getter.start(); 110 111 // 2 seconds is plenty: If successful, we usually complete much faster. 112 setter.join(1000); 113 getter.join(1000); 114 if (setter.isAlive() || getter.isAlive()) { 115 fail("Threads are still alive. Getter iteration count: " + getterCount.get() 116 + ", setter iteration count: " + setterCount.get()); 117 } 118 // Guard against unexpected uncaught exceptions. 119 assertEquals("Setter iterations", iterations, setterCount.get()); 120 assertEquals("Getter iterations", iterations, getterCount.get()); 121 } 122 123 // http://b/30979219 124 @Test testSetDefaultRace()125 public void testSetDefaultRace() throws InterruptedException { 126 // Since this tests a race condition, the test is probabilistic: it's not guaranteed to 127 // fail if the problem exists 128 129 // These iterations are significantly faster than the ones in #testSetDefaultDeadlock 130 final int iterations = 10000; 131 List<Throwable> exceptions = Collections.synchronizedList(new ArrayList<>()); 132 Thread.UncaughtExceptionHandler handler = (t, e) -> exceptions.add(e); 133 134 CyclicBarrier startBarrier = new CyclicBarrier(2); 135 Thread clearer = new Thread(() -> { 136 waitFor(startBarrier); 137 for (int i = 0; i < iterations; i++) { 138 // This is not public API but can effectively be invoked via 139 // java.util.TimeZone.setDefault. Call it directly to reduce the amount of code 140 // involved in this test. 141 android.icu.util.TimeZone.setICUDefault(null); 142 } 143 }); 144 clearer.setName("testSetDefaultRace clearer"); 145 clearer.setUncaughtExceptionHandler(handler); 146 147 Thread getter = new Thread(() -> { 148 waitFor(startBarrier); 149 for (int i = 0; i < iterations; i++) { 150 android.icu.util.TimeZone.getDefault(); 151 } 152 }); 153 getter.setName("testSetDefaultRace getter"); 154 getter.setUncaughtExceptionHandler(handler); 155 156 clearer.start(); 157 getter.start(); 158 159 // 20 seconds is plenty: If successful, we usually complete much faster. 160 clearer.join(10000); 161 getter.join(10000); 162 163 if (!exceptions.isEmpty()) { 164 Throwable firstException = exceptions.get(0); 165 firstException.printStackTrace(); 166 fail("Threads did not succeed successfully: " + firstException); 167 } 168 assertFalse("clearer thread is still alive", clearer.isAlive()); 169 assertFalse("getter thread is still alive", getter.isAlive()); 170 } 171 waitFor(CyclicBarrier barrier)172 private static void waitFor(CyclicBarrier barrier) { 173 try { 174 barrier.await(); 175 } catch (InterruptedException | BrokenBarrierException e) { 176 throw new RuntimeException(e); 177 } 178 } 179 180 /** 181 * Confirms that ICU agrees with the rest of libcore about the version of the TZ data in use. 182 */ 183 @Test testTimeZoneDataVersion()184 public void testTimeZoneDataVersion() { 185 String icu4cTzVersion = ICU.getTZDataVersion(); 186 187 String zoneInfoTzVersion = ZoneInfoDB.getInstance().getVersion(); 188 assertEquals(icu4cTzVersion, zoneInfoTzVersion); 189 190 String icu4jTzVersion = android.icu.util.TimeZone.getTZDataVersion(); 191 assertEquals(icu4jTzVersion, zoneInfoTzVersion); 192 193 String tzLookupTzVersion = TimeZoneFinder.getInstance().getIanaVersion(); 194 assertEquals(icu4jTzVersion, tzLookupTzVersion); 195 } 196 197 /** 198 * Asserts that the time zone format major / minor versions meets expectations. 199 * 200 * <p>If a set of time zone files is to be compatible with a device then the format of the files 201 * must meet the Android team's expectations. This is a sanity check to ensure that devices 202 * running the test (e.g. under CTS) have not modified the TzDataSetVersion major / minor 203 * versions for some reason: if they have it would render updated time zone files sent to the 204 * device incompatible. 205 */ 206 @Test testTimeZoneFormatVersion()207 public void testTimeZoneFormatVersion() { 208 // The code below compares the final static int constant values (inlined at test compile 209 // time) with the version reported at runtime. This saves us hardcoding the numbers in two 210 // places. 211 assertEquals(TzDataSetVersion.CURRENT_FORMAT_MAJOR_VERSION, 212 TzDataSetVersion.currentFormatMajorVersion()); 213 assertEquals(TzDataSetVersion.CURRENT_FORMAT_MINOR_VERSION, 214 TzDataSetVersion.currentFormatMinorVersion()); 215 } 216 217 /** 218 * Asserts that all expected sets of time zone files meet format expectations. 219 * 220 * <p>This uses the device's knowledge of the format version it expects and the 221 * {@link TzDataSetVersion} files that accompany the known time zone data files. 222 * 223 * <p>This is a sanity check to ensure that there's no way of installing incompatible data 224 * on a device. It assumes that {@link TzDataSetVersion} is updated as it should be when changes 225 * are made that might affect time zone code / time zone data compatibility. 226 */ 227 @Test testTzDataSetVersions()228 public void testTzDataSetVersions() throws Exception { 229 String moduleTzVersionFile = "tz/" + TzDataSetVersion.DEFAULT_FILE_NAME; 230 231 String timeZoneModuleVersionFile = 232 TimeZoneDataFiles.getTimeZoneModuleFile(moduleTzVersionFile); 233 // We currently treat the time zone APEX as optional in code. Its is also not present on ART 234 // host environments. 235 if (fileExists(timeZoneModuleVersionFile)) { 236 assertTzDataSetVersionIsCompatible(timeZoneModuleVersionFile); 237 } 238 239 String runtimeModuleVersionFile = 240 TimeZoneDataFiles.getRuntimeModuleFile(moduleTzVersionFile); 241 assertTzDataSetVersionIsCompatible(runtimeModuleVersionFile); 242 243 // Check getRuntimeModuleTzVersionFile() is doing the right thing. 244 // getRuntimeModuleTzVersionFile() should go away when its one user, RulesManagerService, 245 // is removed from the platform code. http://b/123398797 246 assertEquals(TimeZoneDataFiles.getRuntimeModuleTzVersionFile(), runtimeModuleVersionFile); 247 248 // TODO: Remove this once the /system copy of time zone files have gone away. See also 249 // testTimeZoneDebugInfo(). 250 assertTzDataSetVersionIsCompatible( 251 TimeZoneDataFiles.getSystemTimeZoneFile(TzDataSetVersion.DEFAULT_FILE_NAME)); 252 } 253 assertTzDataSetVersionIsCompatible(String versionFile)254 private static void assertTzDataSetVersionIsCompatible(String versionFile) throws Exception { 255 TzDataSetVersion actualVersion = 256 TzDataSetVersion.readFromFile(new File(versionFile)); 257 assertEquals( 258 TzDataSetVersion.currentFormatMajorVersion(), 259 actualVersion.formatMajorVersion); 260 int minDeviceMinorVersion = TzDataSetVersion.currentFormatMinorVersion(); 261 assertTrue(actualVersion.formatMinorVersion >= minDeviceMinorVersion); 262 } 263 264 /** 265 * A test for confirming debug information matches file system state on device. 266 * It can also be used to confirm that device and host environments satisfy file system 267 * expectations. 268 */ 269 @Test testTimeZoneDebugInfo()270 public void testTimeZoneDebugInfo() throws Exception { 271 DebugInfo debugInfo = CoreLibraryDebug.getDebugInfo(); 272 273 // Devices are expected to have a time zone module which overrides or extends the data in 274 // the runtime module depending on the file. It's not actually mandatory for all Android 275 // devices right now although it may be required for some subset of Android devices. It 276 // isn't present on host ART. 277 String tzModuleStatus = getDebugStringValue(debugInfo, 278 "core_library.timezone.source.tzdata_module_status"); 279 String apexRootDir = TimeZoneDataFiles.getTimeZoneModuleFile(""); 280 List<String> dataModuleFiles = 281 createModuleTzFileNames(TimeZoneDataFiles::getTimeZoneModuleFile); 282 String icuOverlayFile = TimeZoneDataFiles.getTimeZoneModuleFile("icu/icu_tzdata.dat"); 283 if (fileExists(apexRootDir)) { 284 assertEquals("OK", tzModuleStatus); 285 dataModuleFiles.forEach(TimeZoneIntegrationTest::assertFileExists); 286 assertFileExists(icuOverlayFile); 287 } else { 288 assertEquals("NOT_FOUND", tzModuleStatus); 289 dataModuleFiles.forEach(TimeZoneIntegrationTest::assertFileDoesNotExist); 290 assertFileDoesNotExist(icuOverlayFile); 291 } 292 293 // Every device should have a runtime module copy of time zone data since we expect every 294 // device to have a runtime module. This is the base copy of time zone data that can be 295 // updated when we update the runtime module. Host ART should match device. 296 assertEquals("OK", getDebugStringValue(debugInfo, 297 "core_library.timezone.source.runtime_module_status")); 298 assertFileExists(TimeZoneDataFiles.getRuntimeModuleFile("")); 299 List<String> runtimeModuleFiles = 300 createModuleTzFileNames(TimeZoneDataFiles::getRuntimeModuleFile); 301 runtimeModuleFiles.forEach(TimeZoneIntegrationTest::assertFileExists); 302 303 String icuDatFileName = "icudt" + VersionInfo.ICU_VERSION.getMajor() + "l.dat"; 304 String runtimeModuleIcuData = 305 TimeZoneDataFiles.getRuntimeModuleFile("icu/" + icuDatFileName); 306 assertFileExists(runtimeModuleIcuData); 307 308 // Devices currently have a subset of the time zone files in /system. These are going away 309 // but we test them while they exist. Host ART should match device. 310 assertEquals("OK", getDebugStringValue(debugInfo, 311 "core_library.timezone.source.system_status")); 312 assertFileExists( 313 TimeZoneDataFiles.getSystemTimeZoneFile(TzDataSetVersion.DEFAULT_FILE_NAME)); 314 assertFileExists(TimeZoneDataFiles.getSystemTimeZoneFile("tzdata")); 315 // The following files once existed in /system but have been removed as part of APEX work. 316 assertFileDoesNotExist(TimeZoneDataFiles.getSystemTimeZoneFile("tzlookup.xml")); 317 318 // It's hard to assert much about this file as there is a symlink in /system on device for 319 // app compatibility (b/122985829) but it doesn't exist in host environments. If the file 320 // exists we can say it should resolve (realpath) to the same file as the runtime module. 321 String systemIcuData = TimeZoneDataFiles.getSystemIcuFile(icuDatFileName); 322 if (new File(systemIcuData).exists()) { 323 assertEquals(Os.realpath(runtimeModuleIcuData), Os.realpath(systemIcuData)); 324 } 325 } 326 createModuleTzFileNames( Function<String, String> pathCreationFunction)327 private static List<String> createModuleTzFileNames( 328 Function<String, String> pathCreationFunction) { 329 List<String> relativePaths = Arrays.asList( 330 "tz/" + TzDataSetVersion.DEFAULT_FILE_NAME, 331 "tz/tzdata", 332 "tz/tzlookup.xml"); 333 return relativePaths.stream().map(pathCreationFunction).collect(Collectors.toList()); 334 } 335 fileExists(String fileName)336 private static boolean fileExists(String fileName) { 337 return new File(fileName).exists(); 338 } 339 assertFileDoesNotExist(String fileName)340 private static void assertFileDoesNotExist(String fileName) { 341 assertFalse(fileName + " must not exist", fileExists(fileName)); 342 } 343 assertFileExists(String fileName)344 private static void assertFileExists(String fileName) { 345 assertTrue(fileName + " must exist", fileExists(fileName)); 346 } 347 getDebugStringValue(DebugInfo debugInfo, String key)348 private String getDebugStringValue(DebugInfo debugInfo, String key) { 349 return debugInfo.getDebugEntry(key).getStringValue(); 350 } 351 352 /** 353 * Confirms that ICU can recognize all the time zone IDs used by the ZoneInfoDB data. 354 * ICU's IDs may be a superset. 355 */ 356 @Test testTimeZoneIdLookup()357 public void testTimeZoneIdLookup() { 358 String[] zoneInfoDbAvailableIds = ZoneInfoDB.getInstance().getAvailableIDs(); 359 360 // ICU has a known set of IDs. We want ANY because we don't want to filter to ICU's 361 // canonical IDs only. 362 Set<String> icuAvailableIds = android.icu.util.TimeZone.getAvailableIDs( 363 android.icu.util.TimeZone.SystemTimeZoneType.ANY, null /* region */, 364 null /* rawOffset */); 365 366 List<String> nonIcuAvailableIds = new ArrayList<>(); 367 List<String> creationFailureIds = new ArrayList<>(); 368 List<String> noCanonicalLookupIds = new ArrayList<>(); 369 List<String> nonSystemIds = new ArrayList<>(); 370 for (String zoneInfoDbId : zoneInfoDbAvailableIds) { 371 if (!icuAvailableIds.contains(zoneInfoDbId)) { 372 nonIcuAvailableIds.add(zoneInfoDbId); 373 } 374 375 boolean[] isSystemId = new boolean[1]; 376 String canonicalId = android.icu.util.TimeZone.getCanonicalID(zoneInfoDbId, isSystemId); 377 if (canonicalId == null) { 378 noCanonicalLookupIds.add(zoneInfoDbId); 379 } 380 if (!isSystemId[0]) { 381 nonSystemIds.add(zoneInfoDbId); 382 } 383 384 android.icu.util.TimeZone icuTimeZone = 385 android.icu.util.TimeZone.getTimeZone(zoneInfoDbId); 386 if (icuTimeZone.getID().equals(android.icu.util.TimeZone.UNKNOWN_ZONE_ID)) { 387 creationFailureIds.add(zoneInfoDbId); 388 } 389 } 390 assertTrue("Non-ICU available IDs: " + nonIcuAvailableIds 391 + ", creation failed IDs: " + creationFailureIds 392 + ", non-system IDs: " + nonSystemIds 393 + ", ids without canonical IDs: " + noCanonicalLookupIds, 394 nonIcuAvailableIds.isEmpty() 395 && creationFailureIds.isEmpty() 396 && nonSystemIds.isEmpty() 397 && noCanonicalLookupIds.isEmpty()); 398 } 399 400 // http://b/30527513 401 @Test testDisplayNamesWithScript()402 public void testDisplayNamesWithScript() throws Exception { 403 Locale latinLocale = Locale.forLanguageTag("sr-Latn-RS"); 404 Locale cyrillicLocale = Locale.forLanguageTag("sr-Cyrl-RS"); 405 Locale noScriptLocale = Locale.forLanguageTag("sr-RS"); 406 java.util.TimeZone tz = java.util.TimeZone.getTimeZone("Europe/London"); 407 408 final String latinName = "Srednje vreme po Griniču"; 409 final String cyrillicName = "Средње време по Гриничу"; 410 411 // Check java.util.TimeZone 412 assertEquals(latinName, tz.getDisplayName(latinLocale)); 413 assertEquals(cyrillicName, tz.getDisplayName(cyrillicLocale)); 414 assertEquals(cyrillicName, tz.getDisplayName(noScriptLocale)); 415 416 // Check ICU TimeZoneNames 417 // The one-argument getDisplayName() override uses LONG_GENERIC style which is different 418 // from what java.util.TimeZone uses. Force the LONG style to get equivalent results. 419 final int style = android.icu.util.TimeZone.LONG; 420 android.icu.util.TimeZone utz = android.icu.util.TimeZone.getTimeZone(tz.getID()); 421 assertEquals(latinName, utz.getDisplayName(false, style, latinLocale)); 422 assertEquals(cyrillicName, utz.getDisplayName(false, style, cyrillicLocale)); 423 assertEquals(cyrillicName, utz.getDisplayName(false, style, noScriptLocale)); 424 } 425 426 /** 427 * This test is to catch issues with the rules update process that could let the 428 * "negative DST" scheme enter the Android data set for either java.util.TimeZone or 429 * android.icu.util.TimeZone. 430 */ 431 @Test testDstMeansSummer()432 public void testDstMeansSummer() { 433 // Ireland was the original example that caused the default IANA upstream tzdata to contain 434 // a zone where DST is in the Winter (since tzdata 2018e, though it was tried in 2018a 435 // first). This change was made to historical and future transitions. 436 // 437 // The upstream reasoning went like this: "Irish *Standard* Time" is summer, so the other 438 // time must be the DST. So, DST is considered to be in the winter and the associated DST 439 // adjustment is negative from the standard time. In the old scheme "Irish Standard Time" / 440 // summer was just modeled as the DST in common with all other global time zones. 441 // 442 // Unfortunately, various users of formatting APIs assume standard and DST times are 443 // consistent and (effectively) that "DST" means "summer". We likely cannot adopt the 444 // concept of a winter DST without risking app compat issues. 445 // 446 // For example, getDisplayName(boolean daylight) has always returned the winter time for 447 // false, and the summer time for true. If we change this then it should be changed on a 448 // major release boundary, with improved APIs (e.g. a version of getDisplayName() that takes 449 // a millis), existing API behavior made dependent on target API version, and after fixing 450 // any platform code that makes incorrect assumptions about DST meaning "1 hour forward". 451 452 final String timeZoneId = "Europe/Dublin"; 453 final Locale locale = Locale.UK; 454 // 26 Oct 2015 01:00:00 GMT - one day after the start of "Greenwich Mean Time" in 455 // Europe/Dublin in 2015. An arbitrary historical example of winter in Ireland. 456 final long winterTimeMillis = 1445821200000L; 457 final String winterTimeName = "Greenwich Mean Time"; 458 final int winterOffsetRawMillis = 0; 459 final int winterOffsetDstMillis = 0; 460 461 // 30 Mar 2015 01:00:00 GMT - one day after the start of "Irish Standard Time" in 462 // Europe/Dublin in 2015. An arbitrary historical example of summer in Ireland. 463 final long summerTimeMillis = 1427677200000L; 464 final String summerTimeName = "Irish Standard Time"; 465 final int summerOffsetRawMillis = 0; 466 final int summerOffsetDstMillis = (int) TimeUnit.HOURS.toMillis(1); 467 468 // There is no common interface between java.util.TimeZone and android.icu.util.TimeZone 469 // so the tests are for each are effectively duplicated. 470 471 // java.util.TimeZone 472 { 473 java.util.TimeZone timeZone = java.util.TimeZone.getTimeZone(timeZoneId); 474 assertTrue(timeZone.useDaylightTime()); 475 476 assertFalse(timeZone.inDaylightTime(new Date(winterTimeMillis))); 477 assertTrue(timeZone.inDaylightTime(new Date(summerTimeMillis))); 478 479 assertEquals(winterOffsetRawMillis + winterOffsetDstMillis, 480 timeZone.getOffset(winterTimeMillis)); 481 assertEquals(summerOffsetRawMillis + summerOffsetDstMillis, 482 timeZone.getOffset(summerTimeMillis)); 483 assertEquals(winterTimeName, 484 timeZone.getDisplayName(false /* daylight */, java.util.TimeZone.LONG, 485 locale)); 486 assertEquals(summerTimeName, 487 timeZone.getDisplayName(true /* daylight */, java.util.TimeZone.LONG, 488 locale)); 489 } 490 491 // android.icu.util.TimeZone 492 { 493 android.icu.util.TimeZone timeZone = android.icu.util.TimeZone.getTimeZone(timeZoneId); 494 assertTrue(timeZone.useDaylightTime()); 495 496 assertFalse(timeZone.inDaylightTime(new Date(winterTimeMillis))); 497 assertTrue(timeZone.inDaylightTime(new Date(summerTimeMillis))); 498 499 assertEquals(winterOffsetRawMillis + winterOffsetDstMillis, 500 timeZone.getOffset(winterTimeMillis)); 501 assertEquals(summerOffsetRawMillis + summerOffsetDstMillis, 502 timeZone.getOffset(summerTimeMillis)); 503 504 // These methods show the trouble we'd have if callers were to take the output from 505 // inDaylightTime() and pass it to getDisplayName(). 506 assertEquals(winterTimeName, 507 timeZone.getDisplayName(false /* daylight */, android.icu.util.TimeZone.LONG, 508 locale)); 509 assertEquals(summerTimeName, 510 timeZone.getDisplayName(true /* daylight */, android.icu.util.TimeZone.LONG, 511 locale)); 512 513 // APIs not identical to java.util.TimeZone tested below. 514 int[] offsets = new int[2]; 515 timeZone.getOffset(winterTimeMillis, false /* local */, offsets); 516 assertEquals(winterOffsetRawMillis, offsets[0]); 517 assertEquals(winterOffsetDstMillis, offsets[1]); 518 519 timeZone.getOffset(summerTimeMillis, false /* local */, offsets); 520 assertEquals(summerOffsetRawMillis, offsets[0]); 521 assertEquals(summerOffsetDstMillis, offsets[1]); 522 } 523 524 // icu TimeZoneNames 525 TimeZoneNames timeZoneNames = TimeZoneNames.getInstance(locale); 526 // getDisplayName: date = winterTimeMillis 527 assertEquals(winterTimeName, timeZoneNames.getDisplayName( 528 timeZoneId, TimeZoneNames.NameType.LONG_STANDARD, winterTimeMillis)); 529 assertEquals(summerTimeName, timeZoneNames.getDisplayName( 530 timeZoneId, TimeZoneNames.NameType.LONG_DAYLIGHT, winterTimeMillis)); 531 // getDisplayName: date = summerTimeMillis 532 assertEquals(winterTimeName, timeZoneNames.getDisplayName( 533 timeZoneId, TimeZoneNames.NameType.LONG_STANDARD, summerTimeMillis)); 534 assertEquals(summerTimeName, timeZoneNames.getDisplayName( 535 timeZoneId, TimeZoneNames.NameType.LONG_DAYLIGHT, summerTimeMillis)); 536 } 537 } 538