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        default: name = ""; break;
79    }
80}
81
82void
83TimeZoneFormatTest::TestTimeZoneRoundTrip(void) {
84    UErrorCode status = U_ZERO_ERROR;
85
86    SimpleTimeZone unknownZone(-31415, ETC_UNKNOWN);
87    int32_t badDstOffset = -1234;
88    int32_t badZoneOffset = -2345;
89
90    int32_t testDateData[][3] = {
91        {2007, 1, 15},
92        {2007, 6, 15},
93        {1990, 1, 15},
94        {1990, 6, 15},
95        {1960, 1, 15},
96        {1960, 6, 15},
97    };
98
99    Calendar *cal = Calendar::createInstance(TimeZone::createTimeZone((UnicodeString)"UTC"), status);
100    if (U_FAILURE(status)) {
101        dataerrln("Calendar::createInstance failed: %s", u_errorName(status));
102        return;
103    }
104
105    // Set up rule equivalency test range
106    UDate low, high;
107    cal->set(1900, UCAL_JANUARY, 1);
108    low = cal->getTime(status);
109    cal->set(2040, UCAL_JANUARY, 1);
110    high = cal->getTime(status);
111    if (U_FAILURE(status)) {
112        errln("getTime failed");
113        return;
114    }
115
116    // Set up test dates
117    UDate DATES[(sizeof(testDateData)/sizeof(int32_t))/3];
118    const int32_t nDates = (sizeof(testDateData)/sizeof(int32_t))/3;
119    cal->clear();
120    for (int32_t i = 0; i < nDates; i++) {
121        cal->set(testDateData[i][0], testDateData[i][1], testDateData[i][2]);
122        DATES[i] = cal->getTime(status);
123        if (U_FAILURE(status)) {
124            errln("getTime failed");
125            return;
126        }
127    }
128
129    // Set up test locales
130    const Locale testLocales[] = {
131        Locale("en"),
132        Locale("en_CA"),
133        Locale("fr"),
134        Locale("zh_Hant")
135    };
136
137    const Locale *LOCALES;
138    int32_t nLocales;
139
140    if (quick) {
141        LOCALES = testLocales;
142        nLocales = sizeof(testLocales)/sizeof(Locale);
143    } else {
144        LOCALES = Locale::getAvailableLocales(nLocales);
145    }
146
147    StringEnumeration *tzids = TimeZone::createEnumeration();
148    int32_t inRaw, inDst;
149    int32_t outRaw, outDst;
150
151    // Run the roundtrip test
152    for (int32_t locidx = 0; locidx < nLocales; locidx++) {
153        UnicodeString localGMTString;
154        SimpleDateFormat gmtFmt(UnicodeString("ZZZZ"), LOCALES[locidx], status);
155        if (U_FAILURE(status)) {
156            dataerrln("Error creating SimpleDateFormat - %s", u_errorName(status));
157            continue;
158        }
159        gmtFmt.setTimeZone(*TimeZone::getGMT());
160        gmtFmt.format(0.0, localGMTString);
161
162        for (int32_t patidx = 0; patidx < NUM_PATTERNS; patidx++) {
163
164            SimpleDateFormat *sdf = new SimpleDateFormat((UnicodeString)PATTERNS[patidx], LOCALES[locidx], status);
165            if (U_FAILURE(status)) {
166                dataerrln((UnicodeString)"new SimpleDateFormat failed for pattern " +
167                    PATTERNS[patidx] + " for locale " + LOCALES[locidx].getName() + " - " + u_errorName(status));
168                status = U_ZERO_ERROR;
169                continue;
170            }
171
172            tzids->reset(status);
173            const UnicodeString *tzid;
174            while ((tzid = tzids->snext(status))) {
175                TimeZone *tz = TimeZone::createTimeZone(*tzid);
176
177                for (int32_t datidx = 0; datidx < nDates; datidx++) {
178                    UnicodeString tzstr;
179                    FieldPosition fpos(0);
180                    // Format
181                    sdf->setTimeZone(*tz);
182                    sdf->format(DATES[datidx], tzstr, fpos);
183
184                    // Before parse, set unknown zone to SimpleDateFormat instance
185                    // just for making sure that it does not depends on the time zone
186                    // originally set.
187                    sdf->setTimeZone(unknownZone);
188
189                    // Parse
190                    ParsePosition pos(0);
191                    Calendar *outcal = Calendar::createInstance(unknownZone, status);
192                    if (U_FAILURE(status)) {
193                        errln("Failed to create an instance of calendar for receiving parse result.");
194                        status = U_ZERO_ERROR;
195                        continue;
196                    }
197                    outcal->set(UCAL_DST_OFFSET, badDstOffset);
198                    outcal->set(UCAL_ZONE_OFFSET, badZoneOffset);
199
200                    sdf->parse(tzstr, *outcal, pos);
201
202                    // Check the result
203                    const TimeZone &outtz = outcal->getTimeZone();
204                    UnicodeString outtzid;
205                    outtz.getID(outtzid);
206
207                    tz->getOffset(DATES[datidx], false, inRaw, inDst, status);
208                    if (U_FAILURE(status)) {
209                        errln((UnicodeString)"Failed to get offsets from time zone" + *tzid);
210                        status = U_ZERO_ERROR;
211                    }
212                    outtz.getOffset(DATES[datidx], false, outRaw, outDst, status);
213                    if (U_FAILURE(status)) {
214                        errln((UnicodeString)"Failed to get offsets from time zone" + outtzid);
215                        status = U_ZERO_ERROR;
216                    }
217
218                    if (uprv_strcmp(PATTERNS[patidx], "V") == 0) {
219                        // Short zone ID - should support roundtrip for canonical CLDR IDs
220                        UnicodeString canonicalID;
221                        TimeZone::getCanonicalID(*tzid, canonicalID, status);
222                        if (U_FAILURE(status)) {
223                            // Uknown ID - we should not get here
224                            errln((UnicodeString)"Unknown ID " + *tzid);
225                            status = U_ZERO_ERROR;
226                        } else if (outtzid != canonicalID) {
227                            if (outtzid.compare(ETC_UNKNOWN, -1) == 0) {
228                                // Note that some zones like Asia/Riyadh87 does not have
229                                // short zone ID and "unk" is used as fallback
230                                logln((UnicodeString)"Canonical round trip failed (probably as expected); tz=" + *tzid
231                                        + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
232                                        + ", time=" + DATES[datidx] + ", str=" + tzstr
233                                        + ", outtz=" + outtzid);
234                            } else {
235                                errln((UnicodeString)"Canonical round trip failed; tz=" + *tzid
236                                    + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
237                                    + ", time=" + DATES[datidx] + ", str=" + tzstr
238                                    + ", outtz=" + outtzid);
239                            }
240                        }
241                    } else if (uprv_strcmp(PATTERNS[patidx], "VV") == 0) {
242                        // Zone ID - full roundtrip support
243                        if (outtzid != *tzid) {
244                            errln((UnicodeString)"Zone ID round trip failued; tz="  + *tzid
245                                + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
246                                + ", time=" + DATES[datidx] + ", str=" + tzstr
247                                + ", outtz=" + outtzid);
248                        }
249                    } else if (uprv_strcmp(PATTERNS[patidx], "VVV") == 0 || uprv_strcmp(PATTERNS[patidx], "VVVV") == 0) {
250                        // Location: time zone rule must be preserved except
251                        // zones not actually associated with a specific location.
252                        // Time zones in this category do not have "/" in its ID.
253                        UnicodeString canonical;
254                        TimeZone::getCanonicalID(*tzid, canonical, status);
255                        if (U_FAILURE(status)) {
256                            // Uknown ID - we should not get here
257                            errln((UnicodeString)"Unknown ID " + *tzid);
258                            status = U_ZERO_ERROR;
259                        } else if (outtzid != canonical) {
260                            // Canonical ID did not match - check the rules
261                            if (!((BasicTimeZone*)&outtz)->hasEquivalentTransitions((BasicTimeZone&)*tz, low, high, TRUE, status)) {
262                                if (canonical.indexOf((UChar)0x27 /*'/'*/) == -1) {
263                                    // Exceptional cases, such as CET, EET, MET and WET
264                                    logln((UnicodeString)"Canonical round trip failed (as expected); tz=" + *tzid
265                                            + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
266                                            + ", time=" + DATES[datidx] + ", str=" + tzstr
267                                            + ", outtz=" + outtzid);
268                                } else {
269                                    errln((UnicodeString)"Canonical round trip failed; tz=" + *tzid
270                                        + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
271                                        + ", time=" + DATES[datidx] + ", str=" + tzstr
272                                        + ", outtz=" + outtzid);
273                                }
274                                if (U_FAILURE(status)) {
275                                    errln("hasEquivalentTransitions failed");
276                                    status = U_ZERO_ERROR;
277                                }
278                            }
279                        }
280
281                    } else {
282                        UBool isOffsetFormat = (*PATTERNS[patidx] == 'Z'
283                                                || *PATTERNS[patidx] == 'O'
284                                                || *PATTERNS[patidx] == 'X'
285                                                || *PATTERNS[patidx] == 'x');
286                        UBool minutesOffset = FALSE;
287                        if (*PATTERNS[patidx] == 'X' || *PATTERNS[patidx] == 'x') {
288                            minutesOffset = (uprv_strlen(PATTERNS[patidx]) <= 3);
289                        }
290
291                        if (!isOffsetFormat) {
292                            // Check if localized GMT format is used as a fallback of name styles
293                            int32_t numDigits = 0;
294                            for (int n = 0; n < tzstr.length(); n++) {
295                                if (u_isdigit(tzstr.charAt(n))) {
296                                    numDigits++;
297                                }
298                            }
299                            isOffsetFormat = (numDigits > 0);
300                        }
301                        if (isOffsetFormat || tzstr == localGMTString) {
302                            // Localized GMT or ISO: total offset (raw + dst) must be preserved.
303                            int32_t inOffset = inRaw + inDst;
304                            int32_t outOffset = outRaw + outDst;
305                            int32_t diff = outOffset - inOffset;
306                            if (minutesOffset) {
307                                diff = (diff / 60000) * 60000;
308                            }
309                            if (diff != 0) {
310                                errln((UnicodeString)"Offset round trip failed; tz=" + *tzid
311                                    + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
312                                    + ", time=" + DATES[datidx] + ", str=" + tzstr
313                                    + ", inOffset=" + inOffset + ", outOffset=" + outOffset);
314                            }
315                        } else {
316                            // Specific or generic: raw offset must be preserved.
317                            if (inRaw != outRaw) {
318                                errln((UnicodeString)"Raw offset round trip failed; tz=" + *tzid
319                                    + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
320                                    + ", time=" + DATES[datidx] + ", str=" + tzstr
321                                    + ", inRawOffset=" + inRaw + ", outRawOffset=" + outRaw);
322                            }
323                        }
324                    }
325                    delete outcal;
326                }
327                delete tz;
328            }
329            delete sdf;
330        }
331    }
332    delete cal;
333    delete tzids;
334}
335
336// Special exclusions in TestTimeZoneRoundTrip.
337// These special cases do not round trip time as designed.
338static UBool isSpecialTimeRoundTripCase(const char* loc,
339                                        const UnicodeString& id,
340                                        const char* pattern,
341                                        UDate time) {
342    struct {
343        const char* loc;
344        const char* id;
345        const char* pattern;
346        UDate time;
347    } EXCLUSIONS[] = {
348        {NULL, "Asia/Chita", "zzzz", 1414252800000.0},
349        {NULL, "Asia/Chita", "vvvv", 1414252800000.0},
350        {NULL, "Asia/Srednekolymsk", "zzzz", 1414241999999.0},
351        {NULL, "Asia/Srednekolymsk", "vvvv", 1414241999999.0},
352        {NULL, NULL, NULL, U_DATE_MIN}
353    };
354
355    UBool isExcluded = FALSE;
356    for (int32_t i = 0; EXCLUSIONS[i].id != NULL; i++) {
357        if (EXCLUSIONS[i].loc == NULL || uprv_strcmp(loc, EXCLUSIONS[i].loc) == 0) {
358            if (id.compare(EXCLUSIONS[i].id) == 0) {
359                if (EXCLUSIONS[i].pattern == NULL || uprv_strcmp(pattern, EXCLUSIONS[i].pattern) == 0) {
360                    if (EXCLUSIONS[i].time == U_DATE_MIN || EXCLUSIONS[i].time == time) {
361                        isExcluded = TRUE;
362                    }
363                }
364            }
365        }
366    }
367    return isExcluded;
368}
369
370struct LocaleData {
371    int32_t index;
372    int32_t testCounts;
373    UDate *times;
374    const Locale* locales; // Static
375    int32_t nLocales; // Static
376    UBool quick; // Static
377    UDate START_TIME; // Static
378    UDate END_TIME; // Static
379    int32_t numDone;
380};
381
382class TestTimeRoundTripThread: public SimpleThread {
383public:
384    TestTimeRoundTripThread(IntlTest& tlog, LocaleData &ld, int32_t i)
385        : log(tlog), data(ld), index(i) {}
386    virtual void run() {
387        UErrorCode status = U_ZERO_ERROR;
388        UBool REALLY_VERBOSE = FALSE;
389
390        // These patterns are ambiguous at DST->STD local time overlap
391        const char* AMBIGUOUS_DST_DECESSION[] = { "v", "vvvv", "V", "VV", "VVV", "VVVV", 0 };
392
393        // These patterns are ambiguous at STD->STD/DST->DST local time overlap
394        const char* AMBIGUOUS_NEGATIVE_SHIFT[] = { "z", "zzzz", "v", "vvvv", "V", "VV", "VVV", "VVVV", 0 };
395
396        // These patterns only support integer minutes offset
397        const char* MINUTES_OFFSET[] = { "X", "XX", "XXX", "x", "xx", "xxx", 0 };
398
399        // Workaround for #6338
400        //UnicodeString BASEPATTERN("yyyy-MM-dd'T'HH:mm:ss.SSS");
401        UnicodeString BASEPATTERN("yyyy.MM.dd HH:mm:ss.SSS");
402
403        // timer for performance analysis
404        UDate timer;
405        UDate testTimes[4];
406        UBool expectedRoundTrip[4];
407        int32_t testLen = 0;
408
409        StringEnumeration *tzids = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL, NULL, NULL, status);
410        if (U_FAILURE(status)) {
411            if (status == U_MISSING_RESOURCE_ERROR) {
412                /* This error is generally caused by data not being present. However, an infinite loop will occur
413                 * because the thread thinks that the test data is never done so we should treat the data as done.
414                 */
415                log.dataerrln("TimeZone::createTimeZoneIDEnumeration failed - %s", u_errorName(status));
416                data.numDone = data.nLocales;
417            } else {
418                log.errln("TimeZone::createTimeZoneIDEnumeration failed: %s", u_errorName(status));
419            }
420            return;
421        }
422
423        int32_t locidx = -1;
424        UDate times[NUM_PATTERNS];
425        for (int32_t i = 0; i < NUM_PATTERNS; i++) {
426            times[i] = 0;
427        }
428
429        int32_t testCounts = 0;
430
431        while (true) {
432            umtx_lock(NULL); // Lock to increment the index
433            for (int32_t i = 0; i < NUM_PATTERNS; i++) {
434                data.times[i] += times[i];
435                data.testCounts += testCounts;
436            }
437            if (data.index < data.nLocales) {
438                locidx = data.index;
439                data.index++;
440            } else {
441                locidx = -1;
442            }
443            umtx_unlock(NULL); // Unlock for other threads to use
444
445            if (locidx == -1) {
446                log.logln((UnicodeString) "Thread " + index + " is done.");
447                break;
448            }
449
450            log.logln((UnicodeString) "\nThread " + index + ": Locale: " + UnicodeString(data.locales[locidx].getName()));
451
452            for (int32_t patidx = 0; patidx < NUM_PATTERNS; patidx++) {
453                log.logln((UnicodeString) "    Pattern: " + PATTERNS[patidx]);
454                times[patidx] = 0;
455
456                UnicodeString pattern(BASEPATTERN);
457                pattern.append(" ").append(PATTERNS[patidx]);
458
459                SimpleDateFormat *sdf = new SimpleDateFormat(pattern, data.locales[locidx], status);
460                if (U_FAILURE(status)) {
461                    log.errcheckln(status, (UnicodeString) "new SimpleDateFormat failed for pattern " +
462                        pattern + " for locale " + data.locales[locidx].getName() + " - " + u_errorName(status));
463                    status = U_ZERO_ERROR;
464                    continue;
465                }
466
467                UBool minutesOffset = contains(MINUTES_OFFSET, PATTERNS[patidx]);
468
469                tzids->reset(status);
470                const UnicodeString *tzid;
471
472                timer = Calendar::getNow();
473
474                while ((tzid = tzids->snext(status))) {
475                    if (uprv_strcmp(PATTERNS[patidx], "V") == 0) {
476                        // Some zones do not have short ID assigned, such as Asia/Riyadh87.
477                        // The time roundtrip will fail for such zones with pattern "V" (short zone ID).
478                        // This is expected behavior.
479                        const UChar* shortZoneID = ZoneMeta::getShortID(*tzid);
480                        if (shortZoneID == NULL) {
481                            continue;
482                        }
483                    } else if (uprv_strcmp(PATTERNS[patidx], "VVV") == 0) {
484                        // Some zones are not associated with any region, such as Etc/GMT+8.
485                        // The time roundtrip will fail for such zone with pattern "VVV" (exemplar location).
486                        // This is expected behavior.
487                        if (tzid->indexOf((UChar)0x2F) < 0 || tzid->indexOf(ETC_SLASH, -1, 0) >= 0
488                            || tzid->indexOf(SYSTEMV_SLASH, -1, 0) >= 0 || tzid->indexOf(RIYADH8, -1, 0) >= 0) {
489                            continue;
490                        }
491                    }
492
493                    // skip known issue, #11052 Ambiguous zone name - Samoa Time
494                    if (*tzid == "Pacific/Apia" && uprv_strcmp(PATTERNS[patidx], "vvvv") == 0) {
495                        continue;
496                    }
497
498                    BasicTimeZone *tz = (BasicTimeZone*) TimeZone::createTimeZone(*tzid);
499                    sdf->setTimeZone(*tz);
500
501                    UDate t = data.START_TIME;
502                    TimeZoneTransition tzt;
503                    UBool tztAvail = FALSE;
504                    UBool middle = TRUE;
505
506                    while (t < data.END_TIME) {
507                        if (!tztAvail) {
508                            testTimes[0] = t;
509                            expectedRoundTrip[0] = TRUE;
510                            testLen = 1;
511                        } else {
512                            int32_t fromOffset = tzt.getFrom()->getRawOffset() + tzt.getFrom()->getDSTSavings();
513                            int32_t toOffset = tzt.getTo()->getRawOffset() + tzt.getTo()->getDSTSavings();
514                            int32_t delta = toOffset - fromOffset;
515                            if (delta < 0) {
516                                UBool isDstDecession = tzt.getFrom()->getDSTSavings() > 0 && tzt.getTo()->getDSTSavings() == 0;
517                                testTimes[0] = t + delta - 1;
518                                expectedRoundTrip[0] = TRUE;
519                                testTimes[1] = t + delta;
520                                expectedRoundTrip[1] = isDstDecession ?
521                                    !contains(AMBIGUOUS_DST_DECESSION, PATTERNS[patidx]) :
522                                    !contains(AMBIGUOUS_NEGATIVE_SHIFT, PATTERNS[patidx]);
523                                testTimes[2] = t - 1;
524                                expectedRoundTrip[2] = isDstDecession ?
525                                    !contains(AMBIGUOUS_DST_DECESSION, PATTERNS[patidx]) :
526                                    !contains(AMBIGUOUS_NEGATIVE_SHIFT, PATTERNS[patidx]);
527                                testTimes[3] = t;
528                                expectedRoundTrip[3] = TRUE;
529                                testLen = 4;
530                            } else {
531                                testTimes[0] = t - 1;
532                                expectedRoundTrip[0] = TRUE;
533                                testTimes[1] = t;
534                                expectedRoundTrip[1] = TRUE;
535                                testLen = 2;
536                            }
537                        }
538                        for (int32_t testidx = 0; testidx < testLen; testidx++) {
539                            if (data.quick) {
540                                // reduce regular test time
541                                if (!expectedRoundTrip[testidx]) {
542                                    continue;
543                                }
544                            }
545
546                            testCounts++;
547
548                            UnicodeString text;
549                            FieldPosition fpos(0);
550                            sdf->format(testTimes[testidx], text, fpos);
551
552                            UDate parsedDate = sdf->parse(text, status);
553                            if (U_FAILURE(status)) {
554                                log.errln((UnicodeString) "Parse failure for text=" + text + ", tzid=" + *tzid + ", locale=" + data.locales[locidx].getName()
555                                        + ", pattern=" + PATTERNS[patidx] + ", time=" + testTimes[testidx]);
556                                status = U_ZERO_ERROR;
557                                continue;
558                            }
559
560                            int32_t timeDiff = (int32_t)(parsedDate - testTimes[testidx]);
561                            UBool bTimeMatch = minutesOffset ?
562                                (timeDiff/60000)*60000 == 0 : timeDiff == 0;
563                            if (!bTimeMatch) {
564                                UnicodeString msg = (UnicodeString) "Time round trip failed for " + "tzid=" + *tzid + ", locale=" + data.locales[locidx].getName() + ", pattern=" + PATTERNS[patidx]
565                                        + ", text=" + text + ", time=" + testTimes[testidx] + ", restime=" + parsedDate + ", diff=" + (parsedDate - testTimes[testidx]);
566                                // Timebomb for TZData update
567                                if (expectedRoundTrip[testidx]
568                                        && !isSpecialTimeRoundTripCase(data.locales[locidx].getName(), *tzid,
569                                                PATTERNS[patidx], testTimes[testidx])) {
570                                    log.errln((UnicodeString) "FAIL: " + msg);
571                                } else if (REALLY_VERBOSE) {
572                                    log.logln(msg);
573                                }
574                            }
575                        }
576                        tztAvail = tz->getNextTransition(t, FALSE, tzt);
577                        if (!tztAvail) {
578                            break;
579                        }
580                        if (middle) {
581                            // Test the date in the middle of two transitions.
582                            t += (int64_t) ((tzt.getTime() - t) / 2);
583                            middle = FALSE;
584                            tztAvail = FALSE;
585                        } else {
586                            t = tzt.getTime();
587                        }
588                    }
589                    delete tz;
590                }
591                times[patidx] += (Calendar::getNow() - timer);
592                delete sdf;
593            }
594            umtx_lock(NULL);
595            data.numDone++;
596            umtx_unlock(NULL);
597        }
598        delete tzids;
599    }
600private:
601    IntlTest& log;
602    LocaleData& data;
603    int32_t index;
604};
605
606void
607TimeZoneFormatTest::TestTimeRoundTrip(void) {
608    int32_t nThreads = threadCount;
609    const Locale *LOCALES;
610    int32_t nLocales;
611    int32_t testCounts = 0;
612
613    UErrorCode status = U_ZERO_ERROR;
614    Calendar *cal = Calendar::createInstance(TimeZone::createTimeZone((UnicodeString) "UTC"), status);
615    if (U_FAILURE(status)) {
616        dataerrln("Calendar::createInstance failed: %s", u_errorName(status));
617        return;
618    }
619
620    const char* testAllProp = getProperty("TimeZoneRoundTripAll");
621    UBool bTestAll = (testAllProp && uprv_strcmp(testAllProp, "true") == 0);
622
623    UDate START_TIME, END_TIME;
624    if (bTestAll || !quick) {
625        cal->set(1900, UCAL_JANUARY, 1);
626    } else {
627        cal->set(1990, UCAL_JANUARY, 1);
628    }
629    START_TIME = cal->getTime(status);
630
631    cal->set(2015, UCAL_JANUARY, 1);
632    END_TIME = cal->getTime(status);
633
634    if (U_FAILURE(status)) {
635        errln("getTime failed");
636        return;
637    }
638
639    UDate times[NUM_PATTERNS];
640    for (int32_t i = 0; i < NUM_PATTERNS; i++) {
641        times[i] = 0;
642    }
643
644    // Set up test locales
645    const Locale locales1[] = {Locale("en")};
646    const Locale locales2[] = {
647        Locale("ar_EG"), Locale("bg_BG"), Locale("ca_ES"), Locale("da_DK"), Locale("de"),
648        Locale("de_DE"), Locale("el_GR"), Locale("en"), Locale("en_AU"), Locale("en_CA"),
649        Locale("en_US"), Locale("es"), Locale("es_ES"), Locale("es_MX"), Locale("fi_FI"),
650        Locale("fr"), Locale("fr_CA"), Locale("fr_FR"), Locale("he_IL"), Locale("hu_HU"),
651        Locale("it"), Locale("it_IT"), Locale("ja"), Locale("ja_JP"), Locale("ko"),
652        Locale("ko_KR"), Locale("nb_NO"), Locale("nl_NL"), Locale("nn_NO"), Locale("pl_PL"),
653        Locale("pt"), Locale("pt_BR"), Locale("pt_PT"), Locale("ru_RU"), Locale("sv_SE"),
654        Locale("th_TH"), Locale("tr_TR"), Locale("zh"), Locale("zh_Hans"), Locale("zh_Hans_CN"),
655        Locale("zh_Hant"), Locale("zh_Hant_TW")
656    };
657
658    if (bTestAll) {
659        LOCALES = Locale::getAvailableLocales(nLocales);
660    } else if (quick) {
661        LOCALES = locales1;
662        nLocales = sizeof(locales1)/sizeof(Locale);
663    } else {
664        LOCALES = locales2;
665        nLocales = sizeof(locales2)/sizeof(Locale);
666    }
667
668    LocaleData data;
669    data.index = 0;
670    data.testCounts = testCounts;
671    data.times = times;
672    data.locales = LOCALES;
673    data.nLocales = nLocales;
674    data.quick = quick;
675    data.START_TIME = START_TIME;
676    data.END_TIME = END_TIME;
677    data.numDone = 0;
678
679#if (ICU_USE_THREADS==0)
680    TestTimeRoundTripThread fakeThread(*this, data, 0);
681    fakeThread.run();
682#else
683    TestTimeRoundTripThread **threads = new TestTimeRoundTripThread*[threadCount];
684    int32_t i;
685    for (i = 0; i < nThreads; i++) {
686        threads[i] = new TestTimeRoundTripThread(*this, data, i);
687        if (threads[i]->start() != 0) {
688            errln("Error starting thread %d", i);
689        }
690    }
691
692    UBool done = false;
693    while (true) {
694        umtx_lock(NULL);
695        if (data.numDone == nLocales) {
696            done = true;
697        }
698        umtx_unlock(NULL);
699        if (done)
700            break;
701        SimpleThread::sleep(1000);
702    }
703
704    for (i = 0; i < nThreads; i++) {
705        delete threads[i];
706    }
707    delete [] threads;
708
709#endif
710    UDate total = 0;
711    logln("### Elapsed time by patterns ###");
712    for (int32_t i = 0; i < NUM_PATTERNS; i++) {
713        logln(UnicodeString("") + data.times[i] + "ms (" + PATTERNS[i] + ")");
714        total += data.times[i];
715    }
716    logln((UnicodeString) "Total: " + total + "ms");
717    logln((UnicodeString) "Iteration: " + data.testCounts);
718
719    delete cal;
720}
721
722
723typedef struct {
724    const char*     text;
725    int32_t         inPos;
726    const char*     locale;
727    UTimeZoneFormatStyle    style;
728    UBool           parseAll;
729    const char*     expected;
730    int32_t         outPos;
731    UTimeZoneFormatTimeType timeType;
732} ParseTestData;
733
734void
735TimeZoneFormatTest::TestParse(void) {
736    const ParseTestData DATA[] = {
737        //   text               inPos   locale      style                               parseAll    expected            outPos  timeType
738            {"Z",               0,      "en_US",    UTZFMT_STYLE_ISO_EXTENDED_FULL,     false,      "Etc/GMT",          1,      UTZFMT_TIME_TYPE_UNKNOWN},
739            {"Z",               0,      "en_US",    UTZFMT_STYLE_SPECIFIC_LONG,         false,      "Etc/GMT",          1,      UTZFMT_TIME_TYPE_UNKNOWN},
740            {"Zambia time",     0,      "en_US",    UTZFMT_STYLE_ISO_EXTENDED_FULL,     true,       "Etc/GMT",          1,      UTZFMT_TIME_TYPE_UNKNOWN},
741            {"Zambia time",     0,      "en_US",    UTZFMT_STYLE_GENERIC_LOCATION,      false,      "Africa/Lusaka",    11,     UTZFMT_TIME_TYPE_UNKNOWN},
742            {"Zambia time",     0,      "en_US",    UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL,  true,       "Africa/Lusaka",    11,     UTZFMT_TIME_TYPE_UNKNOWN},
743            {"+00:00",          0,      "en_US",    UTZFMT_STYLE_ISO_EXTENDED_FULL,     false,      "Etc/GMT",          6,      UTZFMT_TIME_TYPE_UNKNOWN},
744            {"-01:30:45",       0,      "en_US",    UTZFMT_STYLE_ISO_EXTENDED_FULL,     false,      "GMT-01:30:45",     9,      UTZFMT_TIME_TYPE_UNKNOWN},
745            {"-7",              0,      "en_US",    UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL,  false,      "GMT-07:00",        2,      UTZFMT_TIME_TYPE_UNKNOWN},
746            {"-2222",           0,      "en_US",    UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL,  false,      "GMT-22:22",        5,      UTZFMT_TIME_TYPE_UNKNOWN},
747            {"-3333",           0,      "en_US",    UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL,  false,      "GMT-03:33",        4,      UTZFMT_TIME_TYPE_UNKNOWN},
748            {"XXX+01:30YYY",    3,      "en_US",    UTZFMT_STYLE_LOCALIZED_GMT,         false,      "GMT+01:30",        9,      UTZFMT_TIME_TYPE_UNKNOWN},
749            {"GMT0",            0,      "en_US",    UTZFMT_STYLE_SPECIFIC_SHORT,        false,      "Etc/GMT",          3,      UTZFMT_TIME_TYPE_UNKNOWN},
750            {"EST",             0,      "en_US",    UTZFMT_STYLE_SPECIFIC_SHORT,        false,      "America/New_York", 3,      UTZFMT_TIME_TYPE_STANDARD},
751            {"ESTx",            0,      "en_US",    UTZFMT_STYLE_SPECIFIC_SHORT,        false,      "America/New_York", 3,      UTZFMT_TIME_TYPE_STANDARD},
752            {"EDTx",            0,      "en_US",    UTZFMT_STYLE_SPECIFIC_SHORT,        false,      "America/New_York", 3,      UTZFMT_TIME_TYPE_DAYLIGHT},
753            {"EST",             0,      "en_US",    UTZFMT_STYLE_SPECIFIC_LONG,         false,      NULL,               0,      UTZFMT_TIME_TYPE_UNKNOWN},
754            {"EST",             0,      "en_US",    UTZFMT_STYLE_SPECIFIC_LONG,         true,       "America/New_York", 3,      UTZFMT_TIME_TYPE_STANDARD},
755            {"EST",             0,      "en_CA",    UTZFMT_STYLE_SPECIFIC_SHORT,        false,      "America/Toronto",  3,      UTZFMT_TIME_TYPE_STANDARD},
756            {NULL,              0,      NULL,       UTZFMT_STYLE_GENERIC_LOCATION,      false,      NULL,               0,      UTZFMT_TIME_TYPE_UNKNOWN}
757    };
758
759    for (int32_t i = 0; DATA[i].text; i++) {
760        UErrorCode status = U_ZERO_ERROR;
761        LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(Locale(DATA[i].locale), status));
762        if (U_FAILURE(status)) {
763            dataerrln("Fail TimeZoneFormat::createInstance: %s", u_errorName(status));
764            continue;
765        }
766        UTimeZoneFormatTimeType ttype = UTZFMT_TIME_TYPE_UNKNOWN;
767        ParsePosition pos(DATA[i].inPos);
768        int32_t parseOptions = DATA[i].parseAll ? UTZFMT_PARSE_OPTION_ALL_STYLES : UTZFMT_PARSE_OPTION_NONE;
769        TimeZone* tz = tzfmt->parse(DATA[i].style, DATA[i].text, pos, parseOptions, &ttype);
770
771        UnicodeString errMsg;
772        if (tz) {
773            UnicodeString outID;
774            tz->getID(outID);
775            if (outID != UnicodeString(DATA[i].expected)) {
776                errMsg = (UnicodeString)"Time zone ID: " + outID + " - expected: " + DATA[i].expected;
777            } else if (pos.getIndex() != DATA[i].outPos) {
778                errMsg = (UnicodeString)"Parsed pos: " + pos.getIndex() + " - expected: " + DATA[i].outPos;
779            } else if (ttype != DATA[i].timeType) {
780                errMsg = (UnicodeString)"Time type: " + ttype + " - expected: " + DATA[i].timeType;
781            }
782            delete tz;
783        } else {
784            if (DATA[i].expected) {
785                errln((UnicodeString)"Fail: Parse failure - expected: " + DATA[i].expected);
786            }
787        }
788        if (errMsg.length() > 0) {
789            errln((UnicodeString)"Fail: " + errMsg + " [text=" + DATA[i].text + ", pos=" + DATA[i].inPos + ", style=" + DATA[i].style + "]");
790        }
791    }
792}
793
794void
795TimeZoneFormatTest::TestISOFormat(void) {
796    const int32_t OFFSET[] = {
797        0,          // 0
798        999,        // 0.999s
799        -59999,     // -59.999s
800        60000,      // 1m
801        -77777,     // -1m 17.777s
802        1800000,    // 30m
803        -3600000,   // -1h
804        36000000,   // 10h
805        -37800000,  // -10h 30m
806        -37845000,  // -10h 30m 45s
807        108000000,  // 30h
808    };
809
810    const char* ISO_STR[][11] = {
811        // 0
812        {
813            "Z", "Z", "Z", "Z", "Z",
814            "+00", "+0000", "+00:00", "+0000", "+00:00",
815            "+0000"
816        },
817        // 999
818        {
819            "Z", "Z", "Z", "Z", "Z",
820            "+00", "+0000", "+00:00", "+0000", "+00:00",
821            "+0000"
822        },
823        // -59999
824        {
825            "Z", "Z", "Z", "-000059", "-00:00:59",
826            "+00", "+0000", "+00:00", "-000059", "-00:00:59",
827            "-000059"
828        },
829        // 60000
830        {
831            "+0001", "+0001", "+00:01", "+0001", "+00:01",
832            "+0001", "+0001", "+00:01", "+0001", "+00:01",
833            "+0001"
834        },
835        // -77777
836        {
837            "-0001", "-0001", "-00:01", "-000117", "-00:01:17",
838            "-0001", "-0001", "-00:01", "-000117", "-00:01:17",
839            "-000117"
840        },
841        // 1800000
842        {
843            "+0030", "+0030", "+00:30", "+0030", "+00:30",
844            "+0030", "+0030", "+00:30", "+0030", "+00:30",
845            "+0030"
846        },
847        // -3600000
848        {
849            "-01", "-0100", "-01:00", "-0100", "-01:00",
850            "-01", "-0100", "-01:00", "-0100", "-01:00",
851            "-0100"
852        },
853        // 36000000
854        {
855            "+10", "+1000", "+10:00", "+1000", "+10:00",
856            "+10", "+1000", "+10:00", "+1000", "+10:00",
857            "+1000"
858        },
859        // -37800000
860        {
861            "-1030", "-1030", "-10:30", "-1030", "-10:30",
862            "-1030", "-1030", "-10:30", "-1030", "-10:30",
863            "-1030"
864        },
865        // -37845000
866        {
867            "-1030", "-1030", "-10:30", "-103045", "-10:30:45",
868            "-1030", "-1030", "-10:30", "-103045", "-10:30:45",
869            "-103045"
870        },
871        // 108000000
872        {
873            0, 0, 0, 0, 0,
874            0, 0, 0, 0, 0,
875            0
876        }
877    };
878
879    const char* PATTERN[] = {
880        "X", "XX", "XXX", "XXXX", "XXXXX",
881        "x", "xx", "xxx", "xxxx", "xxxxx",
882        "Z", // equivalent to "xxxx"
883        0
884    };
885
886    const int32_t MIN_OFFSET_UNIT[] = {
887        60000, 60000, 60000, 1000, 1000,
888        60000, 60000, 60000, 1000, 1000,
889        1000,
890    };
891
892    // Formatting
893    UErrorCode status = U_ZERO_ERROR;
894    LocalPointer<SimpleDateFormat> sdf(new SimpleDateFormat(status));
895    if (U_FAILURE(status)) {
896        dataerrln("Fail new SimpleDateFormat: %s", u_errorName(status));
897        return;
898    }
899    UDate d = Calendar::getNow();
900
901    for (uint32_t i = 0; i < sizeof(OFFSET)/sizeof(OFFSET[0]); i++) {
902        SimpleTimeZone* tz = new SimpleTimeZone(OFFSET[i], UnicodeString("Zone Offset:") + OFFSET[i] + "ms");
903        sdf->adoptTimeZone(tz);
904        for (int32_t j = 0; PATTERN[j] != 0; j++) {
905            sdf->applyPattern(UnicodeString(PATTERN[j]));
906            UnicodeString result;
907            sdf->format(d, result);
908
909            if (ISO_STR[i][j]) {
910                if (result != UnicodeString(ISO_STR[i][j])) {
911                    errln((UnicodeString)"FAIL: pattern=" + PATTERN[j] + ", offset=" + OFFSET[i] + " -> "
912                        + result + " (expected: " + ISO_STR[i][j] + ")");
913                }
914            } else {
915                // Offset out of range
916                // Note: for now, there is no way to propagate the error status through
917                // the SimpleDateFormat::format above.
918                if (result.length() > 0) {
919                    errln((UnicodeString)"FAIL: Non-Empty result for pattern=" + PATTERN[j] + ", offset=" + OFFSET[i]
920                        + " (expected: empty result)");
921                }
922            }
923        }
924    }
925
926    // Parsing
927    LocalPointer<Calendar> outcal(Calendar::createInstance(status));
928    if (U_FAILURE(status)) {
929        dataerrln("Fail new Calendar: %s", u_errorName(status));
930        return;
931    }
932    for (int32_t i = 0; ISO_STR[i][0] != NULL; i++) {
933        for (int32_t j = 0; PATTERN[j] != 0; j++) {
934            if (ISO_STR[i][j] == 0) {
935                continue;
936            }
937            ParsePosition pos(0);
938            SimpleTimeZone* bogusTZ = new SimpleTimeZone(-1, UnicodeString("Zone Offset: -1ms"));
939            outcal->adoptTimeZone(bogusTZ);
940            sdf->applyPattern(PATTERN[j]);
941
942            sdf->parse(UnicodeString(ISO_STR[i][j]), *(outcal.getAlias()), pos);
943
944            if (pos.getIndex() != (int32_t)uprv_strlen(ISO_STR[i][j])) {
945                errln((UnicodeString)"FAIL: Failed to parse the entire input string: " + ISO_STR[i][j]);
946            }
947
948            const TimeZone& outtz = outcal->getTimeZone();
949            int32_t outOffset = outtz.getRawOffset();
950            int32_t adjustedOffset = OFFSET[i] / MIN_OFFSET_UNIT[j] * MIN_OFFSET_UNIT[j];
951            if (outOffset != adjustedOffset) {
952                errln((UnicodeString)"FAIL: Incorrect offset:" + outOffset + "ms for input string: " + ISO_STR[i][j]
953                    + " (expected:" + adjustedOffset + "ms)");
954            }
955        }
956    }
957}
958
959
960#endif /* #if !UCONFIG_NO_FORMATTING */
961