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