| /******************************************************************** |
| * 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 <float.h> |
| #include <math.h> |
| |
| void CalendarLimitTest::runIndexedTest( int32_t index, UBool exec, 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; |
| UDate d = millis; |
| 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; |
| while (TRUE) { |
| 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; |
| while (TRUE) { |
| 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 |