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-2016, 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 "unicode/timezone.h"
14 #include "unicode/simpletz.h"
15 #include "unicode/calendar.h"
16 #include "unicode/gregocal.h"
17 #include "unicode/localpointer.h"
18 #include "unicode/resbund.h"
19 #include "unicode/strenum.h"
20 #include "unicode/ustring.h"
21 #include "unicode/uversion.h"
22 #include "tztest.h"
23 #include "cmemory.h"
24 #include "putilimp.h"
25 #include "cstring.h"
26 #include "olsontz.h"
27
28 #define CASE(id,test) case id: \
29 name = #test; \
30 if (exec) { \
31 logln(#test "---"); logln(""); \
32 test(); \
33 } \
34 break
35
36 // *****************************************************************************
37 // class TimeZoneTest
38 // *****************************************************************************
39
40 // Some test case data is current date/tzdata version sensitive and producing errors
41 // when year/rule are changed. Although we want to keep our eyes on test failures
42 // caused by tzdata changes while development, keep maintaining test data in maintenance
43 // stream is a little bit hassle. ICU 49 or later versions are using minor version field
44 // to indicate a development build (0) or official release build (others). For development
45 // builds, a test failure triggers an error, while release builds only report them in
46 // verbose mode with logln.
47 static UBool isDevelopmentBuild = (U_ICU_VERSION_MINOR_NUM == 0);
48
runIndexedTest(int32_t index,UBool exec,const char * & name,char *)49 void TimeZoneTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ )
50 {
51 if (exec) {
52 logln("TestSuite TestTimeZone");
53 }
54 TESTCASE_AUTO_BEGIN;
55 TESTCASE_AUTO(TestPRTOffset);
56 TESTCASE_AUTO(TestVariousAPI518);
57 TESTCASE_AUTO(TestGetAvailableIDs913);
58 TESTCASE_AUTO(TestGenericAPI);
59 TESTCASE_AUTO(TestRuleAPI);
60 TESTCASE_AUTO(TestShortZoneIDs);
61 TESTCASE_AUTO(TestCustomParse);
62 TESTCASE_AUTO(TestDisplayName);
63 TESTCASE_AUTO(TestDSTSavings);
64 TESTCASE_AUTO(TestAlternateRules);
65 TESTCASE_AUTO(TestCountries);
66 TESTCASE_AUTO(TestHistorical);
67 TESTCASE_AUTO(TestEquivalentIDs);
68 TESTCASE_AUTO(TestAliasedNames);
69 TESTCASE_AUTO(TestFractionalDST);
70 TESTCASE_AUTO(TestFebruary);
71 TESTCASE_AUTO(TestCanonicalIDAPI);
72 TESTCASE_AUTO(TestCanonicalID);
73 TESTCASE_AUTO(TestDisplayNamesMeta);
74 TESTCASE_AUTO(TestGetRegion);
75 TESTCASE_AUTO(TestGetAvailableIDsNew);
76 TESTCASE_AUTO(TestGetUnknown);
77 TESTCASE_AUTO(TestGetGMT);
78 TESTCASE_AUTO(TestGetWindowsID);
79 TESTCASE_AUTO(TestGetIDForWindowsID);
80 TESTCASE_AUTO(TestCasablancaNameAndOffset22041);
81 TESTCASE_AUTO(TestRawOffsetAndOffsetConsistency22041);
82 TESTCASE_AUTO(TestGetIanaID);
83 TESTCASE_AUTO(TestGMTMinus24ICU22526);
84 TESTCASE_AUTO_END;
85 }
86
87 const int32_t TimeZoneTest::millisPerHour = 3600000;
88
89 // ---------------------------------------------------------------------------------
90
91 /**
92 * Generic API testing for API coverage.
93 */
94 void
TestGenericAPI()95 TimeZoneTest::TestGenericAPI()
96 {
97 UnicodeString id("NewGMT");
98 int32_t offset = 12345;
99
100 SimpleTimeZone *zone = new SimpleTimeZone(offset, id);
101 if (zone->useDaylightTime()) errln("FAIL: useDaylightTime should return false");
102
103 TimeZone* zoneclone = zone->clone();
104 if (!(*zoneclone == *zone)) errln("FAIL: clone or operator== failed");
105 zoneclone->setID("abc");
106 if (!(*zoneclone != *zone)) errln("FAIL: clone or operator!= failed");
107 delete zoneclone;
108
109 zoneclone = zone->clone();
110 if (!(*zoneclone == *zone)) errln("FAIL: clone or operator== failed");
111 zoneclone->setRawOffset(45678);
112 if (!(*zoneclone != *zone)) errln("FAIL: clone or operator!= failed");
113
114 SimpleTimeZone copy(*zone);
115 if (!(copy == *zone)) errln("FAIL: copy constructor or operator== failed");
116 copy = *dynamic_cast<SimpleTimeZone*>(zoneclone);
117 if (!(copy == *zoneclone)) errln("FAIL: assignment operator or operator== failed");
118
119 TimeZone* saveDefault = TimeZone::createDefault();
120 logln((UnicodeString)"TimeZone::createDefault() => " + saveDefault->getID(id));
121
122 TimeZone::adoptDefault(zone);
123 TimeZone* defaultzone = TimeZone::createDefault();
124 if (defaultzone == zone ||
125 !(*defaultzone == *zone))
126 errln("FAIL: createDefault failed");
127 TimeZone::adoptDefault(saveDefault);
128 delete defaultzone;
129 delete zoneclone;
130
131 logln("call uprv_timezone() which uses the host");
132 logln("to get the difference in seconds between coordinated universal");
133 logln("time and local time. E.g., -28,800 for PST (GMT-8hrs)");
134
135 int32_t tzoffset = uprv_timezone();
136 if ((tzoffset % 900) != 0) {
137 /*
138 * Ticket#6364 and #7648
139 * A few time zones are using GMT offests not a multiple of 15 minutes.
140 * Therefore, we should not interpret such case as an error.
141 * We downgrade this from errln to infoln. When we see this message,
142 * we should examine if it is ignorable or not.
143 */
144 infoln("WARNING: t_timezone may be incorrect. It is not a multiple of 15min.", tzoffset);
145 }
146
147 TimeZone* hostZone = TimeZone::detectHostTimeZone();
148 int32_t hostZoneRawOffset = hostZone->getRawOffset();
149 logln("hostZone->getRawOffset() = %d , tzoffset = %d", hostZoneRawOffset, tzoffset * (-1000));
150
151 /* Host time zone's offset should match the offset returned by uprv_timezone() */
152 if (hostZoneRawOffset != tzoffset * (-1000)) {
153 UnicodeString id;
154 hostZone->getID(id);
155 // Known issues in ICU-22274 we have issues in time zone
156 // Africa/Casablanca Europe/Dublin America/Godthab America/Nuuk
157 if (id == u"Africa/Casablanca" || id == u"Europe/Dublin" ||
158 id == u"America/Godthab" || id == u"America/Nuuk" ||
159 id == u"Africa/El_Aaiun" ||
160 id == u"Asia/Qostanay" || // Due to changes in tz2024a
161 id == u"Asia/Almaty" || // Due to changes in tz2024a
162 id == u"America/Scoresbysund" // break after the update of tz2023d
163 ) {
164 logKnownIssue( "ICU-22274", "detectHostTimeZone()'s raw offset != host timezone's offset in TimeZone " + id);
165 } else {
166 errln("FAIL: detectHostTimeZone()'s raw offset != host timezone's offset.\n"
167 "hostZone->getRawOffset()=%d\n"
168 "but uprv_timezone() return %d and "
169 "uprv_timezone() * -1000=%d",
170 hostZoneRawOffset, tzoffset, tzoffset * -1000);
171 }
172 }
173 delete hostZone;
174
175 UErrorCode status = U_ZERO_ERROR;
176 const char* tzver = TimeZone::getTZDataVersion(status);
177 if (U_FAILURE(status)) {
178 errcheckln(status, "FAIL: getTZDataVersion failed - %s", u_errorName(status));
179 } else {
180 int32_t tzverLen = uprv_strlen(tzver);
181 if (tzverLen == 5 || tzverLen == 6 /* 4 digits + 1 or 2 letters */) {
182 logln((UnicodeString)"tzdata version: " + tzver);
183 } else {
184 errln((UnicodeString)"FAIL: getTZDataVersion returned " + tzver);
185 }
186 }
187 }
188
189 // ---------------------------------------------------------------------------------
190
191 /**
192 * Test the setStartRule/setEndRule API calls.
193 */
194 void
TestRuleAPI()195 TimeZoneTest::TestRuleAPI()
196 {
197 UErrorCode status = U_ZERO_ERROR;
198
199 UDate offset = 60*60*1000*1.75; // Pick a weird offset
200 SimpleTimeZone *zone = new SimpleTimeZone((int32_t)offset, "TestZone");
201 if (zone->useDaylightTime()) errln("FAIL: useDaylightTime should return false");
202
203 // Establish our expected transition times. Do this with a non-DST
204 // calendar with the (above) declared local offset.
205 GregorianCalendar *gc = new GregorianCalendar(*zone, status);
206 if (failure(status, "new GregorianCalendar", true)) return;
207 gc->clear();
208 gc->set(1990, UCAL_MARCH, 1);
209 UDate marchOneStd = gc->getTime(status); // Local Std time midnight
210 gc->clear();
211 gc->set(1990, UCAL_JULY, 1);
212 UDate julyOneStd = gc->getTime(status); // Local Std time midnight
213 if (failure(status, "GregorianCalendar::getTime")) return;
214
215 // Starting and ending hours, WALL TIME
216 int32_t startHour = (int32_t)(2.25 * 3600000);
217 int32_t endHour = (int32_t)(3.5 * 3600000);
218
219 zone->setStartRule(UCAL_MARCH, 1, 0, startHour, status);
220 zone->setEndRule (UCAL_JULY, 1, 0, endHour, status);
221
222 delete gc;
223 gc = new GregorianCalendar(*zone, status);
224 if (failure(status, "new GregorianCalendar")) return;
225
226 UDate marchOne = marchOneStd + startHour;
227 UDate julyOne = julyOneStd + endHour - 3600000; // Adjust from wall to Std time
228
229 UDate expMarchOne = 636251400000.0;
230 if (marchOne != expMarchOne)
231 {
232 errln((UnicodeString)"FAIL: Expected start computed as " + marchOne +
233 " = " + dateToString(marchOne));
234 logln((UnicodeString)" Should be " + expMarchOne +
235 " = " + dateToString(expMarchOne));
236 }
237
238 UDate expJulyOne = 646793100000.0;
239 if (julyOne != expJulyOne)
240 {
241 errln((UnicodeString)"FAIL: Expected start computed as " + julyOne +
242 " = " + dateToString(julyOne));
243 logln((UnicodeString)" Should be " + expJulyOne +
244 " = " + dateToString(expJulyOne));
245 }
246
247 testUsingBinarySearch(*zone, date(90, UCAL_JANUARY, 1), date(90, UCAL_JUNE, 15), marchOne);
248 testUsingBinarySearch(*zone, date(90, UCAL_JUNE, 1), date(90, UCAL_DECEMBER, 31), julyOne);
249
250 if (zone->inDaylightTime(marchOne - 1000, status) ||
251 !zone->inDaylightTime(marchOne, status))
252 errln("FAIL: Start rule broken");
253 if (!zone->inDaylightTime(julyOne - 1000, status) ||
254 zone->inDaylightTime(julyOne, status))
255 errln("FAIL: End rule broken");
256
257 zone->setStartYear(1991);
258 if (zone->inDaylightTime(marchOne, status) ||
259 zone->inDaylightTime(julyOne - 1000, status))
260 errln("FAIL: Start year broken");
261
262 failure(status, "TestRuleAPI");
263 delete gc;
264 delete zone;
265 }
266
267 void
findTransition(const TimeZone & tz,UDate min,UDate max)268 TimeZoneTest::findTransition(const TimeZone& tz,
269 UDate min, UDate max) {
270 UErrorCode ec = U_ZERO_ERROR;
271 UnicodeString id,s;
272 UBool startsInDST = tz.inDaylightTime(min, ec);
273 if (failure(ec, "TimeZone::inDaylightTime")) return;
274 if (tz.inDaylightTime(max, ec) == startsInDST) {
275 logln("Error: " + tz.getID(id) + ".inDaylightTime(" + dateToString(min) + ") = " + (startsInDST?"true":"false") +
276 ", inDaylightTime(" + dateToString(max) + ") = " + (startsInDST?"true":"false"));
277 return;
278 }
279 if (failure(ec, "TimeZone::inDaylightTime")) return;
280 while ((max - min) > INTERVAL) {
281 UDate mid = (min + max) / 2;
282 if (tz.inDaylightTime(mid, ec) == startsInDST) {
283 min = mid;
284 } else {
285 max = mid;
286 }
287 if (failure(ec, "TimeZone::inDaylightTime")) return;
288 }
289 min = 1000.0 * uprv_floor(min/1000.0);
290 max = 1000.0 * uprv_floor(max/1000.0);
291 logln(tz.getID(id) + " Before: " + min/1000 + " = " +
292 dateToString(min,s,tz));
293 logln(tz.getID(id) + " After: " + max/1000 + " = " +
294 dateToString(max,s,tz));
295 }
296
297 void
testUsingBinarySearch(const TimeZone & tz,UDate min,UDate max,UDate expectedBoundary)298 TimeZoneTest::testUsingBinarySearch(const TimeZone& tz,
299 UDate min, UDate max,
300 UDate expectedBoundary)
301 {
302 UErrorCode status = U_ZERO_ERROR;
303 UBool startsInDST = tz.inDaylightTime(min, status);
304 if (failure(status, "TimeZone::inDaylightTime")) return;
305 if (tz.inDaylightTime(max, status) == startsInDST) {
306 logln("Error: inDaylightTime(" + dateToString(max) + ") != " + ((!startsInDST)?"true":"false"));
307 return;
308 }
309 if (failure(status, "TimeZone::inDaylightTime")) return;
310 while ((max - min) > INTERVAL) {
311 UDate mid = (min + max) / 2;
312 if (tz.inDaylightTime(mid, status) == startsInDST) {
313 min = mid;
314 } else {
315 max = mid;
316 }
317 if (failure(status, "TimeZone::inDaylightTime")) return;
318 }
319 logln(UnicodeString("Binary Search Before: ") + uprv_floor(0.5 + min) + " = " + dateToString(min));
320 logln(UnicodeString("Binary Search After: ") + uprv_floor(0.5 + max) + " = " + dateToString(max));
321 UDate mindelta = expectedBoundary - min;
322 UDate maxdelta = max - expectedBoundary;
323 if (mindelta >= 0 &&
324 mindelta <= INTERVAL &&
325 maxdelta >= 0 &&
326 maxdelta <= INTERVAL)
327 logln(UnicodeString("PASS: Expected bdry: ") + expectedBoundary + " = " + dateToString(expectedBoundary));
328 else
329 errln(UnicodeString("FAIL: Expected bdry: ") + expectedBoundary + " = " + dateToString(expectedBoundary));
330 }
331
332 const UDate TimeZoneTest::INTERVAL = 100;
333
334 // ---------------------------------------------------------------------------------
335
336 // -------------------------------------
337
338 /**
339 * Test the offset of the PRT timezone.
340 */
341 void
TestPRTOffset()342 TimeZoneTest::TestPRTOffset()
343 {
344 TimeZone* tz = TimeZone::createTimeZone("PRT");
345 if (tz == nullptr) {
346 errln("FAIL: TimeZone(PRT) is null");
347 }
348 else {
349 int32_t expectedHour = -4;
350 double expectedOffset = (((double)expectedHour) * millisPerHour);
351 double foundOffset = tz->getRawOffset();
352 int32_t foundHour = (int32_t)foundOffset / millisPerHour;
353 if (expectedOffset != foundOffset) {
354 dataerrln("FAIL: Offset for PRT should be %d, found %d", expectedHour, foundHour);
355 } else {
356 logln("PASS: Offset for PRT should be %d, found %d", expectedHour, foundHour);
357 }
358 }
359 delete tz;
360 }
361
362 // -------------------------------------
363
364 /**
365 * Regress a specific bug with a sequence of API calls.
366 */
367 void
TestVariousAPI518()368 TimeZoneTest::TestVariousAPI518()
369 {
370 UErrorCode status = U_ZERO_ERROR;
371 TimeZone* time_zone = TimeZone::createTimeZone("PST");
372 UDate d = date(97, UCAL_APRIL, 30);
373 UnicodeString str;
374 logln("The timezone is " + time_zone->getID(str));
375 if (!time_zone->inDaylightTime(d, status)) dataerrln("FAIL: inDaylightTime returned false");
376 if (failure(status, "TimeZone::inDaylightTime", true)) return;
377 if (!time_zone->useDaylightTime()) dataerrln("FAIL: useDaylightTime returned false");
378 if (time_zone->getRawOffset() != - 8 * millisPerHour) dataerrln("FAIL: getRawOffset returned wrong value");
379 GregorianCalendar *gc = new GregorianCalendar(status);
380 if (U_FAILURE(status)) { errln("FAIL: Couldn't create GregorianCalendar"); return; }
381 gc->setTime(d, status);
382 if (U_FAILURE(status)) { errln("FAIL: GregorianCalendar::setTime failed"); return; }
383 if (time_zone->getOffset(gc->AD, gc->get(UCAL_YEAR, status), gc->get(UCAL_MONTH, status),
384 gc->get(UCAL_DATE, status), (uint8_t)gc->get(UCAL_DAY_OF_WEEK, status), 0, status) != - 7 * millisPerHour)
385 dataerrln("FAIL: getOffset returned wrong value");
386 if (U_FAILURE(status)) { errln("FAIL: GregorianCalendar::set failed"); return; }
387 delete gc;
388 delete time_zone;
389 }
390
391 // -------------------------------------
392
393 /**
394 * Test the call which retrieves the available IDs.
395 */
396 void
TestGetAvailableIDs913()397 TimeZoneTest::TestGetAvailableIDs913()
398 {
399 UErrorCode ec = U_ZERO_ERROR;
400 int32_t i;
401
402 #ifdef U_USE_TIMEZONE_OBSOLETE_2_8
403 // Test legacy API -- remove these tests when the corresponding API goes away (duh)
404 int32_t numIDs = -1;
405 const UnicodeString** ids = TimeZone::createAvailableIDs(numIDs);
406 if (ids == 0 || numIDs < 1) {
407 errln("FAIL: createAvailableIDs()");
408 } else {
409 UnicodeString buf("TimeZone::createAvailableIDs() = { ");
410 for(i=0; i<numIDs; ++i) {
411 if (i) buf.append(", ");
412 buf.append(*ids[i]);
413 }
414 buf.append(" } ");
415 logln(buf + numIDs);
416 // we own the array; the caller owns the contained strings (yuck)
417 uprv_free(ids);
418 }
419
420 numIDs = -1;
421 ids = TimeZone::createAvailableIDs(-8*U_MILLIS_PER_HOUR, numIDs);
422 if (ids == 0 || numIDs < 1) {
423 errln("FAIL: createAvailableIDs(-8:00)");
424 } else {
425 UnicodeString buf("TimeZone::createAvailableIDs(-8:00) = { ");
426 for(i=0; i<numIDs; ++i) {
427 if (i) buf.append(", ");
428 buf.append(*ids[i]);
429 }
430 buf.append(" } ");
431 logln(buf + numIDs);
432 // we own the array; the caller owns the contained strings (yuck)
433 uprv_free(ids);
434 }
435 numIDs = -1;
436 ids = TimeZone::createAvailableIDs("US", numIDs);
437 if (ids == 0 || numIDs < 1) {
438 errln("FAIL: createAvailableIDs(US) ids=%d, numIDs=%d", ids, numIDs);
439 } else {
440 UnicodeString buf("TimeZone::createAvailableIDs(US) = { ");
441 for(i=0; i<numIDs; ++i) {
442 if (i) buf.append(", ");
443 buf.append(*ids[i]);
444 }
445 buf.append(" } ");
446 logln(buf + numIDs);
447 // we own the array; the caller owns the contained strings (yuck)
448 uprv_free(ids);
449 }
450 #endif
451
452 UnicodeString str;
453 UnicodeString buf(u"TimeZone::createEnumeration() = { ");
454 int32_t s_length;
455 StringEnumeration* s = TimeZone::createEnumeration(ec);
456 LocalPointer<StringEnumeration> tmp1(TimeZone::createEnumeration(), ec);
457 if (U_FAILURE(ec) || s == nullptr) {
458 dataerrln("Unable to create TimeZone enumeration");
459 return;
460 }
461 s_length = s->count(ec);
462 if (s_length != tmp1->count(ec)) {
463 errln("TimeZone::createEnumeration() with no status args returns a different count.");
464 }
465 for (i = 0; i < s_length;++i) {
466 if (i > 0) buf += ", ";
467 if ((i & 1) == 0) {
468 buf += *s->snext(ec);
469 } else {
470 buf += UnicodeString(s->next(nullptr, ec), "");
471 }
472
473 if((i % 5) == 4) {
474 // replace s with a clone of itself
475 StringEnumeration *s2 = s->clone();
476 if(s2 == nullptr || s_length != s2->count(ec)) {
477 errln("TimezoneEnumeration.clone() failed");
478 } else {
479 delete s;
480 s = s2;
481 }
482 }
483 }
484 buf += " };";
485 logln(buf);
486
487 /* Confirm that the following zones can be retrieved: The first
488 * zone, the last zone, and one in-between. This tests the binary
489 * search through the system zone data.
490 */
491 s->reset(ec);
492 int32_t middle = s_length/2;
493 for (i=0; i<s_length; ++i) {
494 const UnicodeString* id = s->snext(ec);
495 if (i==0 || i==middle || i==(s_length-1)) {
496 TimeZone *z = TimeZone::createTimeZone(*id);
497 if (z == nullptr) {
498 errln(UnicodeString("FAIL: createTimeZone(") +
499 *id + ") -> 0");
500 } else if (z->getID(str) != *id) {
501 errln(UnicodeString("FAIL: createTimeZone(") +
502 *id + ") -> zone " + str);
503 } else {
504 logln(UnicodeString("OK: createTimeZone(") +
505 *id + ") succeeded");
506 }
507 delete z;
508 }
509 }
510 delete s;
511
512 buf.truncate(0);
513 buf += "TimeZone::createEnumeration(GMT+01:00) = { ";
514
515 s = TimeZone::createEnumerationForRawOffset(1 * U_MILLIS_PER_HOUR, ec);
516 LocalPointer<StringEnumeration> tmp2(TimeZone::createEnumeration(1 * U_MILLIS_PER_HOUR), ec);
517 if (U_FAILURE(ec)) {
518 dataerrln("Unable to create TimeZone enumeration for GMT+1");
519 return;
520 }
521 s_length = s->count(ec);
522 if (s_length != tmp2->count(ec)) {
523 errln("TimeZone::createEnumeration(GMT+01:00) with no status args returns a different count.");
524 }
525 for (i = 0; i < s_length;++i) {
526 if (i > 0) buf += ", ";
527 buf += *s->snext(ec);
528 }
529 delete s;
530 buf += " };";
531 logln(buf);
532
533
534 buf.truncate(0);
535 buf += "TimeZone::createEnumeration(US) = { ";
536
537 s = TimeZone::createEnumerationForRegion("US", ec);
538 LocalPointer<StringEnumeration> tmp3(TimeZone::createEnumeration("US"), ec);
539 if (U_FAILURE(ec)) {
540 dataerrln("Unable to create TimeZone enumeration for US");
541 return;
542 }
543 s_length = s->count(ec);
544 if (s_length != tmp3->count(ec)) {
545 errln("TimeZone::createEnumeration(\"US\") with no status args returns a different count.");
546 }
547 for (i = 0; i < s_length; ++i) {
548 if (i > 0) buf += ", ";
549 buf += *s->snext(ec);
550 }
551 buf += " };";
552 logln(buf);
553
554 TimeZone *tz = TimeZone::createTimeZone("PST");
555 if (tz != nullptr) logln("getTimeZone(PST) = " + tz->getID(str));
556 else errln("FAIL: getTimeZone(PST) = null");
557 delete tz;
558 tz = TimeZone::createTimeZone("America/Los_Angeles");
559 if (tz != nullptr) logln("getTimeZone(America/Los_Angeles) = " + tz->getID(str));
560 else errln("FAIL: getTimeZone(PST) = null");
561 delete tz;
562
563 // @bug 4096694
564 tz = TimeZone::createTimeZone("NON_EXISTENT");
565 UnicodeString temp;
566 if (tz == nullptr)
567 errln("FAIL: getTimeZone(NON_EXISTENT) = null");
568 else if (tz->getID(temp) != UCAL_UNKNOWN_ZONE_ID)
569 errln("FAIL: getTimeZone(NON_EXISTENT) = " + temp);
570 delete tz;
571
572 delete s;
573 }
574
575 void
TestGetAvailableIDsNew()576 TimeZoneTest::TestGetAvailableIDsNew()
577 {
578 UErrorCode ec = U_ZERO_ERROR;
579 StringEnumeration *any, *canonical, *canonicalLoc;
580 StringEnumeration *any_US, *canonical_US, *canonicalLoc_US;
581 StringEnumeration *any_W5, *any_CA_W5;
582 StringEnumeration *any_US_E14;
583 int32_t rawOffset;
584 const UnicodeString *id1, *id2;
585 UnicodeString canonicalID;
586 UBool isSystemID;
587 char region[4] = {0};
588 int32_t zoneCount;
589
590 any = canonical = canonicalLoc = any_US = canonical_US = canonicalLoc_US = any_W5 = any_CA_W5 = any_US_E14 = nullptr;
591
592 any = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_ANY, nullptr, nullptr, ec);
593 if (U_FAILURE(ec)) {
594 dataerrln("Failed to create enumeration for ANY");
595 goto cleanup;
596 }
597
598 canonical = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL, nullptr, nullptr, ec);
599 if (U_FAILURE(ec)) {
600 errln("Failed to create enumeration for CANONICAL");
601 goto cleanup;
602 }
603
604 canonicalLoc = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL_LOCATION, nullptr, nullptr, ec);
605 if (U_FAILURE(ec)) {
606 errln("Failed to create enumeration for CANONICALLOC");
607 goto cleanup;
608 }
609
610 any_US = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_ANY, "US", nullptr, ec);
611 if (U_FAILURE(ec)) {
612 errln("Failed to create enumeration for ANY_US");
613 goto cleanup;
614 }
615
616 canonical_US = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL, "US", nullptr, ec);
617 if (U_FAILURE(ec)) {
618 errln("Failed to create enumeration for CANONICAL_US");
619 goto cleanup;
620 }
621
622 canonicalLoc_US = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL_LOCATION, "US", nullptr, ec);
623 if (U_FAILURE(ec)) {
624 errln("Failed to create enumeration for CANONICALLOC_US");
625 goto cleanup;
626 }
627
628 rawOffset = (-5)*60*60*1000;
629 any_W5 = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_ANY, nullptr, &rawOffset, ec);
630 if (U_FAILURE(ec)) {
631 errln("Failed to create enumeration for ANY_W5");
632 goto cleanup;
633 }
634
635 any_CA_W5 = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_ANY, "CA", &rawOffset, ec);
636 if (U_FAILURE(ec)) {
637 errln("Failed to create enumeration for ANY_CA_W5");
638 goto cleanup;
639 }
640
641 rawOffset = 14*60*60*1000;
642 any_US_E14 = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_ANY, "US", &rawOffset, ec);
643 if (U_FAILURE(ec)) {
644 errln("Failed to create enumeration for ANY_US_E14");
645 goto cleanup;
646 }
647
648 checkContainsAll(any, "ANY", canonical, "CANONICAL");
649 checkContainsAll(canonical, "CANONICAL", canonicalLoc, "CANONICALLOC");
650
651 checkContainsAll(any, "ANY", any_US, "ANY_US");
652 checkContainsAll(canonical, "CANONICAL", canonical_US, "CANONICAL_US");
653 checkContainsAll(canonicalLoc, "CANONICALLOC", canonicalLoc_US, "CANONICALLOC_US");
654
655 checkContainsAll(any_US, "ANY_US", canonical_US, "CANONICAL_US");
656 checkContainsAll(canonical_US, "CANONICAL_US", canonicalLoc_US, "CANONICALLOC_US");
657
658 checkContainsAll(any, "ANY", any_W5, "ANY_W5");
659 checkContainsAll(any_W5, "ANY_W5", any_CA_W5, "ANY_CA_W5");
660
661 // And ID in any set, but not in canonical set must not be a canonical ID
662 any->reset(ec);
663 while ((id1 = any->snext(ec)) != nullptr) {
664 UBool found = false;
665 canonical->reset(ec);
666 while ((id2 = canonical->snext(ec)) != nullptr) {
667 if (*id1 == *id2) {
668 found = true;
669 break;
670 }
671 }
672 if (U_FAILURE(ec)) {
673 break;
674 }
675 if (!found) {
676 TimeZone::getCanonicalID(*id1, canonicalID, isSystemID, ec);
677 if (U_FAILURE(ec)) {
678 break;
679 }
680 if (*id1 == canonicalID) {
681 errln((UnicodeString)"FAIL: canonicalID [" + *id1 + "] is not in CANONICAL");
682 }
683 if (!isSystemID) {
684 errln((UnicodeString)"FAIL: ANY contains non-system ID: " + *id1);
685 }
686 }
687 }
688 if (U_FAILURE(ec)) {
689 errln("Error checking IDs in ANY, but not in CANONICAL");
690 ec = U_ZERO_ERROR;
691 }
692
693 // canonical set must contains only canonical IDs
694 canonical->reset(ec);
695 while ((id1 = canonical->snext(ec)) != nullptr) {
696 TimeZone::getCanonicalID(*id1, canonicalID, isSystemID, ec);
697 if (U_FAILURE(ec)) {
698 break;
699 }
700 if (*id1 != canonicalID) {
701 errln((UnicodeString)"FAIL: CANONICAL contains non-canonical ID: " + *id1);
702 }
703 if (!isSystemID) {
704 errln((UnicodeString)"FAILE: CANONICAL contains non-system ID: " + *id1);
705 }
706 }
707 if (U_FAILURE(ec)) {
708 errln("Error checking IDs in CANONICAL");
709 ec = U_ZERO_ERROR;
710 }
711
712 // canonicalLoc set must contain only canonical location IDs
713 canonicalLoc->reset(ec);
714 while ((id1 = canonicalLoc->snext(ec)) != nullptr) {
715 TimeZone::getRegion(*id1, region, sizeof(region), ec);
716 if (U_FAILURE(ec)) {
717 break;
718 }
719 if (uprv_strcmp(region, "001") == 0) {
720 errln((UnicodeString)"FAIL: CANONICALLOC contains non location zone: " + *id1);
721 }
722 }
723 if (U_FAILURE(ec)) {
724 errln("Error checking IDs in CANONICALLOC");
725 ec = U_ZERO_ERROR;
726 }
727
728 // any_US must contain only US zones
729 any_US->reset(ec);
730 while ((id1 = any_US->snext(ec)) != nullptr) {
731 TimeZone::getRegion(*id1, region, sizeof(region), ec);
732 if (U_FAILURE(ec)) {
733 break;
734 }
735 if (uprv_strcmp(region, "US") != 0) {
736 errln((UnicodeString)"FAIL: ANY_US contains non-US zone ID: " + *id1);
737 }
738 }
739 if (U_FAILURE(ec)) {
740 errln("Error checking IDs in ANY_US");
741 ec = U_ZERO_ERROR;
742 }
743
744 // any_W5 must contain only GMT-05:00 zones
745 any_W5->reset(ec);
746 while ((id1 = any_W5->snext(ec)) != nullptr) {
747 TimeZone *tz = TimeZone::createTimeZone(*id1);
748 if (tz->getRawOffset() != (-5)*60*60*1000) {
749 errln((UnicodeString)"FAIL: ANY_W5 contains a zone whose offset is not -05:00: " + *id1);
750 }
751 delete tz;
752 }
753 if (U_FAILURE(ec)) {
754 errln("Error checking IDs in ANY_W5");
755 ec = U_ZERO_ERROR;
756 }
757
758 // No US zone swith GMT+14:00
759 zoneCount = any_US_E14->count(ec);
760 if (U_FAILURE(ec)) {
761 errln("Error checking IDs in ANY_US_E14");
762 ec = U_ZERO_ERROR;
763 } else if (zoneCount != 0) {
764 errln("FAIL: ANY_US_E14 must be empty");
765 }
766
767 cleanup:
768 delete any;
769 delete canonical;
770 delete canonicalLoc;
771 delete any_US;
772 delete canonical_US;
773 delete canonicalLoc_US;
774 delete any_W5;
775 delete any_CA_W5;
776 delete any_US_E14;
777 }
778
779 void
checkContainsAll(StringEnumeration * s1,const char * name1,StringEnumeration * s2,const char * name2)780 TimeZoneTest::checkContainsAll(StringEnumeration *s1, const char *name1,
781 StringEnumeration *s2, const char *name2)
782 {
783 UErrorCode ec = U_ZERO_ERROR;
784 const UnicodeString *id1, *id2;
785
786 s2->reset(ec);
787
788 while ((id2 = s2->snext(ec)) != nullptr) {
789 UBool found = false;
790 s1->reset(ec);
791 while ((id1 = s1->snext(ec)) != nullptr) {
792 if (*id1 == *id2) {
793 found = true;
794 break;
795 }
796 }
797 if (!found) {
798 errln((UnicodeString)"FAIL: " + name1 + "does not contain "
799 + *id2 + " in " + name2);
800 }
801 }
802
803 if (U_FAILURE(ec)) {
804 errln((UnicodeString)"Error checkContainsAll for " + name1 + " - " + name2);
805 }
806 }
807
808 /**
809 * NOTE: As of ICU 2.8, this test confirms that the "tz.alias"
810 * file, used to build ICU alias zones, is working. It also
811 * looks at some genuine Olson compatibility IDs. [aliu]
812 *
813 * This test is problematic. It should really just confirm that
814 * the list of compatibility zone IDs exist and are somewhat
815 * meaningful (that is, they aren't all aliases of GMT). It goes a
816 * bit further -- it hard-codes expectations about zone behavior,
817 * when in fact zones are redefined quite frequently. ICU's build
818 * process means that it is easy to update ICU to contain the
819 * latest Olson zone data, but if a zone tested here changes, then
820 * this test will fail. I have updated the test for 1999j data,
821 * but further updates will probably be required. Note that some
822 * of the concerts listed below no longer apply -- in particular,
823 * we do NOT overwrite real UNIX zones with 3-letter IDs. There
824 * are two points of overlap as of 1999j: MET and EET. These are
825 * both real UNIX zones, so we just use the official
826 * definition. This test has been updated to reflect this.
827 * 12/3/99 aliu
828 *
829 * Added tests for additional zones and aliases from the icuzones file.
830 * Markus Scherer 2006-nov-06
831 *
832 * [srl - from java - 7/5/1998]
833 * @bug 4130885
834 * Certain short zone IDs, used since 1.1.x, are incorrect.
835 *
836 * The worst of these is:
837 *
838 * "CAT" (Central African Time) should be GMT+2:00, but instead returns a
839 * zone at GMT-1:00. The zone at GMT-1:00 should be called EGT, CVT, EGST,
840 * or AZOST, depending on which zone is meant, but in no case is it CAT.
841 *
842 * Other wrong zone IDs:
843 *
844 * ECT (European Central Time) GMT+1:00: ECT is Ecuador Time,
845 * GMT-5:00. European Central time is abbreviated CEST.
846 *
847 * SST (Solomon Island Time) GMT+11:00. SST is actually Samoa Standard Time,
848 * GMT-11:00. Solomon Island time is SBT.
849 *
850 * NST (New Zealand Time) GMT+12:00. NST is the abbreviation for
851 * Newfoundland Standard Time, GMT-3:30. New Zealanders use NZST.
852 *
853 * AST (Alaska Standard Time) GMT-9:00. [This has already been noted in
854 * another bug.] It should be "AKST". AST is Atlantic Standard Time,
855 * GMT-4:00.
856 *
857 * PNT (Phoenix Time) GMT-7:00. PNT usually means Pitcairn Time,
858 * GMT-8:30. There is no standard abbreviation for Phoenix time, as distinct
859 * from MST with daylight savings.
860 *
861 * In addition to these problems, a number of zones are FAKE. That is, they
862 * don't match what people use in the real world.
863 *
864 * FAKE zones:
865 *
866 * EET (should be EEST)
867 * ART (should be EEST)
868 * MET (should be IRST)
869 * NET (should be AMST)
870 * PLT (should be PKT)
871 * BST (should be BDT)
872 * VST (should be ICT)
873 * CTT (should be CST) +
874 * ACT (should be CST) +
875 * AET (should be EST) +
876 * MIT (should be WST) +
877 * IET (should be EST) +
878 * PRT (should be AST) +
879 * CNT (should be NST)
880 * AGT (should be ARST)
881 * BET (should be EST) +
882 *
883 * + A zone with the correct name already exists and means something
884 * else. E.g., EST usually indicates the US Eastern zone, so it cannot be
885 * used for Brazil (BET).
886 */
TestShortZoneIDs()887 void TimeZoneTest::TestShortZoneIDs()
888 {
889 int32_t i;
890 // Create a small struct to hold the array
891 struct
892 {
893 const char *id;
894 int32_t offset;
895 UBool daylight;
896 }
897 kReferenceList [] =
898 {
899 {"HST", -600, false}, // Olson northamerica -10:00
900 {"AST", -540, true}, // ICU Link - America/Anchorage
901 {"PST", -480, true}, // ICU Link - America/Los_Angeles
902 {"PNT", -420, false}, // ICU Link - America/Phoenix
903 {"MST", -420, false}, // updated Aug 2003 aliu
904 {"CST", -360, true}, // Olson northamerica -7:00
905 {"IET", -300, true}, // ICU Link - America/Indiana/Indianapolis
906 {"EST", -300, false}, // Olson northamerica -5:00
907 {"PRT", -240, false}, // ICU Link - America/Puerto_Rico
908 {"CNT", -210, true}, // ICU Link - America/St_Johns
909 {"AGT", -180, false}, // ICU Link - America/Argentina/Buenos_Aires
910 // Per https://mm.icann.org/pipermail/tz-announce/2019-July/000056.html
911 // Brazil has canceled DST and will stay on standard time indefinitely.
912 {"BET", -180, false}, // ICU Link - America/Sao_Paulo
913 {"GMT", 0, false}, // Olson etcetera Link - Etc/GMT
914 {"UTC", 0, false}, // Olson etcetera 0
915 {"ECT", 60, true}, // ICU Link - Europe/Paris
916 {"MET", 60, true}, // Olson europe 1:00 C-Eur
917 {"CAT", 120, false}, // ICU Link - Africa/Maputo
918 {"ART", 120, false}, // ICU Link - Africa/Cairo
919 {"EET", 120, true}, // Olson europe 2:00 EU
920 {"EAT", 180, false}, // ICU Link - Africa/Addis_Ababa
921 {"NET", 240, false}, // ICU Link - Asia/Yerevan
922 {"PLT", 300, false}, // ICU Link - Asia/Karachi
923 {"IST", 330, false}, // ICU Link - Asia/Kolkata
924 {"BST", 360, false}, // ICU Link - Asia/Dhaka
925 {"VST", 420, false}, // ICU Link - Asia/Ho_Chi_Minh
926 {"CTT", 480, false}, // ICU Link - Asia/Shanghai
927 {"JST", 540, false}, // ICU Link - Asia/Tokyo
928 {"ACT", 570, false}, // ICU Link - Australia/Darwin
929 {"AET", 600, true}, // ICU Link - Australia/Sydney
930 {"SST", 660, false}, // ICU Link - Pacific/Guadalcanal
931 {"NST", 720, true}, // ICU Link - Pacific/Auckland
932 {"MIT", 780, false}, // ICU Link - Pacific/Apia
933
934 {"Etc/Unknown", 0, false}, // CLDR
935
936 {"SystemV/AST4ADT", -240, true},
937 {"SystemV/EST5EDT", -300, true},
938 {"SystemV/CST6CDT", -360, true},
939 {"SystemV/MST7MDT", -420, true},
940 {"SystemV/PST8PDT", -480, true},
941 {"SystemV/YST9YDT", -540, true},
942 {"SystemV/AST4", -240, false},
943 {"SystemV/EST5", -300, false},
944 {"SystemV/CST6", -360, false},
945 {"SystemV/MST7", -420, false},
946 {"SystemV/PST8", -480, false},
947 {"SystemV/YST9", -540, false},
948 {"SystemV/HST10", -600, false},
949
950 {"",0,false}
951 };
952
953 for(i=0;kReferenceList[i].id[0];i++) {
954 UnicodeString itsID(kReferenceList[i].id);
955 UBool ok = true;
956 // Check existence.
957 TimeZone *tz = TimeZone::createTimeZone(itsID);
958 if (!tz || (kReferenceList[i].offset != 0 && *tz == *TimeZone::getGMT())) {
959 errln("FAIL: Time Zone " + itsID + " does not exist!");
960 continue;
961 }
962
963 // Check daylight usage.
964 UBool usesDaylight = tz->useDaylightTime();
965 if (usesDaylight != kReferenceList[i].daylight) {
966 if (!isDevelopmentBuild) {
967 logln("Warning: Time Zone " + itsID + " use daylight is " +
968 (usesDaylight?"true":"false") +
969 " but it should be " +
970 ((kReferenceList[i].daylight)?"true":"false"));
971 } else if (!(itsID==UnicodeString(u"ART",-1) && logKnownIssue("ICU-22436", "Wrong DST status for time zone ART"))) {
972 dataerrln("FAIL: Time Zone " + itsID + " use daylight is " +
973 (usesDaylight?"true":"false") +
974 " but it should be " +
975 ((kReferenceList[i].daylight)?"true":"false"));
976 }
977 ok = false;
978 }
979
980 // Check offset
981 int32_t offsetInMinutes = tz->getRawOffset()/60000;
982 if (offsetInMinutes != kReferenceList[i].offset) {
983 if (!isDevelopmentBuild) {
984 logln("FAIL: Time Zone " + itsID + " raw offset is " +
985 offsetInMinutes +
986 " but it should be " + kReferenceList[i].offset);
987 } else {
988 dataerrln("FAIL: Time Zone " + itsID + " raw offset is " +
989 offsetInMinutes +
990 " but it should be " + kReferenceList[i].offset);
991 }
992 ok = false;
993 }
994
995 if (ok) {
996 logln("OK: " + itsID +
997 " useDaylightTime() & getRawOffset() as expected");
998 }
999 delete tz;
1000 }
1001
1002
1003 // OK now test compat
1004 logln("Testing for compatibility zones");
1005
1006 const char* compatibilityMap[] = {
1007 // This list is copied from tz.alias. If tz.alias
1008 // changes, this list must be updated. Current as of Mar 2007
1009 "ACT", "Australia/Darwin",
1010 "AET", "Australia/Sydney",
1011 "AGT", "America/Buenos_Aires",
1012 "ART", "Africa/Cairo",
1013 "AST", "America/Anchorage",
1014 "BET", "America/Sao_Paulo",
1015 "BST", "Asia/Dhaka", // # spelling changed in 2000h; was Asia/Dacca
1016 "CAT", "Africa/Maputo",
1017 "CNT", "America/St_Johns",
1018 "CST", "America/Chicago",
1019 "CTT", "Asia/Shanghai",
1020 "EAT", "Africa/Addis_Ababa",
1021 "ECT", "Europe/Paris",
1022 // EET Europe/Istanbul # EET is a standard UNIX zone
1023 // "EST", "America/New_York", # Defined as -05:00
1024 // "HST", "Pacific/Honolulu", # Defined as -10:00
1025 "IET", "America/Indianapolis",
1026 "IST", "Asia/Calcutta",
1027 "JST", "Asia/Tokyo",
1028 // MET Asia/Tehran # MET is a standard UNIX zone
1029 "MIT", "Pacific/Apia",
1030 // "MST", "America/Denver", # Defined as -07:00
1031 "NET", "Asia/Yerevan",
1032 "NST", "Pacific/Auckland",
1033 "PLT", "Asia/Karachi",
1034 "PNT", "America/Phoenix",
1035 "PRT", "America/Puerto_Rico",
1036 "PST", "America/Los_Angeles",
1037 "SST", "Pacific/Guadalcanal",
1038 "UTC", "Etc/GMT",
1039 "VST", "Asia/Saigon",
1040 "","",""
1041 };
1042
1043 for (i=0;*compatibilityMap[i];i+=2) {
1044 UnicodeString itsID;
1045
1046 const char *zone1 = compatibilityMap[i];
1047 const char *zone2 = compatibilityMap[i+1];
1048
1049 TimeZone *tz1 = TimeZone::createTimeZone(zone1);
1050 TimeZone *tz2 = TimeZone::createTimeZone(zone2);
1051
1052 if (!tz1) {
1053 errln(UnicodeString("FAIL: Could not find short ID zone ") + zone1);
1054 }
1055 if (!tz2) {
1056 errln(UnicodeString("FAIL: Could not find long ID zone ") + zone2);
1057 }
1058
1059 if (tz1 && tz2) {
1060 // make NAME same so comparison will only look at the rest
1061 tz2->setID(tz1->getID(itsID));
1062
1063 if (*tz1 != *tz2) {
1064 errln("FAIL: " + UnicodeString(zone1) +
1065 " != " + UnicodeString(zone2));
1066 } else {
1067 logln("OK: " + UnicodeString(zone1) +
1068 " == " + UnicodeString(zone2));
1069 }
1070 }
1071
1072 delete tz1;
1073 delete tz2;
1074 }
1075 }
1076
1077
1078 /**
1079 * Utility function for TestCustomParse
1080 */
formatOffset(int32_t offset,UnicodeString & rv)1081 UnicodeString& TimeZoneTest::formatOffset(int32_t offset, UnicodeString &rv) {
1082 rv.remove();
1083 char16_t sign = 0x002B;
1084 if (offset < 0) {
1085 sign = 0x002D;
1086 offset = -offset;
1087 }
1088
1089 int32_t s = offset % 60;
1090 offset /= 60;
1091 int32_t m = offset % 60;
1092 int32_t h = offset / 60;
1093
1094 rv += (char16_t)(sign);
1095 if (h >= 10) {
1096 rv += (char16_t)(0x0030 + (h/10));
1097 } else {
1098 rv += (char16_t)0x0030;
1099 }
1100 rv += (char16_t)(0x0030 + (h%10));
1101
1102 rv += (char16_t)0x003A; /* ':' */
1103 if (m >= 10) {
1104 rv += (char16_t)(0x0030 + (m/10));
1105 } else {
1106 rv += (char16_t)0x0030;
1107 }
1108 rv += (char16_t)(0x0030 + (m%10));
1109
1110 if (s) {
1111 rv += (char16_t)0x003A; /* ':' */
1112 if (s >= 10) {
1113 rv += (char16_t)(0x0030 + (s/10));
1114 } else {
1115 rv += (char16_t)0x0030;
1116 }
1117 rv += (char16_t)(0x0030 + (s%10));
1118 }
1119 return rv;
1120 }
1121
1122 /**
1123 * Utility function for TestCustomParse, generating time zone ID
1124 * string for the give offset.
1125 */
formatTZID(int32_t offset,UnicodeString & rv)1126 UnicodeString& TimeZoneTest::formatTZID(int32_t offset, UnicodeString &rv) {
1127 rv.remove();
1128 char16_t sign = 0x002B;
1129 if (offset < 0) {
1130 sign = 0x002D;
1131 offset = -offset;
1132 }
1133
1134 int32_t s = offset % 60;
1135 offset /= 60;
1136 int32_t m = offset % 60;
1137 int32_t h = offset / 60;
1138
1139 rv += "GMT";
1140 rv += (char16_t)(sign);
1141 if (h >= 10) {
1142 rv += (char16_t)(0x0030 + (h/10));
1143 } else {
1144 rv += (char16_t)0x0030;
1145 }
1146 rv += (char16_t)(0x0030 + (h%10));
1147 rv += (char16_t)0x003A;
1148 if (m >= 10) {
1149 rv += (char16_t)(0x0030 + (m/10));
1150 } else {
1151 rv += (char16_t)0x0030;
1152 }
1153 rv += (char16_t)(0x0030 + (m%10));
1154
1155 if (s) {
1156 rv += (char16_t)0x003A;
1157 if (s >= 10) {
1158 rv += (char16_t)(0x0030 + (s/10));
1159 } else {
1160 rv += (char16_t)0x0030;
1161 }
1162 rv += (char16_t)(0x0030 + (s%10));
1163 }
1164 return rv;
1165 }
1166
1167 /**
1168 * As part of the VM fix (see CCC approved RFE 4028006, bug
1169 * 4044013), TimeZone.getTimeZone() has been modified to recognize
1170 * generic IDs of the form GMT[+-]hh:mm, GMT[+-]hhmm, and
1171 * GMT[+-]hh. Test this behavior here.
1172 *
1173 * @bug 4044013
1174 */
TestCustomParse()1175 void TimeZoneTest::TestCustomParse()
1176 {
1177 int32_t i;
1178 const int32_t kUnparseable = 604800; // the number of seconds in a week. More than any offset should be.
1179
1180 struct
1181 {
1182 const char16_t *customId;
1183 int32_t expectedOffset;
1184 }
1185 kData[] =
1186 {
1187 // ID Expected offset in seconds
1188 {u"GMT", kUnparseable}, //Isn't custom. [returns normal GMT]
1189 {u"GMT-YOUR.AD.HERE", kUnparseable},
1190 {u"GMT0", kUnparseable},
1191 {u"GMT+0", (0)},
1192 {u"GMT+1", (1*60*60)},
1193 {u"GMT-0030", (-30*60)},
1194 {u"GMT+15:99", kUnparseable},
1195 {u"GMT+", kUnparseable},
1196 {u"GMT-", kUnparseable},
1197 {u"GMT+0:", kUnparseable},
1198 {u"GMT-:", kUnparseable},
1199 {u"GMT-YOUR.AD.HERE", kUnparseable},
1200 {u"GMT+0010", (10*60)}, // Interpret this as 00:10
1201 {u"GMT-10", (-10*60*60)},
1202 {u"GMT+30", kUnparseable},
1203 {u"GMT-3:30", (-(3*60+30)*60)},
1204 {u"GMT-230", (-(2*60+30)*60)},
1205 {u"GMT+05:13:05", ((5*60+13)*60+5)},
1206 {u"GMT-71023", (-((7*60+10)*60+23))},
1207 {u"GMT+01:23:45:67", kUnparseable},
1208 {u"GMT+01:234", kUnparseable},
1209 {u"GMT-2:31:123", kUnparseable},
1210 {u"GMT+3:75", kUnparseable},
1211 {u"GMT-01010101", kUnparseable},
1212 {u"GMT-4E58", kUnparseable}, // ICU-22637
1213 {u"GMT-4e58", kUnparseable}, // ICU-22637
1214 {u"GMT-1E01", kUnparseable}, // ICU-22637
1215 {u"GMT-2E01", kUnparseable}, // ICU-22637
1216 {u"GMT-2e01", kUnparseable}, // ICU-22637
1217 {u"GMT-9e02", kUnparseable}, // ICU-22637
1218 {u"GMT-1e03", kUnparseable}, // ICU-22637
1219 {u"GMT-2e03", kUnparseable}, // ICU-22637
1220 {u"GMT-500M", kUnparseable}, // ICU-22637
1221 {u"GMT-500T", kUnparseable}, // ICU-22637
1222 {u"GMT-9E00", kUnparseable}, // ICU-22637
1223 {u"GMT-0X0F", kUnparseable}, // ICU-22637
1224 {u"GMT-0x0F", kUnparseable}, // ICU-22637
1225 {u"GMT-0x12", kUnparseable}, // ICU-22637
1226 {u"GMT-B111", kUnparseable}, // ICU-22637
1227 {u"GMT-b111", kUnparseable}, // ICU-22637
1228 {u"GMT-0b11", kUnparseable}, // ICU-22637
1229 {u"GMT-๑๒", (-((12*60)*60))}, // ICU-22637
1230 {u"GMT-๑๒:๓๔", (-((12*60+34)*60))}, // ICU-22637
1231 {u"GMT+๑๒:๓๔:๕๖", ((12*60+34)*60+56)}, // ICU-22637
1232 {nullptr, 0}
1233 };
1234
1235 for (i = 0; kData[i].customId != nullptr; i++) {
1236 UnicodeString id(kData[i].customId);
1237 int32_t exp = kData[i].expectedOffset;
1238 TimeZone *zone = TimeZone::createTimeZone(id);
1239 UnicodeString itsID, temp;
1240
1241 OlsonTimeZone *ozone = dynamic_cast<OlsonTimeZone *>(zone);
1242 if (ozone != nullptr) {
1243 logln(id + " -> Olson time zone");
1244 ozone->operator=(*ozone); // self-assignment should be a no-op
1245 } else {
1246 zone->getID(itsID);
1247 int32_t ioffset = zone->getRawOffset()/1000;
1248 UnicodeString offset, expectedID;
1249 formatOffset(ioffset, offset);
1250 formatTZID(ioffset, expectedID);
1251 logln(id + " -> " + itsID + " " + offset);
1252 if (exp == kUnparseable && itsID != UCAL_UNKNOWN_ZONE_ID) {
1253 errln("Expected parse failure for " + id +
1254 ", got offset of " + offset +
1255 ", id " + itsID);
1256 }
1257 // JDK 1.3 creates custom zones with the ID "Custom"
1258 // JDK 1.4 creates custom zones with IDs of the form "GMT+02:00"
1259 // ICU creates custom zones with IDs of the form "GMT+02:00"
1260 else if (exp != kUnparseable && (ioffset != exp || itsID != expectedID)) {
1261 dataerrln("Expected offset of " + formatOffset(exp, temp) +
1262 ", id " + expectedID +
1263 ", for " + id +
1264 ", got offset of " + offset +
1265 ", id " + itsID);
1266 }
1267 }
1268 delete zone;
1269 }
1270 }
1271
1272 void
TestAliasedNames()1273 TimeZoneTest::TestAliasedNames()
1274 {
1275 struct {
1276 const char *from;
1277 const char *to;
1278 } kData[] = {
1279 /* Generated by org.unicode.cldr.tool.CountItems */
1280
1281 /* zoneID, canonical zoneID */
1282 {"Africa/Asmara", "Africa/Addis_Ababa"},
1283 {"Africa/Timbuktu", "Africa/Abidjan"},
1284 {"America/Argentina/Buenos_Aires", "America/Buenos_Aires"},
1285 {"America/Argentina/Catamarca", "America/Catamarca"},
1286 {"America/Argentina/ComodRivadavia", "America/Catamarca"},
1287 {"America/Argentina/Cordoba", "America/Cordoba"},
1288 {"America/Argentina/Jujuy", "America/Jujuy"},
1289 {"America/Argentina/Mendoza", "America/Mendoza"},
1290 {"America/Atka", "America/Adak"},
1291 {"America/Ensenada", "America/Tijuana"},
1292 {"America/Fort_Wayne", "America/Indianapolis"},
1293 {"America/Indiana/Indianapolis", "America/Indianapolis"},
1294 {"America/Kentucky/Louisville", "America/Louisville"},
1295 {"America/Knox_IN", "America/Indiana/Knox"},
1296 {"America/Porto_Acre", "America/Rio_Branco"},
1297 {"America/Rosario", "America/Cordoba"},
1298 {"America/Shiprock", "America/Denver"},
1299 {"America/Virgin", "America/Anguilla"},
1300 {"Antarctica/South_Pole", "Antarctica/McMurdo"},
1301 {"Asia/Ashkhabad", "Asia/Ashgabat"},
1302 {"Asia/Chongqing", "Asia/Shanghai"},
1303 {"Asia/Chungking", "Asia/Shanghai"},
1304 {"Asia/Dacca", "Asia/Dhaka"},
1305 {"Asia/Harbin", "Asia/Shanghai"},
1306 {"Asia/Ho_Chi_Minh", "Asia/Saigon"},
1307 {"Asia/Istanbul", "Europe/Istanbul"},
1308 {"Asia/Kashgar", "Asia/Urumqi"},
1309 {"Asia/Kathmandu", "Asia/Katmandu"},
1310 {"Asia/Kolkata", "Asia/Calcutta"},
1311 {"Asia/Macao", "Asia/Macau"},
1312 {"Asia/Tel_Aviv", "Asia/Jerusalem"},
1313 {"Asia/Thimbu", "Asia/Thimphu"},
1314 {"Asia/Ujung_Pandang", "Asia/Makassar"},
1315 {"Asia/Ulan_Bator", "Asia/Ulaanbaatar"},
1316 {"Atlantic/Faroe", "Atlantic/Faeroe"},
1317 {"Atlantic/Jan_Mayen", "Arctic/Longyearbyen"},
1318 {"Australia/ACT", "Australia/Sydney"},
1319 {"Australia/Canberra", "Australia/Sydney"},
1320 {"Australia/LHI", "Australia/Lord_Howe"},
1321 {"Australia/NSW", "Australia/Sydney"},
1322 {"Australia/North", "Australia/Darwin"},
1323 {"Australia/Queensland", "Australia/Brisbane"},
1324 {"Australia/South", "Australia/Adelaide"},
1325 {"Australia/Tasmania", "Australia/Hobart"},
1326 {"Australia/Victoria", "Australia/Melbourne"},
1327 {"Australia/West", "Australia/Perth"},
1328 {"Australia/Yancowinna", "Australia/Broken_Hill"},
1329 {"Brazil/Acre", "America/Rio_Branco"},
1330 {"Brazil/DeNoronha", "America/Noronha"},
1331 {"Brazil/East", "America/Sao_Paulo"},
1332 {"Brazil/West", "America/Manaus"},
1333 {"Canada/Atlantic", "America/Halifax"},
1334 {"Canada/Central", "America/Winnipeg"},
1335 {"Canada/East-Saskatchewan", "America/Regina"},
1336 {"Canada/Eastern", "America/Toronto"},
1337 {"Canada/Mountain", "America/Edmonton"},
1338 {"Canada/Newfoundland", "America/St_Johns"},
1339 {"Canada/Pacific", "America/Vancouver"},
1340 {"Canada/Saskatchewan", "America/Regina"},
1341 {"Canada/Yukon", "America/Whitehorse"},
1342 {"Chile/Continental", "America/Santiago"},
1343 {"Chile/EasterIsland", "Pacific/Easter"},
1344 {"Cuba", "America/Havana"},
1345 {"EST", "Etc/GMT+5"},
1346 {"Egypt", "Africa/Cairo"},
1347 {"Eire", "Europe/Dublin"},
1348 {"Etc/GMT+0", "Etc/GMT"},
1349 {"Etc/GMT-0", "Etc/GMT"},
1350 {"Etc/GMT0", "Etc/GMT"},
1351 {"Etc/Greenwich", "Etc/GMT"},
1352 {"Etc/UCT", "Etc/UTC"},
1353 {"Etc/Universal", "Etc/UTC"},
1354 {"Etc/Zulu", "Etc/UTC"},
1355 {"Europe/Belfast", "Europe/London"},
1356 {"Europe/Nicosia", "Asia/Nicosia"},
1357 {"Europe/Tiraspol", "Europe/Chisinau"},
1358 {"GB", "Europe/London"},
1359 {"GB-Eire", "Europe/London"},
1360 {"GMT", "Etc/GMT"},
1361 {"GMT+0", "Etc/GMT"},
1362 {"GMT-0", "Etc/GMT"},
1363 {"GMT0", "Etc/GMT"},
1364 {"Greenwich", "Etc/GMT"},
1365 {"HST", "Etc/GMT+10"},
1366 {"Hongkong", "Asia/Hong_Kong"},
1367 {"Iceland", "Atlantic/Reykjavik"},
1368 {"Iran", "Asia/Tehran"},
1369 {"Israel", "Asia/Jerusalem"},
1370 {"Jamaica", "America/Jamaica"},
1371 {"Japan", "Asia/Tokyo"},
1372 {"Kwajalein", "Pacific/Kwajalein"},
1373 {"Libya", "Africa/Tripoli"},
1374 {"MST", "Etc/GMT+7"},
1375 {"Mexico/BajaNorte", "America/Tijuana"},
1376 {"Mexico/BajaSur", "America/Mazatlan"},
1377 {"Mexico/General", "America/Mexico_City"},
1378 {"NZ", "Antarctica/McMurdo"},
1379 {"NZ-CHAT", "Pacific/Chatham"},
1380 {"Navajo", "America/Denver"},
1381 {"PRC", "Asia/Shanghai"},
1382 {"Pacific/Chuuk", "Pacific/Truk"},
1383 {"Pacific/Pohnpei", "Pacific/Ponape"},
1384 {"Pacific/Samoa", "Pacific/Midway"},
1385 {"Pacific/Yap", "Pacific/Truk"},
1386 {"Poland", "Europe/Warsaw"},
1387 {"Portugal", "Europe/Lisbon"},
1388 {"ROC", "Asia/Taipei"},
1389 {"ROK", "Asia/Seoul"},
1390 {"Singapore", "Asia/Singapore"},
1391 {"SystemV/AST4", "Etc/GMT+4"},
1392 {"SystemV/CST6", "Etc/GMT+6"},
1393 {"SystemV/EST5", "Etc/GMT+5"},
1394 {"SystemV/HST10", "Etc/GMT+10"},
1395 {"SystemV/MST7", "Etc/GMT+7"},
1396 {"SystemV/PST8", "Etc/GMT+8"},
1397 {"SystemV/YST9", "Etc/GMT+9"},
1398 {"Turkey", "Europe/Istanbul"},
1399 {"UCT", "Etc/UTC"},
1400 {"US/Alaska", "America/Anchorage"},
1401 {"US/Aleutian", "America/Adak"},
1402 {"US/Arizona", "America/Phoenix"},
1403 {"US/Central", "America/Chicago"},
1404 {"US/East-Indiana", "America/Indianapolis"},
1405 {"US/Eastern", "America/New_York"},
1406 {"US/Hawaii", "Pacific/Honolulu"},
1407 {"US/Indiana-Starke", "America/Indiana/Knox"},
1408 {"US/Michigan", "America/Detroit"},
1409 {"US/Mountain", "America/Denver"},
1410 {"US/Pacific", "America/Los_Angeles"},
1411 {"US/Pacific-New", "America/Los_Angeles"},
1412 {"US/Samoa", "Pacific/Midway"},
1413 {"UTC", "Etc/UTC"},
1414 {"Universal", "Etc/UTC"},
1415 {"W-SU", "Europe/Moscow"},
1416 {"Zulu", "Etc/UTC"},
1417 /* Total: 136 */
1418 };
1419
1420 TimeZone::EDisplayType styles[] = { TimeZone::SHORT, TimeZone::LONG };
1421 UBool useDst[] = { false, true };
1422 int32_t noLoc = uloc_countAvailable();
1423
1424 int32_t i, j, k, loc;
1425 UnicodeString fromName, toName;
1426 TimeZone *from = nullptr, *to = nullptr;
1427 for(i = 0; i < UPRV_LENGTHOF(kData); i++) {
1428 from = TimeZone::createTimeZone(kData[i].from);
1429 to = TimeZone::createTimeZone(kData[i].to);
1430 if(!from->hasSameRules(*to)) {
1431 errln("different at %i\n", i);
1432 }
1433 if(!quick) {
1434 for(loc = 0; loc < noLoc; loc++) {
1435 const char* locale = uloc_getAvailable(loc);
1436 for(j = 0; j < UPRV_LENGTHOF(styles); j++) {
1437 for(k = 0; k < UPRV_LENGTHOF(useDst); k++) {
1438 fromName.remove();
1439 toName.remove();
1440 from->getDisplayName(useDst[k], styles[j],locale, fromName);
1441 to->getDisplayName(useDst[k], styles[j], locale, toName);
1442 if(fromName.compare(toName) != 0) {
1443 errln("Fail: Expected "+toName+" but got " + prettify(fromName)
1444 + " for locale: " + locale + " index: "+ loc
1445 + " to id "+ kData[i].to
1446 + " from id " + kData[i].from);
1447 }
1448 }
1449 }
1450 }
1451 } else {
1452 fromName.remove();
1453 toName.remove();
1454 from->getDisplayName(fromName);
1455 to->getDisplayName(toName);
1456 if(fromName.compare(toName) != 0) {
1457 errln("Fail: Expected "+toName+" but got " + fromName);
1458 }
1459 }
1460 delete from;
1461 delete to;
1462 }
1463 }
1464
1465 /**
1466 * Test the basic functionality of the getDisplayName() API.
1467 *
1468 * @bug 4112869
1469 * @bug 4028006
1470 *
1471 * See also API change request A41.
1472 *
1473 * 4/21/98 - make smarter, so the test works if the ext resources
1474 * are present or not.
1475 */
1476 void
TestDisplayName()1477 TimeZoneTest::TestDisplayName()
1478 {
1479 UErrorCode status = U_ZERO_ERROR;
1480 int32_t i;
1481 TimeZone *zone = TimeZone::createTimeZone("PST");
1482 UnicodeString name;
1483 zone->getDisplayName(Locale::getEnglish(), name);
1484 logln("PST->" + name);
1485 if (name.compare("Pacific Standard Time") != 0)
1486 dataerrln("Fail: Expected \"Pacific Standard Time\" but got " + name);
1487
1488 //*****************************************************************
1489 // THE FOLLOWING LINES MUST BE UPDATED IF THE LOCALE DATA CHANGES
1490 // THE FOLLOWING LINES MUST BE UPDATED IF THE LOCALE DATA CHANGES
1491 // THE FOLLOWING LINES MUST BE UPDATED IF THE LOCALE DATA CHANGES
1492 //*****************************************************************
1493 struct
1494 {
1495 UBool useDst;
1496 TimeZone::EDisplayType style;
1497 const char *expect;
1498 } kData[] = {
1499 {false, TimeZone::SHORT, "PST"},
1500 {true, TimeZone::SHORT, "PDT"},
1501 {false, TimeZone::LONG, "Pacific Standard Time"},
1502 {true, TimeZone::LONG, "Pacific Daylight Time"},
1503
1504 {false, TimeZone::SHORT_GENERIC, "PT"},
1505 {true, TimeZone::SHORT_GENERIC, "PT"},
1506 {false, TimeZone::LONG_GENERIC, "Pacific Time"},
1507 {true, TimeZone::LONG_GENERIC, "Pacific Time"},
1508
1509 {false, TimeZone::SHORT_GMT, "-0800"},
1510 {true, TimeZone::SHORT_GMT, "-0700"},
1511 {false, TimeZone::LONG_GMT, "GMT-08:00"},
1512 {true, TimeZone::LONG_GMT, "GMT-07:00"},
1513
1514 {false, TimeZone::SHORT_COMMONLY_USED, "PST"},
1515 {true, TimeZone::SHORT_COMMONLY_USED, "PDT"},
1516 {false, TimeZone::GENERIC_LOCATION, "Los Angeles Time"},
1517 {true, TimeZone::GENERIC_LOCATION, "Los Angeles Time"},
1518
1519 {false, TimeZone::LONG, ""}
1520 };
1521
1522 for (i=0; kData[i].expect[0] != '\0'; i++)
1523 {
1524 name.remove();
1525 name = zone->getDisplayName(kData[i].useDst,
1526 kData[i].style,
1527 Locale::getEnglish(), name);
1528 if (name.compare(kData[i].expect) != 0)
1529 dataerrln("Fail: Expected " + UnicodeString(kData[i].expect) + "; got " + name);
1530 logln("PST [with options]->" + name);
1531 }
1532 for (i=0; kData[i].expect[0] != '\0'; i++)
1533 {
1534 name.remove();
1535 name = zone->getDisplayName(kData[i].useDst,
1536 kData[i].style, name);
1537 if (name.compare(kData[i].expect) != 0)
1538 dataerrln("Fail: Expected " + UnicodeString(kData[i].expect) + "; got " + name);
1539 logln("PST [with options]->" + name);
1540 }
1541
1542
1543 // Make sure that we don't display the DST name by constructing a fake
1544 // PST zone that has DST all year long.
1545 SimpleTimeZone *zone2 = new SimpleTimeZone(0, "PST");
1546
1547 zone2->setStartRule(UCAL_JANUARY, 1, 0, 0, status);
1548 zone2->setEndRule(UCAL_DECEMBER, 31, 0, 0, status);
1549
1550 UnicodeString inDaylight;
1551 if (zone2->inDaylightTime(UDate(0), status)) {
1552 inDaylight = UnicodeString("true");
1553 } else {
1554 inDaylight = UnicodeString("false");
1555 }
1556 logln(UnicodeString("Modified PST inDaylightTime->") + inDaylight );
1557 if(U_FAILURE(status))
1558 {
1559 dataerrln("Some sort of error..." + UnicodeString(u_errorName(status))); // REVISIT
1560 }
1561 name.remove();
1562 name = zone2->getDisplayName(Locale::getEnglish(),name);
1563 logln("Modified PST->" + name);
1564 if (name.compare("Pacific Standard Time") != 0)
1565 dataerrln("Fail: Expected \"Pacific Standard Time\"");
1566
1567 // Make sure we get the default display format for Locales
1568 // with no display name data.
1569 Locale mt_MT("mt_MT");
1570 name.remove();
1571 name = zone->getDisplayName(mt_MT,name);
1572 //*****************************************************************
1573 // THE FOLLOWING LINE MUST BE UPDATED IF THE LOCALE DATA CHANGES
1574 // THE FOLLOWING LINE MUST BE UPDATED IF THE LOCALE DATA CHANGES
1575 // THE FOLLOWING LINE MUST BE UPDATED IF THE LOCALE DATA CHANGES
1576 //*****************************************************************
1577 logln("PST(mt_MT)->" + name);
1578
1579 // *** REVISIT SRL how in the world do I check this? looks java specific.
1580 // Now be smart -- check to see if zh resource is even present.
1581 // If not, we expect the en fallback behavior.
1582 ResourceBundle enRB(nullptr,
1583 Locale::getEnglish(), status);
1584 if(U_FAILURE(status))
1585 dataerrln("Couldn't get ResourceBundle for en - %s", u_errorName(status));
1586
1587 ResourceBundle mtRB(nullptr,
1588 mt_MT, status);
1589 //if(U_FAILURE(status))
1590 // errln("Couldn't get ResourceBundle for mt_MT");
1591
1592 UBool noZH = U_FAILURE(status);
1593
1594 if (noZH) {
1595 logln("Warning: Not testing the mt_MT behavior because resource is absent");
1596 if (name != "Pacific Standard Time")
1597 dataerrln("Fail: Expected Pacific Standard Time");
1598 }
1599
1600
1601 if (name.compare("GMT-08:00") &&
1602 name.compare("GMT-8:00") &&
1603 name.compare("GMT-0800") &&
1604 name.compare("GMT-800")) {
1605 dataerrln(UnicodeString("Fail: Expected GMT-08:00 or something similar for PST in mt_MT but got ") + name );
1606 dataerrln("************************************************************");
1607 dataerrln("THE ABOVE FAILURE MAY JUST MEAN THE LOCALE DATA HAS CHANGED");
1608 dataerrln("************************************************************");
1609 }
1610
1611 // Now try a non-existent zone
1612 delete zone2;
1613 zone2 = new SimpleTimeZone(90*60*1000, "xyzzy");
1614 name.remove();
1615 name = zone2->getDisplayName(Locale::getEnglish(),name);
1616 logln("GMT+90min->" + name);
1617 if (name.compare("GMT+01:30") &&
1618 name.compare("GMT+1:30") &&
1619 name.compare("GMT+0130") &&
1620 name.compare("GMT+130"))
1621 dataerrln("Fail: Expected GMT+01:30 or something similar");
1622 name.truncate(0);
1623 zone2->getDisplayName(name);
1624 logln("GMT+90min->" + name);
1625 if (name.compare("GMT+01:30") &&
1626 name.compare("GMT+1:30") &&
1627 name.compare("GMT+0130") &&
1628 name.compare("GMT+130"))
1629 dataerrln("Fail: Expected GMT+01:30 or something similar");
1630 // clean up
1631 delete zone;
1632 delete zone2;
1633 }
1634
1635 /**
1636 * @bug 4107276
1637 */
1638 void
TestDSTSavings()1639 TimeZoneTest::TestDSTSavings()
1640 {
1641 UErrorCode status = U_ZERO_ERROR;
1642 // It might be better to find a way to integrate this test into the main TimeZone
1643 // tests above, but I don't have time to figure out how to do this (or if it's
1644 // even really a good idea). Let's consider that a future. --rtg 1/27/98
1645 SimpleTimeZone *tz = new SimpleTimeZone(-5 * U_MILLIS_PER_HOUR, "dstSavingsTest",
1646 UCAL_MARCH, 1, 0, 0, UCAL_SEPTEMBER, 1, 0, 0,
1647 (int32_t)(0.5 * U_MILLIS_PER_HOUR), status);
1648 if(U_FAILURE(status))
1649 errln("couldn't create TimeZone");
1650
1651 if (tz->getRawOffset() != -5 * U_MILLIS_PER_HOUR)
1652 errln(UnicodeString("Got back a raw offset of ") + (tz->getRawOffset() / U_MILLIS_PER_HOUR) +
1653 " hours instead of -5 hours.");
1654 if (!tz->useDaylightTime())
1655 errln("Test time zone should use DST but claims it doesn't.");
1656 if (tz->getDSTSavings() != 0.5 * U_MILLIS_PER_HOUR)
1657 errln(UnicodeString("Set DST offset to 0.5 hour, but got back ") + (tz->getDSTSavings() /
1658 U_MILLIS_PER_HOUR) + " hours instead.");
1659
1660 int32_t offset = tz->getOffset(GregorianCalendar::AD, 1998, UCAL_JANUARY, 1,
1661 UCAL_THURSDAY, 10 * U_MILLIS_PER_HOUR,status);
1662 if (offset != -5 * U_MILLIS_PER_HOUR)
1663 errln(UnicodeString("The offset for 10 AM, 1/1/98 should have been -5 hours, but we got ")
1664 + (offset / U_MILLIS_PER_HOUR) + " hours.");
1665
1666 offset = tz->getOffset(GregorianCalendar::AD, 1998, UCAL_JUNE, 1, UCAL_MONDAY,
1667 10 * U_MILLIS_PER_HOUR,status);
1668 if (offset != -4.5 * U_MILLIS_PER_HOUR)
1669 errln(UnicodeString("The offset for 10 AM, 6/1/98 should have been -4.5 hours, but we got ")
1670 + (offset / U_MILLIS_PER_HOUR) + " hours.");
1671
1672 tz->setDSTSavings(U_MILLIS_PER_HOUR, status);
1673 offset = tz->getOffset(GregorianCalendar::AD, 1998, UCAL_JANUARY, 1,
1674 UCAL_THURSDAY, 10 * U_MILLIS_PER_HOUR,status);
1675 if (offset != -5 * U_MILLIS_PER_HOUR)
1676 errln(UnicodeString("The offset for 10 AM, 1/1/98 should have been -5 hours, but we got ")
1677 + (offset / U_MILLIS_PER_HOUR) + " hours.");
1678
1679 offset = tz->getOffset(GregorianCalendar::AD, 1998, UCAL_JUNE, 1, UCAL_MONDAY,
1680 10 * U_MILLIS_PER_HOUR,status);
1681 if (offset != -4 * U_MILLIS_PER_HOUR)
1682 errln(UnicodeString("The offset for 10 AM, 6/1/98 (with a 1-hour DST offset) should have been -4 hours, but we got ")
1683 + (offset / U_MILLIS_PER_HOUR) + " hours.");
1684
1685 delete tz;
1686 }
1687
1688 /**
1689 * @bug 4107570
1690 */
1691 void
TestAlternateRules()1692 TimeZoneTest::TestAlternateRules()
1693 {
1694 // Like TestDSTSavings, this test should probably be integrated somehow with the main
1695 // test at the top of this class, but I didn't have time to figure out how to do that.
1696 // --rtg 1/28/98
1697
1698 SimpleTimeZone tz(-5 * U_MILLIS_PER_HOUR, "alternateRuleTest");
1699
1700 // test the day-of-month API
1701 UErrorCode status = U_ZERO_ERROR;
1702 tz.setStartRule(UCAL_MARCH, 10, 12 * U_MILLIS_PER_HOUR, status);
1703 if(U_FAILURE(status))
1704 errln("tz.setStartRule failed");
1705 tz.setEndRule(UCAL_OCTOBER, 20, 12 * U_MILLIS_PER_HOUR, status);
1706 if(U_FAILURE(status))
1707 errln("tz.setStartRule failed");
1708
1709 int32_t offset = tz.getOffset(GregorianCalendar::AD, 1998, UCAL_MARCH, 5,
1710 UCAL_THURSDAY, 10 * U_MILLIS_PER_HOUR,status);
1711 if (offset != -5 * U_MILLIS_PER_HOUR)
1712 errln(UnicodeString("The offset for 10AM, 3/5/98 should have been -5 hours, but we got ")
1713 + (offset / U_MILLIS_PER_HOUR) + " hours.");
1714
1715 offset = tz.getOffset(GregorianCalendar::AD, 1998, UCAL_MARCH, 15,
1716 UCAL_SUNDAY, 10 * millisPerHour,status);
1717 if (offset != -4 * U_MILLIS_PER_HOUR)
1718 errln(UnicodeString("The offset for 10AM, 3/15/98 should have been -4 hours, but we got ")
1719 + (offset / U_MILLIS_PER_HOUR) + " hours.");
1720
1721 offset = tz.getOffset(GregorianCalendar::AD, 1998, UCAL_OCTOBER, 15,
1722 UCAL_THURSDAY, 10 * millisPerHour,status);
1723 if (offset != -4 * U_MILLIS_PER_HOUR)
1724 errln(UnicodeString("The offset for 10AM, 10/15/98 should have been -4 hours, but we got ") + (offset / U_MILLIS_PER_HOUR) + " hours.");
1725
1726 offset = tz.getOffset(GregorianCalendar::AD, 1998, UCAL_OCTOBER, 25,
1727 UCAL_SUNDAY, 10 * millisPerHour,status);
1728 if (offset != -5 * U_MILLIS_PER_HOUR)
1729 errln(UnicodeString("The offset for 10AM, 10/25/98 should have been -5 hours, but we got ")
1730 + (offset / U_MILLIS_PER_HOUR) + " hours.");
1731
1732 // test the day-of-week-after-day-in-month API
1733 tz.setStartRule(UCAL_MARCH, 10, UCAL_FRIDAY, 12 * millisPerHour, true, status);
1734 if(U_FAILURE(status))
1735 errln("tz.setStartRule failed");
1736 tz.setEndRule(UCAL_OCTOBER, 20, UCAL_FRIDAY, 12 * millisPerHour, false, status);
1737 if(U_FAILURE(status))
1738 errln("tz.setStartRule failed");
1739
1740 offset = tz.getOffset(GregorianCalendar::AD, 1998, UCAL_MARCH, 11,
1741 UCAL_WEDNESDAY, 10 * millisPerHour,status);
1742 if (offset != -5 * U_MILLIS_PER_HOUR)
1743 errln(UnicodeString("The offset for 10AM, 3/11/98 should have been -5 hours, but we got ")
1744 + (offset / U_MILLIS_PER_HOUR) + " hours.");
1745
1746 offset = tz.getOffset(GregorianCalendar::AD, 1998, UCAL_MARCH, 14,
1747 UCAL_SATURDAY, 10 * millisPerHour,status);
1748 if (offset != -4 * U_MILLIS_PER_HOUR)
1749 errln(UnicodeString("The offset for 10AM, 3/14/98 should have been -4 hours, but we got ")
1750 + (offset / U_MILLIS_PER_HOUR) + " hours.");
1751
1752 offset = tz.getOffset(GregorianCalendar::AD, 1998, UCAL_OCTOBER, 15,
1753 UCAL_THURSDAY, 10 * millisPerHour,status);
1754 if (offset != -4 * U_MILLIS_PER_HOUR)
1755 errln(UnicodeString("The offset for 10AM, 10/15/98 should have been -4 hours, but we got ")
1756 + (offset / U_MILLIS_PER_HOUR) + " hours.");
1757
1758 offset = tz.getOffset(GregorianCalendar::AD, 1998, UCAL_OCTOBER, 17,
1759 UCAL_SATURDAY, 10 * millisPerHour,status);
1760 if (offset != -5 * U_MILLIS_PER_HOUR)
1761 errln(UnicodeString("The offset for 10AM, 10/17/98 should have been -5 hours, but we got ")
1762 + (offset / U_MILLIS_PER_HOUR) + " hours.");
1763 }
1764
TestFractionalDST()1765 void TimeZoneTest::TestFractionalDST() {
1766 const char* tzName = "Australia/Lord_Howe"; // 30 min offset
1767 TimeZone* tz_icu = TimeZone::createTimeZone(tzName);
1768 int dst_icu = tz_icu->getDSTSavings();
1769 UnicodeString id;
1770 int32_t expected = 1800000;
1771 if (expected != dst_icu) {
1772 dataerrln(UnicodeString("java reports dst savings of ") + expected +
1773 " but icu reports " + dst_icu +
1774 " for tz " + tz_icu->getID(id));
1775 } else {
1776 logln(UnicodeString("both java and icu report dst savings of ") + expected + " for tz " + tz_icu->getID(id));
1777 }
1778 delete tz_icu;
1779 }
1780
1781 /**
1782 * Test country code support. Jitterbug 776.
1783 */
TestCountries()1784 void TimeZoneTest::TestCountries() {
1785 // Make sure America/Los_Angeles is in the "US" group, and
1786 // Asia/Tokyo isn't. Vice versa for the "JP" group.
1787 UErrorCode ec = U_ZERO_ERROR;
1788 int32_t n;
1789 StringEnumeration* s = TimeZone::createEnumerationForRegion("US", ec);
1790 if (U_FAILURE(ec)) {
1791 dataerrln("Unable to create TimeZone enumeration for US");
1792 return;
1793 }
1794 n = s->count(ec);
1795 UBool la = false, tokyo = false;
1796 UnicodeString laZone("America/Los_Angeles", "");
1797 UnicodeString tokyoZone("Asia/Tokyo", "");
1798 int32_t i;
1799
1800 if (n <= 0) {
1801 dataerrln("FAIL: TimeZone::createEnumeration() returned nothing");
1802 return;
1803 }
1804 for (i=0; i<n; ++i) {
1805 const UnicodeString* id = s->snext(ec);
1806 if (*id == (laZone)) {
1807 la = true;
1808 }
1809 if (*id == (tokyoZone)) {
1810 tokyo = true;
1811 }
1812 }
1813 if (!la || tokyo) {
1814 errln("FAIL: " + laZone + " in US = " + la);
1815 errln("FAIL: " + tokyoZone + " in US = " + tokyo);
1816 }
1817 delete s;
1818
1819 s = TimeZone::createEnumerationForRegion("JP", ec);
1820 if (U_FAILURE(ec)) {
1821 dataerrln("Unable to create TimeZone enumeration for JP");
1822 return;
1823 }
1824 n = s->count(ec);
1825 la = false; tokyo = false;
1826
1827 for (i=0; i<n; ++i) {
1828 const UnicodeString* id = s->snext(ec);
1829 if (*id == (laZone)) {
1830 la = true;
1831 }
1832 if (*id == (tokyoZone)) {
1833 tokyo = true;
1834 }
1835 }
1836 if (la || !tokyo) {
1837 errln("FAIL: " + laZone + " in JP = " + la);
1838 errln("FAIL: " + tokyoZone + " in JP = " + tokyo);
1839 }
1840 StringEnumeration* s1 = TimeZone::createEnumerationForRegion("US", ec);
1841 StringEnumeration* s2 = TimeZone::createEnumerationForRegion("US", ec);
1842 if (U_FAILURE(ec)) {
1843 dataerrln("Unable to create TimeZone enumeration for US");
1844 return;
1845 }
1846 for(i=0;i<n;++i){
1847 const UnicodeString* id1 = s1->snext(ec);
1848 if(id1==nullptr || U_FAILURE(ec)){
1849 errln("Failed to fetch next from TimeZone enumeration. Length returned : %i Current Index: %i", n,i);
1850 }
1851 TimeZone* tz1 = TimeZone::createTimeZone(*id1);
1852 for(int j=0; j<n;++j){
1853 const UnicodeString* id2 = s2->snext(ec);
1854 if(id2==nullptr || U_FAILURE(ec)){
1855 errln("Failed to fetch next from TimeZone enumeration. Length returned : %i Current Index: %i", n,i);
1856 }
1857 TimeZone* tz2 = TimeZone::createTimeZone(*id2);
1858 if(tz1->hasSameRules(*tz2)){
1859 logln("ID1 : " + *id1+" == ID2 : " +*id2);
1860 }
1861 delete tz2;
1862 }
1863 delete tz1;
1864 }
1865 delete s1;
1866 delete s2;
1867 delete s;
1868 }
1869
TestHistorical()1870 void TimeZoneTest::TestHistorical() {
1871 const int32_t H = U_MILLIS_PER_HOUR;
1872 struct {
1873 const char* id;
1874 int32_t time; // epoch seconds
1875 int32_t offset; // total offset (millis)
1876 } DATA[] = {
1877 // Add transition points (before/after) as desired to test historical
1878 // behavior.
1879 {"America/Los_Angeles", 638963999, -8*H}, // Sun Apr 01 01:59:59 GMT-08:00 1990
1880 {"America/Los_Angeles", 638964000, -7*H}, // Sun Apr 01 03:00:00 GMT-07:00 1990
1881 {"America/Los_Angeles", 657104399, -7*H}, // Sun Oct 28 01:59:59 GMT-07:00 1990
1882 {"America/Los_Angeles", 657104400, -8*H}, // Sun Oct 28 01:00:00 GMT-08:00 1990
1883 {"America/Goose_Bay", -116445601, -4*H}, // Sun Apr 24 01:59:59 GMT-04:00 1966
1884 {"America/Goose_Bay", -116445600, -3*H}, // Sun Apr 24 03:00:00 GMT-03:00 1966
1885 {"America/Goose_Bay", -100119601, -3*H}, // Sun Oct 30 01:59:59 GMT-03:00 1966
1886 {"America/Goose_Bay", -100119600, -4*H}, // Sun Oct 30 01:00:00 GMT-04:00 1966
1887 {"America/Goose_Bay", -84391201, -4*H}, // Sun Apr 30 01:59:59 GMT-04:00 1967
1888 {"America/Goose_Bay", -84391200, -3*H}, // Sun Apr 30 03:00:00 GMT-03:00 1967
1889 {"America/Goose_Bay", -68670001, -3*H}, // Sun Oct 29 01:59:59 GMT-03:00 1967
1890 {"America/Goose_Bay", -68670000, -4*H}, // Sun Oct 29 01:00:00 GMT-04:00 1967
1891 {nullptr, 0, 0}
1892 };
1893
1894 for (int32_t i = 0; DATA[i].id != nullptr; ++i) {
1895 const char* id = DATA[i].id;
1896 TimeZone *tz = TimeZone::createTimeZone(id);
1897 UnicodeString s;
1898 if (tz == nullptr) {
1899 errln("FAIL: Cannot create %s", id);
1900 } else if (tz->getID(s) != UnicodeString(id)) {
1901 dataerrln((UnicodeString)"FAIL: createTimeZone(" + id + ") => " + s);
1902 } else {
1903 UErrorCode ec = U_ZERO_ERROR;
1904 int32_t raw, dst;
1905 UDate when = (double) DATA[i].time * U_MILLIS_PER_SECOND;
1906 tz->getOffset(when, false, raw, dst, ec);
1907 if (U_FAILURE(ec)) {
1908 errln("FAIL: getOffset");
1909 } else if ((raw+dst) != DATA[i].offset) {
1910 errln((UnicodeString)"FAIL: " + DATA[i].id + ".getOffset(" +
1911 //when + " = " +
1912 dateToString(when) + ") => " +
1913 raw + ", " + dst);
1914 } else {
1915 logln((UnicodeString)"Ok: " + DATA[i].id + ".getOffset(" +
1916 //when + " = " +
1917 dateToString(when) + ") => " +
1918 raw + ", " + dst);
1919 }
1920 }
1921 delete tz;
1922 }
1923 }
1924
TestEquivalentIDs()1925 void TimeZoneTest::TestEquivalentIDs() {
1926 int32_t n = TimeZone::countEquivalentIDs("PST");
1927 if (n < 2) {
1928 dataerrln((UnicodeString)"FAIL: countEquivalentIDs(PST) = " + n);
1929 } else {
1930 UBool sawLA = false;
1931 for (int32_t i=0; i<n; ++i) {
1932 UnicodeString id = TimeZone::getEquivalentID("PST", i);
1933 logln((UnicodeString)"" + i + " : " + id);
1934 if (id == UnicodeString("America/Los_Angeles")) {
1935 sawLA = true;
1936 }
1937 }
1938 if (!sawLA) {
1939 errln("FAIL: America/Los_Angeles should be in the list");
1940 }
1941 }
1942 }
1943
1944 // Test that a transition at the end of February is handled correctly.
TestFebruary()1945 void TimeZoneTest::TestFebruary() {
1946 UErrorCode status = U_ZERO_ERROR;
1947
1948 // Time zone with daylight savings time from the first Sunday in November
1949 // to the last Sunday in February.
1950 // Similar to the new rule for Brazil (Sao Paulo) in tzdata2006n.
1951 //
1952 // Note: In tzdata2007h, the rule had changed, so no actual zones uses
1953 // lastSun in Feb anymore.
1954 SimpleTimeZone tz1(-3 * U_MILLIS_PER_HOUR, // raw offset: 3h before (west of) GMT
1955 UNICODE_STRING("nov-feb", 7),
1956 UCAL_NOVEMBER, 1, UCAL_SUNDAY, // start: November, first, Sunday
1957 0, // midnight wall time
1958 UCAL_FEBRUARY, -1, UCAL_SUNDAY, // end: February, last, Sunday
1959 0, // midnight wall time
1960 status);
1961 if (U_FAILURE(status)) {
1962 errln("Unable to create the SimpleTimeZone(nov-feb): %s", u_errorName(status));
1963 return;
1964 }
1965
1966 // Now hardcode the same rules as for Brazil in tzdata 2006n, so that
1967 // we cover the intended code even when in the future zoneinfo hardcodes
1968 // these transition dates.
1969 SimpleTimeZone tz2(-3 * U_MILLIS_PER_HOUR, // raw offset: 3h before (west of) GMT
1970 UNICODE_STRING("nov-feb2", 8),
1971 UCAL_NOVEMBER, 1, -UCAL_SUNDAY, // start: November, 1 or after, Sunday
1972 0, // midnight wall time
1973 UCAL_FEBRUARY, -29, -UCAL_SUNDAY,// end: February, 29 or before, Sunday
1974 0, // midnight wall time
1975 status);
1976 if (U_FAILURE(status)) {
1977 errln("Unable to create the SimpleTimeZone(nov-feb2): %s", u_errorName(status));
1978 return;
1979 }
1980
1981 // Gregorian calendar with the UTC time zone for getting sample test date/times.
1982 GregorianCalendar gc(*TimeZone::getGMT(), status);
1983 if (U_FAILURE(status)) {
1984 dataerrln("Unable to create the UTC calendar: %s", u_errorName(status));
1985 return;
1986 }
1987
1988 struct {
1989 // UTC time.
1990 int32_t year, month, day, hour, minute, second;
1991 // Expected time zone offset in hours after GMT (negative=before GMT).
1992 int32_t offsetHours;
1993 } data[] = {
1994 { 2006, UCAL_NOVEMBER, 5, 02, 59, 59, -3 },
1995 { 2006, UCAL_NOVEMBER, 5, 03, 00, 00, -2 },
1996 { 2007, UCAL_FEBRUARY, 25, 01, 59, 59, -2 },
1997 { 2007, UCAL_FEBRUARY, 25, 02, 00, 00, -3 },
1998
1999 { 2007, UCAL_NOVEMBER, 4, 02, 59, 59, -3 },
2000 { 2007, UCAL_NOVEMBER, 4, 03, 00, 00, -2 },
2001 { 2008, UCAL_FEBRUARY, 24, 01, 59, 59, -2 },
2002 { 2008, UCAL_FEBRUARY, 24, 02, 00, 00, -3 },
2003
2004 { 2008, UCAL_NOVEMBER, 2, 02, 59, 59, -3 },
2005 { 2008, UCAL_NOVEMBER, 2, 03, 00, 00, -2 },
2006 { 2009, UCAL_FEBRUARY, 22, 01, 59, 59, -2 },
2007 { 2009, UCAL_FEBRUARY, 22, 02, 00, 00, -3 },
2008
2009 { 2009, UCAL_NOVEMBER, 1, 02, 59, 59, -3 },
2010 { 2009, UCAL_NOVEMBER, 1, 03, 00, 00, -2 },
2011 { 2010, UCAL_FEBRUARY, 28, 01, 59, 59, -2 },
2012 { 2010, UCAL_FEBRUARY, 28, 02, 00, 00, -3 }
2013 };
2014
2015 TimeZone *timezones[] = { &tz1, &tz2 };
2016
2017 TimeZone *tz;
2018 UDate dt;
2019 int32_t t, i, raw, dst;
2020 for (t = 0; t < UPRV_LENGTHOF(timezones); ++t) {
2021 tz = timezones[t];
2022 for (i = 0; i < UPRV_LENGTHOF(data); ++i) {
2023 gc.set(data[i].year, data[i].month, data[i].day,
2024 data[i].hour, data[i].minute, data[i].second);
2025 dt = gc.getTime(status);
2026 if (U_FAILURE(status)) {
2027 errln("test case %d.%d: bad date/time %04d-%02d-%02d %02d:%02d:%02d",
2028 t, i,
2029 data[i].year, data[i].month + 1, data[i].day,
2030 data[i].hour, data[i].minute, data[i].second);
2031 status = U_ZERO_ERROR;
2032 continue;
2033 }
2034 tz->getOffset(dt, false, raw, dst, status);
2035 if (U_FAILURE(status)) {
2036 errln("test case %d.%d: tz.getOffset(%04d-%02d-%02d %02d:%02d:%02d) fails: %s",
2037 t, i,
2038 data[i].year, data[i].month + 1, data[i].day,
2039 data[i].hour, data[i].minute, data[i].second,
2040 u_errorName(status));
2041 status = U_ZERO_ERROR;
2042 } else if ((raw + dst) != data[i].offsetHours * U_MILLIS_PER_HOUR) {
2043 errln("test case %d.%d: tz.getOffset(%04d-%02d-%02d %02d:%02d:%02d) returns %d+%d != %d",
2044 t, i,
2045 data[i].year, data[i].month + 1, data[i].day,
2046 data[i].hour, data[i].minute, data[i].second,
2047 raw, dst, data[i].offsetHours * U_MILLIS_PER_HOUR);
2048 }
2049 }
2050 }
2051 }
2052
TestCanonicalIDAPI()2053 void TimeZoneTest::TestCanonicalIDAPI() {
2054 // Bogus input string.
2055 UnicodeString bogus;
2056 bogus.setToBogus();
2057 UnicodeString canonicalID;
2058 UErrorCode ec = U_ZERO_ERROR;
2059 UnicodeString *pResult = &TimeZone::getCanonicalID(bogus, canonicalID, ec);
2060 assertEquals("TimeZone::getCanonicalID(bogus) should fail", (int32_t)U_ILLEGAL_ARGUMENT_ERROR, ec);
2061 assertTrue("TimeZone::getCanonicalID(bogus) should return the dest string", pResult == &canonicalID);
2062
2063 // U_FAILURE on input.
2064 UnicodeString berlin("Europe/Berlin");
2065 ec = U_MEMORY_ALLOCATION_ERROR;
2066 pResult = &TimeZone::getCanonicalID(berlin, canonicalID, ec);
2067 assertEquals("TimeZone::getCanonicalID(failure) should fail", (int32_t)U_MEMORY_ALLOCATION_ERROR, ec);
2068 assertTrue("TimeZone::getCanonicalID(failure) should return the dest string", pResult == &canonicalID);
2069
2070 // Valid input should un-bogus the dest string.
2071 canonicalID.setToBogus();
2072 ec = U_ZERO_ERROR;
2073 pResult = &TimeZone::getCanonicalID(berlin, canonicalID, ec);
2074 assertSuccess("TimeZone::getCanonicalID(bogus dest) should succeed", ec, true);
2075 assertTrue("TimeZone::getCanonicalID(bogus dest) should return the dest string", pResult == &canonicalID);
2076 assertFalse("TimeZone::getCanonicalID(bogus dest) should un-bogus the dest string", canonicalID.isBogus());
2077 assertEquals("TimeZone::getCanonicalID(bogus dest) unexpected result", canonicalID, berlin, true);
2078 }
2079
TestCanonicalID()2080 void TimeZoneTest::TestCanonicalID() {
2081
2082 // Olson (IANA) tzdata used to have very few "Link"s long time ago.
2083 // This test case was written when most of CLDR canonical time zones are
2084 // defined as independent "Zone" in the TZ database.
2085 // Since then, the TZ maintainer found some historic rules in mid 20th century
2086 // were not really reliable, and many zones are now sharing rules.
2087 // As of TZ database release 2022a, there are quite a lot of zones defined
2088 // by "Link" to another zone, so the exception table below becomes really
2089 // big. It might be still useful to make sure CLDR zone aliases are consistent
2090 // with zone rules.
2091 static const struct {
2092 const char *alias; // link-from
2093 const char *zone; // link-to (A zone ID with "Zone" rule)
2094 } excluded1[] = {
2095 {"Africa/Accra", "Africa/Abidjan"},
2096 {"Africa/Addis_Ababa", "Africa/Nairobi"},
2097 {"Africa/Asmera", "Africa/Nairobi"},
2098 {"Africa/Bamako", "Africa/Abidjan"},
2099 {"Africa/Bangui", "Africa/Lagos"},
2100 {"Africa/Banjul", "Africa/Abidjan"},
2101 {"Africa/Blantyre", "Africa/Maputo"},
2102 {"Africa/Brazzaville", "Africa/Lagos"},
2103 {"Africa/Bujumbura", "Africa/Maputo"},
2104 {"Africa/Conakry", "Africa/Abidjan"},
2105 {"Africa/Dakar", "Africa/Abidjan"},
2106 {"Africa/Dar_es_Salaam", "Africa/Nairobi"},
2107 {"Africa/Djibouti", "Africa/Nairobi"},
2108 {"Africa/Douala", "Africa/Lagos"},
2109 {"Africa/Freetown", "Africa/Abidjan"},
2110 {"Africa/Gaborone", "Africa/Maputo"},
2111 {"Africa/Harare", "Africa/Maputo"},
2112 {"Africa/Kampala", "Africa/Nairobi"},
2113 {"Africa/Khartoum", "Africa/Juba"},
2114 {"Africa/Kigali", "Africa/Maputo"},
2115 {"Africa/Kinshasa", "Africa/Lagos"},
2116 {"Africa/Libreville", "Africa/Lagos"},
2117 {"Africa/Lome", "Africa/Abidjan"},
2118 {"Africa/Luanda", "Africa/Lagos"},
2119 {"Africa/Lubumbashi", "Africa/Maputo"},
2120 {"Africa/Lusaka", "Africa/Maputo"},
2121 {"Africa/Maseru", "Africa/Johannesburg"},
2122 {"Africa/Malabo", "Africa/Lagos"},
2123 {"Africa/Mbabane", "Africa/Johannesburg"},
2124 {"Africa/Mogadishu", "Africa/Nairobi"},
2125 {"Africa/Niamey", "Africa/Lagos"},
2126 {"Africa/Nouakchott", "Africa/Abidjan"},
2127 {"Africa/Ouagadougou", "Africa/Abidjan"},
2128 {"Africa/Porto-Novo", "Africa/Lagos"},
2129 {"Africa/Sao_Tome", "Africa/Abidjan"},
2130 {"America/Antigua", "America/Puerto_Rico"},
2131 {"America/Anguilla", "America/Puerto_Rico"},
2132 {"America/Aruba", "America/Puerto_Rico"},
2133 {"America/Atikokan", "America/Panama"},
2134 {"America/Blanc-Sablon", "America/Puerto_Rico"},
2135 {"America/Cayman", "America/Panama"},
2136 {"America/Coral_Harbour", "America/Panama"},
2137 {"America/Creston", "America/Phoenix"},
2138 {"America/Curacao", "America/Puerto_Rico"},
2139 {"America/Dominica", "America/Puerto_Rico"},
2140 {"America/Grenada", "America/Puerto_Rico"},
2141 {"America/Guadeloupe", "America/Puerto_Rico"},
2142 {"America/Kralendijk", "America/Puerto_Rico"},
2143 {"America/Lower_Princes", "America/Puerto_Rico"},
2144 {"America/Marigot", "America/Puerto_Rico"},
2145 {"America/Montreal", "America/Toronto"},
2146 {"America/Montserrat", "America/Puerto_Rico"},
2147 {"America/Nassau", "America/Toronto"},
2148 {"America/Nipigon", "America/Toronto"},
2149 {"America/Pangnirtung", "America/Iqaluit"},
2150 {"America/Port_of_Spain", "America/Puerto_Rico"},
2151 {"America/Rainy_River", "America/Winnipeg"},
2152 {"America/Santa_Isabel", "America/Tijuana"},
2153 {"America/Shiprock", "America/Denver"},
2154 {"America/St_Barthelemy", "America/Puerto_Rico"},
2155 {"America/St_Kitts", "America/Puerto_Rico"},
2156 {"America/St_Lucia", "America/Puerto_Rico"},
2157 {"America/St_Thomas", "America/Puerto_Rico"},
2158 {"America/St_Vincent", "America/Puerto_Rico"},
2159 {"America/Thunder_Bay", "America/Toronto"},
2160 {"America/Tortola", "America/Puerto_Rico"},
2161 {"America/Virgin", "America/Puerto_Rico"},
2162 {"America/Yellowknife", "America/Edmonton"},
2163 {"Antarctica/DumontDUrville", "Pacific/Port_Moresby"},
2164 {"Antarctica/South_Pole", "Antarctica/McMurdo"},
2165 {"Antarctica/Syowa", "Asia/Riyadh"},
2166 {"Arctic/Longyearbyen", "Europe/Berlin"},
2167 {"Asia/Aden", "Asia/Riyadh"},
2168 {"Asia/Brunei", "Asia/Kuching"},
2169 {"Asia/Kuala_Lumpur", "Asia/Singapore"},
2170 {"Asia/Kuwait", "Asia/Riyadh"},
2171 {"Asia/Muscat", "Asia/Dubai"},
2172 {"Asia/Phnom_Penh", "Asia/Bangkok"},
2173 {"Asia/Qatar", "Asia/Bahrain"},
2174 {"Asia/Vientiane", "Asia/Bangkok"},
2175 {"Atlantic/Jan_Mayen", "Europe/Berlin"},
2176 {"Atlantic/Reykjavik", "Africa/Abidjan"},
2177 {"Atlantic/St_Helena", "Africa/Abidjan"},
2178 {"Australia/Currie", "Australia/Hobart"},
2179 {"Australia/Tasmania", "Australia/Hobart"},
2180 {"Europe/Bratislava", "Europe/Prague"},
2181 {"Europe/Brussels", "Europe/Amsterdam"},
2182 {"Europe/Busingen", "Europe/Zurich"},
2183 {"Europe/Copenhagen", "Europe/Berlin"},
2184 {"Europe/Guernsey", "Europe/London"},
2185 {"Europe/Isle_of_Man", "Europe/London"},
2186 {"Europe/Jersey", "Europe/London"},
2187 {"Europe/Ljubljana", "Europe/Belgrade"},
2188 {"Europe/Luxembourg", "Europe/Amsterdam"},
2189 {"Europe/Mariehamn", "Europe/Helsinki"},
2190 {"Europe/Monaco", "Europe/Paris"},
2191 {"Europe/Oslo", "Europe/Berlin"},
2192 {"Europe/Podgorica", "Europe/Belgrade"},
2193 {"Europe/San_Marino", "Europe/Rome"},
2194 {"Europe/Sarajevo", "Europe/Belgrade"},
2195 {"Europe/Skopje", "Europe/Belgrade"},
2196 {"Europe/Stockholm", "Europe/Berlin"},
2197 {"Europe/Uzhgorod", "Europe/Kiev"},
2198 {"Europe/Vaduz", "Europe/Zurich"},
2199 {"Europe/Vatican", "Europe/Rome"},
2200 {"Europe/Zagreb", "Europe/Belgrade"},
2201 {"Europe/Zaporozhye", "Europe/Kiev"},
2202 {"Indian/Antananarivo", "Africa/Nairobi"},
2203 {"Indian/Christmas", "Asia/Bangkok"},
2204 {"Indian/Cocos", "Asia/Rangoon"},
2205 {"Indian/Comoro", "Africa/Nairobi"},
2206 {"Indian/Mahe", "Asia/Dubai"},
2207 {"Indian/Maldives", "Indian/Kerguelen"},
2208 {"Indian/Mayotte", "Africa/Nairobi"},
2209 {"Indian/Reunion", "Asia/Dubai"},
2210 {"Pacific/Auckland", "Antarctica/McMurdo"},
2211 {"Pacific/Johnston", "Pacific/Honolulu"},
2212 {"Pacific/Majuro", "Pacific/Funafuti"},
2213 {"Pacific/Midway", "Pacific/Pago_Pago"},
2214 {"Pacific/Ponape", "Pacific/Guadalcanal"},
2215 {"Pacific/Saipan", "Pacific/Guam"},
2216 {"Pacific/Tarawa", "Pacific/Funafuti"},
2217 {"Pacific/Truk", "Pacific/Port_Moresby"},
2218 {"Pacific/Wake", "Pacific/Funafuti"},
2219 {"Pacific/Wallis", "Pacific/Funafuti"},
2220 {nullptr, nullptr}
2221 };
2222
2223 // Following IDs are aliases of Etc/GMT in CLDR,
2224 // but Olson tzdata has 3 independent definitions
2225 // for Etc/GMT, Etc/UTC, Etc/UCT.
2226 // Until we merge them into one equivalent group
2227 // in zoneinfo.res, we exclude them in the test
2228 // below.
2229 static const char* excluded2[] = {
2230 "Etc/UCT", "UCT",
2231 "Etc/UTC", "UTC",
2232 "Etc/Universal", "Universal",
2233 "Etc/Zulu", "Zulu", nullptr
2234 };
2235
2236 // Walk through equivalency groups
2237 UErrorCode ec = U_ZERO_ERROR;
2238 int32_t s_length, i, j, k;
2239 StringEnumeration* s = TimeZone::createEnumeration(ec);
2240 if (U_FAILURE(ec)) {
2241 dataerrln("Unable to create TimeZone enumeration");
2242 return;
2243 }
2244 UnicodeString canonicalID, tmpCanonical;
2245 s_length = s->count(ec);
2246 for (i = 0; i < s_length;++i) {
2247 const UnicodeString *tzid = s->snext(ec);
2248 int32_t nEquiv = TimeZone::countEquivalentIDs(*tzid);
2249 if (nEquiv == 0) {
2250 continue;
2251 }
2252 UBool bFoundCanonical = false;
2253 // Make sure getCanonicalID returns the exact same result
2254 // for all entries within a same equivalency group with some
2255 // exceptions listed in exluded1.
2256 // Also, one of them must be canonical id.
2257 for (j = 0; j < nEquiv; j++) {
2258 UnicodeString tmp = TimeZone::getEquivalentID(*tzid, j);
2259 TimeZone::getCanonicalID(tmp, tmpCanonical, ec);
2260 if (U_FAILURE(ec)) {
2261 errln((UnicodeString)"FAIL: getCanonicalID(" + tmp + ") failed.");
2262 ec = U_ZERO_ERROR;
2263 continue;
2264 }
2265 // Some exceptional cases
2266 for (k = 0; excluded1[k].alias != nullptr; k++) {
2267 if (tmpCanonical == excluded1[k].alias) {
2268 tmpCanonical = excluded1[k].zone;
2269 break;
2270 }
2271 }
2272 if (j == 0) {
2273 canonicalID = tmpCanonical;
2274 } else if (canonicalID != tmpCanonical) {
2275 errln("FAIL: getCanonicalID(" + tmp + ") returned " + tmpCanonical + " expected:" + canonicalID);
2276 }
2277
2278 if (canonicalID == tmp) {
2279 bFoundCanonical = true;
2280 }
2281 }
2282 // At least one ID in an equvalency group must match the
2283 // canonicalID
2284 if (bFoundCanonical == false) {
2285 // test exclusion because of differences between Olson tzdata and CLDR
2286 UBool isExcluded = false;
2287 for (k = 0; excluded2[k] != nullptr; k++) {
2288 if (*tzid == UnicodeString(excluded2[k])) {
2289 isExcluded = true;
2290 break;
2291 }
2292 }
2293 if (isExcluded) {
2294 continue;
2295 }
2296 errln((UnicodeString)"FAIL: No timezone ids match the canonical ID " + canonicalID);
2297 }
2298 }
2299 delete s;
2300
2301 // Testing some special cases
2302 static const struct {
2303 const char *id;
2304 const char *expected;
2305 UBool isSystem;
2306 } data[] = {
2307 {"GMT-03", "GMT-03:00", false},
2308 {"GMT+4", "GMT+04:00", false},
2309 {"GMT-055", "GMT-00:55", false},
2310 {"GMT+430", "GMT+04:30", false},
2311 {"GMT-12:15", "GMT-12:15", false},
2312 {"GMT-091015", "GMT-09:10:15", false},
2313 {"GMT+1:90", nullptr, false},
2314 {"America/Argentina/Buenos_Aires", "America/Buenos_Aires", true},
2315 {"Etc/Unknown", "Etc/Unknown", false},
2316 {"bogus", nullptr, false},
2317 {"", nullptr, false},
2318 {"America/Marigot", "America/Marigot", true}, // Olson link, but CLDR canonical (#8953)
2319 {"Europe/Bratislava", "Europe/Bratislava", true}, // Same as above
2320 {nullptr, nullptr, false}
2321 };
2322
2323 UBool isSystemID;
2324 for (i = 0; data[i].id != nullptr; i++) {
2325 TimeZone::getCanonicalID(UnicodeString(data[i].id), canonicalID, isSystemID, ec);
2326 if (U_FAILURE(ec)) {
2327 if (ec != U_ILLEGAL_ARGUMENT_ERROR || data[i].expected != nullptr) {
2328 errln((UnicodeString)"FAIL: getCanonicalID(\"" + data[i].id
2329 + "\") returned status U_ILLEGAL_ARGUMENT_ERROR");
2330 }
2331 ec = U_ZERO_ERROR;
2332 continue;
2333 }
2334 if (canonicalID != data[i].expected) {
2335 dataerrln((UnicodeString)"FAIL: getCanonicalID(\"" + data[i].id
2336 + "\") returned " + canonicalID + " - expected: " + data[i].expected);
2337 }
2338 if (isSystemID != data[i].isSystem) {
2339 dataerrln((UnicodeString)"FAIL: getCanonicalID(\"" + data[i].id
2340 + "\") set " + isSystemID + " to isSystemID");
2341 }
2342 }
2343 }
2344
2345 //
2346 // Test Display Names, choosing zones and lcoales where there are multiple
2347 // meta-zones defined.
2348 //
2349 static struct {
2350 const char *zoneName;
2351 const char *localeName;
2352 UBool summerTime;
2353 TimeZone::EDisplayType style;
2354 const char *expectedDisplayName; }
2355 zoneDisplayTestData [] = {
2356 // zone id locale summer format expected display name
2357 {"Europe/London", "en", false, TimeZone::SHORT, "GMT"},
2358 {"Europe/London", "en", false, TimeZone::LONG, "Greenwich Mean Time"},
2359 {"Europe/London", "en", true, TimeZone::SHORT, "GMT+1" /*"BST"*/},
2360 {"Europe/London", "en", true, TimeZone::LONG, "British Summer Time"},
2361
2362 {"America/Anchorage", "en", false, TimeZone::SHORT, "AKST"},
2363 {"America/Anchorage", "en", false, TimeZone::LONG, "Alaska Standard Time"},
2364 {"America/Anchorage", "en", true, TimeZone::SHORT, "AKDT"},
2365 {"America/Anchorage", "en", true, TimeZone::LONG, "Alaska Daylight Time"},
2366
2367 // Southern Hemisphere, all data from meta:Australia_Western
2368 {"Australia/Perth", "en", false, TimeZone::SHORT, "GMT+8"/*"AWST"*/},
2369 {"Australia/Perth", "en", false, TimeZone::LONG, "Australian Western Standard Time"},
2370 // Note: Perth does not observe DST currently. When display name is missing,
2371 // the localized GMT format with the current offset is used even daylight name was
2372 // requested. See #9350.
2373 {"Australia/Perth", "en", true, TimeZone::SHORT, "GMT+8"/*"AWDT"*/},
2374 {"Australia/Perth", "en", true, TimeZone::LONG, "Australian Western Daylight Time"},
2375
2376 {"America/Sao_Paulo", "en", false, TimeZone::SHORT, "GMT-3"/*"BRT"*/},
2377 {"America/Sao_Paulo", "en", false, TimeZone::LONG, "Brasilia Standard Time"},
2378
2379 // Per https://mm.icann.org/pipermail/tz-announce/2019-July/000056.html
2380 // Brazil has canceled DST and will stay on standard time indefinitely.
2381 // {"America/Sao_Paulo", "en", true, TimeZone::SHORT, "GMT-2"/*"BRST"*/},
2382 // {"America/Sao_Paulo", "en", true, TimeZone::LONG, "Brasilia Summer Time"},
2383
2384 // No Summer Time, but had it before 1983.
2385 {"Pacific/Honolulu", "en", false, TimeZone::SHORT, "HST"},
2386 {"Pacific/Honolulu", "en", false, TimeZone::LONG, "Hawaii-Aleutian Standard Time"},
2387 {"Pacific/Honolulu", "en", true, TimeZone::SHORT, "HDT"},
2388 {"Pacific/Honolulu", "en", true, TimeZone::LONG, "Hawaii-Aleutian Daylight Time"},
2389
2390 // Northern, has Summer, not commonly used.
2391 {"Europe/Helsinki", "en", false, TimeZone::SHORT, "GMT+2"/*"EET"*/},
2392 {"Europe/Helsinki", "en", false, TimeZone::LONG, "Eastern European Standard Time"},
2393 {"Europe/Helsinki", "en", true, TimeZone::SHORT, "GMT+3"/*"EEST"*/},
2394 {"Europe/Helsinki", "en", true, TimeZone::LONG, "Eastern European Summer Time"},
2395
2396 // Repeating the test data for DST. The test data below trigger the problem reported
2397 // by Ticket#6644
2398 {"Europe/London", "en", true, TimeZone::SHORT, "GMT+1" /*"BST"*/},
2399 {"Europe/London", "en", true, TimeZone::LONG, "British Summer Time"},
2400
2401 {nullptr, nullptr, false, TimeZone::SHORT, nullptr} // nullptr values terminate list
2402 };
2403
TestDisplayNamesMeta()2404 void TimeZoneTest::TestDisplayNamesMeta() {
2405 UErrorCode status = U_ZERO_ERROR;
2406 GregorianCalendar cal(*TimeZone::getGMT(), status);
2407 if (failure(status, "GregorianCalendar", true)) return;
2408
2409 UBool sawAnError = false;
2410 for (int testNum = 0; zoneDisplayTestData[testNum].zoneName != nullptr; testNum++) {
2411 Locale locale = Locale::createFromName(zoneDisplayTestData[testNum].localeName);
2412 TimeZone *zone = TimeZone::createTimeZone(zoneDisplayTestData[testNum].zoneName);
2413 UnicodeString displayName;
2414 zone->getDisplayName(zoneDisplayTestData[testNum].summerTime,
2415 zoneDisplayTestData[testNum].style,
2416 locale,
2417 displayName);
2418 if (displayName != zoneDisplayTestData[testNum].expectedDisplayName) {
2419 char name[100];
2420 UErrorCode status = U_ZERO_ERROR;
2421 displayName.extract(name, 100, nullptr, status);
2422 if (isDevelopmentBuild) {
2423 sawAnError = true;
2424 dataerrln("Incorrect time zone display name. zone = \"%s\",\n"
2425 " locale = \"%s\", style = %s, Summertime = %d\n"
2426 " Expected \"%s\", "
2427 " Got \"%s\"\n Error: %s", zoneDisplayTestData[testNum].zoneName,
2428 zoneDisplayTestData[testNum].localeName,
2429 zoneDisplayTestData[testNum].style==TimeZone::SHORT ?
2430 "SHORT" : "LONG",
2431 zoneDisplayTestData[testNum].summerTime,
2432 zoneDisplayTestData[testNum].expectedDisplayName,
2433 name,
2434 u_errorName(status));
2435 } else {
2436 logln("Incorrect time zone display name. zone = \"%s\",\n"
2437 " locale = \"%s\", style = %s, Summertime = %d\n"
2438 " Expected \"%s\", "
2439 " Got \"%s\"\n", zoneDisplayTestData[testNum].zoneName,
2440 zoneDisplayTestData[testNum].localeName,
2441 zoneDisplayTestData[testNum].style==TimeZone::SHORT ?
2442 "SHORT" : "LONG",
2443 zoneDisplayTestData[testNum].summerTime,
2444 zoneDisplayTestData[testNum].expectedDisplayName,
2445 name);
2446 }
2447 }
2448 delete zone;
2449 }
2450 if (sawAnError) {
2451 dataerrln("***Note: Errors could be the result of changes to zoneStrings locale data");
2452 }
2453 }
2454
TestGetRegion()2455 void TimeZoneTest::TestGetRegion()
2456 {
2457 static const struct {
2458 const char *id;
2459 const char *region;
2460 } data[] = {
2461 {"America/Los_Angeles", "US"},
2462 {"America/Indianapolis", "US"}, // CLDR canonical, Olson backward
2463 {"America/Indiana/Indianapolis", "US"}, // CLDR alias
2464 {"Mexico/General", "MX"}, // Link America/Mexico_City, Olson backward
2465 {"Etc/UTC", "001"},
2466 {"EST5EDT", "001"},
2467 {"PST", "US"}, // Link America/Los_Angeles
2468 {"Europe/Helsinki", "FI"},
2469 {"Europe/Mariehamn", "AX"}, // Link Europe/Helsinki, but in zone.tab
2470 {"Asia/Riyadh", "SA"},
2471 // tz file solar87 was removed from tzdata2013i
2472 // {"Asia/Riyadh87", "001"}, // this should be "SA" actually, but not in zone.tab
2473 {"Atlantic/Jan_Mayen", "SJ"},
2474 {"Pacific/Truk", "FM"},
2475 {"Etc/Unknown", nullptr}, // CLDR canonical, but not a sysmte zone ID
2476 {"bogus", nullptr}, // bogus
2477 {"GMT+08:00", nullptr}, // a custom ID, not a system zone ID
2478 {nullptr, nullptr}
2479 };
2480
2481 int32_t i;
2482 char region[4];
2483 UErrorCode sts;
2484 for (i = 0; data[i].id; i++) {
2485 sts = U_ZERO_ERROR;
2486 TimeZone::getRegion(data[i].id, region, sizeof(region), sts);
2487 if (U_SUCCESS(sts)) {
2488 if (data[i].region == nullptr) {
2489 errln((UnicodeString)"Fail: getRegion(\"" + data[i].id + "\") returns "
2490 + region + " [expected: U_ILLEGAL_ARGUMENT_ERROR]");
2491 } else if (uprv_strcmp(region, data[i].region) != 0) {
2492 errln((UnicodeString)"Fail: getRegion(\"" + data[i].id + "\") returns "
2493 + region + " [expected: " + data[i].region + "]");
2494 }
2495 } else if (sts == U_ILLEGAL_ARGUMENT_ERROR) {
2496 if (data[i].region != nullptr) {
2497 dataerrln((UnicodeString)"Fail: getRegion(\"" + data[i].id
2498 + "\") returns error status U_ILLEGAL_ARGUMENT_ERROR [expected: "
2499 + data[i].region + "]");
2500 }
2501 } else {
2502 errln((UnicodeString)"Fail: getRegion(\"" + data[i].id
2503 + "\") returns an unexpected error status");
2504 }
2505 }
2506
2507 // Extra test cases for short buffer
2508 int32_t len;
2509 char region2[2];
2510 sts = U_ZERO_ERROR;
2511
2512 len = TimeZone::getRegion("America/New_York", region2, sizeof(region2), sts);
2513 if (sts == U_ILLEGAL_ARGUMENT_ERROR) {
2514 dataerrln("Error calling TimeZone::getRegion");
2515 } else {
2516 if (sts != U_STRING_NOT_TERMINATED_WARNING) {
2517 errln("Expected U_STRING_NOT_TERMINATED_WARNING");
2518 }
2519 if (len != 2) { // length of "US"
2520 errln("Incorrect result length");
2521 }
2522 if (uprv_strncmp(region2, "US", 2) != 0) {
2523 errln("Incorrect result");
2524 }
2525 }
2526
2527 char region1[1];
2528 sts = U_ZERO_ERROR;
2529
2530 len = TimeZone::getRegion("America/Chicago", region1, sizeof(region1), sts);
2531 if (sts == U_ILLEGAL_ARGUMENT_ERROR) {
2532 dataerrln("Error calling TimeZone::getRegion");
2533 } else {
2534 if (sts != U_BUFFER_OVERFLOW_ERROR) {
2535 errln("Expected U_BUFFER_OVERFLOW_ERROR");
2536 }
2537 if (len != 2) { // length of "US"
2538 errln("Incorrect result length");
2539 }
2540 }
2541 }
2542
TestGetUnknown()2543 void TimeZoneTest::TestGetUnknown() {
2544 const TimeZone &unknown = TimeZone::getUnknown();
2545 UnicodeString expectedID = UNICODE_STRING_SIMPLE("Etc/Unknown");
2546 UnicodeString id;
2547 assertEquals("getUnknown() wrong ID", expectedID, unknown.getID(id));
2548 assertTrue("getUnknown() wrong offset", 0 == unknown.getRawOffset());
2549 assertFalse("getUnknown() uses DST", unknown.useDaylightTime());
2550 }
2551
TestGetGMT()2552 void TimeZoneTest::TestGetGMT() {
2553 const TimeZone *gmt = TimeZone::getGMT();
2554 UnicodeString expectedID = UNICODE_STRING_SIMPLE("GMT");
2555 UnicodeString id;
2556 assertEquals("getGMT() wrong ID", expectedID, gmt->getID(id));
2557 assertTrue("getGMT() wrong offset", 0 == gmt->getRawOffset());
2558 assertFalse("getGMT() uses DST", gmt->useDaylightTime());
2559 }
2560
TestGetWindowsID()2561 void TimeZoneTest::TestGetWindowsID() {
2562 static const struct {
2563 const char *id;
2564 const char *winid;
2565 } TESTDATA[] = {
2566 {"America/New_York", "Eastern Standard Time"},
2567 {"America/Montreal", "Eastern Standard Time"},
2568 {"America/Los_Angeles", "Pacific Standard Time"},
2569 {"America/Vancouver", "Pacific Standard Time"},
2570 {"Asia/Shanghai", "China Standard Time"},
2571 {"Asia/Chongqing", "China Standard Time"},
2572 {"America/Indianapolis", "US Eastern Standard Time"}, // CLDR canonical name
2573 {"America/Indiana/Indianapolis", "US Eastern Standard Time"}, // tzdb canonical name
2574 {"Asia/Khandyga", "Yakutsk Standard Time"},
2575 {"Australia/Eucla", "Aus Central W. Standard Time"}, // formerly no Windows ID mapping, now has one
2576 {"Bogus", ""},
2577 {nullptr, nullptr},
2578 };
2579
2580 for (int32_t i = 0; TESTDATA[i].id != nullptr; i++) {
2581 UErrorCode sts = U_ZERO_ERROR;
2582 UnicodeString windowsID;
2583
2584 TimeZone::getWindowsID(UnicodeString(TESTDATA[i].id), windowsID, sts);
2585 assertSuccess(TESTDATA[i].id, sts);
2586 assertEquals(TESTDATA[i].id, UnicodeString(TESTDATA[i].winid), windowsID, true);
2587 }
2588 }
2589
TestGetIDForWindowsID()2590 void TimeZoneTest::TestGetIDForWindowsID() {
2591 static const struct {
2592 const char *winid;
2593 const char *region;
2594 const char *id;
2595 } TESTDATA[] = {
2596 {"Eastern Standard Time", nullptr, "America/New_York"},
2597 {"Eastern Standard Time", "US", "America/New_York"},
2598 {"Eastern Standard Time", "CA", "America/Toronto"},
2599 {"Eastern Standard Time", "CN", "America/New_York"},
2600 {"China Standard Time", nullptr, "Asia/Shanghai"},
2601 {"China Standard Time", "CN", "Asia/Shanghai"},
2602 {"China Standard Time", "HK", "Asia/Hong_Kong"},
2603 {"Mid-Atlantic Standard Time", nullptr, ""}, // No tz database mapping
2604 {"Bogus", nullptr, ""},
2605 {nullptr, nullptr, nullptr},
2606 };
2607
2608 for (int32_t i = 0; TESTDATA[i].winid != nullptr; i++) {
2609 UErrorCode sts = U_ZERO_ERROR;
2610 UnicodeString id;
2611
2612 TimeZone::getIDForWindowsID(UnicodeString(TESTDATA[i].winid), TESTDATA[i].region,
2613 id, sts);
2614 assertSuccess(UnicodeString(TESTDATA[i].winid) + "/" + TESTDATA[i].region, sts);
2615 assertEquals(UnicodeString(TESTDATA[i].winid) + "/" + TESTDATA[i].region, TESTDATA[i].id, id, true);
2616 }
2617 }
2618
TestCasablancaNameAndOffset22041()2619 void TimeZoneTest::TestCasablancaNameAndOffset22041() {
2620 std::unique_ptr<TimeZone> zone(TimeZone::createTimeZone("Africa/Casablanca"));
2621 UnicodeString standardName, summerName;
2622 zone->getDisplayName(false, TimeZone::LONG, Locale::getEnglish(), standardName);
2623 zone->getDisplayName(true, TimeZone::LONG, Locale::getEnglish(), summerName);
2624 int32_t raw, dst;
2625 UErrorCode status = U_ZERO_ERROR;
2626 zone->getOffset(Calendar::getNow(), false, raw, dst, status);
2627 assertEquals(u"TimeZone name for Africa/Casablanca should not contain '+02' since it is located in UTC, but got "
2628 + standardName, -1, standardName.indexOf("+02"));
2629 assertEquals(u"TimeZone name for Africa/Casablanca should not contain '+02' since it is located in UTC, but got "
2630 + summerName, -1, summerName.indexOf("+02"));
2631 assertEquals("getRawOffset() and the raw from getOffset(now, false, raw, dst, status) should not be different but got",
2632 zone->getRawOffset(), raw);
2633 }
2634
TestRawOffsetAndOffsetConsistency22041()2635 void TimeZoneTest::TestRawOffsetAndOffsetConsistency22041() {
2636 UErrorCode status = U_ZERO_ERROR;
2637 LocalPointer<StringEnumeration> s(TimeZone::createEnumeration(status));
2638 if (U_FAILURE(status)) {
2639 dataerrln("Unable to create TimeZone enumeration");
2640 return;
2641 }
2642 const char* tz;
2643 UDate now = Calendar::getNow();
2644 while ((tz = s->next(nullptr, status)) != nullptr && U_SUCCESS(status)) {
2645 std::unique_ptr<TimeZone> zone(TimeZone::createTimeZone(tz));
2646 int32_t raw, dst;
2647 zone->getOffset(now, false, raw, dst, status);
2648 if (U_FAILURE(status)) {
2649 errln("TimeZone '%s' getOffset() return error", tz);
2650 }
2651 assertEquals(u"TimeZone '" + UnicodeString(tz) +
2652 u"' getRawOffset() and the raw from getOffset(now, false, raw, dst, status) should not be different but got",
2653 zone->getRawOffset(), raw);
2654 }
2655 }
2656
TestGetIanaID()2657 void TimeZoneTest::TestGetIanaID() {
2658 const char16_t* UNKNOWN = u"Etc/Unknown";
2659 static const struct {
2660 const char16_t* id;
2661 const char16_t* expected;
2662 } TESTDATA[] = {
2663 {u"", UNKNOWN},
2664 {nullptr, UNKNOWN},
2665 {UNKNOWN, UNKNOWN},
2666 {u"America/New_York", u"America/New_York"},
2667 {u"Asia/Calcutta", u"Asia/Kolkata"},
2668 {u"Europe/Kiev", u"Europe/Kyiv"},
2669 {u"Europe/Zaporozhye", u"Europe/Kyiv"},
2670 {u"Etc/GMT-1", u"Etc/GMT-1"},
2671 {u"Etc/GMT+20", UNKNOWN},
2672 {u"PST8PDT", u"PST8PDT"},
2673 {u"GMT-08:00", UNKNOWN},
2674 {nullptr, nullptr}
2675 };
2676
2677 for (int32_t i = 0; TESTDATA[i].expected != nullptr; i++) {
2678 UErrorCode sts = U_ZERO_ERROR;
2679 UnicodeString inputID(TESTDATA[i].id);
2680 UnicodeString ianaID;
2681
2682 TimeZone::getIanaID(inputID, ianaID, sts);
2683 if (u_strcmp(TESTDATA[i].expected, UNKNOWN) == 0) {
2684 assertEquals(inputID + " should fail", (int32_t)U_ILLEGAL_ARGUMENT_ERROR, sts);
2685 assertTrue(inputID + " should set bogus", ianaID.isBogus());
2686 } else {
2687 assertEquals(inputID, UnicodeString(TESTDATA[i].expected), ianaID);
2688 // Calling getIanaID with an IANA ID should return the same
2689 UnicodeString ianaID2;
2690 TimeZone::getIanaID(ianaID, ianaID2, sts);
2691 assertEquals(ianaID, ianaID, ianaID2);
2692 }
2693 }
2694 }
2695
TestGMTMinus24ICU22526()2696 void TimeZoneTest::TestGMTMinus24ICU22526() {
2697 UErrorCode status = U_ZERO_ERROR;
2698 LocalPointer<TimeZone> tz(TimeZone::createTimeZone("GMT-23:59"), status);
2699 U_ASSERT(U_SUCCESS(status));
2700 GregorianCalendar gc(tz.orphan(), status);
2701 gc.setTime(123456789, status);
2702 gc.get(UCAL_MONTH, status);
2703 }
2704 #endif /* #if !UCONFIG_NO_FORMATTING */
2705