ICU-1351 make fieldDifference handle leap years and large ranges better

X-SVN-Rev: 8419
This commit is contained in:
Alan Liu 2002-04-09 22:06:14 +00:00
parent 4f62829e39
commit 621729e015
3 changed files with 150 additions and 23 deletions

View File

@ -493,34 +493,87 @@ Calendar::complete(UErrorCode& status)
// -------------------------------------
int32_t Calendar::fieldDifference(UDate when, EDateFields field, UErrorCode& status) {
if (U_FAILURE(status)) return 0;
int32_t result = 0;
double ms = getTimeInMillis(status);
if (ms < when) {
while (U_SUCCESS(status)) {
add(field, 1, status);
double newMs = getTimeInMillis(status);
if (newMs > when) {
setTimeInMillis(ms, status);
int32_t Calendar::fieldDifference(UDate targetMs, EDateFields field, UErrorCode& ec) {
if (U_FAILURE(ec)) return 0;
int32_t min = 0;
double startMs = getTimeInMillis(ec);
// Always add from the start millis. This accomodates
// operations like adding years from February 29, 2000 up to
// February 29, 2004. If 1, 1, 1, 1 is added to the year
// field, the DOM gets pinned to 28 and stays there, giving an
// incorrect DOM difference of 1. We have to add 1, reset, 2,
// reset, 3, reset, 4.
if (startMs < targetMs) {
int32_t max = 1;
// Find a value that is too large
while (U_SUCCESS(ec)) {
setTimeInMillis(startMs, ec);
add(field, max, ec);
double ms = getTimeInMillis(ec);
if (ms == targetMs) {
return max;
} else if (ms > targetMs) {
break;
} else {
max <<= 1;
if (max < 0) {
// Field difference too large to fit into int32_t
ec = U_ILLEGAL_ARGUMENT_ERROR;
}
}
ms = newMs;
++result;
}
} else if (ms > when) {
while (U_SUCCESS(status)) {
add(field, -1, status);
double newMs = getTimeInMillis(status);
if (newMs < when) {
setTimeInMillis(ms, status);
break;
// Do a binary search
while ((max - min) > 1 && U_SUCCESS(ec)) {
int32_t t = (min + max) / 2;
setTimeInMillis(startMs, ec);
add(field, t, ec);
double ms = getTimeInMillis(ec);
if (ms == targetMs) {
return t;
} else if (ms > targetMs) {
max = t;
} else {
min = t;
}
}
} else if (startMs > targetMs) {
int32_t max = -1;
// Find a value that is too small
while (U_SUCCESS(ec)) {
setTimeInMillis(startMs, ec);
add(field, max, ec);
double ms = getTimeInMillis(ec);
if (ms == targetMs) {
return max;
} else if (ms < targetMs) {
break;
} else {
max <<= 1;
if (max == 0) {
// Field difference too large to fit into int32_t
ec = U_ILLEGAL_ARGUMENT_ERROR;
}
}
}
// Do a binary search
while ((min - max) > 1 && U_SUCCESS(ec)) {
int32_t t = (min + max) / 2;
setTimeInMillis(startMs, ec);
add(field, t, ec);
double ms = getTimeInMillis(ec);
if (ms == targetMs) {
return t;
} else if (ms < targetMs) {
max = t;
} else {
min = t;
}
ms = newMs;
--result;
}
}
return result;
// Set calendar to end point
setTimeInMillis(startMs, ec);
add(field, min, ec);
return min;
}
// -------------------------------------

View File

@ -67,7 +67,7 @@ CalendarRegressionTest::runIndexedTest( int32_t index, UBool exec, const char* &
CASE(35,Test4197699);
CASE(36,TestJ81);
CASE(37,TestJ438);
CASE(38,TestLeapFieldDifference);
default: name = ""; break;
}
}
@ -1988,6 +1988,79 @@ void CalendarRegressionTest::TestJ438(void) {
delete pcal;
}
/**
* Test behavior of fieldDifference around leap years. Also test a large
* field difference to check binary search.
*/
void CalendarRegressionTest::TestLeapFieldDifference() {
UErrorCode ec = U_ZERO_ERROR;
Calendar* cal = Calendar::createInstance(ec);
if (cal == NULL || U_FAILURE(ec)) {
errln("FAIL: Calendar::createInstance()");
delete cal;
return;
}
cal->set(2004, Calendar::FEBRUARY, 29);
UDate date2004 = cal->getTime(ec);
cal->set(2000, Calendar::FEBRUARY, 29);
UDate date2000 = cal->getTime(ec);
if (U_FAILURE(ec)) {
errln("FAIL: getTime()");
delete cal;
return;
}
int32_t y = cal->fieldDifference(date2004, Calendar::YEAR, ec);
int32_t d = cal->fieldDifference(date2004, Calendar::DAY_OF_YEAR, ec);
if (U_FAILURE(ec)) {
errln("FAIL: fieldDifference()");
delete cal;
return;
}
if (d == 0) {
logln((UnicodeString)"Ok: 2004/Feb/29 - 2000/Feb/29 = " + y + " years, " + d + " days");
} else {
errln((UnicodeString)"FAIL: 2004/Feb/29 - 2000/Feb/29 = " + y + " years, " + d + " days");
}
cal->setTime(date2004, ec);
y = cal->fieldDifference(date2000, Calendar::YEAR, ec);
d = cal->fieldDifference(date2000, Calendar::DAY_OF_YEAR, ec);
if (U_FAILURE(ec)) {
errln("FAIL: setTime() / fieldDifference()");
delete cal;
return;
}
if (d == 0) {
logln((UnicodeString)"Ok: 2000/Feb/29 - 2004/Feb/29 = " + y + " years, " + d + " days");
} else {
errln((UnicodeString)"FAIL: 2000/Feb/29 - 2004/Feb/29 = " + y + " years, " + d + " days");
}
// Test large difference
cal->set(2001, Calendar::APRIL, 5); // 2452005
UDate ayl = cal->getTime(ec);
cal->set(1964, Calendar::SEPTEMBER, 7); // 2438646
UDate asl = cal->getTime(ec);
if (U_FAILURE(ec)) {
errln("FAIL: getTime()");
delete cal;
return;
}
d = cal->fieldDifference(ayl, Calendar::DAY_OF_MONTH, ec);
cal->setTime(ayl, ec);
int32_t d2 = cal->fieldDifference(asl, Calendar::DAY_OF_MONTH, ec);
if (U_FAILURE(ec)) {
errln("FAIL: setTime() / fieldDifference()");
delete cal;
return;
}
if (d == -d2 && d == 13359) {
logln((UnicodeString)"Ok: large field difference symmetrical " + d);
} else {
logln((UnicodeString)"FAIL: large field difference incorrect " + d + ", " + d2 +
", expect +/- 13359");
}
delete cal;
}
UDate
CalendarRegressionTest::makeDate(int32_t y, int32_t m, int32_t d,
int32_t hr, int32_t min, int32_t sec)

View File

@ -60,6 +60,7 @@ public:
void Test4197699(void);
void TestJ81(void);
void TestJ438(void);
void TestLeapFieldDifference(void);
void printdate(GregorianCalendar *cal, const char *string);
void dowTest(UBool lenient) ;