1 /*
2 *******************************************************************************
3 * Copyright (C) 2007-2009, 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 static const char* PATTERNS[] = {"z", "zzzz", "Z", "ZZZZ", "v", "vvvv", "V", "VVVV"};
23 static const int NUM_PATTERNS = sizeof(PATTERNS)/sizeof(const char*);
24
25 void
runIndexedTest(int32_t index,UBool exec,const char * & name,char *)26 TimeZoneFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ )
27 {
28 if (exec) {
29 logln("TestSuite TimeZoneFormatTest");
30 }
31 switch (index) {
32 TESTCASE(0, TestTimeZoneRoundTrip);
33 TESTCASE(1, TestTimeRoundTrip);
34 default: name = ""; break;
35 }
36 }
37
38 void
TestTimeZoneRoundTrip(void)39 TimeZoneFormatTest::TestTimeZoneRoundTrip(void) {
40 UErrorCode status = U_ZERO_ERROR;
41
42 SimpleTimeZone unknownZone(-31415, (UnicodeString)"Etc/Unknown");
43 int32_t badDstOffset = -1234;
44 int32_t badZoneOffset = -2345;
45
46 int32_t testDateData[][3] = {
47 {2007, 1, 15},
48 {2007, 6, 15},
49 {1990, 1, 15},
50 {1990, 6, 15},
51 {1960, 1, 15},
52 {1960, 6, 15},
53 };
54
55 Calendar *cal = Calendar::createInstance(TimeZone::createTimeZone((UnicodeString)"UTC"), status);
56 if (U_FAILURE(status)) {
57 errln("Calendar::createInstance failed");
58 return;
59 }
60
61 // Set up rule equivalency test range
62 UDate low, high;
63 cal->set(1900, UCAL_JANUARY, 1);
64 low = cal->getTime(status);
65 cal->set(2040, UCAL_JANUARY, 1);
66 high = cal->getTime(status);
67 if (U_FAILURE(status)) {
68 errln("getTime failed");
69 return;
70 }
71
72 // Set up test dates
73 UDate DATES[(sizeof(testDateData)/sizeof(int32_t))/3];
74 const int32_t nDates = (sizeof(testDateData)/sizeof(int32_t))/3;
75 cal->clear();
76 for (int32_t i = 0; i < nDates; i++) {
77 cal->set(testDateData[i][0], testDateData[i][1], testDateData[i][2]);
78 DATES[i] = cal->getTime(status);
79 if (U_FAILURE(status)) {
80 errln("getTime failed");
81 return;
82 }
83 }
84
85 // Set up test locales
86 const Locale testLocales[] = {
87 Locale("en"),
88 Locale("en_CA"),
89 Locale("fr"),
90 Locale("zh_Hant")
91 };
92
93 const Locale *LOCALES;
94 int32_t nLocales;
95
96 if (quick) {
97 LOCALES = testLocales;
98 nLocales = sizeof(testLocales)/sizeof(Locale);
99 } else {
100 LOCALES = Locale::getAvailableLocales(nLocales);
101 }
102
103 StringEnumeration *tzids = TimeZone::createEnumeration();
104 if (U_FAILURE(status)) {
105 errln("tzids->count failed");
106 return;
107 }
108
109 int32_t inRaw, inDst;
110 int32_t outRaw, outDst;
111
112 // Run the roundtrip test
113 for (int32_t locidx = 0; locidx < nLocales; locidx++) {
114 for (int32_t patidx = 0; patidx < NUM_PATTERNS; patidx++) {
115
116 SimpleDateFormat *sdf = new SimpleDateFormat((UnicodeString)PATTERNS[patidx], LOCALES[locidx], status);
117 if (U_FAILURE(status)) {
118 errcheckln(status, (UnicodeString)"new SimpleDateFormat failed for pattern " +
119 PATTERNS[patidx] + " for locale " + LOCALES[locidx].getName() + " - " + u_errorName(status));
120 status = U_ZERO_ERROR;
121 continue;
122 }
123
124 tzids->reset(status);
125 const UnicodeString *tzid;
126 while ((tzid = tzids->snext(status))) {
127 TimeZone *tz = TimeZone::createTimeZone(*tzid);
128
129 for (int32_t datidx = 0; datidx < nDates; datidx++) {
130 UnicodeString tzstr;
131 FieldPosition fpos(0);
132 // Format
133 sdf->setTimeZone(*tz);
134 sdf->format(DATES[datidx], tzstr, fpos);
135
136 // Before parse, set unknown zone to SimpleDateFormat instance
137 // just for making sure that it does not depends on the time zone
138 // originally set.
139 sdf->setTimeZone(unknownZone);
140
141 // Parse
142 ParsePosition pos(0);
143 Calendar *outcal = Calendar::createInstance(unknownZone, status);
144 if (U_FAILURE(status)) {
145 errln("Failed to create an instance of calendar for receiving parse result.");
146 status = U_ZERO_ERROR;
147 continue;
148 }
149 outcal->set(UCAL_DST_OFFSET, badDstOffset);
150 outcal->set(UCAL_ZONE_OFFSET, badZoneOffset);
151
152 sdf->parse(tzstr, *outcal, pos);
153
154 // Check the result
155 const TimeZone &outtz = outcal->getTimeZone();
156 UnicodeString outtzid;
157 outtz.getID(outtzid);
158
159 tz->getOffset(DATES[datidx], false, inRaw, inDst, status);
160 if (U_FAILURE(status)) {
161 errln((UnicodeString)"Failed to get offsets from time zone" + *tzid);
162 status = U_ZERO_ERROR;
163 }
164 outtz.getOffset(DATES[datidx], false, outRaw, outDst, status);
165 if (U_FAILURE(status)) {
166 errln((UnicodeString)"Failed to get offsets from time zone" + outtzid);
167 status = U_ZERO_ERROR;
168 }
169
170 if (uprv_strcmp(PATTERNS[patidx], "VVVV") == 0) {
171 // Location: time zone rule must be preserved except
172 // zones not actually associated with a specific location.
173 // Time zones in this category do not have "/" in its ID.
174 UnicodeString canonical;
175 TimeZone::getCanonicalID(*tzid, canonical, status);
176 if (U_FAILURE(status)) {
177 // Uknown ID - we should not get here
178 errln((UnicodeString)"Unknown ID " + *tzid);
179 status = U_ZERO_ERROR;
180 } else if (outtzid != canonical) {
181 // Canonical ID did not match - check the rules
182 if (!((BasicTimeZone*)&outtz)->hasEquivalentTransitions((BasicTimeZone&)*tz, low, high, TRUE, status)) {
183 if (canonical.indexOf((UChar)0x27 /*'/'*/) == -1) {
184 // Exceptional cases, such as CET, EET, MET and WET
185 logln("Canonical round trip failed (as expected); tz=" + *tzid
186 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
187 + ", time=" + DATES[datidx] + ", str=" + tzstr
188 + ", outtz=" + outtzid);
189 } else {
190 errln("Canonical round trip failed; tz=" + *tzid
191 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
192 + ", time=" + DATES[datidx] + ", str=" + tzstr
193 + ", outtz=" + outtzid);
194 }
195 if (U_FAILURE(status)) {
196 errln("hasEquivalentTransitions failed");
197 status = U_ZERO_ERROR;
198 }
199 }
200 }
201
202 } else {
203 // Check if localized GMT format or RFC format is used.
204 int32_t numDigits = 0;
205 for (int n = 0; n < tzstr.length(); n++) {
206 if (u_isdigit(tzstr.charAt(n))) {
207 numDigits++;
208 }
209 }
210 if (numDigits >= 3) {
211 // Localized GMT or RFC: total offset (raw + dst) must be preserved.
212 int32_t inOffset = inRaw + inDst;
213 int32_t outOffset = outRaw + outDst;
214 if (inOffset != outOffset) {
215 errln((UnicodeString)"Offset round trip failed; tz=" + *tzid
216 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
217 + ", time=" + DATES[datidx] + ", str=" + tzstr
218 + ", inOffset=" + inOffset + ", outOffset=" + outOffset);
219 }
220 } else {
221 // Specific or generic: raw offset must be preserved.
222 if (inRaw != outRaw) {
223 errln((UnicodeString)"Raw offset round trip failed; tz=" + *tzid
224 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
225 + ", time=" + DATES[datidx] + ", str=" + tzstr
226 + ", inRawOffset=" + inRaw + ", outRawOffset=" + outRaw);
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
TestTimeRoundTrip(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 const char* testAllProp = getProperty("TimeZoneRoundTripAll");
253 UBool bTestAll = (testAllProp && uprv_strcmp(testAllProp, "true") == 0);
254
255 if (bTestAll || !quick) {
256 cal->set(1900, UCAL_JANUARY, 1);
257 } else {
258 cal->set(1990, UCAL_JANUARY, 1);
259 }
260 START_TIME = cal->getTime(status);
261
262 cal->set(2015, UCAL_JANUARY, 1);
263 END_TIME = cal->getTime(status);
264 if (U_FAILURE(status)) {
265 errln("getTime failed");
266 return;
267 }
268
269 // Whether each pattern is ambiguous at DST->STD local time overlap
270 UBool AMBIGUOUS_DST_DECESSION[] = {FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, FALSE, TRUE};
271 // Whether each pattern is ambiguous at STD->STD/DST->DST local time overlap
272 UBool AMBIGUOUS_NEGATIVE_SHIFT[] = {TRUE, TRUE, FALSE, FALSE, TRUE, TRUE, TRUE, TRUE};
273
274 // Workaround for #6338
275 //UnicodeString BASEPATTERN("yyyy-MM-dd'T'HH:mm:ss.SSS");
276 UnicodeString BASEPATTERN("yyyy.MM.dd HH:mm:ss.SSS");
277
278 // timer for performance analysis
279 UDate timer;
280 UDate times[NUM_PATTERNS];
281 for (int32_t i = 0; i < NUM_PATTERNS; i++) {
282 times[i] = 0;
283 }
284
285 UBool REALLY_VERBOSE = FALSE;
286
287 // Set up test locales
288 const Locale locales1[] = {
289 Locale("en")
290 };
291 const Locale locales2[] = {
292 Locale("ar_EG"), Locale("bg_BG"), Locale("ca_ES"), Locale("da_DK"), Locale("de"),
293 Locale("de_DE"), Locale("el_GR"), Locale("en"), Locale("en_AU"), Locale("en_CA"),
294 Locale("en_US"), Locale("es"), Locale("es_ES"), Locale("es_MX"), Locale("fi_FI"),
295 Locale("fr"), Locale("fr_CA"), Locale("fr_FR"), Locale("he_IL"), Locale("hu_HU"),
296 Locale("it"), Locale("it_IT"), Locale("ja"), Locale("ja_JP"), Locale("ko"),
297 Locale("ko_KR"), Locale("nb_NO"), Locale("nl_NL"), Locale("nn_NO"), Locale("pl_PL"),
298 Locale("pt"), Locale("pt_BR"), Locale("pt_PT"), Locale("ru_RU"), Locale("sv_SE"),
299 Locale("th_TH"), Locale("tr_TR"), Locale("zh"), Locale("zh_Hans"), Locale("zh_Hans_CN"),
300 Locale("zh_Hant"), Locale("zh_Hant_TW")
301 };
302
303 const Locale *LOCALES;
304 int32_t nLocales;
305 if (bTestAll) {
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 errcheckln(status, (UnicodeString)"new SimpleDateFormat failed for pattern " +
341 pattern + " for locale " + LOCALES[locidx].getName() + " - " + u_errorName(status));
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 */
469