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