Using, java.time.Instant
and javax.xml.datatype.XMLGregorianCalendar
, I’m trying to convert from one to the other without losing precision.
Why is this test not passing and how to fix it?
class FooTest { @Test void shouldConvertBackToSameInstant() throws DatatypeConfigurationException { Instant initialInstant = Instant.now(); XMLGregorianCalendar xmlGregorianCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(String.valueOf(initialInstant)); Instant convertedInstant = xmlGregorianCalendar.toGregorianCalendar().toInstant(); assertEquals(initialInstant, convertedInstant); } }
org.opentest4j.AssertionFailedError: Expected :2021-03-10T08:34:30.700715078Z Actual :2021-03-10T08:34:30.700Z
Advertisement
Answer
Why is this test not passing?
Your XMLGregorianCalendar
has got the full precision of your Instant
. It’s your conversion back to Instant
that is lossy. You are converting to GregorianCalendar
through the call to .toGregorianCalendar()
. A GreogrianCalendar
only has got millisecond precision, so this is where you lose precision.
How to fix it?
Just as you go through a String
for your first conversion, you may do the same for the opposite conversion:
Instant convertedInstant = Instant.parse(xmlGregorianCalendar.toString());
Now your test passes (it did on my Java 9).
Concluding remarks
Instant
has got precision up to 9 decimals on the seconds (so nanoseconds). XMLGregorianCalendar
has got virtually infinite precision. So if you have converted an Instant
to an XMLGregorianCalendar
, you know that it hasn’t got precision beyond 9 decimals, so you can safely convert it back. If on the other hand from somewhere you should get an XMLGregorianCalendar
with still higher precision (10 decimals or more), the conversion I have shown you will fail with a DateTimeParseException
.