Date & Time API

Comparing Dates & Times

15 min Lesson 8 of 13

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) — returns true if this value is strictly earlier than other.
  • isAfter(other) — returns true if this value is strictly later than other.
  • isEqual(other) — returns true if 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.

import java.time.LocalDate; LocalDate today = LocalDate.of(2026, 6, 9); LocalDate deadline = LocalDate.of(2026, 12, 31); LocalDate birthdate = LocalDate.of(2026, 6, 9); System.out.println(today.isBefore(deadline)); // true System.out.println(today.isAfter(deadline)); // false System.out.println(today.isEqual(birthdate)); // true
Why not just use 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:

import java.time.ZonedDateTime; import java.time.ZoneId; ZonedDateTime londonNoon = ZonedDateTime.of(2026, 6, 9, 12, 0, 0, 0, ZoneId.of("Europe/London")); ZonedDateTime dubaiAfternoon = ZonedDateTime.of(2026, 6, 9, 15, 0, 0, 0, ZoneId.of("Asia/Dubai")); // London UTC+1 at 12:00 = UTC 11:00 // Dubai UTC+4 at 15:00 = UTC 11:00 → same instant! System.out.println(londonNoon.isEqual(dubaiAfternoon)); // true System.out.println(londonNoon.equals(dubaiAfternoon)); // false (different zones)
When comparing times from different time zones, always use 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.

import java.time.LocalDate; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; LocalDate start = LocalDate.of(2026, 1, 1); LocalDate end = LocalDate.of(2026, 6, 9); long daysBetween = ChronoUnit.DAYS.between(start, end); // 159 long weeksBetween = ChronoUnit.WEEKS.between(start, end); // 22 long monthsBetween = ChronoUnit.MONTHS.between(start, end); // 5 System.out.println(daysBetween); // 159 System.out.println(weeksBetween); // 22 System.out.println(monthsBetween); // 5

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.

LocalDateTime meetingStart = LocalDateTime.of(2026, 6, 9, 9, 0); LocalDateTime meetingEnd = LocalDateTime.of(2026, 6, 9, 11, 30); long hours = ChronoUnit.HOURS.between(meetingStart, meetingEnd); // 2 long minutes = ChronoUnit.MINUTES.between(meetingStart, meetingEnd); // 150 System.out.println(hours); // 2 System.out.println(minutes); // 150
Truncation, not rounding. 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) and Period.between(a, b) give you a structured object with multiple components (e.g. a Period of "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:

import java.time.LocalDate; import java.time.temporal.ChronoUnit; public class BookingValidator { public static String checkBooking(LocalDate checkIn, LocalDate checkOut) { LocalDate today = LocalDate.now(); if (!checkIn.isAfter(today)) { return "Check-in must be in the future."; } if (!checkOut.isAfter(checkIn)) { return "Check-out must be after check-in."; } long nights = ChronoUnit.DAYS.between(checkIn, checkOut); long daysUntilCheckIn = ChronoUnit.DAYS.between(today, checkIn); return String.format( "Booking valid: %d night(s), check-in in %d day(s).", nights, daysUntilCheckIn); } public static void main(String[] args) { System.out.println(checkBooking( LocalDate.of(2026, 7, 1), LocalDate.of(2026, 7, 5))); // Booking valid: 4 night(s), check-in in 22 day(s). } }

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:

import java.time.LocalDate; import java.util.Arrays; import java.util.List; List<LocalDate> dates = Arrays.asList( LocalDate.of(2026, 12, 1), LocalDate.of(2026, 3, 15), LocalDate.of(2026, 6, 9) ); List<LocalDate> sorted = dates.stream() .sorted() // uses Comparable — no comparator needed .toList(); sorted.forEach(System.out::println); // 2026-03-15 // 2026-06-09 // 2026-12-01
Never compare 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.