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