• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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