]> git.saurik.com Git - apple/icu.git/blame - icuSources/test/intltest/tzfmttst.cpp
ICU-531.31.tar.gz
[apple/icu.git] / icuSources / test / intltest / tzfmttst.cpp
CommitLineData
46f4442e
A
1/*
2*******************************************************************************
efa1e659 3* Copyright (C) 2007-2014, International Business Machines Corporation and *
46f4442e
A
4* others. All Rights Reserved. *
5*******************************************************************************
6*/
7#include "unicode/utypes.h"
8
9#if !UCONFIG_NO_FORMATTING
10
11#include "tzfmttst.h"
12
729e4ab9 13#include "simplethread.h"
46f4442e
A
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"
51004dcb
A
21#include "unicode/tzfmt.h"
22#include "unicode/localpointer.h"
46f4442e 23#include "cstring.h"
51004dcb
A
24#include "zonemeta.h"
25
26static 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};
46f4442e
A
50static const int NUM_PATTERNS = sizeof(PATTERNS)/sizeof(const char*);
51
51004dcb
A
52static const UChar ETC_UNKNOWN[] = {0x45, 0x74, 0x63, 0x2F, 0x55, 0x6E, 0x6B, 0x6E, 0x6F, 0x77, 0x6E, 0};
53
54static const UChar ETC_SLASH[] = { 0x45, 0x74, 0x63, 0x2F, 0 }; // "Etc/"
55static const UChar SYSTEMV_SLASH[] = { 0x53, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x56, 0x2F, 0 }; // "SystemV/
56static const UChar RIYADH8[] = { 0x52, 0x69, 0x79, 0x61, 0x64, 0x68, 0x38, 0 }; // "Riyadh8"
57
58static 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
46f4442e
A
67void
68TimeZoneFormatTest::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);
51004dcb
A
76 TESTCASE(2, TestParse);
77 TESTCASE(3, TestISOFormat);
57a6839d 78 TESTCASE(4, TestFormat);
46f4442e
A
79 default: name = ""; break;
80 }
81}
82
83void
84TimeZoneFormatTest::TestTimeZoneRoundTrip(void) {
85 UErrorCode status = U_ZERO_ERROR;
86
51004dcb 87 SimpleTimeZone unknownZone(-31415, ETC_UNKNOWN);
46f4442e
A
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)) {
729e4ab9 102 dataerrln("Calendar::createInstance failed: %s", u_errorName(status));
46f4442e
A
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
729e4ab9 131 const Locale testLocales[] = {
46f4442e
A
132 Locale("en"),
133 Locale("en_CA"),
134 Locale("fr"),
135 Locale("zh_Hant")
136 };
137
138 const Locale *LOCALES;
139 int32_t nLocales;
729e4ab9
A
140
141 if (quick) {
142 LOCALES = testLocales;
143 nLocales = sizeof(testLocales)/sizeof(Locale);
46f4442e 144 } else {
729e4ab9 145 LOCALES = Locale::getAvailableLocales(nLocales);
46f4442e
A
146 }
147
148 StringEnumeration *tzids = TimeZone::createEnumeration();
46f4442e
A
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++) {
4388f060
A
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
46f4442e
A
163 for (int32_t patidx = 0; patidx < NUM_PATTERNS; patidx++) {
164
46f4442e
A
165 SimpleDateFormat *sdf = new SimpleDateFormat((UnicodeString)PATTERNS[patidx], LOCALES[locidx], status);
166 if (U_FAILURE(status)) {
4388f060 167 dataerrln((UnicodeString)"new SimpleDateFormat failed for pattern " +
729e4ab9 168 PATTERNS[patidx] + " for locale " + LOCALES[locidx].getName() + " - " + u_errorName(status));
46f4442e
A
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
51004dcb
A
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) {
729e4ab9
A
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.
46f4442e
A
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)) {
729e4ab9
A
263 if (canonical.indexOf((UChar)0x27 /*'/'*/) == -1) {
264 // Exceptional cases, such as CET, EET, MET and WET
51004dcb 265 logln((UnicodeString)"Canonical round trip failed (as expected); tz=" + *tzid
729e4ab9
A
266 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
267 + ", time=" + DATES[datidx] + ", str=" + tzstr
268 + ", outtz=" + outtzid);
269 } else {
51004dcb 270 errln((UnicodeString)"Canonical round trip failed; tz=" + *tzid
729e4ab9
A
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 {
51004dcb
A
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
4388f060
A
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 }
729e4ab9 299 }
51004dcb 300 isOffsetFormat = (numDigits > 0);
729e4ab9 301 }
4388f060 302 if (isOffsetFormat || tzstr == localGMTString) {
51004dcb 303 // Localized GMT or ISO: total offset (raw + dst) must be preserved.
729e4ab9
A
304 int32_t inOffset = inRaw + inDst;
305 int32_t outOffset = outRaw + outDst;
51004dcb
A
306 int32_t diff = outOffset - inOffset;
307 if (minutesOffset) {
308 diff = (diff / 60000) * 60000;
309 }
310 if (diff != 0) {
729e4ab9 311 errln((UnicodeString)"Offset round trip failed; tz=" + *tzid
46f4442e
A
312 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
313 + ", time=" + DATES[datidx] + ", str=" + tzstr
729e4ab9 314 + ", inOffset=" + inOffset + ", outOffset=" + outOffset);
46f4442e 315 }
729e4ab9
A
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);
46f4442e
A
323 }
324 }
325 }
326 delete outcal;
327 }
328 delete tz;
329 }
330 delete sdf;
331 }
332 }
333 delete cal;
334 delete tzids;
335}
336
efa1e659
A
337// Special exclusions in TestTimeZoneRoundTrip.
338// These special cases do not round trip time as designed.
339static UBool isSpecialTimeRoundTripCase(const char* loc,
340 const UnicodeString& id,
341 const char* pattern,
342 UDate time) {
343 struct {
344 const char* loc;
345 const char* id;
346 const char* pattern;
347 UDate time;
348 } EXCLUSIONS[] = {
349 {NULL, "Asia/Chita", "zzzz", 1414252800000.0},
350 {NULL, "Asia/Chita", "vvvv", 1414252800000.0},
351 {NULL, "Asia/Srednekolymsk", "zzzz", 1414241999999.0},
352 {NULL, "Asia/Srednekolymsk", "vvvv", 1414241999999.0},
353 {NULL, NULL, NULL, U_DATE_MIN}
354 };
355
356 UBool isExcluded = FALSE;
357 for (int32_t i = 0; EXCLUSIONS[i].id != NULL; i++) {
358 if (EXCLUSIONS[i].loc == NULL || uprv_strcmp(loc, EXCLUSIONS[i].loc) == 0) {
359 if (id.compare(EXCLUSIONS[i].id) == 0) {
360 if (EXCLUSIONS[i].pattern == NULL || uprv_strcmp(pattern, EXCLUSIONS[i].pattern) == 0) {
361 if (EXCLUSIONS[i].time == U_DATE_MIN || EXCLUSIONS[i].time == time) {
362 isExcluded = TRUE;
363 }
364 }
365 }
366 }
367 }
368 return isExcluded;
369}
370
729e4ab9
A
371struct LocaleData {
372 int32_t index;
373 int32_t testCounts;
374 UDate *times;
375 const Locale* locales; // Static
376 int32_t nLocales; // Static
377 UBool quick; // Static
378 UDate START_TIME; // Static
379 UDate END_TIME; // Static
380 int32_t numDone;
381};
382
383class TestTimeRoundTripThread: public SimpleThread {
384public:
385 TestTimeRoundTripThread(IntlTest& tlog, LocaleData &ld, int32_t i)
386 : log(tlog), data(ld), index(i) {}
387 virtual void run() {
388 UErrorCode status = U_ZERO_ERROR;
389 UBool REALLY_VERBOSE = FALSE;
390
51004dcb
A
391 // These patterns are ambiguous at DST->STD local time overlap
392 const char* AMBIGUOUS_DST_DECESSION[] = { "v", "vvvv", "V", "VV", "VVV", "VVVV", 0 };
393
394 // These patterns are ambiguous at STD->STD/DST->DST local time overlap
395 const char* AMBIGUOUS_NEGATIVE_SHIFT[] = { "z", "zzzz", "v", "vvvv", "V", "VV", "VVV", "VVVV", 0 };
396
397 // These patterns only support integer minutes offset
398 const char* MINUTES_OFFSET[] = { "X", "XX", "XXX", "x", "xx", "xxx", 0 };
729e4ab9
A
399
400 // Workaround for #6338
401 //UnicodeString BASEPATTERN("yyyy-MM-dd'T'HH:mm:ss.SSS");
402 UnicodeString BASEPATTERN("yyyy.MM.dd HH:mm:ss.SSS");
403
404 // timer for performance analysis
405 UDate timer;
406 UDate testTimes[4];
407 UBool expectedRoundTrip[4];
408 int32_t testLen = 0;
409
4388f060 410 StringEnumeration *tzids = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL, NULL, NULL, status);
729e4ab9 411 if (U_FAILURE(status)) {
4388f060
A
412 if (status == U_MISSING_RESOURCE_ERROR) {
413 /* This error is generally caused by data not being present. However, an infinite loop will occur
414 * because the thread thinks that the test data is never done so we should treat the data as done.
415 */
416 log.dataerrln("TimeZone::createTimeZoneIDEnumeration failed - %s", u_errorName(status));
417 data.numDone = data.nLocales;
418 } else {
419 log.errln("TimeZone::createTimeZoneIDEnumeration failed: %s", u_errorName(status));
420 }
729e4ab9
A
421 return;
422 }
423
424 int32_t locidx = -1;
425 UDate times[NUM_PATTERNS];
426 for (int32_t i = 0; i < NUM_PATTERNS; i++) {
427 times[i] = 0;
428 }
429
430 int32_t testCounts = 0;
431
432 while (true) {
433 umtx_lock(NULL); // Lock to increment the index
434 for (int32_t i = 0; i < NUM_PATTERNS; i++) {
435 data.times[i] += times[i];
436 data.testCounts += testCounts;
437 }
438 if (data.index < data.nLocales) {
439 locidx = data.index;
440 data.index++;
441 } else {
442 locidx = -1;
443 }
444 umtx_unlock(NULL); // Unlock for other threads to use
445
446 if (locidx == -1) {
447 log.logln((UnicodeString) "Thread " + index + " is done.");
448 break;
449 }
450
451 log.logln((UnicodeString) "\nThread " + index + ": Locale: " + UnicodeString(data.locales[locidx].getName()));
452
453 for (int32_t patidx = 0; patidx < NUM_PATTERNS; patidx++) {
454 log.logln((UnicodeString) " Pattern: " + PATTERNS[patidx]);
455 times[patidx] = 0;
456
457 UnicodeString pattern(BASEPATTERN);
458 pattern.append(" ").append(PATTERNS[patidx]);
459
460 SimpleDateFormat *sdf = new SimpleDateFormat(pattern, data.locales[locidx], status);
461 if (U_FAILURE(status)) {
462 log.errcheckln(status, (UnicodeString) "new SimpleDateFormat failed for pattern " +
463 pattern + " for locale " + data.locales[locidx].getName() + " - " + u_errorName(status));
464 status = U_ZERO_ERROR;
465 continue;
466 }
467
51004dcb
A
468 UBool minutesOffset = contains(MINUTES_OFFSET, PATTERNS[patidx]);
469
729e4ab9
A
470 tzids->reset(status);
471 const UnicodeString *tzid;
472
473 timer = Calendar::getNow();
474
475 while ((tzid = tzids->snext(status))) {
51004dcb
A
476 if (uprv_strcmp(PATTERNS[patidx], "V") == 0) {
477 // Some zones do not have short ID assigned, such as Asia/Riyadh87.
478 // The time roundtrip will fail for such zones with pattern "V" (short zone ID).
479 // This is expected behavior.
480 const UChar* shortZoneID = ZoneMeta::getShortID(*tzid);
481 if (shortZoneID == NULL) {
482 continue;
483 }
484 } else if (uprv_strcmp(PATTERNS[patidx], "VVV") == 0) {
485 // Some zones are not associated with any region, such as Etc/GMT+8.
486 // The time roundtrip will fail for such zone with pattern "VVV" (exemplar location).
487 // This is expected behavior.
488 if (tzid->indexOf((UChar)0x2F) < 0 || tzid->indexOf(ETC_SLASH, -1, 0) >= 0
489 || tzid->indexOf(SYSTEMV_SLASH, -1, 0) >= 0 || tzid->indexOf(RIYADH8, -1, 0) >= 0) {
490 continue;
491 }
492 }
493
57a6839d
A
494 if (*tzid == "Pacific/Apia" && uprv_strcmp(PATTERNS[patidx], "vvvv") == 0
495 && log.logKnownIssue("11052", "Ambiguous zone name - Samoa Time")) {
efa1e659
A
496 continue;
497 }
498
729e4ab9
A
499 BasicTimeZone *tz = (BasicTimeZone*) TimeZone::createTimeZone(*tzid);
500 sdf->setTimeZone(*tz);
501
502 UDate t = data.START_TIME;
503 TimeZoneTransition tzt;
504 UBool tztAvail = FALSE;
505 UBool middle = TRUE;
506
507 while (t < data.END_TIME) {
508 if (!tztAvail) {
509 testTimes[0] = t;
510 expectedRoundTrip[0] = TRUE;
511 testLen = 1;
512 } else {
513 int32_t fromOffset = tzt.getFrom()->getRawOffset() + tzt.getFrom()->getDSTSavings();
514 int32_t toOffset = tzt.getTo()->getRawOffset() + tzt.getTo()->getDSTSavings();
515 int32_t delta = toOffset - fromOffset;
516 if (delta < 0) {
517 UBool isDstDecession = tzt.getFrom()->getDSTSavings() > 0 && tzt.getTo()->getDSTSavings() == 0;
518 testTimes[0] = t + delta - 1;
519 expectedRoundTrip[0] = TRUE;
520 testTimes[1] = t + delta;
51004dcb
A
521 expectedRoundTrip[1] = isDstDecession ?
522 !contains(AMBIGUOUS_DST_DECESSION, PATTERNS[patidx]) :
523 !contains(AMBIGUOUS_NEGATIVE_SHIFT, PATTERNS[patidx]);
729e4ab9 524 testTimes[2] = t - 1;
51004dcb
A
525 expectedRoundTrip[2] = isDstDecession ?
526 !contains(AMBIGUOUS_DST_DECESSION, PATTERNS[patidx]) :
527 !contains(AMBIGUOUS_NEGATIVE_SHIFT, PATTERNS[patidx]);
729e4ab9
A
528 testTimes[3] = t;
529 expectedRoundTrip[3] = TRUE;
530 testLen = 4;
531 } else {
532 testTimes[0] = t - 1;
533 expectedRoundTrip[0] = TRUE;
534 testTimes[1] = t;
535 expectedRoundTrip[1] = TRUE;
536 testLen = 2;
537 }
538 }
539 for (int32_t testidx = 0; testidx < testLen; testidx++) {
540 if (data.quick) {
541 // reduce regular test time
542 if (!expectedRoundTrip[testidx]) {
543 continue;
544 }
545 }
546
547 testCounts++;
548
549 UnicodeString text;
550 FieldPosition fpos(0);
551 sdf->format(testTimes[testidx], text, fpos);
552
553 UDate parsedDate = sdf->parse(text, status);
554 if (U_FAILURE(status)) {
555 log.errln((UnicodeString) "Parse failure for text=" + text + ", tzid=" + *tzid + ", locale=" + data.locales[locidx].getName()
556 + ", pattern=" + PATTERNS[patidx] + ", time=" + testTimes[testidx]);
557 status = U_ZERO_ERROR;
558 continue;
559 }
51004dcb
A
560
561 int32_t timeDiff = (int32_t)(parsedDate - testTimes[testidx]);
562 UBool bTimeMatch = minutesOffset ?
563 (timeDiff/60000)*60000 == 0 : timeDiff == 0;
564 if (!bTimeMatch) {
729e4ab9
A
565 UnicodeString msg = (UnicodeString) "Time round trip failed for " + "tzid=" + *tzid + ", locale=" + data.locales[locidx].getName() + ", pattern=" + PATTERNS[patidx]
566 + ", text=" + text + ", time=" + testTimes[testidx] + ", restime=" + parsedDate + ", diff=" + (parsedDate - testTimes[testidx]);
4388f060 567 // Timebomb for TZData update
efa1e659
A
568 if (expectedRoundTrip[testidx]
569 && !isSpecialTimeRoundTripCase(data.locales[locidx].getName(), *tzid,
570 PATTERNS[patidx], testTimes[testidx])) {
729e4ab9
A
571 log.errln((UnicodeString) "FAIL: " + msg);
572 } else if (REALLY_VERBOSE) {
573 log.logln(msg);
574 }
575 }
576 }
577 tztAvail = tz->getNextTransition(t, FALSE, tzt);
578 if (!tztAvail) {
579 break;
580 }
581 if (middle) {
582 // Test the date in the middle of two transitions.
583 t += (int64_t) ((tzt.getTime() - t) / 2);
584 middle = FALSE;
585 tztAvail = FALSE;
586 } else {
587 t = tzt.getTime();
588 }
589 }
590 delete tz;
591 }
592 times[patidx] += (Calendar::getNow() - timer);
593 delete sdf;
594 }
595 umtx_lock(NULL);
596 data.numDone++;
597 umtx_unlock(NULL);
598 }
599 delete tzids;
600 }
601private:
602 IntlTest& log;
603 LocaleData& data;
604 int32_t index;
605};
606
46f4442e
A
607void
608TimeZoneFormatTest::TestTimeRoundTrip(void) {
729e4ab9
A
609 int32_t nThreads = threadCount;
610 const Locale *LOCALES;
611 int32_t nLocales;
612 int32_t testCounts = 0;
46f4442e 613
729e4ab9
A
614 UErrorCode status = U_ZERO_ERROR;
615 Calendar *cal = Calendar::createInstance(TimeZone::createTimeZone((UnicodeString) "UTC"), status);
46f4442e 616 if (U_FAILURE(status)) {
729e4ab9 617 dataerrln("Calendar::createInstance failed: %s", u_errorName(status));
46f4442e
A
618 return;
619 }
620
729e4ab9
A
621 const char* testAllProp = getProperty("TimeZoneRoundTripAll");
622 UBool bTestAll = (testAllProp && uprv_strcmp(testAllProp, "true") == 0);
46f4442e 623
729e4ab9
A
624 UDate START_TIME, END_TIME;
625 if (bTestAll || !quick) {
46f4442e
A
626 cal->set(1900, UCAL_JANUARY, 1);
627 } else {
729e4ab9 628 cal->set(1990, UCAL_JANUARY, 1);
46f4442e
A
629 }
630 START_TIME = cal->getTime(status);
631
632 cal->set(2015, UCAL_JANUARY, 1);
633 END_TIME = cal->getTime(status);
729e4ab9 634
46f4442e
A
635 if (U_FAILURE(status)) {
636 errln("getTime failed");
637 return;
638 }
639
46f4442e
A
640 UDate times[NUM_PATTERNS];
641 for (int32_t i = 0; i < NUM_PATTERNS; i++) {
642 times[i] = 0;
643 }
644
46f4442e 645 // Set up test locales
729e4ab9 646 const Locale locales1[] = {Locale("en")};
46f4442e 647 const Locale locales2[] = {
729e4ab9
A
648 Locale("ar_EG"), Locale("bg_BG"), Locale("ca_ES"), Locale("da_DK"), Locale("de"),
649 Locale("de_DE"), Locale("el_GR"), Locale("en"), Locale("en_AU"), Locale("en_CA"),
650 Locale("en_US"), Locale("es"), Locale("es_ES"), Locale("es_MX"), Locale("fi_FI"),
651 Locale("fr"), Locale("fr_CA"), Locale("fr_FR"), Locale("he_IL"), Locale("hu_HU"),
652 Locale("it"), Locale("it_IT"), Locale("ja"), Locale("ja_JP"), Locale("ko"),
653 Locale("ko_KR"), Locale("nb_NO"), Locale("nl_NL"), Locale("nn_NO"), Locale("pl_PL"),
654 Locale("pt"), Locale("pt_BR"), Locale("pt_PT"), Locale("ru_RU"), Locale("sv_SE"),
655 Locale("th_TH"), Locale("tr_TR"), Locale("zh"), Locale("zh_Hans"), Locale("zh_Hans_CN"),
656 Locale("zh_Hant"), Locale("zh_Hant_TW")
46f4442e
A
657 };
658
729e4ab9 659 if (bTestAll) {
46f4442e
A
660 LOCALES = Locale::getAvailableLocales(nLocales);
661 } else if (quick) {
662 LOCALES = locales1;
663 nLocales = sizeof(locales1)/sizeof(Locale);
664 } else {
665 LOCALES = locales2;
666 nLocales = sizeof(locales2)/sizeof(Locale);
667 }
668
729e4ab9
A
669 LocaleData data;
670 data.index = 0;
671 data.testCounts = testCounts;
672 data.times = times;
673 data.locales = LOCALES;
674 data.nLocales = nLocales;
675 data.quick = quick;
676 data.START_TIME = START_TIME;
677 data.END_TIME = END_TIME;
678 data.numDone = 0;
679
680#if (ICU_USE_THREADS==0)
681 TestTimeRoundTripThread fakeThread(*this, data, 0);
682 fakeThread.run();
683#else
684 TestTimeRoundTripThread **threads = new TestTimeRoundTripThread*[threadCount];
685 int32_t i;
686 for (i = 0; i < nThreads; i++) {
687 threads[i] = new TestTimeRoundTripThread(*this, data, i);
688 if (threads[i]->start() != 0) {
689 errln("Error starting thread %d", i);
690 }
46f4442e
A
691 }
692
729e4ab9
A
693 UBool done = false;
694 while (true) {
695 umtx_lock(NULL);
696 if (data.numDone == nLocales) {
697 done = true;
46f4442e 698 }
729e4ab9
A
699 umtx_unlock(NULL);
700 if (done)
701 break;
702 SimpleThread::sleep(1000);
46f4442e 703 }
729e4ab9
A
704
705 for (i = 0; i < nThreads; i++) {
706 delete threads[i];
707 }
708 delete [] threads;
709
710#endif
46f4442e
A
711 UDate total = 0;
712 logln("### Elapsed time by patterns ###");
713 for (int32_t i = 0; i < NUM_PATTERNS; i++) {
729e4ab9
A
714 logln(UnicodeString("") + data.times[i] + "ms (" + PATTERNS[i] + ")");
715 total += data.times[i];
46f4442e 716 }
729e4ab9
A
717 logln((UnicodeString) "Total: " + total + "ms");
718 logln((UnicodeString) "Iteration: " + data.testCounts);
46f4442e
A
719
720 delete cal;
46f4442e
A
721}
722
51004dcb
A
723
724typedef struct {
725 const char* text;
726 int32_t inPos;
727 const char* locale;
728 UTimeZoneFormatStyle style;
729 UBool parseAll;
730 const char* expected;
731 int32_t outPos;
732 UTimeZoneFormatTimeType timeType;
733} ParseTestData;
734
735void
736TimeZoneFormatTest::TestParse(void) {
737 const ParseTestData DATA[] = {
738 // text inPos locale style parseAll expected outPos timeType
739 {"Z", 0, "en_US", UTZFMT_STYLE_ISO_EXTENDED_FULL, false, "Etc/GMT", 1, UTZFMT_TIME_TYPE_UNKNOWN},
740 {"Z", 0, "en_US", UTZFMT_STYLE_SPECIFIC_LONG, false, "Etc/GMT", 1, UTZFMT_TIME_TYPE_UNKNOWN},
741 {"Zambia time", 0, "en_US", UTZFMT_STYLE_ISO_EXTENDED_FULL, true, "Etc/GMT", 1, UTZFMT_TIME_TYPE_UNKNOWN},
742 {"Zambia time", 0, "en_US", UTZFMT_STYLE_GENERIC_LOCATION, false, "Africa/Lusaka", 11, UTZFMT_TIME_TYPE_UNKNOWN},
743 {"Zambia time", 0, "en_US", UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL, true, "Africa/Lusaka", 11, UTZFMT_TIME_TYPE_UNKNOWN},
744 {"+00:00", 0, "en_US", UTZFMT_STYLE_ISO_EXTENDED_FULL, false, "Etc/GMT", 6, UTZFMT_TIME_TYPE_UNKNOWN},
745 {"-01:30:45", 0, "en_US", UTZFMT_STYLE_ISO_EXTENDED_FULL, false, "GMT-01:30:45", 9, UTZFMT_TIME_TYPE_UNKNOWN},
746 {"-7", 0, "en_US", UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL, false, "GMT-07:00", 2, UTZFMT_TIME_TYPE_UNKNOWN},
747 {"-2222", 0, "en_US", UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL, false, "GMT-22:22", 5, UTZFMT_TIME_TYPE_UNKNOWN},
748 {"-3333", 0, "en_US", UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL, false, "GMT-03:33", 4, UTZFMT_TIME_TYPE_UNKNOWN},
749 {"XXX+01:30YYY", 3, "en_US", UTZFMT_STYLE_LOCALIZED_GMT, false, "GMT+01:30", 9, UTZFMT_TIME_TYPE_UNKNOWN},
750 {"GMT0", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT, false, "Etc/GMT", 3, UTZFMT_TIME_TYPE_UNKNOWN},
751 {"EST", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT, false, "America/New_York", 3, UTZFMT_TIME_TYPE_STANDARD},
752 {"ESTx", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT, false, "America/New_York", 3, UTZFMT_TIME_TYPE_STANDARD},
753 {"EDTx", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT, false, "America/New_York", 3, UTZFMT_TIME_TYPE_DAYLIGHT},
754 {"EST", 0, "en_US", UTZFMT_STYLE_SPECIFIC_LONG, false, NULL, 0, UTZFMT_TIME_TYPE_UNKNOWN},
755 {"EST", 0, "en_US", UTZFMT_STYLE_SPECIFIC_LONG, true, "America/New_York", 3, UTZFMT_TIME_TYPE_STANDARD},
756 {"EST", 0, "en_CA", UTZFMT_STYLE_SPECIFIC_SHORT, false, "America/Toronto", 3, UTZFMT_TIME_TYPE_STANDARD},
757 {NULL, 0, NULL, UTZFMT_STYLE_GENERIC_LOCATION, false, NULL, 0, UTZFMT_TIME_TYPE_UNKNOWN}
758 };
759
760 for (int32_t i = 0; DATA[i].text; i++) {
761 UErrorCode status = U_ZERO_ERROR;
762 LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(Locale(DATA[i].locale), status));
763 if (U_FAILURE(status)) {
764 dataerrln("Fail TimeZoneFormat::createInstance: %s", u_errorName(status));
765 continue;
766 }
767 UTimeZoneFormatTimeType ttype = UTZFMT_TIME_TYPE_UNKNOWN;
768 ParsePosition pos(DATA[i].inPos);
769 int32_t parseOptions = DATA[i].parseAll ? UTZFMT_PARSE_OPTION_ALL_STYLES : UTZFMT_PARSE_OPTION_NONE;
770 TimeZone* tz = tzfmt->parse(DATA[i].style, DATA[i].text, pos, parseOptions, &ttype);
771
772 UnicodeString errMsg;
773 if (tz) {
774 UnicodeString outID;
775 tz->getID(outID);
776 if (outID != UnicodeString(DATA[i].expected)) {
777 errMsg = (UnicodeString)"Time zone ID: " + outID + " - expected: " + DATA[i].expected;
778 } else if (pos.getIndex() != DATA[i].outPos) {
779 errMsg = (UnicodeString)"Parsed pos: " + pos.getIndex() + " - expected: " + DATA[i].outPos;
780 } else if (ttype != DATA[i].timeType) {
781 errMsg = (UnicodeString)"Time type: " + ttype + " - expected: " + DATA[i].timeType;
782 }
783 delete tz;
784 } else {
785 if (DATA[i].expected) {
786 errln((UnicodeString)"Fail: Parse failure - expected: " + DATA[i].expected);
787 }
788 }
789 if (errMsg.length() > 0) {
790 errln((UnicodeString)"Fail: " + errMsg + " [text=" + DATA[i].text + ", pos=" + DATA[i].inPos + ", style=" + DATA[i].style + "]");
791 }
792 }
793}
794
795void
796TimeZoneFormatTest::TestISOFormat(void) {
797 const int32_t OFFSET[] = {
798 0, // 0
799 999, // 0.999s
800 -59999, // -59.999s
801 60000, // 1m
802 -77777, // -1m 17.777s
803 1800000, // 30m
804 -3600000, // -1h
805 36000000, // 10h
806 -37800000, // -10h 30m
807 -37845000, // -10h 30m 45s
808 108000000, // 30h
809 };
810
811 const char* ISO_STR[][11] = {
812 // 0
813 {
814 "Z", "Z", "Z", "Z", "Z",
815 "+00", "+0000", "+00:00", "+0000", "+00:00",
816 "+0000"
817 },
818 // 999
819 {
820 "Z", "Z", "Z", "Z", "Z",
821 "+00", "+0000", "+00:00", "+0000", "+00:00",
822 "+0000"
823 },
824 // -59999
825 {
826 "Z", "Z", "Z", "-000059", "-00:00:59",
827 "+00", "+0000", "+00:00", "-000059", "-00:00:59",
828 "-000059"
829 },
830 // 60000
831 {
832 "+0001", "+0001", "+00:01", "+0001", "+00:01",
833 "+0001", "+0001", "+00:01", "+0001", "+00:01",
834 "+0001"
835 },
836 // -77777
837 {
838 "-0001", "-0001", "-00:01", "-000117", "-00:01:17",
839 "-0001", "-0001", "-00:01", "-000117", "-00:01:17",
840 "-000117"
841 },
842 // 1800000
843 {
844 "+0030", "+0030", "+00:30", "+0030", "+00:30",
845 "+0030", "+0030", "+00:30", "+0030", "+00:30",
846 "+0030"
847 },
848 // -3600000
849 {
850 "-01", "-0100", "-01:00", "-0100", "-01:00",
851 "-01", "-0100", "-01:00", "-0100", "-01:00",
852 "-0100"
853 },
854 // 36000000
855 {
856 "+10", "+1000", "+10:00", "+1000", "+10:00",
857 "+10", "+1000", "+10:00", "+1000", "+10:00",
858 "+1000"
859 },
860 // -37800000
861 {
862 "-1030", "-1030", "-10:30", "-1030", "-10:30",
863 "-1030", "-1030", "-10:30", "-1030", "-10:30",
864 "-1030"
865 },
866 // -37845000
867 {
868 "-1030", "-1030", "-10:30", "-103045", "-10:30:45",
869 "-1030", "-1030", "-10:30", "-103045", "-10:30:45",
870 "-103045"
871 },
872 // 108000000
873 {
874 0, 0, 0, 0, 0,
875 0, 0, 0, 0, 0,
876 0
877 }
878 };
879
880 const char* PATTERN[] = {
881 "X", "XX", "XXX", "XXXX", "XXXXX",
882 "x", "xx", "xxx", "xxxx", "xxxxx",
883 "Z", // equivalent to "xxxx"
884 0
885 };
886
887 const int32_t MIN_OFFSET_UNIT[] = {
888 60000, 60000, 60000, 1000, 1000,
889 60000, 60000, 60000, 1000, 1000,
890 1000,
891 };
892
893 // Formatting
894 UErrorCode status = U_ZERO_ERROR;
895 LocalPointer<SimpleDateFormat> sdf(new SimpleDateFormat(status));
896 if (U_FAILURE(status)) {
897 dataerrln("Fail new SimpleDateFormat: %s", u_errorName(status));
898 return;
899 }
900 UDate d = Calendar::getNow();
901
902 for (uint32_t i = 0; i < sizeof(OFFSET)/sizeof(OFFSET[0]); i++) {
903 SimpleTimeZone* tz = new SimpleTimeZone(OFFSET[i], UnicodeString("Zone Offset:") + OFFSET[i] + "ms");
904 sdf->adoptTimeZone(tz);
905 for (int32_t j = 0; PATTERN[j] != 0; j++) {
906 sdf->applyPattern(UnicodeString(PATTERN[j]));
907 UnicodeString result;
908 sdf->format(d, result);
909
910 if (ISO_STR[i][j]) {
911 if (result != UnicodeString(ISO_STR[i][j])) {
912 errln((UnicodeString)"FAIL: pattern=" + PATTERN[j] + ", offset=" + OFFSET[i] + " -> "
913 + result + " (expected: " + ISO_STR[i][j] + ")");
914 }
915 } else {
916 // Offset out of range
917 // Note: for now, there is no way to propagate the error status through
918 // the SimpleDateFormat::format above.
919 if (result.length() > 0) {
920 errln((UnicodeString)"FAIL: Non-Empty result for pattern=" + PATTERN[j] + ", offset=" + OFFSET[i]
921 + " (expected: empty result)");
922 }
923 }
924 }
925 }
926
927 // Parsing
928 LocalPointer<Calendar> outcal(Calendar::createInstance(status));
929 if (U_FAILURE(status)) {
930 dataerrln("Fail new Calendar: %s", u_errorName(status));
931 return;
932 }
933 for (int32_t i = 0; ISO_STR[i][0] != NULL; i++) {
934 for (int32_t j = 0; PATTERN[j] != 0; j++) {
935 if (ISO_STR[i][j] == 0) {
936 continue;
937 }
938 ParsePosition pos(0);
939 SimpleTimeZone* bogusTZ = new SimpleTimeZone(-1, UnicodeString("Zone Offset: -1ms"));
940 outcal->adoptTimeZone(bogusTZ);
941 sdf->applyPattern(PATTERN[j]);
942
943 sdf->parse(UnicodeString(ISO_STR[i][j]), *(outcal.getAlias()), pos);
944
945 if (pos.getIndex() != (int32_t)uprv_strlen(ISO_STR[i][j])) {
946 errln((UnicodeString)"FAIL: Failed to parse the entire input string: " + ISO_STR[i][j]);
947 }
948
949 const TimeZone& outtz = outcal->getTimeZone();
950 int32_t outOffset = outtz.getRawOffset();
951 int32_t adjustedOffset = OFFSET[i] / MIN_OFFSET_UNIT[j] * MIN_OFFSET_UNIT[j];
952 if (outOffset != adjustedOffset) {
953 errln((UnicodeString)"FAIL: Incorrect offset:" + outOffset + "ms for input string: " + ISO_STR[i][j]
954 + " (expected:" + adjustedOffset + "ms)");
955 }
956 }
957 }
958}
959
960
57a6839d
A
961typedef struct {
962 const char* locale;
963 const char* tzid;
964 UDate date;
965 UTimeZoneFormatStyle style;
966 const char* expected;
967 UTimeZoneFormatTimeType timeType;
968} FormatTestData;
969
970void
971TimeZoneFormatTest::TestFormat(void) {
972 UDate dateJan = 1358208000000.0; // 2013-01-15T00:00:00Z
973 UDate dateJul = 1373846400000.0; // 2013-07-15T00:00:00Z
974
975 const FormatTestData DATA[] = {
976 {
977 "en",
978 "America/Los_Angeles",
979 dateJan,
980 UTZFMT_STYLE_GENERIC_LOCATION,
981 "Los Angeles Time",
982 UTZFMT_TIME_TYPE_UNKNOWN
983 },
984 {
985 "en",
986 "America/Los_Angeles",
987 dateJan,
988 UTZFMT_STYLE_GENERIC_LONG,
989 "Pacific Time",
990 UTZFMT_TIME_TYPE_UNKNOWN
991 },
992 {
993 "en",
994 "America/Los_Angeles",
995 dateJan,
996 UTZFMT_STYLE_SPECIFIC_LONG,
997 "Pacific Standard Time",
998 UTZFMT_TIME_TYPE_STANDARD
999 },
1000 {
1001 "en",
1002 "America/Los_Angeles",
1003 dateJul,
1004 UTZFMT_STYLE_SPECIFIC_LONG,
1005 "Pacific Daylight Time",
1006 UTZFMT_TIME_TYPE_DAYLIGHT
1007 },
1008 {
1009 "ja",
1010 "America/Los_Angeles",
1011 dateJan,
1012 UTZFMT_STYLE_ZONE_ID,
1013 "America/Los_Angeles",
1014 UTZFMT_TIME_TYPE_UNKNOWN
1015 },
1016 {
1017 "fr",
1018 "America/Los_Angeles",
1019 dateJul,
1020 UTZFMT_STYLE_ZONE_ID_SHORT,
1021 "uslax",
1022 UTZFMT_TIME_TYPE_UNKNOWN
1023 },
1024 {
1025 "en",
1026 "America/Los_Angeles",
1027 dateJan,
1028 UTZFMT_STYLE_EXEMPLAR_LOCATION,
1029 "Los Angeles",
1030 UTZFMT_TIME_TYPE_UNKNOWN
1031 },
1032
1033 {
1034 "ja",
1035 "Asia/Tokyo",
1036 dateJan,
1037 UTZFMT_STYLE_GENERIC_LONG,
1038 "\\u65E5\\u672C\\u6A19\\u6E96\\u6642",
1039 UTZFMT_TIME_TYPE_UNKNOWN
1040 },
1041
1042 {0, 0, 0.0, UTZFMT_STYLE_GENERIC_LOCATION, 0, UTZFMT_TIME_TYPE_UNKNOWN}
1043 };
1044
1045 for (int32_t i = 0; DATA[i].locale; i++) {
1046 UErrorCode status = U_ZERO_ERROR;
1047 LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(Locale(DATA[i].locale), status));
1048 if (U_FAILURE(status)) {
1049 dataerrln("Fail TimeZoneFormat::createInstance: %s", u_errorName(status));
1050 continue;
1051 }
1052
1053 LocalPointer<TimeZone> tz(TimeZone::createTimeZone(DATA[i].tzid));
1054 UnicodeString out;
1055 UTimeZoneFormatTimeType timeType;
1056
1057 tzfmt->format(DATA[i].style, *(tz.getAlias()), DATA[i].date, out, &timeType);
1058 UnicodeString expected(DATA[i].expected, -1, US_INV);
1059 expected = expected.unescape();
1060
1061 assertEquals(UnicodeString("Format result for ") + DATA[i].tzid + " (Test Case " + i + ")", expected, out);
1062 if (DATA[i].timeType != timeType) {
1063 dataerrln(UnicodeString("Formatted time zone type (Test Case ") + i + "), returned="
1064 + timeType + ", expected=" + DATA[i].timeType);
1065 }
1066 }
1067}
1068
46f4442e 1069#endif /* #if !UCONFIG_NO_FORMATTING */