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