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