ICU-1351 make fieldDifference handle leap years and large ranges better
X-SVN-Rev: 8419
This commit is contained in:
parent
4f62829e39
commit
621729e015
@ -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;
|
||||
}
|
||||
|
||||
// -------------------------------------
|
||||
|
@ -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)
|
||||
|
@ -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) ;
|
||||
|
Loading…
Reference in New Issue
Block a user