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