ICU-2792 fix anomalous behavior when adding across a DST boundary
X-SVN-Rev: 11866
This commit is contained in:
parent
dbf22af2dd
commit
9b20bed1a3
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user