• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // © 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 
runIndexedTest(int32_t index,UBool exec,const char * & name,char *)19 void 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 
TimeZoneBoundaryTest()52 TimeZoneBoundaryTest::TimeZoneBoundaryTest()
53 :
54 ONE_SECOND(1000),
55 ONE_MINUTE(60 * ONE_SECOND),
56 ONE_HOUR(60 * ONE_MINUTE),
57 ONE_DAY(24 * ONE_HOUR),
58 ONE_YEAR(uprv_floor(365.25 * ONE_DAY)),
59 SIX_MONTHS(ONE_YEAR / 2)
60 {
61 }
62 
63 const int32_t TimeZoneBoundaryTest::MONTH_LENGTH[] = {
64     31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
65 };
66 
67 const UDate TimeZoneBoundaryTest::PST_1997_BEG = 860320800000.0;
68 
69 const UDate TimeZoneBoundaryTest::PST_1997_END = 877856400000.0;
70 
71 const UDate TimeZoneBoundaryTest::INTERVAL = 10;
72 
73 // -------------------------------------
74 
75 void
findDaylightBoundaryUsingDate(UDate d,const char * startMode,UDate expectedBoundary)76 TimeZoneBoundaryTest::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 
107 void
findDaylightBoundaryUsingTimeZone(UDate d,UBool startsInDST,UDate expectedBoundary)108 TimeZoneBoundaryTest::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 
117 void
findDaylightBoundaryUsingTimeZone(UDate d,UBool startsInDST,UDate expectedBoundary,TimeZone * tz)118 TimeZoneBoundaryTest::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 /*
158 UnicodeString*
159 TimeZoneBoundaryTest::showDate(int32_t l)
160 {
161     return showDate(new Date(l));
162 }
163 */
164 // -------------------------------------
165 
166 UnicodeString
showDate(UDate d)167 TimeZoneBoundaryTest::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 
178 UnicodeString
showNN(int32_t n)179 TimeZoneBoundaryTest::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 
190 void
verifyDST(UDate d,TimeZone * time_zone,UBool expUseDaylightTime,UBool expInDaylightTime,UDate expZoneOffset,UDate expDSTOffset)191 TimeZoneBoundaryTest::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     */
verifyMapping(Calendar & cal,int year,int month,int dom,int hour,double epochHours)235 void 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  */
276 void
TestBoundaries()277 TimeZoneBoundaryTest::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 
383 void
testUsingBinarySearch(SimpleTimeZone * tz,UDate d,UDate expectedBoundary)384 TimeZoneBoundaryTest::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  */
421 void
TestNewRules()422 TimeZoneBoundaryTest::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 
450 void
findBoundariesStepwise(int32_t year,UDate interval,TimeZone * z,int32_t expectedChanges)451 TimeZoneBoundaryTest::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  */
503 void
TestStepwise()504 TimeZoneBoundaryTest::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