• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GENERATED SOURCE. DO NOT MODIFY. */
2 // © 2016 and later: Unicode, Inc. and others.
3 // License & terms of use: http://www.unicode.org/copyright.html#License
4 /*
5  ********************************************************************************
6  * Copyright (C) 2007-2016, Google, International Business Machines Corporation
7  * and others. All Rights Reserved.
8  ********************************************************************************
9  */
10 
11 package ohos.global.icu.dev.test.format;
12 
13 import java.text.FieldPosition;
14 import java.text.ParseException;
15 import java.text.ParsePosition;
16 import java.util.ArrayList;
17 import java.util.Arrays;
18 import java.util.Collections;
19 import java.util.Date;
20 import java.util.EnumSet;
21 import java.util.List;
22 import java.util.Locale;
23 import java.util.Random;
24 import java.util.Set;
25 import java.util.TreeSet;
26 import java.util.concurrent.atomic.AtomicInteger;
27 import java.util.regex.Pattern;
28 
29 import org.junit.Test;
30 import org.junit.runner.RunWith;
31 import org.junit.runners.JUnit4;
32 
33 import ohos.global.icu.dev.test.TestFmwk;
34 import ohos.global.icu.impl.TZDBTimeZoneNames;
35 import ohos.global.icu.impl.ZoneMeta;
36 import ohos.global.icu.lang.UCharacter;
37 import ohos.global.icu.text.DateFormat;
38 import ohos.global.icu.text.SimpleDateFormat;
39 import ohos.global.icu.text.TimeZoneFormat;
40 import ohos.global.icu.text.TimeZoneFormat.GMTOffsetPatternType;
41 import ohos.global.icu.text.TimeZoneFormat.ParseOption;
42 import ohos.global.icu.text.TimeZoneFormat.Style;
43 import ohos.global.icu.text.TimeZoneFormat.TimeType;
44 import ohos.global.icu.text.TimeZoneNames;
45 import ohos.global.icu.text.TimeZoneNames.Factory;
46 import ohos.global.icu.text.TimeZoneNames.NameType;
47 import ohos.global.icu.util.BasicTimeZone;
48 import ohos.global.icu.util.Calendar;
49 import ohos.global.icu.util.Output;
50 import ohos.global.icu.util.SimpleTimeZone;
51 import ohos.global.icu.util.TimeZone;
52 import ohos.global.icu.util.TimeZone.SystemTimeZoneType;
53 import ohos.global.icu.util.TimeZoneTransition;
54 import ohos.global.icu.util.ULocale;
55 
56 
57 
58 @RunWith(JUnit4.class)
59 public class TimeZoneFormatTest extends TestFmwk {
60 
61     private static boolean JDKTZ = (TimeZone.getDefaultTimeZoneType() == TimeZone.TIMEZONE_JDK);
62     private static final Pattern EXCL_TZ_PATTERN = Pattern.compile(".*/Riyadh8[7-9]");
63 
64     private static final String[] PATTERNS = {
65         "z",
66         "zzzz",
67         "Z",        // equivalent to "xxxx"
68         "ZZZZ",     // equivalent to "OOOO"
69         "v",
70         "vvvv",
71         "O",
72         "OOOO",
73         "X",
74         "XX",
75         "XXX",
76         "XXXX",
77         "XXXXX",
78         "x",
79         "xx",
80         "xxx",
81         "xxxx",
82         "xxxxx",
83         "V",
84         "VV",
85         "VVV",
86         "VVVV"
87     };
88     boolean REALLY_VERBOSE_LOG = false;
89 
90     /*
91      * Test case for checking if a TimeZone is properly set in the result calendar
92      * and if the result TimeZone has the expected behavior.
93      */
94     @Test
TestTimeZoneRoundTrip()95     public void TestTimeZoneRoundTrip() {
96         boolean TEST_ALL = getBooleanProperty("TimeZoneRoundTripAll", false);
97 
98         TimeZone unknownZone = new SimpleTimeZone(-31415, "Etc/Unknown");
99         int badDstOffset = -1234;
100         int badZoneOffset = -2345;
101 
102         int[][] testDateData = {
103             {2007, 1, 15},
104             {2007, 6, 15},
105             {1990, 1, 15},
106             {1990, 6, 15},
107             {1960, 1, 15},
108             {1960, 6, 15},
109         };
110 
111         Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
112         cal.clear();
113 
114         // Set up rule equivalency test range
115         long low, high;
116         cal.set(1900, 0, 1);
117         low = cal.getTimeInMillis();
118         cal.set(2040, 0, 1);
119         high = cal.getTimeInMillis();
120 
121         // Set up test dates
122         Date[] DATES = new Date[testDateData.length];
123         cal.clear();
124         for (int i = 0; i < DATES.length; i++) {
125             cal.set(testDateData[i][0], testDateData[i][1], testDateData[i][2]);
126             DATES[i] = cal.getTime();
127         }
128 
129         // Set up test locales
130         ULocale[] LOCALES = null;
131         if (TEST_ALL || TestFmwk.getExhaustiveness() > 5) {
132             LOCALES = ULocale.getAvailableLocales();
133         } else {
134             LOCALES = new ULocale[] {new ULocale("en"), new ULocale("en_CA"), new ULocale("fr"),
135                     new ULocale("zh_Hant"), new ULocale("fa"), new ULocale("ccp")};
136         }
137 
138         String[] tzids;
139         if (JDKTZ) {
140             tzids = java.util.TimeZone.getAvailableIDs();
141         } else {
142             tzids = TimeZone.getAvailableIDs();
143         }
144         int[] inOffsets = new int[2];
145         int[] outOffsets = new int[2];
146 
147         // Run the roundtrip test
148         for (int locidx = 0; locidx < LOCALES.length; locidx++) {
149             logln("Locale: " + LOCALES[locidx].toString());
150 
151             String localGMTString = TimeZoneFormat.getInstance(LOCALES[locidx]).formatOffsetLocalizedGMT(0);
152 
153             for (int patidx = 0; patidx < PATTERNS.length; patidx++) {
154                 logln("    pattern: " + PATTERNS[patidx]);
155                 SimpleDateFormat sdf = new SimpleDateFormat(PATTERNS[patidx], LOCALES[locidx]);
156 
157                 for (int tzidx = 0; tzidx < tzids.length; tzidx++) {
158                     if (EXCL_TZ_PATTERN.matcher(tzids[tzidx]).matches()) {
159                         continue;
160                     }
161                     TimeZone tz = TimeZone.getTimeZone(tzids[tzidx]);
162 
163                     for (int datidx = 0; datidx < DATES.length; datidx++) {
164                         // Format
165                         sdf.setTimeZone(tz);
166                         String tzstr = sdf.format(DATES[datidx]);
167 
168                         // Before parse, set unknown zone to SimpleDateFormat instance
169                         // just for making sure that it does not depends on the time zone
170                         // originally set.
171                         sdf.setTimeZone(unknownZone);
172 
173                         // Parse
174                         ParsePosition pos = new ParsePosition(0);
175                         Calendar outcal = Calendar.getInstance(unknownZone);
176                         outcal.set(Calendar.DST_OFFSET, badDstOffset);
177                         outcal.set(Calendar.ZONE_OFFSET, badZoneOffset);
178 
179                         sdf.parse(tzstr, outcal, pos);
180 
181                         // Check the result
182                         TimeZone outtz = outcal.getTimeZone();
183 
184                         tz.getOffset(DATES[datidx].getTime(), false, inOffsets);
185                         outtz.getOffset(DATES[datidx].getTime(), false, outOffsets);
186 
187                         if (PATTERNS[patidx].equals("V")) {
188                             // Short zone ID - should support roundtrip for canonical CLDR IDs
189                             String canonicalID = TimeZone.getCanonicalID(tzids[tzidx]);
190                             if (!outtz.getID().equals(canonicalID)) {
191                                 if (outtz.getID().equals("Etc/Unknown")) {
192                                     // Note that some zones like Asia/Riyadh87 does not have
193                                     // short zone ID and "unk" is used as the fallback
194                                     if (REALLY_VERBOSE_LOG) {
195                                         logln("Canonical round trip failed (probably as expected); tz=" + tzids[tzidx]
196                                             + ", locale=" + LOCALES[locidx] + ", pattern=" + PATTERNS[patidx]
197                                             + ", time=" + DATES[datidx].getTime() + ", str=" + tzstr
198                                             + ", outtz=" + outtz.getID());
199                                     }
200                                 } else {
201                                     errln("Canonical round trip failed; tz=" + tzids[tzidx]
202                                         + ", locale=" + LOCALES[locidx] + ", pattern=" + PATTERNS[patidx]
203                                         + ", time=" + DATES[datidx].getTime() + ", str=" + tzstr
204                                         + ", outtz=" + outtz.getID());
205                                 }
206                             }
207                         } else if (PATTERNS[patidx].equals("VV")) {
208                             // Zone ID - full roundtrip support
209                             if (!outtz.getID().equals(tzids[tzidx])) {
210                                 errln("Zone ID round trip failed; tz=" + tzids[tzidx]
211                                         + ", locale=" + LOCALES[locidx] + ", pattern=" + PATTERNS[patidx]
212                                         + ", time=" + DATES[datidx].getTime() + ", str=" + tzstr
213                                         + ", outtz=" + outtz.getID());
214                             }
215                         } else if (PATTERNS[patidx].equals("VVV") || PATTERNS[patidx].equals("VVVV")) {
216                             // Location: time zone rule must be preserved except
217                             // zones not actually associated with a specific location.
218                             String canonicalID = TimeZone.getCanonicalID(tzids[tzidx]);
219                             if (canonicalID != null && !outtz.getID().equals(canonicalID)) {
220                                 // Canonical ID did not match - check the rules
221                                 boolean bFailure = false;
222                                 if ((tz instanceof BasicTimeZone) && (outtz instanceof BasicTimeZone)) {
223                                     boolean hasNoLocation = TimeZone.getRegion(tzids[tzidx]).equals("001");
224                                     bFailure = !hasNoLocation
225                                                 && !((BasicTimeZone)outtz).hasEquivalentTransitions(tz, low, high);
226                                 }
227                                 if (bFailure) {
228                                     errln("Canonical round trip failed; tz=" + tzids[tzidx]
229                                             + ", locale=" + LOCALES[locidx] + ", pattern=" + PATTERNS[patidx]
230                                             + ", time=" + DATES[datidx].getTime() + ", str=" + tzstr
231                                             + ", outtz=" + outtz.getID());
232                                 } else if (REALLY_VERBOSE_LOG) {
233                                     logln("Canonical round trip failed (as expected); tz=" + tzids[tzidx]
234                                             + ", locale=" + LOCALES[locidx] + ", pattern=" + PATTERNS[patidx]
235                                             + ", time=" + DATES[datidx].getTime() + ", str=" + tzstr
236                                             + ", outtz=" + outtz.getID());
237                                 }
238                             }
239                         } else {
240                             boolean isOffsetFormat = (PATTERNS[patidx].charAt(0) == 'Z'
241                                     || PATTERNS[patidx].charAt(0) == 'O'
242                                     || PATTERNS[patidx].charAt(0) == 'X'
243                                     || PATTERNS[patidx].charAt(0) == 'x');
244                             boolean minutesOffset = false;
245                             if (PATTERNS[patidx].charAt(0) == 'X' || PATTERNS[patidx].charAt(0) == 'x') {
246                                 minutesOffset = PATTERNS[patidx].length() <= 3;
247                             }
248 
249                             if (!isOffsetFormat) {
250                                 // Check if localized GMT format is used as a fallback of name styles
251                                 int numDigits = 0;
252                                 int idx = 0;
253                                 while (idx < tzstr.length()) {
254                                     int cp = tzstr.codePointAt(idx);
255                                     if (UCharacter.isDigit(cp)) {
256                                         numDigits++;
257                                     }
258                                     idx += UCharacter.charCount(cp);
259                                 }
260                                 isOffsetFormat = (numDigits > 0);
261                             }
262 
263                             if (isOffsetFormat || tzstr.equals(localGMTString)) {
264                                 // Localized GMT or ISO: total offset (raw + dst) must be preserved.
265                                 int inOffset = inOffsets[0] + inOffsets[1];
266                                 int outOffset = outOffsets[0] + outOffsets[1];
267                                 int diff = outOffset - inOffset;
268                                 if (minutesOffset) {
269                                     diff = (diff / 60000) * 60000;
270                                 }
271                                 if (diff != 0) {
272                                     errln("Offset round trip failed; tz=" + tzids[tzidx]
273                                         + ", locale=" + LOCALES[locidx] + ", pattern=" + PATTERNS[patidx]
274                                         + ", time=" + DATES[datidx].getTime() + ", str=" + tzstr
275                                         + ", inOffset=" + inOffset + ", outOffset=" + outOffset);
276                                 }
277                             } else {
278                                 // Specific or generic: raw offset must be preserved.
279                                 if (inOffsets[0] != outOffsets[0]) {
280                                     if (JDKTZ && tzids[tzidx].startsWith("SystemV/")) {
281                                         // JDK uses rule SystemV for these zones while
282                                         // ICU handles these zones as aliases of existing time zones
283                                         if (REALLY_VERBOSE_LOG) {
284                                             logln("Raw offset round trip failed; tz=" + tzids[tzidx]
285                                                 + ", locale=" + LOCALES[locidx] + ", pattern=" + PATTERNS[patidx]
286                                                 + ", time=" + DATES[datidx].getTime() + ", str=" + tzstr
287                                                 + ", inRawOffset=" + inOffsets[0] + ", outRawOffset=" + outOffsets[0]);
288                                         }
289 
290                                     } else {
291                                         errln("Raw offset round trip failed; tz=" + tzids[tzidx]
292                                             + ", locale=" + LOCALES[locidx] + ", pattern=" + PATTERNS[patidx]
293                                             + ", time=" + DATES[datidx].getTime() + ", str=" + tzstr
294                                             + ", inRawOffset=" + inOffsets[0] + ", outRawOffset=" + outOffsets[0]);
295                                     }
296                                 }
297                             }
298                         }
299                     }
300                 }
301             }
302         }
303 
304     }
305 
306     /*
307      * Test case of round trip time and text.  This test case detects every canonical TimeZone's
308      * rule transition since 1900 until 2020, then check if time around each transition can
309      * round trip as expected.
310      */
311     @Test
TestTimeRoundTrip()312     public void TestTimeRoundTrip() {
313 
314         boolean TEST_ALL = getBooleanProperty("TimeZoneRoundTripAll", false);
315 
316         int startYear, endYear;
317 
318         if (TEST_ALL || TestFmwk.getExhaustiveness() > 5) {
319             startYear = 1900;
320         } else {
321             startYear = 1990;
322         }
323 
324         Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
325         endYear = cal.get(Calendar.YEAR) + 3;
326 
327         cal.set(startYear, Calendar.JANUARY, 1);
328         final long START_TIME = cal.getTimeInMillis();
329 
330         cal.set(endYear, Calendar.JANUARY, 1);
331         final long END_TIME = cal.getTimeInMillis();
332 
333         // These patterns are ambiguous at DST->STD local time overlap
334         List<String> AMBIGUOUS_DST_DECESSION = Arrays.asList("v", "vvvv", "V", "VV", "VVV", "VVVV");
335 
336         // These patterns are ambiguous at STD->STD/DST->DST local time overlap
337         List<String> AMBIGUOUS_NEGATIVE_SHIFT = Arrays.asList("z", "zzzz", "v", "vvvv", "V", "VV", "VVV", "VVVV");
338 
339         // These patterns only support integer minutes offset
340         List<String> MINUTES_OFFSET = Arrays.asList("X", "XX", "XXX", "x", "xx", "xxx");
341 
342         // Regex pattern used for filtering zone IDs without exemplar location
343         final Pattern LOC_EXCLUSION_PATTERN = Pattern.compile("Etc/.*|SystemV/.*|.*/Riyadh8[7-9]");
344 
345         final String BASEPATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS";
346 
347         ULocale[] LOCALES = null;
348 
349         // timer for performance analysis
350         long[] times = new long[PATTERNS.length];
351         long timer;
352 
353         if (TEST_ALL) {
354             // It may take about an hour for testing all locales
355             LOCALES = ULocale.getAvailableLocales();
356         } else if (TestFmwk.getExhaustiveness() > 5) {
357             LOCALES = new ULocale[] {
358                 new ULocale("ar_EG"), new ULocale("bg_BG"), new ULocale("ca_ES"), new ULocale("da_DK"), new ULocale("de"),
359                 new ULocale("de_DE"), new ULocale("el_GR"), new ULocale("en"), new ULocale("en_AU"), new ULocale("en_CA"),
360                 new ULocale("en_US"), new ULocale("es"), new ULocale("es_ES"), new ULocale("es_MX"), new ULocale("fi_FI"),
361                 new ULocale("fr"), new ULocale("fr_CA"), new ULocale("fr_FR"), new ULocale("he_IL"), new ULocale("hu_HU"),
362                 new ULocale("it"), new ULocale("it_IT"), new ULocale("ja"), new ULocale("ja_JP"), new ULocale("ko"),
363                 new ULocale("ko_KR"), new ULocale("nb_NO"), new ULocale("nl_NL"), new ULocale("nn_NO"), new ULocale("pl_PL"),
364                 new ULocale("pt"), new ULocale("pt_BR"), new ULocale("pt_PT"), new ULocale("ru_RU"), new ULocale("sv_SE"),
365                 new ULocale("th_TH"), new ULocale("tr_TR"), new ULocale("zh"), new ULocale("zh_Hans"), new ULocale("zh_Hans_CN"),
366                 new ULocale("zh_Hant"), new ULocale("zh_Hant_HK"), new ULocale("zh_Hant_TW"), new ULocale("ccp"), new ULocale("fa")
367             };
368         } else {
369             LOCALES = new ULocale[] {
370                 new ULocale("en"),
371             };
372         }
373 
374         SimpleDateFormat sdfGMT = new SimpleDateFormat(BASEPATTERN);
375         sdfGMT.setTimeZone(TimeZone.getTimeZone("Etc/GMT"));
376 
377         long testCounts = 0;
378         long[] testTimes = new long[4];
379         boolean[] expectedRoundTrip = new boolean[4];
380         int testLen = 0;
381         for (int locidx = 0; locidx < LOCALES.length; locidx++) {
382             logln("Locale: " + LOCALES[locidx].toString());
383             for (int patidx = 0; patidx < PATTERNS.length; patidx++) {
384                 logln("    pattern: " + PATTERNS[patidx]);
385                 String pattern = BASEPATTERN + " " + PATTERNS[patidx];
386                 SimpleDateFormat sdf = new SimpleDateFormat(pattern, LOCALES[locidx]);
387                 boolean minutesOffset = MINUTES_OFFSET.contains(PATTERNS[patidx]);
388 
389                 Set<String> ids = null;
390                 if (JDKTZ) {
391                     ids = new TreeSet<String>();
392                     String[] jdkIDs = java.util.TimeZone.getAvailableIDs();
393                     for (String jdkID : jdkIDs) {
394                         if (EXCL_TZ_PATTERN.matcher(jdkID).matches()) {
395                             continue;
396                         }
397                         String tmpID = TimeZone.getCanonicalID(jdkID);
398                         if (tmpID != null) {
399                             ids.add(tmpID);
400                         }
401                     }
402                 } else {
403                     ids = TimeZone.getAvailableIDs(SystemTimeZoneType.CANONICAL, null, null);
404                 }
405 
406                 for (String id : ids) {
407                     if (PATTERNS[patidx].equals("V")) {
408                         // Some zones do not have short ID assigned, such as Asia/Riyadh87.
409                         // The time roundtrip will fail for such zones with pattern "V" (short zone ID).
410                         // This is expected behavior.
411                         String shortZoneID = ZoneMeta.getShortID(id);
412                         if (shortZoneID == null) {
413                             continue;
414                         }
415                     } else if (PATTERNS[patidx].equals("VVV")) {
416                         // Some zones are not associated with any region, such as Etc/GMT+8.
417                         // The time roundtrip will fail for such zones with pattern "VVV" (exemplar location).
418                         // This is expected behavior.
419                         if (id.indexOf('/') < 0 || LOC_EXCLUSION_PATTERN.matcher(id).matches()) {
420                             continue;
421                         }
422                     }
423 
424                     if ((id.equals("Pacific/Apia") || id.equals("Pacific/Midway") || id.equals("Pacific/Pago_Pago"))
425                             && PATTERNS[patidx].equals("vvvv")
426                             && logKnownIssue("11052", "Ambiguous zone name - Samoa Time")) {
427                         continue;
428                     }
429 
430                     BasicTimeZone btz = (BasicTimeZone)TimeZone.getTimeZone(id, TimeZone.TIMEZONE_ICU);
431                     TimeZone tz = TimeZone.getTimeZone(id);
432                     sdf.setTimeZone(tz);
433 
434                     long t = START_TIME;
435                     TimeZoneTransition tzt = null;
436                     boolean middle = true;
437                     boolean last = false;
438                     while (t < END_TIME) {
439                         if (tzt == null) {
440                             testTimes[0] = t;
441                             expectedRoundTrip[0] = true;
442                             testLen = 1;
443                         } else {
444                             int fromOffset = tzt.getFrom().getRawOffset() + tzt.getFrom().getDSTSavings();
445                             int toOffset = tzt.getTo().getRawOffset() + tzt.getTo().getDSTSavings();
446                             int delta = toOffset - fromOffset;
447                             if (delta < 0) {
448                                 boolean isDstDecession = tzt.getFrom().getDSTSavings() > 0 && tzt.getTo().getDSTSavings() == 0;
449                                 testTimes[0] = t + delta - 1;
450                                 expectedRoundTrip[0] = true;
451                                 testTimes[1] = t + delta;
452                                 expectedRoundTrip[1] = isDstDecession ?
453                                         !AMBIGUOUS_DST_DECESSION.contains(PATTERNS[patidx]) :
454                                         !AMBIGUOUS_NEGATIVE_SHIFT.contains(PATTERNS[patidx]);
455                                 testTimes[2] = t - 1;
456                                 expectedRoundTrip[2] = isDstDecession ?
457                                         !AMBIGUOUS_DST_DECESSION.contains(PATTERNS[patidx]) :
458                                         !AMBIGUOUS_NEGATIVE_SHIFT.contains(PATTERNS[patidx]);
459                                 testTimes[3] = t;
460                                 expectedRoundTrip[3] = true;
461                                 testLen = 4;
462                             } else {
463                                 testTimes[0] = t - 1;
464                                 expectedRoundTrip[0] = true;
465                                 testTimes[1] = t;
466                                 expectedRoundTrip[1] = true;
467                                 testLen = 2;
468                             }
469                         }
470                         for (int testidx = 0; testidx < testLen; testidx++) {
471                             testCounts++;
472                             timer = System.currentTimeMillis();
473                             String text = sdf.format(new Date(testTimes[testidx]));
474                             try {
475                                 Date parsedDate = sdf.parse(text);
476                                 long restime = parsedDate.getTime();
477                                 long timeDiff = restime - testTimes[testidx];
478                                 boolean bTimeMatch = minutesOffset ?
479                                         (timeDiff/60000)*60000 == 0 : timeDiff == 0;
480                                 if (!bTimeMatch) {
481                                     StringBuffer msg = new StringBuffer();
482                                     msg.append("Time round trip failed for ")
483                                         .append("tzid=").append(id)
484                                         .append(", locale=").append(LOCALES[locidx])
485                                         .append(", pattern=").append(PATTERNS[patidx])
486                                         .append(", text=").append(text)
487                                         .append(", gmt=").append(sdfGMT.format(new Date(testTimes[testidx])))
488                                         .append(", time=").append(testTimes[testidx])
489                                         .append(", restime=").append(restime)
490                                         .append(", diff=").append(timeDiff);
491                                     if (expectedRoundTrip[testidx]
492                                             && !isSpecialTimeRoundTripCase(LOCALES[locidx], id, PATTERNS[patidx], testTimes[testidx])) {
493                                         errln("FAIL: " + msg.toString());
494                                     } else if (REALLY_VERBOSE_LOG) {
495                                         logln(msg.toString());
496                                     }
497                                 }
498                             } catch (ParseException pe) {
499                                 errln("FAIL: " + pe.getMessage() + " tzid=" + id + ", locale=" + LOCALES[locidx] +
500                                         ", pattern=" + PATTERNS[patidx] + ", text=" + text);
501                             }
502                             times[patidx] += System.currentTimeMillis() - timer;
503                         }
504 
505                         if (last) {
506                             break;
507                         }
508 
509                         tzt = btz.getNextTransition(t, false);
510                         if (tzt == null) {
511                             last = true;
512                             t = END_TIME - 1;
513                         } else if (middle) {
514                             // Test the date in the middle of two transitions.
515                             t += (tzt.getTime() - t)/2;
516                             middle = false;
517                             tzt = null;
518                         } else {
519                             t = tzt.getTime();
520                         }
521                     }
522                 }
523             }
524         }
525 
526         long total = 0;
527         logln("### Elapsed time by patterns ###");
528         for (int i = 0; i < PATTERNS.length; i++) {
529             logln(times[i] + "ms (" + PATTERNS[i] + ")");
530             total += times[i];
531         }
532         logln("Total: " + total + "ms");
533         logln("Iteration: " + testCounts);
534     }
535 
536     // Special exclusions in TestTimeZoneRoundTrip.
537     // These special cases do not round trip time as designed.
isSpecialTimeRoundTripCase(ULocale loc, String id, String pattern, long time)538     private boolean isSpecialTimeRoundTripCase(ULocale loc, String id, String pattern, long time) {
539         final Object[][] EXCLUSIONS = {
540             {null, "Asia/Chita", "zzzz", Long.valueOf(1414252800000L)},
541             {null, "Asia/Chita", "vvvv", Long.valueOf(1414252800000L)},
542             {null, "Asia/Srednekolymsk", "zzzz", Long.valueOf(1414241999999L)},
543             {null, "Asia/Srednekolymsk", "vvvv", Long.valueOf(1414241999999L)},
544         };
545         boolean isExcluded = false;
546         for (Object[] excl : EXCLUSIONS) {
547             if (excl[0] == null || loc.equals(excl[0])) {
548                 if (id.equals(excl[1])) {
549                     if (excl[2] == null || pattern.equals(excl[2])) {
550                         if (excl[3] == null || ((Long)excl[3]).compareTo(time) == 0) {
551                             isExcluded = true;
552                             break;
553                         }
554                     }
555                 }
556             }
557         }
558         return isExcluded;
559     }
560 
561     @Test
TestParse()562     public void TestParse() {
563         final Object[][] DATA = {
564         //   text                   inpos       locale      style
565         //      parseOptions            expected            outpos      time type
566             {"Z",                   0,          "en_US",    Style.ISO_EXTENDED_FULL,
567                 null,                   "Etc/GMT",          1,          TimeType.UNKNOWN},
568 
569             {"Z",                   0,          "en_US",    Style.SPECIFIC_LONG,
570                 null,                   "Etc/GMT",          1,          TimeType.UNKNOWN},
571 
572             {"Zambia time",         0,          "en_US",    Style.ISO_EXTENDED_FULL,
573                 EnumSet.of(ParseOption.ALL_STYLES), "Etc/GMT",  1,      TimeType.UNKNOWN},
574 
575             {"Zambia time",         0,          "en_US",    Style.GENERIC_LOCATION,
576                 null,                   "Africa/Lusaka",    11,         TimeType.UNKNOWN},
577 
578             {"Zambia time",         0,          "en_US",    Style.ISO_BASIC_LOCAL_FULL,
579                 EnumSet.of(ParseOption.ALL_STYLES), "Africa/Lusaka",    11, TimeType.UNKNOWN},
580 
581             {"+00:00",              0,          "en_US",    Style.ISO_EXTENDED_FULL,
582                 null,                   "Etc/GMT",          6,          TimeType.UNKNOWN},
583 
584             {"-01:30:45",           0,          "en_US",    Style.ISO_EXTENDED_FULL,
585                 null,                   "GMT-01:30:45",     9,          TimeType.UNKNOWN},
586 
587             {"-7",                  0,          "en_US",    Style.ISO_BASIC_LOCAL_FULL,
588                 null,                   "GMT-07:00",        2,          TimeType.UNKNOWN},
589 
590             {"-2222",               0,          "en_US",    Style.ISO_BASIC_LOCAL_FULL,
591                 null,                   "GMT-22:22",        5,          TimeType.UNKNOWN},
592 
593             {"-3333",               0,          "en_US",    Style.ISO_BASIC_LOCAL_FULL,
594                 null,                   "GMT-03:33",        4,          TimeType.UNKNOWN},
595 
596             {"XXX+01:30YYY",        3,          "en_US",    Style.LOCALIZED_GMT,
597                 null,                   "GMT+01:30",        9,          TimeType.UNKNOWN},
598 
599             {"GMT0",                0,          "en_US",    Style.SPECIFIC_SHORT,
600                 null,                   "Etc/GMT",          3,          TimeType.UNKNOWN},
601 
602             {"EST",                 0,          "en_US",    Style.SPECIFIC_SHORT,
603                 null,                   "America/New_York", 3,          TimeType.STANDARD},
604 
605             {"ESTx",                0,          "en_US",    Style.SPECIFIC_SHORT,
606                 null,                   "America/New_York", 3,          TimeType.STANDARD},
607 
608             {"EDTx",                0,          "en_US",    Style.SPECIFIC_SHORT,
609                 null,                   "America/New_York", 3,          TimeType.DAYLIGHT},
610 
611             {"EST",                 0,          "en_US",    Style.SPECIFIC_LONG,
612                 null,                   null,               0,          TimeType.UNKNOWN},
613 
614             {"EST",                 0,          "en_US",    Style.SPECIFIC_LONG,
615                 EnumSet.of(ParseOption.ALL_STYLES), "America/New_York", 3,  TimeType.STANDARD},
616 
617             {"EST",                 0,          "en_CA",    Style.SPECIFIC_SHORT,
618                 null,                   "America/Toronto",  3,          TimeType.STANDARD},
619 
620             {"CST",                 0,          "en_US",    Style.SPECIFIC_SHORT,
621                 null,                   "America/Chicago",  3,          TimeType.STANDARD},
622 
623             {"CST",                 0,          "en_GB",    Style.SPECIFIC_SHORT,
624                 null,                   null,               0,          TimeType.UNKNOWN},
625 
626             {"CST",                 0,          "en_GB",    Style.SPECIFIC_SHORT,
627                 EnumSet.of(ParseOption.TZ_DATABASE_ABBREVIATIONS),  "America/Chicago",  3,  TimeType.STANDARD},
628 
629             {"--CST--",             2,          "en_GB",    Style.SPECIFIC_SHORT,
630                 EnumSet.of(ParseOption.TZ_DATABASE_ABBREVIATIONS),  "America/Chicago",  5,  TimeType.STANDARD},
631 
632             {"CST",                 0,          "zh_CN",    Style.SPECIFIC_SHORT,
633                 EnumSet.of(ParseOption.TZ_DATABASE_ABBREVIATIONS),  "Asia/Shanghai",    3,  TimeType.STANDARD},
634 
635             {"AEST",                0,          "en_AU",    Style.SPECIFIC_SHORT,
636                 EnumSet.of(ParseOption.TZ_DATABASE_ABBREVIATIONS),  "Australia/Sydney", 4,  TimeType.STANDARD},
637 
638             {"AST",                 0,          "ar_SA",    Style.SPECIFIC_SHORT,
639                 EnumSet.of(ParseOption.TZ_DATABASE_ABBREVIATIONS),  "Asia/Riyadh",      3,  TimeType.STANDARD},
640 
641             {"AQTST",               0,          "en",       Style.SPECIFIC_LONG,
642                 null,                       null,           0,          TimeType.UNKNOWN},
643 
644             {"AQTST",           0,      "en",       Style.SPECIFIC_LONG,
645                 EnumSet.of(ParseOption.ALL_STYLES), null,   0,          TimeType.UNKNOWN},
646 
647             {"AQTST",           0,      "en",       Style.SPECIFIC_LONG,
648                 EnumSet.of(ParseOption.ALL_STYLES, ParseOption.TZ_DATABASE_ABBREVIATIONS),  "Asia/Aqtobe",  5,  TimeType.DAYLIGHT},
649 
650             {"hora de verano británica", 0,     "es",       Style.SPECIFIC_LONG,
651                 null,                   "Europe/London",    24,         TimeType.DAYLIGHT},
652         };
653 
654         for (Object[] test : DATA) {
655             String text = (String)test[0];
656             int inPos = (Integer)test[1];
657             ULocale loc = new ULocale((String)test[2]);
658             Style style = (Style)test[3];
659             EnumSet<ParseOption> options = (EnumSet<ParseOption>)test[4];
660             String expID = (String)test[5];
661             int expPos = (Integer)test[6];
662             TimeType expType = (TimeType)test[7];
663 
664             TimeZoneFormat tzfmt = TimeZoneFormat.getInstance(loc);
665             Output<TimeType> timeType = new Output<TimeType>(TimeType.UNKNOWN);
666             ParsePosition pos = new ParsePosition(inPos);
667             TimeZone tz = tzfmt.parse(style, text, pos, options, timeType);
668 
669             String errMsg = null;
670             if (tz == null) {
671                 if (expID != null) {
672                     errMsg = "Parse failure - expected: " + expID;
673                 }
674             } else if (!tz.getID().equals(expID)) {
675                 errMsg = "Time zone ID: " + tz.getID() + " - expected: " + expID;
676             } else if (pos.getIndex() != expPos) {
677                 errMsg = "Parsed pos: " + pos.getIndex() + " - expected: " + expPos;
678             } else if (timeType.value != expType) {
679                 errMsg = "Time type: " + timeType + " - expected: " + expType;
680             }
681 
682             if (errMsg != null) {
683                 errln("Fail: " + errMsg +
684                         " [text=" + text + ", pos=" + inPos +
685                         ", locale=" + loc + ", style=" + style + "]");
686             }
687         }
688     }
689 
690     // Coverage tests for other versions of the parse() method. All of them end up
691     // calling the full parse() method tested on the TestParse() test.
692     @Test
TestParseCoverage()693     public void TestParseCoverage() {
694         TimeZone expectedTZ = TimeZone.getTimeZone("America/Los_Angeles");
695         TimeZoneFormat fmt = TimeZoneFormat.getInstance(ULocale.ENGLISH);
696 
697         // Test parse(String)
698         try {
699             TimeZone tz1 = fmt.parse("America/Los_Angeles");
700             if (tz1 == null) {
701                 errln("Parse failure using parse(String) - expected: " + expectedTZ.getID());
702             } else if (!expectedTZ.equals(tz1)) {
703                 errln("Parsed TimeZone: '" + tz1.getID()  + "' using parse(String) - expected: "
704                         + expectedTZ.getID());
705             }
706         } catch (ParseException e) {
707             errln("Parse failure using parse(String) - expected: " + expectedTZ.getID()
708                     + " exception: " + e.getMessage());
709         }
710 
711         // Test parse(String, ParsePosition)
712         TimeZone tz2 = fmt.parse("++America/Los_Angeles", new ParsePosition(2));
713         if (tz2 == null) {
714             errln("Parse failure using parse(String, ParsePosition) - expected: "
715                     + expectedTZ.getID());
716         } else if (!expectedTZ.equals(tz2)) {
717             errln("Parsed TimeZone: '" + tz2.getID()  + "' using parse(String, ParsePosition) - expected: "
718                     + expectedTZ.getID());
719         }
720 
721         // Test parseObject(String, ParsePosition)
722         Object tz3 = fmt.parseObject("++America/Los_Angeles", new ParsePosition(2));
723         if (tz3 == null) {
724             errln("Parse failure using parseObject(String, ParsePosition) - expected: "
725                     + expectedTZ.getID());
726         } else if (!expectedTZ.equals(tz3)) {
727             errln("Parsed TimeZone: '" + ((TimeZone)tz3).getID()
728                     + "' using parseObject(String, ParsePosition) - expected: "
729                     + expectedTZ.getID());
730         }
731     }
732 
733     @Test
TestISOFormat()734     public void TestISOFormat() {
735         final int[] OFFSET = {
736             0,          // 0
737             999,        // 0.999s
738             -59999,     // -59.999s
739             60000,      // 1m
740             -77777,     // -1m 17.777s
741             1800000,    // 30m
742             -3600000,   // -1h
743             36000000,   // 10h
744             -37800000,  // -10h 30m
745             -37845000,  // -10h 30m 45s
746             108000000,  // 30h
747         };
748 
749         final String[][] ISO_STR = {
750             // 0
751             {
752                 "Z", "Z", "Z", "Z", "Z",
753                 "+00", "+0000", "+00:00", "+0000", "+00:00",
754                 "+0000"
755             },
756             // 999
757             {
758                 "Z", "Z", "Z", "Z", "Z",
759                 "+00", "+0000", "+00:00", "+0000", "+00:00",
760                 "+0000"
761             },
762             // -59999
763             {
764                 "Z", "Z", "Z", "-000059", "-00:00:59",
765                 "+00", "+0000", "+00:00", "-000059", "-00:00:59",
766                 "-000059"
767             },
768             // 60000
769             {
770                 "+0001", "+0001", "+00:01", "+0001", "+00:01",
771                 "+0001", "+0001", "+00:01", "+0001", "+00:01",
772                 "+0001"
773             },
774             // -77777
775             {
776                 "-0001", "-0001", "-00:01", "-000117", "-00:01:17",
777                 "-0001", "-0001", "-00:01", "-000117", "-00:01:17",
778                 "-000117"
779             },
780             // 1800000
781             {
782                 "+0030", "+0030", "+00:30", "+0030", "+00:30",
783                 "+0030", "+0030", "+00:30", "+0030", "+00:30",
784                 "+0030"
785             },
786             // -3600000
787             {
788                 "-01", "-0100", "-01:00", "-0100", "-01:00",
789                 "-01", "-0100", "-01:00", "-0100", "-01:00",
790                 "-0100"
791             },
792             // 36000000
793             {
794                 "+10", "+1000", "+10:00", "+1000", "+10:00",
795                 "+10", "+1000", "+10:00", "+1000", "+10:00",
796                 "+1000"
797             },
798             // -37800000
799             {
800                 "-1030", "-1030", "-10:30", "-1030", "-10:30",
801                 "-1030", "-1030", "-10:30", "-1030", "-10:30",
802                 "-1030"
803             },
804             // -37845000
805             {
806                 "-1030", "-1030", "-10:30", "-103045", "-10:30:45",
807                 "-1030", "-1030", "-10:30", "-103045", "-10:30:45",
808                 "-103045"
809             },
810             // 108000000
811             {
812                 null, null, null, null, null,
813                 null, null, null, null, null,
814                 null
815             }
816         };
817 
818         final String[] PATTERN = {
819             "X", "XX", "XXX", "XXXX", "XXXXX", "x", "xx", "xxx", "xxxx", "xxxxx",
820             "Z", // equivalent to "xxxx"
821         };
822 
823         final int[] MIN_OFFSET_UNIT = {
824             60000, 60000, 60000, 1000, 1000, 60000, 60000, 60000, 1000, 1000,
825             1000,
826         };
827 
828         // Formatting
829         SimpleDateFormat sdf = new SimpleDateFormat();
830         Date d = new Date();
831 
832         for (int i = 0; i < OFFSET.length; i++) {
833             SimpleTimeZone tz = new SimpleTimeZone(OFFSET[i], "Zone Offset:" + String.valueOf(OFFSET[i]) + "ms");
834             sdf.setTimeZone(tz);
835             for (int j = 0; j < PATTERN.length; j++) {
836                 sdf.applyPattern(PATTERN[j]);
837                 try {
838                     String result = sdf.format(d);
839                     if (!result.equals(ISO_STR[i][j])) {
840                         errln("FAIL: pattern=" + PATTERN[j] + ", offset=" + OFFSET[i] + " -> "
841                             + result + " (expected: " + ISO_STR[i][j] + ")");
842                     }
843                 } catch (IllegalArgumentException e) {
844                     if (ISO_STR[i][j] != null) {
845                         errln("FAIL: IAE thrown for pattern=" + PATTERN[j] + ", offset=" + OFFSET[i]
846                                 + " (expected: " + ISO_STR[i][j] + ")");
847                     }
848                 }
849             }
850         }
851 
852         // Parsing
853         SimpleTimeZone bogusTZ = new SimpleTimeZone(-1, "Zone Offset: -1ms");
854         for (int i = 0; i < ISO_STR.length; i++) {
855             for (int j = 0; j < ISO_STR[i].length; j++) {
856                 if (ISO_STR[i][j] == null) {
857                     continue;
858                 }
859                 ParsePosition pos = new ParsePosition(0);
860                 Calendar outcal = Calendar.getInstance(bogusTZ);
861                 sdf.applyPattern(PATTERN[j]);
862 
863                 sdf.parse(ISO_STR[i][j], outcal, pos);
864 
865                 if (pos.getIndex() != ISO_STR[i][j].length()) {
866                     errln("FAIL: Failed to parse the entire input string: " + ISO_STR[i][j]);
867                     continue;
868                 }
869 
870                 TimeZone outtz = outcal.getTimeZone();
871                 int outOffset = outtz.getRawOffset();
872                 int adjustedOffset = OFFSET[i] / MIN_OFFSET_UNIT[j] * MIN_OFFSET_UNIT[j];
873 
874                 if (outOffset != adjustedOffset) {
875                     errln("FAIL: Incorrect offset:" + outOffset + "ms for input string: " + ISO_STR[i][j]
876                             + " (expected:" + adjustedOffset + "ms)");
877                 }
878             }
879         }
880     }
881 
882     @Test
TestFormat()883     public void TestFormat() {
884         final Date dateJan = new Date(1358208000000L);  // 2013-01-15T00:00:00Z
885         final Date dateJul = new Date(1373846400000L);  // 2013-07-15T00:00:00Z
886 
887         final Object[][] TESTDATA = {
888             {
889                 "en",
890                 "America/Los_Angeles",
891                 dateJan,
892                 Style.GENERIC_LOCATION,
893                 "Los Angeles Time",
894                 TimeType.UNKNOWN
895             },
896             {
897                 "en",
898                 "America/Los_Angeles",
899                 dateJan,
900                 Style.GENERIC_LONG,
901                 "Pacific Time",
902                 TimeType.UNKNOWN
903             },
904             {
905                 "en",
906                 "America/Los_Angeles",
907                 dateJan,
908                 Style.SPECIFIC_LONG,
909                 "Pacific Standard Time",
910                 TimeType.STANDARD
911             },
912             {
913                 "en",
914                 "America/Los_Angeles",
915                 dateJul,
916                 Style.SPECIFIC_LONG,
917                 "Pacific Daylight Time",
918                 TimeType.DAYLIGHT
919             },
920             {
921                 "ja",
922                 "America/Los_Angeles",
923                 dateJan,
924                 Style.ZONE_ID,
925                 "America/Los_Angeles",
926                 TimeType.UNKNOWN
927             },
928             {
929                 "fr",
930                 "America/Los_Angeles",
931                 dateJul,
932                 Style.ZONE_ID_SHORT,
933                 "uslax",
934                 TimeType.UNKNOWN
935             },
936             {
937                 "en",
938                 "America/Los_Angeles",
939                 dateJan,
940                 Style.EXEMPLAR_LOCATION,
941                 "Los Angeles",
942                 TimeType.UNKNOWN
943             },
944             {
945                 "ja",
946                 "Asia/Tokyo",
947                 dateJan,
948                 Style.GENERIC_LONG,
949                 "\u65E5\u672C\u6A19\u6E96\u6642",   // "日本標準時"
950                 TimeType.UNKNOWN
951             },
952         };
953 
954         for (Object[] testCase : TESTDATA) {
955             TimeZone tz = TimeZone.getTimeZone((String)testCase[1]);
956             Output<TimeType> timeType = new Output<TimeType>();
957 
958             ULocale uloc = new ULocale((String)testCase[0]);
959             TimeZoneFormat tzfmt = TimeZoneFormat.getInstance(uloc);
960             String out = tzfmt.format((Style)testCase[3], tz, ((Date)testCase[2]).getTime(), timeType);
961 
962             if (!out.equals(testCase[4]) || timeType.value != testCase[5]) {
963                 errln("Format result for [locale=" + testCase[0] + ",tzid=" + testCase[1] + ",date=" + testCase[2]
964                         + ",style=" + testCase[3] + "]: expected [output=" + testCase[4] + ",type=" + testCase[5]
965                         + "]; actual [output=" + out + ",type=" + timeType.value + "]");
966             }
967 
968             // with equivalent Java Locale
969             Locale loc = uloc.toLocale();
970             tzfmt = TimeZoneFormat.getInstance(loc);
971             out = tzfmt.format((Style)testCase[3], tz, ((Date)testCase[2]).getTime(), timeType);
972 
973             if (!out.equals(testCase[4]) || timeType.value != testCase[5]) {
974                 errln("Format result for [locale(Java)=" + testCase[0] + ",tzid=" + testCase[1] + ",date=" + testCase[2]
975                         + ",style=" + testCase[3] + "]: expected [output=" + testCase[4] + ",type=" + testCase[5]
976                         + "]; actual [output=" + out + ",type=" + timeType.value + "]");
977             }
978         }
979     }
980 
981     @Test
TestFormatTZDBNames()982     public void TestFormatTZDBNames() {
983         final Date dateJan = new Date(1358208000000L);  // 2013-01-15T00:00:00Z
984         final Date dateJul = new Date(1373846400000L);  // 2013-07-15T00:00:00Z
985 
986         final Object[][] TESTDATA = {
987             {
988                 "en",
989                 "America/Chicago",
990                 dateJan,
991                 Style.SPECIFIC_SHORT,
992                 "CST",
993                 TimeType.STANDARD
994             },
995             {
996                 "en",
997                 "Asia/Shanghai",
998                 dateJan,
999                 Style.SPECIFIC_SHORT,
1000                 "CST",
1001                 TimeType.STANDARD
1002             },
1003             {
1004                 "zh_Hans",
1005                 "Asia/Shanghai",
1006                 dateJan,
1007                 Style.SPECIFIC_SHORT,
1008                 "CST",
1009                 TimeType.STANDARD
1010             },
1011             {
1012                 "en",
1013                 "America/Los_Angeles",
1014                 dateJul,
1015                 Style.SPECIFIC_LONG,
1016                 "GMT-07:00",    // No long display names
1017                 TimeType.DAYLIGHT
1018             },
1019             {
1020                 "ja",
1021                 "America/Los_Angeles",
1022                 dateJul,
1023                 Style.SPECIFIC_SHORT,
1024                 "PDT",
1025                 TimeType.DAYLIGHT
1026             },
1027             {
1028                 "en",
1029                 "Australia/Sydney",
1030                 dateJan,
1031                 Style.SPECIFIC_SHORT,
1032                 "AEDT",
1033                 TimeType.DAYLIGHT
1034             },
1035             {
1036                 "en",
1037                 "Australia/Sydney",
1038                 dateJul,
1039                 Style.SPECIFIC_SHORT,
1040                 "AEST",
1041                 TimeType.STANDARD
1042             },
1043         };
1044 
1045         for (Object[] testCase : TESTDATA) {
1046             ULocale loc = new ULocale((String)testCase[0]);
1047             TimeZoneFormat tzfmt = TimeZoneFormat.getInstance(loc).cloneAsThawed();
1048             TimeZoneNames tzdbNames = TimeZoneNames.getTZDBInstance(loc);
1049             tzfmt.setTimeZoneNames(tzdbNames);
1050 
1051             TimeZone tz = TimeZone.getTimeZone((String)testCase[1]);
1052             Output<TimeType> timeType = new Output<TimeType>();
1053             String out = tzfmt.format((Style)testCase[3], tz, ((Date)testCase[2]).getTime(), timeType);
1054 
1055             if (!out.equals(testCase[4]) || timeType.value != testCase[5]) {
1056                 errln("Format result for [locale=" + testCase[0] + ",tzid=" + testCase[1] + ",date=" + testCase[2]
1057                         + ",style=" + testCase[3] + "]: expected [output=" + testCase[4] + ",type=" + testCase[5]
1058                         + "]; actual [output=" + out + ",type=" + timeType.value + "]");
1059             }
1060         }
1061     }
1062 
1063     // Tests format(Object, StringBuffer, FieldPosition):StringBuffer method
1064     // inherited from Format class
1065     @Test
TestInheritedFormat()1066     public void TestInheritedFormat() {
1067         TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles");
1068         Calendar cal = Calendar.getInstance(tz);
1069         cal.setTimeInMillis(1459187377690L); // Mar 28, 2016
1070 
1071         StringBuffer sb = new StringBuffer();
1072         FieldPosition fp = new FieldPosition(DateFormat.Field.TIME_ZONE);
1073 
1074         TimeZoneFormat fmt = TimeZoneFormat.getInstance(ULocale.ENGLISH);
1075 
1076         // Test formatting a non-timezone related object
1077         try {
1078             fmt.format(new Object(), sb, fp);
1079             errln("ERROR: format non-timezone related object failed");
1080         } catch (IllegalArgumentException e) { /* Expected */ }
1081 
1082         // Test formatting a TimeZone object
1083         sb = new StringBuffer();
1084         fmt.format(tz, sb, fp);
1085         // When formatting a TimeZone object the formatter uses the current date.
1086         String fmtOutput = tz.inDaylightTime(new Date()) ? "GMT-07:00" : "GMT-08:00";
1087         if (!sb.toString().equals(fmtOutput)) {
1088             errln("ERROR: format TimerZone object failed. Expected: " + fmtOutput + ", actual: " + sb);
1089         }
1090 
1091         // Test formatting a Calendar object
1092         sb = new StringBuffer();
1093         fmt.format(cal, sb, fp);
1094         if (!sb.toString().equals("GMT-07:00")) {
1095             errln("ERROR: format Calendar object failed. Expected: GMT-07:00, actual: " + sb);
1096         }
1097     }
1098 
1099     // This is a test case of Ticket#11487.
1100     // Because the problem is reproduced for the very first time,
1101     // the reported problem cannot be reproduced with regular test
1102     // execution. Run this test alone reproduced the problem before
1103     // the fix was merged.
1104     @Test
TestTZDBNamesThreading()1105     public void TestTZDBNamesThreading() {
1106         final TZDBTimeZoneNames names = new TZDBTimeZoneNames(ULocale.ENGLISH);
1107         final AtomicInteger found = new AtomicInteger();
1108         List<Thread> threads = new ArrayList<Thread>();
1109         final int numIteration = 1000;
1110 
1111         try {
1112             for (int i = 0; i < numIteration; i++) {
1113                 Thread thread = new Thread() {
1114                     @Override
1115                     public void run() {
1116                         int resultSize = names.find("GMT", 0, EnumSet.allOf(NameType.class)).size();
1117                         if (resultSize > 0) {
1118                             found.incrementAndGet();
1119                         }
1120                     }
1121                 };
1122                 thread.start();
1123                 threads.add(thread);
1124             }
1125 
1126             for(Thread thread: threads) {
1127                 thread.join();
1128             }
1129         } catch (Throwable t) {
1130             errln(t.toString());
1131         }
1132 
1133         if (found.intValue() != numIteration) {
1134             errln("Incorrect count: " + found.toString() + ", expected: " + numIteration);
1135         }
1136     }
1137 
1138     @Test
TestGetDisplayNames()1139     public void TestGetDisplayNames() {
1140         long date = System.currentTimeMillis();
1141         NameType[] types = new NameType[]{
1142                 NameType.LONG_STANDARD, NameType.LONG_DAYLIGHT,
1143                 NameType.SHORT_STANDARD, NameType.SHORT_DAYLIGHT
1144         };
1145         Set<String> zones = ZoneMeta.getAvailableIDs(SystemTimeZoneType.ANY, null, null);
1146 
1147         int casesTested = 0;
1148         Random rnd = new Random(2016);
1149         for (ULocale uloc : ULocale.getAvailableLocales()) {
1150             if (rnd.nextDouble() > 0.01) { continue; }
1151             for (String zone : zones) {
1152                 if (rnd.nextDouble() > 0.01) { continue; }
1153                 casesTested++;
1154 
1155                 // Test default TimeZoneNames (uses an overridden getDisplayNames)
1156                 {
1157                     TimeZoneNames tznames = TimeZoneNames.getInstance(uloc);
1158                     tznames.loadAllDisplayNames();
1159                     String[] result = new String[types.length];
1160                     tznames.getDisplayNames(zone, types, date, result, 0);
1161                     for (int i=0; i<types.length; i++) {
1162                         NameType type = types[i];
1163                         String expected = result[i];
1164                         String actual = tznames.getDisplayName(zone, type, date);
1165                         assertEquals("TimeZoneNames: getDisplayNames() returns different result than getDisplayName()"
1166                                 + " for " + zone + " in locale " + uloc, expected, actual);
1167                     }
1168                     // Coverage for empty call to getDisplayNames
1169                     tznames.getDisplayNames(null, null, 0, null, 0);
1170                 }
1171 
1172                 // Test TZDBTimeZoneNames (uses getDisplayNames from abstract class)
1173                 {
1174                     TimeZoneNames tznames = new TZDBTimeZoneNames(uloc);
1175                     tznames.loadAllDisplayNames();
1176                     String[] result = new String[types.length];
1177                     tznames.getDisplayNames(zone, types, date, result, 0);
1178                     for (int i=0; i<types.length; i++) {
1179                         NameType type = types[i];
1180                         String expected = result[i];
1181                         String actual = tznames.getDisplayName(zone, type, date);
1182                         assertEquals("TZDBTimeZoneNames: getDisplayNames() returns different result than getDisplayName()"
1183                                 + " for " + zone + " in locale " + uloc, expected, actual);
1184                     }
1185                     // Coverage for empty call to getDisplayNames
1186                     tznames.getDisplayNames(null, null, 0, null, 0);
1187                 }
1188             }
1189         }
1190 
1191         assertTrue("No cases were tested", casesTested > 0);
1192     }
1193 
1194     class TimeZoneNamesInheriter extends TimeZoneNames {
1195         private static final long serialVersionUID = 1L;
1196 
1197         @Override
getAvailableMetaZoneIDs()1198         public Set<String> getAvailableMetaZoneIDs() {
1199             return null;
1200         }
1201 
1202         @Override
getAvailableMetaZoneIDs(String tzID)1203         public Set<String> getAvailableMetaZoneIDs(String tzID) {
1204             return null;
1205         }
1206 
1207         @Override
getMetaZoneID(String tzID, long date)1208         public String getMetaZoneID(String tzID, long date) {
1209             return null;
1210         }
1211 
1212         @Override
getReferenceZoneID(String mzID, String region)1213         public String getReferenceZoneID(String mzID, String region) {
1214             return null;
1215         }
1216 
1217         @Override
getMetaZoneDisplayName(String mzID, NameType type)1218         public String getMetaZoneDisplayName(String mzID, NameType type) {
1219             return null;
1220         }
1221 
1222         @Override
getTimeZoneDisplayName(String tzID, NameType type)1223         public String getTimeZoneDisplayName(String tzID, NameType type) {
1224             return null;
1225         }
1226     }
1227 
1228     // Coverage for default implementation and abstract methods in base class.
1229     @Test
TestDefaultTimeZoneNames()1230     public void TestDefaultTimeZoneNames() {
1231         long date = System.currentTimeMillis();
1232         TimeZoneNames.Factory factory;
1233         try {
1234             Class cls = Class.forName("ohos.global.icu.text.TimeZoneNames$DefaultTimeZoneNames$FactoryImpl");
1235             factory = (Factory) cls.newInstance();
1236         } catch (Exception e) {
1237             errln("Could not create class DefaultTimeZoneNames.FactoryImpl: " + e.getClass() + ": " + e.getMessage());
1238             return;
1239         }
1240         TimeZoneNames tzn = factory.getTimeZoneNames(ULocale.ENGLISH);
1241         assertEquals("Abstract: getAvailableMetaZoneIDs()",
1242                 tzn.getAvailableMetaZoneIDs(), Collections.emptySet());
1243         assertEquals("Abstract: getAvailableMetaZoneIDs(String tzID)",
1244                 tzn.getAvailableMetaZoneIDs("America/Chicago"), Collections.emptySet());
1245         assertEquals("Abstract: getMetaZoneID(String tzID, long date)",
1246                 tzn.getMetaZoneID("America/Chicago", date), null);
1247         assertEquals("Abstract: getReferenceZoneID(String mzID, String region)",
1248                 tzn.getReferenceZoneID("America_Central", "IT"), null);
1249         assertEquals("Abstract: getMetaZoneDisplayName(String mzID, NameType type)",
1250                 tzn.getMetaZoneDisplayName("America_Central", NameType.LONG_DAYLIGHT), null);
1251         assertEquals("Abstract: getTimeZoneDisplayName(String mzID, NameType type)",
1252                 tzn.getTimeZoneDisplayName("America/Chicago", NameType.LONG_DAYLIGHT), null);
1253         assertEquals("Abstract: find(CharSequence text, int start, EnumSet<NameType> nameTypes)",
1254                 tzn.find("foo", 0, EnumSet.noneOf(NameType.class)), Collections.emptyList());
1255 
1256         // Other abstract-class methods that aren't covered
1257         tzn = new TimeZoneNamesInheriter();
1258         try {
1259             tzn.find(null, 0, null);
1260         } catch (UnsupportedOperationException e) {
1261             assertEquals("find() exception", "The method is not implemented in TimeZoneNames base class.", e.getMessage());
1262         }
1263     }
1264 
1265     // Basic get/set test for methods not being called otherwise.
1266     @Test
TestAPI()1267     public void TestAPI() {
1268         TimeZoneFormat tzfmtEn = TimeZoneFormat.getInstance(ULocale.ENGLISH);
1269         TimeZoneFormat tzfmtAr = TimeZoneFormat.getInstance(new ULocale("ar")).cloneAsThawed();
1270         TimeZoneNames tzn = TimeZoneNames.getInstance(Locale.ENGLISH);
1271 
1272         String digits = tzfmtEn.getGMTOffsetDigits();
1273         tzfmtAr.setGMTOffsetDigits(digits);
1274         if (!digits.equals(tzfmtAr.getGMTOffsetDigits())) {
1275             errln("ERROR: get/set GMTOffsetDigits failed");
1276         }
1277 
1278         String pattern = tzfmtEn.getGMTOffsetPattern(GMTOffsetPatternType.POSITIVE_H);
1279         tzfmtAr.setGMTOffsetPattern(GMTOffsetPatternType.POSITIVE_H, pattern);
1280         if (!pattern.equals(tzfmtAr.getGMTOffsetPattern(GMTOffsetPatternType.POSITIVE_H))) {
1281             errln("ERROR: get/set GMTOffsetPattern failed");
1282         }
1283 
1284         String zeroFmt = tzfmtEn.getGMTZeroFormat();
1285         tzfmtAr.setGMTZeroFormat(zeroFmt);
1286         if (!zeroFmt.equals(tzfmtAr.getGMTZeroFormat())) {
1287             errln("ERROR: get/set GMTZeroFormat failed");
1288         }
1289 
1290         Set<String> allAvailableMZIDs = tzn.getAvailableMetaZoneIDs();
1291         if (allAvailableMZIDs.size() < 150 || !allAvailableMZIDs.contains("America_Central")) {
1292             errln("ERROR: getAvailableMetaZoneIDs() did not return expected value");
1293         }
1294 
1295         Set<String> kinshasaAvailableMZIDs = tzn.getAvailableMetaZoneIDs("Africa/Kinshasa");
1296         if (!kinshasaAvailableMZIDs.contains("Africa_Western") || kinshasaAvailableMZIDs.contains("America_Central")) {
1297             errln("ERROR: getAvailableMetaZoneIDs('Africa/Kinshasa') did not return expected value");
1298         }
1299 
1300         try {
1301             new TimeZoneNames.MatchInfo(null, null, null, -1);
1302             assertTrue("MatchInfo doesn't throw IllegalArgumentException", false);
1303         } catch (IllegalArgumentException e) {
1304             assertEquals("MatchInfo constructor exception", "nameType is null", e.getMessage());
1305         }
1306 
1307         try {
1308             new TimeZoneNames.MatchInfo(NameType.LONG_GENERIC, null, null, -1);
1309             assertTrue("MatchInfo doesn't throw IllegalArgumentException", false);
1310         } catch (IllegalArgumentException e) {
1311             assertEquals("MatchInfo constructor exception", "Either tzID or mzID must be available", e.getMessage());
1312         }
1313 
1314         try {
1315             new TimeZoneNames.MatchInfo(NameType.LONG_GENERIC, "America/Chicago", null, -1);
1316             assertTrue("MatchInfo doesn't throw IllegalArgumentException", false);
1317         } catch (IllegalArgumentException e) {
1318             assertEquals("MatchInfo constructor exception", "matchLength must be positive value", e.getMessage());
1319         }
1320     }
1321 }
1322