]>
Commit | Line | Data |
---|---|---|
46f4442e A |
1 | /* |
2 | ******************************************************************************* | |
51004dcb | 3 | * Copyright (C) 2007-2013, International Business Machines Corporation and * |
46f4442e A |
4 | * others. All Rights Reserved. * |
5 | ******************************************************************************* | |
6 | */ | |
7 | #include "unicode/utypes.h" | |
8 | ||
9 | #if !UCONFIG_NO_FORMATTING | |
10 | ||
11 | #include "tzfmttst.h" | |
12 | ||
729e4ab9 | 13 | #include "simplethread.h" |
46f4442e A |
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" | |
51004dcb A |
21 | #include "unicode/tzfmt.h" |
22 | #include "unicode/localpointer.h" | |
46f4442e | 23 | #include "cstring.h" |
51004dcb A |
24 | #include "zonemeta.h" |
25 | ||
26 | static const char* PATTERNS[] = { | |
27 | "z", | |
28 | "zzzz", | |
29 | "Z", // equivalent to "xxxx" | |
30 | "ZZZZ", // equivalent to "OOOO" | |
31 | "v", | |
32 | "vvvv", | |
33 | "O", | |
34 | "OOOO", | |
35 | "X", | |
36 | "XX", | |
37 | "XXX", | |
38 | "XXXX", | |
39 | "XXXXX", | |
40 | "x", | |
41 | "xx", | |
42 | "xxx", | |
43 | "xxxx", | |
44 | "xxxxx", | |
45 | "V", | |
46 | "VV", | |
47 | "VVV", | |
48 | "VVVV" | |
49 | }; | |
46f4442e A |
50 | static const int NUM_PATTERNS = sizeof(PATTERNS)/sizeof(const char*); |
51 | ||
51004dcb A |
52 | static const UChar ETC_UNKNOWN[] = {0x45, 0x74, 0x63, 0x2F, 0x55, 0x6E, 0x6B, 0x6E, 0x6F, 0x77, 0x6E, 0}; |
53 | ||
54 | static const UChar ETC_SLASH[] = { 0x45, 0x74, 0x63, 0x2F, 0 }; // "Etc/" | |
55 | static const UChar SYSTEMV_SLASH[] = { 0x53, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x56, 0x2F, 0 }; // "SystemV/ | |
56 | static const UChar RIYADH8[] = { 0x52, 0x69, 0x79, 0x61, 0x64, 0x68, 0x38, 0 }; // "Riyadh8" | |
57 | ||
58 | static UBool contains(const char** list, const char* str) { | |
59 | for (int32_t i = 0; list[i]; i++) { | |
60 | if (uprv_strcmp(list[i], str) == 0) { | |
61 | return TRUE; | |
62 | } | |
63 | } | |
64 | return FALSE; | |
65 | } | |
66 | ||
46f4442e A |
67 | void |
68 | TimeZoneFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ ) | |
69 | { | |
70 | if (exec) { | |
71 | logln("TestSuite TimeZoneFormatTest"); | |
72 | } | |
73 | switch (index) { | |
74 | TESTCASE(0, TestTimeZoneRoundTrip); | |
75 | TESTCASE(1, TestTimeRoundTrip); | |
51004dcb A |
76 | TESTCASE(2, TestParse); |
77 | TESTCASE(3, TestISOFormat); | |
46f4442e A |
78 | default: name = ""; break; |
79 | } | |
80 | } | |
81 | ||
82 | void | |
83 | TimeZoneFormatTest::TestTimeZoneRoundTrip(void) { | |
84 | UErrorCode status = U_ZERO_ERROR; | |
85 | ||
51004dcb | 86 | SimpleTimeZone unknownZone(-31415, ETC_UNKNOWN); |
46f4442e A |
87 | int32_t badDstOffset = -1234; |
88 | int32_t badZoneOffset = -2345; | |
89 | ||
90 | int32_t testDateData[][3] = { | |
91 | {2007, 1, 15}, | |
92 | {2007, 6, 15}, | |
93 | {1990, 1, 15}, | |
94 | {1990, 6, 15}, | |
95 | {1960, 1, 15}, | |
96 | {1960, 6, 15}, | |
97 | }; | |
98 | ||
99 | Calendar *cal = Calendar::createInstance(TimeZone::createTimeZone((UnicodeString)"UTC"), status); | |
100 | if (U_FAILURE(status)) { | |
729e4ab9 | 101 | dataerrln("Calendar::createInstance failed: %s", u_errorName(status)); |
46f4442e A |
102 | return; |
103 | } | |
104 | ||
105 | // Set up rule equivalency test range | |
106 | UDate low, high; | |
107 | cal->set(1900, UCAL_JANUARY, 1); | |
108 | low = cal->getTime(status); | |
109 | cal->set(2040, UCAL_JANUARY, 1); | |
110 | high = cal->getTime(status); | |
111 | if (U_FAILURE(status)) { | |
112 | errln("getTime failed"); | |
113 | return; | |
114 | } | |
115 | ||
116 | // Set up test dates | |
117 | UDate DATES[(sizeof(testDateData)/sizeof(int32_t))/3]; | |
118 | const int32_t nDates = (sizeof(testDateData)/sizeof(int32_t))/3; | |
119 | cal->clear(); | |
120 | for (int32_t i = 0; i < nDates; i++) { | |
121 | cal->set(testDateData[i][0], testDateData[i][1], testDateData[i][2]); | |
122 | DATES[i] = cal->getTime(status); | |
123 | if (U_FAILURE(status)) { | |
124 | errln("getTime failed"); | |
125 | return; | |
126 | } | |
127 | } | |
128 | ||
129 | // Set up test locales | |
729e4ab9 | 130 | const Locale testLocales[] = { |
46f4442e A |
131 | Locale("en"), |
132 | Locale("en_CA"), | |
133 | Locale("fr"), | |
134 | Locale("zh_Hant") | |
135 | }; | |
136 | ||
137 | const Locale *LOCALES; | |
138 | int32_t nLocales; | |
729e4ab9 A |
139 | |
140 | if (quick) { | |
141 | LOCALES = testLocales; | |
142 | nLocales = sizeof(testLocales)/sizeof(Locale); | |
46f4442e | 143 | } else { |
729e4ab9 | 144 | LOCALES = Locale::getAvailableLocales(nLocales); |
46f4442e A |
145 | } |
146 | ||
147 | StringEnumeration *tzids = TimeZone::createEnumeration(); | |
46f4442e A |
148 | int32_t inRaw, inDst; |
149 | int32_t outRaw, outDst; | |
150 | ||
151 | // Run the roundtrip test | |
152 | for (int32_t locidx = 0; locidx < nLocales; locidx++) { | |
4388f060 A |
153 | UnicodeString localGMTString; |
154 | SimpleDateFormat gmtFmt(UnicodeString("ZZZZ"), LOCALES[locidx], status); | |
155 | if (U_FAILURE(status)) { | |
156 | dataerrln("Error creating SimpleDateFormat - %s", u_errorName(status)); | |
157 | continue; | |
158 | } | |
159 | gmtFmt.setTimeZone(*TimeZone::getGMT()); | |
160 | gmtFmt.format(0.0, localGMTString); | |
161 | ||
46f4442e A |
162 | for (int32_t patidx = 0; patidx < NUM_PATTERNS; patidx++) { |
163 | ||
46f4442e A |
164 | SimpleDateFormat *sdf = new SimpleDateFormat((UnicodeString)PATTERNS[patidx], LOCALES[locidx], status); |
165 | if (U_FAILURE(status)) { | |
4388f060 | 166 | dataerrln((UnicodeString)"new SimpleDateFormat failed for pattern " + |
729e4ab9 | 167 | PATTERNS[patidx] + " for locale " + LOCALES[locidx].getName() + " - " + u_errorName(status)); |
46f4442e A |
168 | status = U_ZERO_ERROR; |
169 | continue; | |
170 | } | |
171 | ||
172 | tzids->reset(status); | |
173 | const UnicodeString *tzid; | |
174 | while ((tzid = tzids->snext(status))) { | |
175 | TimeZone *tz = TimeZone::createTimeZone(*tzid); | |
176 | ||
177 | for (int32_t datidx = 0; datidx < nDates; datidx++) { | |
178 | UnicodeString tzstr; | |
179 | FieldPosition fpos(0); | |
180 | // Format | |
181 | sdf->setTimeZone(*tz); | |
182 | sdf->format(DATES[datidx], tzstr, fpos); | |
183 | ||
184 | // Before parse, set unknown zone to SimpleDateFormat instance | |
185 | // just for making sure that it does not depends on the time zone | |
186 | // originally set. | |
187 | sdf->setTimeZone(unknownZone); | |
188 | ||
189 | // Parse | |
190 | ParsePosition pos(0); | |
191 | Calendar *outcal = Calendar::createInstance(unknownZone, status); | |
192 | if (U_FAILURE(status)) { | |
193 | errln("Failed to create an instance of calendar for receiving parse result."); | |
194 | status = U_ZERO_ERROR; | |
195 | continue; | |
196 | } | |
197 | outcal->set(UCAL_DST_OFFSET, badDstOffset); | |
198 | outcal->set(UCAL_ZONE_OFFSET, badZoneOffset); | |
199 | ||
200 | sdf->parse(tzstr, *outcal, pos); | |
201 | ||
202 | // Check the result | |
203 | const TimeZone &outtz = outcal->getTimeZone(); | |
204 | UnicodeString outtzid; | |
205 | outtz.getID(outtzid); | |
206 | ||
207 | tz->getOffset(DATES[datidx], false, inRaw, inDst, status); | |
208 | if (U_FAILURE(status)) { | |
209 | errln((UnicodeString)"Failed to get offsets from time zone" + *tzid); | |
210 | status = U_ZERO_ERROR; | |
211 | } | |
212 | outtz.getOffset(DATES[datidx], false, outRaw, outDst, status); | |
213 | if (U_FAILURE(status)) { | |
214 | errln((UnicodeString)"Failed to get offsets from time zone" + outtzid); | |
215 | status = U_ZERO_ERROR; | |
216 | } | |
217 | ||
51004dcb A |
218 | if (uprv_strcmp(PATTERNS[patidx], "V") == 0) { |
219 | // Short zone ID - should support roundtrip for canonical CLDR IDs | |
220 | UnicodeString canonicalID; | |
221 | TimeZone::getCanonicalID(*tzid, canonicalID, status); | |
222 | if (U_FAILURE(status)) { | |
223 | // Uknown ID - we should not get here | |
224 | errln((UnicodeString)"Unknown ID " + *tzid); | |
225 | status = U_ZERO_ERROR; | |
226 | } else if (outtzid != canonicalID) { | |
227 | if (outtzid.compare(ETC_UNKNOWN, -1) == 0) { | |
228 | // Note that some zones like Asia/Riyadh87 does not have | |
229 | // short zone ID and "unk" is used as fallback | |
230 | logln((UnicodeString)"Canonical round trip failed (probably as expected); tz=" + *tzid | |
231 | + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx] | |
232 | + ", time=" + DATES[datidx] + ", str=" + tzstr | |
233 | + ", outtz=" + outtzid); | |
234 | } else { | |
235 | errln((UnicodeString)"Canonical round trip failed; tz=" + *tzid | |
236 | + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx] | |
237 | + ", time=" + DATES[datidx] + ", str=" + tzstr | |
238 | + ", outtz=" + outtzid); | |
239 | } | |
240 | } | |
241 | } else if (uprv_strcmp(PATTERNS[patidx], "VV") == 0) { | |
242 | // Zone ID - full roundtrip support | |
243 | if (outtzid != *tzid) { | |
244 | errln((UnicodeString)"Zone ID round trip failued; tz=" + *tzid | |
245 | + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx] | |
246 | + ", time=" + DATES[datidx] + ", str=" + tzstr | |
247 | + ", outtz=" + outtzid); | |
248 | } | |
249 | } else if (uprv_strcmp(PATTERNS[patidx], "VVV") == 0 || uprv_strcmp(PATTERNS[patidx], "VVVV") == 0) { | |
729e4ab9 A |
250 | // Location: time zone rule must be preserved except |
251 | // zones not actually associated with a specific location. | |
252 | // Time zones in this category do not have "/" in its ID. | |
46f4442e A |
253 | UnicodeString canonical; |
254 | TimeZone::getCanonicalID(*tzid, canonical, status); | |
255 | if (U_FAILURE(status)) { | |
256 | // Uknown ID - we should not get here | |
257 | errln((UnicodeString)"Unknown ID " + *tzid); | |
258 | status = U_ZERO_ERROR; | |
259 | } else if (outtzid != canonical) { | |
260 | // Canonical ID did not match - check the rules | |
261 | if (!((BasicTimeZone*)&outtz)->hasEquivalentTransitions((BasicTimeZone&)*tz, low, high, TRUE, status)) { | |
729e4ab9 A |
262 | if (canonical.indexOf((UChar)0x27 /*'/'*/) == -1) { |
263 | // Exceptional cases, such as CET, EET, MET and WET | |
51004dcb | 264 | logln((UnicodeString)"Canonical round trip failed (as expected); tz=" + *tzid |
729e4ab9 A |
265 | + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx] |
266 | + ", time=" + DATES[datidx] + ", str=" + tzstr | |
267 | + ", outtz=" + outtzid); | |
268 | } else { | |
51004dcb | 269 | errln((UnicodeString)"Canonical round trip failed; tz=" + *tzid |
729e4ab9 A |
270 | + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx] |
271 | + ", time=" + DATES[datidx] + ", str=" + tzstr | |
272 | + ", outtz=" + outtzid); | |
273 | } | |
274 | if (U_FAILURE(status)) { | |
275 | errln("hasEquivalentTransitions failed"); | |
276 | status = U_ZERO_ERROR; | |
277 | } | |
278 | } | |
279 | } | |
280 | ||
281 | } else { | |
51004dcb A |
282 | UBool isOffsetFormat = (*PATTERNS[patidx] == 'Z' |
283 | || *PATTERNS[patidx] == 'O' | |
284 | || *PATTERNS[patidx] == 'X' | |
285 | || *PATTERNS[patidx] == 'x'); | |
286 | UBool minutesOffset = FALSE; | |
287 | if (*PATTERNS[patidx] == 'X' || *PATTERNS[patidx] == 'x') { | |
288 | minutesOffset = (uprv_strlen(PATTERNS[patidx]) <= 3); | |
289 | } | |
290 | ||
4388f060 A |
291 | if (!isOffsetFormat) { |
292 | // Check if localized GMT format is used as a fallback of name styles | |
293 | int32_t numDigits = 0; | |
294 | for (int n = 0; n < tzstr.length(); n++) { | |
295 | if (u_isdigit(tzstr.charAt(n))) { | |
296 | numDigits++; | |
297 | } | |
729e4ab9 | 298 | } |
51004dcb | 299 | isOffsetFormat = (numDigits > 0); |
729e4ab9 | 300 | } |
4388f060 | 301 | if (isOffsetFormat || tzstr == localGMTString) { |
51004dcb | 302 | // Localized GMT or ISO: total offset (raw + dst) must be preserved. |
729e4ab9 A |
303 | int32_t inOffset = inRaw + inDst; |
304 | int32_t outOffset = outRaw + outDst; | |
51004dcb A |
305 | int32_t diff = outOffset - inOffset; |
306 | if (minutesOffset) { | |
307 | diff = (diff / 60000) * 60000; | |
308 | } | |
309 | if (diff != 0) { | |
729e4ab9 | 310 | errln((UnicodeString)"Offset round trip failed; tz=" + *tzid |
46f4442e A |
311 | + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx] |
312 | + ", time=" + DATES[datidx] + ", str=" + tzstr | |
729e4ab9 | 313 | + ", inOffset=" + inOffset + ", outOffset=" + outOffset); |
46f4442e | 314 | } |
729e4ab9 A |
315 | } else { |
316 | // Specific or generic: raw offset must be preserved. | |
317 | if (inRaw != outRaw) { | |
318 | errln((UnicodeString)"Raw offset round trip failed; tz=" + *tzid | |
319 | + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx] | |
320 | + ", time=" + DATES[datidx] + ", str=" + tzstr | |
321 | + ", inRawOffset=" + inRaw + ", outRawOffset=" + outRaw); | |
46f4442e A |
322 | } |
323 | } | |
324 | } | |
325 | delete outcal; | |
326 | } | |
327 | delete tz; | |
328 | } | |
329 | delete sdf; | |
330 | } | |
331 | } | |
332 | delete cal; | |
333 | delete tzids; | |
334 | } | |
335 | ||
729e4ab9 A |
336 | struct LocaleData { |
337 | int32_t index; | |
338 | int32_t testCounts; | |
339 | UDate *times; | |
340 | const Locale* locales; // Static | |
341 | int32_t nLocales; // Static | |
342 | UBool quick; // Static | |
343 | UDate START_TIME; // Static | |
344 | UDate END_TIME; // Static | |
345 | int32_t numDone; | |
346 | }; | |
347 | ||
348 | class TestTimeRoundTripThread: public SimpleThread { | |
349 | public: | |
350 | TestTimeRoundTripThread(IntlTest& tlog, LocaleData &ld, int32_t i) | |
351 | : log(tlog), data(ld), index(i) {} | |
352 | virtual void run() { | |
353 | UErrorCode status = U_ZERO_ERROR; | |
354 | UBool REALLY_VERBOSE = FALSE; | |
355 | ||
51004dcb A |
356 | // These patterns are ambiguous at DST->STD local time overlap |
357 | const char* AMBIGUOUS_DST_DECESSION[] = { "v", "vvvv", "V", "VV", "VVV", "VVVV", 0 }; | |
358 | ||
359 | // These patterns are ambiguous at STD->STD/DST->DST local time overlap | |
360 | const char* AMBIGUOUS_NEGATIVE_SHIFT[] = { "z", "zzzz", "v", "vvvv", "V", "VV", "VVV", "VVVV", 0 }; | |
361 | ||
362 | // These patterns only support integer minutes offset | |
363 | const char* MINUTES_OFFSET[] = { "X", "XX", "XXX", "x", "xx", "xxx", 0 }; | |
729e4ab9 A |
364 | |
365 | // Workaround for #6338 | |
366 | //UnicodeString BASEPATTERN("yyyy-MM-dd'T'HH:mm:ss.SSS"); | |
367 | UnicodeString BASEPATTERN("yyyy.MM.dd HH:mm:ss.SSS"); | |
368 | ||
369 | // timer for performance analysis | |
370 | UDate timer; | |
371 | UDate testTimes[4]; | |
372 | UBool expectedRoundTrip[4]; | |
373 | int32_t testLen = 0; | |
374 | ||
4388f060 | 375 | StringEnumeration *tzids = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL, NULL, NULL, status); |
729e4ab9 | 376 | if (U_FAILURE(status)) { |
4388f060 A |
377 | if (status == U_MISSING_RESOURCE_ERROR) { |
378 | /* This error is generally caused by data not being present. However, an infinite loop will occur | |
379 | * because the thread thinks that the test data is never done so we should treat the data as done. | |
380 | */ | |
381 | log.dataerrln("TimeZone::createTimeZoneIDEnumeration failed - %s", u_errorName(status)); | |
382 | data.numDone = data.nLocales; | |
383 | } else { | |
384 | log.errln("TimeZone::createTimeZoneIDEnumeration failed: %s", u_errorName(status)); | |
385 | } | |
729e4ab9 A |
386 | return; |
387 | } | |
388 | ||
389 | int32_t locidx = -1; | |
390 | UDate times[NUM_PATTERNS]; | |
391 | for (int32_t i = 0; i < NUM_PATTERNS; i++) { | |
392 | times[i] = 0; | |
393 | } | |
394 | ||
395 | int32_t testCounts = 0; | |
396 | ||
397 | while (true) { | |
398 | umtx_lock(NULL); // Lock to increment the index | |
399 | for (int32_t i = 0; i < NUM_PATTERNS; i++) { | |
400 | data.times[i] += times[i]; | |
401 | data.testCounts += testCounts; | |
402 | } | |
403 | if (data.index < data.nLocales) { | |
404 | locidx = data.index; | |
405 | data.index++; | |
406 | } else { | |
407 | locidx = -1; | |
408 | } | |
409 | umtx_unlock(NULL); // Unlock for other threads to use | |
410 | ||
411 | if (locidx == -1) { | |
412 | log.logln((UnicodeString) "Thread " + index + " is done."); | |
413 | break; | |
414 | } | |
415 | ||
416 | log.logln((UnicodeString) "\nThread " + index + ": Locale: " + UnicodeString(data.locales[locidx].getName())); | |
417 | ||
418 | for (int32_t patidx = 0; patidx < NUM_PATTERNS; patidx++) { | |
419 | log.logln((UnicodeString) " Pattern: " + PATTERNS[patidx]); | |
420 | times[patidx] = 0; | |
421 | ||
422 | UnicodeString pattern(BASEPATTERN); | |
423 | pattern.append(" ").append(PATTERNS[patidx]); | |
424 | ||
425 | SimpleDateFormat *sdf = new SimpleDateFormat(pattern, data.locales[locidx], status); | |
426 | if (U_FAILURE(status)) { | |
427 | log.errcheckln(status, (UnicodeString) "new SimpleDateFormat failed for pattern " + | |
428 | pattern + " for locale " + data.locales[locidx].getName() + " - " + u_errorName(status)); | |
429 | status = U_ZERO_ERROR; | |
430 | continue; | |
431 | } | |
432 | ||
51004dcb A |
433 | UBool minutesOffset = contains(MINUTES_OFFSET, PATTERNS[patidx]); |
434 | ||
729e4ab9 A |
435 | tzids->reset(status); |
436 | const UnicodeString *tzid; | |
437 | ||
438 | timer = Calendar::getNow(); | |
439 | ||
440 | while ((tzid = tzids->snext(status))) { | |
51004dcb A |
441 | if (uprv_strcmp(PATTERNS[patidx], "V") == 0) { |
442 | // Some zones do not have short ID assigned, such as Asia/Riyadh87. | |
443 | // The time roundtrip will fail for such zones with pattern "V" (short zone ID). | |
444 | // This is expected behavior. | |
445 | const UChar* shortZoneID = ZoneMeta::getShortID(*tzid); | |
446 | if (shortZoneID == NULL) { | |
447 | continue; | |
448 | } | |
449 | } else if (uprv_strcmp(PATTERNS[patidx], "VVV") == 0) { | |
450 | // Some zones are not associated with any region, such as Etc/GMT+8. | |
451 | // The time roundtrip will fail for such zone with pattern "VVV" (exemplar location). | |
452 | // This is expected behavior. | |
453 | if (tzid->indexOf((UChar)0x2F) < 0 || tzid->indexOf(ETC_SLASH, -1, 0) >= 0 | |
454 | || tzid->indexOf(SYSTEMV_SLASH, -1, 0) >= 0 || tzid->indexOf(RIYADH8, -1, 0) >= 0) { | |
455 | continue; | |
456 | } | |
457 | } | |
458 | ||
729e4ab9 A |
459 | BasicTimeZone *tz = (BasicTimeZone*) TimeZone::createTimeZone(*tzid); |
460 | sdf->setTimeZone(*tz); | |
461 | ||
462 | UDate t = data.START_TIME; | |
463 | TimeZoneTransition tzt; | |
464 | UBool tztAvail = FALSE; | |
465 | UBool middle = TRUE; | |
466 | ||
467 | while (t < data.END_TIME) { | |
468 | if (!tztAvail) { | |
469 | testTimes[0] = t; | |
470 | expectedRoundTrip[0] = TRUE; | |
471 | testLen = 1; | |
472 | } else { | |
473 | int32_t fromOffset = tzt.getFrom()->getRawOffset() + tzt.getFrom()->getDSTSavings(); | |
474 | int32_t toOffset = tzt.getTo()->getRawOffset() + tzt.getTo()->getDSTSavings(); | |
475 | int32_t delta = toOffset - fromOffset; | |
476 | if (delta < 0) { | |
477 | UBool isDstDecession = tzt.getFrom()->getDSTSavings() > 0 && tzt.getTo()->getDSTSavings() == 0; | |
478 | testTimes[0] = t + delta - 1; | |
479 | expectedRoundTrip[0] = TRUE; | |
480 | testTimes[1] = t + delta; | |
51004dcb A |
481 | expectedRoundTrip[1] = isDstDecession ? |
482 | !contains(AMBIGUOUS_DST_DECESSION, PATTERNS[patidx]) : | |
483 | !contains(AMBIGUOUS_NEGATIVE_SHIFT, PATTERNS[patidx]); | |
729e4ab9 | 484 | testTimes[2] = t - 1; |
51004dcb A |
485 | expectedRoundTrip[2] = isDstDecession ? |
486 | !contains(AMBIGUOUS_DST_DECESSION, PATTERNS[patidx]) : | |
487 | !contains(AMBIGUOUS_NEGATIVE_SHIFT, PATTERNS[patidx]); | |
729e4ab9 A |
488 | testTimes[3] = t; |
489 | expectedRoundTrip[3] = TRUE; | |
490 | testLen = 4; | |
491 | } else { | |
492 | testTimes[0] = t - 1; | |
493 | expectedRoundTrip[0] = TRUE; | |
494 | testTimes[1] = t; | |
495 | expectedRoundTrip[1] = TRUE; | |
496 | testLen = 2; | |
497 | } | |
498 | } | |
499 | for (int32_t testidx = 0; testidx < testLen; testidx++) { | |
500 | if (data.quick) { | |
501 | // reduce regular test time | |
502 | if (!expectedRoundTrip[testidx]) { | |
503 | continue; | |
504 | } | |
505 | } | |
506 | ||
507 | testCounts++; | |
508 | ||
509 | UnicodeString text; | |
510 | FieldPosition fpos(0); | |
511 | sdf->format(testTimes[testidx], text, fpos); | |
512 | ||
513 | UDate parsedDate = sdf->parse(text, status); | |
514 | if (U_FAILURE(status)) { | |
515 | log.errln((UnicodeString) "Parse failure for text=" + text + ", tzid=" + *tzid + ", locale=" + data.locales[locidx].getName() | |
516 | + ", pattern=" + PATTERNS[patidx] + ", time=" + testTimes[testidx]); | |
517 | status = U_ZERO_ERROR; | |
518 | continue; | |
519 | } | |
51004dcb A |
520 | |
521 | int32_t timeDiff = (int32_t)(parsedDate - testTimes[testidx]); | |
522 | UBool bTimeMatch = minutesOffset ? | |
523 | (timeDiff/60000)*60000 == 0 : timeDiff == 0; | |
524 | if (!bTimeMatch) { | |
729e4ab9 A |
525 | UnicodeString msg = (UnicodeString) "Time round trip failed for " + "tzid=" + *tzid + ", locale=" + data.locales[locidx].getName() + ", pattern=" + PATTERNS[patidx] |
526 | + ", text=" + text + ", time=" + testTimes[testidx] + ", restime=" + parsedDate + ", diff=" + (parsedDate - testTimes[testidx]); | |
4388f060 | 527 | // Timebomb for TZData update |
729e4ab9 A |
528 | if (expectedRoundTrip[testidx]) { |
529 | log.errln((UnicodeString) "FAIL: " + msg); | |
530 | } else if (REALLY_VERBOSE) { | |
531 | log.logln(msg); | |
532 | } | |
533 | } | |
534 | } | |
535 | tztAvail = tz->getNextTransition(t, FALSE, tzt); | |
536 | if (!tztAvail) { | |
537 | break; | |
538 | } | |
539 | if (middle) { | |
540 | // Test the date in the middle of two transitions. | |
541 | t += (int64_t) ((tzt.getTime() - t) / 2); | |
542 | middle = FALSE; | |
543 | tztAvail = FALSE; | |
544 | } else { | |
545 | t = tzt.getTime(); | |
546 | } | |
547 | } | |
548 | delete tz; | |
549 | } | |
550 | times[patidx] += (Calendar::getNow() - timer); | |
551 | delete sdf; | |
552 | } | |
553 | umtx_lock(NULL); | |
554 | data.numDone++; | |
555 | umtx_unlock(NULL); | |
556 | } | |
557 | delete tzids; | |
558 | } | |
559 | private: | |
560 | IntlTest& log; | |
561 | LocaleData& data; | |
562 | int32_t index; | |
563 | }; | |
564 | ||
46f4442e A |
565 | void |
566 | TimeZoneFormatTest::TestTimeRoundTrip(void) { | |
729e4ab9 A |
567 | int32_t nThreads = threadCount; |
568 | const Locale *LOCALES; | |
569 | int32_t nLocales; | |
570 | int32_t testCounts = 0; | |
46f4442e | 571 | |
729e4ab9 A |
572 | UErrorCode status = U_ZERO_ERROR; |
573 | Calendar *cal = Calendar::createInstance(TimeZone::createTimeZone((UnicodeString) "UTC"), status); | |
46f4442e | 574 | if (U_FAILURE(status)) { |
729e4ab9 | 575 | dataerrln("Calendar::createInstance failed: %s", u_errorName(status)); |
46f4442e A |
576 | return; |
577 | } | |
578 | ||
729e4ab9 A |
579 | const char* testAllProp = getProperty("TimeZoneRoundTripAll"); |
580 | UBool bTestAll = (testAllProp && uprv_strcmp(testAllProp, "true") == 0); | |
46f4442e | 581 | |
729e4ab9 A |
582 | UDate START_TIME, END_TIME; |
583 | if (bTestAll || !quick) { | |
46f4442e A |
584 | cal->set(1900, UCAL_JANUARY, 1); |
585 | } else { | |
729e4ab9 | 586 | cal->set(1990, UCAL_JANUARY, 1); |
46f4442e A |
587 | } |
588 | START_TIME = cal->getTime(status); | |
589 | ||
590 | cal->set(2015, UCAL_JANUARY, 1); | |
591 | END_TIME = cal->getTime(status); | |
729e4ab9 | 592 | |
46f4442e A |
593 | if (U_FAILURE(status)) { |
594 | errln("getTime failed"); | |
595 | return; | |
596 | } | |
597 | ||
46f4442e A |
598 | UDate times[NUM_PATTERNS]; |
599 | for (int32_t i = 0; i < NUM_PATTERNS; i++) { | |
600 | times[i] = 0; | |
601 | } | |
602 | ||
46f4442e | 603 | // Set up test locales |
729e4ab9 | 604 | const Locale locales1[] = {Locale("en")}; |
46f4442e | 605 | const Locale locales2[] = { |
729e4ab9 A |
606 | Locale("ar_EG"), Locale("bg_BG"), Locale("ca_ES"), Locale("da_DK"), Locale("de"), |
607 | Locale("de_DE"), Locale("el_GR"), Locale("en"), Locale("en_AU"), Locale("en_CA"), | |
608 | Locale("en_US"), Locale("es"), Locale("es_ES"), Locale("es_MX"), Locale("fi_FI"), | |
609 | Locale("fr"), Locale("fr_CA"), Locale("fr_FR"), Locale("he_IL"), Locale("hu_HU"), | |
610 | Locale("it"), Locale("it_IT"), Locale("ja"), Locale("ja_JP"), Locale("ko"), | |
611 | Locale("ko_KR"), Locale("nb_NO"), Locale("nl_NL"), Locale("nn_NO"), Locale("pl_PL"), | |
612 | Locale("pt"), Locale("pt_BR"), Locale("pt_PT"), Locale("ru_RU"), Locale("sv_SE"), | |
613 | Locale("th_TH"), Locale("tr_TR"), Locale("zh"), Locale("zh_Hans"), Locale("zh_Hans_CN"), | |
614 | Locale("zh_Hant"), Locale("zh_Hant_TW") | |
46f4442e A |
615 | }; |
616 | ||
729e4ab9 | 617 | if (bTestAll) { |
46f4442e A |
618 | LOCALES = Locale::getAvailableLocales(nLocales); |
619 | } else if (quick) { | |
620 | LOCALES = locales1; | |
621 | nLocales = sizeof(locales1)/sizeof(Locale); | |
622 | } else { | |
623 | LOCALES = locales2; | |
624 | nLocales = sizeof(locales2)/sizeof(Locale); | |
625 | } | |
626 | ||
729e4ab9 A |
627 | LocaleData data; |
628 | data.index = 0; | |
629 | data.testCounts = testCounts; | |
630 | data.times = times; | |
631 | data.locales = LOCALES; | |
632 | data.nLocales = nLocales; | |
633 | data.quick = quick; | |
634 | data.START_TIME = START_TIME; | |
635 | data.END_TIME = END_TIME; | |
636 | data.numDone = 0; | |
637 | ||
638 | #if (ICU_USE_THREADS==0) | |
639 | TestTimeRoundTripThread fakeThread(*this, data, 0); | |
640 | fakeThread.run(); | |
641 | #else | |
642 | TestTimeRoundTripThread **threads = new TestTimeRoundTripThread*[threadCount]; | |
643 | int32_t i; | |
644 | for (i = 0; i < nThreads; i++) { | |
645 | threads[i] = new TestTimeRoundTripThread(*this, data, i); | |
646 | if (threads[i]->start() != 0) { | |
647 | errln("Error starting thread %d", i); | |
648 | } | |
46f4442e A |
649 | } |
650 | ||
729e4ab9 A |
651 | UBool done = false; |
652 | while (true) { | |
653 | umtx_lock(NULL); | |
654 | if (data.numDone == nLocales) { | |
655 | done = true; | |
46f4442e | 656 | } |
729e4ab9 A |
657 | umtx_unlock(NULL); |
658 | if (done) | |
659 | break; | |
660 | SimpleThread::sleep(1000); | |
46f4442e | 661 | } |
729e4ab9 A |
662 | |
663 | for (i = 0; i < nThreads; i++) { | |
664 | delete threads[i]; | |
665 | } | |
666 | delete [] threads; | |
667 | ||
668 | #endif | |
46f4442e A |
669 | UDate total = 0; |
670 | logln("### Elapsed time by patterns ###"); | |
671 | for (int32_t i = 0; i < NUM_PATTERNS; i++) { | |
729e4ab9 A |
672 | logln(UnicodeString("") + data.times[i] + "ms (" + PATTERNS[i] + ")"); |
673 | total += data.times[i]; | |
46f4442e | 674 | } |
729e4ab9 A |
675 | logln((UnicodeString) "Total: " + total + "ms"); |
676 | logln((UnicodeString) "Iteration: " + data.testCounts); | |
46f4442e A |
677 | |
678 | delete cal; | |
46f4442e A |
679 | } |
680 | ||
51004dcb A |
681 | |
682 | typedef struct { | |
683 | const char* text; | |
684 | int32_t inPos; | |
685 | const char* locale; | |
686 | UTimeZoneFormatStyle style; | |
687 | UBool parseAll; | |
688 | const char* expected; | |
689 | int32_t outPos; | |
690 | UTimeZoneFormatTimeType timeType; | |
691 | } ParseTestData; | |
692 | ||
693 | void | |
694 | TimeZoneFormatTest::TestParse(void) { | |
695 | const ParseTestData DATA[] = { | |
696 | // text inPos locale style parseAll expected outPos timeType | |
697 | {"Z", 0, "en_US", UTZFMT_STYLE_ISO_EXTENDED_FULL, false, "Etc/GMT", 1, UTZFMT_TIME_TYPE_UNKNOWN}, | |
698 | {"Z", 0, "en_US", UTZFMT_STYLE_SPECIFIC_LONG, false, "Etc/GMT", 1, UTZFMT_TIME_TYPE_UNKNOWN}, | |
699 | {"Zambia time", 0, "en_US", UTZFMT_STYLE_ISO_EXTENDED_FULL, true, "Etc/GMT", 1, UTZFMT_TIME_TYPE_UNKNOWN}, | |
700 | {"Zambia time", 0, "en_US", UTZFMT_STYLE_GENERIC_LOCATION, false, "Africa/Lusaka", 11, UTZFMT_TIME_TYPE_UNKNOWN}, | |
701 | {"Zambia time", 0, "en_US", UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL, true, "Africa/Lusaka", 11, UTZFMT_TIME_TYPE_UNKNOWN}, | |
702 | {"+00:00", 0, "en_US", UTZFMT_STYLE_ISO_EXTENDED_FULL, false, "Etc/GMT", 6, UTZFMT_TIME_TYPE_UNKNOWN}, | |
703 | {"-01:30:45", 0, "en_US", UTZFMT_STYLE_ISO_EXTENDED_FULL, false, "GMT-01:30:45", 9, UTZFMT_TIME_TYPE_UNKNOWN}, | |
704 | {"-7", 0, "en_US", UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL, false, "GMT-07:00", 2, UTZFMT_TIME_TYPE_UNKNOWN}, | |
705 | {"-2222", 0, "en_US", UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL, false, "GMT-22:22", 5, UTZFMT_TIME_TYPE_UNKNOWN}, | |
706 | {"-3333", 0, "en_US", UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL, false, "GMT-03:33", 4, UTZFMT_TIME_TYPE_UNKNOWN}, | |
707 | {"XXX+01:30YYY", 3, "en_US", UTZFMT_STYLE_LOCALIZED_GMT, false, "GMT+01:30", 9, UTZFMT_TIME_TYPE_UNKNOWN}, | |
708 | {"GMT0", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT, false, "Etc/GMT", 3, UTZFMT_TIME_TYPE_UNKNOWN}, | |
709 | {"EST", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT, false, "America/New_York", 3, UTZFMT_TIME_TYPE_STANDARD}, | |
710 | {"ESTx", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT, false, "America/New_York", 3, UTZFMT_TIME_TYPE_STANDARD}, | |
711 | {"EDTx", 0, "en_US", UTZFMT_STYLE_SPECIFIC_SHORT, false, "America/New_York", 3, UTZFMT_TIME_TYPE_DAYLIGHT}, | |
712 | {"EST", 0, "en_US", UTZFMT_STYLE_SPECIFIC_LONG, false, NULL, 0, UTZFMT_TIME_TYPE_UNKNOWN}, | |
713 | {"EST", 0, "en_US", UTZFMT_STYLE_SPECIFIC_LONG, true, "America/New_York", 3, UTZFMT_TIME_TYPE_STANDARD}, | |
714 | {"EST", 0, "en_CA", UTZFMT_STYLE_SPECIFIC_SHORT, false, "America/Toronto", 3, UTZFMT_TIME_TYPE_STANDARD}, | |
715 | {NULL, 0, NULL, UTZFMT_STYLE_GENERIC_LOCATION, false, NULL, 0, UTZFMT_TIME_TYPE_UNKNOWN} | |
716 | }; | |
717 | ||
718 | for (int32_t i = 0; DATA[i].text; i++) { | |
719 | UErrorCode status = U_ZERO_ERROR; | |
720 | LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(Locale(DATA[i].locale), status)); | |
721 | if (U_FAILURE(status)) { | |
722 | dataerrln("Fail TimeZoneFormat::createInstance: %s", u_errorName(status)); | |
723 | continue; | |
724 | } | |
725 | UTimeZoneFormatTimeType ttype = UTZFMT_TIME_TYPE_UNKNOWN; | |
726 | ParsePosition pos(DATA[i].inPos); | |
727 | int32_t parseOptions = DATA[i].parseAll ? UTZFMT_PARSE_OPTION_ALL_STYLES : UTZFMT_PARSE_OPTION_NONE; | |
728 | TimeZone* tz = tzfmt->parse(DATA[i].style, DATA[i].text, pos, parseOptions, &ttype); | |
729 | ||
730 | UnicodeString errMsg; | |
731 | if (tz) { | |
732 | UnicodeString outID; | |
733 | tz->getID(outID); | |
734 | if (outID != UnicodeString(DATA[i].expected)) { | |
735 | errMsg = (UnicodeString)"Time zone ID: " + outID + " - expected: " + DATA[i].expected; | |
736 | } else if (pos.getIndex() != DATA[i].outPos) { | |
737 | errMsg = (UnicodeString)"Parsed pos: " + pos.getIndex() + " - expected: " + DATA[i].outPos; | |
738 | } else if (ttype != DATA[i].timeType) { | |
739 | errMsg = (UnicodeString)"Time type: " + ttype + " - expected: " + DATA[i].timeType; | |
740 | } | |
741 | delete tz; | |
742 | } else { | |
743 | if (DATA[i].expected) { | |
744 | errln((UnicodeString)"Fail: Parse failure - expected: " + DATA[i].expected); | |
745 | } | |
746 | } | |
747 | if (errMsg.length() > 0) { | |
748 | errln((UnicodeString)"Fail: " + errMsg + " [text=" + DATA[i].text + ", pos=" + DATA[i].inPos + ", style=" + DATA[i].style + "]"); | |
749 | } | |
750 | } | |
751 | } | |
752 | ||
753 | void | |
754 | TimeZoneFormatTest::TestISOFormat(void) { | |
755 | const int32_t OFFSET[] = { | |
756 | 0, // 0 | |
757 | 999, // 0.999s | |
758 | -59999, // -59.999s | |
759 | 60000, // 1m | |
760 | -77777, // -1m 17.777s | |
761 | 1800000, // 30m | |
762 | -3600000, // -1h | |
763 | 36000000, // 10h | |
764 | -37800000, // -10h 30m | |
765 | -37845000, // -10h 30m 45s | |
766 | 108000000, // 30h | |
767 | }; | |
768 | ||
769 | const char* ISO_STR[][11] = { | |
770 | // 0 | |
771 | { | |
772 | "Z", "Z", "Z", "Z", "Z", | |
773 | "+00", "+0000", "+00:00", "+0000", "+00:00", | |
774 | "+0000" | |
775 | }, | |
776 | // 999 | |
777 | { | |
778 | "Z", "Z", "Z", "Z", "Z", | |
779 | "+00", "+0000", "+00:00", "+0000", "+00:00", | |
780 | "+0000" | |
781 | }, | |
782 | // -59999 | |
783 | { | |
784 | "Z", "Z", "Z", "-000059", "-00:00:59", | |
785 | "+00", "+0000", "+00:00", "-000059", "-00:00:59", | |
786 | "-000059" | |
787 | }, | |
788 | // 60000 | |
789 | { | |
790 | "+0001", "+0001", "+00:01", "+0001", "+00:01", | |
791 | "+0001", "+0001", "+00:01", "+0001", "+00:01", | |
792 | "+0001" | |
793 | }, | |
794 | // -77777 | |
795 | { | |
796 | "-0001", "-0001", "-00:01", "-000117", "-00:01:17", | |
797 | "-0001", "-0001", "-00:01", "-000117", "-00:01:17", | |
798 | "-000117" | |
799 | }, | |
800 | // 1800000 | |
801 | { | |
802 | "+0030", "+0030", "+00:30", "+0030", "+00:30", | |
803 | "+0030", "+0030", "+00:30", "+0030", "+00:30", | |
804 | "+0030" | |
805 | }, | |
806 | // -3600000 | |
807 | { | |
808 | "-01", "-0100", "-01:00", "-0100", "-01:00", | |
809 | "-01", "-0100", "-01:00", "-0100", "-01:00", | |
810 | "-0100" | |
811 | }, | |
812 | // 36000000 | |
813 | { | |
814 | "+10", "+1000", "+10:00", "+1000", "+10:00", | |
815 | "+10", "+1000", "+10:00", "+1000", "+10:00", | |
816 | "+1000" | |
817 | }, | |
818 | // -37800000 | |
819 | { | |
820 | "-1030", "-1030", "-10:30", "-1030", "-10:30", | |
821 | "-1030", "-1030", "-10:30", "-1030", "-10:30", | |
822 | "-1030" | |
823 | }, | |
824 | // -37845000 | |
825 | { | |
826 | "-1030", "-1030", "-10:30", "-103045", "-10:30:45", | |
827 | "-1030", "-1030", "-10:30", "-103045", "-10:30:45", | |
828 | "-103045" | |
829 | }, | |
830 | // 108000000 | |
831 | { | |
832 | 0, 0, 0, 0, 0, | |
833 | 0, 0, 0, 0, 0, | |
834 | 0 | |
835 | } | |
836 | }; | |
837 | ||
838 | const char* PATTERN[] = { | |
839 | "X", "XX", "XXX", "XXXX", "XXXXX", | |
840 | "x", "xx", "xxx", "xxxx", "xxxxx", | |
841 | "Z", // equivalent to "xxxx" | |
842 | 0 | |
843 | }; | |
844 | ||
845 | const int32_t MIN_OFFSET_UNIT[] = { | |
846 | 60000, 60000, 60000, 1000, 1000, | |
847 | 60000, 60000, 60000, 1000, 1000, | |
848 | 1000, | |
849 | }; | |
850 | ||
851 | // Formatting | |
852 | UErrorCode status = U_ZERO_ERROR; | |
853 | LocalPointer<SimpleDateFormat> sdf(new SimpleDateFormat(status)); | |
854 | if (U_FAILURE(status)) { | |
855 | dataerrln("Fail new SimpleDateFormat: %s", u_errorName(status)); | |
856 | return; | |
857 | } | |
858 | UDate d = Calendar::getNow(); | |
859 | ||
860 | for (uint32_t i = 0; i < sizeof(OFFSET)/sizeof(OFFSET[0]); i++) { | |
861 | SimpleTimeZone* tz = new SimpleTimeZone(OFFSET[i], UnicodeString("Zone Offset:") + OFFSET[i] + "ms"); | |
862 | sdf->adoptTimeZone(tz); | |
863 | for (int32_t j = 0; PATTERN[j] != 0; j++) { | |
864 | sdf->applyPattern(UnicodeString(PATTERN[j])); | |
865 | UnicodeString result; | |
866 | sdf->format(d, result); | |
867 | ||
868 | if (ISO_STR[i][j]) { | |
869 | if (result != UnicodeString(ISO_STR[i][j])) { | |
870 | errln((UnicodeString)"FAIL: pattern=" + PATTERN[j] + ", offset=" + OFFSET[i] + " -> " | |
871 | + result + " (expected: " + ISO_STR[i][j] + ")"); | |
872 | } | |
873 | } else { | |
874 | // Offset out of range | |
875 | // Note: for now, there is no way to propagate the error status through | |
876 | // the SimpleDateFormat::format above. | |
877 | if (result.length() > 0) { | |
878 | errln((UnicodeString)"FAIL: Non-Empty result for pattern=" + PATTERN[j] + ", offset=" + OFFSET[i] | |
879 | + " (expected: empty result)"); | |
880 | } | |
881 | } | |
882 | } | |
883 | } | |
884 | ||
885 | // Parsing | |
886 | LocalPointer<Calendar> outcal(Calendar::createInstance(status)); | |
887 | if (U_FAILURE(status)) { | |
888 | dataerrln("Fail new Calendar: %s", u_errorName(status)); | |
889 | return; | |
890 | } | |
891 | for (int32_t i = 0; ISO_STR[i][0] != NULL; i++) { | |
892 | for (int32_t j = 0; PATTERN[j] != 0; j++) { | |
893 | if (ISO_STR[i][j] == 0) { | |
894 | continue; | |
895 | } | |
896 | ParsePosition pos(0); | |
897 | SimpleTimeZone* bogusTZ = new SimpleTimeZone(-1, UnicodeString("Zone Offset: -1ms")); | |
898 | outcal->adoptTimeZone(bogusTZ); | |
899 | sdf->applyPattern(PATTERN[j]); | |
900 | ||
901 | sdf->parse(UnicodeString(ISO_STR[i][j]), *(outcal.getAlias()), pos); | |
902 | ||
903 | if (pos.getIndex() != (int32_t)uprv_strlen(ISO_STR[i][j])) { | |
904 | errln((UnicodeString)"FAIL: Failed to parse the entire input string: " + ISO_STR[i][j]); | |
905 | } | |
906 | ||
907 | const TimeZone& outtz = outcal->getTimeZone(); | |
908 | int32_t outOffset = outtz.getRawOffset(); | |
909 | int32_t adjustedOffset = OFFSET[i] / MIN_OFFSET_UNIT[j] * MIN_OFFSET_UNIT[j]; | |
910 | if (outOffset != adjustedOffset) { | |
911 | errln((UnicodeString)"FAIL: Incorrect offset:" + outOffset + "ms for input string: " + ISO_STR[i][j] | |
912 | + " (expected:" + adjustedOffset + "ms)"); | |
913 | } | |
914 | } | |
915 | } | |
916 | } | |
917 | ||
918 | ||
46f4442e | 919 | #endif /* #if !UCONFIG_NO_FORMATTING */ |