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