java.time: dates done right
Java 8 introduced java.time to replace the mutable, confusing Date and Calendar. Every class is immutable and thread-safe.
LocalDate: a date with no time, like a birthday.LocalDateTime: date and time with no zone.ZonedDateTime: date and time in a zone such asAsia/Kolkata.Instant: a point on the UTC timeline. Store this for timestamps.Duration(hours, seconds) andPeriod(days, months, years).DateTimeFormatter: thread-safe formatting, unlike the oldSimpleDateFormat.
Rule of thumb for backends: store an Instant (or timestamptz in PostgreSQL) and convert to the user's zone only for display.
LocalDate, LocalTime and LocalDateTime
"Local" types have no time zone: a birthday, an office opening time, a timetable. They're immutable; methods such as plusDays return a new value.
LocalDate today = LocalDate.now();
LocalDate due = today.plusDays(7);
LocalDate diwali = LocalDate.of(2026, 11, 8);
LocalTime opens = LocalTime.of(9, 30);
LocalDateTime meeting = LocalDateTime.of(diwali, opens);
boolean late = today.isAfter(due);Instant, ZonedDateTime and time zones
Instant is a point on the global timeline (UTC), ideal for storing when something happened. ZonedDateTime adds a zone such as Asia/Kolkata for display and calendar logic. Store instants; convert to the user's zone when showing them.
Instant paidAt = Instant.now();
ZonedDateTime inIndia = paidAt.atZone(ZoneId.of("Asia/Kolkata"));
ZonedDateTime inLondon = inIndia.withZoneSameInstant(ZoneId.of("Europe/London"));Duration vs Period
Duration measures time-based amounts (hours, minutes, seconds); Period measures date-based amounts (years, months, days). ChronoUnit.X.between counts units between two values.
Duration call = Duration.ofMinutes(95); // PT1H35M
Period age = Period.between(LocalDate.of(1995, 5, 10), LocalDate.now());
long days = ChronoUnit.DAYS.between(start, end);Formatting and parsing
DateTimeFormatter converts between dates and text. It's immutable and thread-safe, unlike the old SimpleDateFormat. Pattern letters are case-sensitive: MM is month, mm is minutes.
DateTimeFormatter f = DateTimeFormatter.ofPattern("dd-MM-yyyy");
String text = LocalDate.of(2026, 9, 26).format(f); // "26-09-2026"
LocalDate parsed = LocalDate.parse("01-10-2026", f);
String iso = LocalDate.now().toString(); // ISO: 2026-09-26Daylight saving and zone rules
India doesn't use daylight saving, but many countries do, which creates missing and repeated local times. Always convert through a ZoneId and let java.time apply the rules, instead of adding fixed hour offsets.
The old Date and Calendar classes
java.util.Date, Calendar and SimpleDateFormat are mutable, confusing (months start at 0) and not thread-safe. Use java.time in new code, and convert at the edges when an old API needs them.
Date legacy = Date.from(Instant.now());
Instant back = legacy.toInstant();Testing time with Clock
Code that calls LocalDate.now() directly is hard to test. Inject a Clock and use LocalDate.now(clock); tests pass a fixed clock.
class Billing {
private final Clock clock;
Billing(Clock clock) { this.clock = clock; }
boolean isOverdue(LocalDate due) { return LocalDate.now(clock).isAfter(due); }
}
// test: new Billing(Clock.fixed(Instant.parse("2026-10-01T00:00:00Z"), ZoneOffset.UTC))Example
LocalDate today = LocalDate.now();
LocalDate due = today.plusDays(30);
long days = ChronoUnit.DAYS.between(today, due); // 30
Instant createdAt = Instant.now(); // store this in the DB
ZonedDateTime ist = createdAt.atZone(ZoneId.of("Asia/Kolkata"));
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd MMM yyyy, hh:mm a");
System.out.println(ist.format(fmt)); // e.g. 24 Sep 2026, 10:15 AM
Period age = Period.between(LocalDate.of(1995, 5, 20), today);
Duration timeout = Duration.ofSeconds(30);Common mistake
Using LocalDateTime for event timestamps. It has no zone, so the same value means different moments on servers in different regions.
Under the hood
JPA 2.2 and Hibernate map LocalDate, LocalDateTime and Instant natively, with no converters needed. Inject a Clock into services instead of calling now() directly, so tests can freeze time with Clock.fixed(...).
Check yourself
Which type should store "when was this order created"?
How this connects
Know these first
Where this leads
Was this lesson helpful?
Finished reading? Mark it complete to track your progress.