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