Dates and Time

Learning goals

By the end of this chapter you should be able to:

  • Use java.time types instead of legacy Date and Calendar
  • Model instant, local, and zoned time correctly for logs, APIs, and user-facing displays
  • Parse and format with DateTimeFormatter without surprise locale bugs
  • Perform safe arithmetic with Period and Duration

The java.time mental model

Legacy java.util.Date mixed instant and calendar concerns and was mutable. java.time (Java 8+) splits responsibilities:

Type Meaning Example use
Instant Point on UTC timeline Event timestamps in logs/DB
LocalDate Calendar date, no zone Birthdays, billing cycles
LocalTime Clock time, no zone Store opening hours
LocalDateTime Date + time, no zone Meeting without zone agreed
ZonedDateTime Full instant in a zone User-facing “3pm in Tokyo”
OffsetDateTime Instant with fixed offset ISO-8601 API payloads

Rule: store Instant or OffsetDateTime in databases; convert to ZonedDateTime at the UI boundary.


Instants and clocks

Instant now = Instant.now();
Instant later = now.plus(Duration.ofHours(2));

boolean before = now.isBefore(later);
long epochMillis = now.toEpochMilli();
Instant fromMillis = Instant.ofEpochMilli(epochMillis);

For tests, inject a Clock instead of calling Instant.now() directly:

Clock fixed = Clock.fixed(Instant.parse("2026-01-15T10:00:00Z"), ZoneOffset.UTC);
Instant frozen = Instant.now(fixed);  // always the same

Local dates and times

LocalDate today = LocalDate.now();
LocalDate deadline = LocalDate.of(2026, 12, 31);
LocalDate nextWeek = today.plusWeeks(1);

LocalTime start = LocalTime.of(9, 30);
LocalDateTime meeting = LocalDateTime.of(today, start);

LocalDate uses ISO-8601 calendar (2026-08-02). Parsing rejects invalid dates like February 30.

LocalDate parsed = LocalDate.parse("2026-08-02");  // ISO_LOCAL_DATE formatter

Zones and offsets

ZoneId tokyo = ZoneId.of("Asia/Tokyo");
ZonedDateTime zdt = ZonedDateTime.now(tokyo);

Instant sameInstant = zdt.toInstant();  // canonical storage form

ZonedDateTime nyView = sameInstant.atZone(ZoneId.of("America/New_York"));

ZoneId handles daylight saving rules. ZoneOffset is a fixed +09:00 style offset — use when the protocol specifies offset, not a named zone.


Period vs Duration

  • Period — calendar-based: days, months, years (LocalDate.plus)
  • Duration — time-based: seconds, nanos (Instant.plus, LocalTime.plus)
Period threeMonths = Period.ofMonths(3);
LocalDate quarterEnd = LocalDate.now().plus(threeMonths);

Duration timeout = Duration.ofSeconds(30);
Instant expires = Instant.now().plus(timeout);

Do not use Duration.ofDays(30) when you mean “one calendar month” — month lengths differ.


Formatting and parsing

Prefer built-in ISO formatters:

String iso = Instant.now().toString();  // 2026-08-02T17:30:00.123Z
LocalDate d = LocalDate.parse("2026-08-02");

Custom patterns — always set locale explicitly in user-facing code:

DateTimeFormatter fmt = DateTimeFormatter
    .ofPattern("dd MMM yyyy", Locale.US);
String text = LocalDate.now().format(fmt);
LocalDate back = LocalDate.parse("02 Aug 2026", fmt);

DateTimeFormatter is immutable and thread-safe — create once, reuse.


Converting legacy types

Bridge old APIs at the edges:

Date legacy = Date.from(Instant.now());
Instant fromLegacy = legacy.toInstant();

Avoid storing new Date/Calendar in domain models. Convert at integration boundaries only.


Common mistakes

  1. Using LocalDateTime for global events — without zone it is ambiguous across DST boundaries.
  2. Assuming Duration.between on dates — use Period.between for calendar dates.
  3. Hard-coding ZoneId.systemDefault() — server zone may differ from user zone; pass zone as parameter.
  4. Mutable SimpleDateFormat — not thread-safe; replaced by DateTimeFormatter.
  5. Ignoring nanosecondsInstant has ns precision; JSON serializers may truncate to ms.

Practice checkpoint

  1. Store “user clicked button” in a DB column — which type? Answer: Instant (UTC timeline); convert to user zone when displaying.

  2. Add 1 month to January 31 — what is the result? Answer: LocalDate adjusts to last valid day of month (Feb 28/29), per Period rules.

  3. Parse "2026-08-02T15:30:00+05:30" — which type? Answer: OffsetDateTime.parse(...) — includes offset; or ZonedDateTime if you need named zone rules later.

  4. Why inject Clock in tests? Answer: deterministic time; no flakiness from now() moving between assertions.


Interview angles

  • Instant vs ZonedDateTime: instant is universal; zoned is for human rules (DST, local midnight).
  • Legacy migration: DateInstant, CalendarZonedDateTime/LocalDate.
  • API design: accept ISO-8601 strings; document whether offset or zone is required.
  • Thread safety: java.time types are immutable; formatters are thread-safe when shared.

Next: /java/learn/collections/collections-overview/