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 
runIndexedTest(int32_t index,UBool exec,const char * & name,char *)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 
TimeZoneBoundaryTest()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
findDaylightBoundaryUsingDate(UDate d,const char * startMode,UDate expectedBoundary)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
findDaylightBoundaryUsingTimeZone(UDate d,UBool startsInDST,UDate expectedBoundary)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
findDaylightBoundaryUsingTimeZone(UDate d,UBool startsInDST,UDate expectedBoundary,TimeZone * tz)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", 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 /*
156 UnicodeString*
157 TimeZoneBoundaryTest::showDate(int32_t l)
158 {
159     return showDate(new Date(l));
160 }
161 */
162 // -------------------------------------
163 
164 UnicodeString
showDate(UDate d)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
showNN(int32_t n)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
verifyDST(UDate d,TimeZone * time_zone,UBool expUseDaylightTime,UBool expInDaylightTime,UDate expZoneOffset,UDate expDSTOffset)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", 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     */
verifyMapping(Calendar & cal,int year,int month,int dom,int hour,double epochHours)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
TestBoundaries()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         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 
381 void
testUsingBinarySearch(SimpleTimeZone * tz,UDate d,UDate expectedBoundary)382 TimeZoneBoundaryTest::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  */
419 void
TestNewRules()420 TimeZoneBoundaryTest::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 
448 void
findBoundariesStepwise(int32_t year,UDate interval,TimeZone * z,int32_t expectedChanges)449 TimeZoneBoundaryTest::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  */
501 void
TestStepwise()502 TimeZoneBoundaryTest::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