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