LocalDate, LocalTime & LocalDateTime
LocalDate, LocalTime & LocalDateTime
The three most-used classes in java.time are LocalDate, LocalTime, and LocalDateTime. "Local" means they carry no time-zone or offset information — they represent a date or time as humans think about it on a wall clock or calendar, not as a precise point on the global timeline. Understanding when to use each one, and how to create and manipulate instances, is the foundation of everything else in the Date & Time API.
What each class represents
- LocalDate — a calendar date: year, month, day. Example: 2024-03-15. No time, no zone. Use it for birthdays, invoice dates, public holidays — anything where the time of day is irrelevant.
- LocalTime — a time of day: hour, minute, second, nanosecond. Example: 14:30:00. No date, no zone. Use it for opening hours, alarm settings, schedules that repeat every day.
- LocalDateTime — a combined date and time. Example: 2024-03-15T14:30:00. Still no zone. Use it when you need both dimensions but the same meaning applies in every region (e.g., a log line written to a single server in a known location).
LocalDateTime is deliberately ambiguous about the global timeline — it cannot be converted to an Instant (a precise moment in time) without supplying a zone. This is a feature, not a bug: it prevents accidental silent conversions that plagued the old java.util.Date API.
Creating instances: now()
The static factory now() reads the system clock and returns the current date or time in the JVM's default zone. It is the idiomatic way to capture "right now" in human terms:
LocalDate.now() without arguments is hard to unit-test because it reads the real system clock. Instead, inject a java.time.Clock: LocalDate.now(clock). In tests, supply Clock.fixed(...) to pin time to a known instant.
Creating instances: of()
of() constructs a specific date or time from its components. It validates arguments immediately — passing an invalid combination throws DateTimeException at the call site rather than silently rolling over (the old Calendar behaviour):
Creating instances: parse()
When date/time data arrives as text — from a REST API, a CSV, a database VARCHAR — use parse(). By default it expects ISO-8601 format, which the API uses as its canonical wire format:
RuntimeException. When parsing user-supplied or external data, always wrap in a try-catch (or validate first) and provide a meaningful error message. A blank or malformed string will blow up at runtime if left unguarded.
Accessing fields
All three classes expose typed accessors. You can also use the generic get(ChronoField) if you need to handle fields dynamically:
Immutability and the with() pattern
Every java.time class is immutable. Methods like withYear(), withMonth(), and the generic with(TemporalField, long) return a new instance — the original is unchanged. This is the same design principle as String:
LocalDate references passed to multiple methods or stored in collections are inherently thread-safe — no defensive copying required. This eliminates an entire class of concurrency bugs that haunted the old mutable Calendar.
Combining and decomposing LocalDateTime
You can freely move between LocalDateTime and its components:
Summary
Use LocalDate for calendar dates, LocalTime for times of day, and LocalDateTime for combined date-time values — all without any zone or offset attached. Create instances with now() (from the clock), of() (from known components), or parse() (from text). All three classes are immutable; modifying a field produces a new object. In the next lesson we look at Instant — the zone-aware counterpart that represents a precise moment on the global timeline.