1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
4 *******************************************************************************
5 * Copyright (C) 2007-2015, International Business Machines Corporation and *
6 * others. All Rights Reserved. *
7 *******************************************************************************
9 #include "unicode/utypes.h"
11 #if !UCONFIG_NO_FORMATTING
15 #include "unicode/timezone.h"
16 #include "unicode/simpletz.h"
17 #include "unicode/calendar.h"
18 #include "unicode/strenum.h"
19 #include "unicode/smpdtfmt.h"
20 #include "unicode/uchar.h"
21 #include "unicode/basictz.h"
22 #include "unicode/tzfmt.h"
23 #include "unicode/localpointer.h"
28 #include "simplethread.h"
32 static const char* PATTERNS[] = {
35 "Z", // equivalent to "xxxx"
36 "ZZZZ", // equivalent to "OOOO"
57 static const UChar ETC_UNKNOWN[] = {0x45, 0x74, 0x63, 0x2F, 0x55, 0x6E, 0x6B, 0x6E, 0x6F, 0x77, 0x6E, 0};
59 static const UChar ETC_SLASH[] = { 0x45, 0x74, 0x63, 0x2F, 0 }; // "Etc/"
60 static const UChar SYSTEMV_SLASH[] = { 0x53, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x56, 0x2F, 0 }; // "SystemV/
61 static const UChar RIYADH8[] = { 0x52, 0x69, 0x79, 0x61, 0x64, 0x68, 0x38, 0 }; // "Riyadh8"
63 static UBool contains(const char** list, const char* str) {
64 for (int32_t i = 0; list[i]; i++) {
65 if (uprv_strcmp(list[i], str) == 0) {
73 TimeZoneFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ )
76 logln("TestSuite TimeZoneFormatTest");
79 TESTCASE(0, TestTimeZoneRoundTrip);
80 TESTCASE(1, TestTimeRoundTrip);
81 TESTCASE(2, TestParse);
82 TESTCASE(3, TestISOFormat);
83 TESTCASE(4, TestFormat);
84 TESTCASE(5, TestFormatTZDBNames);
85 TESTCASE(6, TestFormatCustomZone);
86 default: name = ""; break;
91 TimeZoneFormatTest::TestTimeZoneRoundTrip(void) {
92 UErrorCode status = U_ZERO_ERROR;
94 SimpleTimeZone unknownZone(-31415, ETC_UNKNOWN);
95 int32_t badDstOffset = -1234;
96 int32_t badZoneOffset = -2345;
98 int32_t testDateData[][3] = {
107 Calendar *cal = Calendar::createInstance(TimeZone::createTimeZone((UnicodeString)"UTC"), status);
108 if (U_FAILURE(status)) {
109 dataerrln("Calendar::createInstance failed: %s", u_errorName(status));
113 // Set up rule equivalency test range
115 cal->set(1900, UCAL_JANUARY, 1);
116 low = cal->getTime(status);
117 cal->set(2040, UCAL_JANUARY, 1);
118 high = cal->getTime(status);
119 if (U_FAILURE(status)) {
120 errln("getTime failed");
125 UDate DATES[UPRV_LENGTHOF(testDateData)];
126 const int32_t nDates = UPRV_LENGTHOF(testDateData);
128 for (int32_t i = 0; i < nDates; i++) {
129 cal->set(testDateData[i][0], testDateData[i][1], testDateData[i][2]);
130 DATES[i] = cal->getTime(status);
131 if (U_FAILURE(status)) {
132 errln("getTime failed");
137 // Set up test locales
138 const Locale testLocales[] = {
145 const Locale *LOCALES;
149 LOCALES = testLocales;
150 nLocales = UPRV_LENGTHOF(testLocales);
152 LOCALES = Locale::getAvailableLocales(nLocales);
155 StringEnumeration *tzids = TimeZone::createEnumeration();
156 int32_t inRaw, inDst;
157 int32_t outRaw, outDst;
159 // Run the roundtrip test
160 for (int32_t locidx = 0; locidx < nLocales; locidx++) {
161 UnicodeString localGMTString;
162 SimpleDateFormat gmtFmt(UnicodeString("ZZZZ"), LOCALES[locidx], status);
163 if (U_FAILURE(status)) {
164 dataerrln("Error creating SimpleDateFormat - %s", u_errorName(status));
167 gmtFmt.setTimeZone(*TimeZone::getGMT());
168 gmtFmt.format(0.0, localGMTString);
170 for (int32_t patidx = 0; patidx < UPRV_LENGTHOF(PATTERNS); patidx++) {
172 SimpleDateFormat *sdf = new SimpleDateFormat((UnicodeString)PATTERNS[patidx], LOCALES[locidx], status);
173 if (U_FAILURE(status)) {
174 dataerrln((UnicodeString)"new SimpleDateFormat failed for pattern " +
175 PATTERNS[patidx] + " for locale " + LOCALES[locidx].getName() + " - " + u_errorName(status));
176 status = U_ZERO_ERROR;
180 tzids->reset(status);
181 const UnicodeString *tzid;
182 while ((tzid = tzids->snext(status))) {
183 TimeZone *tz = TimeZone::createTimeZone(*tzid);
185 for (int32_t datidx = 0; datidx < nDates; datidx++) {
187 FieldPosition fpos(FieldPosition::DONT_CARE);
189 sdf->setTimeZone(*tz);
190 sdf->format(DATES[datidx], tzstr, fpos);
192 // Before parse, set unknown zone to SimpleDateFormat instance
193 // just for making sure that it does not depends on the time zone
195 sdf->setTimeZone(unknownZone);
198 ParsePosition pos(0);
199 Calendar *outcal = Calendar::createInstance(unknownZone, status);
200 if (U_FAILURE(status)) {
201 errln("Failed to create an instance of calendar for receiving parse result.");
202 status = U_ZERO_ERROR;
205 outcal->set(UCAL_DST_OFFSET, badDstOffset);
206 outcal->set(UCAL_ZONE_OFFSET, badZoneOffset);
208 sdf->parse(tzstr, *outcal, pos);
211 const TimeZone &outtz = outcal->getTimeZone();
212 UnicodeString outtzid;
213 outtz.getID(outtzid);
215 tz->getOffset(DATES[datidx], false, inRaw, inDst, status);
216 if (U_FAILURE(status)) {
217 errln((UnicodeString)"Failed to get offsets from time zone" + *tzid);
218 status = U_ZERO_ERROR;
220 outtz.getOffset(DATES[datidx], false, outRaw, outDst, status);
221 if (U_FAILURE(status)) {
222 errln((UnicodeString)"Failed to get offsets from time zone" + outtzid);
223 status = U_ZERO_ERROR;
226 if (uprv_strcmp(PATTERNS[patidx], "V") == 0) {
227 // Short zone ID - should support roundtrip for canonical CLDR IDs
228 UnicodeString canonicalID;
229 TimeZone::getCanonicalID(*tzid, canonicalID, status);
230 if (U_FAILURE(status)) {
231 // Uknown ID - we should not get here
232 errln((UnicodeString)"Unknown ID " + *tzid);
233 status = U_ZERO_ERROR;
234 } else if (outtzid != canonicalID) {
235 if (outtzid.compare(ETC_UNKNOWN, -1) == 0) {
236 // Note that some zones like Asia/Riyadh87 does not have
237 // short zone ID and "unk" is used as fallback
238 logln((UnicodeString)"Canonical round trip failed (probably as expected); tz=" + *tzid
239 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
240 + ", time=" + DATES[datidx] + ", str=" + tzstr
241 + ", outtz=" + outtzid);
243 errln((UnicodeString)"Canonical round trip failed; tz=" + *tzid
244 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
245 + ", time=" + DATES[datidx] + ", str=" + tzstr
246 + ", outtz=" + outtzid);
249 } else if (uprv_strcmp(PATTERNS[patidx], "VV") == 0) {
250 // Zone ID - full roundtrip support
251 if (outtzid != *tzid) {
252 errln((UnicodeString)"Zone ID round trip failued; tz=" + *tzid
253 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
254 + ", time=" + DATES[datidx] + ", str=" + tzstr
255 + ", outtz=" + outtzid);
257 } else if (uprv_strcmp(PATTERNS[patidx], "VVV") == 0 || uprv_strcmp(PATTERNS[patidx], "VVVV") == 0) {
258 // Location: time zone rule must be preserved except
259 // zones not actually associated with a specific location.
260 // Time zones in this category do not have "/" in its ID.
261 UnicodeString canonical;
262 TimeZone::getCanonicalID(*tzid, canonical, status);
263 if (U_FAILURE(status)) {
264 // Uknown ID - we should not get here
265 errln((UnicodeString)"Unknown ID " + *tzid);
266 status = U_ZERO_ERROR;
267 } else if (outtzid != canonical) {
268 // Canonical ID did not match - check the rules
269 if (!((BasicTimeZone*)&outtz)->hasEquivalentTransitions((BasicTimeZone&)*tz, low, high, TRUE, status)) {
270 if (canonical.indexOf((UChar)0x27 /*'/'*/) == -1) {
271 // Exceptional cases, such as CET, EET, MET and WET
272 logln((UnicodeString)"Canonical round trip failed (as expected); tz=" + *tzid
273 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
274 + ", time=" + DATES[datidx] + ", str=" + tzstr
275 + ", outtz=" + outtzid);
277 errln((UnicodeString)"Canonical round trip failed; tz=" + *tzid
278 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
279 + ", time=" + DATES[datidx] + ", str=" + tzstr
280 + ", outtz=" + outtzid);
282 if (U_FAILURE(status)) {
283 errln("hasEquivalentTransitions failed");
284 status = U_ZERO_ERROR;
290 UBool isOffsetFormat = (*PATTERNS[patidx] == 'Z'
291 || *PATTERNS[patidx] == 'O'
292 || *PATTERNS[patidx] == 'X'
293 || *PATTERNS[patidx] == 'x');
294 UBool minutesOffset = FALSE;
295 if (*PATTERNS[patidx] == 'X' || *PATTERNS[patidx] == 'x') {
296 minutesOffset = (uprv_strlen(PATTERNS[patidx]) <= 3);
299 if (!isOffsetFormat) {
300 // Check if localized GMT format is used as a fallback of name styles
301 int32_t numDigits = 0;
302 for (int n = 0; n < tzstr.length(); n++) {
303 if (u_isdigit(tzstr.charAt(n))) {
307 isOffsetFormat = (numDigits > 0);
309 if (isOffsetFormat || tzstr == localGMTString) {
310 // Localized GMT or ISO: total offset (raw + dst) must be preserved.
311 int32_t inOffset = inRaw + inDst;
312 int32_t outOffset = outRaw + outDst;
313 int32_t diff = outOffset - inOffset;
315 diff = (diff / 60000) * 60000;
318 errln((UnicodeString)"Offset round trip failed; tz=" + *tzid
319 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
320 + ", time=" + DATES[datidx] + ", str=" + tzstr
321 + ", inOffset=" + inOffset + ", outOffset=" + outOffset);
324 // Specific or generic: raw offset must be preserved.
325 if (inRaw != outRaw) {
326 errln((UnicodeString)"Raw offset round trip failed; tz=" + *tzid
327 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
328 + ", time=" + DATES[datidx] + ", str=" + tzstr
329 + ", inRawOffset=" + inRaw + ", outRawOffset=" + outRaw);
344 // Special exclusions in TestTimeZoneRoundTrip.
345 // These special cases do not round trip time as designed.
346 static UBool isSpecialTimeRoundTripCase(const char* loc,
347 const UnicodeString& id,
356 {NULL, "Asia/Chita", "zzzz", 1414252800000.0},
357 {NULL, "Asia/Chita", "vvvv", 1414252800000.0},
358 {NULL, "Asia/Srednekolymsk", "zzzz", 1414241999999.0},
359 {NULL, "Asia/Srednekolymsk", "vvvv", 1414241999999.0},
360 {NULL, NULL, NULL, U_DATE_MIN}
363 UBool isExcluded = FALSE;
364 for (int32_t i = 0; EXCLUSIONS[i].id != NULL; i++) {
365 if (EXCLUSIONS[i].loc == NULL || uprv_strcmp(loc, EXCLUSIONS[i].loc) == 0) {
366 if (id.compare(EXCLUSIONS[i].id) == 0) {
367 if (EXCLUSIONS[i].pattern == NULL || uprv_strcmp(pattern, EXCLUSIONS[i].pattern) == 0) {
368 if (EXCLUSIONS[i].time == U_DATE_MIN || EXCLUSIONS[i].time == time) {
378 // LocaleData. Somewhat misnamed. For TestTimeZoneRoundTrip, specifies the locales and patterns
379 // to be tested, and provides an iterator over these for the multi-threaded test
380 // functions to pick up the next combination to be tested.
382 // A single global instance of this struct is shared among all
385 // "locales" is an array of locales to be tested.
386 // PATTERNS (a global) is an array of patterns to be tested for each locale.
387 // "localeIndex" and "patternIndex" keep track of the iteration through the above.
388 // Each of the parallel test threads calls LocaleData::nextTest() in a loop
389 // to find out what to test next. It must be thread safe.
392 int32_t patternIndex;
394 UDate times[UPRV_LENGTHOF(PATTERNS)]; // Performance data, Elapsed time for each pattern.
395 const Locale* locales;
401 LocaleData() : localeIndex(0), patternIndex(0), testCounts(0), locales(NULL),
402 nLocales(0), START_TIME(0), END_TIME(0), numDone(0) {
403 for (int i=0; i<UPRV_LENGTHOF(times); i++) {
408 void resetTestIteration() {
410 patternIndex = UPRV_LENGTHOF(PATTERNS);
414 UBool nextTest(int32_t &rLocaleIndex, int32_t &rPatternIndex) {
416 if (patternIndex >= UPRV_LENGTHOF(PATTERNS) - 1) {
417 if (localeIndex >= nLocales - 1) {
424 rLocaleIndex = localeIndex;
425 rPatternIndex = patternIndex;
430 void addTime(UDate amount, int32_t patIdx) {
432 U_ASSERT(patIdx < UPRV_LENGTHOF(PATTERNS));
433 times[patIdx] += amount;
437 static LocaleData *gLocaleData = NULL;
440 TimeZoneFormatTest::TestTimeRoundTrip(void) {
441 UErrorCode status = U_ZERO_ERROR;
442 LocalPointer <Calendar> cal(Calendar::createInstance(TimeZone::createTimeZone((UnicodeString) "UTC"), status));
443 if (U_FAILURE(status)) {
444 dataerrln("Calendar::createInstance failed: %s", u_errorName(status));
448 const char* testAllProp = getProperty("TimeZoneRoundTripAll");
449 UBool bTestAll = (testAllProp && uprv_strcmp(testAllProp, "true") == 0);
451 UDate START_TIME, END_TIME;
452 if (bTestAll || !quick) {
453 cal->set(1900, UCAL_JANUARY, 1);
455 cal->set(1999, UCAL_JANUARY, 1);
457 START_TIME = cal->getTime(status);
459 cal->set(2022, UCAL_JANUARY, 1);
460 END_TIME = cal->getTime(status);
462 if (U_FAILURE(status)) {
463 errln("getTime failed");
467 LocaleData localeData;
468 gLocaleData = &localeData;
470 // Set up test locales
471 const Locale locales1[] = {Locale("en")};
472 const Locale locales2[] = {
473 Locale("ar_EG"), Locale("bg_BG"), Locale("ca_ES"), Locale("da_DK"), Locale("de"),
474 Locale("de_DE"), Locale("el_GR"), Locale("en"), Locale("en_AU"), Locale("en_CA"),
475 Locale("en_US"), Locale("es"), Locale("es_ES"), Locale("es_MX"), Locale("fi_FI"),
476 Locale("fr"), Locale("fr_CA"), Locale("fr_FR"), Locale("he_IL"), Locale("hu_HU"),
477 Locale("it"), Locale("it_IT"), Locale("ja"), Locale("ja_JP"), Locale("ko"),
478 Locale("ko_KR"), Locale("nb_NO"), Locale("nl_NL"), Locale("nn_NO"), Locale("pl_PL"),
479 Locale("pt"), Locale("pt_BR"), Locale("pt_PT"), Locale("ru_RU"), Locale("sv_SE"),
480 Locale("th_TH"), Locale("tr_TR"), Locale("zh"), Locale("zh_Hans"), Locale("zh_Hans_CN"),
481 Locale("zh_Hant"), Locale("zh_Hant_TW")
485 gLocaleData->locales = Locale::getAvailableLocales(gLocaleData->nLocales);
487 gLocaleData->locales = locales1;
488 gLocaleData->nLocales = UPRV_LENGTHOF(locales1);
490 gLocaleData->locales = locales2;
491 gLocaleData->nLocales = UPRV_LENGTHOF(locales2);
494 gLocaleData->START_TIME = START_TIME;
495 gLocaleData->END_TIME = END_TIME;
496 gLocaleData->resetTestIteration();
498 // start IntlTest.threadCount threads, each running the function RunTimeRoundTripTests().
500 ThreadPool<TimeZoneFormatTest> threads(this, threadCount, &TimeZoneFormatTest::RunTimeRoundTripTests);
501 threads.start(); // Start all threads.
502 threads.join(); // Wait for all threads to finish.
505 logln("### Elapsed time by patterns ###");
506 for (int32_t i = 0; i < UPRV_LENGTHOF(PATTERNS); i++) {
507 logln(UnicodeString("") + gLocaleData->times[i] + "ms (" + PATTERNS[i] + ")");
508 total += gLocaleData->times[i];
510 logln((UnicodeString) "Total: " + total + "ms");
511 logln((UnicodeString) "Iteration: " + gLocaleData->testCounts);
515 // TimeZoneFormatTest::RunTimeRoundTripTests()
516 // This function loops, running time zone format round trip test cases until there are no more, then returns.
517 // Threading: multiple invocations of this function are started in parallel
518 // by TimeZoneFormatTest::TestTimeRoundTrip()
520 void TimeZoneFormatTest::RunTimeRoundTripTests(int32_t threadNumber) {
521 UErrorCode status = U_ZERO_ERROR;
522 UBool REALLY_VERBOSE = FALSE;
524 // These patterns are ambiguous at DST->STD local time overlap
525 const char* AMBIGUOUS_DST_DECESSION[] = { "v", "vvvv", "V", "VV", "VVV", "VVVV", 0 };
527 // These patterns are ambiguous at STD->STD/DST->DST local time overlap
528 const char* AMBIGUOUS_NEGATIVE_SHIFT[] = { "z", "zzzz", "v", "vvvv", "V", "VV", "VVV", "VVVV", 0 };
530 // These patterns only support integer minutes offset
531 const char* MINUTES_OFFSET[] = { "X", "XX", "XXX", "x", "xx", "xxx", 0 };
533 // Workaround for #6338
534 //UnicodeString BASEPATTERN("yyyy-MM-dd'T'HH:mm:ss.SSS");
535 UnicodeString BASEPATTERN("yyyy.MM.dd HH:mm:ss.SSS");
537 // timer for performance analysis
540 UBool expectedRoundTrip[4];
543 StringEnumeration *tzids = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL, NULL, NULL, status);
544 if (U_FAILURE(status)) {
545 if (status == U_MISSING_RESOURCE_ERROR) {
546 // This error is generally caused by data not being present.
547 dataerrln("TimeZone::createTimeZoneIDEnumeration failed - %s", u_errorName(status));
549 errln("TimeZone::createTimeZoneIDEnumeration failed: %s", u_errorName(status));
557 while (gLocaleData->nextTest(locidx, patidx)) {
559 UnicodeString pattern(BASEPATTERN);
560 pattern.append(" ").append(PATTERNS[patidx]);
561 logln(" Thread %d, Locale %s, Pattern %s",
562 threadNumber, gLocaleData->locales[locidx].getName(), CStr(pattern)());
564 SimpleDateFormat *sdf = new SimpleDateFormat(pattern, gLocaleData->locales[locidx], status);
565 if (U_FAILURE(status)) {
566 errcheckln(status, (UnicodeString) "new SimpleDateFormat failed for pattern " +
567 pattern + " for locale " + gLocaleData->locales[locidx].getName() + " - " + u_errorName(status));
568 status = U_ZERO_ERROR;
572 UBool minutesOffset = contains(MINUTES_OFFSET, PATTERNS[patidx]);
574 tzids->reset(status);
575 const UnicodeString *tzid;
577 timer = Calendar::getNow();
579 while ((tzid = tzids->snext(status))) {
580 if (uprv_strcmp(PATTERNS[patidx], "V") == 0) {
581 // Some zones do not have short ID assigned, such as Asia/Riyadh87.
582 // The time roundtrip will fail for such zones with pattern "V" (short zone ID).
583 // This is expected behavior.
584 const UChar* shortZoneID = ZoneMeta::getShortID(*tzid);
585 if (shortZoneID == NULL) {
588 } else if (uprv_strcmp(PATTERNS[patidx], "VVV") == 0) {
589 // Some zones are not associated with any region, such as Etc/GMT+8.
590 // The time roundtrip will fail for such zone with pattern "VVV" (exemplar location).
591 // This is expected behavior.
592 if (tzid->indexOf((UChar)0x2F) < 0 || tzid->indexOf(ETC_SLASH, -1, 0) >= 0
593 || tzid->indexOf(SYSTEMV_SLASH, -1, 0) >= 0 || tzid->indexOf(RIYADH8, -1, 0) >= 0) {
598 if (*tzid == "Pacific/Apia" && uprv_strcmp(PATTERNS[patidx], "vvvv") == 0
599 && logKnownIssue("11052", "Ambiguous zone name - Samoa Time")) {
603 BasicTimeZone *tz = (BasicTimeZone*) TimeZone::createTimeZone(*tzid);
604 sdf->setTimeZone(*tz);
606 UDate t = gLocaleData->START_TIME;
607 TimeZoneTransition tzt;
608 UBool tztAvail = FALSE;
611 while (t < gLocaleData->END_TIME) {
614 expectedRoundTrip[0] = TRUE;
617 int32_t fromOffset = tzt.getFrom()->getRawOffset() + tzt.getFrom()->getDSTSavings();
618 int32_t toOffset = tzt.getTo()->getRawOffset() + tzt.getTo()->getDSTSavings();
619 int32_t delta = toOffset - fromOffset;
621 UBool isDstDecession = tzt.getFrom()->getDSTSavings() > 0 && tzt.getTo()->getDSTSavings() == 0;
622 testTimes[0] = t + delta - 1;
623 expectedRoundTrip[0] = TRUE;
624 testTimes[1] = t + delta;
625 expectedRoundTrip[1] = isDstDecession ?
626 !contains(AMBIGUOUS_DST_DECESSION, PATTERNS[patidx]) :
627 !contains(AMBIGUOUS_NEGATIVE_SHIFT, PATTERNS[patidx]);
628 testTimes[2] = t - 1;
629 expectedRoundTrip[2] = isDstDecession ?
630 !contains(AMBIGUOUS_DST_DECESSION, PATTERNS[patidx]) :
631 !contains(AMBIGUOUS_NEGATIVE_SHIFT, PATTERNS[patidx]);
633 expectedRoundTrip[3] = TRUE;
636 testTimes[0] = t - 1;
637 expectedRoundTrip[0] = TRUE;
639 expectedRoundTrip[1] = TRUE;
643 for (int32_t testidx = 0; testidx < testLen; testidx++) {
645 // reduce regular test time
646 if (!expectedRoundTrip[testidx]) {
653 gLocaleData->testCounts++;
657 FieldPosition fpos(FieldPosition::DONT_CARE);
658 sdf->format(testTimes[testidx], text, fpos);
660 UDate parsedDate = sdf->parse(text, status);
661 if (U_FAILURE(status)) {
662 errln((UnicodeString) "Parse failure for text=" + text + ", tzid=" + *tzid + ", locale=" + gLocaleData->locales[locidx].getName()
663 + ", pattern=" + PATTERNS[patidx] + ", time=" + testTimes[testidx]);
664 status = U_ZERO_ERROR;
668 int32_t timeDiff = (int32_t)(parsedDate - testTimes[testidx]);
669 UBool bTimeMatch = minutesOffset ?
670 (timeDiff/60000)*60000 == 0 : timeDiff == 0;
672 UnicodeString msg = (UnicodeString) "Time round trip failed for " + "tzid=" + *tzid
673 + ", locale=" + gLocaleData->locales[locidx].getName() + ", pattern=" + PATTERNS[patidx]
674 + ", text=" + text + ", time=" + testTimes[testidx] + ", restime=" + parsedDate + ", diff=" + (parsedDate - testTimes[testidx]);
675 // Timebomb for TZData update
676 if (expectedRoundTrip[testidx]
677 && !isSpecialTimeRoundTripCase(gLocaleData->locales[locidx].getName(), *tzid,
678 PATTERNS[patidx], testTimes[testidx])) {
679 errln((UnicodeString) "FAIL: " + msg);
680 } else if (REALLY_VERBOSE) {
685 tztAvail = tz->getNextTransition(t, FALSE, tzt);
690 // Test the date in the middle of two transitions.
691 t += (int64_t) ((tzt.getTime() - t) / 2);
700 UDate elapsedTime = Calendar::getNow() - timer;
701 gLocaleData->addTime(elapsedTime, patidx);
712 UTimeZoneFormatStyle style;
713 uint32_t parseOptions;
714 const char* expected;
716 UTimeZoneFormatTimeType timeType;
720 TimeZoneFormatTest::TestParse(void) {
721 const ParseTestData DATA[] = {
722 // text inPos locale style
723 // parseOptions expected outPos timeType
724 {"Z", 0, "en_US", UTZFMT_STYLE_ISO_EXTENDED_FULL,
725 UTZFMT_PARSE_OPTION_NONE, "Etc/GMT", 1, UTZFMT_TIME_TYPE_UNKNOWN},
727 {"Z", 0, "en_US", UTZFMT_STYLE_SPECIFIC_LONG,
728 UTZFMT_PARSE_OPTION_NONE, "Etc/GMT", 1, UTZFMT_TIME_TYPE_UNKNOWN},
730 {"Zambia time", 0, "en_US", UTZFMT_STYLE_ISO_EXTENDED_FULL,
731 UTZFMT_PARSE_OPTION_ALL_STYLES, "Etc/GMT", 1, UTZFMT_TIME_TYPE_UNKNOWN},
733 {"Zambia time", 0, "en_US", UTZFMT_STYLE_GENERIC_LOCATION,
734 UTZFMT_PARSE_OPTION_NONE, "Africa/Lusaka", 11, UTZFMT_TIME_TYPE_UNKNOWN},
736 {"Zambia time", 0, "en_US", UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL,
737 UTZFMT_PARSE_OPTION_ALL_STYLES, "Africa/Lusaka", 11, UTZFMT_TIME_TYPE_UNKNOWN},
739 {"+00:00", 0, "en_US", UTZFMT_STYLE_ISO_EXTENDED_FULL,
740 UTZFMT_PARSE_OPTION_NONE, "Etc/GMT", 6, UTZFMT_TIME_TYPE_UNKNOWN},
742 {"-01:30:45", 0, "en_US", UTZFMT_STYLE_ISO_EXTENDED_FULL,
743 UTZFMT_PARSE_OPTION_NONE, "GMT-01:30:45", 9, UTZFMT_TIME_TYPE_UNKNOWN},
745 {"-7", 0, "en_US", UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL,
746 UTZFMT_PARSE_OPTION_NONE, "GMT-07:00", 2, UTZFMT_TIME_TYPE_UNKNOWN},
748 {"-2222", 0, "en_US", UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL,
749 UTZFMT_PARSE_OPTION_NONE, "GMT-22:22", 5, UTZFMT_TIME_TYPE_UNKNOWN},
751 {"-3333", 0, "en_US", UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL,
752 UTZFMT_PARSE_OPTION_NONE, "GMT-03:33", 4, UTZFMT_TIME_TYPE_UNKNOWN},
754 {"XXX+01:30YYY", 3, "en_US", UTZFMT_STYLE_LOCALIZED_GMT,
755 UTZFMT_PARSE_OPTION_NONE, "GMT+01:30", 9, UTZFMT_TIME_TYPE_UNKNOWN},
757 {"GMT0", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT,
758 UTZFMT_PARSE_OPTION_NONE, "Etc/GMT", 3, UTZFMT_TIME_TYPE_UNKNOWN},
760 {"EST", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT,
761 UTZFMT_PARSE_OPTION_NONE, "America/New_York", 3, UTZFMT_TIME_TYPE_STANDARD},
763 {"ESTx", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT,
764 UTZFMT_PARSE_OPTION_NONE, "America/New_York", 3, UTZFMT_TIME_TYPE_STANDARD},
766 {"EDTx", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT,
767 UTZFMT_PARSE_OPTION_NONE, "America/New_York", 3, UTZFMT_TIME_TYPE_DAYLIGHT},
769 {"EST", 0, "en_US", UTZFMT_STYLE_SPECIFIC_LONG,
770 UTZFMT_PARSE_OPTION_NONE, NULL, 0, UTZFMT_TIME_TYPE_UNKNOWN},
772 {"EST", 0, "en_US", UTZFMT_STYLE_SPECIFIC_LONG,
773 UTZFMT_PARSE_OPTION_ALL_STYLES, "America/New_York", 3, UTZFMT_TIME_TYPE_STANDARD},
775 {"EST", 0, "en_CA", UTZFMT_STYLE_SPECIFIC_SHORT,
776 UTZFMT_PARSE_OPTION_NONE, "America/Toronto", 3, UTZFMT_TIME_TYPE_STANDARD},
778 {"CST", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT,
779 UTZFMT_PARSE_OPTION_NONE, "America/Chicago", 3, UTZFMT_TIME_TYPE_STANDARD},
781 {"CST", 0, "en_GB", UTZFMT_STYLE_SPECIFIC_SHORT,
782 UTZFMT_PARSE_OPTION_NONE, NULL, 0, UTZFMT_TIME_TYPE_UNKNOWN},
784 {"CST", 0, "en_GB", UTZFMT_STYLE_SPECIFIC_SHORT,
785 UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS, "America/Chicago", 3, UTZFMT_TIME_TYPE_STANDARD},
787 {"--CST--", 2, "en_GB", UTZFMT_STYLE_SPECIFIC_SHORT,
788 UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS, "America/Chicago", 5, UTZFMT_TIME_TYPE_STANDARD},
790 {"CST", 0, "zh_CN", UTZFMT_STYLE_SPECIFIC_SHORT,
791 UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS, "Asia/Shanghai", 3, UTZFMT_TIME_TYPE_STANDARD},
793 {"AEST", 0, "en_AU", UTZFMT_STYLE_SPECIFIC_SHORT,
794 UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS, "Australia/Sydney", 4, UTZFMT_TIME_TYPE_STANDARD},
796 {"AST", 0, "ar_SA", UTZFMT_STYLE_SPECIFIC_SHORT,
797 UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS, "Asia/Riyadh", 3, UTZFMT_TIME_TYPE_STANDARD},
799 {"AQTST", 0, "en", UTZFMT_STYLE_SPECIFIC_LONG,
800 UTZFMT_PARSE_OPTION_NONE, NULL, 0, UTZFMT_TIME_TYPE_UNKNOWN},
802 {"AQTST", 0, "en", UTZFMT_STYLE_SPECIFIC_LONG,
803 UTZFMT_PARSE_OPTION_ALL_STYLES, NULL, 0, UTZFMT_TIME_TYPE_UNKNOWN},
805 {"AQTST", 0, "en", UTZFMT_STYLE_SPECIFIC_LONG,
806 UTZFMT_PARSE_OPTION_ALL_STYLES | UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS, "Asia/Aqtobe", 5, UTZFMT_TIME_TYPE_DAYLIGHT},
808 {NULL, 0, NULL, UTZFMT_STYLE_GENERIC_LOCATION,
809 UTZFMT_PARSE_OPTION_NONE, NULL, 0, UTZFMT_TIME_TYPE_UNKNOWN}
812 for (int32_t i = 0; DATA[i].text; i++) {
813 UErrorCode status = U_ZERO_ERROR;
814 LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(Locale(DATA[i].locale), status));
815 if (U_FAILURE(status)) {
816 dataerrln("Fail TimeZoneFormat::createInstance: %s", u_errorName(status));
819 UTimeZoneFormatTimeType ttype = UTZFMT_TIME_TYPE_UNKNOWN;
820 ParsePosition pos(DATA[i].inPos);
821 TimeZone* tz = tzfmt->parse(DATA[i].style, DATA[i].text, pos, DATA[i].parseOptions, &ttype);
823 UnicodeString errMsg;
827 if (outID != UnicodeString(DATA[i].expected)) {
828 errMsg = (UnicodeString)"Time zone ID: " + outID + " - expected: " + DATA[i].expected;
829 } else if (pos.getIndex() != DATA[i].outPos) {
830 errMsg = (UnicodeString)"Parsed pos: " + pos.getIndex() + " - expected: " + DATA[i].outPos;
831 } else if (ttype != DATA[i].timeType) {
832 errMsg = (UnicodeString)"Time type: " + ttype + " - expected: " + DATA[i].timeType;
836 if (DATA[i].expected) {
837 errMsg = (UnicodeString)"Parse failure - expected: " + DATA[i].expected;
840 if (errMsg.length() > 0) {
841 errln((UnicodeString)"Fail: " + errMsg + " [text=" + DATA[i].text + ", pos=" + DATA[i].inPos + ", style=" + DATA[i].style + "]");
847 TimeZoneFormatTest::TestISOFormat(void) {
848 const int32_t OFFSET[] = {
853 -77777, // -1m 17.777s
857 -37800000, // -10h 30m
858 -37845000, // -10h 30m 45s
862 const char* ISO_STR[][11] = {
865 "Z", "Z", "Z", "Z", "Z",
866 "+00", "+0000", "+00:00", "+0000", "+00:00",
871 "Z", "Z", "Z", "Z", "Z",
872 "+00", "+0000", "+00:00", "+0000", "+00:00",
877 "Z", "Z", "Z", "-000059", "-00:00:59",
878 "+00", "+0000", "+00:00", "-000059", "-00:00:59",
883 "+0001", "+0001", "+00:01", "+0001", "+00:01",
884 "+0001", "+0001", "+00:01", "+0001", "+00:01",
889 "-0001", "-0001", "-00:01", "-000117", "-00:01:17",
890 "-0001", "-0001", "-00:01", "-000117", "-00:01:17",
895 "+0030", "+0030", "+00:30", "+0030", "+00:30",
896 "+0030", "+0030", "+00:30", "+0030", "+00:30",
901 "-01", "-0100", "-01:00", "-0100", "-01:00",
902 "-01", "-0100", "-01:00", "-0100", "-01:00",
907 "+10", "+1000", "+10:00", "+1000", "+10:00",
908 "+10", "+1000", "+10:00", "+1000", "+10:00",
913 "-1030", "-1030", "-10:30", "-1030", "-10:30",
914 "-1030", "-1030", "-10:30", "-1030", "-10:30",
919 "-1030", "-1030", "-10:30", "-103045", "-10:30:45",
920 "-1030", "-1030", "-10:30", "-103045", "-10:30:45",
931 const char* PATTERN[] = {
932 "X", "XX", "XXX", "XXXX", "XXXXX",
933 "x", "xx", "xxx", "xxxx", "xxxxx",
934 "Z", // equivalent to "xxxx"
938 const int32_t MIN_OFFSET_UNIT[] = {
939 60000, 60000, 60000, 1000, 1000,
940 60000, 60000, 60000, 1000, 1000,
945 UErrorCode status = U_ZERO_ERROR;
946 LocalPointer<SimpleDateFormat> sdf(new SimpleDateFormat(status), status);
947 if (U_FAILURE(status)) {
948 dataerrln("Fail new SimpleDateFormat: %s", u_errorName(status));
951 UDate d = Calendar::getNow();
953 for (uint32_t i = 0; i < UPRV_LENGTHOF(OFFSET); i++) {
954 SimpleTimeZone* tz = new SimpleTimeZone(OFFSET[i], UnicodeString("Zone Offset:") + OFFSET[i] + "ms");
955 sdf->adoptTimeZone(tz);
956 for (int32_t j = 0; PATTERN[j] != 0; j++) {
957 sdf->applyPattern(UnicodeString(PATTERN[j]));
958 UnicodeString result;
959 sdf->format(d, result);
962 if (result != UnicodeString(ISO_STR[i][j])) {
963 errln((UnicodeString)"FAIL: pattern=" + PATTERN[j] + ", offset=" + OFFSET[i] + " -> "
964 + result + " (expected: " + ISO_STR[i][j] + ")");
967 // Offset out of range
968 // Note: for now, there is no way to propagate the error status through
969 // the SimpleDateFormat::format above.
970 if (result.length() > 0) {
971 errln((UnicodeString)"FAIL: Non-Empty result for pattern=" + PATTERN[j] + ", offset=" + OFFSET[i]
972 + " (expected: empty result)");
979 LocalPointer<Calendar> outcal(Calendar::createInstance(status));
980 if (U_FAILURE(status)) {
981 dataerrln("Fail new Calendar: %s", u_errorName(status));
984 for (int32_t i = 0; ISO_STR[i][0] != NULL; i++) {
985 for (int32_t j = 0; PATTERN[j] != 0; j++) {
986 if (ISO_STR[i][j] == 0) {
989 ParsePosition pos(0);
990 SimpleTimeZone* bogusTZ = new SimpleTimeZone(-1, UnicodeString("Zone Offset: -1ms"));
991 outcal->adoptTimeZone(bogusTZ);
992 sdf->applyPattern(PATTERN[j]);
994 sdf->parse(UnicodeString(ISO_STR[i][j]), *(outcal.getAlias()), pos);
996 if (pos.getIndex() != (int32_t)uprv_strlen(ISO_STR[i][j])) {
997 errln((UnicodeString)"FAIL: Failed to parse the entire input string: " + ISO_STR[i][j]);
1000 const TimeZone& outtz = outcal->getTimeZone();
1001 int32_t outOffset = outtz.getRawOffset();
1002 int32_t adjustedOffset = OFFSET[i] / MIN_OFFSET_UNIT[j] * MIN_OFFSET_UNIT[j];
1003 if (outOffset != adjustedOffset) {
1004 errln((UnicodeString)"FAIL: Incorrect offset:" + outOffset + "ms for input string: " + ISO_STR[i][j]
1005 + " (expected:" + adjustedOffset + "ms)");
1016 UTimeZoneFormatStyle style;
1017 const char* expected;
1018 UTimeZoneFormatTimeType timeType;
1022 TimeZoneFormatTest::TestFormat(void) {
1023 UDate dateJan = 1358208000000.0; // 2013-01-15T00:00:00Z
1024 UDate dateJul = 1373846400000.0; // 2013-07-15T00:00:00Z
1026 const FormatTestData DATA[] = {
1029 "America/Los_Angeles",
1031 UTZFMT_STYLE_GENERIC_LOCATION,
1033 UTZFMT_TIME_TYPE_UNKNOWN
1037 "America/Los_Angeles",
1039 UTZFMT_STYLE_GENERIC_LONG,
1041 UTZFMT_TIME_TYPE_UNKNOWN
1045 "America/Los_Angeles",
1047 UTZFMT_STYLE_SPECIFIC_LONG,
1048 "Pacific Standard Time",
1049 UTZFMT_TIME_TYPE_STANDARD
1053 "America/Los_Angeles",
1055 UTZFMT_STYLE_SPECIFIC_LONG,
1056 "Pacific Daylight Time",
1057 UTZFMT_TIME_TYPE_DAYLIGHT
1061 "America/Los_Angeles",
1063 UTZFMT_STYLE_ZONE_ID,
1064 "America/Los_Angeles",
1065 UTZFMT_TIME_TYPE_UNKNOWN
1069 "America/Los_Angeles",
1071 UTZFMT_STYLE_ZONE_ID_SHORT,
1073 UTZFMT_TIME_TYPE_UNKNOWN
1077 "America/Los_Angeles",
1079 UTZFMT_STYLE_EXEMPLAR_LOCATION,
1081 UTZFMT_TIME_TYPE_UNKNOWN
1088 UTZFMT_STYLE_GENERIC_LONG,
1089 "\\u65E5\\u672C\\u6A19\\u6E96\\u6642",
1090 UTZFMT_TIME_TYPE_UNKNOWN
1093 {0, 0, 0.0, UTZFMT_STYLE_GENERIC_LOCATION, 0, UTZFMT_TIME_TYPE_UNKNOWN}
1096 for (int32_t i = 0; DATA[i].locale; i++) {
1097 UErrorCode status = U_ZERO_ERROR;
1098 LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(Locale(DATA[i].locale), status));
1099 if (U_FAILURE(status)) {
1100 dataerrln("Fail TimeZoneFormat::createInstance: %s", u_errorName(status));
1104 LocalPointer<TimeZone> tz(TimeZone::createTimeZone(DATA[i].tzid));
1106 UTimeZoneFormatTimeType timeType;
1108 tzfmt->format(DATA[i].style, *(tz.getAlias()), DATA[i].date, out, &timeType);
1109 UnicodeString expected(DATA[i].expected, -1, US_INV);
1110 expected = expected.unescape();
1112 assertEquals(UnicodeString("Format result for ") + DATA[i].tzid + " (Test Case " + i + ")", expected, out);
1113 if (DATA[i].timeType != timeType) {
1114 dataerrln(UnicodeString("Formatted time zone type (Test Case ") + i + "), returned="
1115 + timeType + ", expected=" + DATA[i].timeType);
1121 TimeZoneFormatTest::TestFormatTZDBNames(void) {
1122 UDate dateJan = 1358208000000.0; // 2013-01-15T00:00:00Z
1123 UDate dateJul = 1373846400000.0; // 2013-07-15T00:00:00Z
1125 const FormatTestData DATA[] = {
1130 UTZFMT_STYLE_SPECIFIC_SHORT,
1132 UTZFMT_TIME_TYPE_STANDARD
1138 UTZFMT_STYLE_SPECIFIC_SHORT,
1140 UTZFMT_TIME_TYPE_STANDARD
1146 UTZFMT_STYLE_SPECIFIC_SHORT,
1148 UTZFMT_TIME_TYPE_STANDARD
1152 "America/Los_Angeles",
1154 UTZFMT_STYLE_SPECIFIC_LONG,
1155 "GMT-07:00", // No long display names
1156 UTZFMT_TIME_TYPE_DAYLIGHT
1160 "America/Los_Angeles",
1162 UTZFMT_STYLE_SPECIFIC_SHORT,
1164 UTZFMT_TIME_TYPE_DAYLIGHT
1170 UTZFMT_STYLE_SPECIFIC_SHORT,
1172 UTZFMT_TIME_TYPE_DAYLIGHT
1178 UTZFMT_STYLE_SPECIFIC_SHORT,
1180 UTZFMT_TIME_TYPE_STANDARD
1183 {0, 0, 0.0, UTZFMT_STYLE_GENERIC_LOCATION, 0, UTZFMT_TIME_TYPE_UNKNOWN}
1186 for (int32_t i = 0; DATA[i].locale; i++) {
1187 UErrorCode status = U_ZERO_ERROR;
1188 Locale loc(DATA[i].locale);
1189 LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(loc, status));
1190 if (U_FAILURE(status)) {
1191 dataerrln("Fail TimeZoneFormat::createInstance: %s", u_errorName(status));
1194 TimeZoneNames *tzdbNames = TimeZoneNames::createTZDBInstance(loc, status);
1195 if (U_FAILURE(status)) {
1196 dataerrln("Fail TimeZoneNames::createTZDBInstance: %s", u_errorName(status));
1199 tzfmt->adoptTimeZoneNames(tzdbNames);
1201 LocalPointer<TimeZone> tz(TimeZone::createTimeZone(DATA[i].tzid));
1203 UTimeZoneFormatTimeType timeType;
1205 tzfmt->format(DATA[i].style, *(tz.getAlias()), DATA[i].date, out, &timeType);
1206 UnicodeString expected(DATA[i].expected, -1, US_INV);
1207 expected = expected.unescape();
1209 assertEquals(UnicodeString("Format result for ") + DATA[i].tzid + " (Test Case " + i + ")", expected, out);
1210 if (DATA[i].timeType != timeType) {
1211 dataerrln(UnicodeString("Formatted time zone type (Test Case ") + i + "), returned="
1212 + timeType + ", expected=" + DATA[i].timeType);
1218 TimeZoneFormatTest::TestFormatCustomZone(void) {
1222 const char* expected;
1224 { "abc", 3600000, "GMT+01:00" }, // unknown ID
1225 { "$abc", -3600000, "GMT-01:00" }, // unknown, with ASCII variant char '$'
1226 { "\\u00c1\\u00df\\u00c7", 5400000, "GMT+01:30"}, // unknown, with non-ASCII chars
1230 UDate now = Calendar::getNow();
1232 for (int32_t i = 0; ; i++) {
1233 const char *id = TESTDATA[i].id;
1237 UnicodeString tzid = UnicodeString(id, -1, US_INV).unescape();
1238 SimpleTimeZone tz(TESTDATA[i].offset, tzid);
1240 UErrorCode status = U_ZERO_ERROR;
1241 LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(Locale("en"), status));
1242 if (tzfmt.isNull()) {
1243 dataerrln("FAIL: TimeZoneFormat::createInstance failed for en");
1246 UnicodeString tzstr;
1247 UnicodeString expected = UnicodeString(TESTDATA[i].expected, -1, US_INV).unescape();
1249 tzfmt->format(UTZFMT_STYLE_SPECIFIC_LONG, tz, now, tzstr, NULL);
1250 assertEquals(UnicodeString("Format result for ") + tzid, expected, tzstr);
1255 #endif /* #if !UCONFIG_NO_FORMATTING */