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((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 errln((UnicodeString)"Raw offset round trip failed; tz=" + *tzid
342 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
343 + ", time=" + DATES[datidx] + ", str=" + tzstr
344 + ", inRawOffset=" + inRaw + ", outRawOffset=" + outRaw);
345 }
346 }
347 }
348 delete outcal;
349 }
350 delete tz;
351 }
352 delete sdf;
353 }
354 }
355 delete cal;
356 delete tzids;
357 }
358
359 // Special exclusions in TestTimeZoneRoundTrip.
360 // These special cases do not round trip time as designed.
isSpecialTimeRoundTripCase(const char * loc,const UnicodeString & id,const char * pattern,UDate time)361 static UBool isSpecialTimeRoundTripCase(const char* loc,
362 const UnicodeString& id,
363 const char* pattern,
364 UDate time) {
365 struct {
366 const char* loc;
367 const char* id;
368 const char* pattern;
369 UDate time;
370 } EXCLUSIONS[] = {
371 {nullptr, "Asia/Chita", "zzzz", 1414252800000.0},
372 {nullptr, "Asia/Chita", "vvvv", 1414252800000.0},
373 {nullptr, "Asia/Srednekolymsk", "zzzz", 1414241999999.0},
374 {nullptr, "Asia/Srednekolymsk", "vvvv", 1414241999999.0},
375 {nullptr, nullptr, nullptr, U_DATE_MIN}
376 };
377
378 UBool isExcluded = false;
379 for (int32_t i = 0; EXCLUSIONS[i].id != nullptr; i++) {
380 if (EXCLUSIONS[i].loc == nullptr || uprv_strcmp(loc, EXCLUSIONS[i].loc) == 0) {
381 if (id.compare(EXCLUSIONS[i].id) == 0) {
382 if (EXCLUSIONS[i].pattern == nullptr || uprv_strcmp(pattern, EXCLUSIONS[i].pattern) == 0) {
383 if (EXCLUSIONS[i].time == U_DATE_MIN || EXCLUSIONS[i].time == time) {
384 isExcluded = true;
385 }
386 }
387 }
388 }
389 }
390 return isExcluded;
391 }
392
393 // LocaleData. Somewhat misnamed. For TestTimeZoneRoundTrip, specifies the locales and patterns
394 // to be tested, and provides an iterator over these for the multi-threaded test
395 // functions to pick up the next combination to be tested.
396 //
397 // A single global instance of this struct is shared among all
398 // the test threads.
399 //
400 // "locales" is an array of locales to be tested.
401 // PATTERNS (a global) is an array of patterns to be tested for each locale.
402 // "localeIndex" and "patternIndex" keep track of the iteration through the above.
403 // Each of the parallel test threads calls LocaleData::nextTest() in a loop
404 // to find out what to test next. It must be thread safe.
405 struct LocaleData {
406 int32_t localeIndex;
407 int32_t patternIndex;
408 int32_t testCounts;
409 UDate times[UPRV_LENGTHOF(PATTERNS)]; // Performance data, Elapsed time for each pattern.
410 const Locale* locales;
411 int32_t nLocales;
412 UDate START_TIME;
413 UDate END_TIME;
414 int32_t numDone;
415
LocaleDataLocaleData416 LocaleData() : localeIndex(0), patternIndex(0), testCounts(0), locales(nullptr),
417 nLocales(0), START_TIME(0), END_TIME(0), numDone(0) {
418 for (int i=0; i<UPRV_LENGTHOF(times); i++) {
419 times[i] = 0;
420 }
421 }
422
resetTestIterationLocaleData423 void resetTestIteration() {
424 localeIndex = -1;
425 patternIndex = UPRV_LENGTHOF(PATTERNS);
426 numDone = 0;
427 }
428
nextTestLocaleData429 UBool nextTest(int32_t &rLocaleIndex, int32_t &rPatternIndex) {
430 Mutex lock;
431 if (patternIndex >= UPRV_LENGTHOF(PATTERNS) - 1) {
432 if (localeIndex >= nLocales - 1) {
433 return false;
434 }
435 patternIndex = -1;
436 ++localeIndex;
437 }
438 ++patternIndex;
439 rLocaleIndex = localeIndex;
440 rPatternIndex = patternIndex;
441 ++numDone;
442 return true;
443 }
444
addTimeLocaleData445 void addTime(UDate amount, int32_t patIdx) {
446 Mutex lock;
447 U_ASSERT(patIdx < UPRV_LENGTHOF(PATTERNS));
448 times[patIdx] += amount;
449 }
450 };
451
452 static LocaleData *gLocaleData = nullptr;
453
454 void
TestTimeRoundTrip()455 TimeZoneFormatTest::TestTimeRoundTrip() {
456 UErrorCode status = U_ZERO_ERROR;
457 LocalPointer <Calendar> cal(Calendar::createInstance(TimeZone::createTimeZone((UnicodeString) "UTC"), status));
458 if (U_FAILURE(status)) {
459 dataerrln("Calendar::createInstance failed: %s", u_errorName(status));
460 return;
461 }
462
463 const char* testAllProp = getProperty("TimeZoneRoundTripAll");
464 UBool bTestAll = (testAllProp && uprv_strcmp(testAllProp, "true") == 0);
465
466 UDate START_TIME, END_TIME;
467 if (bTestAll || !quick) {
468 cal->set(1900, UCAL_JANUARY, 1);
469 } else {
470 cal->set(1999, UCAL_JANUARY, 1);
471 }
472 START_TIME = cal->getTime(status);
473
474 cal->set(2022, UCAL_JANUARY, 1);
475 END_TIME = cal->getTime(status);
476
477 if (U_FAILURE(status)) {
478 errln("getTime failed");
479 return;
480 }
481
482 LocaleData localeData;
483 gLocaleData = &localeData;
484
485 // Set up test locales
486 const Locale locales1[] = {Locale("en")};
487 const Locale locales2[] = {
488 Locale("ar_EG"), Locale("bg_BG"), Locale("ca_ES"), Locale("da_DK"), Locale("de"),
489 Locale("de_DE"), Locale("el_GR"), Locale("en"), Locale("en_AU"), Locale("en_CA"),
490 Locale("en_US"), Locale("es"), Locale("es_ES"), Locale("es_MX"), Locale("fi_FI"),
491 Locale("fr"), Locale("fr_CA"), Locale("fr_FR"), Locale("he_IL"), Locale("hu_HU"),
492 Locale("it"), Locale("it_IT"), Locale("ja"), Locale("ja_JP"), Locale("ko"),
493 Locale("ko_KR"), Locale("nb_NO"), Locale("nl_NL"), Locale("nn_NO"), Locale("pl_PL"),
494 Locale("pt"), Locale("pt_BR"), Locale("pt_PT"), Locale("ru_RU"), Locale("sv_SE"),
495 Locale("th_TH"), Locale("tr_TR"), Locale("zh"), Locale("zh_Hans"), Locale("zh_Hans_CN"),
496 Locale("zh_Hant"), Locale("zh_Hant_TW"), Locale("fa"), Locale("ccp")
497 };
498
499 if (bTestAll) {
500 gLocaleData->locales = Locale::getAvailableLocales(gLocaleData->nLocales);
501 } else if (quick) {
502 gLocaleData->locales = locales1;
503 gLocaleData->nLocales = UPRV_LENGTHOF(locales1);
504 } else {
505 gLocaleData->locales = locales2;
506 gLocaleData->nLocales = UPRV_LENGTHOF(locales2);
507 }
508
509 gLocaleData->START_TIME = START_TIME;
510 gLocaleData->END_TIME = END_TIME;
511 gLocaleData->resetTestIteration();
512
513 // start IntlTest.threadCount threads, each running the function RunTimeRoundTripTests().
514
515 ThreadPool<TimeZoneFormatTest> threads(this, threadCount, &TimeZoneFormatTest::RunTimeRoundTripTests);
516 threads.start(); // Start all threads.
517 threads.join(); // Wait for all threads to finish.
518
519 UDate total = 0;
520 logln("### Elapsed time by patterns ###");
521 for (int32_t i = 0; i < UPRV_LENGTHOF(PATTERNS); i++) {
522 logln(UnicodeString("") + gLocaleData->times[i] + "ms (" + PATTERNS[i] + ")");
523 total += gLocaleData->times[i];
524 }
525 logln((UnicodeString) "Total: " + total + "ms");
526 logln((UnicodeString) "Iteration: " + gLocaleData->testCounts);
527 }
528
529
530 // TimeZoneFormatTest::RunTimeRoundTripTests()
531 // This function loops, running time zone format round trip test cases until there are no more, then returns.
532 // Threading: multiple invocations of this function are started in parallel
533 // by TimeZoneFormatTest::TestTimeRoundTrip()
534 //
RunTimeRoundTripTests(int32_t threadNumber)535 void TimeZoneFormatTest::RunTimeRoundTripTests(int32_t threadNumber) {
536 UErrorCode status = U_ZERO_ERROR;
537 UBool REALLY_VERBOSE = false;
538
539 // These patterns are ambiguous at DST->STD local time overlap
540 const char* AMBIGUOUS_DST_DECESSION[] = { "v", "vvvv", "V", "VV", "VVV", "VVVV", nullptr };
541
542 // These patterns are ambiguous at STD->STD/DST->DST local time overlap
543 const char* AMBIGUOUS_NEGATIVE_SHIFT[] = { "z", "zzzz", "v", "vvvv", "V", "VV", "VVV", "VVVV", nullptr };
544
545 // These patterns only support integer minutes offset
546 const char* MINUTES_OFFSET[] = { "X", "XX", "XXX", "x", "xx", "xxx", nullptr };
547
548 // Workaround for #6338
549 //UnicodeString BASEPATTERN("yyyy-MM-dd'T'HH:mm:ss.SSS");
550 UnicodeString BASEPATTERN("yyyy.MM.dd HH:mm:ss.SSS");
551
552 // timer for performance analysis
553 UDate timer;
554 UDate testTimes[4];
555 UBool expectedRoundTrip[4];
556 int32_t testLen = 0;
557
558 StringEnumeration *tzids = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL, nullptr, nullptr, status);
559 if (U_FAILURE(status)) {
560 if (status == U_MISSING_RESOURCE_ERROR) {
561 // This error is generally caused by data not being present.
562 dataerrln("TimeZone::createTimeZoneIDEnumeration failed - %s", u_errorName(status));
563 } else {
564 errln("TimeZone::createTimeZoneIDEnumeration failed: %s", u_errorName(status));
565 }
566 return;
567 }
568
569 int32_t locidx = -1;
570 int32_t patidx = -1;
571
572 while (gLocaleData->nextTest(locidx, patidx)) {
573
574 UnicodeString pattern(BASEPATTERN);
575 pattern.append(" ").append(PATTERNS[patidx]);
576 logln(" Thread %d, Locale %s, Pattern %s",
577 threadNumber, gLocaleData->locales[locidx].getName(), CStr(pattern)());
578
579 SimpleDateFormat *sdf = new SimpleDateFormat(pattern, gLocaleData->locales[locidx], status);
580 if (U_FAILURE(status)) {
581 errcheckln(status, (UnicodeString) "new SimpleDateFormat failed for pattern " +
582 pattern + " for locale " + gLocaleData->locales[locidx].getName() + " - " + u_errorName(status));
583 status = U_ZERO_ERROR;
584 continue;
585 }
586
587 UBool minutesOffset = contains(MINUTES_OFFSET, PATTERNS[patidx]);
588
589 tzids->reset(status);
590 const UnicodeString *tzid;
591
592 timer = Calendar::getNow();
593
594 while ((tzid = tzids->snext(status))) {
595 if (uprv_strcmp(PATTERNS[patidx], "V") == 0) {
596 // Some zones do not have short ID assigned, such as Asia/Riyadh87.
597 // The time roundtrip will fail for such zones with pattern "V" (short zone ID).
598 // This is expected behavior.
599 const char16_t* shortZoneID = ZoneMeta::getShortID(*tzid);
600 if (shortZoneID == nullptr) {
601 continue;
602 }
603 } else if (uprv_strcmp(PATTERNS[patidx], "VVV") == 0) {
604 // Some zones are not associated with any region, such as Etc/GMT+8.
605 // The time roundtrip will fail for such zone with pattern "VVV" (exemplar location).
606 // This is expected behavior.
607 if (tzid->indexOf((char16_t)0x2F) < 0 || tzid->indexOf(ETC_SLASH, -1, 0) >= 0
608 || tzid->indexOf(SYSTEMV_SLASH, -1, 0) >= 0 || tzid->indexOf(RIYADH8, -1, 0) >= 0) {
609 continue;
610 }
611 }
612
613 if ((*tzid == "Pacific/Apia" || *tzid == "Pacific/Midway" || *tzid == "Pacific/Pago_Pago")
614 && uprv_strcmp(PATTERNS[patidx], "vvvv") == 0
615 && logKnownIssue("11052", "Ambiguous zone name - Samoa Time")) {
616 continue;
617 }
618
619 if ((*tzid == "America/Miquelon" || *tzid == "America/Hermosillo" || *tzid == "America/Mazatlan")
620 && uprv_strncmp(gLocaleData->locales[locidx].getName(),"ku",2) == 0
621 && uprv_strcmp(PATTERNS[patidx], "v") == 0
622 && logKnownIssue("CLDR-17024", "TestTimeRoundTrip fail with tz=America/Miquelon, pattern=v, locale=ku")) {
623 continue;
624 }
625
626
627 BasicTimeZone *tz = dynamic_cast<BasicTimeZone*>(TimeZone::createTimeZone(*tzid));
628 sdf->setTimeZone(*tz);
629
630 UDate t = gLocaleData->START_TIME;
631 TimeZoneTransition tzt;
632 UBool tztAvail = false;
633 UBool middle = true;
634
635 while (t < gLocaleData->END_TIME) {
636 if (!tztAvail) {
637 testTimes[0] = t;
638 expectedRoundTrip[0] = true;
639 testLen = 1;
640 } else {
641 int32_t fromOffset = tzt.getFrom()->getRawOffset() + tzt.getFrom()->getDSTSavings();
642 int32_t toOffset = tzt.getTo()->getRawOffset() + tzt.getTo()->getDSTSavings();
643 int32_t delta = toOffset - fromOffset;
644 if (delta < 0) {
645 UBool isDstDecession = tzt.getFrom()->getDSTSavings() > 0 && tzt.getTo()->getDSTSavings() == 0;
646 testTimes[0] = t + delta - 1;
647 expectedRoundTrip[0] = true;
648 testTimes[1] = t + delta;
649 expectedRoundTrip[1] = isDstDecession ?
650 !contains(AMBIGUOUS_DST_DECESSION, PATTERNS[patidx]) :
651 !contains(AMBIGUOUS_NEGATIVE_SHIFT, PATTERNS[patidx]);
652 testTimes[2] = t - 1;
653 expectedRoundTrip[2] = isDstDecession ?
654 !contains(AMBIGUOUS_DST_DECESSION, PATTERNS[patidx]) :
655 !contains(AMBIGUOUS_NEGATIVE_SHIFT, PATTERNS[patidx]);
656 testTimes[3] = t;
657 expectedRoundTrip[3] = true;
658 testLen = 4;
659 } else {
660 testTimes[0] = t - 1;
661 expectedRoundTrip[0] = true;
662 testTimes[1] = t;
663 expectedRoundTrip[1] = true;
664 testLen = 2;
665 }
666 }
667 for (int32_t testidx = 0; testidx < testLen; testidx++) {
668 if (quick) {
669 // reduce regular test time
670 if (!expectedRoundTrip[testidx]) {
671 continue;
672 }
673 }
674
675 {
676 Mutex lock;
677 gLocaleData->testCounts++;
678 }
679
680 UnicodeString text;
681 FieldPosition fpos(FieldPosition::DONT_CARE);
682 sdf->format(testTimes[testidx], text, fpos);
683
684 UDate parsedDate = sdf->parse(text, status);
685 if (U_FAILURE(status)) {
686 errln((UnicodeString) "Parse failure for text=" + text + ", tzid=" + *tzid + ", locale=" + gLocaleData->locales[locidx].getName()
687 + ", pattern=" + PATTERNS[patidx] + ", time=" + testTimes[testidx]);
688 status = U_ZERO_ERROR;
689 continue;
690 }
691
692 int32_t timeDiff = (int32_t)(parsedDate - testTimes[testidx]);
693 UBool bTimeMatch = minutesOffset ?
694 (timeDiff/60000)*60000 == 0 : timeDiff == 0;
695 if (!bTimeMatch) {
696 UnicodeString msg = (UnicodeString) "Time round trip failed for " + "tzid=" + *tzid
697 + ", locale=" + gLocaleData->locales[locidx].getName() + ", pattern=" + PATTERNS[patidx]
698 + ", text=" + text + ", time=" + testTimes[testidx] + ", restime=" + parsedDate + ", diff=" + (parsedDate - testTimes[testidx]);
699 // Timebomb for TZData update
700 if (expectedRoundTrip[testidx]
701 && !isSpecialTimeRoundTripCase(gLocaleData->locales[locidx].getName(), *tzid,
702 PATTERNS[patidx], testTimes[testidx])) {
703 errln((UnicodeString) "FAIL: " + msg);
704 } else if (REALLY_VERBOSE) {
705 logln(msg);
706 }
707 }
708 }
709 tztAvail = tz->getNextTransition(t, false, tzt);
710 if (!tztAvail) {
711 break;
712 }
713 if (middle) {
714 // Test the date in the middle of two transitions.
715 t += (int64_t) ((tzt.getTime() - t) / 2);
716 middle = false;
717 tztAvail = false;
718 } else {
719 t = tzt.getTime();
720 }
721 }
722 delete tz;
723 }
724 UDate elapsedTime = Calendar::getNow() - timer;
725 gLocaleData->addTime(elapsedTime, patidx);
726 delete sdf;
727 }
728 delete tzids;
729 }
730
731 void
TestAdoptDefaultThreadSafe()732 TimeZoneFormatTest::TestAdoptDefaultThreadSafe() {
733 ThreadPool<TimeZoneFormatTest> threads(this, threadCount, &TimeZoneFormatTest::RunAdoptDefaultThreadSafeTests);
734 threads.start(); // Start all threads.
735 threads.join(); // Wait for all threads to finish.
736 }
737
738 static const int32_t kAdoptDefaultIteration = 10;
739 static const int32_t kCreateDefaultIteration = 5000;
740 static const int64_t kStartTime = 1557288964845;
741
RunAdoptDefaultThreadSafeTests(int32_t threadNumber)742 void TimeZoneFormatTest::RunAdoptDefaultThreadSafeTests(int32_t threadNumber) {
743 UErrorCode status = U_ZERO_ERROR;
744 if (threadNumber % 2 == 0) {
745 for (int32_t i = 0; i < kAdoptDefaultIteration; i++) {
746 std::unique_ptr<icu::StringEnumeration> timezones(
747 icu::TimeZone::createEnumeration(status));
748 // Fails with missing data.
749 if (U_FAILURE(status)) {
750 dataerrln("Unable to create TimeZone enumeration");
751 return;
752 }
753 while (const icu::UnicodeString* timezone = timezones->snext(status)) {
754 status = U_ZERO_ERROR;
755 icu::TimeZone::adoptDefault(icu::TimeZone::createTimeZone(*timezone));
756 }
757 }
758 } else {
759 int32_t rawOffset;
760 int32_t dstOffset;
761 int64_t date = kStartTime;
762 for (int32_t i = 0; i < kCreateDefaultIteration; i++) {
763 date += 6000 * i;
764 std::unique_ptr<icu::TimeZone> tz(icu::TimeZone::createDefault());
765 status = U_ZERO_ERROR;
766 tz->getOffset(static_cast<UDate>(date), true, rawOffset, dstOffset, status);
767 status = U_ZERO_ERROR;
768 tz->getOffset(static_cast<UDate>(date), false, rawOffset, dstOffset, status);
769 }
770 }
771 }
772
773 typedef struct {
774 const char* text;
775 int32_t inPos;
776 const char* locale;
777 UTimeZoneFormatStyle style;
778 uint32_t parseOptions;
779 const char* expected;
780 int32_t outPos;
781 UTimeZoneFormatTimeType timeType;
782 } ParseTestData;
783
784 void
TestParse()785 TimeZoneFormatTest::TestParse() {
786 const ParseTestData DATA[] = {
787 // text inPos locale style
788 // parseOptions expected outPos timeType
789 {"Z", 0, "en_US", UTZFMT_STYLE_ISO_EXTENDED_FULL,
790 UTZFMT_PARSE_OPTION_NONE, "Etc/GMT", 1, UTZFMT_TIME_TYPE_UNKNOWN},
791
792 {"Z", 0, "en_US", UTZFMT_STYLE_SPECIFIC_LONG,
793 UTZFMT_PARSE_OPTION_NONE, "Etc/GMT", 1, UTZFMT_TIME_TYPE_UNKNOWN},
794
795 {"Zambia time", 0, "en_US", UTZFMT_STYLE_ISO_EXTENDED_FULL,
796 UTZFMT_PARSE_OPTION_ALL_STYLES, "Etc/GMT", 1, UTZFMT_TIME_TYPE_UNKNOWN},
797
798 {"Zambia time", 0, "en_US", UTZFMT_STYLE_GENERIC_LOCATION,
799 UTZFMT_PARSE_OPTION_NONE, "Africa/Lusaka", 11, UTZFMT_TIME_TYPE_UNKNOWN},
800
801 {"Zambia time", 0, "en_US", UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL,
802 UTZFMT_PARSE_OPTION_ALL_STYLES, "Africa/Lusaka", 11, UTZFMT_TIME_TYPE_UNKNOWN},
803
804 {"+00:00", 0, "en_US", UTZFMT_STYLE_ISO_EXTENDED_FULL,
805 UTZFMT_PARSE_OPTION_NONE, "Etc/GMT", 6, UTZFMT_TIME_TYPE_UNKNOWN},
806
807 {"-01:30:45", 0, "en_US", UTZFMT_STYLE_ISO_EXTENDED_FULL,
808 UTZFMT_PARSE_OPTION_NONE, "GMT-01:30:45", 9, UTZFMT_TIME_TYPE_UNKNOWN},
809
810 {"-7", 0, "en_US", UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL,
811 UTZFMT_PARSE_OPTION_NONE, "GMT-07:00", 2, UTZFMT_TIME_TYPE_UNKNOWN},
812
813 {"-2222", 0, "en_US", UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL,
814 UTZFMT_PARSE_OPTION_NONE, "GMT-22:22", 5, UTZFMT_TIME_TYPE_UNKNOWN},
815
816 {"-3333", 0, "en_US", UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL,
817 UTZFMT_PARSE_OPTION_NONE, "GMT-03:33", 4, UTZFMT_TIME_TYPE_UNKNOWN},
818
819 {"XXX+01:30YYY", 3, "en_US", UTZFMT_STYLE_LOCALIZED_GMT,
820 UTZFMT_PARSE_OPTION_NONE, "GMT+01:30", 9, UTZFMT_TIME_TYPE_UNKNOWN},
821
822 {"GMT0", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT,
823 UTZFMT_PARSE_OPTION_NONE, "Etc/GMT", 3, UTZFMT_TIME_TYPE_UNKNOWN},
824
825 {"EST", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT,
826 UTZFMT_PARSE_OPTION_NONE, "America/New_York", 3, UTZFMT_TIME_TYPE_STANDARD},
827
828 {"ESTx", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT,
829 UTZFMT_PARSE_OPTION_NONE, "America/New_York", 3, UTZFMT_TIME_TYPE_STANDARD},
830
831 {"EDTx", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT,
832 UTZFMT_PARSE_OPTION_NONE, "America/New_York", 3, UTZFMT_TIME_TYPE_DAYLIGHT},
833
834 {"EST", 0, "en_US", UTZFMT_STYLE_SPECIFIC_LONG,
835 UTZFMT_PARSE_OPTION_NONE, nullptr, 0, UTZFMT_TIME_TYPE_UNKNOWN},
836
837 {"EST", 0, "en_US", UTZFMT_STYLE_SPECIFIC_LONG,
838 UTZFMT_PARSE_OPTION_ALL_STYLES, "America/New_York", 3, UTZFMT_TIME_TYPE_STANDARD},
839
840 {"EST", 0, "en_CA", UTZFMT_STYLE_SPECIFIC_SHORT,
841 UTZFMT_PARSE_OPTION_NONE, "America/Toronto", 3, UTZFMT_TIME_TYPE_STANDARD},
842
843 {"CST", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT,
844 UTZFMT_PARSE_OPTION_NONE, "America/Chicago", 3, UTZFMT_TIME_TYPE_STANDARD},
845
846 {"CST", 0, "en_GB", UTZFMT_STYLE_SPECIFIC_SHORT,
847 UTZFMT_PARSE_OPTION_NONE, nullptr, 0, UTZFMT_TIME_TYPE_UNKNOWN},
848
849 {"CST", 0, "en_GB", UTZFMT_STYLE_SPECIFIC_SHORT,
850 UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS, "America/Chicago", 3, UTZFMT_TIME_TYPE_STANDARD},
851
852 {"--CST--", 2, "en_GB", UTZFMT_STYLE_SPECIFIC_SHORT,
853 UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS, "America/Chicago", 5, UTZFMT_TIME_TYPE_STANDARD},
854
855 {"CST", 0, "zh_CN", UTZFMT_STYLE_SPECIFIC_SHORT,
856 UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS, "Asia/Shanghai", 3, UTZFMT_TIME_TYPE_STANDARD},
857
858 {"AEST", 0, "en_AU", UTZFMT_STYLE_SPECIFIC_SHORT,
859 UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS, "Australia/Sydney", 4, UTZFMT_TIME_TYPE_STANDARD},
860
861 {"AST", 0, "ar_SA", UTZFMT_STYLE_SPECIFIC_SHORT,
862 UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS, "Asia/Riyadh", 3, UTZFMT_TIME_TYPE_STANDARD},
863
864 {"AQTST", 0, "en", UTZFMT_STYLE_SPECIFIC_LONG,
865 UTZFMT_PARSE_OPTION_NONE, nullptr, 0, UTZFMT_TIME_TYPE_UNKNOWN},
866
867 {"AQTST", 0, "en", UTZFMT_STYLE_SPECIFIC_LONG,
868 UTZFMT_PARSE_OPTION_ALL_STYLES, nullptr, 0, UTZFMT_TIME_TYPE_UNKNOWN},
869
870 {"AQTST", 0, "en", UTZFMT_STYLE_SPECIFIC_LONG,
871 UTZFMT_PARSE_OPTION_ALL_STYLES | UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS, "Asia/Aqtobe", 5, UTZFMT_TIME_TYPE_DAYLIGHT},
872
873 {nullptr, 0, nullptr, UTZFMT_STYLE_GENERIC_LOCATION,
874 UTZFMT_PARSE_OPTION_NONE, nullptr, 0, UTZFMT_TIME_TYPE_UNKNOWN}
875 };
876
877 for (int32_t i = 0; DATA[i].text; i++) {
878 UErrorCode status = U_ZERO_ERROR;
879 LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(Locale(DATA[i].locale), status));
880 if (U_FAILURE(status)) {
881 dataerrln("Fail TimeZoneFormat::createInstance: %s", u_errorName(status));
882 continue;
883 }
884 UTimeZoneFormatTimeType ttype = UTZFMT_TIME_TYPE_UNKNOWN;
885 ParsePosition pos(DATA[i].inPos);
886 TimeZone* tz = tzfmt->parse(DATA[i].style, DATA[i].text, pos, DATA[i].parseOptions, &ttype);
887
888 UnicodeString errMsg;
889 if (tz) {
890 UnicodeString outID;
891 tz->getID(outID);
892 if (outID != UnicodeString(DATA[i].expected)) {
893 errMsg = (UnicodeString)"Time zone ID: " + outID + " - expected: " + DATA[i].expected;
894 } else if (pos.getIndex() != DATA[i].outPos) {
895 errMsg = (UnicodeString)"Parsed pos: " + pos.getIndex() + " - expected: " + DATA[i].outPos;
896 } else if (ttype != DATA[i].timeType) {
897 errMsg = (UnicodeString)"Time type: " + ttype + " - expected: " + DATA[i].timeType;
898 }
899 delete tz;
900 } else {
901 if (DATA[i].expected) {
902 errMsg = (UnicodeString)"Parse failure - expected: " + DATA[i].expected;
903 }
904 }
905 if (errMsg.length() > 0) {
906 errln((UnicodeString)"Fail: " + errMsg + " [text=" + DATA[i].text + ", pos=" + DATA[i].inPos + ", style=" + DATA[i].style + "]");
907 }
908 }
909 }
910
911 void
TestISOFormat()912 TimeZoneFormatTest::TestISOFormat() {
913 const int32_t OFFSET[] = {
914 0, // 0
915 999, // 0.999s
916 -59999, // -59.999s
917 60000, // 1m
918 -77777, // -1m 17.777s
919 1800000, // 30m
920 -3600000, // -1h
921 36000000, // 10h
922 -37800000, // -10h 30m
923 -37845000, // -10h 30m 45s
924 108000000, // 30h
925 };
926
927 const char* ISO_STR[][11] = {
928 // 0
929 {
930 "Z", "Z", "Z", "Z", "Z",
931 "+00", "+0000", "+00:00", "+0000", "+00:00",
932 "+0000"
933 },
934 // 999
935 {
936 "Z", "Z", "Z", "Z", "Z",
937 "+00", "+0000", "+00:00", "+0000", "+00:00",
938 "+0000"
939 },
940 // -59999
941 {
942 "Z", "Z", "Z", "-000059", "-00:00:59",
943 "+00", "+0000", "+00:00", "-000059", "-00:00:59",
944 "-000059"
945 },
946 // 60000
947 {
948 "+0001", "+0001", "+00:01", "+0001", "+00:01",
949 "+0001", "+0001", "+00:01", "+0001", "+00:01",
950 "+0001"
951 },
952 // -77777
953 {
954 "-0001", "-0001", "-00:01", "-000117", "-00:01:17",
955 "-0001", "-0001", "-00:01", "-000117", "-00:01:17",
956 "-000117"
957 },
958 // 1800000
959 {
960 "+0030", "+0030", "+00:30", "+0030", "+00:30",
961 "+0030", "+0030", "+00:30", "+0030", "+00:30",
962 "+0030"
963 },
964 // -3600000
965 {
966 "-01", "-0100", "-01:00", "-0100", "-01:00",
967 "-01", "-0100", "-01:00", "-0100", "-01:00",
968 "-0100"
969 },
970 // 36000000
971 {
972 "+10", "+1000", "+10:00", "+1000", "+10:00",
973 "+10", "+1000", "+10:00", "+1000", "+10:00",
974 "+1000"
975 },
976 // -37800000
977 {
978 "-1030", "-1030", "-10:30", "-1030", "-10:30",
979 "-1030", "-1030", "-10:30", "-1030", "-10:30",
980 "-1030"
981 },
982 // -37845000
983 {
984 "-1030", "-1030", "-10:30", "-103045", "-10:30:45",
985 "-1030", "-1030", "-10:30", "-103045", "-10:30:45",
986 "-103045"
987 },
988 // 108000000
989 {
990 nullptr, nullptr, nullptr, nullptr, nullptr,
991 nullptr, nullptr, nullptr, nullptr, nullptr,
992 nullptr
993 }
994 };
995
996 const char* PATTERN[] = {
997 "X", "XX", "XXX", "XXXX", "XXXXX",
998 "x", "xx", "xxx", "xxxx", "xxxxx",
999 "Z", // equivalent to "xxxx"
1000 nullptr
1001 };
1002
1003 const int32_t MIN_OFFSET_UNIT[] = {
1004 60000, 60000, 60000, 1000, 1000,
1005 60000, 60000, 60000, 1000, 1000,
1006 1000,
1007 };
1008
1009 // Formatting
1010 UErrorCode status = U_ZERO_ERROR;
1011 LocalPointer<SimpleDateFormat> sdf(new SimpleDateFormat(status), status);
1012 if (U_FAILURE(status)) {
1013 dataerrln("Fail new SimpleDateFormat: %s", u_errorName(status));
1014 return;
1015 }
1016 UDate d = Calendar::getNow();
1017
1018 for (uint32_t i = 0; i < UPRV_LENGTHOF(OFFSET); i++) {
1019 SimpleTimeZone* tz = new SimpleTimeZone(OFFSET[i], UnicodeString("Zone Offset:") + OFFSET[i] + "ms");
1020 sdf->adoptTimeZone(tz);
1021 for (int32_t j = 0; PATTERN[j] != nullptr; j++) {
1022 sdf->applyPattern(UnicodeString(PATTERN[j]));
1023 UnicodeString result;
1024 sdf->format(d, result);
1025
1026 if (ISO_STR[i][j]) {
1027 if (result != UnicodeString(ISO_STR[i][j])) {
1028 errln((UnicodeString)"FAIL: pattern=" + PATTERN[j] + ", offset=" + OFFSET[i] + " -> "
1029 + result + " (expected: " + ISO_STR[i][j] + ")");
1030 }
1031 } else {
1032 // Offset out of range
1033 // Note: for now, there is no way to propagate the error status through
1034 // the SimpleDateFormat::format above.
1035 if (result.length() > 0) {
1036 errln((UnicodeString)"FAIL: Non-Empty result for pattern=" + PATTERN[j] + ", offset=" + OFFSET[i]
1037 + " (expected: empty result)");
1038 }
1039 }
1040 }
1041 }
1042
1043 // Parsing
1044 LocalPointer<Calendar> outcal(Calendar::createInstance(status));
1045 if (U_FAILURE(status)) {
1046 dataerrln("Fail new Calendar: %s", u_errorName(status));
1047 return;
1048 }
1049 for (int32_t i = 0; ISO_STR[i][0] != nullptr; i++) {
1050 for (int32_t j = 0; PATTERN[j] != nullptr; j++) {
1051 if (ISO_STR[i][j] == nullptr) {
1052 continue;
1053 }
1054 ParsePosition pos(0);
1055 SimpleTimeZone* bogusTZ = new SimpleTimeZone(-1, UnicodeString("Zone Offset: -1ms"));
1056 outcal->adoptTimeZone(bogusTZ);
1057 sdf->applyPattern(PATTERN[j]);
1058
1059 sdf->parse(UnicodeString(ISO_STR[i][j]), *(outcal.getAlias()), pos);
1060
1061 if (pos.getIndex() != (int32_t)uprv_strlen(ISO_STR[i][j])) {
1062 errln((UnicodeString)"FAIL: Failed to parse the entire input string: " + ISO_STR[i][j]);
1063 }
1064
1065 const TimeZone& outtz = outcal->getTimeZone();
1066 int32_t outOffset = outtz.getRawOffset();
1067 int32_t adjustedOffset = OFFSET[i] / MIN_OFFSET_UNIT[j] * MIN_OFFSET_UNIT[j];
1068 if (outOffset != adjustedOffset) {
1069 errln((UnicodeString)"FAIL: Incorrect offset:" + outOffset + "ms for input string: " + ISO_STR[i][j]
1070 + " (expected:" + adjustedOffset + "ms)");
1071 }
1072 }
1073 }
1074 }
1075
1076
1077 typedef struct {
1078 const char* locale;
1079 const char* tzid;
1080 UDate date;
1081 UTimeZoneFormatStyle style;
1082 const char* expected;
1083 UTimeZoneFormatTimeType timeType;
1084 } FormatTestData;
1085
1086 void
TestFormat()1087 TimeZoneFormatTest::TestFormat() {
1088 UDate dateJan = 1358208000000.0; // 2013-01-15T00:00:00Z
1089 UDate dateJul = 1373846400000.0; // 2013-07-15T00:00:00Z
1090
1091 const FormatTestData DATA[] = {
1092 {
1093 "en",
1094 "America/Los_Angeles",
1095 dateJan,
1096 UTZFMT_STYLE_GENERIC_LOCATION,
1097 "Los Angeles Time",
1098 UTZFMT_TIME_TYPE_UNKNOWN
1099 },
1100 {
1101 "en",
1102 "America/Los_Angeles",
1103 dateJan,
1104 UTZFMT_STYLE_GENERIC_LONG,
1105 "Pacific Time",
1106 UTZFMT_TIME_TYPE_UNKNOWN
1107 },
1108 {
1109 "en",
1110 "America/Los_Angeles",
1111 dateJan,
1112 UTZFMT_STYLE_SPECIFIC_LONG,
1113 "Pacific Standard Time",
1114 UTZFMT_TIME_TYPE_STANDARD
1115 },
1116 {
1117 "en",
1118 "America/Los_Angeles",
1119 dateJul,
1120 UTZFMT_STYLE_SPECIFIC_LONG,
1121 "Pacific Daylight Time",
1122 UTZFMT_TIME_TYPE_DAYLIGHT
1123 },
1124 {
1125 "ja",
1126 "America/Los_Angeles",
1127 dateJan,
1128 UTZFMT_STYLE_ZONE_ID,
1129 "America/Los_Angeles",
1130 UTZFMT_TIME_TYPE_UNKNOWN
1131 },
1132 {
1133 "fr",
1134 "America/Los_Angeles",
1135 dateJul,
1136 UTZFMT_STYLE_ZONE_ID_SHORT,
1137 "uslax",
1138 UTZFMT_TIME_TYPE_UNKNOWN
1139 },
1140 {
1141 "en",
1142 "America/Los_Angeles",
1143 dateJan,
1144 UTZFMT_STYLE_EXEMPLAR_LOCATION,
1145 "Los Angeles",
1146 UTZFMT_TIME_TYPE_UNKNOWN
1147 },
1148
1149 {
1150 "ja",
1151 "Asia/Tokyo",
1152 dateJan,
1153 UTZFMT_STYLE_GENERIC_LONG,
1154 "\\u65E5\\u672C\\u6A19\\u6E96\\u6642",
1155 UTZFMT_TIME_TYPE_UNKNOWN
1156 },
1157
1158 {nullptr, nullptr, 0.0, UTZFMT_STYLE_GENERIC_LOCATION, nullptr, UTZFMT_TIME_TYPE_UNKNOWN}
1159 };
1160
1161 for (int32_t i = 0; DATA[i].locale; i++) {
1162 UErrorCode status = U_ZERO_ERROR;
1163 LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(Locale(DATA[i].locale), status));
1164 if (U_FAILURE(status)) {
1165 dataerrln("Fail TimeZoneFormat::createInstance: %s", u_errorName(status));
1166 continue;
1167 }
1168
1169 LocalPointer<TimeZone> tz(TimeZone::createTimeZone(DATA[i].tzid));
1170 UnicodeString out;
1171 UTimeZoneFormatTimeType timeType;
1172
1173 tzfmt->format(DATA[i].style, *(tz.getAlias()), DATA[i].date, out, &timeType);
1174 UnicodeString expected(DATA[i].expected, -1, US_INV);
1175 expected = expected.unescape();
1176
1177 assertEquals(UnicodeString("Format result for ") + DATA[i].tzid + " (Test Case " + i + ")", expected, out);
1178 if (DATA[i].timeType != timeType) {
1179 dataerrln(UnicodeString("Formatted time zone type (Test Case ") + i + "), returned="
1180 + timeType + ", expected=" + DATA[i].timeType);
1181 }
1182 }
1183 }
1184
1185 void
TestFormatTZDBNames()1186 TimeZoneFormatTest::TestFormatTZDBNames() {
1187 UDate dateJan = 1358208000000.0; // 2013-01-15T00:00:00Z
1188 UDate dateJul = 1373846400000.0; // 2013-07-15T00:00:00Z
1189
1190 const FormatTestData DATA[] = {
1191 {
1192 "en",
1193 "America/Chicago",
1194 dateJan,
1195 UTZFMT_STYLE_SPECIFIC_SHORT,
1196 "CST",
1197 UTZFMT_TIME_TYPE_STANDARD
1198 },
1199 {
1200 "en",
1201 "Asia/Shanghai",
1202 dateJan,
1203 UTZFMT_STYLE_SPECIFIC_SHORT,
1204 "CST",
1205 UTZFMT_TIME_TYPE_STANDARD
1206 },
1207 {
1208 "zh_Hans",
1209 "Asia/Shanghai",
1210 dateJan,
1211 UTZFMT_STYLE_SPECIFIC_SHORT,
1212 "CST",
1213 UTZFMT_TIME_TYPE_STANDARD
1214 },
1215 {
1216 "en",
1217 "America/Los_Angeles",
1218 dateJul,
1219 UTZFMT_STYLE_SPECIFIC_LONG,
1220 "GMT-07:00", // No long display names
1221 UTZFMT_TIME_TYPE_DAYLIGHT
1222 },
1223 {
1224 "ja",
1225 "America/Los_Angeles",
1226 dateJul,
1227 UTZFMT_STYLE_SPECIFIC_SHORT,
1228 "PDT",
1229 UTZFMT_TIME_TYPE_DAYLIGHT
1230 },
1231 {
1232 "en",
1233 "Australia/Sydney",
1234 dateJan,
1235 UTZFMT_STYLE_SPECIFIC_SHORT,
1236 "AEDT",
1237 UTZFMT_TIME_TYPE_DAYLIGHT
1238 },
1239 {
1240 "en",
1241 "Australia/Sydney",
1242 dateJul,
1243 UTZFMT_STYLE_SPECIFIC_SHORT,
1244 "AEST",
1245 UTZFMT_TIME_TYPE_STANDARD
1246 },
1247
1248 {nullptr, nullptr, 0.0, UTZFMT_STYLE_GENERIC_LOCATION, nullptr, UTZFMT_TIME_TYPE_UNKNOWN}
1249 };
1250
1251 for (int32_t i = 0; DATA[i].locale; i++) {
1252 UErrorCode status = U_ZERO_ERROR;
1253 Locale loc(DATA[i].locale);
1254 LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(loc, status));
1255 if (U_FAILURE(status)) {
1256 dataerrln("Fail TimeZoneFormat::createInstance: %s", u_errorName(status));
1257 continue;
1258 }
1259 TimeZoneNames *tzdbNames = TimeZoneNames::createTZDBInstance(loc, status);
1260 if (U_FAILURE(status)) {
1261 dataerrln("Fail TimeZoneNames::createTZDBInstance: %s", u_errorName(status));
1262 continue;
1263 }
1264 tzfmt->adoptTimeZoneNames(tzdbNames);
1265
1266 LocalPointer<TimeZone> tz(TimeZone::createTimeZone(DATA[i].tzid));
1267 UnicodeString out;
1268 UTimeZoneFormatTimeType timeType;
1269
1270 tzfmt->format(DATA[i].style, *(tz.getAlias()), DATA[i].date, out, &timeType);
1271 UnicodeString expected(DATA[i].expected, -1, US_INV);
1272 expected = expected.unescape();
1273
1274 assertEquals(UnicodeString("Format result for ") + DATA[i].tzid + " (Test Case " + i + ")", expected, out);
1275 if (DATA[i].timeType != timeType) {
1276 dataerrln(UnicodeString("Formatted time zone type (Test Case ") + i + "), returned="
1277 + timeType + ", expected=" + DATA[i].timeType);
1278 }
1279 }
1280 }
1281
1282 void
TestFormatCustomZone()1283 TimeZoneFormatTest::TestFormatCustomZone() {
1284 struct {
1285 const char* id;
1286 int32_t offset;
1287 const char* expected;
1288 } TESTDATA[] = {
1289 { "abc", 3600000, "GMT+01:00" }, // unknown ID
1290 { "$abc", -3600000, "GMT-01:00" }, // unknown, with ASCII variant char '$'
1291 { "\\u00c1\\u00df\\u00c7", 5400000, "GMT+01:30"}, // unknown, with non-ASCII chars
1292 { nullptr, 0, nullptr }
1293 };
1294
1295 UDate now = Calendar::getNow();
1296
1297 for (int32_t i = 0; ; i++) {
1298 const char *id = TESTDATA[i].id;
1299 if (id == nullptr) {
1300 break;
1301 }
1302 UnicodeString tzid = UnicodeString(id, -1, US_INV).unescape();
1303 SimpleTimeZone tz(TESTDATA[i].offset, tzid);
1304
1305 UErrorCode status = U_ZERO_ERROR;
1306 LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(Locale("en"), status));
1307 if (tzfmt.isNull()) {
1308 dataerrln("FAIL: TimeZoneFormat::createInstance failed for en");
1309 return;
1310 }
1311 UnicodeString tzstr;
1312 UnicodeString expected = UnicodeString(TESTDATA[i].expected, -1, US_INV).unescape();
1313
1314 tzfmt->format(UTZFMT_STYLE_SPECIFIC_LONG, tz, now, tzstr, nullptr);
1315 assertEquals(UnicodeString("Format result for ") + tzid, expected, tzstr);
1316 }
1317 }
1318
1319 void
Test22615NonASCIIID()1320 TimeZoneFormatTest::Test22615NonASCIIID() {
1321 UErrorCode status = U_ZERO_ERROR;
1322 LocalPointer<TimeZoneNames> tzdb(TimeZoneNames::createTZDBInstance(Locale("en"), status));
1323 // A test to ensure under the debugging build non ASCII id will not cause
1324 // internal assertion error.
1325 UnicodeString id(9, u'\u00C0', 8);
1326 UnicodeString output;
1327 tzdb->getMetaZoneDisplayName(id, UTZNM_SHORT_STANDARD, output);
1328 assertTrue("getMetaZoneID of non ASCII id should return bogus string",
1329 output.isBogus());
1330
1331 status = U_ZERO_ERROR;
1332 std::unique_ptr<icu::StringEnumeration> enumeration(
1333 tzdb->getAvailableMetaZoneIDs(id, status));
1334 assertSuccess("getAvailableMetaZoneIDs should success", status);
1335 assertEquals("getAvailableMetaZoneIDs with non ASCII id return 0 ids",
1336 0, enumeration->count(status));
1337 assertSuccess("count should success", status);
1338
1339 output.remove();
1340 tzdb->getMetaZoneID(id, 0, output);
1341 assertTrue("getMetaZoneID of non ASCII id should return bogus string",
1342 output.isBogus());
1343
1344 output.remove();
1345 tzdb->getMetaZoneDisplayName(id, UTZNM_EXEMPLAR_LOCATION, output);
1346 assertTrue("getMetaZoneDisplayName of non ASCII id should return bogus string",
1347 output.isBogus());
1348
1349 output.remove();
1350 tzdb->getTimeZoneDisplayName(id, UTZNM_SHORT_DAYLIGHT, output);
1351 assertTrue("getTimeZoneDisplayName of non ASCII id should return bogus string",
1352 output.isBogus());
1353
1354 output.remove();
1355 tzdb->getExemplarLocationName(id, output);
1356 assertTrue("getExemplarLocationName of non ASCII id should return bogus string",
1357 output.isBogus());
1358
1359 output.remove();
1360 tzdb->getDisplayName(id, UTZNM_LONG_GENERIC, 0, output);
1361 assertTrue("getDisplayName of non ASCII id should return bogus string",
1362 output.isBogus());
1363 }
1364
1365 void
Test22614GetMetaZoneNamesNotCrash()1366 TimeZoneFormatTest::Test22614GetMetaZoneNamesNotCrash() {
1367 UErrorCode status = U_ZERO_ERROR;
1368 LocalPointer<TimeZoneNames> tzdbNames(TimeZoneNames::createTZDBInstance(Locale("en"), status));
1369 UnicodeString name;
1370 for (int32_t i = 124; i < 150; i++) {
1371 name.remove();
1372 UnicodeString mzId(i+1, u'A', i);
1373 tzdbNames->getMetaZoneDisplayName(mzId, UTZNM_SHORT_STANDARD, name);
1374 }
1375 }
1376 void
TestFormatTZDBNamesAllZoneCoverage()1377 TimeZoneFormatTest::TestFormatTZDBNamesAllZoneCoverage() {
1378 UErrorCode status = U_ZERO_ERROR;
1379 LocalPointer<StringEnumeration> tzids(TimeZone::createEnumeration(status));
1380 if (U_FAILURE(status)) {
1381 dataerrln("Unable to create TimeZone enumeration", __FILE__, __LINE__);
1382 return;
1383 }
1384 const UnicodeString *tzid;
1385 LocalPointer<TimeZoneNames> tzdbNames(TimeZoneNames::createTZDBInstance(Locale("en"), status));
1386 UDate now = Calendar::getNow();
1387 UnicodeString mzId;
1388 UnicodeString name;
1389 while ((tzid = tzids->snext(status))) {
1390 logln("Zone: " + *tzid);
1391 LocalPointer<TimeZone> tz(TimeZone::createTimeZone(*tzid));
1392 tzdbNames->getMetaZoneID(*tzid, now, mzId);
1393 if (mzId.isBogus()) {
1394 logln((UnicodeString)"Meta zone: <not available>");
1395 } else {
1396 logln((UnicodeString)"Meta zone: " + mzId);
1397 }
1398
1399 // mzID could be bogus here
1400 tzdbNames->getMetaZoneDisplayName(mzId, UTZNM_SHORT_STANDARD, name);
1401 // name could be bogus here
1402 if (name.isBogus()) {
1403 logln((UnicodeString)"Meta zone short standard name: <not available>");
1404 }
1405 else {
1406 logln((UnicodeString)"Meta zone short standard name: " + name);
1407 }
1408
1409 tzdbNames->getMetaZoneDisplayName(mzId, UTZNM_SHORT_DAYLIGHT, name);
1410 // name could be bogus here
1411 if (name.isBogus()) {
1412 logln((UnicodeString)"Meta zone short daylight name: <not available>");
1413 }
1414 else {
1415 logln((UnicodeString)"Meta zone short daylight name: " + name);
1416 }
1417 }
1418 }
1419
1420 // Test for checking parse results are same for a same input string
1421 // using SimpleDateFormat initialized with different regional locales - US and Belize.
1422 // Belize did not observe DST from 1968 to 1973, 1975 to 1982, and 1985 and later.
1423 void
TestCentralTime()1424 TimeZoneFormatTest::TestCentralTime() {
1425 UnicodeString pattern(u"y-MM-dd HH:mm:ss zzzz");
1426 UnicodeString testInputs[] = {
1427 // 1970-01-01 - Chicago:STD/Belize:STD
1428 u"1970-01-01 12:00:00 Central Standard Time",
1429 u"1970-01-01 12:00:00 Central Daylight Time",
1430
1431 // 1970-07-01 - Chicago:STD/Belize:STD
1432 u"1970-07-01 12:00:00 Central Standard Time",
1433 u"1970-07-01 12:00:00 Central Daylight Time",
1434
1435 // 1974-01-01 - Chicago:STD/Belize:DST
1436 u"1974-01-01 12:00:00 Central Standard Time",
1437 u"1974-01-01 12:00:00 Central Daylight Time",
1438
1439 // 2020-01-01 - Chicago:STD/Belize:STD
1440 u"2020-01-01 12:00:00 Central Standard Time",
1441 u"2020-01-01 12:00:00 Central Daylight Time",
1442
1443 // 2020-01-01 - Chicago:DST/Belize:STD
1444 u"2020-07-01 12:00:00 Central Standard Time",
1445 u"2020-07-01 12:00:00 Central Daylight Time",
1446
1447 u""
1448 };
1449
1450 UErrorCode status = U_ZERO_ERROR;
1451 SimpleDateFormat sdfUS(pattern, Locale("en_US"), status);
1452 SimpleDateFormat sdfBZ(pattern, Locale("en_BZ"), status);
1453 if (U_FAILURE(status)) {
1454 errln("Failed to create SimpleDateFormat instance");
1455 return;
1456 }
1457
1458 for (int32_t i = 0; !testInputs[i].isEmpty(); i++) {
1459 UDate dUS = sdfUS.parse(testInputs[i], status);
1460 UDate dBZ = sdfBZ.parse(testInputs[i], status);
1461
1462 if (U_FAILURE(status)) {
1463 errln((UnicodeString)"Failed to parse date string: " + testInputs[i]);
1464 continue;
1465 }
1466
1467 if (dUS != dBZ) {
1468 errln((UnicodeString)"Parse results should be same for input: " + testInputs[i]);
1469 }
1470 }
1471 }
1472 void
TestBogusLocale()1473 TimeZoneFormatTest::TestBogusLocale() {
1474 Locale bogus("not a lang");
1475 UErrorCode status = U_ZERO_ERROR;
1476 std::unique_ptr<icu::TimeZoneFormat> tzfmt(
1477 icu::TimeZoneFormat::createInstance(bogus, status));
1478 if (U_FAILURE(status)) {
1479 errln(u"Failed to createInstance with bogus locale");
1480 }
1481 }
1482 #endif /* #if !UCONFIG_NO_FORMATTING */
1483