Comparing Dates & Times
Comparing Dates & Times
Ordering and measuring temporal values is one of the most common date-and-time tasks in real applications: sorting events chronologically, checking whether a booking has passed, computing how many days remain until a deadline, or throttling a user action to once every 24 hours. The java.time API gives you two distinct tools for this — the comparison predicates (isBefore, isAfter, isEqual) and the measurement utilities provided by ChronoUnit.
The Three Comparison Predicates
Every main date-time type — LocalDate, LocalTime, LocalDateTime, ZonedDateTime, Instant, OffsetDateTime — implements the Comparable interface and exposes three fluent boolean methods:
isBefore(other)— returnstrueif this value is strictly earlier thanother.isAfter(other)— returnstrueif this value is strictly later thanother.isEqual(other)— returnstrueif both values represent the same point (or date).
All three comparisons work on the timeline, not on the object identity. They are null-safe in the sense that they throw NullPointerException immediately if you pass null, giving you a clear failure rather than silent misbehaviour.
equals()? equals() also works on LocalDate, but isEqual() is the preferred API because it signals intent clearly, and on types like ZonedDateTime the distinction matters: two ZonedDateTime objects can represent the same instant but have different zone IDs — isEqual compares the instant on the timeline while equals additionally requires the zone to match.
ZonedDateTime and the isEqual Subtlety
This distinction becomes critical when you work across time zones:
isEqual() (or convert both to Instant first). Using equals() will silently tell you two simultaneous events are "different."
Measuring Gaps with ChronoUnit
The predicates only tell you the direction of a comparison. To know the magnitude — how many days, hours, minutes, or other calendar units separate two values — you use ChronoUnit.between(start, end).
ChronoUnit is an enum in java.time.temporal whose constants span the full temporal range: NANOS, MICROS, MILLIS, SECONDS, MINUTES, HOURS, HALF_DAYS, DAYS, WEEKS, MONTHS, YEARS, DECADES, CENTURIES, MILLENNIA.
The result is always a long. It is negative if end is before start, which makes it easy to distinguish direction without a separate predicate call.
ChronoUnit.HOURS.between on a 2.5-hour span returns 2, not 3. The result is always truncated toward zero. If you need rounding, add half a unit to the dividend before dividing.
ChronoUnit vs Duration/Period
You already know Duration and Period from Lesson 4. The key difference:
Duration.between(a, b)andPeriod.between(a, b)give you a structured object with multiple components (e.g. aPeriodof "1 year, 3 months, 2 days").ChronoUnit.DAYS.between(a, b)gives you a single long — the total count in one unit only. It does not decompose into years and months.
Use ChronoUnit when you need one number (e.g., "days until expiry"), and Period/Duration when you need a human-readable breakdown ("1 year, 3 months, 2 days").
Practical Example: Booking Validation
Below is a realistic snippet that combines both tools — the predicates to gate logic and ChronoUnit to produce a user-friendly message:
Comparing with compareTo
Because every temporal type implements Comparable, you can also use compareTo(other). It returns a negative integer, zero, or a positive integer — identical contract to Comparable elsewhere in Java. This is especially useful when sorting collections:
LocalDate (or any java.time type) with ==. They are objects, and == tests reference identity. Two LocalDate instances representing the same date may or may not share the same reference depending on caching. Always use isEqual(), equals(), or compareTo().
Summary
Use isBefore, isAfter, and isEqual to ask directional questions about temporal values; remember that isEqual compares the point on the timeline while equals also checks zone identity. Use ChronoUnit.UNIT.between(start, end) when you need a single numeric count in a specific unit — it returns a truncated long and is negative when end precedes start. For multi-component breakdowns, reach for Period or Duration instead.