The java.time API offers several types to represent moments in time, and choosing the wrong one to compute durations can produce
incorrect answers, especially around time zone differences and Daylight Saving Time (DST) transitions.
The LocalDateTime class in the java.time API represents calendar and clock-face information without any time zone or
offset from the UTC timeline. Arithmetic on this type is therefore a pure calendar and clock-face delta, not a measure of physical elapsed time.
When the two operands of Duration.between(time1, time2) or ChronoUnit.X.between(time1, time2) actually correspond to
moments observed in different time zones, the result silently differs from the real elapsed duration by the offset between the two zones and by any
Daylight Saving Time (DST) transition occurring within the window. To obtain the real physical elapsed time, both operands must first be anchored to
their zone by being converted to Instant or ZonedDateTime.
The computation does not throw any exception, so the wrong duration silently propagates into downstream logic, such as billing periods, service-level agreement (SLA) timers, scheduling windows, or travel itineraries. The defect is easy to miss in tests that run in a single time zone, and may only surface once the application is deployed across regions or runs through a DST transition.
When computing a duration between two LocalDateTime values observed in known time zones, convert each one with
.atZone(ZoneId) first, then apply Duration.between or ChronoUnit.X.between to the resulting
ZonedDateTime (or Instant) values.
The duration below is computed between two LocalDateTime values observed in different zones. Because LocalDateTime
carries no offset, the result is a purely arithmetic delta that ignores the 6-hour difference between New York and Paris.
LocalDateTime departureNY = LocalDateTime.of(2026, 3, 28, 22, 0); // America/New_York LocalDateTime arrivalParis = LocalDateTime.of(2026, 3, 29, 11, 0); // Europe/Paris long hours = ChronoUnit.HOURS.between(departureNY, arrivalParis); // Noncompliant: returns 13
Anchoring each LocalDateTime to its zone yields the real physical elapsed time.
ZonedDateTime departure = LocalDateTime.of(2026, 3, 28, 22, 0)
.atZone(ZoneId.of("America/New_York"));
ZonedDateTime arrival = LocalDateTime.of(2026, 3, 29, 11, 0)
.atZone(ZoneId.of("Europe/Paris"));
long hours = ChronoUnit.HOURS.between(departure, arrival); // Compliant: returns 7 hours
java.time packageLocalDateTime classInstant classZonedDateTime classChronoUnit class.now() methods should specify a ZoneId or a Clock