]> git.saurik.com Git - apple/icu.git/blobdiff - icuSources/test/intltest/callimts.cpp
ICU-511.32.tar.gz
[apple/icu.git] / icuSources / test / intltest / callimts.cpp
index 40ad122bf47a1bf4d93f4cd68d08e63e5437a225..d53f363b4fd0a732287dae287398659f2b4fc318 100644 (file)
@@ -1,6 +1,6 @@
 /***********************************************************************
  * COPYRIGHT: 
- * Copyright (c) 1997-2006, International Business Machines Corporation
+ * Copyright (c) 1997-2013, International Business Machines Corporation
  * and others. All Rights Reserved.
  ***********************************************************************/
 
@@ -15,6 +15,7 @@
 #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*/ )
@@ -23,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;
     }
 }
@@ -40,7 +49,7 @@ 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;
@@ -84,11 +93,11 @@ 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) {
@@ -96,7 +105,7 @@ CalendarLimitTest::TestCalendarLimit()
        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
@@ -130,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