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