-/********************************************************************
+/***********************************************************************
* COPYRIGHT:
- * Copyright (c) 1997-2001, International Business Machines Corporation and
- * others. All Rights Reserved.
- ********************************************************************/
+ * Copyright (c) 1997-2013, International Business Machines Corporation
+ * and others. All Rights Reserved.
+ ***********************************************************************/
#include "unicode/utypes.h"
#if !UCONFIG_NO_FORMATTING
#include "callimts.h"
+#include "caltest.h"
#include "unicode/calendar.h"
#include "unicode/gregocal.h"
#include "unicode/datefmt.h"
#include "unicode/smpdtfmt.h"
+#include "putilimp.h"
+#include "cstring.h"
U_NAMESPACE_USE
void CalendarLimitTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ )
switch (index) {
// Re-enable this later
case 0:
- name = "TestCalendarLimit";
+ name = "TestCalendarExtremeLimit";
if (exec) {
- logln("TestCalendarLimit---"); logln("");
- TestCalendarLimit();
+ logln("TestCalendarExtremeLimit---"); logln("");
+ TestCalendarExtremeLimit();
}
break;
+ case 1:
+ name = "TestLimits";
+ if (exec) {
+ logln("TestLimits---"); logln("");
+ TestLimits();
+ }
+ break;
+
default: name = ""; break;
}
}
// -------------------------------------
void
-CalendarLimitTest::test(UDate millis, U_NAMESPACE_QUALIFIER Calendar* cal, U_NAMESPACE_QUALIFIER DateFormat* fmt)
+CalendarLimitTest::test(UDate millis, icu::Calendar* cal, icu::DateFormat* fmt)
{
+ static const UDate kDrift = 1e-10;
UErrorCode exception = U_ZERO_ERROR;
UnicodeString theDate;
UErrorCode status = U_ZERO_ERROR;
fmt->format(millis, theDate);
UDate dt = fmt->parse(theDate, status);
// allow a small amount of error (drift)
- if(! withinErr(dt, millis, 1e-10))
- errln(UnicodeString("FAIL:round trip for large milli, got: ") + dt + " wanted: " + millis);
- else {
+ if(! withinErr(dt, millis, kDrift)) {
+ errln("FAIL:round trip for large milli, got: %.1lf wanted: %.1lf. (delta %.2lf greater than %.2lf)",
+ dt, millis, uprv_fabs(millis-dt), uprv_fabs(dt*kDrift));
+ logln(UnicodeString(" ") + theDate + " " + CalendarTest::calToStr(*cal));
+ } else {
logln(UnicodeString("OK: got ") + dt + ", wanted " + millis);
logln(UnicodeString(" ") + theDate);
}
}
void
-CalendarLimitTest::TestCalendarLimit()
+CalendarLimitTest::TestCalendarExtremeLimit()
{
UErrorCode status = U_ZERO_ERROR;
Calendar *cal = Calendar::createInstance(status);
- if (failure(status, "Calendar::createInstance")) return;
+ if (failure(status, "Calendar::createInstance", TRUE)) return;
cal->adoptTimeZone(TimeZone::createTimeZone("GMT"));
DateFormat *fmt = DateFormat::createDateTimeInstance();
+ if(!fmt || !cal) {
+ dataerrln("can't open cal and/or fmt");
+ return;
+ }
fmt->adoptCalendar(cal);
- ((SimpleDateFormat*) fmt)->applyPattern("HH:mm:ss.SSS zzz, EEEE, MMMM d, yyyy G");
+ ((SimpleDateFormat*) fmt)->applyPattern("HH:mm:ss.SSS Z, EEEE, MMMM d, yyyy G");
+
// This test used to test the algorithmic limits of the dates that
// GregorianCalendar could handle. However, the algorithm has
// longer apply. Instead, we now do basic round-trip testing of
// some extreme (but still manageable) dates.
UDate m;
- for ( m = 1e17; m < 1e18; m *= 1.1) {
+ logln("checking 1e16..1e17");
+ for ( m = 1e16; m < 1e17; m *= 1.1) {
test(m, cal, fmt);
}
+ logln("checking -1e14..-1e15");
for ( m = -1e14; m > -1e15; m *= 1.1) {
test(m, cal, fmt);
}
delete fmt;
}
+void
+CalendarLimitTest::TestLimits(void) {
+ static const UDate DEFAULT_START = 944006400000.0; // 1999-12-01T00:00Z
+ static const int32_t DEFAULT_END = -120; // Default for non-quick is run 2 minutes
+
+ static const struct {
+ const char *type;
+ UBool hasLeapMonth;
+ UDate actualTestStart;
+ int32_t actualTestEnd;
+ } TestCases[] = {
+ {"gregorian", FALSE, DEFAULT_START, DEFAULT_END},
+ {"japanese", FALSE, 596937600000.0, DEFAULT_END}, // 1988-12-01T00:00Z, Showa 63
+ {"buddhist", FALSE, DEFAULT_START, DEFAULT_END},
+ {"roc", FALSE, DEFAULT_START, DEFAULT_END},
+ {"persian", FALSE, DEFAULT_START, DEFAULT_END},
+ {"islamic-civil", FALSE, DEFAULT_START, DEFAULT_END},
+ {"islamic", FALSE, DEFAULT_START, 800000}, // Approx. 2250 years from now, after which some rounding errors occur in Islamic calendar
+ {"hebrew", TRUE, DEFAULT_START, DEFAULT_END},
+ {"chinese", TRUE, DEFAULT_START, DEFAULT_END},
+ {"dangi", TRUE, DEFAULT_START, DEFAULT_END},
+ {"indian", FALSE, DEFAULT_START, DEFAULT_END},
+ {"coptic", FALSE, DEFAULT_START, DEFAULT_END},
+ {"ethiopic", FALSE, DEFAULT_START, DEFAULT_END},
+ {"ethiopic-amete-alem", FALSE, DEFAULT_START, DEFAULT_END},
+ {NULL, FALSE, 0, 0}
+ };
+
+ int16_t i = 0;
+ char buf[64];
+
+ for (i = 0; TestCases[i].type; i++) {
+ UErrorCode status = U_ZERO_ERROR;
+ uprv_strcpy(buf, "root@calendar=");
+ strcat(buf, TestCases[i].type);
+ Calendar *cal = Calendar::createInstance(buf, status);
+ if (failure(status, "Calendar::createInstance", TRUE)) {
+ continue;
+ }
+ if (uprv_strcmp(cal->getType(), TestCases[i].type) != 0) {
+ errln((UnicodeString)"FAIL: Wrong calendar type: " + cal->getType()
+ + " Requested: " + TestCases[i].type);
+ delete cal;
+ continue;
+ }
+ // Do the test
+ doTheoreticalLimitsTest(*cal, TestCases[i].hasLeapMonth);
+ doLimitsTest(*cal, TestCases[i].actualTestStart,TestCases[i].actualTestEnd);
+ delete cal;
+ }
+}
+
+void
+CalendarLimitTest::doTheoreticalLimitsTest(Calendar& cal, UBool leapMonth) {
+ const char* calType = cal.getType();
+
+ int32_t nDOW = cal.getMaximum(UCAL_DAY_OF_WEEK);
+ int32_t maxDOY = cal.getMaximum(UCAL_DAY_OF_YEAR);
+ int32_t lmaxDOW = cal.getLeastMaximum(UCAL_DAY_OF_YEAR);
+ int32_t maxWOY = cal.getMaximum(UCAL_WEEK_OF_YEAR);
+ int32_t lmaxWOY = cal.getLeastMaximum(UCAL_WEEK_OF_YEAR);
+ int32_t maxM = cal.getMaximum(UCAL_MONTH) + 1;
+ int32_t lmaxM = cal.getLeastMaximum(UCAL_MONTH) + 1;
+ int32_t maxDOM = cal.getMaximum(UCAL_DAY_OF_MONTH);
+ int32_t lmaxDOM = cal.getLeastMaximum(UCAL_DAY_OF_MONTH);
+ int32_t maxDOWIM = cal.getMaximum(UCAL_DAY_OF_WEEK_IN_MONTH);
+ int32_t lmaxDOWIM = cal.getLeastMaximum(UCAL_DAY_OF_WEEK_IN_MONTH);
+ int32_t maxWOM = cal.getMaximum(UCAL_WEEK_OF_MONTH);
+ int32_t lmaxWOM = cal.getLeastMaximum(UCAL_WEEK_OF_MONTH);
+ int32_t minDaysInFirstWeek = cal.getMinimalDaysInFirstWeek();
+
+ // Day of year
+ int32_t expected;
+ if (!leapMonth) {
+ expected = maxM*maxDOM;
+ if (maxDOY > expected) {
+ errln((UnicodeString)"FAIL: [" + calType + "] Maximum value of DAY_OF_YEAR is too big: "
+ + maxDOY + "/expected: <=" + expected);
+ }
+ expected = lmaxM*lmaxDOM;
+ if (lmaxDOW < expected) {
+ errln((UnicodeString)"FAIL: [" + calType + "] Least maximum value of DAY_OF_YEAR is too small: "
+ + lmaxDOW + "/expected: >=" + expected);
+ }
+ }
+
+ // Week of year
+ expected = maxDOY/nDOW + 1;
+ if (maxWOY > expected) {
+ errln((UnicodeString)"FAIL: [" + calType + "] Maximum value of WEEK_OF_YEAR is too big: "
+ + maxWOY + "/expected: <=" + expected);
+ }
+ expected = lmaxDOW/nDOW;
+ if (lmaxWOY < expected) {
+ errln((UnicodeString)"FAIL: [" + calType + "] Least maximum value of WEEK_OF_YEAR is too small: "
+ + lmaxWOY + "/expected >=" + expected);
+ }
+
+ // Day of week in month
+ expected = (maxDOM + nDOW - 1)/nDOW;
+ if (maxDOWIM != expected) {
+ errln((UnicodeString)"FAIL: [" + calType + "] Maximum value of DAY_OF_WEEK_IN_MONTH is incorrect: "
+ + maxDOWIM + "/expected: " + expected);
+ }
+ expected = (lmaxDOM + nDOW - 1)/nDOW;
+ if (lmaxDOWIM != expected) {
+ errln((UnicodeString)"FAIL: [" + calType + "] Least maximum value of DAY_OF_WEEK_IN_MONTH is incorrect: "
+ + lmaxDOWIM + "/expected: " + expected);
+ }
+
+ // Week of month
+ expected = (maxDOM + (nDOW - 1) + (nDOW - minDaysInFirstWeek)) / nDOW;
+ if (maxWOM != expected) {
+ errln((UnicodeString)"FAIL: [" + calType + "] Maximum value of WEEK_OF_MONTH is incorrect: "
+ + maxWOM + "/expected: " + expected);
+ }
+ expected = (lmaxDOM + (nDOW - minDaysInFirstWeek)) / nDOW;
+ if (lmaxWOM != expected) {
+ errln((UnicodeString)"FAIL: [" + calType + "] Least maximum value of WEEK_OF_MONTH is incorrect: "
+ + lmaxWOM + "/expected: " + expected);
+ }
+}
+
+void
+CalendarLimitTest::doLimitsTest(Calendar& cal, UDate startDate, int32_t endTime) {
+ int32_t testTime = quick ? ( endTime / 40 ) : endTime;
+ doLimitsTest(cal, NULL /*default fields*/, startDate, testTime);
+}
+
+void
+CalendarLimitTest::doLimitsTest(Calendar& cal,
+ const int32_t* fieldsToTest,
+ UDate startDate,
+ int32_t testDuration) {
+ static const int32_t FIELDS[] = {
+ UCAL_ERA,
+ UCAL_YEAR,
+ UCAL_MONTH,
+ UCAL_WEEK_OF_YEAR,
+ UCAL_WEEK_OF_MONTH,
+ UCAL_DAY_OF_MONTH,
+ UCAL_DAY_OF_YEAR,
+ UCAL_DAY_OF_WEEK_IN_MONTH,
+ UCAL_YEAR_WOY,
+ UCAL_EXTENDED_YEAR,
+ -1,
+ };
+
+ static const char* FIELD_NAME[] = {
+ "ERA", "YEAR", "MONTH", "WEEK_OF_YEAR", "WEEK_OF_MONTH",
+ "DAY_OF_MONTH", "DAY_OF_YEAR", "DAY_OF_WEEK",
+ "DAY_OF_WEEK_IN_MONTH", "AM_PM", "HOUR", "HOUR_OF_DAY",
+ "MINUTE", "SECOND", "MILLISECOND", "ZONE_OFFSET",
+ "DST_OFFSET", "YEAR_WOY", "DOW_LOCAL", "EXTENDED_YEAR",
+ "JULIAN_DAY", "MILLISECONDS_IN_DAY",
+ "IS_LEAP_MONTH"
+ };
+
+ UErrorCode status = U_ZERO_ERROR;
+ int32_t i, j;
+ UnicodeString ymd;
+
+ GregorianCalendar greg(status);
+ if (failure(status, "new GregorianCalendar")) {
+ return;
+ }
+ greg.setTime(startDate, status);
+ if (failure(status, "GregorianCalendar::setTime")) {
+ return;
+ }
+ logln((UnicodeString)"Start: " + startDate);
+
+ if (fieldsToTest == NULL) {
+ fieldsToTest = FIELDS;
+ }
+
+
+ // Keep a record of minima and maxima that we actually see.
+ // These are kept in an array of arrays of hashes.
+ int32_t limits[UCAL_FIELD_COUNT][4];
+ for (j = 0; j < UCAL_FIELD_COUNT; j++) {
+ limits[j][0] = INT32_MAX;
+ limits[j][1] = INT32_MIN;
+ limits[j][2] = INT32_MAX;
+ limits[j][3] = INT32_MIN;
+ }
+
+ // This test can run for a long time; show progress.
+ UDate millis = ucal_getNow();
+ UDate mark = millis + 5000; // 5 sec
+ millis -= testDuration * 1000; // stop time if testDuration<0
+
+ for (i = 0;
+ testDuration > 0 ? i < testDuration
+ : ucal_getNow() < millis;
+ ++i) {
+ if (ucal_getNow() >= mark) {
+ logln((UnicodeString)"(" + i + " days)");
+ mark += 5000; // 5 sec
+ }
+ UDate testMillis = greg.getTime(status);
+ cal.setTime(testMillis, status);
+ cal.setMinimalDaysInFirstWeek(1);
+ if (failure(status, "Calendar set/getTime")) {
+ return;
+ }
+ for (j = 0; fieldsToTest[j] >= 0; ++j) {
+ UCalendarDateFields f = (UCalendarDateFields)fieldsToTest[j];
+ int32_t v = cal.get(f, status);
+ int32_t minActual = cal.getActualMinimum(f, status);
+ int32_t maxActual = cal.getActualMaximum(f, status);
+ int32_t minLow = cal.getMinimum(f);
+ int32_t minHigh = cal.getGreatestMinimum(f);
+ int32_t maxLow = cal.getLeastMaximum(f);
+ int32_t maxHigh = cal.getMaximum(f);
+
+ if (limits[j][0] > minActual) {
+ // the minimum
+ limits[j][0] = minActual;
+ }
+ if (limits[j][1] < minActual) {
+ // the greatest minimum
+ limits[j][1] = minActual;
+ }
+ if (limits[j][2] > maxActual) {
+ // the least maximum
+ limits[j][2] = maxActual;
+ }
+ if (limits[j][3] < maxActual) {
+ // the maximum
+ limits[j][3] = maxActual;
+ }
+
+ if (minActual < minLow || minActual > minHigh) {
+ errln((UnicodeString)"Fail: [" + cal.getType() + "] " +
+ ymdToString(cal, ymd) +
+ " Range for min of " + FIELD_NAME[f] + "(" + f +
+ ")=" + minLow + ".." + minHigh +
+ ", actual_min=" + minActual);
+ }
+ if (maxActual < maxLow || maxActual > maxHigh) {
+ errln((UnicodeString)"Fail: [" + cal.getType() + "] " +
+ ymdToString(cal, ymd) +
+ " Range for max of " + FIELD_NAME[f] + "(" + f +
+ ")=" + maxLow + ".." + maxHigh +
+ ", actual_max=" + maxActual);
+ }
+ if (v < minActual || v > maxActual) {
+ // timebomb per #9967, fix with #9972
+ if ( isICUVersionBefore(52,0,2) && uprv_strcmp(cal.getType(), "dangi") == 0 &&
+ testMillis >= 1865635198000.0 ) { // Feb 2029 gregorian, end of dangi 4361
+ logln((UnicodeString)"Fail: [" + cal.getType() + "] " +
+ ymdToString(cal, ymd) +
+ " " + FIELD_NAME[f] + "(" + f + ")=" + v +
+ ", actual=" + minActual + ".." + maxActual +
+ ", allowed=(" + minLow + ".." + minHigh + ")..(" +
+ maxLow + ".." + maxHigh + ")");
+ } else {
+ errln((UnicodeString)"Fail: [" + cal.getType() + "] " +
+ ymdToString(cal, ymd) +
+ " " + FIELD_NAME[f] + "(" + f + ")=" + v +
+ ", actual=" + minActual + ".." + maxActual +
+ ", allowed=(" + minLow + ".." + minHigh + ")..(" +
+ maxLow + ".." + maxHigh + ")");
+ }
+ }
+ }
+ greg.add(UCAL_DAY_OF_YEAR, 1, status);
+ if (failure(status, "Calendar::add")) {
+ return;
+ }
+ }
+
+ // Check actual maxima and minima seen against ranges returned
+ // by API.
+ UnicodeString buf;
+ for (j = 0; fieldsToTest[j] >= 0; ++j) {
+ int32_t rangeLow, rangeHigh;
+ UBool fullRangeSeen = TRUE;
+ UCalendarDateFields f = (UCalendarDateFields)fieldsToTest[j];
+
+ buf.remove();
+ buf.append((UnicodeString)"[" + cal.getType() + "] " + FIELD_NAME[f]);
+
+ // Minumum
+ rangeLow = cal.getMinimum(f);
+ rangeHigh = cal.getGreatestMinimum(f);
+ if (limits[j][0] != rangeLow || limits[j][1] != rangeHigh) {
+ fullRangeSeen = FALSE;
+ }
+ buf.append((UnicodeString)" minima range=" + rangeLow + ".." + rangeHigh);
+ buf.append((UnicodeString)" minima actual=" + limits[j][0] + ".." + limits[j][1]);
+
+ // Maximum
+ rangeLow = cal.getLeastMaximum(f);
+ rangeHigh = cal.getMaximum(f);
+ if (limits[j][2] != rangeLow || limits[j][3] != rangeHigh) {
+ fullRangeSeen = FALSE;
+ }
+ buf.append((UnicodeString)" maxima range=" + rangeLow + ".." + rangeHigh);
+ buf.append((UnicodeString)" maxima actual=" + limits[j][2] + ".." + limits[j][3]);
+
+ if (fullRangeSeen) {
+ logln((UnicodeString)"OK: " + buf);
+ } else {
+ // This may or may not be an error -- if the range of dates
+ // we scan over doesn't happen to contain a minimum or
+ // maximum, it doesn't mean some other range won't.
+ logln((UnicodeString)"Warning: " + buf);
+ }
+ }
+
+ logln((UnicodeString)"End: " + greg.getTime(status));
+}
+
+UnicodeString&
+CalendarLimitTest::ymdToString(const Calendar& cal, UnicodeString& str) {
+ UErrorCode status = U_ZERO_ERROR;
+ str.remove();
+ str.append((UnicodeString)"" + cal.get(UCAL_EXTENDED_YEAR, status)
+ + "/" + (cal.get(UCAL_MONTH, status) + 1)
+ + (cal.get(UCAL_IS_LEAP_MONTH, status) == 1 ? "(leap)" : "")
+ + "/" + cal.get(UCAL_DATE, status)
+ + " " + cal.get(UCAL_HOUR_OF_DAY, status)
+ + ":" + cal.get(UCAL_MINUTE, status)
+ + " zone(hrs) " + cal.get(UCAL_ZONE_OFFSET, status)/(60.0*60.0*1000.0)
+ + " dst(hrs) " + cal.get(UCAL_DST_OFFSET, status)/(60.0*60.0*1000.0)
+ + ", time(millis)=" + cal.getTime(status));
+ return str;
+}
+
#endif /* #if !UCONFIG_NO_FORMATTING */
// eof