]> git.saurik.com Git - apple/icu.git/blob - icuSources/test/intltest/tzfmttst.cpp
ICU-511.32.tar.gz
[apple/icu.git] / icuSources / 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 */