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
|
@Override
|
||||||
public long round(long utcMillis) {
|
public long round(long utcMillis) {
|
||||||
long rounded = field.roundFloor(utcMillis);
|
long rounded = field.roundFloor(utcMillis);
|
||||||
if (timeZone.isFixed() == false && timeZone.getOffset(utcMillis) != timeZone.getOffset(rounded)) {
|
if (timeZone.isFixed() == false) {
|
||||||
// in this case, we crossed a time zone transition. In some edge
|
// special cases for non-fixed time zones with dst transitions
|
||||||
// cases this will
|
if (timeZone.getOffset(utcMillis) != timeZone.getOffset(rounded)) {
|
||||||
// result in a value that is not a rounded value itself. We need
|
/*
|
||||||
// to round again
|
* the offset change indicates a dst transition. In some
|
||||||
// to make sure. This will have no affect in cases where
|
* edge cases this will result in a value that is not a
|
||||||
// 'rounded' was already a proper
|
* rounded value before the transition. We round again to
|
||||||
// rounded value
|
* make sure we really return a rounded value. This will
|
||||||
rounded = field.roundFloor(rounded);
|
* 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);
|
assert rounded == field.roundFloor(rounded);
|
||||||
return 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
|
* 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)
|
* Joda 2.9.4 (see https://github.com/JodaOrg/joda-time/issues/373)
|
||||||
|
|
Loading…
Reference in New Issue