1/* 2******************************************************************************* 3* Copyright (C) 2007-2014, 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 26static 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}; 50static const int NUM_PATTERNS = sizeof(PATTERNS)/sizeof(const char*); 51 52static const UChar ETC_UNKNOWN[] = {0x45, 0x74, 0x63, 0x2F, 0x55, 0x6E, 0x6B, 0x6E, 0x6F, 0x77, 0x6E, 0}; 53 54static const UChar ETC_SLASH[] = { 0x45, 0x74, 0x63, 0x2F, 0 }; // "Etc/" 55static const UChar SYSTEMV_SLASH[] = { 0x53, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x56, 0x2F, 0 }; // "SystemV/ 56static const UChar RIYADH8[] = { 0x52, 0x69, 0x79, 0x61, 0x64, 0x68, 0x38, 0 }; // "Riyadh8" 57 58static 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 67void 68TimeZoneFormatTest::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 82void 83TimeZoneFormatTest::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// Special exclusions in TestTimeZoneRoundTrip. 337// These special cases do not round trip time as designed. 338static UBool isSpecialTimeRoundTripCase(const char* loc, 339 const UnicodeString& id, 340 const char* pattern, 341 UDate time) { 342 struct { 343 const char* loc; 344 const char* id; 345 const char* pattern; 346 UDate time; 347 } EXCLUSIONS[] = { 348 {NULL, "Asia/Chita", "zzzz", 1414252800000.0}, 349 {NULL, "Asia/Chita", "vvvv", 1414252800000.0}, 350 {NULL, "Asia/Srednekolymsk", "zzzz", 1414241999999.0}, 351 {NULL, "Asia/Srednekolymsk", "vvvv", 1414241999999.0}, 352 {NULL, NULL, NULL, U_DATE_MIN} 353 }; 354 355 UBool isExcluded = FALSE; 356 for (int32_t i = 0; EXCLUSIONS[i].id != NULL; i++) { 357 if (EXCLUSIONS[i].loc == NULL || uprv_strcmp(loc, EXCLUSIONS[i].loc) == 0) { 358 if (id.compare(EXCLUSIONS[i].id) == 0) { 359 if (EXCLUSIONS[i].pattern == NULL || uprv_strcmp(pattern, EXCLUSIONS[i].pattern) == 0) { 360 if (EXCLUSIONS[i].time == U_DATE_MIN || EXCLUSIONS[i].time == time) { 361 isExcluded = TRUE; 362 } 363 } 364 } 365 } 366 } 367 return isExcluded; 368} 369 370struct LocaleData { 371 int32_t index; 372 int32_t testCounts; 373 UDate *times; 374 const Locale* locales; // Static 375 int32_t nLocales; // Static 376 UBool quick; // Static 377 UDate START_TIME; // Static 378 UDate END_TIME; // Static 379 int32_t numDone; 380}; 381 382class TestTimeRoundTripThread: public SimpleThread { 383public: 384 TestTimeRoundTripThread(IntlTest& tlog, LocaleData &ld, int32_t i) 385 : log(tlog), data(ld), index(i) {} 386 virtual void run() { 387 UErrorCode status = U_ZERO_ERROR; 388 UBool REALLY_VERBOSE = FALSE; 389 390 // These patterns are ambiguous at DST->STD local time overlap 391 const char* AMBIGUOUS_DST_DECESSION[] = { "v", "vvvv", "V", "VV", "VVV", "VVVV", 0 }; 392 393 // These patterns are ambiguous at STD->STD/DST->DST local time overlap 394 const char* AMBIGUOUS_NEGATIVE_SHIFT[] = { "z", "zzzz", "v", "vvvv", "V", "VV", "VVV", "VVVV", 0 }; 395 396 // These patterns only support integer minutes offset 397 const char* MINUTES_OFFSET[] = { "X", "XX", "XXX", "x", "xx", "xxx", 0 }; 398 399 // Workaround for #6338 400 //UnicodeString BASEPATTERN("yyyy-MM-dd'T'HH:mm:ss.SSS"); 401 UnicodeString BASEPATTERN("yyyy.MM.dd HH:mm:ss.SSS"); 402 403 // timer for performance analysis 404 UDate timer; 405 UDate testTimes[4]; 406 UBool expectedRoundTrip[4]; 407 int32_t testLen = 0; 408 409 StringEnumeration *tzids = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL, NULL, NULL, status); 410 if (U_FAILURE(status)) { 411 if (status == U_MISSING_RESOURCE_ERROR) { 412 /* This error is generally caused by data not being present. However, an infinite loop will occur 413 * because the thread thinks that the test data is never done so we should treat the data as done. 414 */ 415 log.dataerrln("TimeZone::createTimeZoneIDEnumeration failed - %s", u_errorName(status)); 416 data.numDone = data.nLocales; 417 } else { 418 log.errln("TimeZone::createTimeZoneIDEnumeration failed: %s", u_errorName(status)); 419 } 420 return; 421 } 422 423 int32_t locidx = -1; 424 UDate times[NUM_PATTERNS]; 425 for (int32_t i = 0; i < NUM_PATTERNS; i++) { 426 times[i] = 0; 427 } 428 429 int32_t testCounts = 0; 430 431 while (true) { 432 umtx_lock(NULL); // Lock to increment the index 433 for (int32_t i = 0; i < NUM_PATTERNS; i++) { 434 data.times[i] += times[i]; 435 data.testCounts += testCounts; 436 } 437 if (data.index < data.nLocales) { 438 locidx = data.index; 439 data.index++; 440 } else { 441 locidx = -1; 442 } 443 umtx_unlock(NULL); // Unlock for other threads to use 444 445 if (locidx == -1) { 446 log.logln((UnicodeString) "Thread " + index + " is done."); 447 break; 448 } 449 450 log.logln((UnicodeString) "\nThread " + index + ": Locale: " + UnicodeString(data.locales[locidx].getName())); 451 452 for (int32_t patidx = 0; patidx < NUM_PATTERNS; patidx++) { 453 log.logln((UnicodeString) " Pattern: " + PATTERNS[patidx]); 454 times[patidx] = 0; 455 456 UnicodeString pattern(BASEPATTERN); 457 pattern.append(" ").append(PATTERNS[patidx]); 458 459 SimpleDateFormat *sdf = new SimpleDateFormat(pattern, data.locales[locidx], status); 460 if (U_FAILURE(status)) { 461 log.errcheckln(status, (UnicodeString) "new SimpleDateFormat failed for pattern " + 462 pattern + " for locale " + data.locales[locidx].getName() + " - " + u_errorName(status)); 463 status = U_ZERO_ERROR; 464 continue; 465 } 466 467 UBool minutesOffset = contains(MINUTES_OFFSET, PATTERNS[patidx]); 468 469 tzids->reset(status); 470 const UnicodeString *tzid; 471 472 timer = Calendar::getNow(); 473 474 while ((tzid = tzids->snext(status))) { 475 if (uprv_strcmp(PATTERNS[patidx], "V") == 0) { 476 // Some zones do not have short ID assigned, such as Asia/Riyadh87. 477 // The time roundtrip will fail for such zones with pattern "V" (short zone ID). 478 // This is expected behavior. 479 const UChar* shortZoneID = ZoneMeta::getShortID(*tzid); 480 if (shortZoneID == NULL) { 481 continue; 482 } 483 } else if (uprv_strcmp(PATTERNS[patidx], "VVV") == 0) { 484 // Some zones are not associated with any region, such as Etc/GMT+8. 485 // The time roundtrip will fail for such zone with pattern "VVV" (exemplar location). 486 // This is expected behavior. 487 if (tzid->indexOf((UChar)0x2F) < 0 || tzid->indexOf(ETC_SLASH, -1, 0) >= 0 488 || tzid->indexOf(SYSTEMV_SLASH, -1, 0) >= 0 || tzid->indexOf(RIYADH8, -1, 0) >= 0) { 489 continue; 490 } 491 } 492 493 // skip known issue, #11052 Ambiguous zone name - Samoa Time 494 if (*tzid == "Pacific/Apia" && uprv_strcmp(PATTERNS[patidx], "vvvv") == 0) { 495 continue; 496 } 497 498 BasicTimeZone *tz = (BasicTimeZone*) TimeZone::createTimeZone(*tzid); 499 sdf->setTimeZone(*tz); 500 501 UDate t = data.START_TIME; 502 TimeZoneTransition tzt; 503 UBool tztAvail = FALSE; 504 UBool middle = TRUE; 505 506 while (t < data.END_TIME) { 507 if (!tztAvail) { 508 testTimes[0] = t; 509 expectedRoundTrip[0] = TRUE; 510 testLen = 1; 511 } else { 512 int32_t fromOffset = tzt.getFrom()->getRawOffset() + tzt.getFrom()->getDSTSavings(); 513 int32_t toOffset = tzt.getTo()->getRawOffset() + tzt.getTo()->getDSTSavings(); 514 int32_t delta = toOffset - fromOffset; 515 if (delta < 0) { 516 UBool isDstDecession = tzt.getFrom()->getDSTSavings() > 0 && tzt.getTo()->getDSTSavings() == 0; 517 testTimes[0] = t + delta - 1; 518 expectedRoundTrip[0] = TRUE; 519 testTimes[1] = t + delta; 520 expectedRoundTrip[1] = isDstDecession ? 521 !contains(AMBIGUOUS_DST_DECESSION, PATTERNS[patidx]) : 522 !contains(AMBIGUOUS_NEGATIVE_SHIFT, PATTERNS[patidx]); 523 testTimes[2] = t - 1; 524 expectedRoundTrip[2] = isDstDecession ? 525 !contains(AMBIGUOUS_DST_DECESSION, PATTERNS[patidx]) : 526 !contains(AMBIGUOUS_NEGATIVE_SHIFT, PATTERNS[patidx]); 527 testTimes[3] = t; 528 expectedRoundTrip[3] = TRUE; 529 testLen = 4; 530 } else { 531 testTimes[0] = t - 1; 532 expectedRoundTrip[0] = TRUE; 533 testTimes[1] = t; 534 expectedRoundTrip[1] = TRUE; 535 testLen = 2; 536 } 537 } 538 for (int32_t testidx = 0; testidx < testLen; testidx++) { 539 if (data.quick) { 540 // reduce regular test time 541 if (!expectedRoundTrip[testidx]) { 542 continue; 543 } 544 } 545 546 testCounts++; 547 548 UnicodeString text; 549 FieldPosition fpos(0); 550 sdf->format(testTimes[testidx], text, fpos); 551 552 UDate parsedDate = sdf->parse(text, status); 553 if (U_FAILURE(status)) { 554 log.errln((UnicodeString) "Parse failure for text=" + text + ", tzid=" + *tzid + ", locale=" + data.locales[locidx].getName() 555 + ", pattern=" + PATTERNS[patidx] + ", time=" + testTimes[testidx]); 556 status = U_ZERO_ERROR; 557 continue; 558 } 559 560 int32_t timeDiff = (int32_t)(parsedDate - testTimes[testidx]); 561 UBool bTimeMatch = minutesOffset ? 562 (timeDiff/60000)*60000 == 0 : timeDiff == 0; 563 if (!bTimeMatch) { 564 UnicodeString msg = (UnicodeString) "Time round trip failed for " + "tzid=" + *tzid + ", locale=" + data.locales[locidx].getName() + ", pattern=" + PATTERNS[patidx] 565 + ", text=" + text + ", time=" + testTimes[testidx] + ", restime=" + parsedDate + ", diff=" + (parsedDate - testTimes[testidx]); 566 // Timebomb for TZData update 567 if (expectedRoundTrip[testidx] 568 && !isSpecialTimeRoundTripCase(data.locales[locidx].getName(), *tzid, 569 PATTERNS[patidx], testTimes[testidx])) { 570 log.errln((UnicodeString) "FAIL: " + msg); 571 } else if (REALLY_VERBOSE) { 572 log.logln(msg); 573 } 574 } 575 } 576 tztAvail = tz->getNextTransition(t, FALSE, tzt); 577 if (!tztAvail) { 578 break; 579 } 580 if (middle) { 581 // Test the date in the middle of two transitions. 582 t += (int64_t) ((tzt.getTime() - t) / 2); 583 middle = FALSE; 584 tztAvail = FALSE; 585 } else { 586 t = tzt.getTime(); 587 } 588 } 589 delete tz; 590 } 591 times[patidx] += (Calendar::getNow() - timer); 592 delete sdf; 593 } 594 umtx_lock(NULL); 595 data.numDone++; 596 umtx_unlock(NULL); 597 } 598 delete tzids; 599 } 600private: 601 IntlTest& log; 602 LocaleData& data; 603 int32_t index; 604}; 605 606void 607TimeZoneFormatTest::TestTimeRoundTrip(void) { 608 int32_t nThreads = threadCount; 609 const Locale *LOCALES; 610 int32_t nLocales; 611 int32_t testCounts = 0; 612 613 UErrorCode status = U_ZERO_ERROR; 614 Calendar *cal = Calendar::createInstance(TimeZone::createTimeZone((UnicodeString) "UTC"), status); 615 if (U_FAILURE(status)) { 616 dataerrln("Calendar::createInstance failed: %s", u_errorName(status)); 617 return; 618 } 619 620 const char* testAllProp = getProperty("TimeZoneRoundTripAll"); 621 UBool bTestAll = (testAllProp && uprv_strcmp(testAllProp, "true") == 0); 622 623 UDate START_TIME, END_TIME; 624 if (bTestAll || !quick) { 625 cal->set(1900, UCAL_JANUARY, 1); 626 } else { 627 cal->set(1990, UCAL_JANUARY, 1); 628 } 629 START_TIME = cal->getTime(status); 630 631 cal->set(2015, UCAL_JANUARY, 1); 632 END_TIME = cal->getTime(status); 633 634 if (U_FAILURE(status)) { 635 errln("getTime failed"); 636 return; 637 } 638 639 UDate times[NUM_PATTERNS]; 640 for (int32_t i = 0; i < NUM_PATTERNS; i++) { 641 times[i] = 0; 642 } 643 644 // Set up test locales 645 const Locale locales1[] = {Locale("en")}; 646 const Locale locales2[] = { 647 Locale("ar_EG"), Locale("bg_BG"), Locale("ca_ES"), Locale("da_DK"), Locale("de"), 648 Locale("de_DE"), Locale("el_GR"), Locale("en"), Locale("en_AU"), Locale("en_CA"), 649 Locale("en_US"), Locale("es"), Locale("es_ES"), Locale("es_MX"), Locale("fi_FI"), 650 Locale("fr"), Locale("fr_CA"), Locale("fr_FR"), Locale("he_IL"), Locale("hu_HU"), 651 Locale("it"), Locale("it_IT"), Locale("ja"), Locale("ja_JP"), Locale("ko"), 652 Locale("ko_KR"), Locale("nb_NO"), Locale("nl_NL"), Locale("nn_NO"), Locale("pl_PL"), 653 Locale("pt"), Locale("pt_BR"), Locale("pt_PT"), Locale("ru_RU"), Locale("sv_SE"), 654 Locale("th_TH"), Locale("tr_TR"), Locale("zh"), Locale("zh_Hans"), Locale("zh_Hans_CN"), 655 Locale("zh_Hant"), Locale("zh_Hant_TW") 656 }; 657 658 if (bTestAll) { 659 LOCALES = Locale::getAvailableLocales(nLocales); 660 } else if (quick) { 661 LOCALES = locales1; 662 nLocales = sizeof(locales1)/sizeof(Locale); 663 } else { 664 LOCALES = locales2; 665 nLocales = sizeof(locales2)/sizeof(Locale); 666 } 667 668 LocaleData data; 669 data.index = 0; 670 data.testCounts = testCounts; 671 data.times = times; 672 data.locales = LOCALES; 673 data.nLocales = nLocales; 674 data.quick = quick; 675 data.START_TIME = START_TIME; 676 data.END_TIME = END_TIME; 677 data.numDone = 0; 678 679#if (ICU_USE_THREADS==0) 680 TestTimeRoundTripThread fakeThread(*this, data, 0); 681 fakeThread.run(); 682#else 683 TestTimeRoundTripThread **threads = new TestTimeRoundTripThread*[threadCount]; 684 int32_t i; 685 for (i = 0; i < nThreads; i++) { 686 threads[i] = new TestTimeRoundTripThread(*this, data, i); 687 if (threads[i]->start() != 0) { 688 errln("Error starting thread %d", i); 689 } 690 } 691 692 UBool done = false; 693 while (true) { 694 umtx_lock(NULL); 695 if (data.numDone == nLocales) { 696 done = true; 697 } 698 umtx_unlock(NULL); 699 if (done) 700 break; 701 SimpleThread::sleep(1000); 702 } 703 704 for (i = 0; i < nThreads; i++) { 705 delete threads[i]; 706 } 707 delete [] threads; 708 709#endif 710 UDate total = 0; 711 logln("### Elapsed time by patterns ###"); 712 for (int32_t i = 0; i < NUM_PATTERNS; i++) { 713 logln(UnicodeString("") + data.times[i] + "ms (" + PATTERNS[i] + ")"); 714 total += data.times[i]; 715 } 716 logln((UnicodeString) "Total: " + total + "ms"); 717 logln((UnicodeString) "Iteration: " + data.testCounts); 718 719 delete cal; 720} 721 722 723typedef struct { 724 const char* text; 725 int32_t inPos; 726 const char* locale; 727 UTimeZoneFormatStyle style; 728 UBool parseAll; 729 const char* expected; 730 int32_t outPos; 731 UTimeZoneFormatTimeType timeType; 732} ParseTestData; 733 734void 735TimeZoneFormatTest::TestParse(void) { 736 const ParseTestData DATA[] = { 737 // text inPos locale style parseAll expected outPos timeType 738 {"Z", 0, "en_US", UTZFMT_STYLE_ISO_EXTENDED_FULL, false, "Etc/GMT", 1, UTZFMT_TIME_TYPE_UNKNOWN}, 739 {"Z", 0, "en_US", UTZFMT_STYLE_SPECIFIC_LONG, false, "Etc/GMT", 1, UTZFMT_TIME_TYPE_UNKNOWN}, 740 {"Zambia time", 0, "en_US", UTZFMT_STYLE_ISO_EXTENDED_FULL, true, "Etc/GMT", 1, UTZFMT_TIME_TYPE_UNKNOWN}, 741 {"Zambia time", 0, "en_US", UTZFMT_STYLE_GENERIC_LOCATION, false, "Africa/Lusaka", 11, UTZFMT_TIME_TYPE_UNKNOWN}, 742 {"Zambia time", 0, "en_US", UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL, true, "Africa/Lusaka", 11, UTZFMT_TIME_TYPE_UNKNOWN}, 743 {"+00:00", 0, "en_US", UTZFMT_STYLE_ISO_EXTENDED_FULL, false, "Etc/GMT", 6, UTZFMT_TIME_TYPE_UNKNOWN}, 744 {"-01:30:45", 0, "en_US", UTZFMT_STYLE_ISO_EXTENDED_FULL, false, "GMT-01:30:45", 9, UTZFMT_TIME_TYPE_UNKNOWN}, 745 {"-7", 0, "en_US", UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL, false, "GMT-07:00", 2, UTZFMT_TIME_TYPE_UNKNOWN}, 746 {"-2222", 0, "en_US", UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL, false, "GMT-22:22", 5, UTZFMT_TIME_TYPE_UNKNOWN}, 747 {"-3333", 0, "en_US", UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL, false, "GMT-03:33", 4, UTZFMT_TIME_TYPE_UNKNOWN}, 748 {"XXX+01:30YYY", 3, "en_US", UTZFMT_STYLE_LOCALIZED_GMT, false, "GMT+01:30", 9, UTZFMT_TIME_TYPE_UNKNOWN}, 749 {"GMT0", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT, false, "Etc/GMT", 3, UTZFMT_TIME_TYPE_UNKNOWN}, 750 {"EST", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT, false, "America/New_York", 3, UTZFMT_TIME_TYPE_STANDARD}, 751 {"ESTx", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT, false, "America/New_York", 3, UTZFMT_TIME_TYPE_STANDARD}, 752 {"EDTx", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT, false, "America/New_York", 3, UTZFMT_TIME_TYPE_DAYLIGHT}, 753 {"EST", 0, "en_US", UTZFMT_STYLE_SPECIFIC_LONG, false, NULL, 0, UTZFMT_TIME_TYPE_UNKNOWN}, 754 {"EST", 0, "en_US", UTZFMT_STYLE_SPECIFIC_LONG, true, "America/New_York", 3, UTZFMT_TIME_TYPE_STANDARD}, 755 {"EST", 0, "en_CA", UTZFMT_STYLE_SPECIFIC_SHORT, false, "America/Toronto", 3, UTZFMT_TIME_TYPE_STANDARD}, 756 {NULL, 0, NULL, UTZFMT_STYLE_GENERIC_LOCATION, false, NULL, 0, UTZFMT_TIME_TYPE_UNKNOWN} 757 }; 758 759 for (int32_t i = 0; DATA[i].text; i++) { 760 UErrorCode status = U_ZERO_ERROR; 761 LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(Locale(DATA[i].locale), status)); 762 if (U_FAILURE(status)) { 763 dataerrln("Fail TimeZoneFormat::createInstance: %s", u_errorName(status)); 764 continue; 765 } 766 UTimeZoneFormatTimeType ttype = UTZFMT_TIME_TYPE_UNKNOWN; 767 ParsePosition pos(DATA[i].inPos); 768 int32_t parseOptions = DATA[i].parseAll ? UTZFMT_PARSE_OPTION_ALL_STYLES : UTZFMT_PARSE_OPTION_NONE; 769 TimeZone* tz = tzfmt->parse(DATA[i].style, DATA[i].text, pos, parseOptions, &ttype); 770 771 UnicodeString errMsg; 772 if (tz) { 773 UnicodeString outID; 774 tz->getID(outID); 775 if (outID != UnicodeString(DATA[i].expected)) { 776 errMsg = (UnicodeString)"Time zone ID: " + outID + " - expected: " + DATA[i].expected; 777 } else if (pos.getIndex() != DATA[i].outPos) { 778 errMsg = (UnicodeString)"Parsed pos: " + pos.getIndex() + " - expected: " + DATA[i].outPos; 779 } else if (ttype != DATA[i].timeType) { 780 errMsg = (UnicodeString)"Time type: " + ttype + " - expected: " + DATA[i].timeType; 781 } 782 delete tz; 783 } else { 784 if (DATA[i].expected) { 785 errln((UnicodeString)"Fail: Parse failure - expected: " + DATA[i].expected); 786 } 787 } 788 if (errMsg.length() > 0) { 789 errln((UnicodeString)"Fail: " + errMsg + " [text=" + DATA[i].text + ", pos=" + DATA[i].inPos + ", style=" + DATA[i].style + "]"); 790 } 791 } 792} 793 794void 795TimeZoneFormatTest::TestISOFormat(void) { 796 const int32_t OFFSET[] = { 797 0, // 0 798 999, // 0.999s 799 -59999, // -59.999s 800 60000, // 1m 801 -77777, // -1m 17.777s 802 1800000, // 30m 803 -3600000, // -1h 804 36000000, // 10h 805 -37800000, // -10h 30m 806 -37845000, // -10h 30m 45s 807 108000000, // 30h 808 }; 809 810 const char* ISO_STR[][11] = { 811 // 0 812 { 813 "Z", "Z", "Z", "Z", "Z", 814 "+00", "+0000", "+00:00", "+0000", "+00:00", 815 "+0000" 816 }, 817 // 999 818 { 819 "Z", "Z", "Z", "Z", "Z", 820 "+00", "+0000", "+00:00", "+0000", "+00:00", 821 "+0000" 822 }, 823 // -59999 824 { 825 "Z", "Z", "Z", "-000059", "-00:00:59", 826 "+00", "+0000", "+00:00", "-000059", "-00:00:59", 827 "-000059" 828 }, 829 // 60000 830 { 831 "+0001", "+0001", "+00:01", "+0001", "+00:01", 832 "+0001", "+0001", "+00:01", "+0001", "+00:01", 833 "+0001" 834 }, 835 // -77777 836 { 837 "-0001", "-0001", "-00:01", "-000117", "-00:01:17", 838 "-0001", "-0001", "-00:01", "-000117", "-00:01:17", 839 "-000117" 840 }, 841 // 1800000 842 { 843 "+0030", "+0030", "+00:30", "+0030", "+00:30", 844 "+0030", "+0030", "+00:30", "+0030", "+00:30", 845 "+0030" 846 }, 847 // -3600000 848 { 849 "-01", "-0100", "-01:00", "-0100", "-01:00", 850 "-01", "-0100", "-01:00", "-0100", "-01:00", 851 "-0100" 852 }, 853 // 36000000 854 { 855 "+10", "+1000", "+10:00", "+1000", "+10:00", 856 "+10", "+1000", "+10:00", "+1000", "+10:00", 857 "+1000" 858 }, 859 // -37800000 860 { 861 "-1030", "-1030", "-10:30", "-1030", "-10:30", 862 "-1030", "-1030", "-10:30", "-1030", "-10:30", 863 "-1030" 864 }, 865 // -37845000 866 { 867 "-1030", "-1030", "-10:30", "-103045", "-10:30:45", 868 "-1030", "-1030", "-10:30", "-103045", "-10:30:45", 869 "-103045" 870 }, 871 // 108000000 872 { 873 0, 0, 0, 0, 0, 874 0, 0, 0, 0, 0, 875 0 876 } 877 }; 878 879 const char* PATTERN[] = { 880 "X", "XX", "XXX", "XXXX", "XXXXX", 881 "x", "xx", "xxx", "xxxx", "xxxxx", 882 "Z", // equivalent to "xxxx" 883 0 884 }; 885 886 const int32_t MIN_OFFSET_UNIT[] = { 887 60000, 60000, 60000, 1000, 1000, 888 60000, 60000, 60000, 1000, 1000, 889 1000, 890 }; 891 892 // Formatting 893 UErrorCode status = U_ZERO_ERROR; 894 LocalPointer<SimpleDateFormat> sdf(new SimpleDateFormat(status)); 895 if (U_FAILURE(status)) { 896 dataerrln("Fail new SimpleDateFormat: %s", u_errorName(status)); 897 return; 898 } 899 UDate d = Calendar::getNow(); 900 901 for (uint32_t i = 0; i < sizeof(OFFSET)/sizeof(OFFSET[0]); i++) { 902 SimpleTimeZone* tz = new SimpleTimeZone(OFFSET[i], UnicodeString("Zone Offset:") + OFFSET[i] + "ms"); 903 sdf->adoptTimeZone(tz); 904 for (int32_t j = 0; PATTERN[j] != 0; j++) { 905 sdf->applyPattern(UnicodeString(PATTERN[j])); 906 UnicodeString result; 907 sdf->format(d, result); 908 909 if (ISO_STR[i][j]) { 910 if (result != UnicodeString(ISO_STR[i][j])) { 911 errln((UnicodeString)"FAIL: pattern=" + PATTERN[j] + ", offset=" + OFFSET[i] + " -> " 912 + result + " (expected: " + ISO_STR[i][j] + ")"); 913 } 914 } else { 915 // Offset out of range 916 // Note: for now, there is no way to propagate the error status through 917 // the SimpleDateFormat::format above. 918 if (result.length() > 0) { 919 errln((UnicodeString)"FAIL: Non-Empty result for pattern=" + PATTERN[j] + ", offset=" + OFFSET[i] 920 + " (expected: empty result)"); 921 } 922 } 923 } 924 } 925 926 // Parsing 927 LocalPointer<Calendar> outcal(Calendar::createInstance(status)); 928 if (U_FAILURE(status)) { 929 dataerrln("Fail new Calendar: %s", u_errorName(status)); 930 return; 931 } 932 for (int32_t i = 0; ISO_STR[i][0] != NULL; i++) { 933 for (int32_t j = 0; PATTERN[j] != 0; j++) { 934 if (ISO_STR[i][j] == 0) { 935 continue; 936 } 937 ParsePosition pos(0); 938 SimpleTimeZone* bogusTZ = new SimpleTimeZone(-1, UnicodeString("Zone Offset: -1ms")); 939 outcal->adoptTimeZone(bogusTZ); 940 sdf->applyPattern(PATTERN[j]); 941 942 sdf->parse(UnicodeString(ISO_STR[i][j]), *(outcal.getAlias()), pos); 943 944 if (pos.getIndex() != (int32_t)uprv_strlen(ISO_STR[i][j])) { 945 errln((UnicodeString)"FAIL: Failed to parse the entire input string: " + ISO_STR[i][j]); 946 } 947 948 const TimeZone& outtz = outcal->getTimeZone(); 949 int32_t outOffset = outtz.getRawOffset(); 950 int32_t adjustedOffset = OFFSET[i] / MIN_OFFSET_UNIT[j] * MIN_OFFSET_UNIT[j]; 951 if (outOffset != adjustedOffset) { 952 errln((UnicodeString)"FAIL: Incorrect offset:" + outOffset + "ms for input string: " + ISO_STR[i][j] 953 + " (expected:" + adjustedOffset + "ms)"); 954 } 955 } 956 } 957} 958 959 960#endif /* #if !UCONFIG_NO_FORMATTING */ 961