OLD | NEW |
| (Empty) |
1 /*********************************************************************** | |
2 * COPYRIGHT: | |
3 * Copyright (c) 1997-2013, International Business Machines Corporation | |
4 * and others. All Rights Reserved. | |
5 ***********************************************************************/ | |
6 | |
7 #include "unicode/utypes.h" | |
8 | |
9 #if !UCONFIG_NO_FORMATTING | |
10 | |
11 #include "callimts.h" | |
12 #include "caltest.h" | |
13 #include "unicode/calendar.h" | |
14 #include "unicode/gregocal.h" | |
15 #include "unicode/datefmt.h" | |
16 #include "unicode/smpdtfmt.h" | |
17 #include "putilimp.h" | |
18 #include "cstring.h" | |
19 | |
20 U_NAMESPACE_USE | |
21 void CalendarLimitTest::runIndexedTest( int32_t index, UBool exec, const char* &
name, char* /*par*/ ) | |
22 { | |
23 if (exec) logln("TestSuite TestCalendarLimit"); | |
24 switch (index) { | |
25 // Re-enable this later | |
26 case 0: | |
27 name = "TestCalendarExtremeLimit"; | |
28 if (exec) { | |
29 logln("TestCalendarExtremeLimit---"); logln(""); | |
30 TestCalendarExtremeLimit(); | |
31 } | |
32 break; | |
33 case 1: | |
34 name = "TestLimits"; | |
35 if (exec) { | |
36 logln("TestLimits---"); logln(""); | |
37 TestLimits(); | |
38 } | |
39 break; | |
40 | |
41 default: name = ""; break; | |
42 } | |
43 } | |
44 | |
45 | |
46 // ***************************************************************************** | |
47 // class CalendarLimitTest | |
48 // ***************************************************************************** | |
49 | |
50 // ------------------------------------- | |
51 void | |
52 CalendarLimitTest::test(UDate millis, icu::Calendar* cal, icu::DateFormat* fmt) | |
53 { | |
54 static const UDate kDrift = 1e-10; | |
55 UErrorCode exception = U_ZERO_ERROR; | |
56 UnicodeString theDate; | |
57 UErrorCode status = U_ZERO_ERROR; | |
58 cal->setTime(millis, exception); | |
59 if (U_SUCCESS(exception)) { | |
60 fmt->format(millis, theDate); | |
61 UDate dt = fmt->parse(theDate, status); | |
62 // allow a small amount of error (drift) | |
63 if(! withinErr(dt, millis, kDrift)) { | |
64 errln("FAIL:round trip for large milli, got: %.1lf wanted: %.1lf. (del
ta %.2lf greater than %.2lf)", | |
65 dt, millis, uprv_fabs(millis-dt), uprv_fabs(dt*kDrift)); | |
66 logln(UnicodeString(" ") + theDate + " " + CalendarTest::calToStr(*c
al)); | |
67 } else { | |
68 logln(UnicodeString("OK: got ") + dt + ", wanted " + millis); | |
69 logln(UnicodeString(" ") + theDate); | |
70 } | |
71 } | |
72 } | |
73 | |
74 // ------------------------------------- | |
75 | |
76 // bug 986c: deprecate nextDouble/previousDouble | |
77 //|double | |
78 //|CalendarLimitTest::nextDouble(double a) | |
79 //|{ | |
80 //| return uprv_nextDouble(a, TRUE); | |
81 //|} | |
82 //| | |
83 //|double | |
84 //|CalendarLimitTest::previousDouble(double a) | |
85 //|{ | |
86 //| return uprv_nextDouble(a, FALSE); | |
87 //|} | |
88 | |
89 UBool | |
90 CalendarLimitTest::withinErr(double a, double b, double err) | |
91 { | |
92 return ( uprv_fabs(a - b) < uprv_fabs(a * err) ); | |
93 } | |
94 | |
95 void | |
96 CalendarLimitTest::TestCalendarExtremeLimit() | |
97 { | |
98 UErrorCode status = U_ZERO_ERROR; | |
99 Calendar *cal = Calendar::createInstance(status); | |
100 if (failure(status, "Calendar::createInstance", TRUE)) return; | |
101 cal->adoptTimeZone(TimeZone::createTimeZone("GMT")); | |
102 DateFormat *fmt = DateFormat::createDateTimeInstance(); | |
103 if(!fmt || !cal) { | |
104 dataerrln("can't open cal and/or fmt"); | |
105 return; | |
106 } | |
107 fmt->adoptCalendar(cal); | |
108 ((SimpleDateFormat*) fmt)->applyPattern("HH:mm:ss.SSS Z, EEEE, MMMM d, yyyy
G"); | |
109 | |
110 | |
111 // This test used to test the algorithmic limits of the dates that | |
112 // GregorianCalendar could handle. However, the algorithm has | |
113 // been rewritten completely since then and the prior limits no | |
114 // longer apply. Instead, we now do basic round-trip testing of | |
115 // some extreme (but still manageable) dates. | |
116 UDate m; | |
117 logln("checking 1e16..1e17"); | |
118 for ( m = 1e16; m < 1e17; m *= 1.1) { | |
119 test(m, cal, fmt); | |
120 } | |
121 logln("checking -1e14..-1e15"); | |
122 for ( m = -1e14; m > -1e15; m *= 1.1) { | |
123 test(m, cal, fmt); | |
124 } | |
125 | |
126 // This is 2^52 - 1, the largest allowable mantissa with a 0 | |
127 // exponent in a 64-bit double | |
128 UDate VERY_EARLY_MILLIS = - 4503599627370495.0; | |
129 UDate VERY_LATE_MILLIS = 4503599627370495.0; | |
130 | |
131 // I am removing the previousDouble and nextDouble calls below for | |
132 // two reasons: 1. As part of jitterbug 986, I am deprecating | |
133 // these methods and removing calls to them. 2. This test is a | |
134 // non-critical boundary behavior test. | |
135 test(VERY_EARLY_MILLIS, cal, fmt); | |
136 //test(previousDouble(VERY_EARLY_MILLIS), cal, fmt); | |
137 test(VERY_LATE_MILLIS, cal, fmt); | |
138 //test(nextDouble(VERY_LATE_MILLIS), cal, fmt); | |
139 delete fmt; | |
140 } | |
141 | |
142 void | |
143 CalendarLimitTest::TestLimits(void) { | |
144 static const UDate DEFAULT_START = 944006400000.0; // 1999-12-01T00:00Z | |
145 static const int32_t DEFAULT_END = -120; // Default for non-quick is run 2 m
inutes | |
146 | |
147 static const struct { | |
148 const char *type; | |
149 UBool hasLeapMonth; | |
150 UDate actualTestStart; | |
151 int32_t actualTestEnd; | |
152 } TestCases[] = { | |
153 {"gregorian", FALSE, DEFAULT_START, DEFAULT_END}, | |
154 {"japanese", FALSE, 596937600000.0, DEFAULT_END}, // 1988-12
-01T00:00Z, Showa 63 | |
155 {"buddhist", FALSE, DEFAULT_START, DEFAULT_END}, | |
156 {"roc", FALSE, DEFAULT_START, DEFAULT_END}, | |
157 {"persian", FALSE, DEFAULT_START, DEFAULT_END}, | |
158 {"islamic-civil", FALSE, DEFAULT_START, DEFAULT_END}, | |
159 {"islamic", FALSE, DEFAULT_START, 800000}, // Approx. 2250
years from now, after which some rounding errors occur in Islamic calendar | |
160 {"hebrew", TRUE, DEFAULT_START, DEFAULT_END}, | |
161 {"chinese", TRUE, DEFAULT_START, DEFAULT_END}, | |
162 {"dangi", TRUE, DEFAULT_START, DEFAULT_END}, | |
163 {"indian", FALSE, DEFAULT_START, DEFAULT_END}, | |
164 {"coptic", FALSE, DEFAULT_START, DEFAULT_END}, | |
165 {"ethiopic", FALSE, DEFAULT_START, DEFAULT_END}, | |
166 {"ethiopic-amete-alem", FALSE, DEFAULT_START, DEFAULT_END}, | |
167 {NULL, FALSE, 0, 0} | |
168 }; | |
169 | |
170 int16_t i = 0; | |
171 char buf[64]; | |
172 | |
173 for (i = 0; TestCases[i].type; i++) { | |
174 UErrorCode status = U_ZERO_ERROR; | |
175 uprv_strcpy(buf, "root@calendar="); | |
176 strcat(buf, TestCases[i].type); | |
177 Calendar *cal = Calendar::createInstance(buf, status); | |
178 if (failure(status, "Calendar::createInstance", TRUE)) { | |
179 continue; | |
180 } | |
181 if (uprv_strcmp(cal->getType(), TestCases[i].type) != 0) { | |
182 errln((UnicodeString)"FAIL: Wrong calendar type: " + cal->getType() | |
183 + " Requested: " + TestCases[i].type); | |
184 delete cal; | |
185 continue; | |
186 } | |
187 // Do the test | |
188 doTheoreticalLimitsTest(*cal, TestCases[i].hasLeapMonth); | |
189 doLimitsTest(*cal, TestCases[i].actualTestStart,TestCases[i].actualTestE
nd); | |
190 delete cal; | |
191 } | |
192 } | |
193 | |
194 void | |
195 CalendarLimitTest::doTheoreticalLimitsTest(Calendar& cal, UBool leapMonth) { | |
196 const char* calType = cal.getType(); | |
197 | |
198 int32_t nDOW = cal.getMaximum(UCAL_DAY_OF_WEEK); | |
199 int32_t maxDOY = cal.getMaximum(UCAL_DAY_OF_YEAR); | |
200 int32_t lmaxDOW = cal.getLeastMaximum(UCAL_DAY_OF_YEAR); | |
201 int32_t maxWOY = cal.getMaximum(UCAL_WEEK_OF_YEAR); | |
202 int32_t lmaxWOY = cal.getLeastMaximum(UCAL_WEEK_OF_YEAR); | |
203 int32_t maxM = cal.getMaximum(UCAL_MONTH) + 1; | |
204 int32_t lmaxM = cal.getLeastMaximum(UCAL_MONTH) + 1; | |
205 int32_t maxDOM = cal.getMaximum(UCAL_DAY_OF_MONTH); | |
206 int32_t lmaxDOM = cal.getLeastMaximum(UCAL_DAY_OF_MONTH); | |
207 int32_t maxDOWIM = cal.getMaximum(UCAL_DAY_OF_WEEK_IN_MONTH); | |
208 int32_t lmaxDOWIM = cal.getLeastMaximum(UCAL_DAY_OF_WEEK_IN_MONTH); | |
209 int32_t maxWOM = cal.getMaximum(UCAL_WEEK_OF_MONTH); | |
210 int32_t lmaxWOM = cal.getLeastMaximum(UCAL_WEEK_OF_MONTH); | |
211 int32_t minDaysInFirstWeek = cal.getMinimalDaysInFirstWeek(); | |
212 | |
213 // Day of year | |
214 int32_t expected; | |
215 if (!leapMonth) { | |
216 expected = maxM*maxDOM; | |
217 if (maxDOY > expected) { | |
218 errln((UnicodeString)"FAIL: [" + calType + "] Maximum value of DAY_O
F_YEAR is too big: " | |
219 + maxDOY + "/expected: <=" + expected); | |
220 } | |
221 expected = lmaxM*lmaxDOM; | |
222 if (lmaxDOW < expected) { | |
223 errln((UnicodeString)"FAIL: [" + calType + "] Least maximum value of
DAY_OF_YEAR is too small: " | |
224 + lmaxDOW + "/expected: >=" + expected); | |
225 } | |
226 } | |
227 | |
228 // Week of year | |
229 expected = maxDOY/nDOW + 1; | |
230 if (maxWOY > expected) { | |
231 errln((UnicodeString)"FAIL: [" + calType + "] Maximum value of WEEK_OF_Y
EAR is too big: " | |
232 + maxWOY + "/expected: <=" + expected); | |
233 } | |
234 expected = lmaxDOW/nDOW; | |
235 if (lmaxWOY < expected) { | |
236 errln((UnicodeString)"FAIL: [" + calType + "] Least maximum value of WEE
K_OF_YEAR is too small: " | |
237 + lmaxWOY + "/expected >=" + expected); | |
238 } | |
239 | |
240 // Day of week in month | |
241 expected = (maxDOM + nDOW - 1)/nDOW; | |
242 if (maxDOWIM != expected) { | |
243 errln((UnicodeString)"FAIL: [" + calType + "] Maximum value of DAY_OF_WE
EK_IN_MONTH is incorrect: " | |
244 + maxDOWIM + "/expected: " + expected); | |
245 } | |
246 expected = (lmaxDOM + nDOW - 1)/nDOW; | |
247 if (lmaxDOWIM != expected) { | |
248 errln((UnicodeString)"FAIL: [" + calType + "] Least maximum value of DAY
_OF_WEEK_IN_MONTH is incorrect: " | |
249 + lmaxDOWIM + "/expected: " + expected); | |
250 } | |
251 | |
252 // Week of month | |
253 expected = (maxDOM + (nDOW - 1) + (nDOW - minDaysInFirstWeek)) / nDOW; | |
254 if (maxWOM != expected) { | |
255 errln((UnicodeString)"FAIL: [" + calType + "] Maximum value of WEEK_OF_M
ONTH is incorrect: " | |
256 + maxWOM + "/expected: " + expected); | |
257 } | |
258 expected = (lmaxDOM + (nDOW - minDaysInFirstWeek)) / nDOW; | |
259 if (lmaxWOM != expected) { | |
260 errln((UnicodeString)"FAIL: [" + calType + "] Least maximum value of WEE
K_OF_MONTH is incorrect: " | |
261 + lmaxWOM + "/expected: " + expected); | |
262 } | |
263 } | |
264 | |
265 void | |
266 CalendarLimitTest::doLimitsTest(Calendar& cal, UDate startDate, int32_t endTime)
{ | |
267 int32_t testTime = quick ? ( endTime / 40 ) : endTime; | |
268 doLimitsTest(cal, NULL /*default fields*/, startDate, testTime); | |
269 } | |
270 | |
271 void | |
272 CalendarLimitTest::doLimitsTest(Calendar& cal, | |
273 const int32_t* fieldsToTest, | |
274 UDate startDate, | |
275 int32_t testDuration) { | |
276 static const int32_t FIELDS[] = { | |
277 UCAL_ERA, | |
278 UCAL_YEAR, | |
279 UCAL_MONTH, | |
280 UCAL_WEEK_OF_YEAR, | |
281 UCAL_WEEK_OF_MONTH, | |
282 UCAL_DAY_OF_MONTH, | |
283 UCAL_DAY_OF_YEAR, | |
284 UCAL_DAY_OF_WEEK_IN_MONTH, | |
285 UCAL_YEAR_WOY, | |
286 UCAL_EXTENDED_YEAR, | |
287 -1, | |
288 }; | |
289 | |
290 static const char* FIELD_NAME[] = { | |
291 "ERA", "YEAR", "MONTH", "WEEK_OF_YEAR", "WEEK_OF_MONTH", | |
292 "DAY_OF_MONTH", "DAY_OF_YEAR", "DAY_OF_WEEK", | |
293 "DAY_OF_WEEK_IN_MONTH", "AM_PM", "HOUR", "HOUR_OF_DAY", | |
294 "MINUTE", "SECOND", "MILLISECOND", "ZONE_OFFSET", | |
295 "DST_OFFSET", "YEAR_WOY", "DOW_LOCAL", "EXTENDED_YEAR", | |
296 "JULIAN_DAY", "MILLISECONDS_IN_DAY", | |
297 "IS_LEAP_MONTH" | |
298 }; | |
299 | |
300 UErrorCode status = U_ZERO_ERROR; | |
301 int32_t i, j; | |
302 UnicodeString ymd; | |
303 | |
304 GregorianCalendar greg(status); | |
305 if (failure(status, "new GregorianCalendar")) { | |
306 return; | |
307 } | |
308 greg.setTime(startDate, status); | |
309 if (failure(status, "GregorianCalendar::setTime")) { | |
310 return; | |
311 } | |
312 logln((UnicodeString)"Start: " + startDate); | |
313 | |
314 if (fieldsToTest == NULL) { | |
315 fieldsToTest = FIELDS; | |
316 } | |
317 | |
318 | |
319 // Keep a record of minima and maxima that we actually see. | |
320 // These are kept in an array of arrays of hashes. | |
321 int32_t limits[UCAL_FIELD_COUNT][4]; | |
322 for (j = 0; j < UCAL_FIELD_COUNT; j++) { | |
323 limits[j][0] = INT32_MAX; | |
324 limits[j][1] = INT32_MIN; | |
325 limits[j][2] = INT32_MAX; | |
326 limits[j][3] = INT32_MIN; | |
327 } | |
328 | |
329 // This test can run for a long time; show progress. | |
330 UDate millis = ucal_getNow(); | |
331 UDate mark = millis + 5000; // 5 sec | |
332 millis -= testDuration * 1000; // stop time if testDuration<0 | |
333 | |
334 for (i = 0; | |
335 testDuration > 0 ? i < testDuration | |
336 : ucal_getNow() < millis; | |
337 ++i) { | |
338 if (ucal_getNow() >= mark) { | |
339 logln((UnicodeString)"(" + i + " days)"); | |
340 mark += 5000; // 5 sec | |
341 } | |
342 UDate testMillis = greg.getTime(status); | |
343 cal.setTime(testMillis, status); | |
344 cal.setMinimalDaysInFirstWeek(1); | |
345 if (failure(status, "Calendar set/getTime")) { | |
346 return; | |
347 } | |
348 for (j = 0; fieldsToTest[j] >= 0; ++j) { | |
349 UCalendarDateFields f = (UCalendarDateFields)fieldsToTest[j]; | |
350 int32_t v = cal.get(f, status); | |
351 int32_t minActual = cal.getActualMinimum(f, status); | |
352 int32_t maxActual = cal.getActualMaximum(f, status); | |
353 int32_t minLow = cal.getMinimum(f); | |
354 int32_t minHigh = cal.getGreatestMinimum(f); | |
355 int32_t maxLow = cal.getLeastMaximum(f); | |
356 int32_t maxHigh = cal.getMaximum(f); | |
357 | |
358 if (limits[j][0] > minActual) { | |
359 // the minimum | |
360 limits[j][0] = minActual; | |
361 } | |
362 if (limits[j][1] < minActual) { | |
363 // the greatest minimum | |
364 limits[j][1] = minActual; | |
365 } | |
366 if (limits[j][2] > maxActual) { | |
367 // the least maximum | |
368 limits[j][2] = maxActual; | |
369 } | |
370 if (limits[j][3] < maxActual) { | |
371 // the maximum | |
372 limits[j][3] = maxActual; | |
373 } | |
374 | |
375 if (minActual < minLow || minActual > minHigh) { | |
376 errln((UnicodeString)"Fail: [" + cal.getType() + "] " + | |
377 ymdToString(cal, ymd) + | |
378 " Range for min of " + FIELD_NAME[f] + "(" + f + | |
379 ")=" + minLow + ".." + minHigh + | |
380 ", actual_min=" + minActual); | |
381 } | |
382 if (maxActual < maxLow || maxActual > maxHigh) { | |
383 errln((UnicodeString)"Fail: [" + cal.getType() + "] " + | |
384 ymdToString(cal, ymd) + | |
385 " Range for max of " + FIELD_NAME[f] + "(" + f + | |
386 ")=" + maxLow + ".." + maxHigh + | |
387 ", actual_max=" + maxActual); | |
388 } | |
389 if (v < minActual || v > maxActual) { | |
390 // timebomb per #9967, fix with #9972 | |
391 if ( uprv_strcmp(cal.getType(), "dangi") == 0 && | |
392 testMillis >= 1865635198000.0 && | |
393 logKnownIssue("9972", "as per #9967")) { // Feb 2029 gregor
ian, end of dangi 4361 | |
394 logln((UnicodeString)"Fail: [" + cal.getType() + "] " + | |
395 ymdToString(cal, ymd) + | |
396 " " + FIELD_NAME[f] + "(" + f + ")=" + v + | |
397 ", actual=" + minActual + ".." + maxActual + | |
398 ", allowed=(" + minLow + ".." + minHigh + ")..(" + | |
399 maxLow + ".." + maxHigh + ")"); | |
400 } else { | |
401 errln((UnicodeString)"Fail: [" + cal.getType() + "] " + | |
402 ymdToString(cal, ymd) + | |
403 " " + FIELD_NAME[f] + "(" + f + ")=" + v + | |
404 ", actual=" + minActual + ".." + maxActual + | |
405 ", allowed=(" + minLow + ".." + minHigh + ")..(" + | |
406 maxLow + ".." + maxHigh + ")"); | |
407 } | |
408 } | |
409 } | |
410 greg.add(UCAL_DAY_OF_YEAR, 1, status); | |
411 if (failure(status, "Calendar::add")) { | |
412 return; | |
413 } | |
414 } | |
415 | |
416 // Check actual maxima and minima seen against ranges returned | |
417 // by API. | |
418 UnicodeString buf; | |
419 for (j = 0; fieldsToTest[j] >= 0; ++j) { | |
420 int32_t rangeLow, rangeHigh; | |
421 UBool fullRangeSeen = TRUE; | |
422 UCalendarDateFields f = (UCalendarDateFields)fieldsToTest[j]; | |
423 | |
424 buf.remove(); | |
425 buf.append((UnicodeString)"[" + cal.getType() + "] " + FIELD_NAME[f]); | |
426 | |
427 // Minumum | |
428 rangeLow = cal.getMinimum(f); | |
429 rangeHigh = cal.getGreatestMinimum(f); | |
430 if (limits[j][0] != rangeLow || limits[j][1] != rangeHigh) { | |
431 fullRangeSeen = FALSE; | |
432 } | |
433 buf.append((UnicodeString)" minima range=" + rangeLow + ".." + rangeHigh
); | |
434 buf.append((UnicodeString)" minima actual=" + limits[j][0] + ".." + limi
ts[j][1]); | |
435 | |
436 // Maximum | |
437 rangeLow = cal.getLeastMaximum(f); | |
438 rangeHigh = cal.getMaximum(f); | |
439 if (limits[j][2] != rangeLow || limits[j][3] != rangeHigh) { | |
440 fullRangeSeen = FALSE; | |
441 } | |
442 buf.append((UnicodeString)" maxima range=" + rangeLow + ".." + rangeHigh
); | |
443 buf.append((UnicodeString)" maxima actual=" + limits[j][2] + ".." + limi
ts[j][3]); | |
444 | |
445 if (fullRangeSeen) { | |
446 logln((UnicodeString)"OK: " + buf); | |
447 } else { | |
448 // This may or may not be an error -- if the range of dates | |
449 // we scan over doesn't happen to contain a minimum or | |
450 // maximum, it doesn't mean some other range won't. | |
451 logln((UnicodeString)"Warning: " + buf); | |
452 } | |
453 } | |
454 | |
455 logln((UnicodeString)"End: " + greg.getTime(status)); | |
456 } | |
457 | |
458 UnicodeString& | |
459 CalendarLimitTest::ymdToString(const Calendar& cal, UnicodeString& str) { | |
460 UErrorCode status = U_ZERO_ERROR; | |
461 str.remove(); | |
462 str.append((UnicodeString)"" + cal.get(UCAL_EXTENDED_YEAR, status) | |
463 + "/" + (cal.get(UCAL_MONTH, status) + 1) | |
464 + (cal.get(UCAL_IS_LEAP_MONTH, status) == 1 ? "(leap)" : "") | |
465 + "/" + cal.get(UCAL_DATE, status) | |
466 + " " + cal.get(UCAL_HOUR_OF_DAY, status) | |
467 + ":" + cal.get(UCAL_MINUTE, status) | |
468 + " zone(hrs) " + cal.get(UCAL_ZONE_OFFSET, status)/(60.0*60.0*1000.0) | |
469 + " dst(hrs) " + cal.get(UCAL_DST_OFFSET, status)/(60.0*60.0*1000.0) | |
470 + ", time(millis)=" + cal.getTime(status)); | |
471 return str; | |
472 } | |
473 | |
474 #endif /* #if !UCONFIG_NO_FORMATTING */ | |
475 | |
476 // eof | |
OLD | NEW |