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