ICU-2792 fix anomalous behavior when adding across a DST boundary

X-SVN-Rev: 11866
This commit is contained in:
Alan Liu 2003-05-09 19:13:59 +00:00
parent dbf22af2dd
commit 9b20bed1a3
2 changed files with 96 additions and 12 deletions

View File

@ -1365,7 +1365,7 @@ GregorianCalendar::aggregateStamp(int32_t stamp_a, int32_t stamp_b)
// -------------------------------------
void
GregorianCalendar::add(EDateFields field, int32_t amount, UErrorCode& status) {
add((UCalendarDateFields) field, amount, status);
add((UCalendarDateFields) field, amount, status);
}
void
@ -1482,19 +1482,35 @@ GregorianCalendar::add(UCalendarDateFields field, int32_t amount, UErrorCode& st
return;
}
// Save the current DST state.
// In order to keep the hour invariant (for fields where this is
// appropriate), record the DST_OFFSET before and after the add()
// operation. If it has changed, then adjust the millis to
// compensate.
int32_t dst = 0;
if (adjustDST)
dst = internalGet(UCAL_DST_OFFSET);
int32_t hour = 0;
if (adjustDST) {
dst = get(UCAL_DST_OFFSET, status);
hour = internalGet(UCAL_HOUR_OF_DAY);
}
setTimeInMillis(internalGetTime() + delta, status); // Automatically computes fields if necessary
setTimeInMillis(internalGetTime() + delta, status);
if (adjustDST) {
// Now do the DST adjustment alluded to above.
// Only call setTimeInMillis if necessary, because it's an expensive call.
dst -= internalGet(UCAL_DST_OFFSET);
if(dst!= 0)
setTimeInMillis(internalGetTime() + dst, status);
dst -= get(UCAL_DST_OFFSET, status);
if (dst != 0) {
// We have done an hour-invariant adjustment but the
// DST offset has altered. We adjust millis to keep
// the hour constant. In cases such as midnight after
// a DST change which occurs at midnight, there is the
// danger of adjusting into a different day. To avoid
// this we make the adjustment only if it actually
// maintains the hour.
UDate t = internalGetTime();
setTimeInMillis(t + dst, status);
if (get(UCAL_HOUR_OF_DAY, status) != hour) {
setTimeInMillis(t, status);
}
}
}
}
}
@ -1508,7 +1524,7 @@ GregorianCalendar::add(UCalendarDateFields field, int32_t amount, UErrorCode& st
void
GregorianCalendar::roll(EDateFields field, int32_t amount, UErrorCode& status) {
roll((UCalendarDateFields) field, amount, status);
roll((UCalendarDateFields) field, amount, status);
}
void
@ -1856,7 +1872,7 @@ GregorianCalendar::roll(UCalendarDateFields field, int32_t amount, UErrorCode& s
// -------------------------------------
int32_t
GregorianCalendar::getMinimum(EDateFields field) const {
return getMinimum((UCalendarDateFields) field);
return getMinimum((UCalendarDateFields) field);
}
int32_t
@ -2048,3 +2064,4 @@ U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */
//eof

View File

@ -78,6 +78,7 @@ CalendarRegressionTest::runIndexedTest( int32_t index, UBool exec, const char* &
CASE(40,test4059654);
CASE(41,test4092362);
CASE(42,TestWeekShift);
CASE(43,TestTimeZoneTransitionAdd);
default: name = ""; break;
}
}
@ -2161,6 +2162,72 @@ void CalendarRegressionTest::TestWeekShift() {
}
}
/**
* Make sure that when adding a day, we actually wind up in a
* different day. The DST adjustments we use to keep the hour
* constant across DST changes can backfire and change the day.
*/
void CalendarRegressionTest::TestTimeZoneTransitionAdd() {
UErrorCode ec = U_ZERO_ERROR;
Locale locale(Locale::getUS()); // could also be CHINA
SimpleDateFormat dateFormat("MM/dd/yyyy HH:mm z", locale, ec);
StringEnumeration *tz = TimeZone::createEnumeration();
if (tz == NULL) {
errln("FAIL: TimeZone::createEnumeration");
return;
}
UnicodeString buf1, buf2;
const UChar* id;
while ((id = tz->unext(NULL, ec)) != NULL && U_SUCCESS(ec)) {
if (U_FAILURE(ec)) {
errln("FAIL: StringEnumeration::unext");
break;
}
TimeZone *t = TimeZone::createTimeZone(id);
if (t == NULL) {
errln("FAIL: TimeZone::createTimeZone");
break;
}
dateFormat.setTimeZone(*t);
Calendar *cal = Calendar::createInstance(t, locale, ec);
if (cal == NULL || U_FAILURE(ec)) {
errln("FAIL: Calendar::createTimeZone");
delete cal;
break;
}
cal->clear();
// Scan the year 2003, overlapping the edges of the year
cal->set(UCAL_YEAR, 2002);
cal->set(UCAL_MONTH, UCAL_DECEMBER);
cal->set(UCAL_DATE, 25);
for (int32_t i=0; i<365+10 && U_SUCCESS(ec); ++i) {
UDate yesterday = cal->getTime(ec);
int32_t yesterday_day = cal->get(UCAL_DATE, ec);
cal->add(UCAL_DATE, 1, ec);
if (yesterday_day == cal->get(UCAL_DATE, ec)) {
errln(UnicodeString(id) + " " +
dateFormat.format(yesterday, buf1) + " +1d= " +
dateFormat.format(cal->getTime(ec), buf2));
buf1.truncate(0);
buf2.truncate(0);
}
}
}
if (U_FAILURE(ec)) {
errln("FAIL: %s", u_errorName(ec));
}
delete tz;
}
UDate
CalendarRegressionTest::makeDate(int32_t y, int32_t m, int32_t d,
int32_t hr, int32_t min, int32_t sec)