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