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