• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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