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