• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
contains(const char ** list,const char * str)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
runIndexedTest(int32_t index,UBool exec,const char * & name,char *)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         TESTCASE(4, TestFormat);
79         default: name = ""; break;
80     }
81 }
82 
83 void
TestTimeZoneRoundTrip(void)84 TimeZoneFormatTest::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 
337 struct 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 
349 class TestTimeRoundTripThread: public SimpleThread {
350 public:
TestTimeRoundTripThread(IntlTest & tlog,LocaleData & ld,int32_t i)351     TestTimeRoundTripThread(IntlTest& tlog, LocaleData &ld, int32_t i)
352         : log(tlog), data(ld), index(i) {}
run()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     }
560 private:
561     IntlTest& log;
562     LocaleData& data;
563     int32_t index;
564 };
565 
566 void
TestTimeRoundTrip(void)567 TimeZoneFormatTest::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 
683 typedef 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 
694 void
TestParse(void)695 TimeZoneFormatTest::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 
754 void
TestISOFormat(void)755 TimeZoneFormatTest::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 
920 typedef 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 
929 void
TestFormat(void)930 TimeZoneFormatTest::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