/******************************************************************** * COPYRIGHT: * Copyright (c) 1997-1999, International Business Machines Corporation and * others. All Rights Reserved. ********************************************************************/ #include "callimts.h" #include "unicode/calendar.h" #include "unicode/gregocal.h" #include "unicode/datefmt.h" #include "unicode/smpdtfmt.h" #include #include void CalendarLimitTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ ) { if (exec) logln("TestSuite TestCalendarLimit"); switch (index) { // Re-enable this later case 0: name = "TestCalendarLimit"; if (exec) { logln("TestCalendarLimit---"); logln(""); TestCalendarLimit(); } break; default: name = ""; break; } } // ***************************************************************************** // class CalendarLimitTest // ***************************************************************************** // this is 2^52 - 1, the largest allowable mantissa with a 0 exponent in a 64-bit double const UDate CalendarLimitTest::EARLIEST_SUPPORTED_MILLIS = - 4503599627370495.0; const UDate CalendarLimitTest::LATEST_SUPPORTED_MILLIS = 4503599627370495.0; // ------------------------------------- void CalendarLimitTest::test(UDate millis, Calendar* cal, DateFormat* fmt) { UErrorCode exception = U_ZERO_ERROR; UnicodeString theDate; UErrorCode status = U_ZERO_ERROR; cal->setTime(millis, exception); if (U_SUCCESS(exception)) { 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 { logln(UnicodeString("OK: got ") + dt + ", wanted " + millis); logln(UnicodeString(" ") + theDate); } } } // ------------------------------------- double CalendarLimitTest::nextDouble(double a) { return uprv_nextDouble(a, TRUE); } double CalendarLimitTest::previousDouble(double a) { return uprv_nextDouble(a, FALSE); } UBool CalendarLimitTest::withinErr(double a, double b, double err) { return ( uprv_fabs(a - b) < uprv_fabs(a * err) ); } void CalendarLimitTest::TestCalendarLimit() { logln("Limit tests"); logln("--------------------"); UErrorCode status = U_ZERO_ERROR; explore2(EARLIEST_SUPPORTED_MILLIS); explore3(LATEST_SUPPORTED_MILLIS); Calendar *cal = Calendar::createInstance(status); if (failure(status, "Calendar::createInstance")) return; cal->adoptTimeZone(TimeZone::createTimeZone("Africa/Casablanca")); DateFormat *fmt = DateFormat::createDateTimeInstance(); fmt->adoptCalendar(cal); ((SimpleDateFormat*) fmt)->applyPattern("HH:mm:ss.SSS zzz, EEEE, MMMM d, yyyy G"); logln(""); logln("Round trip tests"); logln("--------------------"); // We happen to know that the upper failure point is between // 1e17 and 1e18. UDate m; for ( m = 1e17; m < 1e18; m *= 1.1) { test(m, cal, fmt); } for ( m = -1e14; m > -1e15; m *= 1.1) { test(m, cal, fmt); } test(EARLIEST_SUPPORTED_MILLIS, cal, fmt); test(previousDouble(EARLIEST_SUPPORTED_MILLIS), cal, fmt); delete fmt; } // ------------------------------------- void CalendarLimitTest::explore2(UDate expectedEarlyLimit) { UDate millis = - 1; int32_t* fields = new int32_t[3]; while (timeToFields(millis, fields)) millis *= 2; UDate bad = millis; UDate good = millis / 2; UDate mid; while ( ! withinErr(good, bad, 1e-15) ) { mid = (good + bad) / 2; if (timeToFields(mid, fields)) good = mid; else bad = mid; } timeToFields(good, fields); logln(UnicodeString(UnicodeString("Good: ")) + good + " " + fields[0] + "/" + fields[1] + "/" + fields[2]); timeToFields(bad, fields); logln(UnicodeString(UnicodeString("Bad: ")) + bad + " " + fields[0] + "/" + fields[1] + "/" + fields[2]); if (good <= expectedEarlyLimit) { logln("PASS: Limit <= expected."); } else errln(UnicodeString("FAIL: Expected limit ") + expectedEarlyLimit + "; got " + good); delete[] fields; } void CalendarLimitTest::explore3(UDate expectedLateLimit) { UDate millis = 1; int32_t* fields = new int32_t[3]; int32_t oldYear = -1; int32_t newYear = -1; for (;;) { if(! timeToFields(millis, fields)) break; newYear = fields[0]; if(newYear < oldYear) break; oldYear = newYear; millis *= 2; } // narrow the range a little oldYear = -1; newYear = -1; millis /= 2; for (;;) { if(! timeToFields(millis, fields)) break; newYear = fields[0]; if(newYear < oldYear) break; oldYear = newYear; millis *= 1.01; } // this isn't strictly accurate, but we are only trying to verify that // the Calendar breaks AFTER the latest date it is promised to work with UDate good = millis / 1.01; UDate bad = millis; timeToFields(good, fields); logln(UnicodeString(UnicodeString("Good: ")) + good + " " + fields[0] + "/" + fields[1] + "/" + fields[2]); timeToFields(bad, fields); logln(UnicodeString(UnicodeString("Bad: ")) + bad + " " + fields[0] + "/" + fields[1] + "/" + fields[2]); if (good >= expectedLateLimit) { logln("PASS: Limit >= expected."); } else errln(UnicodeString("FAIL: Expected limit ") + expectedLateLimit + "; got " + good); delete[] fields; } UDate CalendarLimitTest::gregorianCutover = - 12219292800000.0; // ------------------------------------- const int32_t CalendarLimitTest::kEpochStartAsJulianDay = 2440588; // January 1, 1970 (Gregorian) double CalendarLimitTest::millisToJulianDay(UDate millis) { return (double)kEpochStartAsJulianDay + floorDivide(millis, (double)millisPerDay); } int32_t CalendarLimitTest::julianDayOffset = 2440588; int32_t CalendarLimitTest::millisPerDay = 24 * 60 * 60 * 1000; int32_t CalendarLimitTest::YEAR = 0; int32_t CalendarLimitTest::MONTH = 1; int32_t CalendarLimitTest::DATE = 2; // ------------------------------------- double CalendarLimitTest::floorDivide(double numerator, double denominator) { // We do this computation in order to handle // a numerator of Long.MIN_VALUE correctly return uprv_floor(numerator / denominator); /* return (numerator >= 0) ? uprv_trunc(numerator / denominator) : uprv_trunc((numerator + 1) / denominator) - 1; */ } // ------------------------------------- int32_t CalendarLimitTest::floorDivide(int32_t numerator, int32_t denominator) { // We do this computation in order to handle // a numerator of Long.MIN_VALUE correctly return (numerator >= 0) ? numerator / denominator : ((numerator + 1) / denominator) - 1; } // ------------------------------------- int32_t CalendarLimitTest::floorDivide(int32_t numerator, int32_t denominator, int32_t remainder[]) { if (numerator >= 0) { remainder[0] = numerator % denominator; return numerator / denominator; } int32_t quotient = ((numerator + 1) / denominator) - 1; remainder[0] = numerator - (quotient * denominator); return quotient; } // ------------------------------------- int32_t CalendarLimitTest::floorDivide(double numerator, int32_t denominator, int32_t remainder[]) { if (numerator >= 0) { remainder[0] = (int32_t)uprv_fmod(numerator, denominator); return (int32_t)uprv_trunc(numerator / denominator); } int32_t quotient = (int32_t)(uprv_trunc((numerator + 1) / denominator) - 1); remainder[0] = (int32_t)(numerator - (quotient * denominator)); return quotient; } // ------------------------------------- const UDate CalendarLimitTest::kPapalCutover = (2299161.0 - kEpochStartAsJulianDay) * (double)millisPerDay; const int32_t CalendarLimitTest::kJan1_1JulianDay = 1721426; // January 1, year 1 (Gregorian) const int32_t CalendarLimitTest::kNumDays[] = {0,31,59,90,120,151,181,212,243,273,304,334}; // 0-based, for day-in-year const int32_t CalendarLimitTest::kLeapNumDays[] = {0,31,60,91,121,152,182,213,244,274,305,335}; // 0-based, for day-in-year const int32_t CalendarLimitTest::kMonthLength[] = {31,28,31,30,31,30,31,31,30,31,30,31}; // 0-based const int32_t CalendarLimitTest::kLeapMonthLength[] = {31,29,31,30,31,30,31,31,30,31,30,31}; // 0-based UBool CalendarLimitTest::timeToFields(UDate theTime, int32_t* fields) { if(uprv_isInfinite(theTime)) return FALSE; int32_t rawYear; int32_t year, month, date, dayOfWeek, dayOfYear, era; UBool isLeap; // Compute the year, month, and day of month from the given millis // {sfb} for simplicity's sake, assume no one will change the cutover date if (theTime >= kPapalCutover/*fNormalizedGregorianCutover*/) { // The Gregorian epoch day is zero for Monday January 1, year 1. double gregorianEpochDay = millisToJulianDay(theTime) - kJan1_1JulianDay; // Here we convert from the day number to the multiple radix // representation. We use 400-year, 100-year, and 4-year cycles. // For example, the 4-year cycle has 4 years + 1 leap day; giving // 1461 == 365*4 + 1 days. int32_t rem[1]; int32_t n400 = floorDivide(gregorianEpochDay, 146097, rem); // 400-year cycle length int32_t n100 = floorDivide(rem[0], 36524, rem); // 100-year cycle length int32_t n4 = floorDivide(rem[0], 1461, rem); // 4-year cycle length int32_t n1 = floorDivide(rem[0], 365, rem); rawYear = 400*n400 + 100*n100 + 4*n4 + n1; dayOfYear = rem[0]; // zero-based day of year if (n100 == 4 || n1 == 4) dayOfYear = 365; // Dec 31 at end of 4- or 400-yr cycle else ++rawYear; isLeap = ((rawYear&0x3) == 0) && // equiv. to (rawYear%4 == 0) (rawYear%100 != 0 || rawYear%400 == 0); // Gregorian day zero is a Monday dayOfWeek = (int32_t)uprv_fmod(gregorianEpochDay + 1, 7); } else { // The Julian epoch day (not the same as Julian Day) // is zero on Saturday December 30, 0 (Gregorian). double julianEpochDay = millisToJulianDay(theTime) - (kJan1_1JulianDay - 2); //rawYear = floorDivide(4 * julianEpochDay + 1464, 1461); rawYear = (int32_t) floorDivide(4*julianEpochDay + 1464, 1461.0); // Compute the Julian calendar day number for January 1, rawYear //double january1 = 365 * (rawYear - 1) + floorDivide(rawYear - 1, 4); double january1 = 365 * (rawYear - 1) + floorDivide(rawYear - 1, 4L); dayOfYear = (int32_t)uprv_fmod(julianEpochDay - january1, 365.0); // Julian leap years occurred historically every 4 years starting // with 8 AD. Before 8 AD the spacing is irregular; every 3 years // from 45 BC to 9 BC, and then none until 8 AD. However, we don't // implement this historical detail; instead, we implement the // computatinally cleaner proleptic calendar, which assumes // consistent 4-year cycles throughout time. isLeap = ((rawYear & 0x3) == 0); // equiv. to (rawYear%4 == 0) // Julian calendar day zero is a Saturday dayOfWeek = (int32_t)uprv_fmod(julianEpochDay-1, 7); } // Common Julian/Gregorian calculation int32_t correction = 0; int32_t march1 = isLeap ? 60 : 59; // zero-based DOY for March 1 if (dayOfYear >= march1) correction = isLeap ? 1 : 2; month = (12 * (dayOfYear + correction) + 6) / 367; // zero-based month date = dayOfYear - (isLeap ? kLeapNumDays[month] : kNumDays[month]) + 1; // one-based DOM // Normalize day of week dayOfWeek += (dayOfWeek < 0) ? (Calendar::SUNDAY+7) : Calendar::SUNDAY; era = GregorianCalendar::AD; year = rawYear; if (year < 1) { era = GregorianCalendar::BC; year = 1 - year; } //internalSet(ERA, era); //internalSet(YEAR, year); //internalSet(MONTH, month + JANUARY); // 0-based //internalSet(DATE, date); //internalSet(DAY_OF_WEEK, dayOfWeek); //internalSet(DAY_OF_YEAR, ++dayOfYear); // Convert from 0-based to 1-based fields[YEAR] = year; month += Calendar::JANUARY; fields[MONTH] = month; fields[DATE] = date; // month: 0 <= m <= 11 UBool monthLegal = ( (month - Calendar::JANUARY) >= 0 && (month - Calendar::JANUARY) <= 11 ); UBool dateLegal = ( date >= 1 && date <= (isLeap ? kLeapMonthLength[month - Calendar::JANUARY] : kMonthLength[month - Calendar::JANUARY])); UBool yearLegal = (year >= 0); return monthLegal && dateLegal && yearLegal; } // eof