1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3 /***********************************************************************
5 * Copyright (c) 1997-2015, International Business Machines Corporation
6 * and others. All Rights Reserved.
7 ***********************************************************************/
9 #include "unicode/utypes.h"
11 #if !UCONFIG_NO_FORMATTING
15 #include "unicode/calendar.h"
16 #include "unicode/gregocal.h"
17 #include "unicode/datefmt.h"
18 #include "unicode/smpdtfmt.h"
22 #include "simplethread.h"
25 void CalendarLimitTest::runIndexedTest( int32_t index
, UBool exec
, const char* &name
, char* /*par*/ )
27 if (exec
) logln("TestSuite TestCalendarLimit");
29 // Re-enable this later
31 name
= "TestCalendarExtremeLimit";
33 logln("TestCalendarExtremeLimit---"); logln("");
34 TestCalendarExtremeLimit();
40 logln("TestLimits---"); logln("");
45 default: name
= ""; break;
50 // *****************************************************************************
51 // class CalendarLimitTest
52 // *****************************************************************************
54 // -------------------------------------
56 CalendarLimitTest::test(UDate millis
, icu::Calendar
* cal
, icu::DateFormat
* fmt
)
58 static const UDate kDrift
= 1e-10;
59 UErrorCode exception
= U_ZERO_ERROR
;
60 UnicodeString theDate
;
61 UErrorCode status
= U_ZERO_ERROR
;
62 cal
->setTime(millis
, exception
);
63 if (U_SUCCESS(exception
)) {
64 fmt
->format(millis
, theDate
);
65 UDate dt
= fmt
->parse(theDate
, status
);
66 // allow a small amount of error (drift)
67 if(! withinErr(dt
, millis
, kDrift
)) {
68 errln("FAIL:round trip for large milli, got: %.1lf wanted: %.1lf. (delta %.2lf greater than %.2lf)",
69 dt
, millis
, uprv_fabs(millis
-dt
), uprv_fabs(dt
*kDrift
));
70 logln(UnicodeString(" ") + theDate
+ " " + CalendarTest::calToStr(*cal
));
72 logln(UnicodeString("OK: got ") + dt
+ ", wanted " + millis
);
73 logln(UnicodeString(" ") + theDate
);
78 // -------------------------------------
80 // bug 986c: deprecate nextDouble/previousDouble
82 //|CalendarLimitTest::nextDouble(double a)
84 //| return uprv_nextDouble(a, TRUE);
88 //|CalendarLimitTest::previousDouble(double a)
90 //| return uprv_nextDouble(a, FALSE);
94 CalendarLimitTest::withinErr(double a
, double b
, double err
)
96 return ( uprv_fabs(a
- b
) < uprv_fabs(a
* err
) );
100 CalendarLimitTest::TestCalendarExtremeLimit()
102 UErrorCode status
= U_ZERO_ERROR
;
103 Calendar
*cal
= Calendar::createInstance(status
);
104 if (failure(status
, "Calendar::createInstance", TRUE
)) return;
105 cal
->adoptTimeZone(TimeZone::createTimeZone("GMT"));
106 DateFormat
*fmt
= DateFormat::createDateTimeInstance();
108 dataerrln("can't open cal and/or fmt");
111 fmt
->adoptCalendar(cal
);
112 ((SimpleDateFormat
*) fmt
)->applyPattern("HH:mm:ss.SSS Z, EEEE, MMMM d, yyyy G");
115 // This test used to test the algorithmic limits of the dates that
116 // GregorianCalendar could handle. However, the algorithm has
117 // been rewritten completely since then and the prior limits no
118 // longer apply. Instead, we now do basic round-trip testing of
119 // some extreme (but still manageable) dates.
121 logln("checking 1e16..1e17");
122 for ( m
= 1e16
; m
< 1e17
; m
*= 1.1) {
125 logln("checking -1e14..-1e15");
126 for ( m
= -1e14
; m
> -1e15
; m
*= 1.1) {
130 // This is 2^52 - 1, the largest allowable mantissa with a 0
131 // exponent in a 64-bit double
132 UDate VERY_EARLY_MILLIS
= - 4503599627370495.0;
133 UDate VERY_LATE_MILLIS
= 4503599627370495.0;
135 // I am removing the previousDouble and nextDouble calls below for
136 // two reasons: 1. As part of jitterbug 986, I am deprecating
137 // these methods and removing calls to them. 2. This test is a
138 // non-critical boundary behavior test.
139 test(VERY_EARLY_MILLIS
, cal
, fmt
);
140 //test(previousDouble(VERY_EARLY_MILLIS), cal, fmt);
141 test(VERY_LATE_MILLIS
, cal
, fmt
);
142 //test(nextDouble(VERY_LATE_MILLIS), cal, fmt);
151 UDate actualTestStart
;
152 int32_t actualTestEnd
;
155 const UDate DEFAULT_START
= 944006400000.0; // 1999-12-01T00:00Z
156 const int32_t DEFAULT_END
= -120; // Default for non-quick is run 2 minutes
158 TestCase TestCases
[] = {
159 {"gregorian", FALSE
, DEFAULT_START
, DEFAULT_END
},
160 {"japanese", FALSE
, 596937600000.0, DEFAULT_END
}, // 1988-12-01T00:00Z, Showa 63
161 {"buddhist", FALSE
, DEFAULT_START
, DEFAULT_END
},
162 {"roc", FALSE
, DEFAULT_START
, DEFAULT_END
},
163 {"persian", FALSE
, DEFAULT_START
, DEFAULT_END
},
164 {"islamic-civil", FALSE
, DEFAULT_START
, DEFAULT_END
},
165 {"islamic", FALSE
, DEFAULT_START
, 800000}, // Approx. 2250 years from now, after which
166 // some rounding errors occur in Islamic calendar
167 {"hebrew", TRUE
, DEFAULT_START
, DEFAULT_END
},
168 {"chinese", TRUE
, DEFAULT_START
, DEFAULT_END
},
169 {"dangi", TRUE
, DEFAULT_START
, DEFAULT_END
},
170 {"indian", FALSE
, DEFAULT_START
, DEFAULT_END
},
171 {"coptic", FALSE
, DEFAULT_START
, DEFAULT_END
},
172 {"ethiopic", FALSE
, DEFAULT_START
, DEFAULT_END
},
173 {"ethiopic-amete-alem", FALSE
, DEFAULT_START
, DEFAULT_END
}
178 UBool
next (int32_t &rIndex
) {
180 if (fIndex
>= UPRV_LENGTHOF(TestCases
)) {
191 } // anonymous name space
194 CalendarLimitTest::TestLimits(void) {
195 gTestCaseIterator
.reset();
197 ThreadPool
<CalendarLimitTest
> threads(this, threadCount
, &CalendarLimitTest::TestLimitsThread
);
203 void CalendarLimitTest::TestLimitsThread(int32_t threadNum
) {
204 logln("thread %d starting", threadNum
);
205 int32_t testIndex
= 0;
206 LocalPointer
<Calendar
> cal
;
207 while (gTestCaseIterator
.next(testIndex
)) {
208 TestCase
&testCase
= TestCases
[testIndex
];
209 logln("begin test of %s calendar.", testCase
.type
);
210 UErrorCode status
= U_ZERO_ERROR
;
212 uprv_strcpy(buf
, "root@calendar=");
213 strcat(buf
, testCase
.type
);
214 cal
.adoptInstead(Calendar::createInstance(buf
, status
));
215 if (failure(status
, "Calendar::createInstance", TRUE
)) {
218 if (uprv_strcmp(cal
->getType(), testCase
.type
) != 0) {
219 errln((UnicodeString
)"FAIL: Wrong calendar type: " + cal
->getType()
220 + " Requested: " + testCase
.type
);
223 doTheoreticalLimitsTest(*(cal
.getAlias()), testCase
.hasLeapMonth
);
224 doLimitsTest(*(cal
.getAlias()), testCase
.actualTestStart
, testCase
.actualTestEnd
);
225 logln("end test of %s calendar.", testCase
.type
);
231 CalendarLimitTest::doTheoreticalLimitsTest(Calendar
& cal
, UBool leapMonth
) {
232 const char* calType
= cal
.getType();
234 int32_t nDOW
= cal
.getMaximum(UCAL_DAY_OF_WEEK
);
235 int32_t maxDOY
= cal
.getMaximum(UCAL_DAY_OF_YEAR
);
236 int32_t lmaxDOW
= cal
.getLeastMaximum(UCAL_DAY_OF_YEAR
);
237 int32_t maxWOY
= cal
.getMaximum(UCAL_WEEK_OF_YEAR
);
238 int32_t lmaxWOY
= cal
.getLeastMaximum(UCAL_WEEK_OF_YEAR
);
239 int32_t maxM
= cal
.getMaximum(UCAL_MONTH
) + 1;
240 int32_t lmaxM
= cal
.getLeastMaximum(UCAL_MONTH
) + 1;
241 int32_t maxDOM
= cal
.getMaximum(UCAL_DAY_OF_MONTH
);
242 int32_t lmaxDOM
= cal
.getLeastMaximum(UCAL_DAY_OF_MONTH
);
243 int32_t maxDOWIM
= cal
.getMaximum(UCAL_DAY_OF_WEEK_IN_MONTH
);
244 int32_t lmaxDOWIM
= cal
.getLeastMaximum(UCAL_DAY_OF_WEEK_IN_MONTH
);
245 int32_t maxWOM
= cal
.getMaximum(UCAL_WEEK_OF_MONTH
);
246 int32_t lmaxWOM
= cal
.getLeastMaximum(UCAL_WEEK_OF_MONTH
);
247 int32_t minDaysInFirstWeek
= cal
.getMinimalDaysInFirstWeek();
252 expected
= maxM
*maxDOM
;
253 if (maxDOY
> expected
) {
254 errln((UnicodeString
)"FAIL: [" + calType
+ "] Maximum value of DAY_OF_YEAR is too big: "
255 + maxDOY
+ "/expected: <=" + expected
);
257 expected
= lmaxM
*lmaxDOM
;
258 if (lmaxDOW
< expected
) {
259 errln((UnicodeString
)"FAIL: [" + calType
+ "] Least maximum value of DAY_OF_YEAR is too small: "
260 + lmaxDOW
+ "/expected: >=" + expected
);
265 expected
= maxDOY
/nDOW
+ 1;
266 if (maxWOY
> expected
) {
267 errln((UnicodeString
)"FAIL: [" + calType
+ "] Maximum value of WEEK_OF_YEAR is too big: "
268 + maxWOY
+ "/expected: <=" + expected
);
270 expected
= lmaxDOW
/nDOW
;
271 if (lmaxWOY
< expected
) {
272 errln((UnicodeString
)"FAIL: [" + calType
+ "] Least maximum value of WEEK_OF_YEAR is too small: "
273 + lmaxWOY
+ "/expected >=" + expected
);
276 // Day of week in month
277 expected
= (maxDOM
+ nDOW
- 1)/nDOW
;
278 if (maxDOWIM
!= expected
) {
279 errln((UnicodeString
)"FAIL: [" + calType
+ "] Maximum value of DAY_OF_WEEK_IN_MONTH is incorrect: "
280 + maxDOWIM
+ "/expected: " + expected
);
282 expected
= (lmaxDOM
+ nDOW
- 1)/nDOW
;
283 if (lmaxDOWIM
!= expected
) {
284 errln((UnicodeString
)"FAIL: [" + calType
+ "] Least maximum value of DAY_OF_WEEK_IN_MONTH is incorrect: "
285 + lmaxDOWIM
+ "/expected: " + expected
);
289 expected
= (maxDOM
+ (nDOW
- 1) + (nDOW
- minDaysInFirstWeek
)) / nDOW
;
290 if (maxWOM
!= expected
) {
291 errln((UnicodeString
)"FAIL: [" + calType
+ "] Maximum value of WEEK_OF_MONTH is incorrect: "
292 + maxWOM
+ "/expected: " + expected
);
294 expected
= (lmaxDOM
+ (nDOW
- minDaysInFirstWeek
)) / nDOW
;
295 if (lmaxWOM
!= expected
) {
296 errln((UnicodeString
)"FAIL: [" + calType
+ "] Least maximum value of WEEK_OF_MONTH is incorrect: "
297 + lmaxWOM
+ "/expected: " + expected
);
302 CalendarLimitTest::doLimitsTest(Calendar
& cal
, UDate startDate
, int32_t endTime
) {
303 int32_t testTime
= quick
? ( endTime
/ 40 ) : endTime
;
304 doLimitsTest(cal
, NULL
/*default fields*/, startDate
, testTime
);
308 CalendarLimitTest::doLimitsTest(Calendar
& cal
,
309 const int32_t* fieldsToTest
,
311 int32_t testDuration
) {
312 static const int32_t FIELDS
[] = {
320 UCAL_DAY_OF_WEEK_IN_MONTH
,
326 static const char* FIELD_NAME
[] = {
327 "ERA", "YEAR", "MONTH", "WEEK_OF_YEAR", "WEEK_OF_MONTH",
328 "DAY_OF_MONTH", "DAY_OF_YEAR", "DAY_OF_WEEK",
329 "DAY_OF_WEEK_IN_MONTH", "AM_PM", "HOUR", "HOUR_OF_DAY",
330 "MINUTE", "SECOND", "MILLISECOND", "ZONE_OFFSET",
331 "DST_OFFSET", "YEAR_WOY", "DOW_LOCAL", "EXTENDED_YEAR",
332 "JULIAN_DAY", "MILLISECONDS_IN_DAY",
336 UErrorCode status
= U_ZERO_ERROR
;
340 GregorianCalendar
greg(status
);
341 if (failure(status
, "new GregorianCalendar")) {
344 greg
.setTime(startDate
, status
);
345 if (failure(status
, "GregorianCalendar::setTime")) {
348 logln((UnicodeString
)"Start: " + startDate
);
350 if (fieldsToTest
== NULL
) {
351 fieldsToTest
= FIELDS
;
355 // Keep a record of minima and maxima that we actually see.
356 // These are kept in an array of arrays of hashes.
357 int32_t limits
[UCAL_FIELD_COUNT
][4];
358 for (j
= 0; j
< UCAL_FIELD_COUNT
; j
++) {
359 limits
[j
][0] = INT32_MAX
;
360 limits
[j
][1] = INT32_MIN
;
361 limits
[j
][2] = INT32_MAX
;
362 limits
[j
][3] = INT32_MIN
;
365 // This test can run for a long time; show progress.
366 UDate millis
= ucal_getNow();
367 UDate mark
= millis
+ 5000; // 5 sec
368 millis
-= testDuration
* 1000; // stop time if testDuration<0
371 testDuration
> 0 ? i
< testDuration
372 : ucal_getNow() < millis
;
374 if (ucal_getNow() >= mark
) {
375 logln((UnicodeString
)"(" + i
+ " days)");
376 mark
+= 5000; // 5 sec
378 UDate testMillis
= greg
.getTime(status
);
380 if (testMillis
== 2768943600000.0) {
381 // unusual failure, get day of month 0.
382 // doesn't happen with runConfigure and make check inside
383 // icuSources directory (i.e. using ICU build without wrapper makefile)
387 cal
.setTime(testMillis
, status
);
388 cal
.setMinimalDaysInFirstWeek(1);
389 if (failure(status
, "Calendar set/getTime")) {
392 for (j
= 0; fieldsToTest
[j
] >= 0; ++j
) {
393 UCalendarDateFields f
= (UCalendarDateFields
)fieldsToTest
[j
];
394 int32_t v
= cal
.get(f
, status
);
395 int32_t minActual
= cal
.getActualMinimum(f
, status
);
396 int32_t maxActual
= cal
.getActualMaximum(f
, status
);
397 int32_t minLow
= cal
.getMinimum(f
);
398 int32_t minHigh
= cal
.getGreatestMinimum(f
);
399 int32_t maxLow
= cal
.getLeastMaximum(f
);
400 int32_t maxHigh
= cal
.getMaximum(f
);
402 if (limits
[j
][0] > minActual
) {
404 limits
[j
][0] = minActual
;
406 if (limits
[j
][1] < minActual
) {
407 // the greatest minimum
408 limits
[j
][1] = minActual
;
410 if (limits
[j
][2] > maxActual
) {
412 limits
[j
][2] = maxActual
;
414 if (limits
[j
][3] < maxActual
) {
416 limits
[j
][3] = maxActual
;
419 if (minActual
< minLow
|| minActual
> minHigh
) {
420 errln((UnicodeString
)"Fail: [" + cal
.getType() + "] " +
421 ymdToString(cal
, ymd
) +
422 " Range for min of " + FIELD_NAME
[f
] + "(" + f
+
423 ")=" + minLow
+ ".." + minHigh
+
424 ", actual_min=" + minActual
);
426 if (maxActual
< maxLow
|| maxActual
> maxHigh
) {
427 if ( uprv_strcmp(cal
.getType(), "chinese") == 0 &&
428 testMillis
>= 1802044800000.0 &&
429 logKnownIssue("12620", "chinese calendar failures for some actualMax tests")) {
430 logln((UnicodeString
)"KnownFail: [" + cal
.getType() + "] " +
431 ymdToString(cal
, ymd
) +
432 " Range for max of " + FIELD_NAME
[f
] + "(" + f
+
433 ")=" + maxLow
+ ".." + maxHigh
+
434 ", actual_max=" + maxActual
);
436 errln((UnicodeString
)"Fail: [" + cal
.getType() + "] " +
437 ymdToString(cal
, ymd
) +
438 " Range for max of " + FIELD_NAME
[f
] + "(" + f
+
439 ")=" + maxLow
+ ".." + maxHigh
+
440 ", actual_max=" + maxActual
);
443 if (v
< minActual
|| v
> maxActual
) {
444 // timebomb per #9967, fix with #9972
445 if ( uprv_strcmp(cal
.getType(), "dangi") == 0 &&
446 testMillis
>= 1865635198000.0 &&
447 logKnownIssue("9972", "as per #9967")) { // Feb 2029 gregorian, end of dangi 4361
448 logln((UnicodeString
)"KnownFail: [" + cal
.getType() + "] " +
449 ymdToString(cal
, ymd
) +
450 " " + FIELD_NAME
[f
] + "(" + f
+ ")=" + v
+
451 ", actual=" + minActual
+ ".." + maxActual
+
452 ", allowed=(" + minLow
+ ".." + minHigh
+ ")..(" +
453 maxLow
+ ".." + maxHigh
+ ")");
454 } else if ( uprv_strcmp(cal
.getType(), "chinese") == 0 &&
455 testMillis
>= 1832544000000.0 &&
456 logKnownIssue("12620", "chinese calendar failures for some actualMax tests")) {
457 logln((UnicodeString
)"KnownFail: [" + cal
.getType() + "] " +
458 ymdToString(cal
, ymd
) +
459 " " + FIELD_NAME
[f
] + "(" + f
+ ")=" + v
+
460 ", actual=" + minActual
+ ".." + maxActual
+
461 ", allowed=(" + minLow
+ ".." + minHigh
+ ")..(" +
462 maxLow
+ ".." + maxHigh
+ ")");
464 errln((UnicodeString
)"Fail: [" + cal
.getType() + "] " +
465 ymdToString(cal
, ymd
) +
466 " " + FIELD_NAME
[f
] + "(" + f
+ ")=" + v
+
467 ", actual=" + minActual
+ ".." + maxActual
+
468 ", allowed=(" + minLow
+ ".." + minHigh
+ ")..(" +
469 maxLow
+ ".." + maxHigh
+ ")");
473 greg
.add(UCAL_DAY_OF_YEAR
, 1, status
);
474 if (failure(status
, "Calendar::add")) {
479 // Check actual maxima and minima seen against ranges returned
482 for (j
= 0; fieldsToTest
[j
] >= 0; ++j
) {
483 int32_t rangeLow
, rangeHigh
;
484 UBool fullRangeSeen
= TRUE
;
485 UCalendarDateFields f
= (UCalendarDateFields
)fieldsToTest
[j
];
488 buf
.append((UnicodeString
)"[" + cal
.getType() + "] " + FIELD_NAME
[f
]);
491 rangeLow
= cal
.getMinimum(f
);
492 rangeHigh
= cal
.getGreatestMinimum(f
);
493 if (limits
[j
][0] != rangeLow
|| limits
[j
][1] != rangeHigh
) {
494 fullRangeSeen
= FALSE
;
496 buf
.append((UnicodeString
)" minima range=" + rangeLow
+ ".." + rangeHigh
);
497 buf
.append((UnicodeString
)" minima actual=" + limits
[j
][0] + ".." + limits
[j
][1]);
500 rangeLow
= cal
.getLeastMaximum(f
);
501 rangeHigh
= cal
.getMaximum(f
);
502 if (limits
[j
][2] != rangeLow
|| limits
[j
][3] != rangeHigh
) {
503 fullRangeSeen
= FALSE
;
505 buf
.append((UnicodeString
)" maxima range=" + rangeLow
+ ".." + rangeHigh
);
506 buf
.append((UnicodeString
)" maxima actual=" + limits
[j
][2] + ".." + limits
[j
][3]);
509 logln((UnicodeString
)"OK: " + buf
);
511 // This may or may not be an error -- if the range of dates
512 // we scan over doesn't happen to contain a minimum or
513 // maximum, it doesn't mean some other range won't.
514 logln((UnicodeString
)"Warning: " + buf
);
518 logln((UnicodeString
)"End: " + greg
.getTime(status
));
522 CalendarLimitTest::ymdToString(const Calendar
& cal
, UnicodeString
& str
) {
523 UErrorCode status
= U_ZERO_ERROR
;
525 str
.append((UnicodeString
)"" + cal
.get(UCAL_EXTENDED_YEAR
, status
)
526 + "/" + (cal
.get(UCAL_MONTH
, status
) + 1)
527 + (cal
.get(UCAL_IS_LEAP_MONTH
, status
) == 1 ? "(leap)" : "")
528 + "/" + cal
.get(UCAL_DATE
, status
)
529 + " " + cal
.get(UCAL_HOUR_OF_DAY
, status
)
530 + ":" + cal
.get(UCAL_MINUTE
, status
)
531 + " zone(hrs) " + cal
.get(UCAL_ZONE_OFFSET
, status
)/(60.0*60.0*1000.0)
532 + " dst(hrs) " + cal
.get(UCAL_DST_OFFSET
, status
)/(60.0*60.0*1000.0)
533 + ", time(millis)=" + cal
.getTime(status
));
537 #endif /* #if !UCONFIG_NO_FORMATTING */