X-Git-Url: https://git.saurik.com/apple/icu.git/blobdiff_plain/b75a7d8f3b4adbae880cab104ce2c6a50eee4db2..e4f10fab0c078f399c9deef476d9c9b73b47dff8:/icuSources/test/intltest/callimts.cpp?ds=sidebyside diff --git a/icuSources/test/intltest/callimts.cpp b/icuSources/test/intltest/callimts.cpp index df6147e4..d53f363b 100644 --- a/icuSources/test/intltest/callimts.cpp +++ b/icuSources/test/intltest/callimts.cpp @@ -1,18 +1,21 @@ -/******************************************************************** +/*********************************************************************** * 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*/ ) @@ -21,12 +24,20 @@ void CalendarLimitTest::runIndexedTest( int32_t index, UBool exec, const char* & 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; } } @@ -38,8 +49,9 @@ void CalendarLimitTest::runIndexedTest( int32_t index, UBool exec, const char* & // ------------------------------------- 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; @@ -48,9 +60,11 @@ CalendarLimitTest::test(UDate millis, U_NAMESPACE_QUALIFIER Calendar* cal, U_NAM 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); } @@ -79,15 +93,20 @@ CalendarLimitTest::withinErr(double a, double b, double err) } 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 @@ -95,9 +114,11 @@ CalendarLimitTest::TestCalendarLimit() // 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); } @@ -118,6 +139,337 @@ CalendarLimitTest::TestCalendarLimit() 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