Home | History | Annotate | Download | only in intltest
      1 /***********************************************************************
      2  * COPYRIGHT:
      3  * Copyright (c) 1997-2009, 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 
     17 void 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 
     50 TimeZoneBoundaryTest::TimeZoneBoundaryTest()
     51 :
     52 ONE_SECOND(1000),
     53 ONE_MINUTE(60 * ONE_SECOND),
     54 ONE_HOUR(60 * ONE_MINUTE),
     55 ONE_DAY(24 * ONE_HOUR),
     56 ONE_YEAR(uprv_floor(365.25 * ONE_DAY)),
     57 SIX_MONTHS(ONE_YEAR / 2)
     58 {
     59 }
     60 
     61 const int32_t TimeZoneBoundaryTest::MONTH_LENGTH[] = {
     62     31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
     63 };
     64 
     65 const UDate TimeZoneBoundaryTest::PST_1997_BEG = 860320800000.0;
     66 
     67 const UDate TimeZoneBoundaryTest::PST_1997_END = 877856400000.0;
     68 
     69 const UDate TimeZoneBoundaryTest::INTERVAL = 10;
     70 
     71 // -------------------------------------
     72 
     73 void
     74 TimeZoneBoundaryTest::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 
    105 void
    106 TimeZoneBoundaryTest::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 
    115 void
    116 TimeZoneBoundaryTest::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")) 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 /*
    156 UnicodeString*
    157 TimeZoneBoundaryTest::showDate(int32_t l)
    158 {
    159     return showDate(new Date(l));
    160 }
    161 */
    162 // -------------------------------------
    163 
    164 UnicodeString
    165 TimeZoneBoundaryTest::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 
    176 UnicodeString
    177 TimeZoneBoundaryTest::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 
    188 void
    189 TimeZoneBoundaryTest::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"))
    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     */
    233 void 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  */
    274 void
    275 TimeZoneBoundaryTest::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         errln("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         errln("Could not create calendar. Error: %s", u_errorName(status));
    297     }
    298     TimeZone* save = TimeZone::createDefault();
    299     TimeZone::setDefault(*pst);
    300 
    301     // DST changeover for PST is 4/6/1997 at 2 hours past midnight
    302     // at 238978.0 epoch hours.
    303     tempcal->clear();
    304     tempcal->set(1997, Calendar::APRIL, 6);
    305     UDate d = tempcal->getTime(status);
    306 
    307     // i is minutes past midnight standard time
    308     for (int i=-120; i<=180; i+=60)
    309     {
    310         UBool inDST = (i >= 120);
    311         tempcal->setTime(d + i*60*1000, status);
    312         verifyDST(tempcal->getTime(status),pst, TRUE, inDST, -8*ONE_HOUR,inDST ? -7*ONE_HOUR : -8*ONE_HOUR);
    313     }
    314     TimeZone::setDefault(*save);
    315     delete save;
    316     delete utccal;
    317     delete tempcal;
    318 
    319 #if 1
    320     {
    321         logln("--- Test a ---");
    322         UDate d = date(97, UCAL_APRIL, 6);
    323         TimeZone *z = TimeZone::createTimeZone("PST");
    324         for (int32_t i = 60; i <= 180; i += 15) {
    325             UBool inDST = (i >= 120);
    326             UDate e = d + i * 60 * 1000;
    327             verifyDST(e, z, TRUE, inDST, - 8 * ONE_HOUR, inDST ? - 7 * ONE_HOUR: - 8 * ONE_HOUR);
    328         }
    329         delete z;
    330     }
    331 #endif
    332 #if 1
    333     {
    334         logln("--- Test b ---");
    335         TimeZone *tz;
    336         TimeZone::setDefault(*(tz = TimeZone::createTimeZone("PST")));
    337         delete tz;
    338         logln("========================================");
    339         findDaylightBoundaryUsingDate(date(97, 0, 1), "PST", PST_1997_BEG);
    340         logln("========================================");
    341         findDaylightBoundaryUsingDate(date(97, 6, 1), "PDT", PST_1997_END);
    342     }
    343 #endif
    344 #if 1
    345     {
    346         logln("--- Test c ---");
    347         logln("========================================");
    348         TimeZone* z = TimeZone::createTimeZone("Australia/Adelaide");
    349         findDaylightBoundaryUsingTimeZone(date(97, 0, 1), TRUE, 859653000000.0, z);
    350         logln("========================================");
    351         findDaylightBoundaryUsingTimeZone(date(97, 6, 1), FALSE, 877797000000.0, z);
    352         delete z;
    353     }
    354 #endif
    355 #if 1
    356     {
    357         logln("--- Test d ---");
    358         logln("========================================");
    359         findDaylightBoundaryUsingTimeZone(date(97, 0, 1), FALSE, PST_1997_BEG);
    360         logln("========================================");
    361         findDaylightBoundaryUsingTimeZone(date(97, 6, 1), TRUE, PST_1997_END);
    362     }
    363 #endif
    364 #if 0
    365     {
    366         logln("--- Test e ---");
    367         TimeZone *z = TimeZone::createDefault();
    368         logln(UnicodeString("") + z->getOffset(1, 97, 3, 4, 6, 0) + " " + date(97, 3, 4));
    369         logln(UnicodeString("") + z->getOffset(1, 97, 3, 5, 7, 0) + " " + date(97, 3, 5));
    370         logln(UnicodeString("") + z->getOffset(1, 97, 3, 6, 1, 0) + " " + date(97, 3, 6));
    371         logln(UnicodeString("") + z->getOffset(1, 97, 3, 7, 2, 0) + " " + date(97, 3, 7));
    372         delete z;
    373     }
    374 #endif
    375 }
    376 
    377 // -------------------------------------
    378 
    379 void
    380 TimeZoneBoundaryTest::testUsingBinarySearch(SimpleTimeZone* tz, UDate d, UDate expectedBoundary)
    381 {
    382     UErrorCode status = U_ZERO_ERROR;
    383     UDate min = d;
    384     UDate max = min + SIX_MONTHS;
    385     UBool startsInDST = tz->inDaylightTime(d, status);
    386     if (failure(status, "SimpleTimeZone::inDaylightTime")) return;
    387     if (tz->inDaylightTime(max, status) == startsInDST) {
    388         errln("Error: inDaylightTime(" + dateToString(max) + ") != " + ((!startsInDST)?"true":"false"));
    389     }
    390     if (failure(status, "SimpleTimeZone::inDaylightTime")) return;
    391     while ((max - min) > INTERVAL) {
    392         UDate mid = (min + max) / 2;
    393         if (tz->inDaylightTime(mid, status) == startsInDST) {
    394             min = mid;
    395         }
    396         else {
    397             max = mid;
    398         }
    399         if (failure(status, "SimpleTimeZone::inDaylightTime")) return;
    400     }
    401     logln("Binary Search Before: " + showDate(min));
    402     logln("Binary Search After:  " + showDate(max));
    403     UDate mindelta = expectedBoundary - min;
    404     UDate maxdelta = max - expectedBoundary;
    405     if (mindelta >= 0 &&
    406         mindelta <= INTERVAL &&
    407         maxdelta >= 0 &&
    408         maxdelta <= INTERVAL) logln(UnicodeString("PASS: Expected boundary at ") + expectedBoundary);
    409     else errln(UnicodeString("FAIL: Expected boundary at ") + expectedBoundary);
    410 }
    411 
    412 // -------------------------------------
    413 
    414 /**
    415  * Test the handling of the "new" rules; that is, rules other than nth Day of week.
    416  */
    417 void
    418 TimeZoneBoundaryTest::TestNewRules()
    419 {
    420 #if 1
    421     {
    422         UErrorCode status = U_ZERO_ERROR;
    423         SimpleTimeZone *tz;
    424         logln("-----------------------------------------------------------------");
    425         logln("Aug 2ndTues .. Mar 15");
    426         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);
    427         logln("========================================");
    428         testUsingBinarySearch(tz, date(97, 0, 1), 858416400000.0);
    429         logln("========================================");
    430         testUsingBinarySearch(tz, date(97, 6, 1), 871380000000.0);
    431         delete tz;
    432         logln("-----------------------------------------------------------------");
    433         logln("Apr Wed>=14 .. Sep Sun<=20");
    434         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);
    435         logln("========================================");
    436         testUsingBinarySearch(tz, date(97, 0, 1), 861184800000.0);
    437         logln("========================================");
    438         testUsingBinarySearch(tz, date(97, 6, 1), 874227600000.0);
    439         delete tz;
    440     }
    441 #endif
    442 }
    443 
    444 // -------------------------------------
    445 
    446 void
    447 TimeZoneBoundaryTest::findBoundariesStepwise(int32_t year, UDate interval, TimeZone* z, int32_t expectedChanges)
    448 {
    449     UErrorCode status = U_ZERO_ERROR;
    450     UnicodeString str;
    451     UDate d = date(year - 1900, UCAL_JANUARY, 1);
    452     UDate time = d;
    453     UDate limit = time + ONE_YEAR + ONE_DAY;
    454     UBool lastState = z->inDaylightTime(d, status);
    455     if (failure(status, "TimeZone::inDaylightTime")) return;
    456     int32_t changes = 0;
    457     logln(UnicodeString("-- Zone ") + z->getID(str) + " starts in " + year + " with DST = " + (lastState?"true":"false"));
    458     logln(UnicodeString("useDaylightTime = ") + (z->useDaylightTime()?"true":"false"));
    459     while (time < limit) {
    460         d = time;
    461         UBool state = z->inDaylightTime(d, status);
    462         if (failure(status, "TimeZone::inDaylightTime")) return;
    463         if (state != lastState) {
    464             logln(UnicodeString(state ? "Entry ": "Exit ") + "at " + d);
    465             lastState = state;++changes;
    466         }
    467         time += interval;
    468     }
    469     if (changes == 0) {
    470         if (!lastState &&
    471             !z->useDaylightTime()) logln("No DST");
    472         else errln("FAIL: DST all year, or no DST with true useDaylightTime");
    473     }
    474     else if (changes != 2) {
    475         errln(UnicodeString("FAIL: ") + changes + " changes seen; should see 0 or 2");
    476     }
    477     else if (!z->useDaylightTime()) {
    478         errln("FAIL: useDaylightTime false but 2 changes seen");
    479     }
    480     if (changes != expectedChanges) {
    481         dataerrln(UnicodeString("FAIL: ") + changes + " changes seen; expected " + expectedChanges);
    482     }
    483 }
    484 
    485 // -------------------------------------
    486 
    487 /**
    488  * This test is problematic. It makes assumptions about the behavior
    489  * of specific zones. Since ICU's zone table is based on the Olson
    490  * zones (the UNIX zones), and those change from time to time, this
    491  * test can fail after a zone table update. If that happens, the
    492  * selected zones need to be updated to have the behavior
    493  * expected. That is, they should have DST, not have DST, and have DST
    494  * -- other than that this test isn't picky. 12/3/99 aliu
    495  *
    496  * Test the behavior of SimpleTimeZone at the transition into and out of DST.
    497  * Use a stepwise march to find boundaries.
    498  */
    499 void
    500 TimeZoneBoundaryTest::TestStepwise()
    501 {
    502     TimeZone *zone =  TimeZone::createTimeZone("America/New_York");
    503     findBoundariesStepwise(1997, ONE_DAY, zone, 2);
    504     delete zone;
    505     zone = TimeZone::createTimeZone("UTC"); // updated 12/3/99 aliu
    506     findBoundariesStepwise(1997, ONE_DAY, zone, 0);
    507     delete zone;
    508     zone = TimeZone::createTimeZone("Australia/Adelaide");
    509     findBoundariesStepwise(1997, ONE_DAY, zone, 2);
    510     delete zone;
    511 }
    512 
    513 #endif /* #if !UCONFIG_NO_FORMATTING */
    514