2 *******************************************************************************
3 * Copyright (C) 2007-2012, 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", "ZZZZZ", "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 int32_t inRaw
, inDst
;
106 int32_t outRaw
, outDst
;
108 // Run the roundtrip test
109 for (int32_t locidx
= 0; locidx
< nLocales
; locidx
++) {
110 UnicodeString localGMTString
;
111 SimpleDateFormat
gmtFmt(UnicodeString("ZZZZ"), LOCALES
[locidx
], status
);
112 if (U_FAILURE(status
)) {
113 dataerrln("Error creating SimpleDateFormat - %s", u_errorName(status
));
116 gmtFmt
.setTimeZone(*TimeZone::getGMT());
117 gmtFmt
.format(0.0, localGMTString
);
119 for (int32_t patidx
= 0; patidx
< NUM_PATTERNS
; patidx
++) {
121 SimpleDateFormat
*sdf
= new SimpleDateFormat((UnicodeString
)PATTERNS
[patidx
], LOCALES
[locidx
], status
);
122 if (U_FAILURE(status
)) {
123 dataerrln((UnicodeString
)"new SimpleDateFormat failed for pattern " +
124 PATTERNS
[patidx
] + " for locale " + LOCALES
[locidx
].getName() + " - " + u_errorName(status
));
125 status
= U_ZERO_ERROR
;
129 tzids
->reset(status
);
130 const UnicodeString
*tzid
;
131 while ((tzid
= tzids
->snext(status
))) {
132 TimeZone
*tz
= TimeZone::createTimeZone(*tzid
);
134 for (int32_t datidx
= 0; datidx
< nDates
; datidx
++) {
136 FieldPosition
fpos(0);
138 sdf
->setTimeZone(*tz
);
139 sdf
->format(DATES
[datidx
], tzstr
, fpos
);
141 // Before parse, set unknown zone to SimpleDateFormat instance
142 // just for making sure that it does not depends on the time zone
144 sdf
->setTimeZone(unknownZone
);
147 ParsePosition
pos(0);
148 Calendar
*outcal
= Calendar::createInstance(unknownZone
, status
);
149 if (U_FAILURE(status
)) {
150 errln("Failed to create an instance of calendar for receiving parse result.");
151 status
= U_ZERO_ERROR
;
154 outcal
->set(UCAL_DST_OFFSET
, badDstOffset
);
155 outcal
->set(UCAL_ZONE_OFFSET
, badZoneOffset
);
157 sdf
->parse(tzstr
, *outcal
, pos
);
160 const TimeZone
&outtz
= outcal
->getTimeZone();
161 UnicodeString outtzid
;
162 outtz
.getID(outtzid
);
164 tz
->getOffset(DATES
[datidx
], false, inRaw
, inDst
, status
);
165 if (U_FAILURE(status
)) {
166 errln((UnicodeString
)"Failed to get offsets from time zone" + *tzid
);
167 status
= U_ZERO_ERROR
;
169 outtz
.getOffset(DATES
[datidx
], false, outRaw
, outDst
, status
);
170 if (U_FAILURE(status
)) {
171 errln((UnicodeString
)"Failed to get offsets from time zone" + outtzid
);
172 status
= U_ZERO_ERROR
;
175 if (uprv_strcmp(PATTERNS
[patidx
], "VVVV") == 0) {
176 // Location: time zone rule must be preserved except
177 // zones not actually associated with a specific location.
178 // Time zones in this category do not have "/" in its ID.
179 UnicodeString canonical
;
180 TimeZone::getCanonicalID(*tzid
, canonical
, status
);
181 if (U_FAILURE(status
)) {
182 // Uknown ID - we should not get here
183 errln((UnicodeString
)"Unknown ID " + *tzid
);
184 status
= U_ZERO_ERROR
;
185 } else if (outtzid
!= canonical
) {
186 // Canonical ID did not match - check the rules
187 if (!((BasicTimeZone
*)&outtz
)->hasEquivalentTransitions((BasicTimeZone
&)*tz
, low
, high
, TRUE
, status
)) {
188 if (canonical
.indexOf((UChar
)0x27 /*'/'*/) == -1) {
189 // Exceptional cases, such as CET, EET, MET and WET
190 logln("Canonical round trip failed (as expected); tz=" + *tzid
191 + ", locale=" + LOCALES
[locidx
].getName() + ", pattern=" + PATTERNS
[patidx
]
192 + ", time=" + DATES
[datidx
] + ", str=" + tzstr
193 + ", outtz=" + outtzid
);
195 errln("Canonical round trip failed; tz=" + *tzid
196 + ", locale=" + LOCALES
[locidx
].getName() + ", pattern=" + PATTERNS
[patidx
]
197 + ", time=" + DATES
[datidx
] + ", str=" + tzstr
198 + ", outtz=" + outtzid
);
200 if (U_FAILURE(status
)) {
201 errln("hasEquivalentTransitions failed");
202 status
= U_ZERO_ERROR
;
208 // Check if localized GMT format or RFC format is used.
209 UBool isOffsetFormat
= (*PATTERNS
[patidx
] == 'Z');
210 if (!isOffsetFormat
) {
211 // Check if localized GMT format is used as a fallback of name styles
212 int32_t numDigits
= 0;
213 for (int n
= 0; n
< tzstr
.length(); n
++) {
214 if (u_isdigit(tzstr
.charAt(n
))) {
218 isOffsetFormat
= (numDigits
>= 3);
220 if (isOffsetFormat
|| tzstr
== localGMTString
) {
221 // Localized GMT or RFC: total offset (raw + dst) must be preserved.
222 int32_t inOffset
= inRaw
+ inDst
;
223 int32_t outOffset
= outRaw
+ outDst
;
224 if (inOffset
!= outOffset
) {
225 errln((UnicodeString
)"Offset round trip failed; tz=" + *tzid
226 + ", locale=" + LOCALES
[locidx
].getName() + ", pattern=" + PATTERNS
[patidx
]
227 + ", time=" + DATES
[datidx
] + ", str=" + tzstr
228 + ", inOffset=" + inOffset
+ ", outOffset=" + outOffset
);
231 // Specific or generic: raw offset must be preserved.
232 if (inRaw
!= outRaw
) {
233 errln((UnicodeString
)"Raw offset round trip failed; tz=" + *tzid
234 + ", locale=" + LOCALES
[locidx
].getName() + ", pattern=" + PATTERNS
[patidx
]
235 + ", time=" + DATES
[datidx
] + ", str=" + tzstr
236 + ", inRawOffset=" + inRaw
+ ", outRawOffset=" + outRaw
);
255 const Locale
* locales
; // Static
256 int32_t nLocales
; // Static
257 UBool quick
; // Static
258 UDate START_TIME
; // Static
259 UDate END_TIME
; // Static
263 class TestTimeRoundTripThread
: public SimpleThread
{
265 TestTimeRoundTripThread(IntlTest
& tlog
, LocaleData
&ld
, int32_t i
)
266 : log(tlog
), data(ld
), index(i
) {}
268 UErrorCode status
= U_ZERO_ERROR
;
269 UBool REALLY_VERBOSE
= FALSE
;
271 // Whether each pattern is ambiguous at DST->STD local time overlap
272 UBool AMBIGUOUS_DST_DECESSION
[] = { FALSE
, FALSE
, FALSE
, FALSE
, FALSE
, TRUE
, TRUE
, FALSE
, TRUE
};
273 // Whether each pattern is ambiguous at STD->STD/DST->DST local time overlap
274 UBool AMBIGUOUS_NEGATIVE_SHIFT
[] = { TRUE
, TRUE
, FALSE
, FALSE
, FALSE
, TRUE
, TRUE
, TRUE
, TRUE
};
276 // Workaround for #6338
277 //UnicodeString BASEPATTERN("yyyy-MM-dd'T'HH:mm:ss.SSS");
278 UnicodeString
BASEPATTERN("yyyy.MM.dd HH:mm:ss.SSS");
280 // timer for performance analysis
283 UBool expectedRoundTrip
[4];
286 StringEnumeration
*tzids
= TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL
, NULL
, NULL
, status
);
287 if (U_FAILURE(status
)) {
288 if (status
== U_MISSING_RESOURCE_ERROR
) {
289 /* This error is generally caused by data not being present. However, an infinite loop will occur
290 * because the thread thinks that the test data is never done so we should treat the data as done.
292 log
.dataerrln("TimeZone::createTimeZoneIDEnumeration failed - %s", u_errorName(status
));
293 data
.numDone
= data
.nLocales
;
295 log
.errln("TimeZone::createTimeZoneIDEnumeration failed: %s", u_errorName(status
));
301 UDate times
[NUM_PATTERNS
];
302 for (int32_t i
= 0; i
< NUM_PATTERNS
; i
++) {
306 int32_t testCounts
= 0;
309 umtx_lock(NULL
); // Lock to increment the index
310 for (int32_t i
= 0; i
< NUM_PATTERNS
; i
++) {
311 data
.times
[i
] += times
[i
];
312 data
.testCounts
+= testCounts
;
314 if (data
.index
< data
.nLocales
) {
320 umtx_unlock(NULL
); // Unlock for other threads to use
323 log
.logln((UnicodeString
) "Thread " + index
+ " is done.");
327 log
.logln((UnicodeString
) "\nThread " + index
+ ": Locale: " + UnicodeString(data
.locales
[locidx
].getName()));
329 for (int32_t patidx
= 0; patidx
< NUM_PATTERNS
; patidx
++) {
330 log
.logln((UnicodeString
) " Pattern: " + PATTERNS
[patidx
]);
333 UnicodeString
pattern(BASEPATTERN
);
334 pattern
.append(" ").append(PATTERNS
[patidx
]);
336 SimpleDateFormat
*sdf
= new SimpleDateFormat(pattern
, data
.locales
[locidx
], status
);
337 if (U_FAILURE(status
)) {
338 log
.errcheckln(status
, (UnicodeString
) "new SimpleDateFormat failed for pattern " +
339 pattern
+ " for locale " + data
.locales
[locidx
].getName() + " - " + u_errorName(status
));
340 status
= U_ZERO_ERROR
;
344 tzids
->reset(status
);
345 const UnicodeString
*tzid
;
347 timer
= Calendar::getNow();
349 while ((tzid
= tzids
->snext(status
))) {
350 BasicTimeZone
*tz
= (BasicTimeZone
*) TimeZone::createTimeZone(*tzid
);
351 sdf
->setTimeZone(*tz
);
353 UDate t
= data
.START_TIME
;
354 TimeZoneTransition tzt
;
355 UBool tztAvail
= FALSE
;
358 while (t
< data
.END_TIME
) {
361 expectedRoundTrip
[0] = TRUE
;
364 int32_t fromOffset
= tzt
.getFrom()->getRawOffset() + tzt
.getFrom()->getDSTSavings();
365 int32_t toOffset
= tzt
.getTo()->getRawOffset() + tzt
.getTo()->getDSTSavings();
366 int32_t delta
= toOffset
- fromOffset
;
368 UBool isDstDecession
= tzt
.getFrom()->getDSTSavings() > 0 && tzt
.getTo()->getDSTSavings() == 0;
369 testTimes
[0] = t
+ delta
- 1;
370 expectedRoundTrip
[0] = TRUE
;
371 testTimes
[1] = t
+ delta
;
372 expectedRoundTrip
[1] = isDstDecession
? !AMBIGUOUS_DST_DECESSION
[patidx
] : !AMBIGUOUS_NEGATIVE_SHIFT
[patidx
];
373 testTimes
[2] = t
- 1;
374 expectedRoundTrip
[2] = isDstDecession
? !AMBIGUOUS_DST_DECESSION
[patidx
] : !AMBIGUOUS_NEGATIVE_SHIFT
[patidx
];
376 expectedRoundTrip
[3] = TRUE
;
379 testTimes
[0] = t
- 1;
380 expectedRoundTrip
[0] = TRUE
;
382 expectedRoundTrip
[1] = TRUE
;
386 for (int32_t testidx
= 0; testidx
< testLen
; testidx
++) {
388 // reduce regular test time
389 if (!expectedRoundTrip
[testidx
]) {
397 FieldPosition
fpos(0);
398 sdf
->format(testTimes
[testidx
], text
, fpos
);
400 UDate parsedDate
= sdf
->parse(text
, status
);
401 if (U_FAILURE(status
)) {
402 log
.errln((UnicodeString
) "Parse failure for text=" + text
+ ", tzid=" + *tzid
+ ", locale=" + data
.locales
[locidx
].getName()
403 + ", pattern=" + PATTERNS
[patidx
] + ", time=" + testTimes
[testidx
]);
404 status
= U_ZERO_ERROR
;
407 if (parsedDate
!= testTimes
[testidx
]) {
408 UnicodeString msg
= (UnicodeString
) "Time round trip failed for " + "tzid=" + *tzid
+ ", locale=" + data
.locales
[locidx
].getName() + ", pattern=" + PATTERNS
[patidx
]
409 + ", text=" + text
+ ", time=" + testTimes
[testidx
] + ", restime=" + parsedDate
+ ", diff=" + (parsedDate
- testTimes
[testidx
]);
410 // Timebomb for TZData update
411 if (expectedRoundTrip
[testidx
]) {
412 log
.errln((UnicodeString
) "FAIL: " + msg
);
413 } else if (REALLY_VERBOSE
) {
418 tztAvail
= tz
->getNextTransition(t
, FALSE
, tzt
);
423 // Test the date in the middle of two transitions.
424 t
+= (int64_t) ((tzt
.getTime() - t
) / 2);
433 times
[patidx
] += (Calendar::getNow() - timer
);
449 TimeZoneFormatTest::TestTimeRoundTrip(void) {
450 int32_t nThreads
= threadCount
;
451 const Locale
*LOCALES
;
453 int32_t testCounts
= 0;
455 UErrorCode status
= U_ZERO_ERROR
;
456 Calendar
*cal
= Calendar::createInstance(TimeZone::createTimeZone((UnicodeString
) "UTC"), status
);
457 if (U_FAILURE(status
)) {
458 dataerrln("Calendar::createInstance failed: %s", u_errorName(status
));
462 const char* testAllProp
= getProperty("TimeZoneRoundTripAll");
463 UBool bTestAll
= (testAllProp
&& uprv_strcmp(testAllProp
, "true") == 0);
465 UDate START_TIME
, END_TIME
;
466 if (bTestAll
|| !quick
) {
467 cal
->set(1900, UCAL_JANUARY
, 1);
469 cal
->set(1990, UCAL_JANUARY
, 1);
471 START_TIME
= cal
->getTime(status
);
473 cal
->set(2015, UCAL_JANUARY
, 1);
474 END_TIME
= cal
->getTime(status
);
476 if (U_FAILURE(status
)) {
477 errln("getTime failed");
481 UDate times
[NUM_PATTERNS
];
482 for (int32_t i
= 0; i
< NUM_PATTERNS
; i
++) {
486 // Set up test locales
487 const Locale locales1
[] = {Locale("en")};
488 const Locale locales2
[] = {
489 Locale("ar_EG"), Locale("bg_BG"), Locale("ca_ES"), Locale("da_DK"), Locale("de"),
490 Locale("de_DE"), Locale("el_GR"), Locale("en"), Locale("en_AU"), Locale("en_CA"),
491 Locale("en_US"), Locale("es"), Locale("es_ES"), Locale("es_MX"), Locale("fi_FI"),
492 Locale("fr"), Locale("fr_CA"), Locale("fr_FR"), Locale("he_IL"), Locale("hu_HU"),
493 Locale("it"), Locale("it_IT"), Locale("ja"), Locale("ja_JP"), Locale("ko"),
494 Locale("ko_KR"), Locale("nb_NO"), Locale("nl_NL"), Locale("nn_NO"), Locale("pl_PL"),
495 Locale("pt"), Locale("pt_BR"), Locale("pt_PT"), Locale("ru_RU"), Locale("sv_SE"),
496 Locale("th_TH"), Locale("tr_TR"), Locale("zh"), Locale("zh_Hans"), Locale("zh_Hans_CN"),
497 Locale("zh_Hant"), Locale("zh_Hant_TW")
501 LOCALES
= Locale::getAvailableLocales(nLocales
);
504 nLocales
= sizeof(locales1
)/sizeof(Locale
);
507 nLocales
= sizeof(locales2
)/sizeof(Locale
);
512 data
.testCounts
= testCounts
;
514 data
.locales
= LOCALES
;
515 data
.nLocales
= nLocales
;
517 data
.START_TIME
= START_TIME
;
518 data
.END_TIME
= END_TIME
;
521 #if (ICU_USE_THREADS==0)
522 TestTimeRoundTripThread
fakeThread(*this, data
, 0);
525 TestTimeRoundTripThread
**threads
= new TestTimeRoundTripThread
*[threadCount
];
527 for (i
= 0; i
< nThreads
; i
++) {
528 threads
[i
] = new TestTimeRoundTripThread(*this, data
, i
);
529 if (threads
[i
]->start() != 0) {
530 errln("Error starting thread %d", i
);
537 if (data
.numDone
== nLocales
) {
543 SimpleThread::sleep(1000);
546 for (i
= 0; i
< nThreads
; i
++) {
553 logln("### Elapsed time by patterns ###");
554 for (int32_t i
= 0; i
< NUM_PATTERNS
; i
++) {
555 logln(UnicodeString("") + data
.times
[i
] + "ms (" + PATTERNS
[i
] + ")");
556 total
+= data
.times
[i
];
558 logln((UnicodeString
) "Total: " + total
+ "ms");
559 logln((UnicodeString
) "Iteration: " + data
.testCounts
);
564 #endif /* #if !UCONFIG_NO_FORMATTING */