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