]> git.saurik.com Git - apple/icu.git/blame - icuSources/test/intltest/tzbdtest.cpp
ICU-66108.tar.gz
[apple/icu.git] / icuSources / test / intltest / tzbdtest.cpp
CommitLineData
f3c0d7a5
A
1// © 2016 and later: Unicode, Inc. and others.
2// License & terms of use: http://www.unicode.org/copyright.html
374ca955 3/***********************************************************************
b75a7d8f 4 * COPYRIGHT:
729e4ab9 5 * Copyright (c) 1997-2010, International Business Machines Corporation
374ca955
A
6 * and others. All Rights Reserved.
7 ***********************************************************************/
b75a7d8f
A
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"
374ca955 17#include "putilimp.h"
b75a7d8f
A
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);
729e4ab9 102 else dataerrln(UnicodeString("FAIL: Expected boundary at ") + expectedBoundary);
b75a7d8f
A
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) {
729e4ab9 125 dataerrln("FAIL: " + tz->getID(str) + " inDaylightTime(" + dateToString(d) + ") != " + (startsInDST ? "true" : "false"));
b75a7d8f
A
126 startsInDST = !startsInDST;
127 }
729e4ab9 128 if (failure(status, "TimeZone::inDaylightTime", TRUE)) return;
b75a7d8f 129 if (tz->inDaylightTime(max, status) == startsInDST) {
729e4ab9 130 dataerrln("FAIL: " + tz->getID(str) + " inDaylightTime(" + dateToString(max) + ") != " + (startsInDST ? "false" : "true"));
b75a7d8f
A
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{
374ca955
A
181 UnicodeString nStr;
182 if (n < 10) {
183 nStr += UnicodeString("0", "");
184 }
185 return nStr + n;
b75a7d8f
A
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"));
73c04bcf 198 else
729e4ab9
A
199 dataerrln(UnicodeString("FAIL: inDaylightTime = ") + (time_zone->inDaylightTime(d, status)?"true":"false"));
200 if (failure(status, "TimeZone::inDaylightTime", TRUE))
73c04bcf 201 return;
b75a7d8f
A
202 if (time_zone->useDaylightTime() == expUseDaylightTime)
203 logln(UnicodeString("PASS: useDaylightTime = ") + (time_zone->useDaylightTime()?"true":"false"));
73c04bcf 204 else
729e4ab9 205 dataerrln(UnicodeString("FAIL: useDaylightTime = ") + (time_zone->useDaylightTime()?"true":"false"));
73c04bcf
A
206 if (time_zone->getRawOffset() == expZoneOffset)
207 logln(UnicodeString("PASS: getRawOffset() = ") + (expZoneOffset / ONE_HOUR));
208 else
729e4ab9 209 dataerrln(UnicodeString("FAIL: getRawOffset() = ") + (time_zone->getRawOffset() / ONE_HOUR) + "; expected " + (expZoneOffset / ONE_HOUR));
73c04bcf 210
b75a7d8f
A
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));
729e4ab9 221 else dataerrln(UnicodeString("FAIL: getOffset() = ") + (offset / ONE_HOUR) + "; expected " + (expDSTOffset / ONE_HOUR));
b75a7d8f
A
222 delete gc;
223}
224
225// -------------------------------------
73c04bcf
A
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 {
729e4ab9 247 dataerrln(UnicodeString("FAIL: ") + year + "/" + (month+1) + "/" + dom + " " + hour + ":00 => " +
73c04bcf
A
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 {
729e4ab9 262 dataerrln(UnicodeString("FAIL: ") + epochHours + " (" + ed + ") => " +
73c04bcf
A
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
b75a7d8f
A
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{
73c04bcf
A
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{
729e4ab9 291 dataerrln("Could not create calendar. Error: %s", u_errorName(status));
73c04bcf
A
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{
729e4ab9 298 dataerrln("Could not create calendar. Error: %s", u_errorName(status));
73c04bcf
A
299 }
300 TimeZone* save = TimeZone::createDefault();
301 TimeZone::setDefault(*pst);
729e4ab9
A
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);
73c04bcf 309
729e4ab9
A
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 }
73c04bcf
A
317 }
318 TimeZone::setDefault(*save);
319 delete save;
320 delete utccal;
321 delete tempcal;
322
b75a7d8f
A
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);
729e4ab9 390 if (failure(status, "SimpleTimeZone::inDaylightTime", TRUE)) return;
b75a7d8f 391 if (tz->inDaylightTime(max, status) == startsInDST) {
73c04bcf 392 errln("Error: inDaylightTime(" + dateToString(max) + ") != " + ((!startsInDST)?"true":"false"));
b75a7d8f
A
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);
729e4ab9 459 if (failure(status, "TimeZone::inDaylightTime", TRUE)) return;
b75a7d8f
A
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) {
729e4ab9 485 dataerrln(UnicodeString("FAIL: ") + changes + " changes seen; expected " + expectedChanges);
b75a7d8f
A
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{
374ca955 506 TimeZone *zone = TimeZone::createTimeZone("America/New_York");
b75a7d8f
A
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 */