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