1/*********************************************************************** 2 * COPYRIGHT: 3 * Copyright (c) 1997-2010, International Business Machines Corporation 4 * and others. All Rights Reserved. 5 ***********************************************************************/ 6 7#include "unicode/utypes.h" 8 9#if !UCONFIG_NO_FORMATTING 10 11#include "tzbdtest.h" 12#include "unicode/timezone.h" 13#include "unicode/simpletz.h" 14#include "unicode/gregocal.h" 15#include "putilimp.h" 16 17void TimeZoneBoundaryTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ ) 18{ 19 if (exec) logln("TestSuite TestTimeZoneBoundary"); 20 switch (index) { 21 case 0: 22 name = "TestBoundaries"; 23 if (exec) { 24 logln("TestBoundaries---"); logln(""); 25 TestBoundaries(); 26 } 27 break; 28 case 1: 29 name = "TestNewRules"; 30 if (exec) { 31 logln("TestNewRules---"); logln(""); 32 TestNewRules(); 33 } 34 break; 35 case 2: 36 name = "TestStepwise"; 37 if (exec) { 38 logln("TestStepwise---"); logln(""); 39 TestStepwise(); 40 } 41 break; 42 default: name = ""; break; 43 } 44} 45 46// ***************************************************************************** 47// class TimeZoneBoundaryTest 48// ***************************************************************************** 49 50TimeZoneBoundaryTest::TimeZoneBoundaryTest() 51: 52ONE_SECOND(1000), 53ONE_MINUTE(60 * ONE_SECOND), 54ONE_HOUR(60 * ONE_MINUTE), 55ONE_DAY(24 * ONE_HOUR), 56ONE_YEAR(uprv_floor(365.25 * ONE_DAY)), 57SIX_MONTHS(ONE_YEAR / 2) 58{ 59} 60 61const int32_t TimeZoneBoundaryTest::MONTH_LENGTH[] = { 62 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 63}; 64 65const UDate TimeZoneBoundaryTest::PST_1997_BEG = 860320800000.0; 66 67const UDate TimeZoneBoundaryTest::PST_1997_END = 877856400000.0; 68 69const UDate TimeZoneBoundaryTest::INTERVAL = 10; 70 71// ------------------------------------- 72 73void 74TimeZoneBoundaryTest::findDaylightBoundaryUsingDate(UDate d, const char* startMode, UDate expectedBoundary) 75{ 76 UnicodeString str; 77 if (dateToString(d, str).indexOf(startMode) == - 1) { 78 logln(UnicodeString("Error: ") + startMode + " not present in " + str); 79 } 80 UDate min = d; 81 UDate max = min + SIX_MONTHS; 82 while ((max - min) > INTERVAL) { 83 UDate mid = (min + max) / 2; 84 UnicodeString* s = &dateToString(mid, str); 85 if (s->indexOf(startMode) != - 1) { 86 min = mid; 87 } 88 else { 89 max = mid; 90 } 91 } 92 logln("Date Before: " + showDate(min)); 93 logln("Date After: " + showDate(max)); 94 UDate mindelta = expectedBoundary - min; 95 UDate maxdelta = max - expectedBoundary; 96 if (mindelta >= 0 && 97 mindelta <= INTERVAL && 98 maxdelta >= 0 && 99 maxdelta <= INTERVAL) logln(UnicodeString("PASS: Expected boundary at ") + expectedBoundary); 100 else dataerrln(UnicodeString("FAIL: Expected boundary at ") + expectedBoundary); 101} 102 103// ------------------------------------- 104 105void 106TimeZoneBoundaryTest::findDaylightBoundaryUsingTimeZone(UDate d, UBool startsInDST, UDate expectedBoundary) 107{ 108 TimeZone *zone = TimeZone::createDefault(); 109 findDaylightBoundaryUsingTimeZone(d, startsInDST, expectedBoundary, zone); 110 delete zone; 111} 112 113// ------------------------------------- 114 115void 116TimeZoneBoundaryTest::findDaylightBoundaryUsingTimeZone(UDate d, UBool startsInDST, UDate expectedBoundary, TimeZone* tz) 117{ 118 UErrorCode status = U_ZERO_ERROR; 119 UnicodeString str; 120 UDate min = d; 121 UDate max = min + SIX_MONTHS; 122 if (tz->inDaylightTime(d, status) != startsInDST) { 123 dataerrln("FAIL: " + tz->getID(str) + " inDaylightTime(" + dateToString(d) + ") != " + (startsInDST ? "true" : "false")); 124 startsInDST = !startsInDST; 125 } 126 if (failure(status, "TimeZone::inDaylightTime", TRUE)) return; 127 if (tz->inDaylightTime(max, status) == startsInDST) { 128 dataerrln("FAIL: " + tz->getID(str) + " inDaylightTime(" + dateToString(max) + ") != " + (startsInDST ? "false" : "true")); 129 return; 130 } 131 if (failure(status, "TimeZone::inDaylightTime")) return; 132 while ((max - min) > INTERVAL) { 133 UDate mid = (min + max) / 2; 134 UBool isIn = tz->inDaylightTime(mid, status); 135 if (failure(status, "TimeZone::inDaylightTime")) return; 136 if (isIn == startsInDST) { 137 min = mid; 138 } 139 else { 140 max = mid; 141 } 142 } 143 logln(tz->getID(str) + " Before: " + showDate(min)); 144 logln(tz->getID(str) + " After: " + showDate(max)); 145 UDate mindelta = expectedBoundary - min; 146 UDate maxdelta = max - expectedBoundary; 147 if (mindelta >= 0 && 148 mindelta <= INTERVAL && 149 maxdelta >= 0 && 150 maxdelta <= INTERVAL) logln(UnicodeString("PASS: Expected boundary at ") + expectedBoundary); 151 else errln(UnicodeString("FAIL: Expected boundary at ") + expectedBoundary); 152} 153 154// ------------------------------------- 155/* 156UnicodeString* 157TimeZoneBoundaryTest::showDate(int32_t l) 158{ 159 return showDate(new Date(l)); 160} 161*/ 162// ------------------------------------- 163 164UnicodeString 165TimeZoneBoundaryTest::showDate(UDate d) 166{ 167 int32_t y, m, day, h, min, sec; 168 dateToFields(d, y, m, day, h, min, sec); 169 return UnicodeString("") + y + "/" + showNN(m + 1) + "/" + 170 showNN(day) + " " + showNN(h) + ":" + showNN(min) + 171 " \"" + dateToString(d) + "\" = " + uprv_floor(d+0.5); 172} 173 174// ------------------------------------- 175 176UnicodeString 177TimeZoneBoundaryTest::showNN(int32_t n) 178{ 179 UnicodeString nStr; 180 if (n < 10) { 181 nStr += UnicodeString("0", ""); 182 } 183 return nStr + n; 184} 185 186// ------------------------------------- 187 188void 189TimeZoneBoundaryTest::verifyDST(UDate d, TimeZone* time_zone, UBool expUseDaylightTime, UBool expInDaylightTime, UDate expZoneOffset, UDate expDSTOffset) 190{ 191 UnicodeString str; 192 UErrorCode status = U_ZERO_ERROR; 193 logln("-- Verifying time " + dateToString(d) + " in zone " + time_zone->getID(str)); 194 if (time_zone->inDaylightTime(d, status) == expInDaylightTime) 195 logln(UnicodeString("PASS: inDaylightTime = ") + (time_zone->inDaylightTime(d, status)?"true":"false")); 196 else 197 dataerrln(UnicodeString("FAIL: inDaylightTime = ") + (time_zone->inDaylightTime(d, status)?"true":"false")); 198 if (failure(status, "TimeZone::inDaylightTime", TRUE)) 199 return; 200 if (time_zone->useDaylightTime() == expUseDaylightTime) 201 logln(UnicodeString("PASS: useDaylightTime = ") + (time_zone->useDaylightTime()?"true":"false")); 202 else 203 dataerrln(UnicodeString("FAIL: useDaylightTime = ") + (time_zone->useDaylightTime()?"true":"false")); 204 if (time_zone->getRawOffset() == expZoneOffset) 205 logln(UnicodeString("PASS: getRawOffset() = ") + (expZoneOffset / ONE_HOUR)); 206 else 207 dataerrln(UnicodeString("FAIL: getRawOffset() = ") + (time_zone->getRawOffset() / ONE_HOUR) + "; expected " + (expZoneOffset / ONE_HOUR)); 208 209 GregorianCalendar *gc = new GregorianCalendar(time_zone->clone(), status); 210 gc->setTime(d, status); 211 if (failure(status, "GregorianCalendar::setTime")) return; 212 int32_t offset = time_zone->getOffset((uint8_t)gc->get(UCAL_ERA, status), 213 gc->get(UCAL_YEAR, status), gc->get(UCAL_MONTH, status), 214 gc->get(UCAL_DATE, status), (uint8_t)gc->get(UCAL_DAY_OF_WEEK, status), 215 ((gc->get(UCAL_HOUR_OF_DAY, status) * 60 + gc->get(UCAL_MINUTE, status)) * 60 + gc->get(UCAL_SECOND, status)) * 1000 + gc->get(UCAL_MILLISECOND, status), 216 status); 217 if (failure(status, "GregorianCalendar::get")) return; 218 if (offset == expDSTOffset) logln(UnicodeString("PASS: getOffset() = ") + (offset / ONE_HOUR)); 219 else dataerrln(UnicodeString("FAIL: getOffset() = ") + (offset / ONE_HOUR) + "; expected " + (expDSTOffset / ONE_HOUR)); 220 delete gc; 221} 222 223// ------------------------------------- 224/** 225 * Check that the given year/month/dom/hour maps to and from the 226 * given epochHours. This verifies the functioning of the 227 * calendar and time zone in conjunction with one another, 228 * including the calendar time->fields and fields->time and 229 * the time zone getOffset method. 230 * 231 * @param epochHours hours after Jan 1 1970 0:00 GMT. 232 */ 233void TimeZoneBoundaryTest::verifyMapping(Calendar& cal, int year, int month, int dom, int hour, 234 double epochHours) { 235 double H = 3600000.0; 236 UErrorCode status = U_ZERO_ERROR; 237 cal.clear(); 238 cal.set(year, month, dom, hour, 0, 0); 239 UDate e = cal.getTime(status)/ H; 240 UDate ed = (epochHours * H); 241 if (e == epochHours) { 242 logln(UnicodeString("Ok: ") + year + "/" + (month+1) + "/" + dom + " " + hour + ":00 => " + 243 e + " (" + ed + ")"); 244 } else { 245 dataerrln(UnicodeString("FAIL: ") + year + "/" + (month+1) + "/" + dom + " " + hour + ":00 => " + 246 e + " (" + (e * H) + ")" + 247 ", expected " + epochHours + " (" + ed + ")"); 248 } 249 cal.setTime(ed, status); 250 if (cal.get(UCAL_YEAR, status) == year && 251 cal.get(UCAL_MONTH, status) == month && 252 cal.get(UCAL_DATE, status) == dom && 253 cal.get(UCAL_MILLISECONDS_IN_DAY, status) == hour * 3600000) { 254 logln(UnicodeString("Ok: ") + epochHours + " (" + ed + ") => " + 255 cal.get(UCAL_YEAR, status) + "/" + 256 (cal.get(UCAL_MONTH, status)+1) + "/" + 257 cal.get(UCAL_DATE, status) + " " + 258 cal.get(UCAL_MILLISECOND, status)/H); 259 } else { 260 dataerrln(UnicodeString("FAIL: ") + epochHours + " (" + ed + ") => " + 261 cal.get(UCAL_YEAR, status) + "/" + 262 (cal.get(UCAL_MONTH, status)+1) + "/" + 263 cal.get(UCAL_DATE, status) + " " + 264 cal.get(UCAL_MILLISECOND, status)/H + 265 ", expected " + year + "/" + (month+1) + "/" + dom + 266 " " + hour); 267 } 268} 269 270/** 271 * Test the behavior of SimpleTimeZone at the transition into and out of DST. 272 * Use a binary search to find boundaries. 273 */ 274void 275TimeZoneBoundaryTest::TestBoundaries() 276{ 277 UErrorCode status = U_ZERO_ERROR; 278 TimeZone* pst = TimeZone::createTimeZone("PST"); 279 Calendar* tempcal = Calendar::createInstance(pst, status); 280 if(U_SUCCESS(status)){ 281 verifyMapping(*tempcal, 1997, Calendar::APRIL, 3, 0, 238904.0); 282 verifyMapping(*tempcal, 1997, Calendar::APRIL, 4, 0, 238928.0); 283 verifyMapping(*tempcal, 1997, Calendar::APRIL, 5, 0, 238952.0); 284 verifyMapping(*tempcal, 1997, Calendar::APRIL, 5, 23, 238975.0); 285 verifyMapping(*tempcal, 1997, Calendar::APRIL, 6, 0, 238976.0); 286 verifyMapping(*tempcal, 1997, Calendar::APRIL, 6, 1, 238977.0); 287 verifyMapping(*tempcal, 1997, Calendar::APRIL, 6, 3, 238978.0); 288 }else{ 289 dataerrln("Could not create calendar. Error: %s", u_errorName(status)); 290 } 291 TimeZone* utc = TimeZone::createTimeZone("UTC"); 292 Calendar* utccal = Calendar::createInstance(utc, status); 293 if(U_SUCCESS(status)){ 294 verifyMapping(*utccal, 1997, Calendar::APRIL, 6, 0, 238968.0); 295 }else{ 296 dataerrln("Could not create calendar. Error: %s", u_errorName(status)); 297 } 298 TimeZone* save = TimeZone::createDefault(); 299 TimeZone::setDefault(*pst); 300 301 if (tempcal != NULL) { 302 // DST changeover for PST is 4/6/1997 at 2 hours past midnight 303 // at 238978.0 epoch hours. 304 tempcal->clear(); 305 tempcal->set(1997, Calendar::APRIL, 6); 306 UDate d = tempcal->getTime(status); 307 308 // i is minutes past midnight standard time 309 for (int i=-120; i<=180; i+=60) 310 { 311 UBool inDST = (i >= 120); 312 tempcal->setTime(d + i*60*1000, status); 313 verifyDST(tempcal->getTime(status),pst, TRUE, inDST, -8*ONE_HOUR,inDST ? -7*ONE_HOUR : -8*ONE_HOUR); 314 } 315 } 316 TimeZone::setDefault(*save); 317 delete save; 318 delete utccal; 319 delete tempcal; 320 321#if 1 322 { 323 logln("--- Test a ---"); 324 UDate d = date(97, UCAL_APRIL, 6); 325 TimeZone *z = TimeZone::createTimeZone("PST"); 326 for (int32_t i = 60; i <= 180; i += 15) { 327 UBool inDST = (i >= 120); 328 UDate e = d + i * 60 * 1000; 329 verifyDST(e, z, TRUE, inDST, - 8 * ONE_HOUR, inDST ? - 7 * ONE_HOUR: - 8 * ONE_HOUR); 330 } 331 delete z; 332 } 333#endif 334#if 1 335 { 336 logln("--- Test b ---"); 337 TimeZone *tz; 338 TimeZone::setDefault(*(tz = TimeZone::createTimeZone("PST"))); 339 delete tz; 340 logln("========================================"); 341 findDaylightBoundaryUsingDate(date(97, 0, 1), "PST", PST_1997_BEG); 342 logln("========================================"); 343 findDaylightBoundaryUsingDate(date(97, 6, 1), "PDT", PST_1997_END); 344 } 345#endif 346#if 1 347 { 348 logln("--- Test c ---"); 349 logln("========================================"); 350 TimeZone* z = TimeZone::createTimeZone("Australia/Adelaide"); 351 findDaylightBoundaryUsingTimeZone(date(97, 0, 1), TRUE, 859653000000.0, z); 352 logln("========================================"); 353 findDaylightBoundaryUsingTimeZone(date(97, 6, 1), FALSE, 877797000000.0, z); 354 delete z; 355 } 356#endif 357#if 1 358 { 359 logln("--- Test d ---"); 360 logln("========================================"); 361 findDaylightBoundaryUsingTimeZone(date(97, 0, 1), FALSE, PST_1997_BEG); 362 logln("========================================"); 363 findDaylightBoundaryUsingTimeZone(date(97, 6, 1), TRUE, PST_1997_END); 364 } 365#endif 366#if 0 367 { 368 logln("--- Test e ---"); 369 TimeZone *z = TimeZone::createDefault(); 370 logln(UnicodeString("") + z->getOffset(1, 97, 3, 4, 6, 0) + " " + date(97, 3, 4)); 371 logln(UnicodeString("") + z->getOffset(1, 97, 3, 5, 7, 0) + " " + date(97, 3, 5)); 372 logln(UnicodeString("") + z->getOffset(1, 97, 3, 6, 1, 0) + " " + date(97, 3, 6)); 373 logln(UnicodeString("") + z->getOffset(1, 97, 3, 7, 2, 0) + " " + date(97, 3, 7)); 374 delete z; 375 } 376#endif 377} 378 379// ------------------------------------- 380 381void 382TimeZoneBoundaryTest::testUsingBinarySearch(SimpleTimeZone* tz, UDate d, UDate expectedBoundary) 383{ 384 UErrorCode status = U_ZERO_ERROR; 385 UDate min = d; 386 UDate max = min + SIX_MONTHS; 387 UBool startsInDST = tz->inDaylightTime(d, status); 388 if (failure(status, "SimpleTimeZone::inDaylightTime", TRUE)) return; 389 if (tz->inDaylightTime(max, status) == startsInDST) { 390 errln("Error: inDaylightTime(" + dateToString(max) + ") != " + ((!startsInDST)?"true":"false")); 391 } 392 if (failure(status, "SimpleTimeZone::inDaylightTime")) return; 393 while ((max - min) > INTERVAL) { 394 UDate mid = (min + max) / 2; 395 if (tz->inDaylightTime(mid, status) == startsInDST) { 396 min = mid; 397 } 398 else { 399 max = mid; 400 } 401 if (failure(status, "SimpleTimeZone::inDaylightTime")) return; 402 } 403 logln("Binary Search Before: " + showDate(min)); 404 logln("Binary Search After: " + showDate(max)); 405 UDate mindelta = expectedBoundary - min; 406 UDate maxdelta = max - expectedBoundary; 407 if (mindelta >= 0 && 408 mindelta <= INTERVAL && 409 maxdelta >= 0 && 410 maxdelta <= INTERVAL) logln(UnicodeString("PASS: Expected boundary at ") + expectedBoundary); 411 else errln(UnicodeString("FAIL: Expected boundary at ") + expectedBoundary); 412} 413 414// ------------------------------------- 415 416/** 417 * Test the handling of the "new" rules; that is, rules other than nth Day of week. 418 */ 419void 420TimeZoneBoundaryTest::TestNewRules() 421{ 422#if 1 423 { 424 UErrorCode status = U_ZERO_ERROR; 425 SimpleTimeZone *tz; 426 logln("-----------------------------------------------------------------"); 427 logln("Aug 2ndTues .. Mar 15"); 428 tz = new SimpleTimeZone(- 8 * (int32_t)ONE_HOUR, "Test_1", UCAL_AUGUST, 2, UCAL_TUESDAY, 2 * (int32_t)ONE_HOUR, UCAL_MARCH, 15, 0, 2 * (int32_t)ONE_HOUR, status); 429 logln("========================================"); 430 testUsingBinarySearch(tz, date(97, 0, 1), 858416400000.0); 431 logln("========================================"); 432 testUsingBinarySearch(tz, date(97, 6, 1), 871380000000.0); 433 delete tz; 434 logln("-----------------------------------------------------------------"); 435 logln("Apr Wed>=14 .. Sep Sun<=20"); 436 tz = new SimpleTimeZone(- 8 * (int32_t)ONE_HOUR, "Test_2", UCAL_APRIL, 14, - UCAL_WEDNESDAY, 2 *(int32_t)ONE_HOUR, UCAL_SEPTEMBER, - 20, - UCAL_SUNDAY, 2 * (int32_t)ONE_HOUR, status); 437 logln("========================================"); 438 testUsingBinarySearch(tz, date(97, 0, 1), 861184800000.0); 439 logln("========================================"); 440 testUsingBinarySearch(tz, date(97, 6, 1), 874227600000.0); 441 delete tz; 442 } 443#endif 444} 445 446// ------------------------------------- 447 448void 449TimeZoneBoundaryTest::findBoundariesStepwise(int32_t year, UDate interval, TimeZone* z, int32_t expectedChanges) 450{ 451 UErrorCode status = U_ZERO_ERROR; 452 UnicodeString str; 453 UDate d = date(year - 1900, UCAL_JANUARY, 1); 454 UDate time = d; 455 UDate limit = time + ONE_YEAR + ONE_DAY; 456 UBool lastState = z->inDaylightTime(d, status); 457 if (failure(status, "TimeZone::inDaylightTime", TRUE)) return; 458 int32_t changes = 0; 459 logln(UnicodeString("-- Zone ") + z->getID(str) + " starts in " + year + " with DST = " + (lastState?"true":"false")); 460 logln(UnicodeString("useDaylightTime = ") + (z->useDaylightTime()?"true":"false")); 461 while (time < limit) { 462 d = time; 463 UBool state = z->inDaylightTime(d, status); 464 if (failure(status, "TimeZone::inDaylightTime")) return; 465 if (state != lastState) { 466 logln(UnicodeString(state ? "Entry ": "Exit ") + "at " + d); 467 lastState = state;++changes; 468 } 469 time += interval; 470 } 471 if (changes == 0) { 472 if (!lastState && 473 !z->useDaylightTime()) logln("No DST"); 474 else errln("FAIL: DST all year, or no DST with true useDaylightTime"); 475 } 476 else if (changes != 2) { 477 errln(UnicodeString("FAIL: ") + changes + " changes seen; should see 0 or 2"); 478 } 479 else if (!z->useDaylightTime()) { 480 errln("FAIL: useDaylightTime false but 2 changes seen"); 481 } 482 if (changes != expectedChanges) { 483 dataerrln(UnicodeString("FAIL: ") + changes + " changes seen; expected " + expectedChanges); 484 } 485} 486 487// ------------------------------------- 488 489/** 490 * This test is problematic. It makes assumptions about the behavior 491 * of specific zones. Since ICU's zone table is based on the Olson 492 * zones (the UNIX zones), and those change from time to time, this 493 * test can fail after a zone table update. If that happens, the 494 * selected zones need to be updated to have the behavior 495 * expected. That is, they should have DST, not have DST, and have DST 496 * -- other than that this test isn't picky. 12/3/99 aliu 497 * 498 * Test the behavior of SimpleTimeZone at the transition into and out of DST. 499 * Use a stepwise march to find boundaries. 500 */ 501void 502TimeZoneBoundaryTest::TestStepwise() 503{ 504 TimeZone *zone = TimeZone::createTimeZone("America/New_York"); 505 findBoundariesStepwise(1997, ONE_DAY, zone, 2); 506 delete zone; 507 zone = TimeZone::createTimeZone("UTC"); // updated 12/3/99 aliu 508 findBoundariesStepwise(1997, ONE_DAY, zone, 0); 509 delete zone; 510 zone = TimeZone::createTimeZone("Australia/Adelaide"); 511 findBoundariesStepwise(1997, ONE_DAY, zone, 2); 512 delete zone; 513} 514 515#endif /* #if !UCONFIG_NO_FORMATTING */ 516