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