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