blob: cb9058799d49dc2280874c0ca1742a27878121e8 [file] [log] [blame]
/********************************************************************
* 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, 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