]> git.saurik.com Git - apple/icu.git/blob - icuSources/test/intltest/tzfmttst.cpp
ICU-62108.0.1.tar.gz
[apple/icu.git] / icuSources / test / intltest / tzfmttst.cpp
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
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
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
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.
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
407 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
414 void resetTestIteration() {
415 localeIndex = -1;
416 patternIndex = UPRV_LENGTHOF(PATTERNS);
417 numDone = 0;
418 }
419
420 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
436 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
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 //
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 // skip some zone/format combinations until we have a metazone for them and/or
610 // better fallback zone formatting...
611 if ((*tzid == "America/Punta_Arenas" || *tzid == "Antarctica/Palmer") &&
612 uprv_strcmp(PATTERNS[patidx], "zzzz") == 0) {
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;
621 UBool tztAvail = FALSE;
622 UBool middle = TRUE;
623
624 while (t < gLocaleData->END_TIME) {
625 if (!tztAvail) {
626 testTimes[0] = t;
627 expectedRoundTrip[0] = TRUE;
628 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;
636 expectedRoundTrip[0] = TRUE;
637 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;
646 expectedRoundTrip[3] = TRUE;
647 testLen = 4;
648 } else {
649 testTimes[0] = t - 1;
650 expectedRoundTrip[0] = TRUE;
651 testTimes[1] = t;
652 expectedRoundTrip[1] = TRUE;
653 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 }
698 tztAvail = tz->getNextTransition(t, FALSE, tzt);
699 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);
705 middle = FALSE;
706 tztAvail = FALSE;
707 } 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
720
721 typedef struct {
722 const char* text;
723 int32_t inPos;
724 const char* locale;
725 UTimeZoneFormatStyle style;
726 uint32_t parseOptions;
727 const char* expected;
728 int32_t outPos;
729 UTimeZoneFormatTimeType timeType;
730 } ParseTestData;
731
732 void
733 TimeZoneFormatTest::TestParse(void) {
734 const ParseTestData DATA[] = {
735 // text inPos locale style
736 // parseOptions expected outPos timeType
737 {"Z", 0, "en_US", UTZFMT_STYLE_ISO_EXTENDED_FULL,
738 UTZFMT_PARSE_OPTION_NONE, "Etc/GMT", 1, UTZFMT_TIME_TYPE_UNKNOWN},
739
740 {"Z", 0, "en_US", UTZFMT_STYLE_SPECIFIC_LONG,
741 UTZFMT_PARSE_OPTION_NONE, "Etc/GMT", 1, UTZFMT_TIME_TYPE_UNKNOWN},
742
743 {"Zambia time", 0, "en_US", UTZFMT_STYLE_ISO_EXTENDED_FULL,
744 UTZFMT_PARSE_OPTION_ALL_STYLES, "Etc/GMT", 1, UTZFMT_TIME_TYPE_UNKNOWN},
745
746 {"Zambia time", 0, "en_US", UTZFMT_STYLE_GENERIC_LOCATION,
747 UTZFMT_PARSE_OPTION_NONE, "Africa/Lusaka", 11, UTZFMT_TIME_TYPE_UNKNOWN},
748
749 {"Zambia time", 0, "en_US", UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL,
750 UTZFMT_PARSE_OPTION_ALL_STYLES, "Africa/Lusaka", 11, UTZFMT_TIME_TYPE_UNKNOWN},
751
752 {"+00:00", 0, "en_US", UTZFMT_STYLE_ISO_EXTENDED_FULL,
753 UTZFMT_PARSE_OPTION_NONE, "Etc/GMT", 6, UTZFMT_TIME_TYPE_UNKNOWN},
754
755 {"-01:30:45", 0, "en_US", UTZFMT_STYLE_ISO_EXTENDED_FULL,
756 UTZFMT_PARSE_OPTION_NONE, "GMT-01:30:45", 9, UTZFMT_TIME_TYPE_UNKNOWN},
757
758 {"-7", 0, "en_US", UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL,
759 UTZFMT_PARSE_OPTION_NONE, "GMT-07:00", 2, UTZFMT_TIME_TYPE_UNKNOWN},
760
761 {"-2222", 0, "en_US", UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL,
762 UTZFMT_PARSE_OPTION_NONE, "GMT-22:22", 5, UTZFMT_TIME_TYPE_UNKNOWN},
763
764 {"-3333", 0, "en_US", UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL,
765 UTZFMT_PARSE_OPTION_NONE, "GMT-03:33", 4, UTZFMT_TIME_TYPE_UNKNOWN},
766
767 {"XXX+01:30YYY", 3, "en_US", UTZFMT_STYLE_LOCALIZED_GMT,
768 UTZFMT_PARSE_OPTION_NONE, "GMT+01:30", 9, UTZFMT_TIME_TYPE_UNKNOWN},
769
770 {"GMT0", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT,
771 UTZFMT_PARSE_OPTION_NONE, "Etc/GMT", 3, UTZFMT_TIME_TYPE_UNKNOWN},
772
773 {"EST", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT,
774 UTZFMT_PARSE_OPTION_NONE, "America/New_York", 3, UTZFMT_TIME_TYPE_STANDARD},
775
776 {"ESTx", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT,
777 UTZFMT_PARSE_OPTION_NONE, "America/New_York", 3, UTZFMT_TIME_TYPE_STANDARD},
778
779 {"EDTx", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT,
780 UTZFMT_PARSE_OPTION_NONE, "America/New_York", 3, UTZFMT_TIME_TYPE_DAYLIGHT},
781
782 {"EST", 0, "en_US", UTZFMT_STYLE_SPECIFIC_LONG,
783 UTZFMT_PARSE_OPTION_NONE, NULL, 0, UTZFMT_TIME_TYPE_UNKNOWN},
784
785 {"EST", 0, "en_US", UTZFMT_STYLE_SPECIFIC_LONG,
786 UTZFMT_PARSE_OPTION_ALL_STYLES, "America/New_York", 3, UTZFMT_TIME_TYPE_STANDARD},
787
788 {"EST", 0, "en_CA", UTZFMT_STYLE_SPECIFIC_SHORT,
789 UTZFMT_PARSE_OPTION_NONE, "America/Toronto", 3, UTZFMT_TIME_TYPE_STANDARD},
790
791 {"CST", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT,
792 UTZFMT_PARSE_OPTION_NONE, "America/Chicago", 3, UTZFMT_TIME_TYPE_STANDARD},
793
794 {"CST", 0, "en_GB", UTZFMT_STYLE_SPECIFIC_SHORT,
795 UTZFMT_PARSE_OPTION_NONE, NULL, 0, UTZFMT_TIME_TYPE_UNKNOWN},
796
797 {"CST", 0, "en_GB", UTZFMT_STYLE_SPECIFIC_SHORT,
798 UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS, "America/Chicago", 3, UTZFMT_TIME_TYPE_STANDARD},
799
800 {"--CST--", 2, "en_GB", UTZFMT_STYLE_SPECIFIC_SHORT,
801 UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS, "America/Chicago", 5, UTZFMT_TIME_TYPE_STANDARD},
802
803 {"CST", 0, "zh_CN", UTZFMT_STYLE_SPECIFIC_SHORT,
804 UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS, "Asia/Shanghai", 3, UTZFMT_TIME_TYPE_STANDARD},
805
806 {"AEST", 0, "en_AU", UTZFMT_STYLE_SPECIFIC_SHORT,
807 UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS, "Australia/Sydney", 4, UTZFMT_TIME_TYPE_STANDARD},
808
809 {"AST", 0, "ar_SA", UTZFMT_STYLE_SPECIFIC_SHORT,
810 UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS, "Asia/Riyadh", 3, UTZFMT_TIME_TYPE_STANDARD},
811
812 {"AQTST", 0, "en", UTZFMT_STYLE_SPECIFIC_LONG,
813 UTZFMT_PARSE_OPTION_NONE, NULL, 0, UTZFMT_TIME_TYPE_UNKNOWN},
814
815 {"AQTST", 0, "en", UTZFMT_STYLE_SPECIFIC_LONG,
816 UTZFMT_PARSE_OPTION_ALL_STYLES, NULL, 0, UTZFMT_TIME_TYPE_UNKNOWN},
817
818 {"AQTST", 0, "en", UTZFMT_STYLE_SPECIFIC_LONG,
819 UTZFMT_PARSE_OPTION_ALL_STYLES | UTZFMT_PARSE_OPTION_TZ_DATABASE_ABBREVIATIONS, "Asia/Aqtobe", 5, UTZFMT_TIME_TYPE_DAYLIGHT},
820
821 {NULL, 0, NULL, UTZFMT_STYLE_GENERIC_LOCATION,
822 UTZFMT_PARSE_OPTION_NONE, NULL, 0, UTZFMT_TIME_TYPE_UNKNOWN}
823 };
824
825 for (int32_t i = 0; DATA[i].text; i++) {
826 UErrorCode status = U_ZERO_ERROR;
827 LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(Locale(DATA[i].locale), status));
828 if (U_FAILURE(status)) {
829 dataerrln("Fail TimeZoneFormat::createInstance: %s", u_errorName(status));
830 continue;
831 }
832 UTimeZoneFormatTimeType ttype = UTZFMT_TIME_TYPE_UNKNOWN;
833 ParsePosition pos(DATA[i].inPos);
834 TimeZone* tz = tzfmt->parse(DATA[i].style, DATA[i].text, pos, DATA[i].parseOptions, &ttype);
835
836 UnicodeString errMsg;
837 if (tz) {
838 UnicodeString outID;
839 tz->getID(outID);
840 if (outID != UnicodeString(DATA[i].expected)) {
841 errMsg = (UnicodeString)"Time zone ID: " + outID + " - expected: " + DATA[i].expected;
842 } else if (pos.getIndex() != DATA[i].outPos) {
843 errMsg = (UnicodeString)"Parsed pos: " + pos.getIndex() + " - expected: " + DATA[i].outPos;
844 } else if (ttype != DATA[i].timeType) {
845 errMsg = (UnicodeString)"Time type: " + ttype + " - expected: " + DATA[i].timeType;
846 }
847 delete tz;
848 } else {
849 if (DATA[i].expected) {
850 errMsg = (UnicodeString)"Parse failure - expected: " + DATA[i].expected;
851 }
852 }
853 if (errMsg.length() > 0) {
854 errln((UnicodeString)"Fail: " + errMsg + " [text=" + DATA[i].text + ", pos=" + DATA[i].inPos + ", style=" + DATA[i].style + "]");
855 }
856 }
857 }
858
859 void
860 TimeZoneFormatTest::TestISOFormat(void) {
861 const int32_t OFFSET[] = {
862 0, // 0
863 999, // 0.999s
864 -59999, // -59.999s
865 60000, // 1m
866 -77777, // -1m 17.777s
867 1800000, // 30m
868 -3600000, // -1h
869 36000000, // 10h
870 -37800000, // -10h 30m
871 -37845000, // -10h 30m 45s
872 108000000, // 30h
873 };
874
875 const char* ISO_STR[][11] = {
876 // 0
877 {
878 "Z", "Z", "Z", "Z", "Z",
879 "+00", "+0000", "+00:00", "+0000", "+00:00",
880 "+0000"
881 },
882 // 999
883 {
884 "Z", "Z", "Z", "Z", "Z",
885 "+00", "+0000", "+00:00", "+0000", "+00:00",
886 "+0000"
887 },
888 // -59999
889 {
890 "Z", "Z", "Z", "-000059", "-00:00:59",
891 "+00", "+0000", "+00:00", "-000059", "-00:00:59",
892 "-000059"
893 },
894 // 60000
895 {
896 "+0001", "+0001", "+00:01", "+0001", "+00:01",
897 "+0001", "+0001", "+00:01", "+0001", "+00:01",
898 "+0001"
899 },
900 // -77777
901 {
902 "-0001", "-0001", "-00:01", "-000117", "-00:01:17",
903 "-0001", "-0001", "-00:01", "-000117", "-00:01:17",
904 "-000117"
905 },
906 // 1800000
907 {
908 "+0030", "+0030", "+00:30", "+0030", "+00:30",
909 "+0030", "+0030", "+00:30", "+0030", "+00:30",
910 "+0030"
911 },
912 // -3600000
913 {
914 "-01", "-0100", "-01:00", "-0100", "-01:00",
915 "-01", "-0100", "-01:00", "-0100", "-01:00",
916 "-0100"
917 },
918 // 36000000
919 {
920 "+10", "+1000", "+10:00", "+1000", "+10:00",
921 "+10", "+1000", "+10:00", "+1000", "+10:00",
922 "+1000"
923 },
924 // -37800000
925 {
926 "-1030", "-1030", "-10:30", "-1030", "-10:30",
927 "-1030", "-1030", "-10:30", "-1030", "-10:30",
928 "-1030"
929 },
930 // -37845000
931 {
932 "-1030", "-1030", "-10:30", "-103045", "-10:30:45",
933 "-1030", "-1030", "-10:30", "-103045", "-10:30:45",
934 "-103045"
935 },
936 // 108000000
937 {
938 0, 0, 0, 0, 0,
939 0, 0, 0, 0, 0,
940 0
941 }
942 };
943
944 const char* PATTERN[] = {
945 "X", "XX", "XXX", "XXXX", "XXXXX",
946 "x", "xx", "xxx", "xxxx", "xxxxx",
947 "Z", // equivalent to "xxxx"
948 0
949 };
950
951 const int32_t MIN_OFFSET_UNIT[] = {
952 60000, 60000, 60000, 1000, 1000,
953 60000, 60000, 60000, 1000, 1000,
954 1000,
955 };
956
957 // Formatting
958 UErrorCode status = U_ZERO_ERROR;
959 LocalPointer<SimpleDateFormat> sdf(new SimpleDateFormat(status), status);
960 if (U_FAILURE(status)) {
961 dataerrln("Fail new SimpleDateFormat: %s", u_errorName(status));
962 return;
963 }
964 UDate d = Calendar::getNow();
965
966 for (uint32_t i = 0; i < UPRV_LENGTHOF(OFFSET); i++) {
967 SimpleTimeZone* tz = new SimpleTimeZone(OFFSET[i], UnicodeString("Zone Offset:") + OFFSET[i] + "ms");
968 sdf->adoptTimeZone(tz);
969 for (int32_t j = 0; PATTERN[j] != 0; j++) {
970 sdf->applyPattern(UnicodeString(PATTERN[j]));
971 UnicodeString result;
972 sdf->format(d, result);
973
974 if (ISO_STR[i][j]) {
975 if (result != UnicodeString(ISO_STR[i][j])) {
976 errln((UnicodeString)"FAIL: pattern=" + PATTERN[j] + ", offset=" + OFFSET[i] + " -> "
977 + result + " (expected: " + ISO_STR[i][j] + ")");
978 }
979 } else {
980 // Offset out of range
981 // Note: for now, there is no way to propagate the error status through
982 // the SimpleDateFormat::format above.
983 if (result.length() > 0) {
984 errln((UnicodeString)"FAIL: Non-Empty result for pattern=" + PATTERN[j] + ", offset=" + OFFSET[i]
985 + " (expected: empty result)");
986 }
987 }
988 }
989 }
990
991 // Parsing
992 LocalPointer<Calendar> outcal(Calendar::createInstance(status));
993 if (U_FAILURE(status)) {
994 dataerrln("Fail new Calendar: %s", u_errorName(status));
995 return;
996 }
997 for (int32_t i = 0; ISO_STR[i][0] != NULL; i++) {
998 for (int32_t j = 0; PATTERN[j] != 0; j++) {
999 if (ISO_STR[i][j] == 0) {
1000 continue;
1001 }
1002 ParsePosition pos(0);
1003 SimpleTimeZone* bogusTZ = new SimpleTimeZone(-1, UnicodeString("Zone Offset: -1ms"));
1004 outcal->adoptTimeZone(bogusTZ);
1005 sdf->applyPattern(PATTERN[j]);
1006
1007 sdf->parse(UnicodeString(ISO_STR[i][j]), *(outcal.getAlias()), pos);
1008
1009 if (pos.getIndex() != (int32_t)uprv_strlen(ISO_STR[i][j])) {
1010 errln((UnicodeString)"FAIL: Failed to parse the entire input string: " + ISO_STR[i][j]);
1011 }
1012
1013 const TimeZone& outtz = outcal->getTimeZone();
1014 int32_t outOffset = outtz.getRawOffset();
1015 int32_t adjustedOffset = OFFSET[i] / MIN_OFFSET_UNIT[j] * MIN_OFFSET_UNIT[j];
1016 if (outOffset != adjustedOffset) {
1017 errln((UnicodeString)"FAIL: Incorrect offset:" + outOffset + "ms for input string: " + ISO_STR[i][j]
1018 + " (expected:" + adjustedOffset + "ms)");
1019 }
1020 }
1021 }
1022 }
1023
1024
1025 typedef struct {
1026 const char* locale;
1027 const char* tzid;
1028 UDate date;
1029 UTimeZoneFormatStyle style;
1030 const char* expected;
1031 UTimeZoneFormatTimeType timeType;
1032 } FormatTestData;
1033
1034 void
1035 TimeZoneFormatTest::TestFormat(void) {
1036 UDate dateJan = 1358208000000.0; // 2013-01-15T00:00:00Z
1037 UDate dateJul = 1373846400000.0; // 2013-07-15T00:00:00Z
1038
1039 const FormatTestData DATA[] = {
1040 {
1041 "en",
1042 "America/Los_Angeles",
1043 dateJan,
1044 UTZFMT_STYLE_GENERIC_LOCATION,
1045 "Los Angeles Time",
1046 UTZFMT_TIME_TYPE_UNKNOWN
1047 },
1048 {
1049 "en",
1050 "America/Los_Angeles",
1051 dateJan,
1052 UTZFMT_STYLE_GENERIC_LONG,
1053 "Pacific Time",
1054 UTZFMT_TIME_TYPE_UNKNOWN
1055 },
1056 {
1057 "en",
1058 "America/Los_Angeles",
1059 dateJan,
1060 UTZFMT_STYLE_SPECIFIC_LONG,
1061 "Pacific Standard Time",
1062 UTZFMT_TIME_TYPE_STANDARD
1063 },
1064 {
1065 "en",
1066 "America/Los_Angeles",
1067 dateJul,
1068 UTZFMT_STYLE_SPECIFIC_LONG,
1069 "Pacific Daylight Time",
1070 UTZFMT_TIME_TYPE_DAYLIGHT
1071 },
1072 {
1073 "ja",
1074 "America/Los_Angeles",
1075 dateJan,
1076 UTZFMT_STYLE_ZONE_ID,
1077 "America/Los_Angeles",
1078 UTZFMT_TIME_TYPE_UNKNOWN
1079 },
1080 {
1081 "fr",
1082 "America/Los_Angeles",
1083 dateJul,
1084 UTZFMT_STYLE_ZONE_ID_SHORT,
1085 "uslax",
1086 UTZFMT_TIME_TYPE_UNKNOWN
1087 },
1088 {
1089 "en",
1090 "America/Los_Angeles",
1091 dateJan,
1092 UTZFMT_STYLE_EXEMPLAR_LOCATION,
1093 "Los Angeles",
1094 UTZFMT_TIME_TYPE_UNKNOWN
1095 },
1096
1097 {
1098 "ja",
1099 "Asia/Tokyo",
1100 dateJan,
1101 UTZFMT_STYLE_GENERIC_LONG,
1102 "\\u65E5\\u672C\\u6A19\\u6E96\\u6642",
1103 UTZFMT_TIME_TYPE_UNKNOWN
1104 },
1105
1106 {0, 0, 0.0, UTZFMT_STYLE_GENERIC_LOCATION, 0, UTZFMT_TIME_TYPE_UNKNOWN}
1107 };
1108
1109 for (int32_t i = 0; DATA[i].locale; i++) {
1110 UErrorCode status = U_ZERO_ERROR;
1111 LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(Locale(DATA[i].locale), status));
1112 if (U_FAILURE(status)) {
1113 dataerrln("Fail TimeZoneFormat::createInstance: %s", u_errorName(status));
1114 continue;
1115 }
1116
1117 LocalPointer<TimeZone> tz(TimeZone::createTimeZone(DATA[i].tzid));
1118 UnicodeString out;
1119 UTimeZoneFormatTimeType timeType;
1120
1121 tzfmt->format(DATA[i].style, *(tz.getAlias()), DATA[i].date, out, &timeType);
1122 UnicodeString expected(DATA[i].expected, -1, US_INV);
1123 expected = expected.unescape();
1124
1125 assertEquals(UnicodeString("Format result for ") + DATA[i].tzid + " (Test Case " + i + ")", expected, out);
1126 if (DATA[i].timeType != timeType) {
1127 dataerrln(UnicodeString("Formatted time zone type (Test Case ") + i + "), returned="
1128 + timeType + ", expected=" + DATA[i].timeType);
1129 }
1130 }
1131 }
1132
1133 void
1134 TimeZoneFormatTest::TestFormatTZDBNames(void) {
1135 UDate dateJan = 1358208000000.0; // 2013-01-15T00:00:00Z
1136 UDate dateJul = 1373846400000.0; // 2013-07-15T00:00:00Z
1137
1138 const FormatTestData DATA[] = {
1139 {
1140 "en",
1141 "America/Chicago",
1142 dateJan,
1143 UTZFMT_STYLE_SPECIFIC_SHORT,
1144 "CST",
1145 UTZFMT_TIME_TYPE_STANDARD
1146 },
1147 {
1148 "en",
1149 "Asia/Shanghai",
1150 dateJan,
1151 UTZFMT_STYLE_SPECIFIC_SHORT,
1152 "CST",
1153 UTZFMT_TIME_TYPE_STANDARD
1154 },
1155 {
1156 "zh_Hans",
1157 "Asia/Shanghai",
1158 dateJan,
1159 UTZFMT_STYLE_SPECIFIC_SHORT,
1160 "CST",
1161 UTZFMT_TIME_TYPE_STANDARD
1162 },
1163 {
1164 "en",
1165 "America/Los_Angeles",
1166 dateJul,
1167 UTZFMT_STYLE_SPECIFIC_LONG,
1168 "GMT-07:00", // No long display names
1169 UTZFMT_TIME_TYPE_DAYLIGHT
1170 },
1171 {
1172 "ja",
1173 "America/Los_Angeles",
1174 dateJul,
1175 UTZFMT_STYLE_SPECIFIC_SHORT,
1176 "PDT",
1177 UTZFMT_TIME_TYPE_DAYLIGHT
1178 },
1179 {
1180 "en",
1181 "Australia/Sydney",
1182 dateJan,
1183 UTZFMT_STYLE_SPECIFIC_SHORT,
1184 "AEDT",
1185 UTZFMT_TIME_TYPE_DAYLIGHT
1186 },
1187 {
1188 "en",
1189 "Australia/Sydney",
1190 dateJul,
1191 UTZFMT_STYLE_SPECIFIC_SHORT,
1192 "AEST",
1193 UTZFMT_TIME_TYPE_STANDARD
1194 },
1195
1196 {0, 0, 0.0, UTZFMT_STYLE_GENERIC_LOCATION, 0, UTZFMT_TIME_TYPE_UNKNOWN}
1197 };
1198
1199 for (int32_t i = 0; DATA[i].locale; i++) {
1200 UErrorCode status = U_ZERO_ERROR;
1201 Locale loc(DATA[i].locale);
1202 LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(loc, status));
1203 if (U_FAILURE(status)) {
1204 dataerrln("Fail TimeZoneFormat::createInstance: %s", u_errorName(status));
1205 continue;
1206 }
1207 TimeZoneNames *tzdbNames = TimeZoneNames::createTZDBInstance(loc, status);
1208 if (U_FAILURE(status)) {
1209 dataerrln("Fail TimeZoneNames::createTZDBInstance: %s", u_errorName(status));
1210 continue;
1211 }
1212 tzfmt->adoptTimeZoneNames(tzdbNames);
1213
1214 LocalPointer<TimeZone> tz(TimeZone::createTimeZone(DATA[i].tzid));
1215 UnicodeString out;
1216 UTimeZoneFormatTimeType timeType;
1217
1218 tzfmt->format(DATA[i].style, *(tz.getAlias()), DATA[i].date, out, &timeType);
1219 UnicodeString expected(DATA[i].expected, -1, US_INV);
1220 expected = expected.unescape();
1221
1222 assertEquals(UnicodeString("Format result for ") + DATA[i].tzid + " (Test Case " + i + ")", expected, out);
1223 if (DATA[i].timeType != timeType) {
1224 dataerrln(UnicodeString("Formatted time zone type (Test Case ") + i + "), returned="
1225 + timeType + ", expected=" + DATA[i].timeType);
1226 }
1227 }
1228 }
1229
1230 void
1231 TimeZoneFormatTest::TestFormatCustomZone(void) {
1232 struct {
1233 const char* id;
1234 int32_t offset;
1235 const char* expected;
1236 } TESTDATA[] = {
1237 { "abc", 3600000, "GMT+01:00" }, // unknown ID
1238 { "$abc", -3600000, "GMT-01:00" }, // unknown, with ASCII variant char '$'
1239 { "\\u00c1\\u00df\\u00c7", 5400000, "GMT+01:30"}, // unknown, with non-ASCII chars
1240 { 0, 0, 0 }
1241 };
1242
1243 UDate now = Calendar::getNow();
1244
1245 for (int32_t i = 0; ; i++) {
1246 const char *id = TESTDATA[i].id;
1247 if (id == 0) {
1248 break;
1249 }
1250 UnicodeString tzid = UnicodeString(id, -1, US_INV).unescape();
1251 SimpleTimeZone tz(TESTDATA[i].offset, tzid);
1252
1253 UErrorCode status = U_ZERO_ERROR;
1254 LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(Locale("en"), status));
1255 if (tzfmt.isNull()) {
1256 dataerrln("FAIL: TimeZoneFormat::createInstance failed for en");
1257 return;
1258 }
1259 UnicodeString tzstr;
1260 UnicodeString expected = UnicodeString(TESTDATA[i].expected, -1, US_INV).unescape();
1261
1262 tzfmt->format(UTZFMT_STYLE_SPECIFIC_LONG, tz, now, tzstr, NULL);
1263 assertEquals(UnicodeString("Format result for ") + tzid, expected, tzstr);
1264 }
1265 }
1266
1267 void
1268 TimeZoneFormatTest::TestFormatTZDBNamesAllZoneCoverage(void) {
1269 UErrorCode status = U_ZERO_ERROR;
1270 LocalPointer<StringEnumeration> tzids(TimeZone::createEnumeration());
1271 if (tzids.getAlias() == nullptr) {
1272 dataerrln("%s %d tzids is null", __FILE__, __LINE__);
1273 return;
1274 }
1275 const UnicodeString *tzid;
1276 LocalPointer<TimeZoneNames> tzdbNames(TimeZoneNames::createTZDBInstance(Locale("en"), status));
1277 UDate now = Calendar::getNow();
1278 UnicodeString mzId;
1279 UnicodeString name;
1280 while ((tzid = tzids->snext(status))) {
1281 logln("Zone: " + *tzid);
1282 LocalPointer<TimeZone> tz(TimeZone::createTimeZone(*tzid));
1283 tzdbNames->getMetaZoneID(*tzid, now, mzId);
1284 if (mzId.isBogus()) {
1285 logln((UnicodeString)"Meta zone: <not available>");
1286 } else {
1287 logln((UnicodeString)"Meta zone: " + mzId);
1288 }
1289
1290 // mzID could be bogus here
1291 tzdbNames->getMetaZoneDisplayName(mzId, UTZNM_SHORT_STANDARD, name);
1292 // name could be bogus here
1293 if (name.isBogus()) {
1294 logln((UnicodeString)"Meta zone short standard name: <not available>");
1295 }
1296 else {
1297 logln((UnicodeString)"Meta zone short standard name: " + name);
1298 }
1299
1300 tzdbNames->getMetaZoneDisplayName(mzId, UTZNM_SHORT_DAYLIGHT, name);
1301 // name could be bogus here
1302 if (name.isBogus()) {
1303 logln((UnicodeString)"Meta zone short daylight name: <not available>");
1304 }
1305 else {
1306 logln((UnicodeString)"Meta zone short daylight name: " + name);
1307 }
1308 }
1309 }
1310
1311 #endif /* #if !UCONFIG_NO_FORMATTING */