2 *******************************************************************************
3 * Copyright (C) 2007-2010, International Business Machines Corporation and *
4 * others. All Rights Reserved. *
5 *******************************************************************************
7 #include "unicode/utypes.h"
9 #if !UCONFIG_NO_FORMATTING
13 #include "simplethread.h"
14 #include "unicode/timezone.h"
15 #include "unicode/simpletz.h"
16 #include "unicode/calendar.h"
17 #include "unicode/strenum.h"
18 #include "unicode/smpdtfmt.h"
19 #include "unicode/uchar.h"
20 #include "unicode/basictz.h"
23 static const char* PATTERNS
[] = {"z", "zzzz", "Z", "ZZZZ", "v", "vvvv", "V", "VVVV"};
24 static const int NUM_PATTERNS
= sizeof(PATTERNS
)/sizeof(const char*);
27 TimeZoneFormatTest::runIndexedTest( int32_t index
, UBool exec
, const char* &name
, char* /*par*/ )
30 logln("TestSuite TimeZoneFormatTest");
33 TESTCASE(0, TestTimeZoneRoundTrip
);
34 TESTCASE(1, TestTimeRoundTrip
);
35 default: name
= ""; break;
40 TimeZoneFormatTest::TestTimeZoneRoundTrip(void) {
41 UErrorCode status
= U_ZERO_ERROR
;
43 SimpleTimeZone
unknownZone(-31415, (UnicodeString
)"Etc/Unknown");
44 int32_t badDstOffset
= -1234;
45 int32_t badZoneOffset
= -2345;
47 int32_t testDateData
[][3] = {
56 Calendar
*cal
= Calendar::createInstance(TimeZone::createTimeZone((UnicodeString
)"UTC"), status
);
57 if (U_FAILURE(status
)) {
58 dataerrln("Calendar::createInstance failed: %s", u_errorName(status
));
62 // Set up rule equivalency test range
64 cal
->set(1900, UCAL_JANUARY
, 1);
65 low
= cal
->getTime(status
);
66 cal
->set(2040, UCAL_JANUARY
, 1);
67 high
= cal
->getTime(status
);
68 if (U_FAILURE(status
)) {
69 errln("getTime failed");
74 UDate DATES
[(sizeof(testDateData
)/sizeof(int32_t))/3];
75 const int32_t nDates
= (sizeof(testDateData
)/sizeof(int32_t))/3;
77 for (int32_t i
= 0; i
< nDates
; i
++) {
78 cal
->set(testDateData
[i
][0], testDateData
[i
][1], testDateData
[i
][2]);
79 DATES
[i
] = cal
->getTime(status
);
80 if (U_FAILURE(status
)) {
81 errln("getTime failed");
86 // Set up test locales
87 const Locale testLocales
[] = {
94 const Locale
*LOCALES
;
98 LOCALES
= testLocales
;
99 nLocales
= sizeof(testLocales
)/sizeof(Locale
);
101 LOCALES
= Locale::getAvailableLocales(nLocales
);
104 StringEnumeration
*tzids
= TimeZone::createEnumeration();
105 if (U_FAILURE(status
)) {
106 errln("tzids->count failed");
110 int32_t inRaw
, inDst
;
111 int32_t outRaw
, outDst
;
113 // Run the roundtrip test
114 for (int32_t locidx
= 0; locidx
< nLocales
; locidx
++) {
115 for (int32_t patidx
= 0; patidx
< NUM_PATTERNS
; patidx
++) {
117 SimpleDateFormat
*sdf
= new SimpleDateFormat((UnicodeString
)PATTERNS
[patidx
], LOCALES
[locidx
], status
);
118 if (U_FAILURE(status
)) {
119 errcheckln(status
, (UnicodeString
)"new SimpleDateFormat failed for pattern " +
120 PATTERNS
[patidx
] + " for locale " + LOCALES
[locidx
].getName() + " - " + u_errorName(status
));
121 status
= U_ZERO_ERROR
;
125 tzids
->reset(status
);
126 const UnicodeString
*tzid
;
127 while ((tzid
= tzids
->snext(status
))) {
128 TimeZone
*tz
= TimeZone::createTimeZone(*tzid
);
130 for (int32_t datidx
= 0; datidx
< nDates
; datidx
++) {
132 FieldPosition
fpos(0);
134 sdf
->setTimeZone(*tz
);
135 sdf
->format(DATES
[datidx
], tzstr
, fpos
);
137 // Before parse, set unknown zone to SimpleDateFormat instance
138 // just for making sure that it does not depends on the time zone
140 sdf
->setTimeZone(unknownZone
);
143 ParsePosition
pos(0);
144 Calendar
*outcal
= Calendar::createInstance(unknownZone
, status
);
145 if (U_FAILURE(status
)) {
146 errln("Failed to create an instance of calendar for receiving parse result.");
147 status
= U_ZERO_ERROR
;
150 outcal
->set(UCAL_DST_OFFSET
, badDstOffset
);
151 outcal
->set(UCAL_ZONE_OFFSET
, badZoneOffset
);
153 sdf
->parse(tzstr
, *outcal
, pos
);
156 const TimeZone
&outtz
= outcal
->getTimeZone();
157 UnicodeString outtzid
;
158 outtz
.getID(outtzid
);
160 tz
->getOffset(DATES
[datidx
], false, inRaw
, inDst
, status
);
161 if (U_FAILURE(status
)) {
162 errln((UnicodeString
)"Failed to get offsets from time zone" + *tzid
);
163 status
= U_ZERO_ERROR
;
165 outtz
.getOffset(DATES
[datidx
], false, outRaw
, outDst
, status
);
166 if (U_FAILURE(status
)) {
167 errln((UnicodeString
)"Failed to get offsets from time zone" + outtzid
);
168 status
= U_ZERO_ERROR
;
171 if (uprv_strcmp(PATTERNS
[patidx
], "VVVV") == 0) {
172 // Location: time zone rule must be preserved except
173 // zones not actually associated with a specific location.
174 // Time zones in this category do not have "/" in its ID.
175 UnicodeString canonical
;
176 TimeZone::getCanonicalID(*tzid
, canonical
, status
);
177 if (U_FAILURE(status
)) {
178 // Uknown ID - we should not get here
179 errln((UnicodeString
)"Unknown ID " + *tzid
);
180 status
= U_ZERO_ERROR
;
181 } else if (outtzid
!= canonical
) {
182 // Canonical ID did not match - check the rules
183 if (!((BasicTimeZone
*)&outtz
)->hasEquivalentTransitions((BasicTimeZone
&)*tz
, low
, high
, TRUE
, status
)) {
184 if (canonical
.indexOf((UChar
)0x27 /*'/'*/) == -1) {
185 // Exceptional cases, such as CET, EET, MET and WET
186 logln("Canonical round trip failed (as expected); tz=" + *tzid
187 + ", locale=" + LOCALES
[locidx
].getName() + ", pattern=" + PATTERNS
[patidx
]
188 + ", time=" + DATES
[datidx
] + ", str=" + tzstr
189 + ", outtz=" + outtzid
);
191 errln("Canonical round trip failed; tz=" + *tzid
192 + ", locale=" + LOCALES
[locidx
].getName() + ", pattern=" + PATTERNS
[patidx
]
193 + ", time=" + DATES
[datidx
] + ", str=" + tzstr
194 + ", outtz=" + outtzid
);
196 if (U_FAILURE(status
)) {
197 errln("hasEquivalentTransitions failed");
198 status
= U_ZERO_ERROR
;
204 // Check if localized GMT format or RFC format is used.
205 int32_t numDigits
= 0;
206 for (int n
= 0; n
< tzstr
.length(); n
++) {
207 if (u_isdigit(tzstr
.charAt(n
))) {
211 if (numDigits
>= 3) {
212 // Localized GMT or RFC: total offset (raw + dst) must be preserved.
213 int32_t inOffset
= inRaw
+ inDst
;
214 int32_t outOffset
= outRaw
+ outDst
;
215 if (inOffset
!= outOffset
) {
216 errln((UnicodeString
)"Offset round trip failed; tz=" + *tzid
217 + ", locale=" + LOCALES
[locidx
].getName() + ", pattern=" + PATTERNS
[patidx
]
218 + ", time=" + DATES
[datidx
] + ", str=" + tzstr
219 + ", inOffset=" + inOffset
+ ", outOffset=" + outOffset
);
222 // Specific or generic: raw offset must be preserved.
223 if (inRaw
!= outRaw
) {
224 errln((UnicodeString
)"Raw offset round trip failed; tz=" + *tzid
225 + ", locale=" + LOCALES
[locidx
].getName() + ", pattern=" + PATTERNS
[patidx
]
226 + ", time=" + DATES
[datidx
] + ", str=" + tzstr
227 + ", inRawOffset=" + inRaw
+ ", outRawOffset=" + outRaw
);
246 const Locale
* locales
; // Static
247 int32_t nLocales
; // Static
248 UBool quick
; // Static
249 UDate START_TIME
; // Static
250 UDate END_TIME
; // Static
254 class TestTimeRoundTripThread
: public SimpleThread
{
256 TestTimeRoundTripThread(IntlTest
& tlog
, LocaleData
&ld
, int32_t i
)
257 : log(tlog
), data(ld
), index(i
) {}
259 UErrorCode status
= U_ZERO_ERROR
;
260 UBool REALLY_VERBOSE
= FALSE
;
262 // Whether each pattern is ambiguous at DST->STD local time overlap
263 UBool AMBIGUOUS_DST_DECESSION
[] = { FALSE
, FALSE
, FALSE
, FALSE
, TRUE
, TRUE
, FALSE
, TRUE
};
264 // Whether each pattern is ambiguous at STD->STD/DST->DST local time overlap
265 UBool AMBIGUOUS_NEGATIVE_SHIFT
[] = { TRUE
, TRUE
, FALSE
, FALSE
, TRUE
, TRUE
, TRUE
, TRUE
};
267 // Workaround for #6338
268 //UnicodeString BASEPATTERN("yyyy-MM-dd'T'HH:mm:ss.SSS");
269 UnicodeString
BASEPATTERN("yyyy.MM.dd HH:mm:ss.SSS");
271 // timer for performance analysis
274 UBool expectedRoundTrip
[4];
277 StringEnumeration
*tzids
= TimeZone::createEnumeration();
278 if (U_FAILURE(status
)) {
279 log
.errln("tzids->count failed");
284 UDate times
[NUM_PATTERNS
];
285 for (int32_t i
= 0; i
< NUM_PATTERNS
; i
++) {
289 int32_t testCounts
= 0;
292 umtx_lock(NULL
); // Lock to increment the index
293 for (int32_t i
= 0; i
< NUM_PATTERNS
; i
++) {
294 data
.times
[i
] += times
[i
];
295 data
.testCounts
+= testCounts
;
297 if (data
.index
< data
.nLocales
) {
303 umtx_unlock(NULL
); // Unlock for other threads to use
306 log
.logln((UnicodeString
) "Thread " + index
+ " is done.");
310 log
.logln((UnicodeString
) "\nThread " + index
+ ": Locale: " + UnicodeString(data
.locales
[locidx
].getName()));
312 for (int32_t patidx
= 0; patidx
< NUM_PATTERNS
; patidx
++) {
313 log
.logln((UnicodeString
) " Pattern: " + PATTERNS
[patidx
]);
316 UnicodeString
pattern(BASEPATTERN
);
317 pattern
.append(" ").append(PATTERNS
[patidx
]);
319 SimpleDateFormat
*sdf
= new SimpleDateFormat(pattern
, data
.locales
[locidx
], status
);
320 if (U_FAILURE(status
)) {
321 log
.errcheckln(status
, (UnicodeString
) "new SimpleDateFormat failed for pattern " +
322 pattern
+ " for locale " + data
.locales
[locidx
].getName() + " - " + u_errorName(status
));
323 status
= U_ZERO_ERROR
;
327 tzids
->reset(status
);
328 const UnicodeString
*tzid
;
330 timer
= Calendar::getNow();
332 while ((tzid
= tzids
->snext(status
))) {
333 UnicodeString canonical
;
334 TimeZone::getCanonicalID(*tzid
, canonical
, status
);
335 if (U_FAILURE(status
)) {
336 // Unknown ID - we should not get here
337 status
= U_ZERO_ERROR
;
340 if (*tzid
!= canonical
) {
344 BasicTimeZone
*tz
= (BasicTimeZone
*) TimeZone::createTimeZone(*tzid
);
345 sdf
->setTimeZone(*tz
);
347 UDate t
= data
.START_TIME
;
348 TimeZoneTransition tzt
;
349 UBool tztAvail
= FALSE
;
352 while (t
< data
.END_TIME
) {
355 expectedRoundTrip
[0] = TRUE
;
358 int32_t fromOffset
= tzt
.getFrom()->getRawOffset() + tzt
.getFrom()->getDSTSavings();
359 int32_t toOffset
= tzt
.getTo()->getRawOffset() + tzt
.getTo()->getDSTSavings();
360 int32_t delta
= toOffset
- fromOffset
;
362 UBool isDstDecession
= tzt
.getFrom()->getDSTSavings() > 0 && tzt
.getTo()->getDSTSavings() == 0;
363 testTimes
[0] = t
+ delta
- 1;
364 expectedRoundTrip
[0] = TRUE
;
365 testTimes
[1] = t
+ delta
;
366 expectedRoundTrip
[1] = isDstDecession
? !AMBIGUOUS_DST_DECESSION
[patidx
] : !AMBIGUOUS_NEGATIVE_SHIFT
[patidx
];
367 testTimes
[2] = t
- 1;
368 expectedRoundTrip
[2] = isDstDecession
? !AMBIGUOUS_DST_DECESSION
[patidx
] : !AMBIGUOUS_NEGATIVE_SHIFT
[patidx
];
370 expectedRoundTrip
[3] = TRUE
;
373 testTimes
[0] = t
- 1;
374 expectedRoundTrip
[0] = TRUE
;
376 expectedRoundTrip
[1] = TRUE
;
380 for (int32_t testidx
= 0; testidx
< testLen
; testidx
++) {
382 // reduce regular test time
383 if (!expectedRoundTrip
[testidx
]) {
391 FieldPosition
fpos(0);
392 sdf
->format(testTimes
[testidx
], text
, fpos
);
394 UDate parsedDate
= sdf
->parse(text
, status
);
395 if (U_FAILURE(status
)) {
396 log
.errln((UnicodeString
) "Parse failure for text=" + text
+ ", tzid=" + *tzid
+ ", locale=" + data
.locales
[locidx
].getName()
397 + ", pattern=" + PATTERNS
[patidx
] + ", time=" + testTimes
[testidx
]);
398 status
= U_ZERO_ERROR
;
401 if (parsedDate
!= testTimes
[testidx
]) {
402 UnicodeString msg
= (UnicodeString
) "Time round trip failed for " + "tzid=" + *tzid
+ ", locale=" + data
.locales
[locidx
].getName() + ", pattern=" + PATTERNS
[patidx
]
403 + ", text=" + text
+ ", time=" + testTimes
[testidx
] + ", restime=" + parsedDate
+ ", diff=" + (parsedDate
- testTimes
[testidx
]);
404 if (expectedRoundTrip
[testidx
]) {
405 log
.errln((UnicodeString
) "FAIL: " + msg
);
406 } else if (REALLY_VERBOSE
) {
411 tztAvail
= tz
->getNextTransition(t
, FALSE
, tzt
);
416 // Test the date in the middle of two transitions.
417 t
+= (int64_t) ((tzt
.getTime() - t
) / 2);
426 times
[patidx
] += (Calendar::getNow() - timer
);
442 TimeZoneFormatTest::TestTimeRoundTrip(void) {
443 int32_t nThreads
= threadCount
;
444 const Locale
*LOCALES
;
446 int32_t testCounts
= 0;
448 UErrorCode status
= U_ZERO_ERROR
;
449 Calendar
*cal
= Calendar::createInstance(TimeZone::createTimeZone((UnicodeString
) "UTC"), status
);
450 if (U_FAILURE(status
)) {
451 dataerrln("Calendar::createInstance failed: %s", u_errorName(status
));
455 const char* testAllProp
= getProperty("TimeZoneRoundTripAll");
456 UBool bTestAll
= (testAllProp
&& uprv_strcmp(testAllProp
, "true") == 0);
458 UDate START_TIME
, END_TIME
;
459 if (bTestAll
|| !quick
) {
460 cal
->set(1900, UCAL_JANUARY
, 1);
462 cal
->set(1990, UCAL_JANUARY
, 1);
464 START_TIME
= cal
->getTime(status
);
466 cal
->set(2015, UCAL_JANUARY
, 1);
467 END_TIME
= cal
->getTime(status
);
469 if (U_FAILURE(status
)) {
470 errln("getTime failed");
474 UDate times
[NUM_PATTERNS
];
475 for (int32_t i
= 0; i
< NUM_PATTERNS
; i
++) {
479 // Set up test locales
480 const Locale locales1
[] = {Locale("en")};
481 const Locale locales2
[] = {
482 Locale("ar_EG"), Locale("bg_BG"), Locale("ca_ES"), Locale("da_DK"), Locale("de"),
483 Locale("de_DE"), Locale("el_GR"), Locale("en"), Locale("en_AU"), Locale("en_CA"),
484 Locale("en_US"), Locale("es"), Locale("es_ES"), Locale("es_MX"), Locale("fi_FI"),
485 Locale("fr"), Locale("fr_CA"), Locale("fr_FR"), Locale("he_IL"), Locale("hu_HU"),
486 Locale("it"), Locale("it_IT"), Locale("ja"), Locale("ja_JP"), Locale("ko"),
487 Locale("ko_KR"), Locale("nb_NO"), Locale("nl_NL"), Locale("nn_NO"), Locale("pl_PL"),
488 Locale("pt"), Locale("pt_BR"), Locale("pt_PT"), Locale("ru_RU"), Locale("sv_SE"),
489 Locale("th_TH"), Locale("tr_TR"), Locale("zh"), Locale("zh_Hans"), Locale("zh_Hans_CN"),
490 Locale("zh_Hant"), Locale("zh_Hant_TW")
494 LOCALES
= Locale::getAvailableLocales(nLocales
);
497 nLocales
= sizeof(locales1
)/sizeof(Locale
);
500 nLocales
= sizeof(locales2
)/sizeof(Locale
);
505 data
.testCounts
= testCounts
;
507 data
.locales
= LOCALES
;
508 data
.nLocales
= nLocales
;
510 data
.START_TIME
= START_TIME
;
511 data
.END_TIME
= END_TIME
;
514 #if (ICU_USE_THREADS==0)
515 TestTimeRoundTripThread
fakeThread(*this, data
, 0);
518 TestTimeRoundTripThread
**threads
= new TestTimeRoundTripThread
*[threadCount
];
520 for (i
= 0; i
< nThreads
; i
++) {
521 threads
[i
] = new TestTimeRoundTripThread(*this, data
, i
);
522 if (threads
[i
]->start() != 0) {
523 errln("Error starting thread %d", i
);
530 if (data
.numDone
== nLocales
) {
536 SimpleThread::sleep(1000);
539 for (i
= 0; i
< nThreads
; i
++) {
546 logln("### Elapsed time by patterns ###");
547 for (int32_t i
= 0; i
< NUM_PATTERNS
; i
++) {
548 logln(UnicodeString("") + data
.times
[i
] + "ms (" + PATTERNS
[i
] + ")");
549 total
+= data
.times
[i
];
551 logln((UnicodeString
) "Total: " + total
+ "ms");
552 logln((UnicodeString
) "Iteration: " + data
.testCounts
);
557 #endif /* #if !UCONFIG_NO_FORMATTING */