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