2 *******************************************************************************
3 * Copyright (C) 2007-2008, International Business Machines Corporation and *
4 * others. All Rights Reserved. *
5 *******************************************************************************
7 #include "unicode/utypes.h"
9 #if !UCONFIG_NO_FORMATTING
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"
24 static const char* PATTERNS
[] = {"z", "zzzz", "Z", "ZZZZ", "v", "vvvv", "V", "VVVV"};
25 static const int NUM_PATTERNS
= sizeof(PATTERNS
)/sizeof(const char*);
28 TimeZoneFormatTest::runIndexedTest( int32_t index
, UBool exec
, const char* &name
, char* /*par*/ )
31 logln("TestSuite TimeZoneFormatTest");
34 TESTCASE(0, TestTimeZoneRoundTrip
);
35 TESTCASE(1, TestTimeRoundTrip
);
36 default: name
= ""; break;
41 TimeZoneFormatTest::TestTimeZoneRoundTrip(void) {
42 UErrorCode status
= U_ZERO_ERROR
;
44 SimpleTimeZone
unknownZone(-31415, (UnicodeString
)"Etc/Unknown");
45 int32_t badDstOffset
= -1234;
46 int32_t badZoneOffset
= -2345;
48 int32_t testDateData
[][3] = {
57 Calendar
*cal
= Calendar::createInstance(TimeZone::createTimeZone((UnicodeString
)"UTC"), status
);
58 if (U_FAILURE(status
)) {
59 errln("Calendar::createInstance failed");
63 // Set up rule equivalency test range
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");
75 UDate DATES
[(sizeof(testDateData
)/sizeof(int32_t))/3];
76 const int32_t nDates
= (sizeof(testDateData
)/sizeof(int32_t))/3;
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");
87 // Set up test locales
88 const Locale locales1
[] = {
91 const Locale locales2
[] = {
99 const Locale
*LOCALES
;
102 LOCALES
= Locale::getAvailableLocales(nLocales
);
105 nLocales
= sizeof(locales1
)/sizeof(Locale
);
108 nLocales
= sizeof(locales2
)/sizeof(Locale
);
111 StringEnumeration
*tzids
= TimeZone::createEnumeration();
112 if (U_FAILURE(status
)) {
113 errln("tzids->count failed");
117 int32_t inRaw
, inDst
;
118 int32_t outRaw
, outDst
;
120 // Run the roundtrip test
121 for (int32_t locidx
= 0; locidx
< nLocales
; locidx
++) {
122 for (int32_t patidx
= 0; patidx
< NUM_PATTERNS
; patidx
++) {
124 //DEBUG static const char* PATTERNS[] = {"z", "zzzz", "Z", "ZZZZ", "v", "vvvv", "V", "VVVV"};
125 //if (patidx != 1) continue;
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
;
135 tzids
->reset(status
);
136 const UnicodeString
*tzid
;
137 while ((tzid
= tzids
->snext(status
))) {
138 TimeZone
*tz
= TimeZone::createTimeZone(*tzid
);
140 for (int32_t datidx
= 0; datidx
< nDates
; datidx
++) {
142 FieldPosition
fpos(0);
144 sdf
->setTimeZone(*tz
);
145 sdf
->format(DATES
[datidx
], tzstr
, fpos
);
147 // Before parse, set unknown zone to SimpleDateFormat instance
148 // just for making sure that it does not depends on the time zone
150 sdf
->setTimeZone(unknownZone
);
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
;
160 outcal
->set(UCAL_DST_OFFSET
, badDstOffset
);
161 outcal
->set(UCAL_ZONE_OFFSET
, badZoneOffset
);
163 sdf
->parse(tzstr
, *outcal
, pos
);
166 const TimeZone
&outtz
= outcal
->getTimeZone();
167 UnicodeString outtzid
;
168 outtz
.getID(outtzid
);
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
;
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
;
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
))) {
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
);
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
);
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
);
224 if (U_FAILURE(status
)) {
225 errln("hasEquivalentTransitions failed");
226 status
= U_ZERO_ERROR
;
242 TimeZoneFormatTest::TestTimeRoundTrip(void) {
243 UErrorCode status
= U_ZERO_ERROR
;
245 Calendar
*cal
= Calendar::createInstance(TimeZone::createTimeZone((UnicodeString
)"UTC"), status
);
246 if (U_FAILURE(status
)) {
247 errln("Calendar::createInstance failed");
251 UDate START_TIME
, END_TIME
;
254 cal
->set(1900, UCAL_JANUARY
, 1);
256 cal
->set(1965, UCAL_JANUARY
, 1);
258 START_TIME
= cal
->getTime(status
);
260 cal
->set(2015, UCAL_JANUARY
, 1);
261 END_TIME
= cal
->getTime(status
);
262 if (U_FAILURE(status
)) {
263 errln("getTime failed");
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
};
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");
276 // timer for performance analysis
278 UDate times
[NUM_PATTERNS
];
279 for (int32_t i
= 0; i
< NUM_PATTERNS
; i
++) {
283 UBool REALLY_VERBOSE
= FALSE
;
285 // Set up test locales
286 const Locale locales1
[] = {
289 const Locale locales2
[] = {
299 Locale("zh_Hans_CN"),
303 const Locale
*LOCALES
;
306 LOCALES
= Locale::getAvailableLocales(nLocales
);
309 nLocales
= sizeof(locales1
)/sizeof(Locale
);
312 nLocales
= sizeof(locales2
)/sizeof(Locale
);
315 StringEnumeration
*tzids
= TimeZone::createEnumeration();
316 if (U_FAILURE(status
)) {
317 errln("tzids->count failed");
321 int32_t testCounts
= 0;
323 UBool expectedRoundTrip
[4];
326 for (int32_t locidx
= 0; locidx
< nLocales
; locidx
++) {
327 logln((UnicodeString
)"Locale: " + LOCALES
[locidx
].getName());
329 for (int32_t patidx
= 0; patidx
< NUM_PATTERNS
; patidx
++) {
330 logln((UnicodeString
)" pattern: " + PATTERNS
[patidx
]);
332 //DEBUG static const char* PATTERNS[] = {"z", "zzzz", "Z", "ZZZZ", "v", "vvvv", "V", "VVVV"};
333 //if (patidx != 1) continue;
335 UnicodeString
pattern(BASEPATTERN
);
336 pattern
.append(" ").append(PATTERNS
[patidx
]);
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
;
346 tzids
->reset(status
);
347 const UnicodeString
*tzid
;
349 timer
= Calendar::getNow();
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
;
359 if (*tzid
!= canonical
) {
363 BasicTimeZone
*tz
= (BasicTimeZone
*)TimeZone::createTimeZone(*tzid
);
364 sdf
->setTimeZone(*tz
);
366 UDate t
= START_TIME
;
367 TimeZoneTransition tzt
;
368 UBool tztAvail
= FALSE
;
371 while (t
< END_TIME
) {
374 expectedRoundTrip
[0] = TRUE
;
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
;
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
];
391 expectedRoundTrip
[3] = TRUE
;
394 testTimes
[0] = t
- 1;
395 expectedRoundTrip
[0] = TRUE
;
397 expectedRoundTrip
[1] = TRUE
;
401 for (int32_t testidx
= 0; testidx
< testLen
; testidx
++) {
403 // reduce regular test time
404 if (!expectedRoundTrip
[testidx
]) {
411 FieldPosition
fpos(0);
412 sdf
->format(testTimes
[testidx
], text
, fpos
);
414 UDate parsedDate
= sdf
->parse(text
, status
);
415 if (U_FAILURE(status
)) {
416 errln((UnicodeString
)"Failed to parse " + text
);
417 status
= U_ZERO_ERROR
;
420 if (parsedDate
!= testTimes
[testidx
]) {
421 UnicodeString msg
= (UnicodeString
)"Time round trip failed for "
423 + ", locale=" + LOCALES
[locidx
].getName()
424 + ", pattern=" + PATTERNS
[patidx
]
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
) {
436 tztAvail
= tz
->getNextTransition(t
, FALSE
, tzt
);
441 // Test the date in the middle of two transitions.
442 t
+= (int64_t)((tzt
.getTime() - t
)/2);
451 times
[patidx
] += (Calendar::getNow() - timer
);
456 logln("### Elapsed time by patterns ###");
457 for (int32_t i
= 0; i
< NUM_PATTERNS
; i
++) {
458 logln(UnicodeString("") + times
[i
] + "ms (" + PATTERNS
[i
] + ")");
461 logln((UnicodeString
)"Total: " + total
+ "ms");
462 logln((UnicodeString
)"Iteration: " + testCounts
);
468 #endif /* #if !UCONFIG_NO_FORMATTING */