Imported Upstream version 51.1
[platform/upstream/icu.git] / source / test / intltest / tzfmttst.cpp
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
26 static 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 };
50 static const int NUM_PATTERNS = sizeof(PATTERNS)/sizeof(const char*);
51
52 static const UChar ETC_UNKNOWN[] = {0x45, 0x74, 0x63, 0x2F, 0x55, 0x6E, 0x6B, 0x6E, 0x6F, 0x77, 0x6E, 0};
53
54 static const UChar ETC_SLASH[] = { 0x45, 0x74, 0x63, 0x2F, 0 }; // "Etc/"
55 static const UChar SYSTEMV_SLASH[] = { 0x53, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x56, 0x2F, 0 }; // "SystemV/
56 static const UChar RIYADH8[] = { 0x52, 0x69, 0x79, 0x61, 0x64, 0x68, 0x38, 0 }; // "Riyadh8"
57
58 static 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
67 void
68 TimeZoneFormatTest::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
82 void
83 TimeZoneFormatTest::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 struct LocaleData {
337     int32_t index;
338     int32_t testCounts;
339     UDate *times;
340     const Locale* locales; // Static
341     int32_t nLocales; // Static
342     UBool quick; // Static
343     UDate START_TIME; // Static
344     UDate END_TIME; // Static
345     int32_t numDone;
346 };
347
348 class TestTimeRoundTripThread: public SimpleThread {
349 public:
350     TestTimeRoundTripThread(IntlTest& tlog, LocaleData &ld, int32_t i)
351         : log(tlog), data(ld), index(i) {}
352     virtual void run() {
353         UErrorCode status = U_ZERO_ERROR;
354         UBool REALLY_VERBOSE = FALSE;
355
356         // These patterns are ambiguous at DST->STD local time overlap
357         const char* AMBIGUOUS_DST_DECESSION[] = { "v", "vvvv", "V", "VV", "VVV", "VVVV", 0 };
358
359         // These patterns are ambiguous at STD->STD/DST->DST local time overlap
360         const char* AMBIGUOUS_NEGATIVE_SHIFT[] = { "z", "zzzz", "v", "vvvv", "V", "VV", "VVV", "VVVV", 0 };
361
362         // These patterns only support integer minutes offset
363         const char* MINUTES_OFFSET[] = { "X", "XX", "XXX", "x", "xx", "xxx", 0 };
364
365         // Workaround for #6338
366         //UnicodeString BASEPATTERN("yyyy-MM-dd'T'HH:mm:ss.SSS");
367         UnicodeString BASEPATTERN("yyyy.MM.dd HH:mm:ss.SSS");
368
369         // timer for performance analysis
370         UDate timer;
371         UDate testTimes[4];
372         UBool expectedRoundTrip[4];
373         int32_t testLen = 0;
374
375         StringEnumeration *tzids = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL, NULL, NULL, status);
376         if (U_FAILURE(status)) {
377             if (status == U_MISSING_RESOURCE_ERROR) {
378                 /* This error is generally caused by data not being present. However, an infinite loop will occur
379                  * because the thread thinks that the test data is never done so we should treat the data as done.
380                  */
381                 log.dataerrln("TimeZone::createTimeZoneIDEnumeration failed - %s", u_errorName(status));
382                 data.numDone = data.nLocales;
383             } else {
384                 log.errln("TimeZone::createTimeZoneIDEnumeration failed: %s", u_errorName(status));
385             }
386             return;
387         }
388
389         int32_t locidx = -1;
390         UDate times[NUM_PATTERNS];
391         for (int32_t i = 0; i < NUM_PATTERNS; i++) {
392             times[i] = 0;
393         }
394
395         int32_t testCounts = 0;
396
397         while (true) {
398             umtx_lock(NULL); // Lock to increment the index
399             for (int32_t i = 0; i < NUM_PATTERNS; i++) {
400                 data.times[i] += times[i];
401                 data.testCounts += testCounts;
402             }
403             if (data.index < data.nLocales) {
404                 locidx = data.index;
405                 data.index++;
406             } else {
407                 locidx = -1;
408             }
409             umtx_unlock(NULL); // Unlock for other threads to use
410
411             if (locidx == -1) {
412                 log.logln((UnicodeString) "Thread " + index + " is done.");
413                 break;
414             }
415
416             log.logln((UnicodeString) "\nThread " + index + ": Locale: " + UnicodeString(data.locales[locidx].getName()));
417
418             for (int32_t patidx = 0; patidx < NUM_PATTERNS; patidx++) {
419                 log.logln((UnicodeString) "    Pattern: " + PATTERNS[patidx]);
420                 times[patidx] = 0;
421
422                 UnicodeString pattern(BASEPATTERN);
423                 pattern.append(" ").append(PATTERNS[patidx]);
424
425                 SimpleDateFormat *sdf = new SimpleDateFormat(pattern, data.locales[locidx], status);
426                 if (U_FAILURE(status)) {
427                     log.errcheckln(status, (UnicodeString) "new SimpleDateFormat failed for pattern " + 
428                         pattern + " for locale " + data.locales[locidx].getName() + " - " + u_errorName(status));
429                     status = U_ZERO_ERROR;
430                     continue;
431                 }
432
433                 UBool minutesOffset = contains(MINUTES_OFFSET, PATTERNS[patidx]);
434
435                 tzids->reset(status);
436                 const UnicodeString *tzid;
437
438                 timer = Calendar::getNow();
439
440                 while ((tzid = tzids->snext(status))) {
441                     if (uprv_strcmp(PATTERNS[patidx], "V") == 0) {
442                         // Some zones do not have short ID assigned, such as Asia/Riyadh87.
443                         // The time roundtrip will fail for such zones with pattern "V" (short zone ID).
444                         // This is expected behavior.
445                         const UChar* shortZoneID = ZoneMeta::getShortID(*tzid);
446                         if (shortZoneID == NULL) {
447                             continue;
448                         }
449                     } else if (uprv_strcmp(PATTERNS[patidx], "VVV") == 0) {
450                         // Some zones are not associated with any region, such as Etc/GMT+8.
451                         // The time roundtrip will fail for such zone with pattern "VVV" (exemplar location).
452                         // This is expected behavior.
453                         if (tzid->indexOf((UChar)0x2F) < 0 || tzid->indexOf(ETC_SLASH, -1, 0) >= 0
454                             || tzid->indexOf(SYSTEMV_SLASH, -1, 0) >= 0 || tzid->indexOf(RIYADH8, -1, 0) >= 0) {
455                             continue;
456                         }
457                     }
458
459                     BasicTimeZone *tz = (BasicTimeZone*) TimeZone::createTimeZone(*tzid);
460                     sdf->setTimeZone(*tz);
461
462                     UDate t = data.START_TIME;
463                     TimeZoneTransition tzt;
464                     UBool tztAvail = FALSE;
465                     UBool middle = TRUE;
466
467                     while (t < data.END_TIME) {
468                         if (!tztAvail) {
469                             testTimes[0] = t;
470                             expectedRoundTrip[0] = TRUE;
471                             testLen = 1;
472                         } else {
473                             int32_t fromOffset = tzt.getFrom()->getRawOffset() + tzt.getFrom()->getDSTSavings();
474                             int32_t toOffset = tzt.getTo()->getRawOffset() + tzt.getTo()->getDSTSavings();
475                             int32_t delta = toOffset - fromOffset;
476                             if (delta < 0) {
477                                 UBool isDstDecession = tzt.getFrom()->getDSTSavings() > 0 && tzt.getTo()->getDSTSavings() == 0;
478                                 testTimes[0] = t + delta - 1;
479                                 expectedRoundTrip[0] = TRUE;
480                                 testTimes[1] = t + delta;
481                                 expectedRoundTrip[1] = isDstDecession ?
482                                     !contains(AMBIGUOUS_DST_DECESSION, PATTERNS[patidx]) :
483                                     !contains(AMBIGUOUS_NEGATIVE_SHIFT, PATTERNS[patidx]);
484                                 testTimes[2] = t - 1;
485                                 expectedRoundTrip[2] = isDstDecession ?
486                                     !contains(AMBIGUOUS_DST_DECESSION, PATTERNS[patidx]) :
487                                     !contains(AMBIGUOUS_NEGATIVE_SHIFT, PATTERNS[patidx]);
488                                 testTimes[3] = t;
489                                 expectedRoundTrip[3] = TRUE;
490                                 testLen = 4;
491                             } else {
492                                 testTimes[0] = t - 1;
493                                 expectedRoundTrip[0] = TRUE;
494                                 testTimes[1] = t;
495                                 expectedRoundTrip[1] = TRUE;
496                                 testLen = 2;
497                             }
498                         }
499                         for (int32_t testidx = 0; testidx < testLen; testidx++) {
500                             if (data.quick) {
501                                 // reduce regular test time
502                                 if (!expectedRoundTrip[testidx]) {
503                                     continue;
504                                 }
505                             }
506
507                             testCounts++;
508
509                             UnicodeString text;
510                             FieldPosition fpos(0);
511                             sdf->format(testTimes[testidx], text, fpos);
512
513                             UDate parsedDate = sdf->parse(text, status);
514                             if (U_FAILURE(status)) {
515                                 log.errln((UnicodeString) "Parse failure for text=" + text + ", tzid=" + *tzid + ", locale=" + data.locales[locidx].getName()
516                                         + ", pattern=" + PATTERNS[patidx] + ", time=" + testTimes[testidx]);
517                                 status = U_ZERO_ERROR;
518                                 continue;
519                             }
520
521                             int32_t timeDiff = (int32_t)(parsedDate - testTimes[testidx]);
522                             UBool bTimeMatch = minutesOffset ?
523                                 (timeDiff/60000)*60000 == 0 : timeDiff == 0;
524                             if (!bTimeMatch) {
525                                 UnicodeString msg = (UnicodeString) "Time round trip failed for " + "tzid=" + *tzid + ", locale=" + data.locales[locidx].getName() + ", pattern=" + PATTERNS[patidx]
526                                         + ", text=" + text + ", time=" + testTimes[testidx] + ", restime=" + parsedDate + ", diff=" + (parsedDate - testTimes[testidx]);
527                                 // Timebomb for TZData update
528                                 if (expectedRoundTrip[testidx]) {
529                                     log.errln((UnicodeString) "FAIL: " + msg);
530                                 } else if (REALLY_VERBOSE) {
531                                     log.logln(msg);
532                                 }
533                             }
534                         }
535                         tztAvail = tz->getNextTransition(t, FALSE, tzt);
536                         if (!tztAvail) {
537                             break;
538                         }
539                         if (middle) {
540                             // Test the date in the middle of two transitions.
541                             t += (int64_t) ((tzt.getTime() - t) / 2);
542                             middle = FALSE;
543                             tztAvail = FALSE;
544                         } else {
545                             t = tzt.getTime();
546                         }
547                     }
548                     delete tz;
549                 }
550                 times[patidx] += (Calendar::getNow() - timer);
551                 delete sdf;
552             }
553             umtx_lock(NULL);
554             data.numDone++;
555             umtx_unlock(NULL);
556         }
557         delete tzids;
558     }
559 private:
560     IntlTest& log;
561     LocaleData& data;
562     int32_t index;
563 };
564
565 void
566 TimeZoneFormatTest::TestTimeRoundTrip(void) {
567     int32_t nThreads = threadCount;
568     const Locale *LOCALES;
569     int32_t nLocales;
570     int32_t testCounts = 0;
571
572     UErrorCode status = U_ZERO_ERROR;
573     Calendar *cal = Calendar::createInstance(TimeZone::createTimeZone((UnicodeString) "UTC"), status);
574     if (U_FAILURE(status)) {
575         dataerrln("Calendar::createInstance failed: %s", u_errorName(status));
576         return;
577     }
578
579     const char* testAllProp = getProperty("TimeZoneRoundTripAll");
580     UBool bTestAll = (testAllProp && uprv_strcmp(testAllProp, "true") == 0);
581
582     UDate START_TIME, END_TIME;
583     if (bTestAll || !quick) {
584         cal->set(1900, UCAL_JANUARY, 1);
585     } else {
586         cal->set(1990, UCAL_JANUARY, 1);
587     }
588     START_TIME = cal->getTime(status);
589
590     cal->set(2015, UCAL_JANUARY, 1);
591     END_TIME = cal->getTime(status);
592
593     if (U_FAILURE(status)) {
594         errln("getTime failed");
595         return;
596     }
597
598     UDate times[NUM_PATTERNS];
599     for (int32_t i = 0; i < NUM_PATTERNS; i++) {
600         times[i] = 0;
601     }
602
603     // Set up test locales
604     const Locale locales1[] = {Locale("en")};
605     const Locale locales2[] = {
606         Locale("ar_EG"), Locale("bg_BG"), Locale("ca_ES"), Locale("da_DK"), Locale("de"),
607         Locale("de_DE"), Locale("el_GR"), Locale("en"), Locale("en_AU"), Locale("en_CA"),
608         Locale("en_US"), Locale("es"), Locale("es_ES"), Locale("es_MX"), Locale("fi_FI"),
609         Locale("fr"), Locale("fr_CA"), Locale("fr_FR"), Locale("he_IL"), Locale("hu_HU"),
610         Locale("it"), Locale("it_IT"), Locale("ja"), Locale("ja_JP"), Locale("ko"),
611         Locale("ko_KR"), Locale("nb_NO"), Locale("nl_NL"), Locale("nn_NO"), Locale("pl_PL"),
612         Locale("pt"), Locale("pt_BR"), Locale("pt_PT"), Locale("ru_RU"), Locale("sv_SE"),
613         Locale("th_TH"), Locale("tr_TR"), Locale("zh"), Locale("zh_Hans"), Locale("zh_Hans_CN"),
614         Locale("zh_Hant"), Locale("zh_Hant_TW")
615     };
616
617     if (bTestAll) {
618         LOCALES = Locale::getAvailableLocales(nLocales);
619     } else if (quick) {
620         LOCALES = locales1;
621         nLocales = sizeof(locales1)/sizeof(Locale);
622     } else {
623         LOCALES = locales2;
624         nLocales = sizeof(locales2)/sizeof(Locale);
625     }
626
627     LocaleData data;
628     data.index = 0;
629     data.testCounts = testCounts;
630     data.times = times;
631     data.locales = LOCALES;
632     data.nLocales = nLocales;
633     data.quick = quick;
634     data.START_TIME = START_TIME;
635     data.END_TIME = END_TIME;
636     data.numDone = 0;
637
638 #if (ICU_USE_THREADS==0)
639     TestTimeRoundTripThread fakeThread(*this, data, 0);
640     fakeThread.run();
641 #else
642     TestTimeRoundTripThread **threads = new TestTimeRoundTripThread*[threadCount];
643     int32_t i;
644     for (i = 0; i < nThreads; i++) {
645         threads[i] = new TestTimeRoundTripThread(*this, data, i);
646         if (threads[i]->start() != 0) {
647             errln("Error starting thread %d", i);
648         }
649     }
650
651     UBool done = false;
652     while (true) {
653         umtx_lock(NULL);
654         if (data.numDone == nLocales) {
655             done = true;
656         }
657         umtx_unlock(NULL);
658         if (done)
659             break;
660         SimpleThread::sleep(1000);
661     }
662
663     for (i = 0; i < nThreads; i++) {
664         delete threads[i];
665     }
666     delete [] threads;
667
668 #endif
669     UDate total = 0;
670     logln("### Elapsed time by patterns ###");
671     for (int32_t i = 0; i < NUM_PATTERNS; i++) {
672         logln(UnicodeString("") + data.times[i] + "ms (" + PATTERNS[i] + ")");
673         total += data.times[i];
674     }
675     logln((UnicodeString) "Total: " + total + "ms");
676     logln((UnicodeString) "Iteration: " + data.testCounts);
677
678     delete cal;
679 }
680
681
682 typedef struct {
683     const char*     text;
684     int32_t         inPos;
685     const char*     locale;
686     UTimeZoneFormatStyle    style;
687     UBool           parseAll;
688     const char*     expected;
689     int32_t         outPos;
690     UTimeZoneFormatTimeType timeType;
691 } ParseTestData;
692
693 void
694 TimeZoneFormatTest::TestParse(void) {
695     const ParseTestData DATA[] = {
696         //   text               inPos   locale      style                               parseAll    expected            outPos  timeType
697             {"Z",               0,      "en_US",    UTZFMT_STYLE_ISO_EXTENDED_FULL,     false,      "Etc/GMT",          1,      UTZFMT_TIME_TYPE_UNKNOWN},
698             {"Z",               0,      "en_US",    UTZFMT_STYLE_SPECIFIC_LONG,         false,      "Etc/GMT",          1,      UTZFMT_TIME_TYPE_UNKNOWN},
699             {"Zambia time",     0,      "en_US",    UTZFMT_STYLE_ISO_EXTENDED_FULL,     true,       "Etc/GMT",          1,      UTZFMT_TIME_TYPE_UNKNOWN},
700             {"Zambia time",     0,      "en_US",    UTZFMT_STYLE_GENERIC_LOCATION,      false,      "Africa/Lusaka",    11,     UTZFMT_TIME_TYPE_UNKNOWN},
701             {"Zambia time",     0,      "en_US",    UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL,  true,       "Africa/Lusaka",    11,     UTZFMT_TIME_TYPE_UNKNOWN},
702             {"+00:00",          0,      "en_US",    UTZFMT_STYLE_ISO_EXTENDED_FULL,     false,      "Etc/GMT",          6,      UTZFMT_TIME_TYPE_UNKNOWN},
703             {"-01:30:45",       0,      "en_US",    UTZFMT_STYLE_ISO_EXTENDED_FULL,     false,      "GMT-01:30:45",     9,      UTZFMT_TIME_TYPE_UNKNOWN},
704             {"-7",              0,      "en_US",    UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL,  false,      "GMT-07:00",        2,      UTZFMT_TIME_TYPE_UNKNOWN},
705             {"-2222",           0,      "en_US",    UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL,  false,      "GMT-22:22",        5,      UTZFMT_TIME_TYPE_UNKNOWN},
706             {"-3333",           0,      "en_US",    UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL,  false,      "GMT-03:33",        4,      UTZFMT_TIME_TYPE_UNKNOWN},
707             {"XXX+01:30YYY",    3,      "en_US",    UTZFMT_STYLE_LOCALIZED_GMT,         false,      "GMT+01:30",        9,      UTZFMT_TIME_TYPE_UNKNOWN},
708             {"GMT0",            0,      "en_US",    UTZFMT_STYLE_SPECIFIC_SHORT,        false,      "Etc/GMT",          3,      UTZFMT_TIME_TYPE_UNKNOWN},
709             {"EST",             0,      "en_US",    UTZFMT_STYLE_SPECIFIC_SHORT,        false,      "America/New_York", 3,      UTZFMT_TIME_TYPE_STANDARD},
710             {"ESTx",            0,      "en_US",    UTZFMT_STYLE_SPECIFIC_SHORT,        false,      "America/New_York", 3,      UTZFMT_TIME_TYPE_STANDARD},
711             {"EDTx",            0,      "en_US",    UTZFMT_STYLE_SPECIFIC_SHORT,        false,      "America/New_York", 3,      UTZFMT_TIME_TYPE_DAYLIGHT},
712             {"EST",             0,      "en_US",    UTZFMT_STYLE_SPECIFIC_LONG,         false,      NULL,               0,      UTZFMT_TIME_TYPE_UNKNOWN},
713             {"EST",             0,      "en_US",    UTZFMT_STYLE_SPECIFIC_LONG,         true,       "America/New_York", 3,      UTZFMT_TIME_TYPE_STANDARD},
714             {"EST",             0,      "en_CA",    UTZFMT_STYLE_SPECIFIC_SHORT,        false,      "America/Toronto",  3,      UTZFMT_TIME_TYPE_STANDARD},
715             {NULL,              0,      NULL,       UTZFMT_STYLE_GENERIC_LOCATION,      false,      NULL,               0,      UTZFMT_TIME_TYPE_UNKNOWN}
716     };
717
718     for (int32_t i = 0; DATA[i].text; i++) {
719         UErrorCode status = U_ZERO_ERROR;
720         LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(Locale(DATA[i].locale), status));
721         if (U_FAILURE(status)) {
722             dataerrln("Fail TimeZoneFormat::createInstance: %s", u_errorName(status));
723             continue;
724         }
725         UTimeZoneFormatTimeType ttype = UTZFMT_TIME_TYPE_UNKNOWN;
726         ParsePosition pos(DATA[i].inPos);
727         int32_t parseOptions = DATA[i].parseAll ? UTZFMT_PARSE_OPTION_ALL_STYLES : UTZFMT_PARSE_OPTION_NONE;
728         TimeZone* tz = tzfmt->parse(DATA[i].style, DATA[i].text, pos, parseOptions, &ttype);
729
730         UnicodeString errMsg;
731         if (tz) {
732             UnicodeString outID;
733             tz->getID(outID);
734             if (outID != UnicodeString(DATA[i].expected)) {
735                 errMsg = (UnicodeString)"Time zone ID: " + outID + " - expected: " + DATA[i].expected;
736             } else if (pos.getIndex() != DATA[i].outPos) {
737                 errMsg = (UnicodeString)"Parsed pos: " + pos.getIndex() + " - expected: " + DATA[i].outPos;
738             } else if (ttype != DATA[i].timeType) {
739                 errMsg = (UnicodeString)"Time type: " + ttype + " - expected: " + DATA[i].timeType;
740             }
741             delete tz;
742         } else {
743             if (DATA[i].expected) {
744                 errln((UnicodeString)"Fail: Parse failure - expected: " + DATA[i].expected);
745             }
746         }
747         if (errMsg.length() > 0) {
748             errln((UnicodeString)"Fail: " + errMsg + " [text=" + DATA[i].text + ", pos=" + DATA[i].inPos + ", style=" + DATA[i].style + "]");
749         }
750     }
751 }
752
753 void
754 TimeZoneFormatTest::TestISOFormat(void) {
755     const int32_t OFFSET[] = {
756         0,          // 0
757         999,        // 0.999s
758         -59999,     // -59.999s
759         60000,      // 1m
760         -77777,     // -1m 17.777s
761         1800000,    // 30m
762         -3600000,   // -1h
763         36000000,   // 10h
764         -37800000,  // -10h 30m
765         -37845000,  // -10h 30m 45s
766         108000000,  // 30h
767     };
768
769     const char* ISO_STR[][11] = {
770         // 0
771         {
772             "Z", "Z", "Z", "Z", "Z",
773             "+00", "+0000", "+00:00", "+0000", "+00:00",
774             "+0000"
775         },
776         // 999
777         {
778             "Z", "Z", "Z", "Z", "Z",
779             "+00", "+0000", "+00:00", "+0000", "+00:00",
780             "+0000"
781         },
782         // -59999
783         {
784             "Z", "Z", "Z", "-000059", "-00:00:59",
785             "+00", "+0000", "+00:00", "-000059", "-00:00:59",
786             "-000059"
787         },
788         // 60000
789         {
790             "+0001", "+0001", "+00:01", "+0001", "+00:01",
791             "+0001", "+0001", "+00:01", "+0001", "+00:01",
792             "+0001"
793         },
794         // -77777
795         {
796             "-0001", "-0001", "-00:01", "-000117", "-00:01:17",
797             "-0001", "-0001", "-00:01", "-000117", "-00:01:17",
798             "-000117"
799         },
800         // 1800000
801         {
802             "+0030", "+0030", "+00:30", "+0030", "+00:30",
803             "+0030", "+0030", "+00:30", "+0030", "+00:30",
804             "+0030"
805         },
806         // -3600000
807         {
808             "-01", "-0100", "-01:00", "-0100", "-01:00",
809             "-01", "-0100", "-01:00", "-0100", "-01:00",
810             "-0100"
811         },
812         // 36000000
813         {
814             "+10", "+1000", "+10:00", "+1000", "+10:00",
815             "+10", "+1000", "+10:00", "+1000", "+10:00",
816             "+1000"
817         },
818         // -37800000
819         {
820             "-1030", "-1030", "-10:30", "-1030", "-10:30",
821             "-1030", "-1030", "-10:30", "-1030", "-10:30",
822             "-1030"
823         },
824         // -37845000
825         {
826             "-1030", "-1030", "-10:30", "-103045", "-10:30:45",
827             "-1030", "-1030", "-10:30", "-103045", "-10:30:45",
828             "-103045"
829         },
830         // 108000000
831         {
832             0, 0, 0, 0, 0,
833             0, 0, 0, 0, 0,
834             0
835         }
836     };
837
838     const char* PATTERN[] = {
839         "X", "XX", "XXX", "XXXX", "XXXXX",
840         "x", "xx", "xxx", "xxxx", "xxxxx",
841         "Z", // equivalent to "xxxx"
842         0
843     };
844
845     const int32_t MIN_OFFSET_UNIT[] = {
846         60000, 60000, 60000, 1000, 1000,
847         60000, 60000, 60000, 1000, 1000,
848         1000,
849     };
850
851     // Formatting
852     UErrorCode status = U_ZERO_ERROR;
853     LocalPointer<SimpleDateFormat> sdf(new SimpleDateFormat(status));
854     if (U_FAILURE(status)) {
855         dataerrln("Fail new SimpleDateFormat: %s", u_errorName(status));
856         return;
857     }
858     UDate d = Calendar::getNow();
859
860     for (uint32_t i = 0; i < sizeof(OFFSET)/sizeof(OFFSET[0]); i++) {
861         SimpleTimeZone* tz = new SimpleTimeZone(OFFSET[i], UnicodeString("Zone Offset:") + OFFSET[i] + "ms");
862         sdf->adoptTimeZone(tz);
863         for (int32_t j = 0; PATTERN[j] != 0; j++) {
864             sdf->applyPattern(UnicodeString(PATTERN[j]));
865             UnicodeString result;
866             sdf->format(d, result);
867
868             if (ISO_STR[i][j]) {
869                 if (result != UnicodeString(ISO_STR[i][j])) {
870                     errln((UnicodeString)"FAIL: pattern=" + PATTERN[j] + ", offset=" + OFFSET[i] + " -> "
871                         + result + " (expected: " + ISO_STR[i][j] + ")");
872                 }
873             } else {
874                 // Offset out of range
875                 // Note: for now, there is no way to propagate the error status through
876                 // the SimpleDateFormat::format above.
877                 if (result.length() > 0) {
878                     errln((UnicodeString)"FAIL: Non-Empty result for pattern=" + PATTERN[j] + ", offset=" + OFFSET[i]
879                         + " (expected: empty result)");
880                 }
881             }
882         }
883     }
884
885     // Parsing
886     LocalPointer<Calendar> outcal(Calendar::createInstance(status));
887     if (U_FAILURE(status)) {
888         dataerrln("Fail new Calendar: %s", u_errorName(status));
889         return;
890     }
891     for (int32_t i = 0; ISO_STR[i][0] != NULL; i++) {
892         for (int32_t j = 0; PATTERN[j] != 0; j++) {
893             if (ISO_STR[i][j] == 0) {
894                 continue;
895             }
896             ParsePosition pos(0);
897             SimpleTimeZone* bogusTZ = new SimpleTimeZone(-1, UnicodeString("Zone Offset: -1ms"));
898             outcal->adoptTimeZone(bogusTZ);
899             sdf->applyPattern(PATTERN[j]);
900
901             sdf->parse(UnicodeString(ISO_STR[i][j]), *(outcal.getAlias()), pos);
902
903             if (pos.getIndex() != (int32_t)uprv_strlen(ISO_STR[i][j])) {
904                 errln((UnicodeString)"FAIL: Failed to parse the entire input string: " + ISO_STR[i][j]);
905             }
906
907             const TimeZone& outtz = outcal->getTimeZone();
908             int32_t outOffset = outtz.getRawOffset();
909             int32_t adjustedOffset = OFFSET[i] / MIN_OFFSET_UNIT[j] * MIN_OFFSET_UNIT[j];
910             if (outOffset != adjustedOffset) {
911                 errln((UnicodeString)"FAIL: Incorrect offset:" + outOffset + "ms for input string: " + ISO_STR[i][j]
912                     + " (expected:" + adjustedOffset + "ms)");
913             }
914         }
915     }
916 }
917
918
919 #endif /* #if !UCONFIG_NO_FORMATTING */