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