1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html#License
3 /*
4  *******************************************************************************
5  * Copyright (C) 2008-2015, International Business Machines Corporation and    *
6  * others. All Rights Reserved.                                                *
7  *******************************************************************************
8  */
9 package com.ibm.icu.dev.test.localespi;
10 
11 import java.util.Collections;
12 import java.util.HashSet;
13 import java.util.Locale;
14 import java.util.Set;
15 import java.util.TimeZone;
16 
17 import org.junit.Test;
18 import org.junit.runner.RunWith;
19 import org.junit.runners.JUnit4;
20 
21 import com.ibm.icu.dev.test.TestFmwk;
22 import com.ibm.icu.text.TimeZoneNames;
23 import com.ibm.icu.text.TimeZoneNames.NameType;
24 import com.ibm.icu.util.ULocale;
25 
26 @RunWith(JUnit4.class)
27 public class TimeZoneNameTest extends TestFmwk {
28 
29     private static final Set<String> ProblematicZones = new HashSet<String>();
30     static {
31         // Since tzdata2013e, Pacific/Johnston is defined as below:
32         //
33         //     Link Pacific/Honolulu Pacific/Johnston
34         //
35         // JDK TimeZone.getDisplayName no longer passes Pacific/Johnston to a
36         // TimeZoneNameProvider implementation. As of CLDR 25M1, Pacific/Johnston
37         // has a different set of names from Pacific/Honolulu. This test case
38         // expects JRE calls a TimeZoneNameProvider without such normalization
39         // (and I believe it's a JDK bug). For now, we ignore the test failure
40         // caused by Pacific/Johnston with this the JDK problem.
41         ProblematicZones.add("Pacific/Johnston");
42     }
43 
44     @Test
TestTimeZoneNames()45     public void TestTimeZoneNames() {
46         Locale[] locales = Locale.getAvailableLocales();
47         String[] tzids = TimeZone.getAvailableIDs();
48 
49         for (Locale loc : locales) {
50             boolean warningOnly = false;
51             if (TestUtil.isExcluded(loc)) {
52                 warningOnly = true;
53             }
54 
55             for (String tzid : tzids) {
56                 // Java has a problem when a provider does not supply all 4 names
57                 // for a zone. For this reason, ICU TimeZoneName provider does not return
58                 // localized names unless these 4 names are available.
59 
60                 String icuStdLong = getIcuDisplayName(tzid, false, TimeZone.LONG, loc);
61                 String icuDstLong = getIcuDisplayName(tzid, true, TimeZone.LONG, loc);
62                 String icuStdShort = getIcuDisplayName(tzid, false, TimeZone.SHORT, loc);
63                 String icuDstShort = getIcuDisplayName(tzid, true, TimeZone.SHORT, loc);
64 
65                 if (icuStdLong != null && icuDstLong != null && icuStdShort != null && icuDstShort != null) {
66                     checkDisplayNamePair(TimeZone.SHORT, tzid, loc, warningOnly || ProblematicZones.contains(tzid));
67                     checkDisplayNamePair(TimeZone.LONG, tzid, loc, warningOnly || ProblematicZones.contains(tzid));
68                 } else {
69                     logln("Localized long standard name is not available for "
70                             + tzid + " in locale " + loc + " in ICU");
71                 }
72             }
73         }
74     }
75 
checkDisplayNamePair(int style, String tzid, Locale loc, boolean warnOnly)76     private void checkDisplayNamePair(int style, String tzid, Locale loc, boolean warnOnly) {
77         /* Note: There are two problems here.
78          *
79          * It looks Java 6 requires a TimeZoneNameProvider to return both standard name and daylight name
80          * for a zone.  If the provider implementation only returns either of them, Java 6 also ignore
81          * the other.  In ICU, there are zones which do not have daylight names, especially zones which
82          * do not use daylight time.  This test case does not check a standard name if its daylight name
83          * is not available because of the Java 6 implementation problem.
84          *
85          * Another problem is that ICU always use a standard name for a zone which does not use daylight
86          * saving time even daylight name is requested.
87          */
88 
89         String icuStdName = getIcuDisplayName(tzid, false, style, loc);
90         String icuDstName = getIcuDisplayName(tzid, true, style, loc);
91         if (icuStdName != null && icuDstName != null && !icuStdName.equals(icuDstName)) {
92             checkDisplayName(false, style, tzid, loc, icuStdName, warnOnly);
93             checkDisplayName(true, style, tzid, loc, icuDstName, warnOnly);
94         }
95     }
96 
getIcuDisplayName(String tzid, boolean daylight, int style, Locale loc)97     private String getIcuDisplayName(String tzid, boolean daylight, int style, Locale loc) {
98         String icuName = null;
99         boolean[] isSystemID = new boolean[1];
100         String canonicalID = com.ibm.icu.util.TimeZone.getCanonicalID(tzid, isSystemID);
101         if (isSystemID[0]) {
102             long date = System.currentTimeMillis();
103             TimeZoneNames tznames = TimeZoneNames.getInstance(ULocale.forLocale(loc));
104             switch (style) {
105             case TimeZone.LONG:
106                 icuName = daylight ?
107                         tznames.getDisplayName(canonicalID, NameType.LONG_DAYLIGHT, date) :
108                         tznames.getDisplayName(canonicalID, NameType.LONG_STANDARD, date);
109                 break;
110             case TimeZone.SHORT:
111                 icuName = daylight ?
112                         tznames.getDisplayName(canonicalID, NameType.SHORT_DAYLIGHT, date) :
113                         tznames.getDisplayName(canonicalID, NameType.SHORT_STANDARD, date);
114                 break;
115             }
116         }
117         return icuName;
118     }
119 
checkDisplayName(boolean daylight, int style, String tzid, Locale loc, String icuname, boolean warnOnly)120     private void checkDisplayName(boolean daylight, int style,  String tzid, Locale loc, String icuname, boolean warnOnly) {
121         String styleStr = (style == TimeZone.SHORT) ? "SHORT" : "LONG";
122         TimeZone tz = TimeZone.getTimeZone(tzid);
123         String name = tz.getDisplayName(daylight, style, loc);
124 
125         if (TestUtil.isICUExtendedLocale(loc)) {
126             // The name should be taken from ICU
127             if (!name.equals(icuname)) {
128                 if (warnOnly) {
129                     logln("WARNING: TimeZone name by ICU is " + icuname + ", but got " + name
130                             + " for time zone " + tz.getID() + " in locale " + loc
131                             + " (daylight=" + daylight + ", style=" + styleStr + ")");
132 
133                 } else {
134                     errln("FAIL: TimeZone name by ICU is " + icuname + ", but got " + name
135                             + " for time zone " + tz.getID() + " in locale " + loc
136                             + " (daylight=" + daylight + ", style=" + styleStr + ")");
137                 }
138             }
139         } else {
140             if (!name.equals(icuname)) {
141                 logln("INFO: TimeZone name by ICU is " + icuname + ", but got " + name
142                         + " for time zone " + tz.getID() + " in locale " + loc
143                         + " (daylight=" + daylight + ", style=" + styleStr + ")");
144             }
145             // Try explicit ICU locale (xx_yy_ICU)
146             Locale icuLoc = TestUtil.toICUExtendedLocale(loc);
147             name = tz.getDisplayName(daylight, style, icuLoc);
148             if (!name.equals(icuname)) {
149                 if (warnOnly) {
150                     logln("WARNING: TimeZone name by ICU is " + icuname + ", but got " + name
151                             + " for time zone " + tz.getID() + " in locale " + icuLoc
152                             + " (daylight=" + daylight + ", style=" + styleStr + ")");
153                 } else {
154                     errln("FAIL: TimeZone name by ICU is " + icuname + ", but got " + name
155                             + " for time zone " + tz.getID() + " in locale " + icuLoc
156                             + " (daylight=" + daylight + ", style=" + styleStr + ")");
157                 }
158             }
159         }
160     }
161 
162     @Test
testGetInstance_Locale()163     public void testGetInstance_Locale() {
164         TimeZoneNames uLocaleInstance = TimeZoneNames.getInstance(ULocale.CANADA);
165         TimeZoneNames localeInstance = TimeZoneNames.getInstance(Locale.CANADA);
166 
167         Set<String> uLocaleAvailableIds = uLocaleInstance.getAvailableMetaZoneIDs();
168         Set<String> localeAvailableIds = localeInstance.getAvailableMetaZoneIDs();
169         assertEquals("Available ids", uLocaleAvailableIds, localeAvailableIds);
170 
171         for (String availableId : uLocaleAvailableIds) {
172             long date = 1458385200000L;
173             TimeZoneNames.NameType nameType = TimeZoneNames.NameType.SHORT_GENERIC;
174             String uLocaleName = uLocaleInstance.getDisplayName(availableId, nameType, date);
175             String localeName = localeInstance.getDisplayName(availableId, nameType, date);
176             assertEquals("Id: " + availableId, uLocaleName, localeName);
177         }
178     }
179 
180     @Test
testGetAvailableMetaZoneIDs()181     public void testGetAvailableMetaZoneIDs() {
182         TimeZoneNames japaneseNames = TimeZoneNames.getInstance(ULocale.JAPANESE);
183         Set<String> allJapan = japaneseNames.getAvailableMetaZoneIDs();
184 
185         TimeZoneNames tzdbNames = TimeZoneNames.getTZDBInstance(ULocale.CHINESE);
186         Set<String> tzdbAll = tzdbNames.getAvailableMetaZoneIDs();
187 
188         // The data is the same in the current implementation.
189         assertEquals("MetaZone IDs different between locales", allJapan, tzdbAll);
190 
191         // Make sure that there is something.
192         assertTrue("count of zone ids is less than 100", allJapan.size() >= 180);
193     }
194 
195     @Test
testGetAvailableMetaZoneIDs_String()196     public void testGetAvailableMetaZoneIDs_String() {
197         TimeZoneNames japaneseNames = TimeZoneNames.getInstance(ULocale.JAPANESE);
198         assertEquals("Timezone name mismatch", Collections.singleton("America_Pacific"),
199                 japaneseNames.getAvailableMetaZoneIDs("America/Los_Angeles"));
200 
201         TimeZoneNames tzdbNames = TimeZoneNames.getTZDBInstance(ULocale.CHINESE);
202         assertEquals("Timezone name mismatch", Collections.singleton("Taipei"),
203                 tzdbNames.getAvailableMetaZoneIDs("Asia/Taipei"));
204     }
205 
206     @Test
testGetMetaZoneDisplayName()207     public void testGetMetaZoneDisplayName() {
208         TimeZoneNames usNames = TimeZoneNames.getInstance(ULocale.US);
209 
210         String europeanCentralName = usNames.getMetaZoneDisplayName("Europe_Central",
211                 TimeZoneNames.NameType.LONG_STANDARD);
212         assertEquals("Timezone name mismatch", "Central European Standard Time",
213                 europeanCentralName);
214 
215         TimeZoneNames tzdbNames = TimeZoneNames.getTZDBInstance(ULocale.CHINESE);
216         String americaPacificName = tzdbNames.getMetaZoneDisplayName("America_Pacific",
217                 TimeZoneNames.NameType.SHORT_DAYLIGHT);
218         assertEquals("Timezone name mismatch", "PDT", americaPacificName);
219     }
220 
221     @Test
testGetMetaZoneID()222     public void testGetMetaZoneID() {
223         TimeZoneNames usNames = TimeZoneNames.getInstance(ULocale.US);
224 
225         String europeanCentralName = usNames.getMetaZoneID("Europe/Paris", 0);
226         assertEquals("Timezone name mismatch", "Europe_Central", europeanCentralName);
227 
228         TimeZoneNames tzdbNames = TimeZoneNames.getTZDBInstance(ULocale.KOREAN);
229         String seoulName = tzdbNames.getMetaZoneID("Asia/Seoul", 0);
230         assertEquals("Timezone name mismatch", "Korea", seoulName);
231 
232         // Now try Jan 1st 1945 GMT
233         seoulName = tzdbNames.getMetaZoneID("Asia/Seoul", -786240000000L);
234         assertNull("Timezone name mismatch", seoulName);
235     }
236 
237     @Test
testGetTimeZoneDisplayName()238     public void testGetTimeZoneDisplayName() {
239         TimeZoneNames frenchNames = TimeZoneNames.getInstance(ULocale.FRENCH);
240         String dublinName = frenchNames.getTimeZoneDisplayName("Europe/Dublin",
241                 TimeZoneNames.NameType.LONG_DAYLIGHT);
242         assertEquals("Timezone name mismatch", "heure d’été irlandaise", dublinName);
243 
244         String dublinLocation = frenchNames.getTimeZoneDisplayName("Europe/Dublin",
245                 TimeZoneNames.NameType.EXEMPLAR_LOCATION);
246         assertEquals("Timezone name mismatch", "Dublin", dublinLocation);
247 
248         // All the names returned by this are null.
249         TimeZoneNames tzdbNames = TimeZoneNames.getTZDBInstance(ULocale.KOREAN);
250         for (String tzId : TimeZone.getAvailableIDs()) {
251             for (TimeZoneNames.NameType nameType : TimeZoneNames.NameType.values()) {
252                 String name = tzdbNames.getTimeZoneDisplayName(tzId, nameType);
253                 assertNull("TZ:" + tzId + ", NameType: " + nameType + ", value: " + name, name);
254             }
255         }
256     }
257 }
258