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