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