]> git.saurik.com Git - apple/icu.git/blob - icuSources/test/intltest/tzfmttst.cpp
ICU-400.37.tar.gz
[apple/icu.git] / icuSources / test / intltest / tzfmttst.cpp
1 /*
2 *******************************************************************************
3 * Copyright (C) 2007-2008, International Business Machines Corporation and *
4 * others. All Rights Reserved. *
5 *******************************************************************************
6 */
7 #include "unicode/utypes.h"
8
9 #if !UCONFIG_NO_FORMATTING
10
11 #include "tzfmttst.h"
12
13 #include "unicode/timezone.h"
14 #include "unicode/simpletz.h"
15 #include "unicode/calendar.h"
16 #include "unicode/strenum.h"
17 #include "unicode/smpdtfmt.h"
18 #include "unicode/uchar.h"
19 #include "unicode/basictz.h"
20 #include "cstring.h"
21
22 #define DEBUG_ALL 0
23
24 static const char* PATTERNS[] = {"z", "zzzz", "Z", "ZZZZ", "v", "vvvv", "V", "VVVV"};
25 static const int NUM_PATTERNS = sizeof(PATTERNS)/sizeof(const char*);
26
27 void
28 TimeZoneFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ )
29 {
30 if (exec) {
31 logln("TestSuite TimeZoneFormatTest");
32 }
33 switch (index) {
34 TESTCASE(0, TestTimeZoneRoundTrip);
35 TESTCASE(1, TestTimeRoundTrip);
36 default: name = ""; break;
37 }
38 }
39
40 void
41 TimeZoneFormatTest::TestTimeZoneRoundTrip(void) {
42 UErrorCode status = U_ZERO_ERROR;
43
44 SimpleTimeZone unknownZone(-31415, (UnicodeString)"Etc/Unknown");
45 int32_t badDstOffset = -1234;
46 int32_t badZoneOffset = -2345;
47
48 int32_t testDateData[][3] = {
49 {2007, 1, 15},
50 {2007, 6, 15},
51 {1990, 1, 15},
52 {1990, 6, 15},
53 {1960, 1, 15},
54 {1960, 6, 15},
55 };
56
57 Calendar *cal = Calendar::createInstance(TimeZone::createTimeZone((UnicodeString)"UTC"), status);
58 if (U_FAILURE(status)) {
59 errln("Calendar::createInstance failed");
60 return;
61 }
62
63 // Set up rule equivalency test range
64 UDate low, high;
65 cal->set(1900, UCAL_JANUARY, 1);
66 low = cal->getTime(status);
67 cal->set(2040, UCAL_JANUARY, 1);
68 high = cal->getTime(status);
69 if (U_FAILURE(status)) {
70 errln("getTime failed");
71 return;
72 }
73
74 // Set up test dates
75 UDate DATES[(sizeof(testDateData)/sizeof(int32_t))/3];
76 const int32_t nDates = (sizeof(testDateData)/sizeof(int32_t))/3;
77 cal->clear();
78 for (int32_t i = 0; i < nDates; i++) {
79 cal->set(testDateData[i][0], testDateData[i][1], testDateData[i][2]);
80 DATES[i] = cal->getTime(status);
81 if (U_FAILURE(status)) {
82 errln("getTime failed");
83 return;
84 }
85 }
86
87 // Set up test locales
88 const Locale locales1[] = {
89 Locale("en_US")
90 };
91 const Locale locales2[] = {
92 Locale("en_US"),
93 Locale("en"),
94 Locale("en_CA"),
95 Locale("fr"),
96 Locale("zh_Hant")
97 };
98
99 const Locale *LOCALES;
100 int32_t nLocales;
101 if (DEBUG_ALL) {
102 LOCALES = Locale::getAvailableLocales(nLocales);
103 } else if (quick) {
104 LOCALES = locales1;
105 nLocales = sizeof(locales1)/sizeof(Locale);
106 } else {
107 LOCALES = locales2;
108 nLocales = sizeof(locales2)/sizeof(Locale);
109 }
110
111 StringEnumeration *tzids = TimeZone::createEnumeration();
112 if (U_FAILURE(status)) {
113 errln("tzids->count failed");
114 return;
115 }
116
117 int32_t inRaw, inDst;
118 int32_t outRaw, outDst;
119
120 // Run the roundtrip test
121 for (int32_t locidx = 0; locidx < nLocales; locidx++) {
122 for (int32_t patidx = 0; patidx < NUM_PATTERNS; patidx++) {
123
124 //DEBUG static const char* PATTERNS[] = {"z", "zzzz", "Z", "ZZZZ", "v", "vvvv", "V", "VVVV"};
125 //if (patidx != 1) continue;
126
127 SimpleDateFormat *sdf = new SimpleDateFormat((UnicodeString)PATTERNS[patidx], LOCALES[locidx], status);
128 if (U_FAILURE(status)) {
129 errln((UnicodeString)"new SimpleDateFormat failed for pattern " +
130 PATTERNS[patidx] + " for locale " + LOCALES[locidx].getName());
131 status = U_ZERO_ERROR;
132 continue;
133 }
134
135 tzids->reset(status);
136 const UnicodeString *tzid;
137 while ((tzid = tzids->snext(status))) {
138 TimeZone *tz = TimeZone::createTimeZone(*tzid);
139
140 for (int32_t datidx = 0; datidx < nDates; datidx++) {
141 UnicodeString tzstr;
142 FieldPosition fpos(0);
143 // Format
144 sdf->setTimeZone(*tz);
145 sdf->format(DATES[datidx], tzstr, fpos);
146
147 // Before parse, set unknown zone to SimpleDateFormat instance
148 // just for making sure that it does not depends on the time zone
149 // originally set.
150 sdf->setTimeZone(unknownZone);
151
152 // Parse
153 ParsePosition pos(0);
154 Calendar *outcal = Calendar::createInstance(unknownZone, status);
155 if (U_FAILURE(status)) {
156 errln("Failed to create an instance of calendar for receiving parse result.");
157 status = U_ZERO_ERROR;
158 continue;
159 }
160 outcal->set(UCAL_DST_OFFSET, badDstOffset);
161 outcal->set(UCAL_ZONE_OFFSET, badZoneOffset);
162
163 sdf->parse(tzstr, *outcal, pos);
164
165 // Check the result
166 const TimeZone &outtz = outcal->getTimeZone();
167 UnicodeString outtzid;
168 outtz.getID(outtzid);
169
170 tz->getOffset(DATES[datidx], false, inRaw, inDst, status);
171 if (U_FAILURE(status)) {
172 errln((UnicodeString)"Failed to get offsets from time zone" + *tzid);
173 status = U_ZERO_ERROR;
174 }
175 outtz.getOffset(DATES[datidx], false, outRaw, outDst, status);
176 if (U_FAILURE(status)) {
177 errln((UnicodeString)"Failed to get offsets from time zone" + outtzid);
178 status = U_ZERO_ERROR;
179 }
180
181 // Check if localized GMT format or RFC format is used.
182 int32_t numDigits = 0;
183 for (int n = 0; n < tzstr.length(); n++) {
184 if (u_isdigit(tzstr.charAt(n))) {
185 numDigits++;
186 }
187 }
188 if (numDigits >= 3) {
189 // Localized GMT or RFC: total offset (raw + dst) must be preserved.
190 int32_t inOffset = inRaw + inDst;
191 int32_t outOffset = outRaw + outDst;
192 if (inOffset != outOffset) {
193 errln((UnicodeString)"Offset round trip failed; tz=" + *tzid
194 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
195 + ", time=" + DATES[datidx] + ", str=" + tzstr
196 + ", inOffset=" + inOffset + ", outOffset=" + outOffset);
197 }
198 } else if (uprv_strcmp(PATTERNS[patidx], "z") == 0 || uprv_strcmp(PATTERNS[patidx], "zzzz") == 0
199 || uprv_strcmp(PATTERNS[patidx], "v") == 0 || uprv_strcmp(PATTERNS[patidx], "vvvv") == 0
200 || uprv_strcmp(PATTERNS[patidx], "V") == 0) {
201 // Specific or generic: raw offset must be preserved.
202 if (inRaw != outRaw) {
203 errln((UnicodeString)"Raw offset round trip failed; tz=" + *tzid
204 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
205 + ", time=" + DATES[datidx] + ", str=" + tzstr
206 + ", inRawOffset=" + inRaw + ", outRawOffset=" + outRaw);
207 }
208 } else { // "VVVV"
209 // Location: time zone rule must be preserved.
210 UnicodeString canonical;
211 TimeZone::getCanonicalID(*tzid, canonical, status);
212 if (U_FAILURE(status)) {
213 // Uknown ID - we should not get here
214 errln((UnicodeString)"Unknown ID " + *tzid);
215 status = U_ZERO_ERROR;
216 } else if (outtzid != canonical) {
217 // Canonical ID did not match - check the rules
218 if (!((BasicTimeZone*)&outtz)->hasEquivalentTransitions((BasicTimeZone&)*tz, low, high, TRUE, status)) {
219 errln("Canonical round trip failed; tz=" + *tzid
220 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
221 + ", time=" + DATES[datidx] + ", str=" + tzstr
222 + ", outtz=" + outtzid);
223 }
224 if (U_FAILURE(status)) {
225 errln("hasEquivalentTransitions failed");
226 status = U_ZERO_ERROR;
227 }
228 }
229 }
230 delete outcal;
231 }
232 delete tz;
233 }
234 delete sdf;
235 }
236 }
237 delete cal;
238 delete tzids;
239 }
240
241 void
242 TimeZoneFormatTest::TestTimeRoundTrip(void) {
243 UErrorCode status = U_ZERO_ERROR;
244
245 Calendar *cal = Calendar::createInstance(TimeZone::createTimeZone((UnicodeString)"UTC"), status);
246 if (U_FAILURE(status)) {
247 errln("Calendar::createInstance failed");
248 return;
249 }
250
251 UDate START_TIME, END_TIME;
252
253 if (DEBUG_ALL) {
254 cal->set(1900, UCAL_JANUARY, 1);
255 } else {
256 cal->set(1965, UCAL_JANUARY, 1);
257 }
258 START_TIME = cal->getTime(status);
259
260 cal->set(2015, UCAL_JANUARY, 1);
261 END_TIME = cal->getTime(status);
262 if (U_FAILURE(status)) {
263 errln("getTime failed");
264 return;
265 }
266
267 // Whether each pattern is ambiguous at DST->STD local time overlap
268 UBool AMBIGUOUS_DST_DECESSION[] = {FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, FALSE, TRUE};
269 // Whether each pattern is ambiguous at STD->STD/DST->DST local time overlap
270 UBool AMBIGUOUS_NEGATIVE_SHIFT[] = {TRUE, TRUE, FALSE, FALSE, TRUE, TRUE, TRUE, TRUE};
271
272 // Workaround for #6338
273 //UnicodeString BASEPATTERN("yyyy-MM-dd'T'HH:mm:ss.SSS");
274 UnicodeString BASEPATTERN("yyyy.MM.dd HH:mm:ss.SSS");
275
276 // timer for performance analysis
277 UDate timer;
278 UDate times[NUM_PATTERNS];
279 for (int32_t i = 0; i < NUM_PATTERNS; i++) {
280 times[i] = 0;
281 }
282
283 UBool REALLY_VERBOSE = FALSE;
284
285 // Set up test locales
286 const Locale locales1[] = {
287 Locale("en_US")
288 };
289 const Locale locales2[] = {
290 Locale("en_US"),
291 Locale("en"),
292 Locale("de_DE"),
293 Locale("es_ES"),
294 Locale("fr_FR"),
295 Locale("it_IT"),
296 Locale("ja_JP"),
297 Locale("ko_KR"),
298 Locale("pt_BR"),
299 Locale("zh_Hans_CN"),
300 Locale("zh_Hant_TW")
301 };
302
303 const Locale *LOCALES;
304 int32_t nLocales;
305 if (DEBUG_ALL) {
306 LOCALES = Locale::getAvailableLocales(nLocales);
307 } else if (quick) {
308 LOCALES = locales1;
309 nLocales = sizeof(locales1)/sizeof(Locale);
310 } else {
311 LOCALES = locales2;
312 nLocales = sizeof(locales2)/sizeof(Locale);
313 }
314
315 StringEnumeration *tzids = TimeZone::createEnumeration();
316 if (U_FAILURE(status)) {
317 errln("tzids->count failed");
318 return;
319 }
320
321 int32_t testCounts = 0;
322 UDate testTimes[4];
323 UBool expectedRoundTrip[4];
324 int32_t testLen = 0;
325
326 for (int32_t locidx = 0; locidx < nLocales; locidx++) {
327 logln((UnicodeString)"Locale: " + LOCALES[locidx].getName());
328
329 for (int32_t patidx = 0; patidx < NUM_PATTERNS; patidx++) {
330 logln((UnicodeString)" pattern: " + PATTERNS[patidx]);
331
332 //DEBUG static const char* PATTERNS[] = {"z", "zzzz", "Z", "ZZZZ", "v", "vvvv", "V", "VVVV"};
333 //if (patidx != 1) continue;
334
335 UnicodeString pattern(BASEPATTERN);
336 pattern.append(" ").append(PATTERNS[patidx]);
337
338 SimpleDateFormat *sdf = new SimpleDateFormat(pattern, LOCALES[locidx], status);
339 if (U_FAILURE(status)) {
340 errln((UnicodeString)"new SimpleDateFormat failed for pattern " +
341 pattern + " for locale " + LOCALES[locidx].getName());
342 status = U_ZERO_ERROR;
343 continue;
344 }
345
346 tzids->reset(status);
347 const UnicodeString *tzid;
348
349 timer = Calendar::getNow();
350
351 while ((tzid = tzids->snext(status))) {
352 UnicodeString canonical;
353 TimeZone::getCanonicalID(*tzid, canonical, status);
354 if (U_FAILURE(status)) {
355 // Unknown ID - we should not get here
356 status = U_ZERO_ERROR;
357 continue;
358 }
359 if (*tzid != canonical) {
360 // Skip aliases
361 continue;
362 }
363 BasicTimeZone *tz = (BasicTimeZone*)TimeZone::createTimeZone(*tzid);
364 sdf->setTimeZone(*tz);
365
366 UDate t = START_TIME;
367 TimeZoneTransition tzt;
368 UBool tztAvail = FALSE;
369 UBool middle = TRUE;
370
371 while (t < END_TIME) {
372 if (!tztAvail) {
373 testTimes[0] = t;
374 expectedRoundTrip[0] = TRUE;
375 testLen = 1;
376 } else {
377 int32_t fromOffset = tzt.getFrom()->getRawOffset() + tzt.getFrom()->getDSTSavings();
378 int32_t toOffset = tzt.getTo()->getRawOffset() + tzt.getTo()->getDSTSavings();
379 int32_t delta = toOffset - fromOffset;
380 if (delta < 0) {
381 UBool isDstDecession = tzt.getFrom()->getDSTSavings() > 0 && tzt.getTo()->getDSTSavings() == 0;
382 testTimes[0] = t + delta - 1;
383 expectedRoundTrip[0] = TRUE;
384 testTimes[1] = t + delta;
385 expectedRoundTrip[1] = isDstDecession ?
386 !AMBIGUOUS_DST_DECESSION[patidx] : !AMBIGUOUS_NEGATIVE_SHIFT[patidx];
387 testTimes[2] = t - 1;
388 expectedRoundTrip[2] = isDstDecession ?
389 !AMBIGUOUS_DST_DECESSION[patidx] : !AMBIGUOUS_NEGATIVE_SHIFT[patidx];
390 testTimes[3] = t;
391 expectedRoundTrip[3] = TRUE;
392 testLen = 4;
393 } else {
394 testTimes[0] = t - 1;
395 expectedRoundTrip[0] = TRUE;
396 testTimes[1] = t;
397 expectedRoundTrip[1] = TRUE;
398 testLen = 2;
399 }
400 }
401 for (int32_t testidx = 0; testidx < testLen; testidx++) {
402 if (quick) {
403 // reduce regular test time
404 if (!expectedRoundTrip[testidx]) {
405 continue;
406 }
407 }
408 testCounts++;
409
410 UnicodeString text;
411 FieldPosition fpos(0);
412 sdf->format(testTimes[testidx], text, fpos);
413
414 UDate parsedDate = sdf->parse(text, status);
415 if (U_FAILURE(status)) {
416 errln((UnicodeString)"Failed to parse " + text);
417 status = U_ZERO_ERROR;
418 continue;
419 }
420 if (parsedDate != testTimes[testidx]) {
421 UnicodeString msg = (UnicodeString)"Time round trip failed for "
422 + "tzid=" + *tzid
423 + ", locale=" + LOCALES[locidx].getName()
424 + ", pattern=" + PATTERNS[patidx]
425 + ", text=" + text
426 + ", time=" + testTimes[testidx]
427 + ", restime=" + parsedDate
428 + ", diff=" + (parsedDate - testTimes[testidx]);
429 if (expectedRoundTrip[testidx]) {
430 errln((UnicodeString)"FAIL: " + msg);
431 } else if (REALLY_VERBOSE) {
432 logln(msg);
433 }
434 }
435 }
436 tztAvail = tz->getNextTransition(t, FALSE, tzt);
437 if (!tztAvail) {
438 break;
439 }
440 if (middle) {
441 // Test the date in the middle of two transitions.
442 t += (int64_t)((tzt.getTime() - t)/2);
443 middle = FALSE;
444 tztAvail = FALSE;
445 } else {
446 t = tzt.getTime();
447 }
448 }
449 delete tz;
450 }
451 times[patidx] += (Calendar::getNow() - timer);
452 delete sdf;
453 }
454 }
455 UDate total = 0;
456 logln("### Elapsed time by patterns ###");
457 for (int32_t i = 0; i < NUM_PATTERNS; i++) {
458 logln(UnicodeString("") + times[i] + "ms (" + PATTERNS[i] + ")");
459 total += times[i];
460 }
461 logln((UnicodeString)"Total: " + total + "ms");
462 logln((UnicodeString)"Iteration: " + testCounts);
463
464 delete cal;
465 delete tzids;
466 }
467
468 #endif /* #if !UCONFIG_NO_FORMATTING */