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