1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3 /*
4 *******************************************************************************
5 * Copyright (C) 2007-2015, International Business Machines Corporation and *
6 * others. All Rights Reserved. *
7 *******************************************************************************
8 */
9 #include "unicode/utypes.h"
10
11 #if !UCONFIG_NO_FORMATTING
12
13 #include "tzfmttst.h"
14
15 #include "unicode/timezone.h"
16 #include "unicode/simpletz.h"
17 #include "unicode/calendar.h"
18 #include "unicode/strenum.h"
19 #include "unicode/smpdtfmt.h"
20 #include "unicode/uchar.h"
21 #include "unicode/basictz.h"
22 #include "unicode/tzfmt.h"
23 #include "unicode/localpointer.h"
24 #include "unicode/utf16.h"
25
26 #include "cstring.h"
27 #include "cstr.h"
28 #include "mutex.h"
29 #include "simplethread.h"
30 #include "uassert.h"
31 #include "zonemeta.h"
32
33 static const char* PATTERNS[] = {
34 "z",
35 "zzzz",
36 "Z", // equivalent to "xxxx"
37 "ZZZZ", // equivalent to "OOOO"
38 "v",
39 "vvvv",
40 "O",
41 "OOOO",
42 "X",
43 "XX",
44 "XXX",
45 "XXXX",
46 "XXXXX",
47 "x",
48 "xx",
49 "xxx",
50 "xxxx",
51 "xxxxx",
52 "V",
53 "VV",
54 "VVV",
55 "VVVV"
56 };
57
58 static const char16_t ETC_UNKNOWN[] = {0x45, 0x74, 0x63, 0x2F, 0x55, 0x6E, 0x6B, 0x6E, 0x6F, 0x77, 0x6E, 0};
59
60 static const char16_t ETC_SLASH[] = { 0x45, 0x74, 0x63, 0x2F, 0 }; // "Etc/"
61 static const char16_t SYSTEMV_SLASH[] = { 0x53, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x56, 0x2F, 0 }; // "SystemV/
62 static const char16_t RIYADH8[] = { 0x52, 0x69, 0x79, 0x61, 0x64, 0x68, 0x38, 0 }; // "Riyadh8"
63
contains(const char ** list,const char * str)64 static UBool contains(const char** list, const char* str) {
65 for (int32_t i = 0; list[i]; i++) {
66 if (uprv_strcmp(list[i], str) == 0) {
67 return true;
68 }
69 }
70 return false;
71 }
72
73 void
runIndexedTest(int32_t index,UBool exec,const char * & name,char *)74 TimeZoneFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ )
75 {
76 if (exec) {
77 logln("TestSuite TimeZoneFormatTest");
78 }
79 switch (index) {
80 TESTCASE(0, TestTimeZoneRoundTrip);
81 TESTCASE(1, TestTimeRoundTrip);
82 TESTCASE(2, TestParse);
83 TESTCASE(3, TestISOFormat);
84 TESTCASE(4, TestFormat);
85 TESTCASE(5, TestFormatTZDBNames);
86 TESTCASE(6, TestFormatCustomZone);
87 TESTCASE(7, TestFormatTZDBNamesAllZoneCoverage);
88 TESTCASE(8, TestAdoptDefaultThreadSafe);
89 TESTCASE(9, TestCentralTime);
90 TESTCASE(10, TestBogusLocale);
91 TESTCASE(11, Test22614GetMetaZoneNamesNotCrash);
92 TESTCASE(12, Test22615NonASCIIID);
93 default: name = ""; break;
94 }
95 }
96
97 void
TestTimeZoneRoundTrip()98 TimeZoneFormatTest::TestTimeZoneRoundTrip() {
99 UErrorCode status = U_ZERO_ERROR;
100
101 SimpleTimeZone unknownZone(-31415, ETC_UNKNOWN);
102 int32_t badDstOffset = -1234;
103 int32_t badZoneOffset = -2345;
104
105 int32_t testDateData[][3] = {
106 {2007, 1, 15},
107 {2007, 6, 15},
108 {1990, 1, 15},
109 {1990, 6, 15},
110 {1960, 1, 15},
111 {1960, 6, 15},
112 };
113
114 Calendar* cal = Calendar::createInstance(TimeZone::createTimeZone(UnicodeString("UTC")), status);
115 if (U_FAILURE(status)) {
116 dataerrln("Calendar::createInstance failed: %s", u_errorName(status));
117 return;
118 }
119
120 // Set up rule equivalency test range
121 UDate low, high;
122 cal->set(1900, UCAL_JANUARY, 1);
123 low = cal->getTime(status);
124 cal->set(2040, UCAL_JANUARY, 1);
125 high = cal->getTime(status);
126 if (U_FAILURE(status)) {
127 errln("getTime failed");
128 return;
129 }
130
131 // Set up test dates
132 UDate DATES[UPRV_LENGTHOF(testDateData)];
133 const int32_t nDates = UPRV_LENGTHOF(testDateData);
134 cal->clear();
135 for (int32_t i = 0; i < nDates; i++) {
136 cal->set(testDateData[i][0], testDateData[i][1], testDateData[i][2]);
137 DATES[i] = cal->getTime(status);
138 if (U_FAILURE(status)) {
139 errln("getTime failed");
140 return;
141 }
142 }
143
144 // Set up test locales
145 const Locale testLocales[] = {
146 Locale("en"),
147 Locale("en_CA"),
148 Locale("fr"),
149 Locale("zh_Hant"),
150 Locale("fa"),
151 Locale("ccp")
152 };
153
154 const Locale *LOCALES;
155 int32_t nLocales;
156
157 if (quick) {
158 LOCALES = testLocales;
159 nLocales = UPRV_LENGTHOF(testLocales);
160 } else {
161 LOCALES = Locale::getAvailableLocales(nLocales);
162 }
163
164 StringEnumeration *tzids = TimeZone::createEnumeration(status);
165 if (U_FAILURE(status)) {
166 dataerrln("Unable to create TimeZone enumeration");
167 return;
168 }
169 int32_t inRaw, inDst;
170 int32_t outRaw, outDst;
171
172 // Run the roundtrip test
173 for (int32_t locidx = 0; locidx < nLocales; locidx++) {
174 UnicodeString localGMTString;
175 SimpleDateFormat gmtFmt(UnicodeString("ZZZZ"), LOCALES[locidx], status);
176 if (U_FAILURE(status)) {
177 dataerrln("Error creating SimpleDateFormat - %s", u_errorName(status));
178 continue;
179 }
180 gmtFmt.setTimeZone(*TimeZone::getGMT());
181 gmtFmt.format(0.0, localGMTString);
182
183 for (int32_t patidx = 0; patidx < UPRV_LENGTHOF(PATTERNS); patidx++) {
184 SimpleDateFormat* sdf = new SimpleDateFormat(UnicodeString(PATTERNS[patidx]), LOCALES[locidx], status);
185 if (U_FAILURE(status)) {
186 dataerrln(UnicodeString("new SimpleDateFormat failed for pattern ") +
187 PATTERNS[patidx] + " for locale " + LOCALES[locidx].getName() + " - " + u_errorName(status));
188 status = U_ZERO_ERROR;
189 continue;
190 }
191
192 tzids->reset(status);
193 const UnicodeString *tzid;
194 while ((tzid = tzids->snext(status))) {
195 TimeZone *tz = TimeZone::createTimeZone(*tzid);
196
197 for (int32_t datidx = 0; datidx < nDates; datidx++) {
198 UnicodeString tzstr;
199 FieldPosition fpos(FieldPosition::DONT_CARE);
200 // Format
201 sdf->setTimeZone(*tz);
202 sdf->format(DATES[datidx], tzstr, fpos);
203
204 // Before parse, set unknown zone to SimpleDateFormat instance
205 // just for making sure that it does not depends on the time zone
206 // originally set.
207 sdf->setTimeZone(unknownZone);
208
209 // Parse
210 ParsePosition pos(0);
211 Calendar *outcal = Calendar::createInstance(unknownZone, status);
212 if (U_FAILURE(status)) {
213 errln("Failed to create an instance of calendar for receiving parse result.");
214 status = U_ZERO_ERROR;
215 continue;
216 }
217 outcal->set(UCAL_DST_OFFSET, badDstOffset);
218 outcal->set(UCAL_ZONE_OFFSET, badZoneOffset);
219
220 sdf->parse(tzstr, *outcal, pos);
221
222 // Check the result
223 const TimeZone &outtz = outcal->getTimeZone();
224 UnicodeString outtzid;
225 outtz.getID(outtzid);
226
227 tz->getOffset(DATES[datidx], false, inRaw, inDst, status);
228 if (U_FAILURE(status)) {
229 errln(UnicodeString("Failed to get offsets from time zone") + *tzid);
230 status = U_ZERO_ERROR;
231 }
232 outtz.getOffset(DATES[datidx], false, outRaw, outDst, status);
233 if (U_FAILURE(status)) {
234 errln(UnicodeString("Failed to get offsets from time zone") + outtzid);
235 status = U_ZERO_ERROR;
236 }
237
238 if (uprv_strcmp(PATTERNS[patidx], "V") == 0) {
239 // Short zone ID - should support roundtrip for canonical CLDR IDs
240 UnicodeString canonicalID;
241 TimeZone::getCanonicalID(*tzid, canonicalID, status);
242 if (U_FAILURE(status)) {
243 // Unknown ID - we should not get here
244 errln(UnicodeString("Unknown ID ") + *tzid);
245 status = U_ZERO_ERROR;
246 } else if (outtzid != canonicalID) {
247 if (outtzid.compare(ETC_UNKNOWN, -1) == 0) {
248 // Note that some zones like Asia/Riyadh87 does not have
249 // short zone ID and "unk" is used as fallback
250 logln(UnicodeString("Canonical round trip failed (probably as expected); tz=") + *tzid
251 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
252 + ", time=" + DATES[datidx] + ", str=" + tzstr
253 + ", outtz=" + outtzid);
254 } else {
255 errln(UnicodeString("Canonical round trip failed; tz=") + *tzid
256 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
257 + ", time=" + DATES[datidx] + ", str=" + tzstr
258 + ", outtz=" + outtzid);
259 }
260 }
261 } else if (uprv_strcmp(PATTERNS[patidx], "VV") == 0) {
262 // Zone ID - full roundtrip support
263 if (outtzid != *tzid) {
264 errln(UnicodeString("Zone ID round trip failued; tz=") + *tzid
265 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
266 + ", time=" + DATES[datidx] + ", str=" + tzstr
267 + ", outtz=" + outtzid);
268 }
269 } else if (uprv_strcmp(PATTERNS[patidx], "VVV") == 0 || uprv_strcmp(PATTERNS[patidx], "VVVV") == 0) {
270 // Location: time zone rule must be preserved except
271 // zones not actually associated with a specific location.
272 // Time zones in this category do not have "/" in its ID.
273 UnicodeString canonical;
274 TimeZone::getCanonicalID(*tzid, canonical, status);
275 if (U_FAILURE(status)) {
276 // Unknown ID - we should not get here
277 errln(UnicodeString("Unknown ID ") + *tzid);
278 status = U_ZERO_ERROR;
279 } else if (outtzid != canonical) {
280 // Canonical ID did not match - check the rules
281 if (!(dynamic_cast<const BasicTimeZone*>(&outtz))->hasEquivalentTransitions(dynamic_cast<BasicTimeZone&>(*tz), low, high, true, status)) {
282 if (canonical.indexOf(static_cast<char16_t>(0x27) /*'/'*/) == -1) {
283 // Exceptional cases, such as CET, EET, MET and WET
284 logln(UnicodeString("Canonical round trip failed (as expected); tz=") + *tzid
285 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
286 + ", time=" + DATES[datidx] + ", str=" + tzstr
287 + ", outtz=" + outtzid);
288 } else {
289 errln(UnicodeString("Canonical round trip failed; tz=") + *tzid
290 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
291 + ", time=" + DATES[datidx] + ", str=" + tzstr
292 + ", outtz=" + outtzid);
293 }
294 if (U_FAILURE(status)) {
295 errln("hasEquivalentTransitions failed");
296 status = U_ZERO_ERROR;
297 }
298 }
299 }
300
301 } else {
302 UBool isOffsetFormat = (*PATTERNS[patidx] == 'Z'
303 || *PATTERNS[patidx] == 'O'
304 || *PATTERNS[patidx] == 'X'
305 || *PATTERNS[patidx] == 'x');
306 UBool minutesOffset = false;
307 if (*PATTERNS[patidx] == 'X' || *PATTERNS[patidx] == 'x') {
308 minutesOffset = (uprv_strlen(PATTERNS[patidx]) <= 3);
309 }
310
311 if (!isOffsetFormat) {
312 // Check if localized GMT format is used as a fallback of name styles
313 int32_t numDigits = 0;
314 int32_t idx = 0;
315 while (idx < tzstr.length()) {
316 UChar32 cp = tzstr.char32At(idx);
317 if (u_isdigit(cp)) {
318 numDigits++;
319 }
320 idx += U16_LENGTH(cp);
321 }
322 isOffsetFormat = (numDigits > 0);
323 }
324 if (isOffsetFormat || tzstr == localGMTString) {
325 // Localized GMT or ISO: total offset (raw + dst) must be preserved.
326 int32_t inOffset = inRaw + inDst;
327 int32_t outOffset = outRaw + outDst;
328 int32_t diff = outOffset - inOffset;
329 if (minutesOffset) {
330 diff = (diff / 60000) * 60000;
331 }
332 if (diff != 0) {
333 errln(UnicodeString("Offset round trip failed; tz=") + *tzid
334 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
335 + ", time=" + DATES[datidx] + ", str=" + tzstr
336 + ", inOffset=" + inOffset + ", outOffset=" + outOffset);
337 }
338 } else {
339 // Specific or generic: raw offset must be preserved.
340 if (inRaw != outRaw) {
341 if ((strcmp(LOCALES[locidx].getName(), "tg") == 0 || strcmp(LOCALES[locidx].getName(), "tg_TJ") == 0)
342 && logKnownIssue("ICU-22857", "Time zone round test fails for tg/tg_TJ")) {
343 continue;
344 }
345 errln(UnicodeString("Raw offset round trip failed; tz=") + *tzid
346 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
347 + ", time=" + DATES[datidx] + ", str=" + tzstr
348 + ", inRawOffset=" + inRaw + ", outRawOffset=" + outRaw);
349 }
350 }
351 }
352 delete outcal;
353 }
354 delete tz;
355 }
356 delete sdf;
357 }
358 }
359 delete cal;
360 delete tzids;
361 }
362
363 // Special exclusions in TestTimeZoneRoundTrip.
364 // These special cases do not round trip time as designed.
isSpecialTimeRoundTripCase(const char * loc,const UnicodeString & id,const char * pattern,UDate time)365 static UBool isSpecialTimeRoundTripCase(const char* loc,
366 const UnicodeString& id,
367 const char* pattern,
368 UDate time) {
369 struct {
370 const char* loc;
371 const char* id;
372 const char* pattern;
373 UDate time;
374 } EXCLUSIONS[] = {
375 {nullptr, "Asia/Chita", "zzzz", 1414252800000.0},
376 {nullptr, "Asia/Chita", "vvvv", 1414252800000.0},
377 {nullptr, "Asia/Srednekolymsk", "zzzz", 1414241999999.0},
378 {nullptr, "Asia/Srednekolymsk", "vvvv", 1414241999999.0},
379 {nullptr, nullptr, nullptr, U_DATE_MIN}
380 };
381
382 UBool isExcluded = false;
383 for (int32_t i = 0; EXCLUSIONS[i].id != nullptr; i++) {
384 if (EXCLUSIONS[i].loc == nullptr || uprv_strcmp(loc, EXCLUSIONS[i].loc) == 0) {
385 if (id.compare(EXCLUSIONS[i].id) == 0) {
386 if (EXCLUSIONS[i].pattern == nullptr || uprv_strcmp(pattern, EXCLUSIONS[i].pattern) == 0) {
387 if (EXCLUSIONS[i].time == U_DATE_MIN || EXCLUSIONS[i].time == time) {
388 isExcluded = true;
389 }
390 }
391 }
392 }
393 }
394 return isExcluded;
395 }
396
397 // LocaleData. Somewhat misnamed. For TestTimeZoneRoundTrip, specifies the locales and patterns
398 // to be tested, and provides an iterator over these for the multi-threaded test
399 // functions to pick up the next combination to be tested.
400 //
401 // A single global instance of this struct is shared among all
402 // the test threads.
403 //
404 // "locales" is an array of locales to be tested.
405 // PATTERNS (a global) is an array of patterns to be tested for each locale.
406 // "localeIndex" and "patternIndex" keep track of the iteration through the above.
407 // Each of the parallel test threads calls LocaleData::nextTest() in a loop
408 // to find out what to test next. It must be thread safe.
409 struct LocaleData {
410 int32_t localeIndex;
411 int32_t patternIndex;
412 int32_t testCounts;
413 UDate times[UPRV_LENGTHOF(PATTERNS)]; // Performance data, Elapsed time for each pattern.
414 const Locale* locales;
415 int32_t nLocales;
416 UDate START_TIME;
417 UDate END_TIME;
418 int32_t numDone;
419
LocaleDataLocaleData420 LocaleData() : localeIndex(0), patternIndex(0), testCounts(0), locales(nullptr),
421 nLocales(0), START_TIME(0), END_TIME(0), numDone(0) {
422 for (int i=0; i<UPRV_LENGTHOF(times); i++) {
423 times[i] = 0;
424 }
425 }
426
resetTestIterationLocaleData427 void resetTestIteration() {
428 localeIndex = -1;
429 patternIndex = UPRV_LENGTHOF(PATTERNS);
430 numDone = 0;
431 }
432
nextTestLocaleData433 UBool nextTest(int32_t &rLocaleIndex, int32_t &rPatternIndex) {
434 Mutex lock;
435 if (patternIndex >= UPRV_LENGTHOF(PATTERNS) - 1) {
436 if (localeIndex >= nLocales - 1) {
437 return false;
438 }
439 patternIndex = -1;
440 ++localeIndex;
441 }
442 ++patternIndex;
443 rLocaleIndex = localeIndex;
444 rPatternIndex = patternIndex;
445 ++numDone;
446 return true;
447 }
448
addTimeLocaleData449 void addTime(UDate amount, int32_t patIdx) {
450 Mutex lock;
451 U_ASSERT(patIdx < UPRV_LENGTHOF(PATTERNS));
452 times[patIdx] += amount;
453 }
454 };
455
456 static LocaleData *gLocaleData = nullptr;
457
458 void
TestTimeRoundTrip()459 TimeZoneFormatTest::TestTimeRoundTrip() {
460 UErrorCode status = U_ZERO_ERROR;
461 LocalPointer<Calendar> cal(Calendar::createInstance(TimeZone::createTimeZone(UnicodeString("UTC")), status));
462 if (U_FAILURE(status)) {
463 dataerrln("Calendar::createInstance failed: %s", u_errorName(status));
464 return;
465 }
466
467 const char* testAllProp = getProperty("TimeZoneRoundTripAll");
468 UBool bTestAll = (testAllProp && uprv_strcmp(testAllProp, "true") == 0);
469
470 UDate START_TIME, END_TIME;
471 if (bTestAll || !quick) {
472 cal->set(1900, UCAL_JANUARY, 1);
473 } else {
474 cal->set(1999, UCAL_JANUARY, 1);
475 }
476 START_TIME = cal->getTime(status);
477
478 cal->set(2022, UCAL_JANUARY, 1);
479 END_TIME = cal->getTime(status);
480
481 if (U_FAILURE(status)) {
482 errln("getTime failed");
483 return;
484 }
485
486 LocaleData localeData;
487 gLocaleData = &localeData;
488
489 // Set up test locales
490 const Locale locales1[] = {Locale("en")};
491 const Locale locales2[] = {
492 Locale("ar_EG"), Locale("bg_BG"), Locale("ca_ES"), Locale("da_DK"), Locale("de"),
493 Locale("de_DE"), Locale("el_GR"), Locale("en"), Locale("en_AU"), Locale("en_CA"),
494 Locale("en_US"), Locale("es"), Locale("es_ES"), Locale("es_MX"), Locale("fi_FI"),
495 Locale("fr"), Locale("fr_CA"), Locale("fr_FR"), Locale("he_IL"), Locale("hu_HU"),
496 Locale("it"), Locale("it_IT"), Locale("ja"), Locale("ja_JP"), Locale("ko"),
497 Locale("ko_KR"), Locale("nb_NO"), Locale("nl_NL"), Locale("nn_NO"), Locale("pl_PL"),
498 Locale("pt"), Locale("pt_BR"), Locale("pt_PT"), Locale("ru_RU"), Locale("sv_SE"),
499 Locale("th_TH"), Locale("tr_TR"), Locale("zh"), Locale("zh_Hans"), Locale("zh_Hans_CN"),
500 Locale("zh_Hant"), Locale("zh_Hant_TW"), Locale("fa"), Locale("ccp")
501 };
502
503 if (bTestAll) {
504 gLocaleData->locales = Locale::getAvailableLocales(gLocaleData->nLocales);
505 } else if (quick) {
506 gLocaleData->locales = locales1;
507 gLocaleData->nLocales = UPRV_LENGTHOF(locales1);
508 } else {
509 gLocaleData->locales = locales2;
510 gLocaleData->nLocales = UPRV_LENGTHOF(locales2);
511 }
512
513 gLocaleData->START_TIME = START_TIME;
514 gLocaleData->END_TIME = END_TIME;
515 gLocaleData->resetTestIteration();
516
517 // start IntlTest.threadCount threads, each running the function RunTimeRoundTripTests().
518
519 ThreadPool<TimeZoneFormatTest> threads(this, threadCount, &TimeZoneFormatTest::RunTimeRoundTripTests);
520 threads.start(); // Start all threads.
521 threads.join(); // Wait for all threads to finish.
522
523 UDate total = 0;
524 logln("### Elapsed time by patterns ###");
525 for (int32_t i = 0; i < UPRV_LENGTHOF(PATTERNS); i++) {
526 logln(UnicodeString("") + gLocaleData->times[i] + "ms (" + PATTERNS[i] + ")");
527 total += gLocaleData->times[i];
528 }
529 logln(UnicodeString("Total: ") + total + "ms");
530 logln(UnicodeString("Iteration: ") + gLocaleData->testCounts);
531 }
532
533
534 // TimeZoneFormatTest::RunTimeRoundTripTests()
535 // This function loops, running time zone format round trip test cases until there are no more, then returns.
536 // Threading: multiple invocations of this function are started in parallel
537 // by TimeZoneFormatTest::TestTimeRoundTrip()
538 //
RunTimeRoundTripTests(int32_t threadNumber)539 void TimeZoneFormatTest::RunTimeRoundTripTests(int32_t threadNumber) {
540 UErrorCode status = U_ZERO_ERROR;
541 UBool REALLY_VERBOSE = false;
542
543 // These patterns are ambiguous at DST->STD local time overlap
544 const char* AMBIGUOUS_DST_DECESSION[] = { "v", "vvvv", "V", "VV", "VVV", "VVVV", nullptr };
545
546 // These patterns are ambiguous at STD->STD/DST->DST local time overlap
547 const char* AMBIGUOUS_NEGATIVE_SHIFT[] = { "z", "zzzz", "v", "vvvv", "V", "VV", "VVV", "VVVV", nullptr };
548
549 // These patterns only support integer minutes offset
550 const char* MINUTES_OFFSET[] = { "X", "XX", "XXX", "x", "xx", "xxx", nullptr };
551
552 // Workaround for #6338
553 //UnicodeString BASEPATTERN("yyyy-MM-dd'T'HH:mm:ss.SSS");
554 UnicodeString BASEPATTERN("yyyy.MM.dd HH:mm:ss.SSS");
555
556 // timer for performance analysis
557 UDate timer;
558 UDate testTimes[4];
559 UBool expectedRoundTrip[4];
560 int32_t testLen = 0;
561
562 StringEnumeration *tzids = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL, nullptr, nullptr, status);
563 if (U_FAILURE(status)) {
564 if (status == U_MISSING_RESOURCE_ERROR) {
565 // This error is generally caused by data not being present.
566 dataerrln("TimeZone::createTimeZoneIDEnumeration failed - %s", u_errorName(status));
567 } else {
568 errln("TimeZone::createTimeZoneIDEnumeration failed: %s", u_errorName(status));
569 }
570 return;
571 }
572
573 int32_t locidx = -1;
574 int32_t patidx = -1;
575
576 while (gLocaleData->nextTest(locidx, patidx)) {
577
578 UnicodeString pattern(BASEPATTERN);
579 pattern.append(" ").append(PATTERNS[patidx]);
580 logln(" Thread %d, Locale %s, Pattern %s",
581 threadNumber, gLocaleData->locales[locidx].getName(), CStr(pattern)());
582
583 SimpleDateFormat *sdf = new SimpleDateFormat(pattern, gLocaleData->locales[locidx], status);
584 if (U_FAILURE(status)) {
585 errcheckln(status, UnicodeString("new SimpleDateFormat failed for pattern ") +
586 pattern + " for locale " + gLocaleData->locales[locidx].getName() + " - " + u_errorName(status));
587 status = U_ZERO_ERROR;
588 continue;
589 }
590
591 UBool minutesOffset = contains(MINUTES_OFFSET, PATTERNS[patidx]);
592
593 tzids->reset(status);
594 const UnicodeString *tzid;
595
596 timer = Calendar::getNow();
597
598 while ((tzid = tzids->snext(status))) {
599 if (uprv_strcmp(PATTERNS[patidx], "V") == 0) {
600 // Some zones do not have short ID assigned, such as Asia/Riyadh87.
601 // The time roundtrip will fail for such zones with pattern "V" (short zone ID).
602 // This is expected behavior.
603 const char16_t* shortZoneID = ZoneMeta::getShortID(*tzid);
604 if (shortZoneID == nullptr) {
605 continue;
606 }
607 } else if (uprv_strcmp(PATTERNS[patidx], "VVV") == 0) {
608 // Some zones are not associated with any region, such as Etc/GMT+8.
609 // The time roundtrip will fail for such zone with pattern "VVV" (exemplar location).
610 // This is expected behavior.
611 if (tzid->indexOf(static_cast<char16_t>(0x2F)) < 0 || tzid->indexOf(ETC_SLASH, -1, 0) >= 0
612 || tzid->indexOf(SYSTEMV_SLASH, -1, 0) >= 0 || tzid->indexOf(RIYADH8, -1, 0) >= 0) {
613 continue;
614 }
615 }
616
617 if ((*tzid == "Pacific/Apia" || *tzid == "Pacific/Midway" || *tzid == "Pacific/Pago_Pago")
618 && uprv_strcmp(PATTERNS[patidx], "vvvv") == 0
619 && logKnownIssue("11052", "Ambiguous zone name - Samoa Time")) {
620 continue;
621 }
622
623 BasicTimeZone *tz = dynamic_cast<BasicTimeZone*>(TimeZone::createTimeZone(*tzid));
624 sdf->setTimeZone(*tz);
625
626
627 UDate t = gLocaleData->START_TIME;
628 TimeZoneTransition tzt;
629 UBool tztAvail = false;
630 UBool middle = true;
631
632 while (t < gLocaleData->END_TIME) {
633 if (!tztAvail) {
634 testTimes[0] = t;
635 expectedRoundTrip[0] = true;
636 testLen = 1;
637 } else {
638 int32_t fromOffset = tzt.getFrom()->getRawOffset() + tzt.getFrom()->getDSTSavings();
639 int32_t toOffset = tzt.getTo()->getRawOffset() + tzt.getTo()->getDSTSavings();
640 int32_t delta = toOffset - fromOffset;
641 if (delta < 0) {
642 UBool isDstDecession = tzt.getFrom()->getDSTSavings() > 0 && tzt.getTo()->getDSTSavings() == 0;
643 testTimes[0] = t + delta - 1;
644 expectedRoundTrip[0] = true;
645 testTimes[1] = t + delta;
646 expectedRoundTrip[1] = isDstDecession ?
647 !contains(AMBIGUOUS_DST_DECESSION, PATTERNS[patidx]) :
648 !contains(AMBIGUOUS_NEGATIVE_SHIFT, PATTERNS[patidx]);
649 testTimes[2] = t - 1;
650 expectedRoundTrip[2] = isDstDecession ?
651 !contains(AMBIGUOUS_DST_DECESSION, PATTERNS[patidx]) :
652 !contains(AMBIGUOUS_NEGATIVE_SHIFT, PATTERNS[patidx]);
653 testTimes[3] = t;
654 expectedRoundTrip[3] = true;
655 testLen = 4;
656 } else {
657 testTimes[0] = t - 1;
658 expectedRoundTrip[0] = true;
659 testTimes[1] = t;
660 expectedRoundTrip[1] = true;
661 testLen = 2;
662 }
663 }
664 for (int32_t testidx = 0; testidx < testLen; testidx++) {
665 if (quick) {
666 // reduce regular test time
667 if (!expectedRoundTrip[testidx]) {
668 continue;
669 }
670 }
671
672 {
673 Mutex lock;
674 gLocaleData->testCounts++;
675 }
676
677 UnicodeString text;
678 FieldPosition fpos(FieldPosition::DONT_CARE);
679 sdf->format(testTimes[testidx], text, fpos);
680
681 UDate parsedDate = sdf->parse(text, status);
682 if (U_FAILURE(status)) {
683 errln(UnicodeString("Parse failure for text=") + text + ", tzid=" + *tzid + ", locale=" + gLocaleData->locales[locidx].getName()
684 + ", pattern=" + PATTERNS[patidx] + ", time=" + testTimes[testidx]);
685 status = U_ZERO_ERROR;
686 continue;
687 }
688
689 int32_t timeDiff = static_cast<int32_t>(parsedDate - testTimes[testidx]);
690 UBool bTimeMatch = minutesOffset ?
691 (timeDiff/60000)*60000 == 0 : timeDiff == 0;
692 if (!bTimeMatch) {
693 UnicodeString msg = UnicodeString("Time round trip failed for ") + "tzid=" + *tzid
694 + ", locale=" + gLocaleData->locales[locidx].getName() + ", pattern=" + PATTERNS[patidx]
695 + ", text=" + text + ", time=" + testTimes[testidx] + ", restime=" + parsedDate + ", diff=" + (parsedDate - testTimes[testidx]);
696 // Timebomb for TZData update
697 if (expectedRoundTrip[testidx]
698 && !isSpecialTimeRoundTripCase(gLocaleData->locales[locidx].getName(), *tzid,
699 PATTERNS[patidx], testTimes[testidx])) {
700 errln(UnicodeString("FAIL: ") + msg);
701 } else if (REALLY_VERBOSE) {
702 logln(msg);
703 }
704 }
705 }
706 tztAvail = tz->getNextTransition(t, false, tzt);
707 if (!tztAvail) {
708 break;
709 }
710 if (middle) {
711 // Test the date in the middle of two transitions.
712 t += static_cast<int64_t>((tzt.getTime() - t) / 2);
713 middle = false;
714 tztAvail = false;
715 } else {
716 t = tzt.getTime();
717 }
718 }
719 delete tz;
720 }
721 UDate elapsedTime = Calendar::getNow() - timer;
722 gLocaleData->addTime(elapsedTime, patidx);
723 delete sdf;
724 }
725 delete tzids;
726 }
727
728 void
TestAdoptDefaultThreadSafe()729 TimeZoneFormatTest::TestAdoptDefaultThreadSafe() {
730 ThreadPool<TimeZoneFormatTest> threads(this, threadCount, &TimeZoneFormatTest::RunAdoptDefaultThreadSafeTests);
731 threads.start(); // Start all threads.
732 threads.join(); // Wait for all threads to finish.
733 }
734
735 static const int32_t kAdoptDefaultIteration = 10;
736 static const int32_t kCreateDefaultIteration = 5000;
737 static const int64_t kStartTime = 1557288964845;
738
RunAdoptDefaultThreadSafeTests(int32_t threadNumber)739 void TimeZoneFormatTest::RunAdoptDefaultThreadSafeTests(int32_t threadNumber) {
740 UErrorCode status = U_ZERO_ERROR;
741 if (threadNumber % 2 == 0) {
742 for (int32_t i = 0; i < kAdoptDefaultIteration; i++) {
743 std::unique_ptr<icu::StringEnumeration> timezones(
744 icu::TimeZone::createEnumeration(status));
745 // Fails with missing data.
746 if (U_FAILURE(status)) {
747 dataerrln("Unable to create TimeZone enumeration");
748 return;
749 }
750 while (const icu::UnicodeString* timezone = timezones->snext(status)) {
751 status = U_ZERO_ERROR;
752 icu::TimeZone::adoptDefault(icu::TimeZone::createTimeZone(*timezone));
753 }
754 }
755 } else {
756 int32_t rawOffset;
757 int32_t dstOffset;
758 int64_t date = kStartTime;
759 for (int32_t i = 0; i < kCreateDefaultIteration; i++) {
760 date += 6000 * i;
761 std::unique_ptr<icu::TimeZone> tz(icu::TimeZone::createDefault());
762 status = U_ZERO_ERROR;
763 tz->getOffset(static_cast<UDate>(date), true, rawOffset, dstOffset, status);
764 status = U_ZERO_ERROR;
765 tz->getOffset(static_cast<UDate>(date), false, rawOffset, dstOffset, status);
766 }
767 }
768 }
769
770 typedef struct {
771 const char* text;
772 int32_t inPos;
773 const char* locale;
774 UTimeZoneFormatStyle style;
775 uint32_t parseOptions;
776 const char* expected;
777 int32_t outPos;
778 UTimeZoneFormatTimeType timeType;
779 } ParseTestData;
780
781 void
TestParse()782 TimeZoneFormatTest::TestParse() {
783 const ParseTestData DATA[] = {
784 // text inPos locale style
785 // parseOptions expected outPos timeType
786 {"Z", 0, "en_US", UTZFMT_STYLE_ISO_EXTENDED_FULL,
787 UTZFMT_PARSE_OPTION_NONE, "Etc/GMT", 1, UTZFMT_TIME_TYPE_UNKNOWN},
788
789 {"Z", 0, "en_US", UTZFMT_STYLE_SPECIFIC_LONG,
790 UTZFMT_PARSE_OPTION_NONE, "Etc/GMT", 1, UTZFMT_TIME_TYPE_UNKNOWN},
791
792 {"Zambia time", 0, "en_US", UTZFMT_STYLE_ISO_EXTENDED_FULL,
793 UTZFMT_PARSE_OPTION_ALL_STYLES, "Etc/GMT", 1, UTZFMT_TIME_TYPE_UNKNOWN},
794
795 {"Zambia time", 0, "en_US", UTZFMT_STYLE_GENERIC_LOCATION,
796 UTZFMT_PARSE_OPTION_NONE, "Africa/Lusaka", 11, UTZFMT_TIME_TYPE_UNKNOWN},
797
798 {"Zambia time", 0, "en_US", UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL,
799 UTZFMT_PARSE_OPTION_ALL_STYLES, "Africa/Lusaka", 11, UTZFMT_TIME_TYPE_UNKNOWN},
800
801 {"+00:00", 0, "en_US", UTZFMT_STYLE_ISO_EXTENDED_FULL,
802 UTZFMT_PARSE_OPTION_NONE, "Etc/GMT", 6, UTZFMT_TIME_TYPE_UNKNOWN},
803
804 {"-01:30:45", 0, "en_US", UTZFMT_STYLE_ISO_EXTENDED_FULL,
805 UTZFMT_PARSE_OPTION_NONE, "GMT-01:30:45", 9, UTZFMT_TIME_TYPE_UNKNOWN},
806
807 {"-7", 0, "en_US", UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL,
808 UTZFMT_PARSE_OPTION_NONE, "GMT-07:00", 2, UTZFMT_TIME_TYPE_UNKNOWN},
809
810 {"-2222", 0, "en_US", UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL,
811 UTZFMT_PARSE_OPTION_NONE, "GMT-22:22", 5, UTZFMT_TIME_TYPE_UNKNOWN},
812
813 {"-3333", 0, "en_US", UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL,
814 UTZFMT_PARSE_OPTION_NONE, "GMT-03:33", 4, UTZFMT_TIME_TYPE_UNKNOWN},
815
816 {"XXX+01:30YYY", 3, "en_US", UTZFMT_STYLE_LOCALIZED_GMT,
817 UTZFMT_PARSE_OPTION_NONE, "GMT+01:30", 9, UTZFMT_TIME_TYPE_UNKNOWN},
818
819 {"GMT0", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT,
820 UTZFMT_PARSE_OPTION_NONE, "Etc/GMT", 3, UTZFMT_TIME_TYPE_UNKNOWN},
821
822 {"EST", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT,
823 UTZFMT_PARSE_OPTION_NONE, "America/New_York", 3, UTZFMT_TIME_TYPE_STANDARD},
824
825 {"ESTx", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT,
826 UTZFMT_PARSE_OPTION_NONE, "America/New_York", 3, UTZFMT_TIME_TYPE_STANDARD},
827
828 {"EDTx", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT,
829 UTZFMT_PARSE_OPTION_NONE, "America/New_York", 3, UTZFMT_TIME_TYPE_DAYLIGHT},
830
831 {"EST", 0, "en_US", UTZFMT_STYLE_SPECIFIC_LONG,
832 UTZFMT_PARSE_OPTION_NONE, nullptr, 0, UTZFMT_TIME_TYPE_UNKNOWN},
833
834 {"EST", 0, "en_US", UTZFMT_STYLE_SPECIFIC_LONG,
835 UTZFMT_PARSE_OPTION_ALL_STYLES, "America/New_York", 3, UTZFMT_TIME_TYPE_STANDARD},
836
837 {"EST", 0, "en_CA", UTZFMT_STYLE_SPECIFIC_SHORT,
838 UTZFMT_PARSE_OPTION_NONE, "America/Toronto", 3, UTZFMT_TIME_TYPE_STANDARD},
839
840 {"CST", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT,
841 UTZFMT_PARSE_OPTION_NONE, "America/Chicago", 3, UTZFMT_TIME_TYPE_STANDARD},
842
843 {"CST", 0, "en_GB", UTZFMT_STYLE_SPECIFIC_SHORT,
844 UTZFMT_PARSE_OPTION_NONE, nullptr, 0, UTZFMT_TIME_TYPE_UNKNOWN},
845
846 {"CST", 0, "en_GB", UTZFMT_STYLE_SPECIFIC_SHORT,
847 UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS, "America/Chicago", 3, UTZFMT_TIME_TYPE_STANDARD},
848
849 {"--CST--", 2, "en_GB", UTZFMT_STYLE_SPECIFIC_SHORT,
850 UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS, "America/Chicago", 5, UTZFMT_TIME_TYPE_STANDARD},
851
852 {"CST", 0, "zh_CN", UTZFMT_STYLE_SPECIFIC_SHORT,
853 UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS, "Asia/Shanghai", 3, UTZFMT_TIME_TYPE_STANDARD},
854
855 {"AEST", 0, "en_AU", UTZFMT_STYLE_SPECIFIC_SHORT,
856 UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS, "Australia/Sydney", 4, UTZFMT_TIME_TYPE_STANDARD},
857
858 {"AST", 0, "ar_SA", UTZFMT_STYLE_SPECIFIC_SHORT,
859 UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS, "Asia/Riyadh", 3, UTZFMT_TIME_TYPE_STANDARD},
860
861 {"AQTST", 0, "en", UTZFMT_STYLE_SPECIFIC_LONG,
862 UTZFMT_PARSE_OPTION_NONE, nullptr, 0, UTZFMT_TIME_TYPE_UNKNOWN},
863
864 {"AQTST", 0, "en", UTZFMT_STYLE_SPECIFIC_LONG,
865 UTZFMT_PARSE_OPTION_ALL_STYLES, nullptr, 0, UTZFMT_TIME_TYPE_UNKNOWN},
866
867 {"AQTST", 0, "en", UTZFMT_STYLE_SPECIFIC_LONG,
868 UTZFMT_PARSE_OPTION_ALL_STYLES | UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS, "Asia/Aqtobe", 5, UTZFMT_TIME_TYPE_DAYLIGHT},
869
870 {nullptr, 0, nullptr, UTZFMT_STYLE_GENERIC_LOCATION,
871 UTZFMT_PARSE_OPTION_NONE, nullptr, 0, UTZFMT_TIME_TYPE_UNKNOWN}
872 };
873
874 for (int32_t i = 0; DATA[i].text; i++) {
875 UErrorCode status = U_ZERO_ERROR;
876 LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(Locale(DATA[i].locale), status));
877 if (U_FAILURE(status)) {
878 dataerrln("Fail TimeZoneFormat::createInstance: %s", u_errorName(status));
879 continue;
880 }
881 UTimeZoneFormatTimeType ttype = UTZFMT_TIME_TYPE_UNKNOWN;
882 ParsePosition pos(DATA[i].inPos);
883 TimeZone* tz = tzfmt->parse(DATA[i].style, DATA[i].text, pos, DATA[i].parseOptions, &ttype);
884
885 UnicodeString errMsg;
886 if (tz) {
887 UnicodeString outID;
888 tz->getID(outID);
889 if (outID != UnicodeString(DATA[i].expected)) {
890 errMsg = UnicodeString("Time zone ID: ") + outID + " - expected: " + DATA[i].expected;
891 } else if (pos.getIndex() != DATA[i].outPos) {
892 errMsg = UnicodeString("Parsed pos: ") + pos.getIndex() + " - expected: " + DATA[i].outPos;
893 } else if (ttype != DATA[i].timeType) {
894 errMsg = UnicodeString("Time type: ") + ttype + " - expected: " + DATA[i].timeType;
895 }
896 delete tz;
897 } else {
898 if (DATA[i].expected) {
899 errMsg = UnicodeString("Parse failure - expected: ") + DATA[i].expected;
900 }
901 }
902 if (errMsg.length() > 0) {
903 errln(UnicodeString("Fail: ") + errMsg + " [text=" + DATA[i].text + ", pos=" + DATA[i].inPos + ", style=" + DATA[i].style + "]");
904 }
905 }
906 }
907
908 void
TestISOFormat()909 TimeZoneFormatTest::TestISOFormat() {
910 const int32_t OFFSET[] = {
911 0, // 0
912 999, // 0.999s
913 -59999, // -59.999s
914 60000, // 1m
915 -77777, // -1m 17.777s
916 1800000, // 30m
917 -3600000, // -1h
918 36000000, // 10h
919 -37800000, // -10h 30m
920 -37845000, // -10h 30m 45s
921 108000000, // 30h
922 };
923
924 const char* ISO_STR[][11] = {
925 // 0
926 {
927 "Z", "Z", "Z", "Z", "Z",
928 "+00", "+0000", "+00:00", "+0000", "+00:00",
929 "+0000"
930 },
931 // 999
932 {
933 "Z", "Z", "Z", "Z", "Z",
934 "+00", "+0000", "+00:00", "+0000", "+00:00",
935 "+0000"
936 },
937 // -59999
938 {
939 "Z", "Z", "Z", "-000059", "-00:00:59",
940 "+00", "+0000", "+00:00", "-000059", "-00:00:59",
941 "-000059"
942 },
943 // 60000
944 {
945 "+0001", "+0001", "+00:01", "+0001", "+00:01",
946 "+0001", "+0001", "+00:01", "+0001", "+00:01",
947 "+0001"
948 },
949 // -77777
950 {
951 "-0001", "-0001", "-00:01", "-000117", "-00:01:17",
952 "-0001", "-0001", "-00:01", "-000117", "-00:01:17",
953 "-000117"
954 },
955 // 1800000
956 {
957 "+0030", "+0030", "+00:30", "+0030", "+00:30",
958 "+0030", "+0030", "+00:30", "+0030", "+00:30",
959 "+0030"
960 },
961 // -3600000
962 {
963 "-01", "-0100", "-01:00", "-0100", "-01:00",
964 "-01", "-0100", "-01:00", "-0100", "-01:00",
965 "-0100"
966 },
967 // 36000000
968 {
969 "+10", "+1000", "+10:00", "+1000", "+10:00",
970 "+10", "+1000", "+10:00", "+1000", "+10:00",
971 "+1000"
972 },
973 // -37800000
974 {
975 "-1030", "-1030", "-10:30", "-1030", "-10:30",
976 "-1030", "-1030", "-10:30", "-1030", "-10:30",
977 "-1030"
978 },
979 // -37845000
980 {
981 "-1030", "-1030", "-10:30", "-103045", "-10:30:45",
982 "-1030", "-1030", "-10:30", "-103045", "-10:30:45",
983 "-103045"
984 },
985 // 108000000
986 {
987 nullptr, nullptr, nullptr, nullptr, nullptr,
988 nullptr, nullptr, nullptr, nullptr, nullptr,
989 nullptr
990 }
991 };
992
993 const char* PATTERN[] = {
994 "X", "XX", "XXX", "XXXX", "XXXXX",
995 "x", "xx", "xxx", "xxxx", "xxxxx",
996 "Z", // equivalent to "xxxx"
997 nullptr
998 };
999
1000 const int32_t MIN_OFFSET_UNIT[] = {
1001 60000, 60000, 60000, 1000, 1000,
1002 60000, 60000, 60000, 1000, 1000,
1003 1000,
1004 };
1005
1006 // Formatting
1007 UErrorCode status = U_ZERO_ERROR;
1008 LocalPointer<SimpleDateFormat> sdf(new SimpleDateFormat(status), status);
1009 if (U_FAILURE(status)) {
1010 dataerrln("Fail new SimpleDateFormat: %s", u_errorName(status));
1011 return;
1012 }
1013 UDate d = Calendar::getNow();
1014
1015 for (uint32_t i = 0; i < UPRV_LENGTHOF(OFFSET); i++) {
1016 SimpleTimeZone* tz = new SimpleTimeZone(OFFSET[i], UnicodeString("Zone Offset:") + OFFSET[i] + "ms");
1017 sdf->adoptTimeZone(tz);
1018 for (int32_t j = 0; PATTERN[j] != nullptr; j++) {
1019 sdf->applyPattern(UnicodeString(PATTERN[j]));
1020 UnicodeString result;
1021 sdf->format(d, result);
1022
1023 if (ISO_STR[i][j]) {
1024 if (result != UnicodeString(ISO_STR[i][j])) {
1025 errln(UnicodeString("FAIL: pattern=") + PATTERN[j] + ", offset=" + OFFSET[i] + " -> "
1026 + result + " (expected: " + ISO_STR[i][j] + ")");
1027 }
1028 } else {
1029 // Offset out of range
1030 // Note: for now, there is no way to propagate the error status through
1031 // the SimpleDateFormat::format above.
1032 if (result.length() > 0) {
1033 errln(UnicodeString("FAIL: Non-Empty result for pattern=") + PATTERN[j] + ", offset=" + OFFSET[i]
1034 + " (expected: empty result)");
1035 }
1036 }
1037 }
1038 }
1039
1040 // Parsing
1041 LocalPointer<Calendar> outcal(Calendar::createInstance(status));
1042 if (U_FAILURE(status)) {
1043 dataerrln("Fail new Calendar: %s", u_errorName(status));
1044 return;
1045 }
1046 for (int32_t i = 0; ISO_STR[i][0] != nullptr; i++) {
1047 for (int32_t j = 0; PATTERN[j] != nullptr; j++) {
1048 if (ISO_STR[i][j] == nullptr) {
1049 continue;
1050 }
1051 ParsePosition pos(0);
1052 SimpleTimeZone* bogusTZ = new SimpleTimeZone(-1, UnicodeString("Zone Offset: -1ms"));
1053 outcal->adoptTimeZone(bogusTZ);
1054 sdf->applyPattern(PATTERN[j]);
1055
1056 sdf->parse(UnicodeString(ISO_STR[i][j]), *(outcal.getAlias()), pos);
1057
1058 if (pos.getIndex() != static_cast<int32_t>(uprv_strlen(ISO_STR[i][j]))) {
1059 errln(UnicodeString("FAIL: Failed to parse the entire input string: ") + ISO_STR[i][j]);
1060 }
1061
1062 const TimeZone& outtz = outcal->getTimeZone();
1063 int32_t outOffset = outtz.getRawOffset();
1064 int32_t adjustedOffset = OFFSET[i] / MIN_OFFSET_UNIT[j] * MIN_OFFSET_UNIT[j];
1065 if (outOffset != adjustedOffset) {
1066 errln(UnicodeString("FAIL: Incorrect offset:") + outOffset + "ms for input string: " + ISO_STR[i][j]
1067 + " (expected:" + adjustedOffset + "ms)");
1068 }
1069 }
1070 }
1071 }
1072
1073
1074 typedef struct {
1075 const char* locale;
1076 const char* tzid;
1077 UDate date;
1078 UTimeZoneFormatStyle style;
1079 const char* expected;
1080 UTimeZoneFormatTimeType timeType;
1081 } FormatTestData;
1082
1083 void
TestFormat()1084 TimeZoneFormatTest::TestFormat() {
1085 UDate dateJan = 1358208000000.0; // 2013-01-15T00:00:00Z
1086 UDate dateJul = 1373846400000.0; // 2013-07-15T00:00:00Z
1087
1088 const FormatTestData DATA[] = {
1089 {
1090 "en",
1091 "America/Los_Angeles",
1092 dateJan,
1093 UTZFMT_STYLE_GENERIC_LOCATION,
1094 "Los Angeles Time",
1095 UTZFMT_TIME_TYPE_UNKNOWN
1096 },
1097 {
1098 "en",
1099 "America/Los_Angeles",
1100 dateJan,
1101 UTZFMT_STYLE_GENERIC_LONG,
1102 "Pacific Time",
1103 UTZFMT_TIME_TYPE_UNKNOWN
1104 },
1105 {
1106 "en",
1107 "America/Los_Angeles",
1108 dateJan,
1109 UTZFMT_STYLE_SPECIFIC_LONG,
1110 "Pacific Standard Time",
1111 UTZFMT_TIME_TYPE_STANDARD
1112 },
1113 {
1114 "en",
1115 "America/Los_Angeles",
1116 dateJul,
1117 UTZFMT_STYLE_SPECIFIC_LONG,
1118 "Pacific Daylight Time",
1119 UTZFMT_TIME_TYPE_DAYLIGHT
1120 },
1121 {
1122 "ja",
1123 "America/Los_Angeles",
1124 dateJan,
1125 UTZFMT_STYLE_ZONE_ID,
1126 "America/Los_Angeles",
1127 UTZFMT_TIME_TYPE_UNKNOWN
1128 },
1129 {
1130 "fr",
1131 "America/Los_Angeles",
1132 dateJul,
1133 UTZFMT_STYLE_ZONE_ID_SHORT,
1134 "uslax",
1135 UTZFMT_TIME_TYPE_UNKNOWN
1136 },
1137 {
1138 "en",
1139 "America/Los_Angeles",
1140 dateJan,
1141 UTZFMT_STYLE_EXEMPLAR_LOCATION,
1142 "Los Angeles",
1143 UTZFMT_TIME_TYPE_UNKNOWN
1144 },
1145
1146 {
1147 "ja",
1148 "Asia/Tokyo",
1149 dateJan,
1150 UTZFMT_STYLE_GENERIC_LONG,
1151 "\\u65E5\\u672C\\u6A19\\u6E96\\u6642",
1152 UTZFMT_TIME_TYPE_UNKNOWN
1153 },
1154
1155 {nullptr, nullptr, 0.0, UTZFMT_STYLE_GENERIC_LOCATION, nullptr, UTZFMT_TIME_TYPE_UNKNOWN}
1156 };
1157
1158 for (int32_t i = 0; DATA[i].locale; i++) {
1159 UErrorCode status = U_ZERO_ERROR;
1160 LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(Locale(DATA[i].locale), status));
1161 if (U_FAILURE(status)) {
1162 dataerrln("Fail TimeZoneFormat::createInstance: %s", u_errorName(status));
1163 continue;
1164 }
1165
1166 LocalPointer<TimeZone> tz(TimeZone::createTimeZone(DATA[i].tzid));
1167 UnicodeString out;
1168 UTimeZoneFormatTimeType timeType;
1169
1170 tzfmt->format(DATA[i].style, *(tz.getAlias()), DATA[i].date, out, &timeType);
1171 UnicodeString expected(DATA[i].expected, -1, US_INV);
1172 expected = expected.unescape();
1173
1174 assertEquals(UnicodeString("Format result for ") + DATA[i].tzid + " (Test Case " + i + ")", expected, out);
1175 if (DATA[i].timeType != timeType) {
1176 dataerrln(UnicodeString("Formatted time zone type (Test Case ") + i + "), returned="
1177 + timeType + ", expected=" + DATA[i].timeType);
1178 }
1179 }
1180 }
1181
1182 void
TestFormatTZDBNames()1183 TimeZoneFormatTest::TestFormatTZDBNames() {
1184 UDate dateJan = 1358208000000.0; // 2013-01-15T00:00:00Z
1185 UDate dateJul = 1373846400000.0; // 2013-07-15T00:00:00Z
1186
1187 const FormatTestData DATA[] = {
1188 {
1189 "en",
1190 "America/Chicago",
1191 dateJan,
1192 UTZFMT_STYLE_SPECIFIC_SHORT,
1193 "CST",
1194 UTZFMT_TIME_TYPE_STANDARD
1195 },
1196 {
1197 "en",
1198 "Asia/Shanghai",
1199 dateJan,
1200 UTZFMT_STYLE_SPECIFIC_SHORT,
1201 "CST",
1202 UTZFMT_TIME_TYPE_STANDARD
1203 },
1204 {
1205 "zh_Hans",
1206 "Asia/Shanghai",
1207 dateJan,
1208 UTZFMT_STYLE_SPECIFIC_SHORT,
1209 "CST",
1210 UTZFMT_TIME_TYPE_STANDARD
1211 },
1212 {
1213 "en",
1214 "America/Los_Angeles",
1215 dateJul,
1216 UTZFMT_STYLE_SPECIFIC_LONG,
1217 "GMT-07:00", // No long display names
1218 UTZFMT_TIME_TYPE_DAYLIGHT
1219 },
1220 {
1221 "ja",
1222 "America/Los_Angeles",
1223 dateJul,
1224 UTZFMT_STYLE_SPECIFIC_SHORT,
1225 "PDT",
1226 UTZFMT_TIME_TYPE_DAYLIGHT
1227 },
1228 {
1229 "en",
1230 "Australia/Sydney",
1231 dateJan,
1232 UTZFMT_STYLE_SPECIFIC_SHORT,
1233 "AEDT",
1234 UTZFMT_TIME_TYPE_DAYLIGHT
1235 },
1236 {
1237 "en",
1238 "Australia/Sydney",
1239 dateJul,
1240 UTZFMT_STYLE_SPECIFIC_SHORT,
1241 "AEST",
1242 UTZFMT_TIME_TYPE_STANDARD
1243 },
1244
1245 {nullptr, nullptr, 0.0, UTZFMT_STYLE_GENERIC_LOCATION, nullptr, UTZFMT_TIME_TYPE_UNKNOWN}
1246 };
1247
1248 for (int32_t i = 0; DATA[i].locale; i++) {
1249 UErrorCode status = U_ZERO_ERROR;
1250 Locale loc(DATA[i].locale);
1251 LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(loc, status));
1252 if (U_FAILURE(status)) {
1253 dataerrln("Fail TimeZoneFormat::createInstance: %s", u_errorName(status));
1254 continue;
1255 }
1256 TimeZoneNames *tzdbNames = TimeZoneNames::createTZDBInstance(loc, status);
1257 if (U_FAILURE(status)) {
1258 dataerrln("Fail TimeZoneNames::createTZDBInstance: %s", u_errorName(status));
1259 continue;
1260 }
1261 tzfmt->adoptTimeZoneNames(tzdbNames);
1262
1263 LocalPointer<TimeZone> tz(TimeZone::createTimeZone(DATA[i].tzid));
1264 UnicodeString out;
1265 UTimeZoneFormatTimeType timeType;
1266
1267 tzfmt->format(DATA[i].style, *(tz.getAlias()), DATA[i].date, out, &timeType);
1268 UnicodeString expected(DATA[i].expected, -1, US_INV);
1269 expected = expected.unescape();
1270
1271 assertEquals(UnicodeString("Format result for ") + DATA[i].tzid + " (Test Case " + i + ")", expected, out);
1272 if (DATA[i].timeType != timeType) {
1273 dataerrln(UnicodeString("Formatted time zone type (Test Case ") + i + "), returned="
1274 + timeType + ", expected=" + DATA[i].timeType);
1275 }
1276 }
1277 }
1278
1279 void
TestFormatCustomZone()1280 TimeZoneFormatTest::TestFormatCustomZone() {
1281 struct {
1282 const char* id;
1283 int32_t offset;
1284 const char* expected;
1285 } TESTDATA[] = {
1286 { "abc", 3600000, "GMT+01:00" }, // unknown ID
1287 { "$abc", -3600000, "GMT-01:00" }, // unknown, with ASCII variant char '$'
1288 { "\\u00c1\\u00df\\u00c7", 5400000, "GMT+01:30"}, // unknown, with non-ASCII chars
1289 { nullptr, 0, nullptr }
1290 };
1291
1292 UDate now = Calendar::getNow();
1293
1294 for (int32_t i = 0; ; i++) {
1295 const char *id = TESTDATA[i].id;
1296 if (id == nullptr) {
1297 break;
1298 }
1299 UnicodeString tzid = UnicodeString(id, -1, US_INV).unescape();
1300 SimpleTimeZone tz(TESTDATA[i].offset, tzid);
1301
1302 UErrorCode status = U_ZERO_ERROR;
1303 LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(Locale("en"), status));
1304 if (tzfmt.isNull()) {
1305 dataerrln("FAIL: TimeZoneFormat::createInstance failed for en");
1306 return;
1307 }
1308 UnicodeString tzstr;
1309 UnicodeString expected = UnicodeString(TESTDATA[i].expected, -1, US_INV).unescape();
1310
1311 tzfmt->format(UTZFMT_STYLE_SPECIFIC_LONG, tz, now, tzstr, nullptr);
1312 assertEquals(UnicodeString("Format result for ") + tzid, expected, tzstr);
1313 }
1314 }
1315
1316 void
Test22615NonASCIIID()1317 TimeZoneFormatTest::Test22615NonASCIIID() {
1318 UErrorCode status = U_ZERO_ERROR;
1319 LocalPointer<TimeZoneNames> tzdb(TimeZoneNames::createTZDBInstance(Locale("en"), status));
1320 // A test to ensure under the debugging build non ASCII id will not cause
1321 // internal assertion error.
1322 UnicodeString id(9, u'\u00C0', 8);
1323 UnicodeString output;
1324 tzdb->getMetaZoneDisplayName(id, UTZNM_SHORT_STANDARD, output);
1325 assertTrue("getMetaZoneID of non ASCII id should return bogus string",
1326 output.isBogus());
1327
1328 status = U_ZERO_ERROR;
1329 std::unique_ptr<icu::StringEnumeration> enumeration(
1330 tzdb->getAvailableMetaZoneIDs(id, status));
1331 assertSuccess("getAvailableMetaZoneIDs should success", status);
1332 assertEquals("getAvailableMetaZoneIDs with non ASCII id return 0 ids",
1333 0, enumeration->count(status));
1334 assertSuccess("count should success", status);
1335
1336 output.remove();
1337 tzdb->getMetaZoneID(id, 0, output);
1338 assertTrue("getMetaZoneID of non ASCII id should return bogus string",
1339 output.isBogus());
1340
1341 output.remove();
1342 tzdb->getMetaZoneDisplayName(id, UTZNM_EXEMPLAR_LOCATION, output);
1343 assertTrue("getMetaZoneDisplayName of non ASCII id should return bogus string",
1344 output.isBogus());
1345
1346 output.remove();
1347 tzdb->getTimeZoneDisplayName(id, UTZNM_SHORT_DAYLIGHT, output);
1348 assertTrue("getTimeZoneDisplayName of non ASCII id should return bogus string",
1349 output.isBogus());
1350
1351 output.remove();
1352 tzdb->getExemplarLocationName(id, output);
1353 assertTrue("getExemplarLocationName of non ASCII id should return bogus string",
1354 output.isBogus());
1355
1356 output.remove();
1357 tzdb->getDisplayName(id, UTZNM_LONG_GENERIC, 0, output);
1358 assertTrue("getDisplayName of non ASCII id should return bogus string",
1359 output.isBogus());
1360 }
1361
1362 void
Test22614GetMetaZoneNamesNotCrash()1363 TimeZoneFormatTest::Test22614GetMetaZoneNamesNotCrash() {
1364 UErrorCode status = U_ZERO_ERROR;
1365 LocalPointer<TimeZoneNames> tzdbNames(TimeZoneNames::createTZDBInstance(Locale("en"), status));
1366 UnicodeString name;
1367 for (int32_t i = 124; i < 150; i++) {
1368 name.remove();
1369 UnicodeString mzId(i+1, u'A', i);
1370 tzdbNames->getMetaZoneDisplayName(mzId, UTZNM_SHORT_STANDARD, name);
1371 }
1372 }
1373 void
TestFormatTZDBNamesAllZoneCoverage()1374 TimeZoneFormatTest::TestFormatTZDBNamesAllZoneCoverage() {
1375 UErrorCode status = U_ZERO_ERROR;
1376 LocalPointer<StringEnumeration> tzids(TimeZone::createEnumeration(status));
1377 if (U_FAILURE(status)) {
1378 dataerrln("Unable to create TimeZone enumeration", __FILE__, __LINE__);
1379 return;
1380 }
1381 const UnicodeString *tzid;
1382 LocalPointer<TimeZoneNames> tzdbNames(TimeZoneNames::createTZDBInstance(Locale("en"), status));
1383 UDate now = Calendar::getNow();
1384 UnicodeString mzId;
1385 UnicodeString name;
1386 while ((tzid = tzids->snext(status))) {
1387 logln("Zone: " + *tzid);
1388 LocalPointer<TimeZone> tz(TimeZone::createTimeZone(*tzid));
1389 tzdbNames->getMetaZoneID(*tzid, now, mzId);
1390 if (mzId.isBogus()) {
1391 logln(UnicodeString("Meta zone: <not available>"));
1392 } else {
1393 logln(UnicodeString("Meta zone: ") + mzId);
1394 }
1395
1396 // mzID could be bogus here
1397 tzdbNames->getMetaZoneDisplayName(mzId, UTZNM_SHORT_STANDARD, name);
1398 // name could be bogus here
1399 if (name.isBogus()) {
1400 logln(UnicodeString("Meta zone short standard name: <not available>"));
1401 }
1402 else {
1403 logln(UnicodeString("Meta zone short standard name: ") + name);
1404 }
1405
1406 tzdbNames->getMetaZoneDisplayName(mzId, UTZNM_SHORT_DAYLIGHT, name);
1407 // name could be bogus here
1408 if (name.isBogus()) {
1409 logln(UnicodeString("Meta zone short daylight name: <not available>"));
1410 }
1411 else {
1412 logln(UnicodeString("Meta zone short daylight name: ") + name);
1413 }
1414 }
1415 }
1416
1417 // Test for checking parse results are same for a same input string
1418 // using SimpleDateFormat initialized with different regional locales - US and Belize.
1419 // Belize did not observe DST from 1968 to 1973, 1975 to 1982, and 1985 and later.
1420 void
TestCentralTime()1421 TimeZoneFormatTest::TestCentralTime() {
1422 UnicodeString pattern(u"y-MM-dd HH:mm:ss zzzz");
1423 UnicodeString testInputs[] = {
1424 // 1970-01-01 - Chicago:STD/Belize:STD
1425 u"1970-01-01 12:00:00 Central Standard Time",
1426 u"1970-01-01 12:00:00 Central Daylight Time",
1427
1428 // 1970-07-01 - Chicago:STD/Belize:STD
1429 u"1970-07-01 12:00:00 Central Standard Time",
1430 u"1970-07-01 12:00:00 Central Daylight Time",
1431
1432 // 1974-01-01 - Chicago:STD/Belize:DST
1433 u"1974-01-01 12:00:00 Central Standard Time",
1434 u"1974-01-01 12:00:00 Central Daylight Time",
1435
1436 // 2020-01-01 - Chicago:STD/Belize:STD
1437 u"2020-01-01 12:00:00 Central Standard Time",
1438 u"2020-01-01 12:00:00 Central Daylight Time",
1439
1440 // 2020-01-01 - Chicago:DST/Belize:STD
1441 u"2020-07-01 12:00:00 Central Standard Time",
1442 u"2020-07-01 12:00:00 Central Daylight Time",
1443
1444 u""
1445 };
1446
1447 UErrorCode status = U_ZERO_ERROR;
1448 SimpleDateFormat sdfUS(pattern, Locale("en_US"), status);
1449 SimpleDateFormat sdfBZ(pattern, Locale("en_BZ"), status);
1450 if (U_FAILURE(status)) {
1451 errln("Failed to create SimpleDateFormat instance");
1452 return;
1453 }
1454
1455 for (int32_t i = 0; !testInputs[i].isEmpty(); i++) {
1456 UDate dUS = sdfUS.parse(testInputs[i], status);
1457 UDate dBZ = sdfBZ.parse(testInputs[i], status);
1458
1459 if (U_FAILURE(status)) {
1460 errln(UnicodeString("Failed to parse date string: ") + testInputs[i]);
1461 continue;
1462 }
1463
1464 if (dUS != dBZ) {
1465 errln(UnicodeString("Parse results should be same for input: ") + testInputs[i]);
1466 }
1467 }
1468 }
1469 void
TestBogusLocale()1470 TimeZoneFormatTest::TestBogusLocale() {
1471 Locale bogus("not a lang");
1472 UErrorCode status = U_ZERO_ERROR;
1473 std::unique_ptr<icu::TimeZoneFormat> tzfmt(
1474 icu::TimeZoneFormat::createInstance(bogus, status));
1475 if (U_FAILURE(status)) {
1476 errln(u"Failed to createInstance with bogus locale");
1477 }
1478 }
1479 #endif /* #if !UCONFIG_NO_FORMATTING */
1480