• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3 /*
4 *******************************************************************************
5 * Copyright (C) 2007-2015, International Business Machines Corporation and    *
6 * others. All Rights Reserved.                                                *
7 *******************************************************************************
8 */
9 #include "unicode/utypes.h"
10 
11 #if !UCONFIG_NO_FORMATTING
12 
13 #include "tzfmttst.h"
14 
15 #include "unicode/timezone.h"
16 #include "unicode/simpletz.h"
17 #include "unicode/calendar.h"
18 #include "unicode/strenum.h"
19 #include "unicode/smpdtfmt.h"
20 #include "unicode/uchar.h"
21 #include "unicode/basictz.h"
22 #include "unicode/tzfmt.h"
23 #include "unicode/localpointer.h"
24 #include "unicode/utf16.h"
25 
26 #include "cstring.h"
27 #include "cstr.h"
28 #include "mutex.h"
29 #include "simplethread.h"
30 #include "uassert.h"
31 #include "zonemeta.h"
32 
33 static const char* PATTERNS[] = {
34     "z",
35     "zzzz",
36     "Z",    // equivalent to "xxxx"
37     "ZZZZ", // equivalent to "OOOO"
38     "v",
39     "vvvv",
40     "O",
41     "OOOO",
42     "X",
43     "XX",
44     "XXX",
45     "XXXX",
46     "XXXXX",
47     "x",
48     "xx",
49     "xxx",
50     "xxxx",
51     "xxxxx",
52     "V",
53     "VV",
54     "VVV",
55     "VVVV"
56 };
57 
58 static const UChar ETC_UNKNOWN[] = {0x45, 0x74, 0x63, 0x2F, 0x55, 0x6E, 0x6B, 0x6E, 0x6F, 0x77, 0x6E, 0};
59 
60 static const UChar ETC_SLASH[] = { 0x45, 0x74, 0x63, 0x2F, 0 }; // "Etc/"
61 static const UChar SYSTEMV_SLASH[] = { 0x53, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x56, 0x2F, 0 }; // "SystemV/
62 static const UChar RIYADH8[] = { 0x52, 0x69, 0x79, 0x61, 0x64, 0x68, 0x38, 0 }; // "Riyadh8"
63 
contains(const char ** list,const char * str)64 static UBool contains(const char** list, const char* str) {
65     for (int32_t i = 0; list[i]; i++) {
66         if (uprv_strcmp(list[i], str) == 0) {
67             return TRUE;
68         }
69     }
70     return FALSE;
71 }
72 
73 void
runIndexedTest(int32_t index,UBool exec,const char * & name,char *)74 TimeZoneFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ )
75 {
76     if (exec) {
77         logln("TestSuite TimeZoneFormatTest");
78     }
79     switch (index) {
80         TESTCASE(0, TestTimeZoneRoundTrip);
81         TESTCASE(1, TestTimeRoundTrip);
82         TESTCASE(2, TestParse);
83         TESTCASE(3, TestISOFormat);
84         TESTCASE(4, TestFormat);
85         TESTCASE(5, TestFormatTZDBNames);
86         TESTCASE(6, TestFormatCustomZone);
87         TESTCASE(7, TestFormatTZDBNamesAllZoneCoverage);
88         TESTCASE(8, TestAdoptDefaultThreadSafe);
89         TESTCASE(9, TestCentralTime);
90     default: name = ""; break;
91     }
92 }
93 
94 void
TestTimeZoneRoundTrip(void)95 TimeZoneFormatTest::TestTimeZoneRoundTrip(void) {
96     UErrorCode status = U_ZERO_ERROR;
97 
98     SimpleTimeZone unknownZone(-31415, ETC_UNKNOWN);
99     int32_t badDstOffset = -1234;
100     int32_t badZoneOffset = -2345;
101 
102     int32_t testDateData[][3] = {
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::createInstance(TimeZone::createTimeZone((UnicodeString)"UTC"), status);
112     if (U_FAILURE(status)) {
113         dataerrln("Calendar::createInstance failed: %s", u_errorName(status));
114         return;
115     }
116 
117     // Set up rule equivalency test range
118     UDate low, high;
119     cal->set(1900, UCAL_JANUARY, 1);
120     low = cal->getTime(status);
121     cal->set(2040, UCAL_JANUARY, 1);
122     high = cal->getTime(status);
123     if (U_FAILURE(status)) {
124         errln("getTime failed");
125         return;
126     }
127 
128     // Set up test dates
129     UDate DATES[UPRV_LENGTHOF(testDateData)];
130     const int32_t nDates = UPRV_LENGTHOF(testDateData);
131     cal->clear();
132     for (int32_t i = 0; i < nDates; i++) {
133         cal->set(testDateData[i][0], testDateData[i][1], testDateData[i][2]);
134         DATES[i] = cal->getTime(status);
135         if (U_FAILURE(status)) {
136             errln("getTime failed");
137             return;
138         }
139     }
140 
141     // Set up test locales
142     const Locale testLocales[] = {
143         Locale("en"),
144         Locale("en_CA"),
145         Locale("fr"),
146         Locale("zh_Hant"),
147         Locale("fa"),
148         Locale("ccp")
149     };
150 
151     const Locale *LOCALES;
152     int32_t nLocales;
153 
154     if (quick) {
155         LOCALES = testLocales;
156         nLocales = UPRV_LENGTHOF(testLocales);
157     } else {
158         LOCALES = Locale::getAvailableLocales(nLocales);
159     }
160 
161     StringEnumeration *tzids = TimeZone::createEnumeration(status);
162     if (U_FAILURE(status)) {
163         dataerrln("Unable to create TimeZone enumeration");
164         return;
165     }
166     int32_t inRaw, inDst;
167     int32_t outRaw, outDst;
168 
169     // Run the roundtrip test
170     for (int32_t locidx = 0; locidx < nLocales; locidx++) {
171         UnicodeString localGMTString;
172         SimpleDateFormat gmtFmt(UnicodeString("ZZZZ"), LOCALES[locidx], status);
173         if (U_FAILURE(status)) {
174             dataerrln("Error creating SimpleDateFormat - %s", u_errorName(status));
175             continue;
176         }
177         gmtFmt.setTimeZone(*TimeZone::getGMT());
178         gmtFmt.format(0.0, localGMTString);
179 
180         for (int32_t patidx = 0; patidx < UPRV_LENGTHOF(PATTERNS); patidx++) {
181             SimpleDateFormat *sdf = new SimpleDateFormat((UnicodeString)PATTERNS[patidx], LOCALES[locidx], status);
182             if (U_FAILURE(status)) {
183                 dataerrln((UnicodeString)"new SimpleDateFormat failed for pattern " +
184                     PATTERNS[patidx] + " for locale " + LOCALES[locidx].getName() + " - " + u_errorName(status));
185                 status = U_ZERO_ERROR;
186                 continue;
187             }
188 
189             tzids->reset(status);
190             const UnicodeString *tzid;
191             while ((tzid = tzids->snext(status))) {
192                 TimeZone *tz = TimeZone::createTimeZone(*tzid);
193 
194                 for (int32_t datidx = 0; datidx < nDates; datidx++) {
195                     UnicodeString tzstr;
196                     FieldPosition fpos(FieldPosition::DONT_CARE);
197                     // Format
198                     sdf->setTimeZone(*tz);
199                     sdf->format(DATES[datidx], tzstr, fpos);
200 
201                     // Before parse, set unknown zone to SimpleDateFormat instance
202                     // just for making sure that it does not depends on the time zone
203                     // originally set.
204                     sdf->setTimeZone(unknownZone);
205 
206                     // Parse
207                     ParsePosition pos(0);
208                     Calendar *outcal = Calendar::createInstance(unknownZone, status);
209                     if (U_FAILURE(status)) {
210                         errln("Failed to create an instance of calendar for receiving parse result.");
211                         status = U_ZERO_ERROR;
212                         continue;
213                     }
214                     outcal->set(UCAL_DST_OFFSET, badDstOffset);
215                     outcal->set(UCAL_ZONE_OFFSET, badZoneOffset);
216 
217                     sdf->parse(tzstr, *outcal, pos);
218 
219                     // Check the result
220                     const TimeZone &outtz = outcal->getTimeZone();
221                     UnicodeString outtzid;
222                     outtz.getID(outtzid);
223 
224                     tz->getOffset(DATES[datidx], false, inRaw, inDst, status);
225                     if (U_FAILURE(status)) {
226                         errln((UnicodeString)"Failed to get offsets from time zone" + *tzid);
227                         status = U_ZERO_ERROR;
228                     }
229                     outtz.getOffset(DATES[datidx], false, outRaw, outDst, status);
230                     if (U_FAILURE(status)) {
231                         errln((UnicodeString)"Failed to get offsets from time zone" + outtzid);
232                         status = U_ZERO_ERROR;
233                     }
234 
235                     if (uprv_strcmp(PATTERNS[patidx], "V") == 0) {
236                         // Short zone ID - should support roundtrip for canonical CLDR IDs
237                         UnicodeString canonicalID;
238                         TimeZone::getCanonicalID(*tzid, canonicalID, status);
239                         if (U_FAILURE(status)) {
240                             // Unknown ID - we should not get here
241                             errln((UnicodeString)"Unknown ID " + *tzid);
242                             status = U_ZERO_ERROR;
243                         } else if (outtzid != canonicalID) {
244                             if (outtzid.compare(ETC_UNKNOWN, -1) == 0) {
245                                 // Note that some zones like Asia/Riyadh87 does not have
246                                 // short zone ID and "unk" is used as fallback
247                                 logln((UnicodeString)"Canonical round trip failed (probably as expected); tz=" + *tzid
248                                         + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
249                                         + ", time=" + DATES[datidx] + ", str=" + tzstr
250                                         + ", outtz=" + outtzid);
251                             } else {
252                                 errln((UnicodeString)"Canonical round trip failed; tz=" + *tzid
253                                     + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
254                                     + ", time=" + DATES[datidx] + ", str=" + tzstr
255                                     + ", outtz=" + outtzid);
256                             }
257                         }
258                     } else if (uprv_strcmp(PATTERNS[patidx], "VV") == 0) {
259                         // Zone ID - full roundtrip support
260                         if (outtzid != *tzid) {
261                             errln((UnicodeString)"Zone ID round trip failued; tz="  + *tzid
262                                 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
263                                 + ", time=" + DATES[datidx] + ", str=" + tzstr
264                                 + ", outtz=" + outtzid);
265                         }
266                     } else if (uprv_strcmp(PATTERNS[patidx], "VVV") == 0 || uprv_strcmp(PATTERNS[patidx], "VVVV") == 0) {
267                         // Location: time zone rule must be preserved except
268                         // zones not actually associated with a specific location.
269                         // Time zones in this category do not have "/" in its ID.
270                         UnicodeString canonical;
271                         TimeZone::getCanonicalID(*tzid, canonical, status);
272                         if (U_FAILURE(status)) {
273                             // Unknown ID - we should not get here
274                             errln((UnicodeString)"Unknown ID " + *tzid);
275                             status = U_ZERO_ERROR;
276                         } else if (outtzid != canonical) {
277                             // Canonical ID did not match - check the rules
278                             if (!((BasicTimeZone*)&outtz)->hasEquivalentTransitions((BasicTimeZone&)*tz, low, high, TRUE, status)) {
279                                 if (canonical.indexOf((UChar)0x27 /*'/'*/) == -1) {
280                                     // Exceptional cases, such as CET, EET, MET and WET
281                                     logln((UnicodeString)"Canonical round trip failed (as expected); tz=" + *tzid
282                                             + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
283                                             + ", time=" + DATES[datidx] + ", str=" + tzstr
284                                             + ", outtz=" + outtzid);
285                                 } else {
286                                     errln((UnicodeString)"Canonical round trip failed; tz=" + *tzid
287                                         + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
288                                         + ", time=" + DATES[datidx] + ", str=" + tzstr
289                                         + ", outtz=" + outtzid);
290                                 }
291                                 if (U_FAILURE(status)) {
292                                     errln("hasEquivalentTransitions failed");
293                                     status = U_ZERO_ERROR;
294                                 }
295                             }
296                         }
297 
298                     } else {
299                         UBool isOffsetFormat = (*PATTERNS[patidx] == 'Z'
300                                                 || *PATTERNS[patidx] == 'O'
301                                                 || *PATTERNS[patidx] == 'X'
302                                                 || *PATTERNS[patidx] == 'x');
303                         UBool minutesOffset = FALSE;
304                         if (*PATTERNS[patidx] == 'X' || *PATTERNS[patidx] == 'x') {
305                             minutesOffset = (uprv_strlen(PATTERNS[patidx]) <= 3);
306                         }
307 
308                         if (!isOffsetFormat) {
309                             // Check if localized GMT format is used as a fallback of name styles
310                             int32_t numDigits = 0;
311                             int32_t idx = 0;
312                             while (idx < tzstr.length()) {
313                                 UChar32 cp = tzstr.char32At(idx);
314                                 if (u_isdigit(cp)) {
315                                     numDigits++;
316                                 }
317                                 idx += U16_LENGTH(cp);
318                             }
319                             isOffsetFormat = (numDigits > 0);
320                         }
321                         if (isOffsetFormat || tzstr == localGMTString) {
322                             // Localized GMT or ISO: total offset (raw + dst) must be preserved.
323                             int32_t inOffset = inRaw + inDst;
324                             int32_t outOffset = outRaw + outDst;
325                             int32_t diff = outOffset - inOffset;
326                             if (minutesOffset) {
327                                 diff = (diff / 60000) * 60000;
328                             }
329                             if (diff != 0) {
330                                 errln((UnicodeString)"Offset round trip failed; tz=" + *tzid
331                                     + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
332                                     + ", time=" + DATES[datidx] + ", str=" + tzstr
333                                     + ", inOffset=" + inOffset + ", outOffset=" + outOffset);
334                             }
335                         } else {
336                             // Specific or generic: raw offset must be preserved.
337                             if (inRaw != outRaw) {
338                                 errln((UnicodeString)"Raw offset round trip failed; tz=" + *tzid
339                                     + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
340                                     + ", time=" + DATES[datidx] + ", str=" + tzstr
341                                     + ", inRawOffset=" + inRaw + ", outRawOffset=" + outRaw);
342                             }
343                         }
344                     }
345                     delete outcal;
346                 }
347                 delete tz;
348             }
349             delete sdf;
350         }
351     }
352     delete cal;
353     delete tzids;
354 }
355 
356 // Special exclusions in TestTimeZoneRoundTrip.
357 // These special cases do not round trip time as designed.
isSpecialTimeRoundTripCase(const char * loc,const UnicodeString & id,const char * pattern,UDate time)358 static UBool isSpecialTimeRoundTripCase(const char* loc,
359                                         const UnicodeString& id,
360                                         const char* pattern,
361                                         UDate time) {
362     struct {
363         const char* loc;
364         const char* id;
365         const char* pattern;
366         UDate time;
367     } EXCLUSIONS[] = {
368         {NULL, "Asia/Chita", "zzzz", 1414252800000.0},
369         {NULL, "Asia/Chita", "vvvv", 1414252800000.0},
370         {NULL, "Asia/Srednekolymsk", "zzzz", 1414241999999.0},
371         {NULL, "Asia/Srednekolymsk", "vvvv", 1414241999999.0},
372         {NULL, NULL, NULL, U_DATE_MIN}
373     };
374 
375     UBool isExcluded = FALSE;
376     for (int32_t i = 0; EXCLUSIONS[i].id != NULL; i++) {
377         if (EXCLUSIONS[i].loc == NULL || uprv_strcmp(loc, EXCLUSIONS[i].loc) == 0) {
378             if (id.compare(EXCLUSIONS[i].id) == 0) {
379                 if (EXCLUSIONS[i].pattern == NULL || uprv_strcmp(pattern, EXCLUSIONS[i].pattern) == 0) {
380                     if (EXCLUSIONS[i].time == U_DATE_MIN || EXCLUSIONS[i].time == time) {
381                         isExcluded = TRUE;
382                     }
383                 }
384             }
385         }
386     }
387     return isExcluded;
388 }
389 
390 // LocaleData. Somewhat misnamed. For TestTimeZoneRoundTrip, specifies the locales and patterns
391 //             to be tested, and provides an iterator over these for the multi-threaded test
392 //             functions to pick up the next combination to be tested.
393 //
394 //             A single global instance of this struct is shared among all
395 //             the test threads.
396 //
397 //             "locales" is an array of locales to be tested.
398 //             PATTERNS (a global) is an array of patterns to be tested for each locale.
399 //             "localeIndex" and "patternIndex" keep track of the iteration through the above.
400 //             Each of the parallel test threads calls LocaleData::nextTest() in a loop
401 //                to find out what to test next. It must be thread safe.
402 struct LocaleData {
403     int32_t localeIndex;
404     int32_t patternIndex;
405     int32_t testCounts;
406     UDate times[UPRV_LENGTHOF(PATTERNS)];    // Performance data, Elapsed time for each pattern.
407     const Locale* locales;
408     int32_t nLocales;
409     UDate START_TIME;
410     UDate END_TIME;
411     int32_t numDone;
412 
LocaleDataLocaleData413     LocaleData() : localeIndex(0), patternIndex(0), testCounts(0), locales(NULL),
414                    nLocales(0), START_TIME(0), END_TIME(0), numDone(0) {
415         for (int i=0; i<UPRV_LENGTHOF(times); i++) {
416             times[i] = 0;
417         }
418     }
419 
resetTestIterationLocaleData420     void resetTestIteration() {
421         localeIndex = -1;
422         patternIndex = UPRV_LENGTHOF(PATTERNS);
423         numDone = 0;
424     }
425 
nextTestLocaleData426     UBool nextTest(int32_t &rLocaleIndex, int32_t &rPatternIndex) {
427         Mutex lock;
428         if (patternIndex >= UPRV_LENGTHOF(PATTERNS) - 1) {
429             if (localeIndex >= nLocales - 1) {
430                 return FALSE;
431             }
432             patternIndex = -1;
433             ++localeIndex;
434         }
435         ++patternIndex;
436         rLocaleIndex = localeIndex;
437         rPatternIndex = patternIndex;
438         ++numDone;
439         return TRUE;
440     }
441 
addTimeLocaleData442     void addTime(UDate amount, int32_t patIdx) {
443         Mutex lock;
444         U_ASSERT(patIdx < UPRV_LENGTHOF(PATTERNS));
445         times[patIdx] += amount;
446     }
447 };
448 
449 static LocaleData *gLocaleData = NULL;
450 
451 void
TestTimeRoundTrip(void)452 TimeZoneFormatTest::TestTimeRoundTrip(void) {
453     UErrorCode status = U_ZERO_ERROR;
454     LocalPointer <Calendar> cal(Calendar::createInstance(TimeZone::createTimeZone((UnicodeString) "UTC"), status));
455     if (U_FAILURE(status)) {
456         dataerrln("Calendar::createInstance failed: %s", u_errorName(status));
457         return;
458     }
459 
460     const char* testAllProp = getProperty("TimeZoneRoundTripAll");
461     UBool bTestAll = (testAllProp && uprv_strcmp(testAllProp, "true") == 0);
462 
463     UDate START_TIME, END_TIME;
464     if (bTestAll || !quick) {
465         cal->set(1900, UCAL_JANUARY, 1);
466     } else {
467         cal->set(1999, UCAL_JANUARY, 1);
468     }
469     START_TIME = cal->getTime(status);
470 
471     cal->set(2022, UCAL_JANUARY, 1);
472     END_TIME = cal->getTime(status);
473 
474     if (U_FAILURE(status)) {
475         errln("getTime failed");
476         return;
477     }
478 
479     LocaleData localeData;
480     gLocaleData = &localeData;
481 
482     // Set up test locales
483     const Locale locales1[] = {Locale("en")};
484     const Locale locales2[] = {
485         Locale("ar_EG"), Locale("bg_BG"), Locale("ca_ES"), Locale("da_DK"), Locale("de"),
486         Locale("de_DE"), Locale("el_GR"), Locale("en"), Locale("en_AU"), Locale("en_CA"),
487         Locale("en_US"), Locale("es"), Locale("es_ES"), Locale("es_MX"), Locale("fi_FI"),
488         Locale("fr"), Locale("fr_CA"), Locale("fr_FR"), Locale("he_IL"), Locale("hu_HU"),
489         Locale("it"), Locale("it_IT"), Locale("ja"), Locale("ja_JP"), Locale("ko"),
490         Locale("ko_KR"), Locale("nb_NO"), Locale("nl_NL"), Locale("nn_NO"), Locale("pl_PL"),
491         Locale("pt"), Locale("pt_BR"), Locale("pt_PT"), Locale("ru_RU"), Locale("sv_SE"),
492         Locale("th_TH"), Locale("tr_TR"), Locale("zh"), Locale("zh_Hans"), Locale("zh_Hans_CN"),
493         Locale("zh_Hant"), Locale("zh_Hant_TW"), Locale("fa"), Locale("ccp")
494     };
495 
496     if (bTestAll) {
497         gLocaleData->locales = Locale::getAvailableLocales(gLocaleData->nLocales);
498     } else if (quick) {
499         gLocaleData->locales = locales1;
500         gLocaleData->nLocales = UPRV_LENGTHOF(locales1);
501     } else {
502         gLocaleData->locales = locales2;
503         gLocaleData->nLocales = UPRV_LENGTHOF(locales2);
504     }
505 
506     gLocaleData->START_TIME = START_TIME;
507     gLocaleData->END_TIME = END_TIME;
508     gLocaleData->resetTestIteration();
509 
510     // start IntlTest.threadCount threads, each running the function RunTimeRoundTripTests().
511 
512     ThreadPool<TimeZoneFormatTest> threads(this, threadCount, &TimeZoneFormatTest::RunTimeRoundTripTests);
513     threads.start();   // Start all threads.
514     threads.join();    // Wait for all threads to finish.
515 
516     UDate total = 0;
517     logln("### Elapsed time by patterns ###");
518     for (int32_t i = 0; i < UPRV_LENGTHOF(PATTERNS); i++) {
519         logln(UnicodeString("") + gLocaleData->times[i] + "ms (" + PATTERNS[i] + ")");
520         total += gLocaleData->times[i];
521     }
522     logln((UnicodeString) "Total: " + total + "ms");
523     logln((UnicodeString) "Iteration: " + gLocaleData->testCounts);
524 }
525 
526 
527 // TimeZoneFormatTest::RunTimeRoundTripTests()
528 //    This function loops, running time zone format round trip test cases until there are no more, then returns.
529 //    Threading: multiple invocations of this function are started in parallel
530 //               by TimeZoneFormatTest::TestTimeRoundTrip()
531 //
RunTimeRoundTripTests(int32_t threadNumber)532 void TimeZoneFormatTest::RunTimeRoundTripTests(int32_t threadNumber) {
533     UErrorCode status = U_ZERO_ERROR;
534     UBool REALLY_VERBOSE = FALSE;
535 
536     // These patterns are ambiguous at DST->STD local time overlap
537     const char* AMBIGUOUS_DST_DECESSION[] = { "v", "vvvv", "V", "VV", "VVV", "VVVV", 0 };
538 
539     // These patterns are ambiguous at STD->STD/DST->DST local time overlap
540     const char* AMBIGUOUS_NEGATIVE_SHIFT[] = { "z", "zzzz", "v", "vvvv", "V", "VV", "VVV", "VVVV", 0 };
541 
542     // These patterns only support integer minutes offset
543     const char* MINUTES_OFFSET[] = { "X", "XX", "XXX", "x", "xx", "xxx", 0 };
544 
545     // Workaround for #6338
546     //UnicodeString BASEPATTERN("yyyy-MM-dd'T'HH:mm:ss.SSS");
547     UnicodeString BASEPATTERN("yyyy.MM.dd HH:mm:ss.SSS");
548 
549     // timer for performance analysis
550     UDate timer;
551     UDate testTimes[4];
552     UBool expectedRoundTrip[4];
553     int32_t testLen = 0;
554 
555     StringEnumeration *tzids = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL, NULL, NULL, status);
556     if (U_FAILURE(status)) {
557         if (status == U_MISSING_RESOURCE_ERROR) {
558             // This error is generally caused by data not being present.
559             dataerrln("TimeZone::createTimeZoneIDEnumeration failed - %s", u_errorName(status));
560         } else {
561             errln("TimeZone::createTimeZoneIDEnumeration failed: %s", u_errorName(status));
562         }
563         return;
564     }
565 
566     int32_t locidx = -1;
567     int32_t patidx = -1;
568 
569     while (gLocaleData->nextTest(locidx, patidx)) {
570 
571         UnicodeString pattern(BASEPATTERN);
572         pattern.append(" ").append(PATTERNS[patidx]);
573         logln("    Thread %d, Locale %s, Pattern %s",
574                 threadNumber, gLocaleData->locales[locidx].getName(), CStr(pattern)());
575 
576         SimpleDateFormat *sdf = new SimpleDateFormat(pattern, gLocaleData->locales[locidx], status);
577         if (U_FAILURE(status)) {
578             errcheckln(status, (UnicodeString) "new SimpleDateFormat failed for pattern " +
579                 pattern + " for locale " + gLocaleData->locales[locidx].getName() + " - " + u_errorName(status));
580             status = U_ZERO_ERROR;
581             continue;
582         }
583 
584         UBool minutesOffset = contains(MINUTES_OFFSET, PATTERNS[patidx]);
585 
586         tzids->reset(status);
587         const UnicodeString *tzid;
588 
589         timer = Calendar::getNow();
590 
591         while ((tzid = tzids->snext(status))) {
592             if (uprv_strcmp(PATTERNS[patidx], "V") == 0) {
593                 // Some zones do not have short ID assigned, such as Asia/Riyadh87.
594                 // The time roundtrip will fail for such zones with pattern "V" (short zone ID).
595                 // This is expected behavior.
596                 const UChar* shortZoneID = ZoneMeta::getShortID(*tzid);
597                 if (shortZoneID == NULL) {
598                     continue;
599                 }
600             } else if (uprv_strcmp(PATTERNS[patidx], "VVV") == 0) {
601                 // Some zones are not associated with any region, such as Etc/GMT+8.
602                 // The time roundtrip will fail for such zone with pattern "VVV" (exemplar location).
603                 // This is expected behavior.
604                 if (tzid->indexOf((UChar)0x2F) < 0 || tzid->indexOf(ETC_SLASH, -1, 0) >= 0
605                     || tzid->indexOf(SYSTEMV_SLASH, -1, 0) >= 0 || tzid->indexOf(RIYADH8, -1, 0) >= 0) {
606                     continue;
607                 }
608             }
609 
610             if ((*tzid == "Pacific/Apia" || *tzid == "Pacific/Midway" || *tzid == "Pacific/Pago_Pago")
611                     && uprv_strcmp(PATTERNS[patidx], "vvvv") == 0
612                     && logKnownIssue("11052", "Ambiguous zone name - Samoa Time")) {
613                 continue;
614             }
615 
616             BasicTimeZone *tz = (BasicTimeZone*) TimeZone::createTimeZone(*tzid);
617             sdf->setTimeZone(*tz);
618 
619             UDate t = gLocaleData->START_TIME;
620             TimeZoneTransition tzt;
621             UBool tztAvail = FALSE;
622             UBool middle = TRUE;
623 
624             while (t < gLocaleData->END_TIME) {
625                 if (!tztAvail) {
626                     testTimes[0] = t;
627                     expectedRoundTrip[0] = TRUE;
628                     testLen = 1;
629                 } else {
630                     int32_t fromOffset = tzt.getFrom()->getRawOffset() + tzt.getFrom()->getDSTSavings();
631                     int32_t toOffset = tzt.getTo()->getRawOffset() + tzt.getTo()->getDSTSavings();
632                     int32_t delta = toOffset - fromOffset;
633                     if (delta < 0) {
634                         UBool isDstDecession = tzt.getFrom()->getDSTSavings() > 0 && tzt.getTo()->getDSTSavings() == 0;
635                         testTimes[0] = t + delta - 1;
636                         expectedRoundTrip[0] = TRUE;
637                         testTimes[1] = t + delta;
638                         expectedRoundTrip[1] = isDstDecession ?
639                             !contains(AMBIGUOUS_DST_DECESSION, PATTERNS[patidx]) :
640                             !contains(AMBIGUOUS_NEGATIVE_SHIFT, PATTERNS[patidx]);
641                         testTimes[2] = t - 1;
642                         expectedRoundTrip[2] = isDstDecession ?
643                             !contains(AMBIGUOUS_DST_DECESSION, PATTERNS[patidx]) :
644                             !contains(AMBIGUOUS_NEGATIVE_SHIFT, PATTERNS[patidx]);
645                         testTimes[3] = t;
646                         expectedRoundTrip[3] = TRUE;
647                         testLen = 4;
648                     } else {
649                         testTimes[0] = t - 1;
650                         expectedRoundTrip[0] = TRUE;
651                         testTimes[1] = t;
652                         expectedRoundTrip[1] = TRUE;
653                         testLen = 2;
654                     }
655                 }
656                 for (int32_t testidx = 0; testidx < testLen; testidx++) {
657                     if (quick) {
658                         // reduce regular test time
659                         if (!expectedRoundTrip[testidx]) {
660                             continue;
661                         }
662                     }
663 
664                     {
665                         Mutex lock;
666                         gLocaleData->testCounts++;
667                     }
668 
669                     UnicodeString text;
670                     FieldPosition fpos(FieldPosition::DONT_CARE);
671                     sdf->format(testTimes[testidx], text, fpos);
672 
673                     UDate parsedDate = sdf->parse(text, status);
674                     if (U_FAILURE(status)) {
675                         errln((UnicodeString) "Parse failure for text=" + text + ", tzid=" + *tzid + ", locale=" + gLocaleData->locales[locidx].getName()
676                                 + ", pattern=" + PATTERNS[patidx] + ", time=" + testTimes[testidx]);
677                         status = U_ZERO_ERROR;
678                         continue;
679                     }
680 
681                     int32_t timeDiff = (int32_t)(parsedDate - testTimes[testidx]);
682                     UBool bTimeMatch = minutesOffset ?
683                         (timeDiff/60000)*60000 == 0 : timeDiff == 0;
684                     if (!bTimeMatch) {
685                         UnicodeString msg = (UnicodeString) "Time round trip failed for " + "tzid=" + *tzid
686                                 + ", locale=" + gLocaleData->locales[locidx].getName() + ", pattern=" + PATTERNS[patidx]
687                                 + ", text=" + text + ", time=" + testTimes[testidx] + ", restime=" + parsedDate + ", diff=" + (parsedDate - testTimes[testidx]);
688                         // Timebomb for TZData update
689                         if (expectedRoundTrip[testidx]
690                                 && !isSpecialTimeRoundTripCase(gLocaleData->locales[locidx].getName(), *tzid,
691                                         PATTERNS[patidx], testTimes[testidx])) {
692                             errln((UnicodeString) "FAIL: " + msg);
693                         } else if (REALLY_VERBOSE) {
694                             logln(msg);
695                         }
696                     }
697                 }
698                 tztAvail = tz->getNextTransition(t, FALSE, tzt);
699                 if (!tztAvail) {
700                     break;
701                 }
702                 if (middle) {
703                     // Test the date in the middle of two transitions.
704                     t += (int64_t) ((tzt.getTime() - t) / 2);
705                     middle = FALSE;
706                     tztAvail = FALSE;
707                 } else {
708                     t = tzt.getTime();
709                 }
710             }
711             delete tz;
712         }
713         UDate elapsedTime = Calendar::getNow() - timer;
714         gLocaleData->addTime(elapsedTime, patidx);
715         delete sdf;
716     }
717     delete tzids;
718 }
719 
720 void
TestAdoptDefaultThreadSafe(void)721 TimeZoneFormatTest::TestAdoptDefaultThreadSafe(void) {
722     ThreadPool<TimeZoneFormatTest> threads(this, threadCount, &TimeZoneFormatTest::RunAdoptDefaultThreadSafeTests);
723     threads.start();   // Start all threads.
724     threads.join();    // Wait for all threads to finish.
725 }
726 
727 static const int32_t kAdoptDefaultIteration = 10;
728 static const int32_t kCreateDefaultIteration = 5000;
729 static const int64_t kStartTime = 1557288964845;
730 
RunAdoptDefaultThreadSafeTests(int32_t threadNumber)731 void TimeZoneFormatTest::RunAdoptDefaultThreadSafeTests(int32_t threadNumber) {
732     UErrorCode status = U_ZERO_ERROR;
733     if (threadNumber % 2 == 0) {
734         for (int32_t i = 0; i < kAdoptDefaultIteration; i++) {
735             std::unique_ptr<icu::StringEnumeration> timezones(
736                     icu::TimeZone::createEnumeration(status));
737             // Fails with missing data.
738             if (U_FAILURE(status)) {
739                 dataerrln("Unable to create TimeZone enumeration");
740                 return;
741             }
742             while (const icu::UnicodeString* timezone = timezones->snext(status)) {
743                 status = U_ZERO_ERROR;
744                 icu::TimeZone::adoptDefault(icu::TimeZone::createTimeZone(*timezone));
745             }
746         }
747     } else {
748         int32_t rawOffset;
749         int32_t dstOffset;
750         int64_t date = kStartTime;
751         for (int32_t i = 0; i < kCreateDefaultIteration; i++) {
752             date += 6000 * i;
753             std::unique_ptr<icu::TimeZone> tz(icu::TimeZone::createDefault());
754             status = U_ZERO_ERROR;
755             tz->getOffset(static_cast<UDate>(date), TRUE, rawOffset, dstOffset, status);
756             status = U_ZERO_ERROR;
757             tz->getOffset(static_cast<UDate>(date), FALSE, rawOffset, dstOffset, status);
758         }
759     }
760 }
761 
762 typedef struct {
763     const char*     text;
764     int32_t         inPos;
765     const char*     locale;
766     UTimeZoneFormatStyle    style;
767     uint32_t        parseOptions;
768     const char*     expected;
769     int32_t         outPos;
770     UTimeZoneFormatTimeType timeType;
771 } ParseTestData;
772 
773 void
TestParse(void)774 TimeZoneFormatTest::TestParse(void) {
775     const ParseTestData DATA[] = {
776         //   text               inPos   locale      style
777         //      parseOptions                        expected            outPos  timeType
778             {"Z",               0,      "en_US",    UTZFMT_STYLE_ISO_EXTENDED_FULL,
779                 UTZFMT_PARSE_OPTION_NONE,           "Etc/GMT",          1,      UTZFMT_TIME_TYPE_UNKNOWN},
780 
781             {"Z",               0,      "en_US",    UTZFMT_STYLE_SPECIFIC_LONG,
782                 UTZFMT_PARSE_OPTION_NONE,           "Etc/GMT",          1,      UTZFMT_TIME_TYPE_UNKNOWN},
783 
784             {"Zambia time",     0,      "en_US",    UTZFMT_STYLE_ISO_EXTENDED_FULL,
785                 UTZFMT_PARSE_OPTION_ALL_STYLES,     "Etc/GMT",          1,      UTZFMT_TIME_TYPE_UNKNOWN},
786 
787             {"Zambia time",     0,      "en_US",    UTZFMT_STYLE_GENERIC_LOCATION,
788                 UTZFMT_PARSE_OPTION_NONE,           "Africa/Lusaka",    11,     UTZFMT_TIME_TYPE_UNKNOWN},
789 
790             {"Zambia time",     0,      "en_US",    UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL,
791                 UTZFMT_PARSE_OPTION_ALL_STYLES,     "Africa/Lusaka",    11,     UTZFMT_TIME_TYPE_UNKNOWN},
792 
793             {"+00:00",          0,      "en_US",    UTZFMT_STYLE_ISO_EXTENDED_FULL,
794                 UTZFMT_PARSE_OPTION_NONE,           "Etc/GMT",          6,      UTZFMT_TIME_TYPE_UNKNOWN},
795 
796             {"-01:30:45",       0,      "en_US",    UTZFMT_STYLE_ISO_EXTENDED_FULL,
797                 UTZFMT_PARSE_OPTION_NONE,           "GMT-01:30:45",     9,      UTZFMT_TIME_TYPE_UNKNOWN},
798 
799             {"-7",              0,      "en_US",    UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL,
800                 UTZFMT_PARSE_OPTION_NONE,           "GMT-07:00",        2,      UTZFMT_TIME_TYPE_UNKNOWN},
801 
802             {"-2222",           0,      "en_US",    UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL,
803                 UTZFMT_PARSE_OPTION_NONE,           "GMT-22:22",        5,      UTZFMT_TIME_TYPE_UNKNOWN},
804 
805             {"-3333",           0,      "en_US",    UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL,
806                 UTZFMT_PARSE_OPTION_NONE,           "GMT-03:33",        4,      UTZFMT_TIME_TYPE_UNKNOWN},
807 
808             {"XXX+01:30YYY",    3,      "en_US",    UTZFMT_STYLE_LOCALIZED_GMT,
809                 UTZFMT_PARSE_OPTION_NONE,           "GMT+01:30",        9,      UTZFMT_TIME_TYPE_UNKNOWN},
810 
811             {"GMT0",            0,      "en_US",    UTZFMT_STYLE_SPECIFIC_SHORT,
812                 UTZFMT_PARSE_OPTION_NONE,           "Etc/GMT",          3,      UTZFMT_TIME_TYPE_UNKNOWN},
813 
814             {"EST",             0,      "en_US",    UTZFMT_STYLE_SPECIFIC_SHORT,
815                 UTZFMT_PARSE_OPTION_NONE,           "America/New_York", 3,      UTZFMT_TIME_TYPE_STANDARD},
816 
817             {"ESTx",            0,      "en_US",    UTZFMT_STYLE_SPECIFIC_SHORT,
818                 UTZFMT_PARSE_OPTION_NONE,           "America/New_York", 3,      UTZFMT_TIME_TYPE_STANDARD},
819 
820             {"EDTx",            0,      "en_US",    UTZFMT_STYLE_SPECIFIC_SHORT,
821                 UTZFMT_PARSE_OPTION_NONE,           "America/New_York", 3,      UTZFMT_TIME_TYPE_DAYLIGHT},
822 
823             {"EST",             0,      "en_US",    UTZFMT_STYLE_SPECIFIC_LONG,
824                 UTZFMT_PARSE_OPTION_NONE,           NULL,               0,      UTZFMT_TIME_TYPE_UNKNOWN},
825 
826             {"EST",             0,      "en_US",    UTZFMT_STYLE_SPECIFIC_LONG,
827                 UTZFMT_PARSE_OPTION_ALL_STYLES,     "America/New_York", 3,      UTZFMT_TIME_TYPE_STANDARD},
828 
829             {"EST",             0,      "en_CA",    UTZFMT_STYLE_SPECIFIC_SHORT,
830                 UTZFMT_PARSE_OPTION_NONE,           "America/Toronto",  3,      UTZFMT_TIME_TYPE_STANDARD},
831 
832             {"CST",             0,      "en_US",    UTZFMT_STYLE_SPECIFIC_SHORT,
833                 UTZFMT_PARSE_OPTION_NONE,           "America/Chicago",  3,      UTZFMT_TIME_TYPE_STANDARD},
834 
835             {"CST",             0,      "en_GB",    UTZFMT_STYLE_SPECIFIC_SHORT,
836                 UTZFMT_PARSE_OPTION_NONE,           NULL,               0,      UTZFMT_TIME_TYPE_UNKNOWN},
837 
838             {"CST",             0,      "en_GB",    UTZFMT_STYLE_SPECIFIC_SHORT,
839                 UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS,  "America/Chicago",  3,  UTZFMT_TIME_TYPE_STANDARD},
840 
841             {"--CST--",           2,    "en_GB",    UTZFMT_STYLE_SPECIFIC_SHORT,
842                 UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS,  "America/Chicago",  5,  UTZFMT_TIME_TYPE_STANDARD},
843 
844             {"CST",             0,      "zh_CN",    UTZFMT_STYLE_SPECIFIC_SHORT,
845                 UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS,  "Asia/Shanghai",    3,  UTZFMT_TIME_TYPE_STANDARD},
846 
847             {"AEST",            0,      "en_AU",    UTZFMT_STYLE_SPECIFIC_SHORT,
848                 UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS,  "Australia/Sydney", 4,  UTZFMT_TIME_TYPE_STANDARD},
849 
850             {"AST",             0,      "ar_SA",    UTZFMT_STYLE_SPECIFIC_SHORT,
851                 UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS,  "Asia/Riyadh",      3,  UTZFMT_TIME_TYPE_STANDARD},
852 
853             {"AQTST",           0,      "en",       UTZFMT_STYLE_SPECIFIC_LONG,
854                 UTZFMT_PARSE_OPTION_NONE,           NULL,               0,      UTZFMT_TIME_TYPE_UNKNOWN},
855 
856             {"AQTST",           0,      "en",       UTZFMT_STYLE_SPECIFIC_LONG,
857                 UTZFMT_PARSE_OPTION_ALL_STYLES,     NULL,               0,      UTZFMT_TIME_TYPE_UNKNOWN},
858 
859             {"AQTST",           0,      "en",       UTZFMT_STYLE_SPECIFIC_LONG,
860                 UTZFMT_PARSE_OPTION_ALL_STYLES | UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS, "Asia/Aqtobe",  5,  UTZFMT_TIME_TYPE_DAYLIGHT},
861 
862             {NULL,              0,      NULL,       UTZFMT_STYLE_GENERIC_LOCATION,
863                 UTZFMT_PARSE_OPTION_NONE,           NULL,               0,      UTZFMT_TIME_TYPE_UNKNOWN}
864     };
865 
866     for (int32_t i = 0; DATA[i].text; i++) {
867         UErrorCode status = U_ZERO_ERROR;
868         LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(Locale(DATA[i].locale), status));
869         if (U_FAILURE(status)) {
870             dataerrln("Fail TimeZoneFormat::createInstance: %s", u_errorName(status));
871             continue;
872         }
873         UTimeZoneFormatTimeType ttype = UTZFMT_TIME_TYPE_UNKNOWN;
874         ParsePosition pos(DATA[i].inPos);
875         TimeZone* tz = tzfmt->parse(DATA[i].style, DATA[i].text, pos, DATA[i].parseOptions, &ttype);
876 
877         UnicodeString errMsg;
878         if (tz) {
879             UnicodeString outID;
880             tz->getID(outID);
881             if (outID != UnicodeString(DATA[i].expected)) {
882                 errMsg = (UnicodeString)"Time zone ID: " + outID + " - expected: " + DATA[i].expected;
883             } else if (pos.getIndex() != DATA[i].outPos) {
884                 errMsg = (UnicodeString)"Parsed pos: " + pos.getIndex() + " - expected: " + DATA[i].outPos;
885             } else if (ttype != DATA[i].timeType) {
886                 errMsg = (UnicodeString)"Time type: " + ttype + " - expected: " + DATA[i].timeType;
887             }
888             delete tz;
889         } else {
890             if (DATA[i].expected) {
891                 errMsg = (UnicodeString)"Parse failure - expected: " + DATA[i].expected;
892             }
893         }
894         if (errMsg.length() > 0) {
895             errln((UnicodeString)"Fail: " + errMsg + " [text=" + DATA[i].text + ", pos=" + DATA[i].inPos + ", style=" + DATA[i].style + "]");
896         }
897     }
898 }
899 
900 void
TestISOFormat(void)901 TimeZoneFormatTest::TestISOFormat(void) {
902     const int32_t OFFSET[] = {
903         0,          // 0
904         999,        // 0.999s
905         -59999,     // -59.999s
906         60000,      // 1m
907         -77777,     // -1m 17.777s
908         1800000,    // 30m
909         -3600000,   // -1h
910         36000000,   // 10h
911         -37800000,  // -10h 30m
912         -37845000,  // -10h 30m 45s
913         108000000,  // 30h
914     };
915 
916     const char* ISO_STR[][11] = {
917         // 0
918         {
919             "Z", "Z", "Z", "Z", "Z",
920             "+00", "+0000", "+00:00", "+0000", "+00:00",
921             "+0000"
922         },
923         // 999
924         {
925             "Z", "Z", "Z", "Z", "Z",
926             "+00", "+0000", "+00:00", "+0000", "+00:00",
927             "+0000"
928         },
929         // -59999
930         {
931             "Z", "Z", "Z", "-000059", "-00:00:59",
932             "+00", "+0000", "+00:00", "-000059", "-00:00:59",
933             "-000059"
934         },
935         // 60000
936         {
937             "+0001", "+0001", "+00:01", "+0001", "+00:01",
938             "+0001", "+0001", "+00:01", "+0001", "+00:01",
939             "+0001"
940         },
941         // -77777
942         {
943             "-0001", "-0001", "-00:01", "-000117", "-00:01:17",
944             "-0001", "-0001", "-00:01", "-000117", "-00:01:17",
945             "-000117"
946         },
947         // 1800000
948         {
949             "+0030", "+0030", "+00:30", "+0030", "+00:30",
950             "+0030", "+0030", "+00:30", "+0030", "+00:30",
951             "+0030"
952         },
953         // -3600000
954         {
955             "-01", "-0100", "-01:00", "-0100", "-01:00",
956             "-01", "-0100", "-01:00", "-0100", "-01:00",
957             "-0100"
958         },
959         // 36000000
960         {
961             "+10", "+1000", "+10:00", "+1000", "+10:00",
962             "+10", "+1000", "+10:00", "+1000", "+10:00",
963             "+1000"
964         },
965         // -37800000
966         {
967             "-1030", "-1030", "-10:30", "-1030", "-10:30",
968             "-1030", "-1030", "-10:30", "-1030", "-10:30",
969             "-1030"
970         },
971         // -37845000
972         {
973             "-1030", "-1030", "-10:30", "-103045", "-10:30:45",
974             "-1030", "-1030", "-10:30", "-103045", "-10:30:45",
975             "-103045"
976         },
977         // 108000000
978         {
979             0, 0, 0, 0, 0,
980             0, 0, 0, 0, 0,
981             0
982         }
983     };
984 
985     const char* PATTERN[] = {
986         "X", "XX", "XXX", "XXXX", "XXXXX",
987         "x", "xx", "xxx", "xxxx", "xxxxx",
988         "Z", // equivalent to "xxxx"
989         0
990     };
991 
992     const int32_t MIN_OFFSET_UNIT[] = {
993         60000, 60000, 60000, 1000, 1000,
994         60000, 60000, 60000, 1000, 1000,
995         1000,
996     };
997 
998     // Formatting
999     UErrorCode status = U_ZERO_ERROR;
1000     LocalPointer<SimpleDateFormat> sdf(new SimpleDateFormat(status), status);
1001     if (U_FAILURE(status)) {
1002         dataerrln("Fail new SimpleDateFormat: %s", u_errorName(status));
1003         return;
1004     }
1005     UDate d = Calendar::getNow();
1006 
1007     for (uint32_t i = 0; i < UPRV_LENGTHOF(OFFSET); i++) {
1008         SimpleTimeZone* tz = new SimpleTimeZone(OFFSET[i], UnicodeString("Zone Offset:") + OFFSET[i] + "ms");
1009         sdf->adoptTimeZone(tz);
1010         for (int32_t j = 0; PATTERN[j] != 0; j++) {
1011             sdf->applyPattern(UnicodeString(PATTERN[j]));
1012             UnicodeString result;
1013             sdf->format(d, result);
1014 
1015             if (ISO_STR[i][j]) {
1016                 if (result != UnicodeString(ISO_STR[i][j])) {
1017                     errln((UnicodeString)"FAIL: pattern=" + PATTERN[j] + ", offset=" + OFFSET[i] + " -> "
1018                         + result + " (expected: " + ISO_STR[i][j] + ")");
1019                 }
1020             } else {
1021                 // Offset out of range
1022                 // Note: for now, there is no way to propagate the error status through
1023                 // the SimpleDateFormat::format above.
1024                 if (result.length() > 0) {
1025                     errln((UnicodeString)"FAIL: Non-Empty result for pattern=" + PATTERN[j] + ", offset=" + OFFSET[i]
1026                         + " (expected: empty result)");
1027                 }
1028             }
1029         }
1030     }
1031 
1032     // Parsing
1033     LocalPointer<Calendar> outcal(Calendar::createInstance(status));
1034     if (U_FAILURE(status)) {
1035         dataerrln("Fail new Calendar: %s", u_errorName(status));
1036         return;
1037     }
1038     for (int32_t i = 0; ISO_STR[i][0] != NULL; i++) {
1039         for (int32_t j = 0; PATTERN[j] != 0; j++) {
1040             if (ISO_STR[i][j] == 0) {
1041                 continue;
1042             }
1043             ParsePosition pos(0);
1044             SimpleTimeZone* bogusTZ = new SimpleTimeZone(-1, UnicodeString("Zone Offset: -1ms"));
1045             outcal->adoptTimeZone(bogusTZ);
1046             sdf->applyPattern(PATTERN[j]);
1047 
1048             sdf->parse(UnicodeString(ISO_STR[i][j]), *(outcal.getAlias()), pos);
1049 
1050             if (pos.getIndex() != (int32_t)uprv_strlen(ISO_STR[i][j])) {
1051                 errln((UnicodeString)"FAIL: Failed to parse the entire input string: " + ISO_STR[i][j]);
1052             }
1053 
1054             const TimeZone& outtz = outcal->getTimeZone();
1055             int32_t outOffset = outtz.getRawOffset();
1056             int32_t adjustedOffset = OFFSET[i] / MIN_OFFSET_UNIT[j] * MIN_OFFSET_UNIT[j];
1057             if (outOffset != adjustedOffset) {
1058                 errln((UnicodeString)"FAIL: Incorrect offset:" + outOffset + "ms for input string: " + ISO_STR[i][j]
1059                     + " (expected:" + adjustedOffset + "ms)");
1060             }
1061         }
1062     }
1063 }
1064 
1065 
1066 typedef struct {
1067     const char*     locale;
1068     const char*     tzid;
1069     UDate           date;
1070     UTimeZoneFormatStyle    style;
1071     const char*     expected;
1072     UTimeZoneFormatTimeType timeType;
1073 } FormatTestData;
1074 
1075 void
TestFormat(void)1076 TimeZoneFormatTest::TestFormat(void) {
1077     UDate dateJan = 1358208000000.0;    // 2013-01-15T00:00:00Z
1078     UDate dateJul = 1373846400000.0;    // 2013-07-15T00:00:00Z
1079 
1080     const FormatTestData DATA[] = {
1081         {
1082             "en",
1083             "America/Los_Angeles",
1084             dateJan,
1085             UTZFMT_STYLE_GENERIC_LOCATION,
1086             "Los Angeles Time",
1087             UTZFMT_TIME_TYPE_UNKNOWN
1088         },
1089         {
1090             "en",
1091             "America/Los_Angeles",
1092             dateJan,
1093             UTZFMT_STYLE_GENERIC_LONG,
1094             "Pacific Time",
1095             UTZFMT_TIME_TYPE_UNKNOWN
1096         },
1097         {
1098             "en",
1099             "America/Los_Angeles",
1100             dateJan,
1101             UTZFMT_STYLE_SPECIFIC_LONG,
1102             "Pacific Standard Time",
1103             UTZFMT_TIME_TYPE_STANDARD
1104         },
1105         {
1106             "en",
1107             "America/Los_Angeles",
1108             dateJul,
1109             UTZFMT_STYLE_SPECIFIC_LONG,
1110             "Pacific Daylight Time",
1111             UTZFMT_TIME_TYPE_DAYLIGHT
1112         },
1113         {
1114             "ja",
1115             "America/Los_Angeles",
1116             dateJan,
1117             UTZFMT_STYLE_ZONE_ID,
1118             "America/Los_Angeles",
1119             UTZFMT_TIME_TYPE_UNKNOWN
1120         },
1121         {
1122             "fr",
1123             "America/Los_Angeles",
1124             dateJul,
1125             UTZFMT_STYLE_ZONE_ID_SHORT,
1126             "uslax",
1127             UTZFMT_TIME_TYPE_UNKNOWN
1128         },
1129         {
1130             "en",
1131             "America/Los_Angeles",
1132             dateJan,
1133             UTZFMT_STYLE_EXEMPLAR_LOCATION,
1134             "Los Angeles",
1135             UTZFMT_TIME_TYPE_UNKNOWN
1136         },
1137 
1138         {
1139             "ja",
1140             "Asia/Tokyo",
1141             dateJan,
1142             UTZFMT_STYLE_GENERIC_LONG,
1143             "\\u65E5\\u672C\\u6A19\\u6E96\\u6642",
1144             UTZFMT_TIME_TYPE_UNKNOWN
1145         },
1146 
1147         {0, 0, 0.0, UTZFMT_STYLE_GENERIC_LOCATION, 0, UTZFMT_TIME_TYPE_UNKNOWN}
1148     };
1149 
1150     for (int32_t i = 0; DATA[i].locale; i++) {
1151         UErrorCode status = U_ZERO_ERROR;
1152         LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(Locale(DATA[i].locale), status));
1153         if (U_FAILURE(status)) {
1154             dataerrln("Fail TimeZoneFormat::createInstance: %s", u_errorName(status));
1155             continue;
1156         }
1157 
1158         LocalPointer<TimeZone> tz(TimeZone::createTimeZone(DATA[i].tzid));
1159         UnicodeString out;
1160         UTimeZoneFormatTimeType timeType;
1161 
1162         tzfmt->format(DATA[i].style, *(tz.getAlias()), DATA[i].date, out, &timeType);
1163         UnicodeString expected(DATA[i].expected, -1, US_INV);
1164         expected = expected.unescape();
1165 
1166         assertEquals(UnicodeString("Format result for ") + DATA[i].tzid + " (Test Case " + i + ")", expected, out);
1167         if (DATA[i].timeType != timeType) {
1168             dataerrln(UnicodeString("Formatted time zone type (Test Case ") + i + "), returned="
1169                 + timeType + ", expected=" + DATA[i].timeType);
1170         }
1171     }
1172 }
1173 
1174 void
TestFormatTZDBNames(void)1175 TimeZoneFormatTest::TestFormatTZDBNames(void) {
1176     UDate dateJan = 1358208000000.0;    // 2013-01-15T00:00:00Z
1177     UDate dateJul = 1373846400000.0;    // 2013-07-15T00:00:00Z
1178 
1179     const FormatTestData DATA[] = {
1180         {
1181             "en",
1182             "America/Chicago",
1183             dateJan,
1184             UTZFMT_STYLE_SPECIFIC_SHORT,
1185             "CST",
1186             UTZFMT_TIME_TYPE_STANDARD
1187         },
1188         {
1189             "en",
1190             "Asia/Shanghai",
1191             dateJan,
1192             UTZFMT_STYLE_SPECIFIC_SHORT,
1193             "CST",
1194             UTZFMT_TIME_TYPE_STANDARD
1195         },
1196         {
1197             "zh_Hans",
1198             "Asia/Shanghai",
1199             dateJan,
1200             UTZFMT_STYLE_SPECIFIC_SHORT,
1201             "CST",
1202             UTZFMT_TIME_TYPE_STANDARD
1203         },
1204         {
1205             "en",
1206             "America/Los_Angeles",
1207             dateJul,
1208             UTZFMT_STYLE_SPECIFIC_LONG,
1209             "GMT-07:00",    // No long display names
1210             UTZFMT_TIME_TYPE_DAYLIGHT
1211         },
1212         {
1213             "ja",
1214             "America/Los_Angeles",
1215             dateJul,
1216             UTZFMT_STYLE_SPECIFIC_SHORT,
1217             "PDT",
1218             UTZFMT_TIME_TYPE_DAYLIGHT
1219         },
1220         {
1221             "en",
1222             "Australia/Sydney",
1223             dateJan,
1224             UTZFMT_STYLE_SPECIFIC_SHORT,
1225             "AEDT",
1226             UTZFMT_TIME_TYPE_DAYLIGHT
1227         },
1228         {
1229             "en",
1230             "Australia/Sydney",
1231             dateJul,
1232             UTZFMT_STYLE_SPECIFIC_SHORT,
1233             "AEST",
1234             UTZFMT_TIME_TYPE_STANDARD
1235         },
1236 
1237         {0, 0, 0.0, UTZFMT_STYLE_GENERIC_LOCATION, 0, UTZFMT_TIME_TYPE_UNKNOWN}
1238     };
1239 
1240     for (int32_t i = 0; DATA[i].locale; i++) {
1241         UErrorCode status = U_ZERO_ERROR;
1242         Locale loc(DATA[i].locale);
1243         LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(loc, status));
1244         if (U_FAILURE(status)) {
1245             dataerrln("Fail TimeZoneFormat::createInstance: %s", u_errorName(status));
1246             continue;
1247         }
1248         TimeZoneNames *tzdbNames = TimeZoneNames::createTZDBInstance(loc, status);
1249         if (U_FAILURE(status)) {
1250             dataerrln("Fail TimeZoneNames::createTZDBInstance: %s", u_errorName(status));
1251             continue;
1252         }
1253         tzfmt->adoptTimeZoneNames(tzdbNames);
1254 
1255         LocalPointer<TimeZone> tz(TimeZone::createTimeZone(DATA[i].tzid));
1256         UnicodeString out;
1257         UTimeZoneFormatTimeType timeType;
1258 
1259         tzfmt->format(DATA[i].style, *(tz.getAlias()), DATA[i].date, out, &timeType);
1260         UnicodeString expected(DATA[i].expected, -1, US_INV);
1261         expected = expected.unescape();
1262 
1263         assertEquals(UnicodeString("Format result for ") + DATA[i].tzid + " (Test Case " + i + ")", expected, out);
1264         if (DATA[i].timeType != timeType) {
1265             dataerrln(UnicodeString("Formatted time zone type (Test Case ") + i + "), returned="
1266                 + timeType + ", expected=" + DATA[i].timeType);
1267         }
1268     }
1269 }
1270 
1271 void
TestFormatCustomZone(void)1272 TimeZoneFormatTest::TestFormatCustomZone(void) {
1273     struct {
1274         const char* id;
1275         int32_t offset;
1276         const char* expected;
1277     } TESTDATA[] = {
1278         { "abc", 3600000, "GMT+01:00" },                    // unknown ID
1279         { "$abc", -3600000, "GMT-01:00" },                 // unknown, with ASCII variant char '$'
1280         { "\\u00c1\\u00df\\u00c7", 5400000, "GMT+01:30"},    // unknown, with non-ASCII chars
1281         { 0, 0, 0 }
1282     };
1283 
1284     UDate now = Calendar::getNow();
1285 
1286     for (int32_t i = 0; ; i++) {
1287         const char *id = TESTDATA[i].id;
1288         if (id == 0) {
1289             break;
1290         }
1291         UnicodeString tzid = UnicodeString(id, -1, US_INV).unescape();
1292         SimpleTimeZone tz(TESTDATA[i].offset, tzid);
1293 
1294         UErrorCode status = U_ZERO_ERROR;
1295         LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(Locale("en"), status));
1296         if (tzfmt.isNull()) {
1297             dataerrln("FAIL: TimeZoneFormat::createInstance failed for en");
1298             return;
1299         }
1300         UnicodeString tzstr;
1301         UnicodeString expected = UnicodeString(TESTDATA[i].expected, -1, US_INV).unescape();
1302 
1303         tzfmt->format(UTZFMT_STYLE_SPECIFIC_LONG, tz, now, tzstr, NULL);
1304         assertEquals(UnicodeString("Format result for ") + tzid, expected, tzstr);
1305     }
1306 }
1307 
1308 void
TestFormatTZDBNamesAllZoneCoverage(void)1309 TimeZoneFormatTest::TestFormatTZDBNamesAllZoneCoverage(void) {
1310     UErrorCode status = U_ZERO_ERROR;
1311     LocalPointer<StringEnumeration> tzids(TimeZone::createEnumeration(status));
1312     if (U_FAILURE(status)) {
1313         dataerrln("Unable to create TimeZone enumeration", __FILE__, __LINE__);
1314         return;
1315     }
1316     const UnicodeString *tzid;
1317     LocalPointer<TimeZoneNames> tzdbNames(TimeZoneNames::createTZDBInstance(Locale("en"), status));
1318     UDate now = Calendar::getNow();
1319     UnicodeString mzId;
1320     UnicodeString name;
1321     while ((tzid = tzids->snext(status))) {
1322         logln("Zone: " + *tzid);
1323         LocalPointer<TimeZone> tz(TimeZone::createTimeZone(*tzid));
1324         tzdbNames->getMetaZoneID(*tzid, now, mzId);
1325         if (mzId.isBogus()) {
1326             logln((UnicodeString)"Meta zone: <not available>");
1327         } else {
1328             logln((UnicodeString)"Meta zone: " + mzId);
1329         }
1330 
1331         // mzID could be bogus here
1332         tzdbNames->getMetaZoneDisplayName(mzId, UTZNM_SHORT_STANDARD, name);
1333         // name could be bogus here
1334         if (name.isBogus()) {
1335             logln((UnicodeString)"Meta zone short standard name: <not available>");
1336         }
1337         else {
1338             logln((UnicodeString)"Meta zone short standard name: " + name);
1339         }
1340 
1341         tzdbNames->getMetaZoneDisplayName(mzId, UTZNM_SHORT_DAYLIGHT, name);
1342         // name could be bogus here
1343         if (name.isBogus()) {
1344             logln((UnicodeString)"Meta zone short daylight name: <not available>");
1345         }
1346         else {
1347             logln((UnicodeString)"Meta zone short daylight name: " + name);
1348         }
1349     }
1350 }
1351 
1352 // Test for checking parse results are same for a same input string
1353 // using SimpleDateFormat initialized with different regional locales - US and Belize.
1354 // Belize did not observe DST from 1968 to 1973, 1975 to 1982, and 1985 and later.
1355 void
TestCentralTime(void)1356 TimeZoneFormatTest::TestCentralTime(void) {
1357     UnicodeString pattern(u"y-MM-dd HH:mm:ss zzzz");
1358     UnicodeString testInputs[] = {
1359         // 1970-01-01 - Chicago:STD/Belize:STD
1360         u"1970-01-01 12:00:00 Central Standard Time",
1361         u"1970-01-01 12:00:00 Central Daylight Time",
1362 
1363         // 1970-07-01 - Chicago:STD/Belize:STD
1364         u"1970-07-01 12:00:00 Central Standard Time",
1365         u"1970-07-01 12:00:00 Central Daylight Time",
1366 
1367         // 1974-01-01 - Chicago:STD/Belize:DST
1368         u"1974-01-01 12:00:00 Central Standard Time",
1369         u"1974-01-01 12:00:00 Central Daylight Time",
1370 
1371         // 2020-01-01 - Chicago:STD/Belize:STD
1372         u"2020-01-01 12:00:00 Central Standard Time",
1373         u"2020-01-01 12:00:00 Central Daylight Time",
1374 
1375         // 2020-01-01 - Chicago:DST/Belize:STD
1376         u"2020-07-01 12:00:00 Central Standard Time",
1377         u"2020-07-01 12:00:00 Central Daylight Time",
1378 
1379         u""
1380     };
1381 
1382     UErrorCode status = U_ZERO_ERROR;
1383     SimpleDateFormat sdfUS(pattern, Locale("en_US"), status);
1384     SimpleDateFormat sdfBZ(pattern, Locale("en_BZ"), status);
1385     if (U_FAILURE(status)) {
1386         errln("Failed to create SimpleDateFormat instance");
1387         return;
1388     }
1389 
1390     for (int32_t i = 0; !testInputs[i].isEmpty(); i++) {
1391         UDate dUS = sdfUS.parse(testInputs[i], status);
1392         UDate dBZ = sdfBZ.parse(testInputs[i], status);
1393 
1394         if (U_FAILURE(status)) {
1395             errln((UnicodeString)"Failed to parse date string: " + testInputs[i]);
1396             continue;
1397         }
1398 
1399         if (dUS != dBZ) {
1400             errln((UnicodeString)"Parse results should be same for input: " + testInputs[i]);
1401         }
1402     }
1403 }
1404 #endif /* #if !UCONFIG_NO_FORMATTING */
1405