1 /***********************************************************************
3 * Copyright (c) 1997-2008, International Business Machines Corporation
4 * and others. All Rights Reserved.
5 ***********************************************************************/
7 #include "unicode/utypes.h"
9 #if !UCONFIG_NO_FORMATTING
13 #include "unicode/calendar.h"
14 #include "unicode/gregocal.h"
15 #include "unicode/datefmt.h"
16 #include "unicode/smpdtfmt.h"
21 void CalendarLimitTest::runIndexedTest( int32_t index
, UBool exec
, const char* &name
, char* /*par*/ )
23 if (exec
) logln("TestSuite TestCalendarLimit");
25 // Re-enable this later
27 name
= "TestCalendarExtremeLimit";
29 logln("TestCalendarExtremeLimit---"); logln("");
30 TestCalendarExtremeLimit();
36 logln("TestLimits---"); logln("");
41 default: name
= ""; break;
46 // *****************************************************************************
47 // class CalendarLimitTest
48 // *****************************************************************************
50 // -------------------------------------
52 CalendarLimitTest::test(UDate millis
, U_NAMESPACE_QUALIFIER Calendar
* cal
, U_NAMESPACE_QUALIFIER DateFormat
* fmt
)
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. (delta %.2lf greater than %.2lf)",
65 dt
, millis
, uprv_fabs(millis
-dt
), uprv_fabs(dt
*kDrift
));
66 logln(UnicodeString(" ") + theDate
+ " " + CalendarTest::calToStr(*cal
));
68 logln(UnicodeString("OK: got ") + dt
+ ", wanted " + millis
);
69 logln(UnicodeString(" ") + theDate
);
74 // -------------------------------------
76 // bug 986c: deprecate nextDouble/previousDouble
78 //|CalendarLimitTest::nextDouble(double a)
80 //| return uprv_nextDouble(a, TRUE);
84 //|CalendarLimitTest::previousDouble(double a)
86 //| return uprv_nextDouble(a, FALSE);
90 CalendarLimitTest::withinErr(double a
, double b
, double err
)
92 return ( uprv_fabs(a
- b
) < uprv_fabs(a
* err
) );
96 CalendarLimitTest::TestCalendarExtremeLimit()
98 UErrorCode status
= U_ZERO_ERROR
;
99 Calendar
*cal
= Calendar::createInstance(status
);
100 if (failure(status
, "Calendar::createInstance")) return;
101 cal
->adoptTimeZone(TimeZone::createTimeZone("GMT"));
102 DateFormat
*fmt
= DateFormat::createDateTimeInstance();
104 dataerrln("can't open cal and/or fmt");
107 fmt
->adoptCalendar(cal
);
108 ((SimpleDateFormat
*) fmt
)->applyPattern("HH:mm:ss.SSS zzz, EEEE, MMMM d, yyyy G");
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.
117 logln("checking 1e16..1e17");
118 for ( m
= 1e16
; m
< 1e17
; m
*= 1.1) {
121 logln("checking -1e14..-1e15");
122 for ( m
= -1e14
; m
> -1e15
; m
*= 1.1) {
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;
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);
143 CalendarLimitTest::TestLimits(void) {
144 static const UDate DEFAULT_START
= 944006400000.0; // 1999-12-01T00:00Z
146 static const struct {
149 UDate actualTestStart
;
151 {"gregorian", FALSE
, DEFAULT_START
},
152 {"japanese", FALSE
, 596937600000.0}, // 1988-12-01T00:00Z, Showa 63
153 {"buddhist", FALSE
, DEFAULT_START
},
154 {"roc", FALSE
, DEFAULT_START
},
155 {"persian", FALSE
, DEFAULT_START
},
156 {"islamic-civil", FALSE
, DEFAULT_START
},
157 //{"islamic", FALSE, DEFAULT_START}, // TODO: there is a bug in monthlength calculation
158 {"hebrew", TRUE
, DEFAULT_START
},
159 {"chinese", TRUE
, DEFAULT_START
},
160 {"indian", FALSE
, DEFAULT_START
},
161 {"coptic", FALSE
, DEFAULT_START
},
162 {"ethiopic", FALSE
, DEFAULT_START
},
163 {"ethiopic-amete-alem", FALSE
, DEFAULT_START
},
170 for (i
= 0; TestCases
[i
].type
; i
++) {
171 UErrorCode status
= U_ZERO_ERROR
;
172 uprv_strcpy(buf
, "root@calendar=");
173 strcat(buf
, TestCases
[i
].type
);
174 Calendar
*cal
= Calendar::createInstance(buf
, status
);
175 if (failure(status
, "Calendar::createInstance")) {
178 if (uprv_strcmp(cal
->getType(), TestCases
[i
].type
) != 0) {
179 errln((UnicodeString
)"FAIL: Wrong calendar type: " + cal
->getType()
180 + " Requested: " + TestCases
[i
].type
);
185 doTheoreticalLimitsTest(*cal
, TestCases
[i
].hasLeapMonth
);
186 doLimitsTest(*cal
, TestCases
[i
].actualTestStart
);
192 CalendarLimitTest::doTheoreticalLimitsTest(Calendar
& cal
, UBool leapMonth
) {
193 const char* calType
= cal
.getType();
195 int32_t nDOW
= cal
.getMaximum(UCAL_DAY_OF_WEEK
);
196 int32_t maxDOY
= cal
.getMaximum(UCAL_DAY_OF_YEAR
);
197 int32_t lmaxDOW
= cal
.getLeastMaximum(UCAL_DAY_OF_YEAR
);
198 int32_t maxWOY
= cal
.getMaximum(UCAL_WEEK_OF_YEAR
);
199 int32_t lmaxWOY
= cal
.getLeastMaximum(UCAL_WEEK_OF_YEAR
);
200 int32_t maxM
= cal
.getMaximum(UCAL_MONTH
) + 1;
201 int32_t lmaxM
= cal
.getLeastMaximum(UCAL_MONTH
) + 1;
202 int32_t maxDOM
= cal
.getMaximum(UCAL_DAY_OF_MONTH
);
203 int32_t lmaxDOM
= cal
.getLeastMaximum(UCAL_DAY_OF_MONTH
);
204 int32_t maxDOWIM
= cal
.getMaximum(UCAL_DAY_OF_WEEK_IN_MONTH
);
205 int32_t lmaxDOWIM
= cal
.getLeastMaximum(UCAL_DAY_OF_WEEK_IN_MONTH
);
206 int32_t maxWOM
= cal
.getMaximum(UCAL_WEEK_OF_MONTH
);
207 int32_t lmaxWOM
= cal
.getLeastMaximum(UCAL_WEEK_OF_MONTH
);
208 int32_t minDaysInFirstWeek
= cal
.getMinimalDaysInFirstWeek();
213 expected
= maxM
*maxDOM
;
214 if (maxDOY
> expected
) {
215 errln((UnicodeString
)"FAIL: [" + calType
+ "] Maximum value of DAY_OF_YEAR is too big: "
216 + maxDOY
+ "/expected: <=" + expected
);
218 expected
= lmaxM
*lmaxDOM
;
219 if (lmaxDOW
< expected
) {
220 errln((UnicodeString
)"FAIL: [" + calType
+ "] Least maximum value of DAY_OF_YEAR is too small: "
221 + lmaxDOW
+ "/expected: >=" + expected
);
226 expected
= maxDOY
/nDOW
+ 1;
227 if (maxWOY
> expected
) {
228 errln((UnicodeString
)"FAIL: [" + calType
+ "] Maximum value of WEEK_OF_YEAR is too big: "
229 + maxWOY
+ "/expected: <=" + expected
);
231 expected
= lmaxDOW
/nDOW
;
232 if (lmaxWOY
< expected
) {
233 errln((UnicodeString
)"FAIL: [" + calType
+ "] Least maximum value of WEEK_OF_YEAR is too small: "
234 + lmaxWOY
+ "/expected >=" + expected
);
237 // Day of week in month
238 expected
= (maxDOM
+ nDOW
- 1)/nDOW
;
239 if (maxDOWIM
!= expected
) {
240 errln((UnicodeString
)"FAIL: [" + calType
+ "] Maximum value of DAY_OF_WEEK_IN_MONTH is incorrect: "
241 + maxDOWIM
+ "/expected: " + expected
);
243 expected
= (lmaxDOM
+ nDOW
- 1)/nDOW
;
244 if (lmaxDOWIM
!= expected
) {
245 errln((UnicodeString
)"FAIL: [" + calType
+ "] Least maximum value of DAY_OF_WEEK_IN_MONTH is incorrect: "
246 + lmaxDOWIM
+ "/expected: " + expected
);
250 expected
= (maxDOM
+ (nDOW
- 1) + (nDOW
- minDaysInFirstWeek
)) / nDOW
;
251 if (maxWOM
!= expected
) {
252 errln((UnicodeString
)"FAIL: [" + calType
+ "] Maximum value of WEEK_OF_MONTH is incorrect: "
253 + maxWOM
+ "/expected: " + expected
);
255 expected
= (lmaxDOM
+ (nDOW
- minDaysInFirstWeek
)) / nDOW
;
256 if (lmaxWOM
!= expected
) {
257 errln((UnicodeString
)"FAIL: [" + calType
+ "] Least maximum value of WEEK_OF_MONTH is incorrect: "
258 + lmaxWOM
+ "/expected: " + expected
);
263 CalendarLimitTest::doLimitsTest(Calendar
& cal
, UDate startDate
) {
264 int32_t testTime
= quick
? -3 : -120;
265 doLimitsTest(cal
, NULL
/*default fields*/, startDate
, testTime
);
269 CalendarLimitTest::doLimitsTest(Calendar
& cal
,
270 const int32_t* fieldsToTest
,
272 int32_t testDuration
) {
273 static const int32_t FIELDS
[] = {
281 UCAL_DAY_OF_WEEK_IN_MONTH
,
287 static const char* FIELD_NAME
[] = {
288 "ERA", "YEAR", "MONTH", "WEEK_OF_YEAR", "WEEK_OF_MONTH",
289 "DAY_OF_MONTH", "DAY_OF_YEAR", "DAY_OF_WEEK",
290 "DAY_OF_WEEK_IN_MONTH", "AM_PM", "HOUR", "HOUR_OF_DAY",
291 "MINUTE", "SECOND", "MILLISECOND", "ZONE_OFFSET",
292 "DST_OFFSET", "YEAR_WOY", "DOW_LOCAL", "EXTENDED_YEAR",
293 "JULIAN_DAY", "MILLISECONDS_IN_DAY",
297 UErrorCode status
= U_ZERO_ERROR
;
301 GregorianCalendar
greg(status
);
302 if (failure(status
, "new GregorianCalendar")) {
305 greg
.setTime(startDate
, status
);
306 if (failure(status
, "GregorianCalendar::setTime")) {
309 logln((UnicodeString
)"Start: " + startDate
);
311 if (fieldsToTest
== NULL
) {
312 fieldsToTest
= FIELDS
;
316 // Keep a record of minima and maxima that we actually see.
317 // These are kept in an array of arrays of hashes.
318 int32_t limits
[UCAL_FIELD_COUNT
][4];
319 for (j
= 0; j
< UCAL_FIELD_COUNT
; j
++) {
320 limits
[j
][0] = INT32_MAX
;
321 limits
[j
][1] = INT32_MIN
;
322 limits
[j
][2] = INT32_MAX
;
323 limits
[j
][3] = INT32_MIN
;
326 // This test can run for a long time; show progress.
327 UDate millis
= ucal_getNow();
328 UDate mark
= millis
+ 5000; // 5 sec
329 millis
-= testDuration
* 1000; // stop time if testDuration<0
332 testDuration
> 0 ? i
< testDuration
333 : ucal_getNow() < millis
;
335 if (ucal_getNow() >= mark
) {
336 logln((UnicodeString
)"(" + i
+ " days)");
337 mark
+= 5000; // 5 sec
339 cal
.setTime(greg
.getTime(status
), status
);
340 if (failure(status
, "Calendar set/getTime")) {
343 for (j
= 0; fieldsToTest
[j
] >= 0; ++j
) {
344 UCalendarDateFields f
= (UCalendarDateFields
)fieldsToTest
[j
];
345 int32_t v
= cal
.get(f
, status
);
346 int32_t minActual
= cal
.getActualMinimum(f
, status
);
347 int32_t maxActual
= cal
.getActualMaximum(f
, status
);
348 int32_t minLow
= cal
.getMinimum(f
);
349 int32_t minHigh
= cal
.getGreatestMinimum(f
);
350 int32_t maxLow
= cal
.getLeastMaximum(f
);
351 int32_t maxHigh
= cal
.getMaximum(f
);
353 if (limits
[j
][0] > minActual
) {
355 limits
[j
][0] = minActual
;
357 if (limits
[j
][1] < minActual
) {
358 // the greatest minimum
359 limits
[j
][1] = minActual
;
361 if (limits
[j
][2] > maxActual
) {
363 limits
[j
][2] = maxActual
;
365 if (limits
[j
][3] < maxActual
) {
367 limits
[j
][3] = maxActual
;
370 if (minActual
< minLow
|| minActual
> minHigh
) {
371 errln((UnicodeString
)"Fail: [" + cal
.getType() + "] " +
372 ymdToString(cal
, ymd
) +
373 " Range for min of " + FIELD_NAME
[f
] + "(" + f
+
374 ")=" + minLow
+ ".." + minHigh
+
375 ", actual_min=" + minActual
);
377 if (maxActual
< maxLow
|| maxActual
> maxHigh
) {
378 errln((UnicodeString
)"Fail: [" + cal
.getType() + "] " +
379 ymdToString(cal
, ymd
) +
380 " Range for max of " + FIELD_NAME
[f
] + "(" + f
+
381 ")=" + maxLow
+ ".." + maxHigh
+
382 ", actual_max=" + maxActual
);
384 if (v
< minActual
|| v
> maxActual
) {
385 errln((UnicodeString
)"Fail: [" + cal
.getType() + "] " +
386 ymdToString(cal
, ymd
) +
387 " " + FIELD_NAME
[f
] + "(" + f
+ ")=" + v
+
388 ", actual range=" + minActual
+ ".." + maxActual
+
389 ", allowed=(" + minLow
+ ".." + minHigh
+ ")..(" +
390 maxLow
+ ".." + maxHigh
+ ")");
393 greg
.add(UCAL_DAY_OF_YEAR
, 1, status
);
394 if (failure(status
, "Calendar::add")) {
399 // Check actual maxima and minima seen against ranges returned
402 for (j
= 0; fieldsToTest
[j
] >= 0; ++j
) {
403 int32_t rangeLow
, rangeHigh
;
404 UBool fullRangeSeen
= TRUE
;
405 UCalendarDateFields f
= (UCalendarDateFields
)fieldsToTest
[j
];
408 buf
.append((UnicodeString
)"[" + cal
.getType() + "] " + FIELD_NAME
[f
]);
411 rangeLow
= cal
.getMinimum(f
);
412 rangeHigh
= cal
.getGreatestMinimum(f
);
413 if (limits
[j
][0] != rangeLow
|| limits
[j
][1] != rangeHigh
) {
414 fullRangeSeen
= FALSE
;
416 buf
.append((UnicodeString
)" minima range=" + rangeLow
+ ".." + rangeHigh
);
417 buf
.append((UnicodeString
)" minima actual=" + limits
[j
][0] + ".." + limits
[j
][1]);
420 rangeLow
= cal
.getLeastMaximum(f
);
421 rangeHigh
= cal
.getMaximum(f
);
422 if (limits
[j
][2] != rangeLow
|| limits
[j
][3] != rangeHigh
) {
423 fullRangeSeen
= FALSE
;
425 buf
.append((UnicodeString
)" maxima range=" + rangeLow
+ ".." + rangeHigh
);
426 buf
.append((UnicodeString
)" maxima actual=" + limits
[j
][2] + ".." + limits
[j
][3]);
429 logln((UnicodeString
)"OK: " + buf
);
431 // This may or may not be an error -- if the range of dates
432 // we scan over doesn't happen to contain a minimum or
433 // maximum, it doesn't mean some other range won't.
434 logln((UnicodeString
)"Warning: " + buf
);
438 logln((UnicodeString
)"End: " + greg
.getTime(status
));
442 CalendarLimitTest::ymdToString(const Calendar
& cal
, UnicodeString
& str
) {
443 UErrorCode status
= U_ZERO_ERROR
;
445 str
.append((UnicodeString
)"" + cal
.get(UCAL_EXTENDED_YEAR
, status
)
446 + "/" + (cal
.get(UCAL_MONTH
, status
) + 1)
447 + (cal
.get(UCAL_IS_LEAP_MONTH
, status
) == 1 ? "(leap)" : "")
448 + "/" + cal
.get(UCAL_DATE
, status
)
449 + ", time=" + cal
.getTime(status
));
453 #endif /* #if !UCONFIG_NO_FORMATTING */