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
33static 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
58static const UChar ETC_UNKNOWN[] = {0x45, 0x74, 0x63, 0x2F, 0x55, 0x6E, 0x6B, 0x6E, 0x6F, 0x77, 0x6E, 0};
59
60static const UChar ETC_SLASH[] = { 0x45, 0x74, 0x63, 0x2F, 0 }; // "Etc/"
61static const UChar SYSTEMV_SLASH[] = { 0x53, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x56, 0x2F, 0 }; // "SystemV/
62static const UChar RIYADH8[] = { 0x52, 0x69, 0x79, 0x61, 0x64, 0x68, 0x38, 0 }; // "Riyadh8"
63
64static 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
73void
74TimeZoneFormatTest::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
92void
93TimeZoneFormatTest::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.
352static 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.
396struct 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
407    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
414    void resetTestIteration() {
415        localeIndex = -1;
416        patternIndex = UPRV_LENGTHOF(PATTERNS);
417        numDone = 0;
418    }
419
420    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
436    void addTime(UDate amount, int32_t patIdx) {
437        Mutex lock;
438        U_ASSERT(patIdx < UPRV_LENGTHOF(PATTERNS));
439        times[patIdx] += amount;
440    }
441};
442
443static LocaleData *gLocaleData = NULL;
444
445void
446TimeZoneFormatTest::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//
526void 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
715typedef 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
726void
727TimeZoneFormatTest::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
853void
854TimeZoneFormatTest::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
1019typedef 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
1028void
1029TimeZoneFormatTest::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
1127void
1128TimeZoneFormatTest::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
1224void
1225TimeZoneFormatTest::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
1261void
1262TimeZoneFormatTest::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