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