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