2 *******************************************************************************
3 * Copyright (C) 2007-2014, International Business Machines Corporation and *
4 * others. All Rights Reserved. *
5 *******************************************************************************
7 #include "unicode/utypes.h"
9 #if !UCONFIG_NO_FORMATTING
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"
26 static const char* PATTERNS
[] = {
29 "Z", // equivalent to "xxxx"
30 "ZZZZ", // equivalent to "OOOO"
50 static const int NUM_PATTERNS
= sizeof(PATTERNS
)/sizeof(const char*);
52 static const UChar ETC_UNKNOWN
[] = {0x45, 0x74, 0x63, 0x2F, 0x55, 0x6E, 0x6B, 0x6E, 0x6F, 0x77, 0x6E, 0};
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"
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) {
68 TimeZoneFormatTest::runIndexedTest( int32_t index
, UBool exec
, const char* &name
, char* /*par*/ )
71 logln("TestSuite TimeZoneFormatTest");
74 TESTCASE(0, TestTimeZoneRoundTrip
);
75 TESTCASE(1, TestTimeRoundTrip
);
76 TESTCASE(2, TestParse
);
77 TESTCASE(3, TestISOFormat
);
78 TESTCASE(4, TestFormat
);
79 default: name
= ""; break;
84 TimeZoneFormatTest::TestTimeZoneRoundTrip(void) {
85 UErrorCode status
= U_ZERO_ERROR
;
87 SimpleTimeZone
unknownZone(-31415, ETC_UNKNOWN
);
88 int32_t badDstOffset
= -1234;
89 int32_t badZoneOffset
= -2345;
91 int32_t testDateData
[][3] = {
100 Calendar
*cal
= Calendar::createInstance(TimeZone::createTimeZone((UnicodeString
)"UTC"), status
);
101 if (U_FAILURE(status
)) {
102 dataerrln("Calendar::createInstance failed: %s", u_errorName(status
));
106 // Set up rule equivalency test range
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");
118 UDate DATES
[(sizeof(testDateData
)/sizeof(int32_t))/3];
119 const int32_t nDates
= (sizeof(testDateData
)/sizeof(int32_t))/3;
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");
130 // Set up test locales
131 const Locale testLocales
[] = {
138 const Locale
*LOCALES
;
142 LOCALES
= testLocales
;
143 nLocales
= sizeof(testLocales
)/sizeof(Locale
);
145 LOCALES
= Locale::getAvailableLocales(nLocales
);
148 StringEnumeration
*tzids
= TimeZone::createEnumeration();
149 int32_t inRaw
, inDst
;
150 int32_t outRaw
, outDst
;
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
));
160 gmtFmt
.setTimeZone(*TimeZone::getGMT());
161 gmtFmt
.format(0.0, localGMTString
);
163 for (int32_t patidx
= 0; patidx
< NUM_PATTERNS
; patidx
++) {
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
;
173 tzids
->reset(status
);
174 const UnicodeString
*tzid
;
175 while ((tzid
= tzids
->snext(status
))) {
176 TimeZone
*tz
= TimeZone::createTimeZone(*tzid
);
178 for (int32_t datidx
= 0; datidx
< nDates
; datidx
++) {
180 FieldPosition
fpos(0);
182 sdf
->setTimeZone(*tz
);
183 sdf
->format(DATES
[datidx
], tzstr
, fpos
);
185 // Before parse, set unknown zone to SimpleDateFormat instance
186 // just for making sure that it does not depends on the time zone
188 sdf
->setTimeZone(unknownZone
);
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
;
198 outcal
->set(UCAL_DST_OFFSET
, badDstOffset
);
199 outcal
->set(UCAL_ZONE_OFFSET
, badZoneOffset
);
201 sdf
->parse(tzstr
, *outcal
, pos
);
204 const TimeZone
&outtz
= outcal
->getTimeZone();
205 UnicodeString outtzid
;
206 outtz
.getID(outtzid
);
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
;
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
;
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
);
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
);
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
);
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
);
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
);
275 if (U_FAILURE(status
)) {
276 errln("hasEquivalentTransitions failed");
277 status
= U_ZERO_ERROR
;
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);
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
))) {
300 isOffsetFormat
= (numDigits
> 0);
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
;
308 diff
= (diff
/ 60000) * 60000;
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
);
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
);
337 // Special exclusions in TestTimeZoneRoundTrip.
338 // These special cases do not round trip time as designed.
339 static UBool
isSpecialTimeRoundTripCase(const char* loc
,
340 const UnicodeString
& id
,
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
}
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
) {
375 const Locale
* locales
; // Static
376 int32_t nLocales
; // Static
377 UBool quick
; // Static
378 UDate START_TIME
; // Static
379 UDate END_TIME
; // Static
383 class TestTimeRoundTripThread
: public SimpleThread
{
385 TestTimeRoundTripThread(IntlTest
& tlog
, LocaleData
&ld
, int32_t i
)
386 : log(tlog
), data(ld
), index(i
) {}
388 UErrorCode status
= U_ZERO_ERROR
;
389 UBool REALLY_VERBOSE
= FALSE
;
391 // These patterns are ambiguous at DST->STD local time overlap
392 const char* AMBIGUOUS_DST_DECESSION
[] = { "v", "vvvv", "V", "VV", "VVV", "VVVV", 0 };
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 };
397 // These patterns only support integer minutes offset
398 const char* MINUTES_OFFSET
[] = { "X", "XX", "XXX", "x", "xx", "xxx", 0 };
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");
404 // timer for performance analysis
407 UBool expectedRoundTrip
[4];
410 StringEnumeration
*tzids
= TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL
, NULL
, NULL
, status
);
411 if (U_FAILURE(status
)) {
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.
416 log
.dataerrln("TimeZone::createTimeZoneIDEnumeration failed - %s", u_errorName(status
));
417 data
.numDone
= data
.nLocales
;
419 log
.errln("TimeZone::createTimeZoneIDEnumeration failed: %s", u_errorName(status
));
425 UDate times
[NUM_PATTERNS
];
426 for (int32_t i
= 0; i
< NUM_PATTERNS
; i
++) {
430 int32_t testCounts
= 0;
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
;
438 if (data
.index
< data
.nLocales
) {
444 umtx_unlock(NULL
); // Unlock for other threads to use
447 log
.logln((UnicodeString
) "Thread " + index
+ " is done.");
451 log
.logln((UnicodeString
) "\nThread " + index
+ ": Locale: " + UnicodeString(data
.locales
[locidx
].getName()));
453 for (int32_t patidx
= 0; patidx
< NUM_PATTERNS
; patidx
++) {
454 log
.logln((UnicodeString
) " Pattern: " + PATTERNS
[patidx
]);
457 UnicodeString
pattern(BASEPATTERN
);
458 pattern
.append(" ").append(PATTERNS
[patidx
]);
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
;
468 UBool minutesOffset
= contains(MINUTES_OFFSET
, PATTERNS
[patidx
]);
470 tzids
->reset(status
);
471 const UnicodeString
*tzid
;
473 timer
= Calendar::getNow();
475 while ((tzid
= tzids
->snext(status
))) {
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
) {
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) {
494 if (*tzid
== "Pacific/Apia" && uprv_strcmp(PATTERNS
[patidx
], "vvvv") == 0
495 && log
.logKnownIssue("11052", "Ambiguous zone name - Samoa Time")) {
499 BasicTimeZone
*tz
= (BasicTimeZone
*) TimeZone::createTimeZone(*tzid
);
500 sdf
->setTimeZone(*tz
);
502 UDate t
= data
.START_TIME
;
503 TimeZoneTransition tzt
;
504 UBool tztAvail
= FALSE
;
507 while (t
< data
.END_TIME
) {
510 expectedRoundTrip
[0] = TRUE
;
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
;
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
;
521 expectedRoundTrip
[1] = isDstDecession
?
522 !contains(AMBIGUOUS_DST_DECESSION
, PATTERNS
[patidx
]) :
523 !contains(AMBIGUOUS_NEGATIVE_SHIFT
, PATTERNS
[patidx
]);
524 testTimes
[2] = t
- 1;
525 expectedRoundTrip
[2] = isDstDecession
?
526 !contains(AMBIGUOUS_DST_DECESSION
, PATTERNS
[patidx
]) :
527 !contains(AMBIGUOUS_NEGATIVE_SHIFT
, PATTERNS
[patidx
]);
529 expectedRoundTrip
[3] = TRUE
;
532 testTimes
[0] = t
- 1;
533 expectedRoundTrip
[0] = TRUE
;
535 expectedRoundTrip
[1] = TRUE
;
539 for (int32_t testidx
= 0; testidx
< testLen
; testidx
++) {
541 // reduce regular test time
542 if (!expectedRoundTrip
[testidx
]) {
550 FieldPosition
fpos(0);
551 sdf
->format(testTimes
[testidx
], text
, fpos
);
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
;
561 int32_t timeDiff
= (int32_t)(parsedDate
- testTimes
[testidx
]);
562 UBool bTimeMatch
= minutesOffset
?
563 (timeDiff
/60000)*60000 == 0 : timeDiff
== 0;
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
]);
567 // Timebomb for TZData update
568 if (expectedRoundTrip
[testidx
]
569 && !isSpecialTimeRoundTripCase(data
.locales
[locidx
].getName(), *tzid
,
570 PATTERNS
[patidx
], testTimes
[testidx
])) {
571 log
.errln((UnicodeString
) "FAIL: " + msg
);
572 } else if (REALLY_VERBOSE
) {
577 tztAvail
= tz
->getNextTransition(t
, FALSE
, tzt
);
582 // Test the date in the middle of two transitions.
583 t
+= (int64_t) ((tzt
.getTime() - t
) / 2);
592 times
[patidx
] += (Calendar::getNow() - timer
);
608 TimeZoneFormatTest::TestTimeRoundTrip(void) {
609 int32_t nThreads
= threadCount
;
610 const Locale
*LOCALES
;
612 int32_t testCounts
= 0;
614 UErrorCode status
= U_ZERO_ERROR
;
615 Calendar
*cal
= Calendar::createInstance(TimeZone::createTimeZone((UnicodeString
) "UTC"), status
);
616 if (U_FAILURE(status
)) {
617 dataerrln("Calendar::createInstance failed: %s", u_errorName(status
));
621 const char* testAllProp
= getProperty("TimeZoneRoundTripAll");
622 UBool bTestAll
= (testAllProp
&& uprv_strcmp(testAllProp
, "true") == 0);
624 UDate START_TIME
, END_TIME
;
625 if (bTestAll
|| !quick
) {
626 cal
->set(1900, UCAL_JANUARY
, 1);
628 cal
->set(1990, UCAL_JANUARY
, 1);
630 START_TIME
= cal
->getTime(status
);
632 cal
->set(2015, UCAL_JANUARY
, 1);
633 END_TIME
= cal
->getTime(status
);
635 if (U_FAILURE(status
)) {
636 errln("getTime failed");
640 UDate times
[NUM_PATTERNS
];
641 for (int32_t i
= 0; i
< NUM_PATTERNS
; i
++) {
645 // Set up test locales
646 const Locale locales1
[] = {Locale("en")};
647 const Locale locales2
[] = {
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")
660 LOCALES
= Locale::getAvailableLocales(nLocales
);
663 nLocales
= sizeof(locales1
)/sizeof(Locale
);
666 nLocales
= sizeof(locales2
)/sizeof(Locale
);
671 data
.testCounts
= testCounts
;
673 data
.locales
= LOCALES
;
674 data
.nLocales
= nLocales
;
676 data
.START_TIME
= START_TIME
;
677 data
.END_TIME
= END_TIME
;
680 #if (ICU_USE_THREADS==0)
681 TestTimeRoundTripThread
fakeThread(*this, data
, 0);
684 TestTimeRoundTripThread
**threads
= new TestTimeRoundTripThread
*[threadCount
];
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
);
696 if (data
.numDone
== nLocales
) {
702 SimpleThread::sleep(1000);
705 for (i
= 0; i
< nThreads
; i
++) {
712 logln("### Elapsed time by patterns ###");
713 for (int32_t i
= 0; i
< NUM_PATTERNS
; i
++) {
714 logln(UnicodeString("") + data
.times
[i
] + "ms (" + PATTERNS
[i
] + ")");
715 total
+= data
.times
[i
];
717 logln((UnicodeString
) "Total: " + total
+ "ms");
718 logln((UnicodeString
) "Iteration: " + data
.testCounts
);
728 UTimeZoneFormatStyle style
;
730 const char* expected
;
732 UTimeZoneFormatTimeType timeType
;
736 TimeZoneFormatTest::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
}
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
));
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
);
772 UnicodeString errMsg
;
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
;
785 if (DATA
[i
].expected
) {
786 errln((UnicodeString
)"Fail: Parse failure - expected: " + DATA
[i
].expected
);
789 if (errMsg
.length() > 0) {
790 errln((UnicodeString
)"Fail: " + errMsg
+ " [text=" + DATA
[i
].text
+ ", pos=" + DATA
[i
].inPos
+ ", style=" + DATA
[i
].style
+ "]");
796 TimeZoneFormatTest::TestISOFormat(void) {
797 const int32_t OFFSET
[] = {
802 -77777, // -1m 17.777s
806 -37800000, // -10h 30m
807 -37845000, // -10h 30m 45s
811 const char* ISO_STR
[][11] = {
814 "Z", "Z", "Z", "Z", "Z",
815 "+00", "+0000", "+00:00", "+0000", "+00:00",
820 "Z", "Z", "Z", "Z", "Z",
821 "+00", "+0000", "+00:00", "+0000", "+00:00",
826 "Z", "Z", "Z", "-000059", "-00:00:59",
827 "+00", "+0000", "+00:00", "-000059", "-00:00:59",
832 "+0001", "+0001", "+00:01", "+0001", "+00:01",
833 "+0001", "+0001", "+00:01", "+0001", "+00:01",
838 "-0001", "-0001", "-00:01", "-000117", "-00:01:17",
839 "-0001", "-0001", "-00:01", "-000117", "-00:01:17",
844 "+0030", "+0030", "+00:30", "+0030", "+00:30",
845 "+0030", "+0030", "+00:30", "+0030", "+00:30",
850 "-01", "-0100", "-01:00", "-0100", "-01:00",
851 "-01", "-0100", "-01:00", "-0100", "-01:00",
856 "+10", "+1000", "+10:00", "+1000", "+10:00",
857 "+10", "+1000", "+10:00", "+1000", "+10:00",
862 "-1030", "-1030", "-10:30", "-1030", "-10:30",
863 "-1030", "-1030", "-10:30", "-1030", "-10:30",
868 "-1030", "-1030", "-10:30", "-103045", "-10:30:45",
869 "-1030", "-1030", "-10:30", "-103045", "-10:30:45",
880 const char* PATTERN
[] = {
881 "X", "XX", "XXX", "XXXX", "XXXXX",
882 "x", "xx", "xxx", "xxxx", "xxxxx",
883 "Z", // equivalent to "xxxx"
887 const int32_t MIN_OFFSET_UNIT
[] = {
888 60000, 60000, 60000, 1000, 1000,
889 60000, 60000, 60000, 1000, 1000,
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
));
900 UDate d
= Calendar::getNow();
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
);
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
] + ")");
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)");
928 LocalPointer
<Calendar
> outcal(Calendar::createInstance(status
));
929 if (U_FAILURE(status
)) {
930 dataerrln("Fail new Calendar: %s", u_errorName(status
));
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) {
938 ParsePosition
pos(0);
939 SimpleTimeZone
* bogusTZ
= new SimpleTimeZone(-1, UnicodeString("Zone Offset: -1ms"));
940 outcal
->adoptTimeZone(bogusTZ
);
941 sdf
->applyPattern(PATTERN
[j
]);
943 sdf
->parse(UnicodeString(ISO_STR
[i
][j
]), *(outcal
.getAlias()), pos
);
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
]);
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)");
965 UTimeZoneFormatStyle style
;
966 const char* expected
;
967 UTimeZoneFormatTimeType timeType
;
971 TimeZoneFormatTest::TestFormat(void) {
972 UDate dateJan
= 1358208000000.0; // 2013-01-15T00:00:00Z
973 UDate dateJul
= 1373846400000.0; // 2013-07-15T00:00:00Z
975 const FormatTestData DATA
[] = {
978 "America/Los_Angeles",
980 UTZFMT_STYLE_GENERIC_LOCATION
,
982 UTZFMT_TIME_TYPE_UNKNOWN
986 "America/Los_Angeles",
988 UTZFMT_STYLE_GENERIC_LONG
,
990 UTZFMT_TIME_TYPE_UNKNOWN
994 "America/Los_Angeles",
996 UTZFMT_STYLE_SPECIFIC_LONG
,
997 "Pacific Standard Time",
998 UTZFMT_TIME_TYPE_STANDARD
1002 "America/Los_Angeles",
1004 UTZFMT_STYLE_SPECIFIC_LONG
,
1005 "Pacific Daylight Time",
1006 UTZFMT_TIME_TYPE_DAYLIGHT
1010 "America/Los_Angeles",
1012 UTZFMT_STYLE_ZONE_ID
,
1013 "America/Los_Angeles",
1014 UTZFMT_TIME_TYPE_UNKNOWN
1018 "America/Los_Angeles",
1020 UTZFMT_STYLE_ZONE_ID_SHORT
,
1022 UTZFMT_TIME_TYPE_UNKNOWN
1026 "America/Los_Angeles",
1028 UTZFMT_STYLE_EXEMPLAR_LOCATION
,
1030 UTZFMT_TIME_TYPE_UNKNOWN
1037 UTZFMT_STYLE_GENERIC_LONG
,
1038 "\\u65E5\\u672C\\u6A19\\u6E96\\u6642",
1039 UTZFMT_TIME_TYPE_UNKNOWN
1042 {0, 0, 0.0, UTZFMT_STYLE_GENERIC_LOCATION
, 0, UTZFMT_TIME_TYPE_UNKNOWN
}
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
));
1053 LocalPointer
<TimeZone
> tz(TimeZone::createTimeZone(DATA
[i
].tzid
));
1055 UTimeZoneFormatTimeType timeType
;
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();
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
);
1069 #endif /* #if !UCONFIG_NO_FORMATTING */