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