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