1 // 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 3 /* 4 ******************************************************************************* 5 * Copyright (C) 2007-2015, International Business Machines Corporation and * 6 * others. All Rights Reserved. * 7 ******************************************************************************* 8 */ 9 #include "unicode/utypes.h" 10 11 #if !UCONFIG_NO_FORMATTING 12 13 #include "tzfmttst.h" 14 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" 24 #include "unicode/utf16.h" 25 26 #include "cstring.h" 27 #include "cstr.h" 28 #include "mutex.h" 29 #include "simplethread.h" 30 #include "uassert.h" 31 #include "zonemeta.h" 32 33 static const char* PATTERNS[] = { 34 "z", 35 "zzzz", 36 "Z", // equivalent to "xxxx" 37 "ZZZZ", // equivalent to "OOOO" 38 "v", 39 "vvvv", 40 "O", 41 "OOOO", 42 "X", 43 "XX", 44 "XXX", 45 "XXXX", 46 "XXXXX", 47 "x", 48 "xx", 49 "xxx", 50 "xxxx", 51 "xxxxx", 52 "V", 53 "VV", 54 "VVV", 55 "VVVV" 56 }; 57 58 static const UChar ETC_UNKNOWN[] = {0x45, 0x74, 0x63, 0x2F, 0x55, 0x6E, 0x6B, 0x6E, 0x6F, 0x77, 0x6E, 0}; 59 60 static const UChar ETC_SLASH[] = { 0x45, 0x74, 0x63, 0x2F, 0 }; // "Etc/" 61 static const UChar SYSTEMV_SLASH[] = { 0x53, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x56, 0x2F, 0 }; // "SystemV/ 62 static const UChar RIYADH8[] = { 0x52, 0x69, 0x79, 0x61, 0x64, 0x68, 0x38, 0 }; // "Riyadh8" 63 64 static UBool contains(const char** list, const char* str) { 65 for (int32_t i = 0; list[i]; i++) { 66 if (uprv_strcmp(list[i], str) == 0) { 67 return TRUE; 68 } 69 } 70 return FALSE; 71 } 72 73 void 74 TimeZoneFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ ) 75 { 76 if (exec) { 77 logln("TestSuite TimeZoneFormatTest"); 78 } 79 switch (index) { 80 TESTCASE(0, TestTimeZoneRoundTrip); 81 TESTCASE(1, TestTimeRoundTrip); 82 TESTCASE(2, TestParse); 83 TESTCASE(3, TestISOFormat); 84 TESTCASE(4, TestFormat); 85 TESTCASE(5, TestFormatTZDBNames); 86 TESTCASE(6, TestFormatCustomZone); 87 TESTCASE(7, TestFormatTZDBNamesAllZoneCoverage); 88 default: name = ""; break; 89 } 90 } 91 92 void 93 TimeZoneFormatTest::TestTimeZoneRoundTrip(void) { 94 UErrorCode status = U_ZERO_ERROR; 95 96 SimpleTimeZone unknownZone(-31415, ETC_UNKNOWN); 97 int32_t badDstOffset = -1234; 98 int32_t badZoneOffset = -2345; 99 100 int32_t testDateData[][3] = { 101 {2007, 1, 15}, 102 {2007, 6, 15}, 103 {1990, 1, 15}, 104 {1990, 6, 15}, 105 {1960, 1, 15}, 106 {1960, 6, 15}, 107 }; 108 109 Calendar *cal = Calendar::createInstance(TimeZone::createTimeZone((UnicodeString)"UTC"), status); 110 if (U_FAILURE(status)) { 111 dataerrln("Calendar::createInstance failed: %s", u_errorName(status)); 112 return; 113 } 114 115 // Set up rule equivalency test range 116 UDate low, high; 117 cal->set(1900, UCAL_JANUARY, 1); 118 low = cal->getTime(status); 119 cal->set(2040, UCAL_JANUARY, 1); 120 high = cal->getTime(status); 121 if (U_FAILURE(status)) { 122 errln("getTime failed"); 123 return; 124 } 125 126 // Set up test dates 127 UDate DATES[UPRV_LENGTHOF(testDateData)]; 128 const int32_t nDates = UPRV_LENGTHOF(testDateData); 129 cal->clear(); 130 for (int32_t i = 0; i < nDates; i++) { 131 cal->set(testDateData[i][0], testDateData[i][1], testDateData[i][2]); 132 DATES[i] = cal->getTime(status); 133 if (U_FAILURE(status)) { 134 errln("getTime failed"); 135 return; 136 } 137 } 138 139 // Set up test locales 140 const Locale testLocales[] = { 141 Locale("en"), 142 Locale("en_CA"), 143 Locale("fr"), 144 Locale("zh_Hant"), 145 Locale("fa"), 146 Locale("ccp") 147 }; 148 149 const Locale *LOCALES; 150 int32_t nLocales; 151 152 if (quick) { 153 LOCALES = testLocales; 154 nLocales = UPRV_LENGTHOF(testLocales); 155 } else { 156 LOCALES = Locale::getAvailableLocales(nLocales); 157 } 158 159 StringEnumeration *tzids = TimeZone::createEnumeration(); 160 int32_t inRaw, inDst; 161 int32_t outRaw, outDst; 162 163 // Run the roundtrip test 164 for (int32_t locidx = 0; locidx < nLocales; locidx++) { 165 UnicodeString localGMTString; 166 SimpleDateFormat gmtFmt(UnicodeString("ZZZZ"), LOCALES[locidx], status); 167 if (U_FAILURE(status)) { 168 dataerrln("Error creating SimpleDateFormat - %s", u_errorName(status)); 169 continue; 170 } 171 gmtFmt.setTimeZone(*TimeZone::getGMT()); 172 gmtFmt.format(0.0, localGMTString); 173 174 for (int32_t patidx = 0; patidx < UPRV_LENGTHOF(PATTERNS); patidx++) { 175 SimpleDateFormat *sdf = new SimpleDateFormat((UnicodeString)PATTERNS[patidx], LOCALES[locidx], status); 176 if (U_FAILURE(status)) { 177 dataerrln((UnicodeString)"new SimpleDateFormat failed for pattern " + 178 PATTERNS[patidx] + " for locale " + LOCALES[locidx].getName() + " - " + u_errorName(status)); 179 status = U_ZERO_ERROR; 180 continue; 181 } 182 183 tzids->reset(status); 184 const UnicodeString *tzid; 185 while ((tzid = tzids->snext(status))) { 186 TimeZone *tz = TimeZone::createTimeZone(*tzid); 187 188 for (int32_t datidx = 0; datidx < nDates; datidx++) { 189 UnicodeString tzstr; 190 FieldPosition fpos(FieldPosition::DONT_CARE); 191 // Format 192 sdf->setTimeZone(*tz); 193 sdf->format(DATES[datidx], tzstr, fpos); 194 195 // Before parse, set unknown zone to SimpleDateFormat instance 196 // just for making sure that it does not depends on the time zone 197 // originally set. 198 sdf->setTimeZone(unknownZone); 199 200 // Parse 201 ParsePosition pos(0); 202 Calendar *outcal = Calendar::createInstance(unknownZone, status); 203 if (U_FAILURE(status)) { 204 errln("Failed to create an instance of calendar for receiving parse result."); 205 status = U_ZERO_ERROR; 206 continue; 207 } 208 outcal->set(UCAL_DST_OFFSET, badDstOffset); 209 outcal->set(UCAL_ZONE_OFFSET, badZoneOffset); 210 211 sdf->parse(tzstr, *outcal, pos); 212 213 // Check the result 214 const TimeZone &outtz = outcal->getTimeZone(); 215 UnicodeString outtzid; 216 outtz.getID(outtzid); 217 218 tz->getOffset(DATES[datidx], false, inRaw, inDst, status); 219 if (U_FAILURE(status)) { 220 errln((UnicodeString)"Failed to get offsets from time zone" + *tzid); 221 status = U_ZERO_ERROR; 222 } 223 outtz.getOffset(DATES[datidx], false, outRaw, outDst, status); 224 if (U_FAILURE(status)) { 225 errln((UnicodeString)"Failed to get offsets from time zone" + outtzid); 226 status = U_ZERO_ERROR; 227 } 228 229 if (uprv_strcmp(PATTERNS[patidx], "V") == 0) { 230 // Short zone ID - should support roundtrip for canonical CLDR IDs 231 UnicodeString canonicalID; 232 TimeZone::getCanonicalID(*tzid, canonicalID, status); 233 if (U_FAILURE(status)) { 234 // Uknown ID - we should not get here 235 errln((UnicodeString)"Unknown ID " + *tzid); 236 status = U_ZERO_ERROR; 237 } else if (outtzid != canonicalID) { 238 if (outtzid.compare(ETC_UNKNOWN, -1) == 0) { 239 // Note that some zones like Asia/Riyadh87 does not have 240 // short zone ID and "unk" is used as fallback 241 logln((UnicodeString)"Canonical round trip failed (probably as expected); tz=" + *tzid 242 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx] 243 + ", time=" + DATES[datidx] + ", str=" + tzstr 244 + ", outtz=" + outtzid); 245 } else { 246 errln((UnicodeString)"Canonical round trip failed; tz=" + *tzid 247 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx] 248 + ", time=" + DATES[datidx] + ", str=" + tzstr 249 + ", outtz=" + outtzid); 250 } 251 } 252 } else if (uprv_strcmp(PATTERNS[patidx], "VV") == 0) { 253 // Zone ID - full roundtrip support 254 if (outtzid != *tzid) { 255 errln((UnicodeString)"Zone ID round trip failued; tz=" + *tzid 256 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx] 257 + ", time=" + DATES[datidx] + ", str=" + tzstr 258 + ", outtz=" + outtzid); 259 } 260 } else if (uprv_strcmp(PATTERNS[patidx], "VVV") == 0 || uprv_strcmp(PATTERNS[patidx], "VVVV") == 0) { 261 // Location: time zone rule must be preserved except 262 // zones not actually associated with a specific location. 263 // Time zones in this category do not have "/" in its ID. 264 UnicodeString canonical; 265 TimeZone::getCanonicalID(*tzid, canonical, status); 266 if (U_FAILURE(status)) { 267 // Uknown ID - we should not get here 268 errln((UnicodeString)"Unknown ID " + *tzid); 269 status = U_ZERO_ERROR; 270 } else if (outtzid != canonical) { 271 // Canonical ID did not match - check the rules 272 if (!((BasicTimeZone*)&outtz)->hasEquivalentTransitions((BasicTimeZone&)*tz, low, high, TRUE, status)) { 273 if (canonical.indexOf((UChar)0x27 /*'/'*/) == -1) { 274 // Exceptional cases, such as CET, EET, MET and WET 275 logln((UnicodeString)"Canonical round trip failed (as expected); tz=" + *tzid 276 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx] 277 + ", time=" + DATES[datidx] + ", str=" + tzstr 278 + ", outtz=" + outtzid); 279 } else { 280 errln((UnicodeString)"Canonical round trip failed; tz=" + *tzid 281 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx] 282 + ", time=" + DATES[datidx] + ", str=" + tzstr 283 + ", outtz=" + outtzid); 284 } 285 if (U_FAILURE(status)) { 286 errln("hasEquivalentTransitions failed"); 287 status = U_ZERO_ERROR; 288 } 289 } 290 } 291 292 } else { 293 UBool isOffsetFormat = (*PATTERNS[patidx] == 'Z' 294 || *PATTERNS[patidx] == 'O' 295 || *PATTERNS[patidx] == 'X' 296 || *PATTERNS[patidx] == 'x'); 297 UBool minutesOffset = FALSE; 298 if (*PATTERNS[patidx] == 'X' || *PATTERNS[patidx] == 'x') { 299 minutesOffset = (uprv_strlen(PATTERNS[patidx]) <= 3); 300 } 301 302 if (!isOffsetFormat) { 303 // Check if localized GMT format is used as a fallback of name styles 304 int32_t numDigits = 0; 305 int32_t idx = 0; 306 while (idx < tzstr.length()) { 307 UChar32 cp = tzstr.char32At(idx); 308 if (u_isdigit(cp)) { 309 numDigits++; 310 } 311 idx += U16_LENGTH(cp); 312 } 313 isOffsetFormat = (numDigits > 0); 314 } 315 if (isOffsetFormat || tzstr == localGMTString) { 316 // Localized GMT or ISO: total offset (raw + dst) must be preserved. 317 int32_t inOffset = inRaw + inDst; 318 int32_t outOffset = outRaw + outDst; 319 int32_t diff = outOffset - inOffset; 320 if (minutesOffset) { 321 diff = (diff / 60000) * 60000; 322 } 323 if (diff != 0) { 324 errln((UnicodeString)"Offset round trip failed; tz=" + *tzid 325 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx] 326 + ", time=" + DATES[datidx] + ", str=" + tzstr 327 + ", inOffset=" + inOffset + ", outOffset=" + outOffset); 328 } 329 } else { 330 // Specific or generic: raw offset must be preserved. 331 if (inRaw != outRaw) { 332 errln((UnicodeString)"Raw offset round trip failed; tz=" + *tzid 333 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx] 334 + ", time=" + DATES[datidx] + ", str=" + tzstr 335 + ", inRawOffset=" + inRaw + ", outRawOffset=" + outRaw); 336 } 337 } 338 } 339 delete outcal; 340 } 341 delete tz; 342 } 343 delete sdf; 344 } 345 } 346 delete cal; 347 delete tzids; 348 } 349 350 // Special exclusions in TestTimeZoneRoundTrip. 351 // These special cases do not round trip time as designed. 352 static UBool isSpecialTimeRoundTripCase(const char* loc, 353 const UnicodeString& id, 354 const char* pattern, 355 UDate time) { 356 struct { 357 const char* loc; 358 const char* id; 359 const char* pattern; 360 UDate time; 361 } EXCLUSIONS[] = { 362 {NULL, "Asia/Chita", "zzzz", 1414252800000.0}, 363 {NULL, "Asia/Chita", "vvvv", 1414252800000.0}, 364 {NULL, "Asia/Srednekolymsk", "zzzz", 1414241999999.0}, 365 {NULL, "Asia/Srednekolymsk", "vvvv", 1414241999999.0}, 366 {NULL, NULL, NULL, U_DATE_MIN} 367 }; 368 369 UBool isExcluded = FALSE; 370 for (int32_t i = 0; EXCLUSIONS[i].id != NULL; i++) { 371 if (EXCLUSIONS[i].loc == NULL || uprv_strcmp(loc, EXCLUSIONS[i].loc) == 0) { 372 if (id.compare(EXCLUSIONS[i].id) == 0) { 373 if (EXCLUSIONS[i].pattern == NULL || uprv_strcmp(pattern, EXCLUSIONS[i].pattern) == 0) { 374 if (EXCLUSIONS[i].time == U_DATE_MIN || EXCLUSIONS[i].time == time) { 375 isExcluded = TRUE; 376 } 377 } 378 } 379 } 380 } 381 return isExcluded; 382 } 383 384 // LocaleData. Somewhat misnamed. For TestTimeZoneRoundTrip, specifies the locales and patterns 385 // to be tested, and provides an iterator over these for the multi-threaded test 386 // functions to pick up the next combination to be tested. 387 // 388 // A single global instance of this struct is shared among all 389 // the test threads. 390 // 391 // "locales" is an array of locales to be tested. 392 // PATTERNS (a global) is an array of patterns to be tested for each locale. 393 // "localeIndex" and "patternIndex" keep track of the iteration through the above. 394 // Each of the parallel test threads calls LocaleData::nextTest() in a loop 395 // to find out what to test next. It must be thread safe. 396 struct LocaleData { 397 int32_t localeIndex; 398 int32_t patternIndex; 399 int32_t testCounts; 400 UDate times[UPRV_LENGTHOF(PATTERNS)]; // Performance data, Elapsed time for each pattern. 401 const Locale* locales; 402 int32_t nLocales; 403 UDate START_TIME; 404 UDate END_TIME; 405 int32_t numDone; 406 407 LocaleData() : localeIndex(0), patternIndex(0), testCounts(0), locales(NULL), 408 nLocales(0), START_TIME(0), END_TIME(0), numDone(0) { 409 for (int i=0; i<UPRV_LENGTHOF(times); i++) { 410 times[i] = 0; 411 } 412 }; 413 414 void resetTestIteration() { 415 localeIndex = -1; 416 patternIndex = UPRV_LENGTHOF(PATTERNS); 417 numDone = 0; 418 } 419 420 UBool nextTest(int32_t &rLocaleIndex, int32_t &rPatternIndex) { 421 Mutex lock; 422 if (patternIndex >= UPRV_LENGTHOF(PATTERNS) - 1) { 423 if (localeIndex >= nLocales - 1) { 424 return FALSE; 425 } 426 patternIndex = -1; 427 ++localeIndex; 428 } 429 ++patternIndex; 430 rLocaleIndex = localeIndex; 431 rPatternIndex = patternIndex; 432 ++numDone; 433 return TRUE; 434 } 435 436 void addTime(UDate amount, int32_t patIdx) { 437 Mutex lock; 438 U_ASSERT(patIdx < UPRV_LENGTHOF(PATTERNS)); 439 times[patIdx] += amount; 440 } 441 }; 442 443 static LocaleData *gLocaleData = NULL; 444 445 void 446 TimeZoneFormatTest::TestTimeRoundTrip(void) { 447 UErrorCode status = U_ZERO_ERROR; 448 LocalPointer <Calendar> cal(Calendar::createInstance(TimeZone::createTimeZone((UnicodeString) "UTC"), status)); 449 if (U_FAILURE(status)) { 450 dataerrln("Calendar::createInstance failed: %s", u_errorName(status)); 451 return; 452 } 453 454 const char* testAllProp = getProperty("TimeZoneRoundTripAll"); 455 UBool bTestAll = (testAllProp && uprv_strcmp(testAllProp, "true") == 0); 456 457 UDate START_TIME, END_TIME; 458 if (bTestAll || !quick) { 459 cal->set(1900, UCAL_JANUARY, 1); 460 } else { 461 cal->set(1999, UCAL_JANUARY, 1); 462 } 463 START_TIME = cal->getTime(status); 464 465 cal->set(2022, UCAL_JANUARY, 1); 466 END_TIME = cal->getTime(status); 467 468 if (U_FAILURE(status)) { 469 errln("getTime failed"); 470 return; 471 } 472 473 LocaleData localeData; 474 gLocaleData = &localeData; 475 476 // Set up test locales 477 const Locale locales1[] = {Locale("en")}; 478 const Locale locales2[] = { 479 Locale("ar_EG"), Locale("bg_BG"), Locale("ca_ES"), Locale("da_DK"), Locale("de"), 480 Locale("de_DE"), Locale("el_GR"), Locale("en"), Locale("en_AU"), Locale("en_CA"), 481 Locale("en_US"), Locale("es"), Locale("es_ES"), Locale("es_MX"), Locale("fi_FI"), 482 Locale("fr"), Locale("fr_CA"), Locale("fr_FR"), Locale("he_IL"), Locale("hu_HU"), 483 Locale("it"), Locale("it_IT"), Locale("ja"), Locale("ja_JP"), Locale("ko"), 484 Locale("ko_KR"), Locale("nb_NO"), Locale("nl_NL"), Locale("nn_NO"), Locale("pl_PL"), 485 Locale("pt"), Locale("pt_BR"), Locale("pt_PT"), Locale("ru_RU"), Locale("sv_SE"), 486 Locale("th_TH"), Locale("tr_TR"), Locale("zh"), Locale("zh_Hans"), Locale("zh_Hans_CN"), 487 Locale("zh_Hant"), Locale("zh_Hant_TW"), Locale("fa"), Locale("ccp") 488 }; 489 490 if (bTestAll) { 491 gLocaleData->locales = Locale::getAvailableLocales(gLocaleData->nLocales); 492 } else if (quick) { 493 gLocaleData->locales = locales1; 494 gLocaleData->nLocales = UPRV_LENGTHOF(locales1); 495 } else { 496 gLocaleData->locales = locales2; 497 gLocaleData->nLocales = UPRV_LENGTHOF(locales2); 498 } 499 500 gLocaleData->START_TIME = START_TIME; 501 gLocaleData->END_TIME = END_TIME; 502 gLocaleData->resetTestIteration(); 503 504 // start IntlTest.threadCount threads, each running the function RunTimeRoundTripTests(). 505 506 ThreadPool<TimeZoneFormatTest> threads(this, threadCount, &TimeZoneFormatTest::RunTimeRoundTripTests); 507 threads.start(); // Start all threads. 508 threads.join(); // Wait for all threads to finish. 509 510 UDate total = 0; 511 logln("### Elapsed time by patterns ###"); 512 for (int32_t i = 0; i < UPRV_LENGTHOF(PATTERNS); i++) { 513 logln(UnicodeString("") + gLocaleData->times[i] + "ms (" + PATTERNS[i] + ")"); 514 total += gLocaleData->times[i]; 515 } 516 logln((UnicodeString) "Total: " + total + "ms"); 517 logln((UnicodeString) "Iteration: " + gLocaleData->testCounts); 518 } 519 520 521 // TimeZoneFormatTest::RunTimeRoundTripTests() 522 // This function loops, running time zone format round trip test cases until there are no more, then returns. 523 // Threading: multiple invocations of this function are started in parallel 524 // by TimeZoneFormatTest::TestTimeRoundTrip() 525 // 526 void TimeZoneFormatTest::RunTimeRoundTripTests(int32_t threadNumber) { 527 UErrorCode status = U_ZERO_ERROR; 528 UBool REALLY_VERBOSE = FALSE; 529 530 // These patterns are ambiguous at DST->STD local time overlap 531 const char* AMBIGUOUS_DST_DECESSION[] = { "v", "vvvv", "V", "VV", "VVV", "VVVV", 0 }; 532 533 // These patterns are ambiguous at STD->STD/DST->DST local time overlap 534 const char* AMBIGUOUS_NEGATIVE_SHIFT[] = { "z", "zzzz", "v", "vvvv", "V", "VV", "VVV", "VVVV", 0 }; 535 536 // These patterns only support integer minutes offset 537 const char* MINUTES_OFFSET[] = { "X", "XX", "XXX", "x", "xx", "xxx", 0 }; 538 539 // Workaround for #6338 540 //UnicodeString BASEPATTERN("yyyy-MM-dd'T'HH:mm:ss.SSS"); 541 UnicodeString BASEPATTERN("yyyy.MM.dd HH:mm:ss.SSS"); 542 543 // timer for performance analysis 544 UDate timer; 545 UDate testTimes[4]; 546 UBool expectedRoundTrip[4]; 547 int32_t testLen = 0; 548 549 StringEnumeration *tzids = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL, NULL, NULL, status); 550 if (U_FAILURE(status)) { 551 if (status == U_MISSING_RESOURCE_ERROR) { 552 // This error is generally caused by data not being present. 553 dataerrln("TimeZone::createTimeZoneIDEnumeration failed - %s", u_errorName(status)); 554 } else { 555 errln("TimeZone::createTimeZoneIDEnumeration failed: %s", u_errorName(status)); 556 } 557 return; 558 } 559 560 int32_t locidx = -1; 561 int32_t patidx = -1; 562 563 while (gLocaleData->nextTest(locidx, patidx)) { 564 565 UnicodeString pattern(BASEPATTERN); 566 pattern.append(" ").append(PATTERNS[patidx]); 567 logln(" Thread %d, Locale %s, Pattern %s", 568 threadNumber, gLocaleData->locales[locidx].getName(), CStr(pattern)()); 569 570 SimpleDateFormat *sdf = new SimpleDateFormat(pattern, gLocaleData->locales[locidx], status); 571 if (U_FAILURE(status)) { 572 errcheckln(status, (UnicodeString) "new SimpleDateFormat failed for pattern " + 573 pattern + " for locale " + gLocaleData->locales[locidx].getName() + " - " + u_errorName(status)); 574 status = U_ZERO_ERROR; 575 continue; 576 } 577 578 UBool minutesOffset = contains(MINUTES_OFFSET, PATTERNS[patidx]); 579 580 tzids->reset(status); 581 const UnicodeString *tzid; 582 583 timer = Calendar::getNow(); 584 585 while ((tzid = tzids->snext(status))) { 586 if (uprv_strcmp(PATTERNS[patidx], "V") == 0) { 587 // Some zones do not have short ID assigned, such as Asia/Riyadh87. 588 // The time roundtrip will fail for such zones with pattern "V" (short zone ID). 589 // This is expected behavior. 590 const UChar* shortZoneID = ZoneMeta::getShortID(*tzid); 591 if (shortZoneID == NULL) { 592 continue; 593 } 594 } else if (uprv_strcmp(PATTERNS[patidx], "VVV") == 0) { 595 // Some zones are not associated with any region, such as Etc/GMT+8. 596 // The time roundtrip will fail for such zone with pattern "VVV" (exemplar location). 597 // This is expected behavior. 598 if (tzid->indexOf((UChar)0x2F) < 0 || tzid->indexOf(ETC_SLASH, -1, 0) >= 0 599 || tzid->indexOf(SYSTEMV_SLASH, -1, 0) >= 0 || tzid->indexOf(RIYADH8, -1, 0) >= 0) { 600 continue; 601 } 602 } 603 604 if ((*tzid == "Pacific/Apia" || *tzid == "Pacific/Midway" || *tzid == "Pacific/Pago_Pago") 605 && uprv_strcmp(PATTERNS[patidx], "vvvv") == 0 606 && logKnownIssue("11052", "Ambiguous zone name - Samoa Time")) { 607 continue; 608 } 609 610 BasicTimeZone *tz = (BasicTimeZone*) TimeZone::createTimeZone(*tzid); 611 sdf->setTimeZone(*tz); 612 613 UDate t = gLocaleData->START_TIME; 614 TimeZoneTransition tzt; 615 UBool tztAvail = FALSE; 616 UBool middle = TRUE; 617 618 while (t < gLocaleData->END_TIME) { 619 if (!tztAvail) { 620 testTimes[0] = t; 621 expectedRoundTrip[0] = TRUE; 622 testLen = 1; 623 } else { 624 int32_t fromOffset = tzt.getFrom()->getRawOffset() + tzt.getFrom()->getDSTSavings(); 625 int32_t toOffset = tzt.getTo()->getRawOffset() + tzt.getTo()->getDSTSavings(); 626 int32_t delta = toOffset - fromOffset; 627 if (delta < 0) { 628 UBool isDstDecession = tzt.getFrom()->getDSTSavings() > 0 && tzt.getTo()->getDSTSavings() == 0; 629 testTimes[0] = t + delta - 1; 630 expectedRoundTrip[0] = TRUE; 631 testTimes[1] = t + delta; 632 expectedRoundTrip[1] = isDstDecession ? 633 !contains(AMBIGUOUS_DST_DECESSION, PATTERNS[patidx]) : 634 !contains(AMBIGUOUS_NEGATIVE_SHIFT, PATTERNS[patidx]); 635 testTimes[2] = t - 1; 636 expectedRoundTrip[2] = isDstDecession ? 637 !contains(AMBIGUOUS_DST_DECESSION, PATTERNS[patidx]) : 638 !contains(AMBIGUOUS_NEGATIVE_SHIFT, PATTERNS[patidx]); 639 testTimes[3] = t; 640 expectedRoundTrip[3] = TRUE; 641 testLen = 4; 642 } else { 643 testTimes[0] = t - 1; 644 expectedRoundTrip[0] = TRUE; 645 testTimes[1] = t; 646 expectedRoundTrip[1] = TRUE; 647 testLen = 2; 648 } 649 } 650 for (int32_t testidx = 0; testidx < testLen; testidx++) { 651 if (quick) { 652 // reduce regular test time 653 if (!expectedRoundTrip[testidx]) { 654 continue; 655 } 656 } 657 658 { 659 Mutex lock; 660 gLocaleData->testCounts++; 661 } 662 663 UnicodeString text; 664 FieldPosition fpos(FieldPosition::DONT_CARE); 665 sdf->format(testTimes[testidx], text, fpos); 666 667 UDate parsedDate = sdf->parse(text, status); 668 if (U_FAILURE(status)) { 669 errln((UnicodeString) "Parse failure for text=" + text + ", tzid=" + *tzid + ", locale=" + gLocaleData->locales[locidx].getName() 670 + ", pattern=" + PATTERNS[patidx] + ", time=" + testTimes[testidx]); 671 status = U_ZERO_ERROR; 672 continue; 673 } 674 675 int32_t timeDiff = (int32_t)(parsedDate - testTimes[testidx]); 676 UBool bTimeMatch = minutesOffset ? 677 (timeDiff/60000)*60000 == 0 : timeDiff == 0; 678 if (!bTimeMatch) { 679 UnicodeString msg = (UnicodeString) "Time round trip failed for " + "tzid=" + *tzid 680 + ", locale=" + gLocaleData->locales[locidx].getName() + ", pattern=" + PATTERNS[patidx] 681 + ", text=" + text + ", time=" + testTimes[testidx] + ", restime=" + parsedDate + ", diff=" + (parsedDate - testTimes[testidx]); 682 // Timebomb for TZData update 683 if (expectedRoundTrip[testidx] 684 && !isSpecialTimeRoundTripCase(gLocaleData->locales[locidx].getName(), *tzid, 685 PATTERNS[patidx], testTimes[testidx])) { 686 errln((UnicodeString) "FAIL: " + msg); 687 } else if (REALLY_VERBOSE) { 688 logln(msg); 689 } 690 } 691 } 692 tztAvail = tz->getNextTransition(t, FALSE, tzt); 693 if (!tztAvail) { 694 break; 695 } 696 if (middle) { 697 // Test the date in the middle of two transitions. 698 t += (int64_t) ((tzt.getTime() - t) / 2); 699 middle = FALSE; 700 tztAvail = FALSE; 701 } else { 702 t = tzt.getTime(); 703 } 704 } 705 delete tz; 706 } 707 UDate elapsedTime = Calendar::getNow() - timer; 708 gLocaleData->addTime(elapsedTime, patidx); 709 delete sdf; 710 } 711 delete tzids; 712 } 713 714 715 typedef struct { 716 const char* text; 717 int32_t inPos; 718 const char* locale; 719 UTimeZoneFormatStyle style; 720 uint32_t parseOptions; 721 const char* expected; 722 int32_t outPos; 723 UTimeZoneFormatTimeType timeType; 724 } ParseTestData; 725 726 void 727 TimeZoneFormatTest::TestParse(void) { 728 const ParseTestData DATA[] = { 729 // text inPos locale style 730 // parseOptions expected outPos timeType 731 {"Z", 0, "en_US", UTZFMT_STYLE_ISO_EXTENDED_FULL, 732 UTZFMT_PARSE_OPTION_NONE, "Etc/GMT", 1, UTZFMT_TIME_TYPE_UNKNOWN}, 733 734 {"Z", 0, "en_US", UTZFMT_STYLE_SPECIFIC_LONG, 735 UTZFMT_PARSE_OPTION_NONE, "Etc/GMT", 1, UTZFMT_TIME_TYPE_UNKNOWN}, 736 737 {"Zambia time", 0, "en_US", UTZFMT_STYLE_ISO_EXTENDED_FULL, 738 UTZFMT_PARSE_OPTION_ALL_STYLES, "Etc/GMT", 1, UTZFMT_TIME_TYPE_UNKNOWN}, 739 740 {"Zambia time", 0, "en_US", UTZFMT_STYLE_GENERIC_LOCATION, 741 UTZFMT_PARSE_OPTION_NONE, "Africa/Lusaka", 11, UTZFMT_TIME_TYPE_UNKNOWN}, 742 743 {"Zambia time", 0, "en_US", UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL, 744 UTZFMT_PARSE_OPTION_ALL_STYLES, "Africa/Lusaka", 11, UTZFMT_TIME_TYPE_UNKNOWN}, 745 746 {"+00:00", 0, "en_US", UTZFMT_STYLE_ISO_EXTENDED_FULL, 747 UTZFMT_PARSE_OPTION_NONE, "Etc/GMT", 6, UTZFMT_TIME_TYPE_UNKNOWN}, 748 749 {"-01:30:45", 0, "en_US", UTZFMT_STYLE_ISO_EXTENDED_FULL, 750 UTZFMT_PARSE_OPTION_NONE, "GMT-01:30:45", 9, UTZFMT_TIME_TYPE_UNKNOWN}, 751 752 {"-7", 0, "en_US", UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL, 753 UTZFMT_PARSE_OPTION_NONE, "GMT-07:00", 2, UTZFMT_TIME_TYPE_UNKNOWN}, 754 755 {"-2222", 0, "en_US", UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL, 756 UTZFMT_PARSE_OPTION_NONE, "GMT-22:22", 5, UTZFMT_TIME_TYPE_UNKNOWN}, 757 758 {"-3333", 0, "en_US", UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL, 759 UTZFMT_PARSE_OPTION_NONE, "GMT-03:33", 4, UTZFMT_TIME_TYPE_UNKNOWN}, 760 761 {"XXX+01:30YYY", 3, "en_US", UTZFMT_STYLE_LOCALIZED_GMT, 762 UTZFMT_PARSE_OPTION_NONE, "GMT+01:30", 9, UTZFMT_TIME_TYPE_UNKNOWN}, 763 764 {"GMT0", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT, 765 UTZFMT_PARSE_OPTION_NONE, "Etc/GMT", 3, UTZFMT_TIME_TYPE_UNKNOWN}, 766 767 {"EST", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT, 768 UTZFMT_PARSE_OPTION_NONE, "America/New_York", 3, UTZFMT_TIME_TYPE_STANDARD}, 769 770 {"ESTx", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT, 771 UTZFMT_PARSE_OPTION_NONE, "America/New_York", 3, UTZFMT_TIME_TYPE_STANDARD}, 772 773 {"EDTx", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT, 774 UTZFMT_PARSE_OPTION_NONE, "America/New_York", 3, UTZFMT_TIME_TYPE_DAYLIGHT}, 775 776 {"EST", 0, "en_US", UTZFMT_STYLE_SPECIFIC_LONG, 777 UTZFMT_PARSE_OPTION_NONE, NULL, 0, UTZFMT_TIME_TYPE_UNKNOWN}, 778 779 {"EST", 0, "en_US", UTZFMT_STYLE_SPECIFIC_LONG, 780 UTZFMT_PARSE_OPTION_ALL_STYLES, "America/New_York", 3, UTZFMT_TIME_TYPE_STANDARD}, 781 782 {"EST", 0, "en_CA", UTZFMT_STYLE_SPECIFIC_SHORT, 783 UTZFMT_PARSE_OPTION_NONE, "America/Toronto", 3, UTZFMT_TIME_TYPE_STANDARD}, 784 785 {"CST", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT, 786 UTZFMT_PARSE_OPTION_NONE, "America/Chicago", 3, UTZFMT_TIME_TYPE_STANDARD}, 787 788 {"CST", 0, "en_GB", UTZFMT_STYLE_SPECIFIC_SHORT, 789 UTZFMT_PARSE_OPTION_NONE, NULL, 0, UTZFMT_TIME_TYPE_UNKNOWN}, 790 791 {"CST", 0, "en_GB", UTZFMT_STYLE_SPECIFIC_SHORT, 792 UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS, "America/Chicago", 3, UTZFMT_TIME_TYPE_STANDARD}, 793 794 {"--CST--", 2, "en_GB", UTZFMT_STYLE_SPECIFIC_SHORT, 795 UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS, "America/Chicago", 5, UTZFMT_TIME_TYPE_STANDARD}, 796 797 {"CST", 0, "zh_CN", UTZFMT_STYLE_SPECIFIC_SHORT, 798 UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS, "Asia/Shanghai", 3, UTZFMT_TIME_TYPE_STANDARD}, 799 800 {"AEST", 0, "en_AU", UTZFMT_STYLE_SPECIFIC_SHORT, 801 UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS, "Australia/Sydney", 4, UTZFMT_TIME_TYPE_STANDARD}, 802 803 {"AST", 0, "ar_SA", UTZFMT_STYLE_SPECIFIC_SHORT, 804 UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS, "Asia/Riyadh", 3, UTZFMT_TIME_TYPE_STANDARD}, 805 806 {"AQTST", 0, "en", UTZFMT_STYLE_SPECIFIC_LONG, 807 UTZFMT_PARSE_OPTION_NONE, NULL, 0, UTZFMT_TIME_TYPE_UNKNOWN}, 808 809 {"AQTST", 0, "en", UTZFMT_STYLE_SPECIFIC_LONG, 810 UTZFMT_PARSE_OPTION_ALL_STYLES, NULL, 0, UTZFMT_TIME_TYPE_UNKNOWN}, 811 812 {"AQTST", 0, "en", UTZFMT_STYLE_SPECIFIC_LONG, 813 UTZFMT_PARSE_OPTION_ALL_STYLES | UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS, "Asia/Aqtobe", 5, UTZFMT_TIME_TYPE_DAYLIGHT}, 814 815 {NULL, 0, NULL, UTZFMT_STYLE_GENERIC_LOCATION, 816 UTZFMT_PARSE_OPTION_NONE, NULL, 0, UTZFMT_TIME_TYPE_UNKNOWN} 817 }; 818 819 for (int32_t i = 0; DATA[i].text; i++) { 820 UErrorCode status = U_ZERO_ERROR; 821 LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(Locale(DATA[i].locale), status)); 822 if (U_FAILURE(status)) { 823 dataerrln("Fail TimeZoneFormat::createInstance: %s", u_errorName(status)); 824 continue; 825 } 826 UTimeZoneFormatTimeType ttype = UTZFMT_TIME_TYPE_UNKNOWN; 827 ParsePosition pos(DATA[i].inPos); 828 TimeZone* tz = tzfmt->parse(DATA[i].style, DATA[i].text, pos, DATA[i].parseOptions, &ttype); 829 830 UnicodeString errMsg; 831 if (tz) { 832 UnicodeString outID; 833 tz->getID(outID); 834 if (outID != UnicodeString(DATA[i].expected)) { 835 errMsg = (UnicodeString)"Time zone ID: " + outID + " - expected: " + DATA[i].expected; 836 } else if (pos.getIndex() != DATA[i].outPos) { 837 errMsg = (UnicodeString)"Parsed pos: " + pos.getIndex() + " - expected: " + DATA[i].outPos; 838 } else if (ttype != DATA[i].timeType) { 839 errMsg = (UnicodeString)"Time type: " + ttype + " - expected: " + DATA[i].timeType; 840 } 841 delete tz; 842 } else { 843 if (DATA[i].expected) { 844 errMsg = (UnicodeString)"Parse failure - expected: " + DATA[i].expected; 845 } 846 } 847 if (errMsg.length() > 0) { 848 errln((UnicodeString)"Fail: " + errMsg + " [text=" + DATA[i].text + ", pos=" + DATA[i].inPos + ", style=" + DATA[i].style + "]"); 849 } 850 } 851 } 852 853 void 854 TimeZoneFormatTest::TestISOFormat(void) { 855 const int32_t OFFSET[] = { 856 0, // 0 857 999, // 0.999s 858 -59999, // -59.999s 859 60000, // 1m 860 -77777, // -1m 17.777s 861 1800000, // 30m 862 -3600000, // -1h 863 36000000, // 10h 864 -37800000, // -10h 30m 865 -37845000, // -10h 30m 45s 866 108000000, // 30h 867 }; 868 869 const char* ISO_STR[][11] = { 870 // 0 871 { 872 "Z", "Z", "Z", "Z", "Z", 873 "+00", "+0000", "+00:00", "+0000", "+00:00", 874 "+0000" 875 }, 876 // 999 877 { 878 "Z", "Z", "Z", "Z", "Z", 879 "+00", "+0000", "+00:00", "+0000", "+00:00", 880 "+0000" 881 }, 882 // -59999 883 { 884 "Z", "Z", "Z", "-000059", "-00:00:59", 885 "+00", "+0000", "+00:00", "-000059", "-00:00:59", 886 "-000059" 887 }, 888 // 60000 889 { 890 "+0001", "+0001", "+00:01", "+0001", "+00:01", 891 "+0001", "+0001", "+00:01", "+0001", "+00:01", 892 "+0001" 893 }, 894 // -77777 895 { 896 "-0001", "-0001", "-00:01", "-000117", "-00:01:17", 897 "-0001", "-0001", "-00:01", "-000117", "-00:01:17", 898 "-000117" 899 }, 900 // 1800000 901 { 902 "+0030", "+0030", "+00:30", "+0030", "+00:30", 903 "+0030", "+0030", "+00:30", "+0030", "+00:30", 904 "+0030" 905 }, 906 // -3600000 907 { 908 "-01", "-0100", "-01:00", "-0100", "-01:00", 909 "-01", "-0100", "-01:00", "-0100", "-01:00", 910 "-0100" 911 }, 912 // 36000000 913 { 914 "+10", "+1000", "+10:00", "+1000", "+10:00", 915 "+10", "+1000", "+10:00", "+1000", "+10:00", 916 "+1000" 917 }, 918 // -37800000 919 { 920 "-1030", "-1030", "-10:30", "-1030", "-10:30", 921 "-1030", "-1030", "-10:30", "-1030", "-10:30", 922 "-1030" 923 }, 924 // -37845000 925 { 926 "-1030", "-1030", "-10:30", "-103045", "-10:30:45", 927 "-1030", "-1030", "-10:30", "-103045", "-10:30:45", 928 "-103045" 929 }, 930 // 108000000 931 { 932 0, 0, 0, 0, 0, 933 0, 0, 0, 0, 0, 934 0 935 } 936 }; 937 938 const char* PATTERN[] = { 939 "X", "XX", "XXX", "XXXX", "XXXXX", 940 "x", "xx", "xxx", "xxxx", "xxxxx", 941 "Z", // equivalent to "xxxx" 942 0 943 }; 944 945 const int32_t MIN_OFFSET_UNIT[] = { 946 60000, 60000, 60000, 1000, 1000, 947 60000, 60000, 60000, 1000, 1000, 948 1000, 949 }; 950 951 // Formatting 952 UErrorCode status = U_ZERO_ERROR; 953 LocalPointer<SimpleDateFormat> sdf(new SimpleDateFormat(status), status); 954 if (U_FAILURE(status)) { 955 dataerrln("Fail new SimpleDateFormat: %s", u_errorName(status)); 956 return; 957 } 958 UDate d = Calendar::getNow(); 959 960 for (uint32_t i = 0; i < UPRV_LENGTHOF(OFFSET); i++) { 961 SimpleTimeZone* tz = new SimpleTimeZone(OFFSET[i], UnicodeString("Zone Offset:") + OFFSET[i] + "ms"); 962 sdf->adoptTimeZone(tz); 963 for (int32_t j = 0; PATTERN[j] != 0; j++) { 964 sdf->applyPattern(UnicodeString(PATTERN[j])); 965 UnicodeString result; 966 sdf->format(d, result); 967 968 if (ISO_STR[i][j]) { 969 if (result != UnicodeString(ISO_STR[i][j])) { 970 errln((UnicodeString)"FAIL: pattern=" + PATTERN[j] + ", offset=" + OFFSET[i] + " -> " 971 + result + " (expected: " + ISO_STR[i][j] + ")"); 972 } 973 } else { 974 // Offset out of range 975 // Note: for now, there is no way to propagate the error status through 976 // the SimpleDateFormat::format above. 977 if (result.length() > 0) { 978 errln((UnicodeString)"FAIL: Non-Empty result for pattern=" + PATTERN[j] + ", offset=" + OFFSET[i] 979 + " (expected: empty result)"); 980 } 981 } 982 } 983 } 984 985 // Parsing 986 LocalPointer<Calendar> outcal(Calendar::createInstance(status)); 987 if (U_FAILURE(status)) { 988 dataerrln("Fail new Calendar: %s", u_errorName(status)); 989 return; 990 } 991 for (int32_t i = 0; ISO_STR[i][0] != NULL; i++) { 992 for (int32_t j = 0; PATTERN[j] != 0; j++) { 993 if (ISO_STR[i][j] == 0) { 994 continue; 995 } 996 ParsePosition pos(0); 997 SimpleTimeZone* bogusTZ = new SimpleTimeZone(-1, UnicodeString("Zone Offset: -1ms")); 998 outcal->adoptTimeZone(bogusTZ); 999 sdf->applyPattern(PATTERN[j]); 1000 1001 sdf->parse(UnicodeString(ISO_STR[i][j]), *(outcal.getAlias()), pos); 1002 1003 if (pos.getIndex() != (int32_t)uprv_strlen(ISO_STR[i][j])) { 1004 errln((UnicodeString)"FAIL: Failed to parse the entire input string: " + ISO_STR[i][j]); 1005 } 1006 1007 const TimeZone& outtz = outcal->getTimeZone(); 1008 int32_t outOffset = outtz.getRawOffset(); 1009 int32_t adjustedOffset = OFFSET[i] / MIN_OFFSET_UNIT[j] * MIN_OFFSET_UNIT[j]; 1010 if (outOffset != adjustedOffset) { 1011 errln((UnicodeString)"FAIL: Incorrect offset:" + outOffset + "ms for input string: " + ISO_STR[i][j] 1012 + " (expected:" + adjustedOffset + "ms)"); 1013 } 1014 } 1015 } 1016 } 1017 1018 1019 typedef struct { 1020 const char* locale; 1021 const char* tzid; 1022 UDate date; 1023 UTimeZoneFormatStyle style; 1024 const char* expected; 1025 UTimeZoneFormatTimeType timeType; 1026 } FormatTestData; 1027 1028 void 1029 TimeZoneFormatTest::TestFormat(void) { 1030 UDate dateJan = 1358208000000.0; // 2013-01-15T00:00:00Z 1031 UDate dateJul = 1373846400000.0; // 2013-07-15T00:00:00Z 1032 1033 const FormatTestData DATA[] = { 1034 { 1035 "en", 1036 "America/Los_Angeles", 1037 dateJan, 1038 UTZFMT_STYLE_GENERIC_LOCATION, 1039 "Los Angeles Time", 1040 UTZFMT_TIME_TYPE_UNKNOWN 1041 }, 1042 { 1043 "en", 1044 "America/Los_Angeles", 1045 dateJan, 1046 UTZFMT_STYLE_GENERIC_LONG, 1047 "Pacific Time", 1048 UTZFMT_TIME_TYPE_UNKNOWN 1049 }, 1050 { 1051 "en", 1052 "America/Los_Angeles", 1053 dateJan, 1054 UTZFMT_STYLE_SPECIFIC_LONG, 1055 "Pacific Standard Time", 1056 UTZFMT_TIME_TYPE_STANDARD 1057 }, 1058 { 1059 "en", 1060 "America/Los_Angeles", 1061 dateJul, 1062 UTZFMT_STYLE_SPECIFIC_LONG, 1063 "Pacific Daylight Time", 1064 UTZFMT_TIME_TYPE_DAYLIGHT 1065 }, 1066 { 1067 "ja", 1068 "America/Los_Angeles", 1069 dateJan, 1070 UTZFMT_STYLE_ZONE_ID, 1071 "America/Los_Angeles", 1072 UTZFMT_TIME_TYPE_UNKNOWN 1073 }, 1074 { 1075 "fr", 1076 "America/Los_Angeles", 1077 dateJul, 1078 UTZFMT_STYLE_ZONE_ID_SHORT, 1079 "uslax", 1080 UTZFMT_TIME_TYPE_UNKNOWN 1081 }, 1082 { 1083 "en", 1084 "America/Los_Angeles", 1085 dateJan, 1086 UTZFMT_STYLE_EXEMPLAR_LOCATION, 1087 "Los Angeles", 1088 UTZFMT_TIME_TYPE_UNKNOWN 1089 }, 1090 1091 { 1092 "ja", 1093 "Asia/Tokyo", 1094 dateJan, 1095 UTZFMT_STYLE_GENERIC_LONG, 1096 "\\u65E5\\u672C\\u6A19\\u6E96\\u6642", 1097 UTZFMT_TIME_TYPE_UNKNOWN 1098 }, 1099 1100 {0, 0, 0.0, UTZFMT_STYLE_GENERIC_LOCATION, 0, UTZFMT_TIME_TYPE_UNKNOWN} 1101 }; 1102 1103 for (int32_t i = 0; DATA[i].locale; i++) { 1104 UErrorCode status = U_ZERO_ERROR; 1105 LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(Locale(DATA[i].locale), status)); 1106 if (U_FAILURE(status)) { 1107 dataerrln("Fail TimeZoneFormat::createInstance: %s", u_errorName(status)); 1108 continue; 1109 } 1110 1111 LocalPointer<TimeZone> tz(TimeZone::createTimeZone(DATA[i].tzid)); 1112 UnicodeString out; 1113 UTimeZoneFormatTimeType timeType; 1114 1115 tzfmt->format(DATA[i].style, *(tz.getAlias()), DATA[i].date, out, &timeType); 1116 UnicodeString expected(DATA[i].expected, -1, US_INV); 1117 expected = expected.unescape(); 1118 1119 assertEquals(UnicodeString("Format result for ") + DATA[i].tzid + " (Test Case " + i + ")", expected, out); 1120 if (DATA[i].timeType != timeType) { 1121 dataerrln(UnicodeString("Formatted time zone type (Test Case ") + i + "), returned=" 1122 + timeType + ", expected=" + DATA[i].timeType); 1123 } 1124 } 1125 } 1126 1127 void 1128 TimeZoneFormatTest::TestFormatTZDBNames(void) { 1129 UDate dateJan = 1358208000000.0; // 2013-01-15T00:00:00Z 1130 UDate dateJul = 1373846400000.0; // 2013-07-15T00:00:00Z 1131 1132 const FormatTestData DATA[] = { 1133 { 1134 "en", 1135 "America/Chicago", 1136 dateJan, 1137 UTZFMT_STYLE_SPECIFIC_SHORT, 1138 "CST", 1139 UTZFMT_TIME_TYPE_STANDARD 1140 }, 1141 { 1142 "en", 1143 "Asia/Shanghai", 1144 dateJan, 1145 UTZFMT_STYLE_SPECIFIC_SHORT, 1146 "CST", 1147 UTZFMT_TIME_TYPE_STANDARD 1148 }, 1149 { 1150 "zh_Hans", 1151 "Asia/Shanghai", 1152 dateJan, 1153 UTZFMT_STYLE_SPECIFIC_SHORT, 1154 "CST", 1155 UTZFMT_TIME_TYPE_STANDARD 1156 }, 1157 { 1158 "en", 1159 "America/Los_Angeles", 1160 dateJul, 1161 UTZFMT_STYLE_SPECIFIC_LONG, 1162 "GMT-07:00", // No long display names 1163 UTZFMT_TIME_TYPE_DAYLIGHT 1164 }, 1165 { 1166 "ja", 1167 "America/Los_Angeles", 1168 dateJul, 1169 UTZFMT_STYLE_SPECIFIC_SHORT, 1170 "PDT", 1171 UTZFMT_TIME_TYPE_DAYLIGHT 1172 }, 1173 { 1174 "en", 1175 "Australia/Sydney", 1176 dateJan, 1177 UTZFMT_STYLE_SPECIFIC_SHORT, 1178 "AEDT", 1179 UTZFMT_TIME_TYPE_DAYLIGHT 1180 }, 1181 { 1182 "en", 1183 "Australia/Sydney", 1184 dateJul, 1185 UTZFMT_STYLE_SPECIFIC_SHORT, 1186 "AEST", 1187 UTZFMT_TIME_TYPE_STANDARD 1188 }, 1189 1190 {0, 0, 0.0, UTZFMT_STYLE_GENERIC_LOCATION, 0, UTZFMT_TIME_TYPE_UNKNOWN} 1191 }; 1192 1193 for (int32_t i = 0; DATA[i].locale; i++) { 1194 UErrorCode status = U_ZERO_ERROR; 1195 Locale loc(DATA[i].locale); 1196 LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(loc, status)); 1197 if (U_FAILURE(status)) { 1198 dataerrln("Fail TimeZoneFormat::createInstance: %s", u_errorName(status)); 1199 continue; 1200 } 1201 TimeZoneNames *tzdbNames = TimeZoneNames::createTZDBInstance(loc, status); 1202 if (U_FAILURE(status)) { 1203 dataerrln("Fail TimeZoneNames::createTZDBInstance: %s", u_errorName(status)); 1204 continue; 1205 } 1206 tzfmt->adoptTimeZoneNames(tzdbNames); 1207 1208 LocalPointer<TimeZone> tz(TimeZone::createTimeZone(DATA[i].tzid)); 1209 UnicodeString out; 1210 UTimeZoneFormatTimeType timeType; 1211 1212 tzfmt->format(DATA[i].style, *(tz.getAlias()), DATA[i].date, out, &timeType); 1213 UnicodeString expected(DATA[i].expected, -1, US_INV); 1214 expected = expected.unescape(); 1215 1216 assertEquals(UnicodeString("Format result for ") + DATA[i].tzid + " (Test Case " + i + ")", expected, out); 1217 if (DATA[i].timeType != timeType) { 1218 dataerrln(UnicodeString("Formatted time zone type (Test Case ") + i + "), returned=" 1219 + timeType + ", expected=" + DATA[i].timeType); 1220 } 1221 } 1222 } 1223 1224 void 1225 TimeZoneFormatTest::TestFormatCustomZone(void) { 1226 struct { 1227 const char* id; 1228 int32_t offset; 1229 const char* expected; 1230 } TESTDATA[] = { 1231 { "abc", 3600000, "GMT+01:00" }, // unknown ID 1232 { "$abc", -3600000, "GMT-01:00" }, // unknown, with ASCII variant char '$' 1233 { "\\u00c1\\u00df\\u00c7", 5400000, "GMT+01:30"}, // unknown, with non-ASCII chars 1234 { 0, 0, 0 } 1235 }; 1236 1237 UDate now = Calendar::getNow(); 1238 1239 for (int32_t i = 0; ; i++) { 1240 const char *id = TESTDATA[i].id; 1241 if (id == 0) { 1242 break; 1243 } 1244 UnicodeString tzid = UnicodeString(id, -1, US_INV).unescape(); 1245 SimpleTimeZone tz(TESTDATA[i].offset, tzid); 1246 1247 UErrorCode status = U_ZERO_ERROR; 1248 LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(Locale("en"), status)); 1249 if (tzfmt.isNull()) { 1250 dataerrln("FAIL: TimeZoneFormat::createInstance failed for en"); 1251 return; 1252 } 1253 UnicodeString tzstr; 1254 UnicodeString expected = UnicodeString(TESTDATA[i].expected, -1, US_INV).unescape(); 1255 1256 tzfmt->format(UTZFMT_STYLE_SPECIFIC_LONG, tz, now, tzstr, NULL); 1257 assertEquals(UnicodeString("Format result for ") + tzid, expected, tzstr); 1258 } 1259 } 1260 1261 void 1262 TimeZoneFormatTest::TestFormatTZDBNamesAllZoneCoverage(void) { 1263 UErrorCode status = U_ZERO_ERROR; 1264 LocalPointer<StringEnumeration> tzids(TimeZone::createEnumeration()); 1265 if (tzids.getAlias() == nullptr) { 1266 dataerrln("%s %d tzids is null", __FILE__, __LINE__); 1267 return; 1268 } 1269 const UnicodeString *tzid; 1270 LocalPointer<TimeZoneNames> tzdbNames(TimeZoneNames::createTZDBInstance(Locale("en"), status)); 1271 UDate now = Calendar::getNow(); 1272 UnicodeString mzId; 1273 UnicodeString name; 1274 while ((tzid = tzids->snext(status))) { 1275 logln("Zone: " + *tzid); 1276 LocalPointer<TimeZone> tz(TimeZone::createTimeZone(*tzid)); 1277 tzdbNames->getMetaZoneID(*tzid, now, mzId); 1278 if (mzId.isBogus()) { 1279 logln((UnicodeString)"Meta zone: <not available>"); 1280 } else { 1281 logln((UnicodeString)"Meta zone: " + mzId); 1282 } 1283 1284 // mzID could be bogus here 1285 tzdbNames->getMetaZoneDisplayName(mzId, UTZNM_SHORT_STANDARD, name); 1286 // name could be bogus here 1287 if (name.isBogus()) { 1288 logln((UnicodeString)"Meta zone short standard name: <not available>"); 1289 } 1290 else { 1291 logln((UnicodeString)"Meta zone short standard name: " + name); 1292 } 1293 1294 tzdbNames->getMetaZoneDisplayName(mzId, UTZNM_SHORT_DAYLIGHT, name); 1295 // name could be bogus here 1296 if (name.isBogus()) { 1297 logln((UnicodeString)"Meta zone short daylight name: <not available>"); 1298 } 1299 else { 1300 logln((UnicodeString)"Meta zone short daylight name: " + name); 1301 } 1302 } 1303 } 1304 1305 #endif /* #if !UCONFIG_NO_FORMATTING */ 1306