AbsoluteDate.getComponents() loses precision, produces invalid times
AbsoluteDate.getComponents(...)
carefully maintains full precision and passes two number to TimeComponents
. Then the TimeComponents
constructor adds those two numbers back together, losing some extra digits:
second = wholeSeconds + fractional;
This can cause second
to be 60.0
(or 61.0
during a leap second), an invalid time. This also means that new AbsoluteDate(date.getComponents(scale), scale).equals(date)
is sometimes usually false
but occasionally true
depending on cancellation in the least significant bits.
One possible solution is to use the same precision in AbsoluteDate
and TimeComponents
. This would mean using an int
for whole seconds and a double
for fractional seconds in TimeComponents
. Even with this option there is no guarantee that there would be a unique DateTimeComponents
for each AbsoluteDate
.
Another possible solution is to always round down when computing the sum. This would increase the error from half an ULP to a whole ULP (~ 7 fs). This would fix the invalid time problem, but would not address converting an AbsoluteDate
to components and back.
I'm leaning towards the second option.
Here is a failing test case:
@Test
public void testInvalidSecond() {
// setup
AbsoluteDate date = new AbsoluteDate(2017, 1, 1, utc)
.shiftedBy(59)
.shiftedBy(FastMath.nextDown(1.0));
// action
DateTimeComponents actual = date.getComponents(utc);
// verify
MatcherAssert.assertThat(actual.getTime().getSecond(),
CoreMatchers.is(FastMath.nextDown(60.0)));
}