Why doesn't this work?
@Test
public void test_calendar() throws DatatypeConfigurationException {
ZonedDateTime zdt = ZonedDateTime.now();
GregorianCalendar gc = GregorianCalendar.from(zdt);
XMLGregorianCalendar xgc = DatatypeFactory.newInstance().newXMLGregorianCalendar(gc);
assertThat(gc.toZonedDateTime(), comparesEqualTo(zdt));
assertThat(xgc.toGregorianCalendar(), comparesEqualTo(gc));
assertThat(xgc.toGregorianCalendar().toZonedDateTime(), comparesEqualTo(zdt)); // <== fails here
}
The above code fails with:
Expected: a value equal to <2025-06-19T18:13:44.885-05:00[America/Chicago]>
but: <2025-06-19T18:13:44.885-05:00[GMT-05:00]> was greater than <2025-06-19T18:13:44.885-05:00[America/Chicago]>
I see that the time zones don't match, but why don't the gc to zdt and xgc to gc fail?
Edit: removed superfluous code.
XMLGregorianCalendar truncates fractional seconds and also returns the second type of ZoneId while ZonedDateTime returns the third type.
The comparison could fail in either of both in that order.
First, comparesEqualTo
uses comparesTo
of the ZonedDateTime
object
comparesEqualTo
public static <T extends java.lang.Comparable> Matcher comparesEqualTo(T value)
Creates a matcher of Comparable object that matches when the examined object is equal to the specified value, as reported by the compareTo method of the examined object.
ZonedDateTime.compareTo()
inherits from ChronoZonedDateTime.compareTo()
The comparison is based first on the instant, then on the local date-time, then on the zone ID, then on the chronology.
The comparison fails at instant level in this case but would also fail on zoneId level as can be seen on JetBrain source code or OpenJdk source code.
import java.time.ZonedDateTime;
import java.time.temporal.ChronoField;
import java.util.GregorianCalendar;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
public class XmlDateTest {
public static void main(String[] args) throws DatatypeConfigurationException {
ZonedDateTime zdt = ZonedDateTime.now();
GregorianCalendar gc = GregorianCalendar.from(zdt);
XMLGregorianCalendar xgc = DatatypeFactory.newInstance().newXMLGregorianCalendar(gc);
System.out.println("zdt\t" + zdt);
System.out.println("zdt_fraction\t" + zdt.getLong(ChronoField.NANO_OF_SECOND) / 1E9);
System.out.println("xgc_fraction\t" + xgc.getFractionalSecond());
// zero if equal
System.out.println(
"zdt equals xgc? " + zdt.toInstant().compareTo(xgc.toGregorianCalendar().toZonedDateTime().toInstant()));
// zone id is also reported as a different string even if both are logically equivalent
System.out.println("zdt zone id\t" + zdt.getZone().getId());
System.out.println("xgc zone id\t" + xgc.toGregorianCalendar().toZonedDateTime().getZone().getId());
System.out.println(System.getProperty("os.name"));
System.out.println(System.getProperty("java.runtime.name"));
System.out.println(System.getProperty("java.version"));
}
}
Result
zdt 2025-06-19T21:37:32.676844595-03:00[America/Argentina/Mendoza]
zdt_fraction 0.676844595
xgc_fraction 0.676
zdt equals xgc? 844595
zdt zone id America/Argentina/Mendoza
xgc zone id GMT-03:00
As an special use case, there's no difference at start of day since there are no fractional seconds involved but compareTo
might be triggering on ZoneId.
zdt = ZonedDateTime.of(LocalDate.now().atStartOfDay(), ZoneId.systemDefault());
zdt 2025-06-20T00:00-03:00[America/Argentina/Mendoza]
zdt_nanos 0.0
xgc_nanos 0.000
zdt equals xgc? 0
zdt zone id America/Argentina/Mendoza
xgc zone id GMT-03:00
Test env
Linux
OpenJDK Runtime Environment
22.0.1
Linux
OpenJDK Runtime Environment
17.0.15
NOTE: time precision might depend on OS and JVM version