Fix time zone rounding edge case for DST overlaps
When using TimeUnitRounding with a DAY_OF_MONTH unit, failing tests in #20833 uncovered an issue when the DST shift happenes just one hour after midnight local time and sets back the clock to midnight, leading to an overlap. Previously this would lead to two different rounding values, depending on whether a date before or after the transition was rounded. This change detects this special case and correct for it by using the previous rounding date for both cases. Closes #20833
This commit is contained in:
parent
f03723a812
commit
cd4634bdc6
|
@ -128,15 +128,38 @@ public abstract class Rounding implements Streamable {
|
|||
@Override
|
||||
public long round(long utcMillis) {
|
||||
long rounded = field.roundFloor(utcMillis);
|
||||
if (timeZone.isFixed() == false && timeZone.getOffset(utcMillis) != timeZone.getOffset(rounded)) {
|
||||
// in this case, we crossed a time zone transition. In some edge
|
||||
// cases this will
|
||||
// result in a value that is not a rounded value itself. We need
|
||||
// to round again
|
||||
// to make sure. This will have no affect in cases where
|
||||
// 'rounded' was already a proper
|
||||
// rounded value
|
||||
rounded = field.roundFloor(rounded);
|
||||
if (timeZone.isFixed() == false) {
|
||||
// special cases for non-fixed time zones with dst transitions
|
||||
if (timeZone.getOffset(utcMillis) != timeZone.getOffset(rounded)) {
|
||||
/*
|
||||
* the offset change indicates a dst transition. In some
|
||||
* edge cases this will result in a value that is not a
|
||||
* rounded value before the transition. We round again to
|
||||
* make sure we really return a rounded value. This will
|
||||
* have no effect in cases where we already had a valid
|
||||
* rounded value
|
||||
*/
|
||||
rounded = field.roundFloor(rounded);
|
||||
} else {
|
||||
/*
|
||||
* check if the current time instant is at a start of a DST
|
||||
* overlap by comparing the offset of the instant and the
|
||||
* previous millisecond. We want to detect negative offset
|
||||
* changes that result in an overlap
|
||||
*/
|
||||
if (timeZone.getOffset(rounded) < timeZone.getOffset(rounded - 1)) {
|
||||
/*
|
||||
* we are rounding a date just after a DST overlap. if
|
||||
* the overlap is smaller than the time unit we are
|
||||
* rounding to, we want to add the overlapping part to
|
||||
* the following rounding interval
|
||||
*/
|
||||
long previousRounded = field.roundFloor(rounded - 1);
|
||||
if (rounded - previousRounded < field.getDurationField().getUnitMillis()) {
|
||||
rounded = previousRounded;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
assert rounded == field.roundFloor(rounded);
|
||||
return rounded;
|
||||
|
|
|
@ -514,6 +514,44 @@ public class TimeZoneRoundingTests extends ESTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* tests for dst transition with overlaps and day roundings.
|
||||
*/
|
||||
public void testDST_END_Edgecases() {
|
||||
// First case, dst happens at 1am local time, switching back one hour.
|
||||
// We want the overlapping hour to count for the next day, making it a 25h interval
|
||||
|
||||
DateTimeUnit timeUnit = DateTimeUnit.DAY_OF_MONTH;
|
||||
DateTimeZone tz = DateTimeZone.forID("Atlantic/Azores");
|
||||
Rounding rounding = new Rounding.TimeUnitRounding(timeUnit, tz);
|
||||
|
||||
// Sunday, 29 October 2000, 01:00:00 clocks were turned backward 1 hour
|
||||
// to Sunday, 29 October 2000, 00:00:00 local standard time instead
|
||||
|
||||
long midnightBeforeTransition = time("2000-10-29T00:00:00", tz);
|
||||
long nextMidnight = time("2000-10-30T00:00:00", tz);
|
||||
|
||||
assertInterval(midnightBeforeTransition, nextMidnight, rounding, 25 * 60, tz);
|
||||
|
||||
// Second case, dst happens at 0am local time, switching back one hour to 23pm local time.
|
||||
// We want the overlapping hour to count for the previous day here
|
||||
|
||||
tz = DateTimeZone.forID("America/Lima");
|
||||
rounding = new Rounding.TimeUnitRounding(timeUnit, tz);
|
||||
|
||||
// Sunday, 1 April 1990, 00:00:00 clocks were turned backward 1 hour to
|
||||
// Saturday, 31 March 1990, 23:00:00 local standard time instead
|
||||
|
||||
midnightBeforeTransition = time("1990-03-31T00:00:00.000-04:00");
|
||||
nextMidnight = time("1990-04-01T00:00:00.000-05:00");
|
||||
assertInterval(midnightBeforeTransition, nextMidnight, rounding, 25 * 60, tz);
|
||||
|
||||
// make sure the next interval is 24h long again
|
||||
long midnightAfterTransition = time("1990-04-01T00:00:00.000-05:00");
|
||||
nextMidnight = time("1990-04-02T00:00:00.000-05:00");
|
||||
assertInterval(midnightAfterTransition, nextMidnight, rounding, 24 * 60, tz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that time zones are correctly parsed. There is a bug with
|
||||
* Joda 2.9.4 (see https://github.com/JodaOrg/joda-time/issues/373)
|
||||
|
|
Loading…
Reference in New Issue