1/* 2******************************************************************************* 3* Copyright (C) 2007-2012, 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 "cstring.h" 22 23static const char* PATTERNS[] = {"z", "zzzz", "Z", "ZZZZ", "ZZZZZ", "v", "vvvv", "V", "VVVV"}; 24static const int NUM_PATTERNS = sizeof(PATTERNS)/sizeof(const char*); 25 26void 27TimeZoneFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ ) 28{ 29 if (exec) { 30 logln("TestSuite TimeZoneFormatTest"); 31 } 32 switch (index) { 33 TESTCASE(0, TestTimeZoneRoundTrip); 34 TESTCASE(1, TestTimeRoundTrip); 35 default: name = ""; break; 36 } 37} 38 39void 40TimeZoneFormatTest::TestTimeZoneRoundTrip(void) { 41 UErrorCode status = U_ZERO_ERROR; 42 43 SimpleTimeZone unknownZone(-31415, (UnicodeString)"Etc/Unknown"); 44 int32_t badDstOffset = -1234; 45 int32_t badZoneOffset = -2345; 46 47 int32_t testDateData[][3] = { 48 {2007, 1, 15}, 49 {2007, 6, 15}, 50 {1990, 1, 15}, 51 {1990, 6, 15}, 52 {1960, 1, 15}, 53 {1960, 6, 15}, 54 }; 55 56 Calendar *cal = Calendar::createInstance(TimeZone::createTimeZone((UnicodeString)"UTC"), status); 57 if (U_FAILURE(status)) { 58 dataerrln("Calendar::createInstance failed: %s", u_errorName(status)); 59 return; 60 } 61 62 // Set up rule equivalency test range 63 UDate low, high; 64 cal->set(1900, UCAL_JANUARY, 1); 65 low = cal->getTime(status); 66 cal->set(2040, UCAL_JANUARY, 1); 67 high = cal->getTime(status); 68 if (U_FAILURE(status)) { 69 errln("getTime failed"); 70 return; 71 } 72 73 // Set up test dates 74 UDate DATES[(sizeof(testDateData)/sizeof(int32_t))/3]; 75 const int32_t nDates = (sizeof(testDateData)/sizeof(int32_t))/3; 76 cal->clear(); 77 for (int32_t i = 0; i < nDates; i++) { 78 cal->set(testDateData[i][0], testDateData[i][1], testDateData[i][2]); 79 DATES[i] = cal->getTime(status); 80 if (U_FAILURE(status)) { 81 errln("getTime failed"); 82 return; 83 } 84 } 85 86 // Set up test locales 87 const Locale testLocales[] = { 88 Locale("en"), 89 Locale("en_CA"), 90 Locale("fr"), 91 Locale("zh_Hant") 92 }; 93 94 const Locale *LOCALES; 95 int32_t nLocales; 96 97 if (quick) { 98 LOCALES = testLocales; 99 nLocales = sizeof(testLocales)/sizeof(Locale); 100 } else { 101 LOCALES = Locale::getAvailableLocales(nLocales); 102 } 103 104 StringEnumeration *tzids = TimeZone::createEnumeration(); 105 int32_t inRaw, inDst; 106 int32_t outRaw, outDst; 107 108 // Run the roundtrip test 109 for (int32_t locidx = 0; locidx < nLocales; locidx++) { 110 UnicodeString localGMTString; 111 SimpleDateFormat gmtFmt(UnicodeString("ZZZZ"), LOCALES[locidx], status); 112 if (U_FAILURE(status)) { 113 dataerrln("Error creating SimpleDateFormat - %s", u_errorName(status)); 114 continue; 115 } 116 gmtFmt.setTimeZone(*TimeZone::getGMT()); 117 gmtFmt.format(0.0, localGMTString); 118 119 for (int32_t patidx = 0; patidx < NUM_PATTERNS; patidx++) { 120 121 SimpleDateFormat *sdf = new SimpleDateFormat((UnicodeString)PATTERNS[patidx], LOCALES[locidx], status); 122 if (U_FAILURE(status)) { 123 dataerrln((UnicodeString)"new SimpleDateFormat failed for pattern " + 124 PATTERNS[patidx] + " for locale " + LOCALES[locidx].getName() + " - " + u_errorName(status)); 125 status = U_ZERO_ERROR; 126 continue; 127 } 128 129 tzids->reset(status); 130 const UnicodeString *tzid; 131 while ((tzid = tzids->snext(status))) { 132 TimeZone *tz = TimeZone::createTimeZone(*tzid); 133 134 for (int32_t datidx = 0; datidx < nDates; datidx++) { 135 UnicodeString tzstr; 136 FieldPosition fpos(0); 137 // Format 138 sdf->setTimeZone(*tz); 139 sdf->format(DATES[datidx], tzstr, fpos); 140 141 // Before parse, set unknown zone to SimpleDateFormat instance 142 // just for making sure that it does not depends on the time zone 143 // originally set. 144 sdf->setTimeZone(unknownZone); 145 146 // Parse 147 ParsePosition pos(0); 148 Calendar *outcal = Calendar::createInstance(unknownZone, status); 149 if (U_FAILURE(status)) { 150 errln("Failed to create an instance of calendar for receiving parse result."); 151 status = U_ZERO_ERROR; 152 continue; 153 } 154 outcal->set(UCAL_DST_OFFSET, badDstOffset); 155 outcal->set(UCAL_ZONE_OFFSET, badZoneOffset); 156 157 sdf->parse(tzstr, *outcal, pos); 158 159 // Check the result 160 const TimeZone &outtz = outcal->getTimeZone(); 161 UnicodeString outtzid; 162 outtz.getID(outtzid); 163 164 tz->getOffset(DATES[datidx], false, inRaw, inDst, status); 165 if (U_FAILURE(status)) { 166 errln((UnicodeString)"Failed to get offsets from time zone" + *tzid); 167 status = U_ZERO_ERROR; 168 } 169 outtz.getOffset(DATES[datidx], false, outRaw, outDst, status); 170 if (U_FAILURE(status)) { 171 errln((UnicodeString)"Failed to get offsets from time zone" + outtzid); 172 status = U_ZERO_ERROR; 173 } 174 175 if (uprv_strcmp(PATTERNS[patidx], "VVVV") == 0) { 176 // Location: time zone rule must be preserved except 177 // zones not actually associated with a specific location. 178 // Time zones in this category do not have "/" in its ID. 179 UnicodeString canonical; 180 TimeZone::getCanonicalID(*tzid, canonical, status); 181 if (U_FAILURE(status)) { 182 // Uknown ID - we should not get here 183 errln((UnicodeString)"Unknown ID " + *tzid); 184 status = U_ZERO_ERROR; 185 } else if (outtzid != canonical) { 186 // Canonical ID did not match - check the rules 187 if (!((BasicTimeZone*)&outtz)->hasEquivalentTransitions((BasicTimeZone&)*tz, low, high, TRUE, status)) { 188 if (canonical.indexOf((UChar)0x27 /*'/'*/) == -1) { 189 // Exceptional cases, such as CET, EET, MET and WET 190 logln("Canonical round trip failed (as expected); tz=" + *tzid 191 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx] 192 + ", time=" + DATES[datidx] + ", str=" + tzstr 193 + ", outtz=" + outtzid); 194 } else { 195 errln("Canonical round trip failed; tz=" + *tzid 196 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx] 197 + ", time=" + DATES[datidx] + ", str=" + tzstr 198 + ", outtz=" + outtzid); 199 } 200 if (U_FAILURE(status)) { 201 errln("hasEquivalentTransitions failed"); 202 status = U_ZERO_ERROR; 203 } 204 } 205 } 206 207 } else { 208 // Check if localized GMT format or RFC format is used. 209 UBool isOffsetFormat = (*PATTERNS[patidx] == 'Z'); 210 if (!isOffsetFormat) { 211 // Check if localized GMT format is used as a fallback of name styles 212 int32_t numDigits = 0; 213 for (int n = 0; n < tzstr.length(); n++) { 214 if (u_isdigit(tzstr.charAt(n))) { 215 numDigits++; 216 } 217 } 218 isOffsetFormat = (numDigits >= 3); 219 } 220 if (isOffsetFormat || tzstr == localGMTString) { 221 // Localized GMT or RFC: total offset (raw + dst) must be preserved. 222 int32_t inOffset = inRaw + inDst; 223 int32_t outOffset = outRaw + outDst; 224 if (inOffset != outOffset) { 225 errln((UnicodeString)"Offset round trip failed; tz=" + *tzid 226 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx] 227 + ", time=" + DATES[datidx] + ", str=" + tzstr 228 + ", inOffset=" + inOffset + ", outOffset=" + outOffset); 229 } 230 } else { 231 // Specific or generic: raw offset must be preserved. 232 if (inRaw != outRaw) { 233 errln((UnicodeString)"Raw offset round trip failed; tz=" + *tzid 234 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx] 235 + ", time=" + DATES[datidx] + ", str=" + tzstr 236 + ", inRawOffset=" + inRaw + ", outRawOffset=" + outRaw); 237 } 238 } 239 } 240 delete outcal; 241 } 242 delete tz; 243 } 244 delete sdf; 245 } 246 } 247 delete cal; 248 delete tzids; 249} 250 251struct LocaleData { 252 int32_t index; 253 int32_t testCounts; 254 UDate *times; 255 const Locale* locales; // Static 256 int32_t nLocales; // Static 257 UBool quick; // Static 258 UDate START_TIME; // Static 259 UDate END_TIME; // Static 260 int32_t numDone; 261}; 262 263class TestTimeRoundTripThread: public SimpleThread { 264public: 265 TestTimeRoundTripThread(IntlTest& tlog, LocaleData &ld, int32_t i) 266 : log(tlog), data(ld), index(i) {} 267 virtual void run() { 268 UErrorCode status = U_ZERO_ERROR; 269 UBool REALLY_VERBOSE = FALSE; 270 271 // Whether each pattern is ambiguous at DST->STD local time overlap 272 UBool AMBIGUOUS_DST_DECESSION[] = { FALSE, FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, FALSE, TRUE }; 273 // Whether each pattern is ambiguous at STD->STD/DST->DST local time overlap 274 UBool AMBIGUOUS_NEGATIVE_SHIFT[] = { TRUE, TRUE, FALSE, FALSE, FALSE, TRUE, TRUE, TRUE, TRUE }; 275 276 // Workaround for #6338 277 //UnicodeString BASEPATTERN("yyyy-MM-dd'T'HH:mm:ss.SSS"); 278 UnicodeString BASEPATTERN("yyyy.MM.dd HH:mm:ss.SSS"); 279 280 // timer for performance analysis 281 UDate timer; 282 UDate testTimes[4]; 283 UBool expectedRoundTrip[4]; 284 int32_t testLen = 0; 285 286 StringEnumeration *tzids = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL, NULL, NULL, status); 287 if (U_FAILURE(status)) { 288 if (status == U_MISSING_RESOURCE_ERROR) { 289 /* This error is generally caused by data not being present. However, an infinite loop will occur 290 * because the thread thinks that the test data is never done so we should treat the data as done. 291 */ 292 log.dataerrln("TimeZone::createTimeZoneIDEnumeration failed - %s", u_errorName(status)); 293 data.numDone = data.nLocales; 294 } else { 295 log.errln("TimeZone::createTimeZoneIDEnumeration failed: %s", u_errorName(status)); 296 } 297 return; 298 } 299 300 int32_t locidx = -1; 301 UDate times[NUM_PATTERNS]; 302 for (int32_t i = 0; i < NUM_PATTERNS; i++) { 303 times[i] = 0; 304 } 305 306 int32_t testCounts = 0; 307 308 while (true) { 309 umtx_lock(NULL); // Lock to increment the index 310 for (int32_t i = 0; i < NUM_PATTERNS; i++) { 311 data.times[i] += times[i]; 312 data.testCounts += testCounts; 313 } 314 if (data.index < data.nLocales) { 315 locidx = data.index; 316 data.index++; 317 } else { 318 locidx = -1; 319 } 320 umtx_unlock(NULL); // Unlock for other threads to use 321 322 if (locidx == -1) { 323 log.logln((UnicodeString) "Thread " + index + " is done."); 324 break; 325 } 326 327 log.logln((UnicodeString) "\nThread " + index + ": Locale: " + UnicodeString(data.locales[locidx].getName())); 328 329 for (int32_t patidx = 0; patidx < NUM_PATTERNS; patidx++) { 330 log.logln((UnicodeString) " Pattern: " + PATTERNS[patidx]); 331 times[patidx] = 0; 332 333 UnicodeString pattern(BASEPATTERN); 334 pattern.append(" ").append(PATTERNS[patidx]); 335 336 SimpleDateFormat *sdf = new SimpleDateFormat(pattern, data.locales[locidx], status); 337 if (U_FAILURE(status)) { 338 log.errcheckln(status, (UnicodeString) "new SimpleDateFormat failed for pattern " + 339 pattern + " for locale " + data.locales[locidx].getName() + " - " + u_errorName(status)); 340 status = U_ZERO_ERROR; 341 continue; 342 } 343 344 tzids->reset(status); 345 const UnicodeString *tzid; 346 347 timer = Calendar::getNow(); 348 349 while ((tzid = tzids->snext(status))) { 350 BasicTimeZone *tz = (BasicTimeZone*) TimeZone::createTimeZone(*tzid); 351 sdf->setTimeZone(*tz); 352 353 UDate t = data.START_TIME; 354 TimeZoneTransition tzt; 355 UBool tztAvail = FALSE; 356 UBool middle = TRUE; 357 358 while (t < data.END_TIME) { 359 if (!tztAvail) { 360 testTimes[0] = t; 361 expectedRoundTrip[0] = TRUE; 362 testLen = 1; 363 } else { 364 int32_t fromOffset = tzt.getFrom()->getRawOffset() + tzt.getFrom()->getDSTSavings(); 365 int32_t toOffset = tzt.getTo()->getRawOffset() + tzt.getTo()->getDSTSavings(); 366 int32_t delta = toOffset - fromOffset; 367 if (delta < 0) { 368 UBool isDstDecession = tzt.getFrom()->getDSTSavings() > 0 && tzt.getTo()->getDSTSavings() == 0; 369 testTimes[0] = t + delta - 1; 370 expectedRoundTrip[0] = TRUE; 371 testTimes[1] = t + delta; 372 expectedRoundTrip[1] = isDstDecession ? !AMBIGUOUS_DST_DECESSION[patidx] : !AMBIGUOUS_NEGATIVE_SHIFT[patidx]; 373 testTimes[2] = t - 1; 374 expectedRoundTrip[2] = isDstDecession ? !AMBIGUOUS_DST_DECESSION[patidx] : !AMBIGUOUS_NEGATIVE_SHIFT[patidx]; 375 testTimes[3] = t; 376 expectedRoundTrip[3] = TRUE; 377 testLen = 4; 378 } else { 379 testTimes[0] = t - 1; 380 expectedRoundTrip[0] = TRUE; 381 testTimes[1] = t; 382 expectedRoundTrip[1] = TRUE; 383 testLen = 2; 384 } 385 } 386 for (int32_t testidx = 0; testidx < testLen; testidx++) { 387 if (data.quick) { 388 // reduce regular test time 389 if (!expectedRoundTrip[testidx]) { 390 continue; 391 } 392 } 393 394 testCounts++; 395 396 UnicodeString text; 397 FieldPosition fpos(0); 398 sdf->format(testTimes[testidx], text, fpos); 399 400 UDate parsedDate = sdf->parse(text, status); 401 if (U_FAILURE(status)) { 402 log.errln((UnicodeString) "Parse failure for text=" + text + ", tzid=" + *tzid + ", locale=" + data.locales[locidx].getName() 403 + ", pattern=" + PATTERNS[patidx] + ", time=" + testTimes[testidx]); 404 status = U_ZERO_ERROR; 405 continue; 406 } 407 if (parsedDate != testTimes[testidx]) { 408 UnicodeString msg = (UnicodeString) "Time round trip failed for " + "tzid=" + *tzid + ", locale=" + data.locales[locidx].getName() + ", pattern=" + PATTERNS[patidx] 409 + ", text=" + text + ", time=" + testTimes[testidx] + ", restime=" + parsedDate + ", diff=" + (parsedDate - testTimes[testidx]); 410 // Timebomb for TZData update 411 if (expectedRoundTrip[testidx]) { 412 log.errln((UnicodeString) "FAIL: " + msg); 413 } else if (REALLY_VERBOSE) { 414 log.logln(msg); 415 } 416 } 417 } 418 tztAvail = tz->getNextTransition(t, FALSE, tzt); 419 if (!tztAvail) { 420 break; 421 } 422 if (middle) { 423 // Test the date in the middle of two transitions. 424 t += (int64_t) ((tzt.getTime() - t) / 2); 425 middle = FALSE; 426 tztAvail = FALSE; 427 } else { 428 t = tzt.getTime(); 429 } 430 } 431 delete tz; 432 } 433 times[patidx] += (Calendar::getNow() - timer); 434 delete sdf; 435 } 436 umtx_lock(NULL); 437 data.numDone++; 438 umtx_unlock(NULL); 439 } 440 delete tzids; 441 } 442private: 443 IntlTest& log; 444 LocaleData& data; 445 int32_t index; 446}; 447 448void 449TimeZoneFormatTest::TestTimeRoundTrip(void) { 450 int32_t nThreads = threadCount; 451 const Locale *LOCALES; 452 int32_t nLocales; 453 int32_t testCounts = 0; 454 455 UErrorCode status = U_ZERO_ERROR; 456 Calendar *cal = Calendar::createInstance(TimeZone::createTimeZone((UnicodeString) "UTC"), status); 457 if (U_FAILURE(status)) { 458 dataerrln("Calendar::createInstance failed: %s", u_errorName(status)); 459 return; 460 } 461 462 const char* testAllProp = getProperty("TimeZoneRoundTripAll"); 463 UBool bTestAll = (testAllProp && uprv_strcmp(testAllProp, "true") == 0); 464 465 UDate START_TIME, END_TIME; 466 if (bTestAll || !quick) { 467 cal->set(1900, UCAL_JANUARY, 1); 468 } else { 469 cal->set(1990, UCAL_JANUARY, 1); 470 } 471 START_TIME = cal->getTime(status); 472 473 cal->set(2015, UCAL_JANUARY, 1); 474 END_TIME = cal->getTime(status); 475 476 if (U_FAILURE(status)) { 477 errln("getTime failed"); 478 return; 479 } 480 481 UDate times[NUM_PATTERNS]; 482 for (int32_t i = 0; i < NUM_PATTERNS; i++) { 483 times[i] = 0; 484 } 485 486 // Set up test locales 487 const Locale locales1[] = {Locale("en")}; 488 const Locale locales2[] = { 489 Locale("ar_EG"), Locale("bg_BG"), Locale("ca_ES"), Locale("da_DK"), Locale("de"), 490 Locale("de_DE"), Locale("el_GR"), Locale("en"), Locale("en_AU"), Locale("en_CA"), 491 Locale("en_US"), Locale("es"), Locale("es_ES"), Locale("es_MX"), Locale("fi_FI"), 492 Locale("fr"), Locale("fr_CA"), Locale("fr_FR"), Locale("he_IL"), Locale("hu_HU"), 493 Locale("it"), Locale("it_IT"), Locale("ja"), Locale("ja_JP"), Locale("ko"), 494 Locale("ko_KR"), Locale("nb_NO"), Locale("nl_NL"), Locale("nn_NO"), Locale("pl_PL"), 495 Locale("pt"), Locale("pt_BR"), Locale("pt_PT"), Locale("ru_RU"), Locale("sv_SE"), 496 Locale("th_TH"), Locale("tr_TR"), Locale("zh"), Locale("zh_Hans"), Locale("zh_Hans_CN"), 497 Locale("zh_Hant"), Locale("zh_Hant_TW") 498 }; 499 500 if (bTestAll) { 501 LOCALES = Locale::getAvailableLocales(nLocales); 502 } else if (quick) { 503 LOCALES = locales1; 504 nLocales = sizeof(locales1)/sizeof(Locale); 505 } else { 506 LOCALES = locales2; 507 nLocales = sizeof(locales2)/sizeof(Locale); 508 } 509 510 LocaleData data; 511 data.index = 0; 512 data.testCounts = testCounts; 513 data.times = times; 514 data.locales = LOCALES; 515 data.nLocales = nLocales; 516 data.quick = quick; 517 data.START_TIME = START_TIME; 518 data.END_TIME = END_TIME; 519 data.numDone = 0; 520 521#if (ICU_USE_THREADS==0) 522 TestTimeRoundTripThread fakeThread(*this, data, 0); 523 fakeThread.run(); 524#else 525 TestTimeRoundTripThread **threads = new TestTimeRoundTripThread*[threadCount]; 526 int32_t i; 527 for (i = 0; i < nThreads; i++) { 528 threads[i] = new TestTimeRoundTripThread(*this, data, i); 529 if (threads[i]->start() != 0) { 530 errln("Error starting thread %d", i); 531 } 532 } 533 534 UBool done = false; 535 while (true) { 536 umtx_lock(NULL); 537 if (data.numDone == nLocales) { 538 done = true; 539 } 540 umtx_unlock(NULL); 541 if (done) 542 break; 543 SimpleThread::sleep(1000); 544 } 545 546 for (i = 0; i < nThreads; i++) { 547 delete threads[i]; 548 } 549 delete [] threads; 550 551#endif 552 UDate total = 0; 553 logln("### Elapsed time by patterns ###"); 554 for (int32_t i = 0; i < NUM_PATTERNS; i++) { 555 logln(UnicodeString("") + data.times[i] + "ms (" + PATTERNS[i] + ")"); 556 total += data.times[i]; 557 } 558 logln((UnicodeString) "Total: " + total + "ms"); 559 logln((UnicodeString) "Iteration: " + data.testCounts); 560 561 delete cal; 562} 563 564#endif /* #if !UCONFIG_NO_FORMATTING */ 565