Date & Time API

LocalDate, LocalTime & LocalDateTime

15 min Lesson 2 of 13

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).
No zone does not mean UTC. A 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:

import java.time.LocalDate; import java.time.LocalTime; import java.time.LocalDateTime; LocalDate today = LocalDate.now(); // e.g. 2024-03-15 LocalTime timeNow = LocalTime.now(); // e.g. 14:30:22.548 LocalDateTime dtNow = LocalDateTime.now(); // e.g. 2024-03-15T14:30:22.548 System.out.println(today); // 2024-03-15 System.out.println(timeNow); // 14:30:22.548167 System.out.println(dtNow); // 2024-03-15T14:30:22.548167
Pass a Clock for testability. Production code that calls 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):

import java.time.LocalDate; import java.time.LocalTime; import java.time.LocalDateTime; import java.time.Month; // year, month (int 1-12), day LocalDate birthday = LocalDate.of(1990, 6, 15); // using the Month enum — self-documenting and avoids the off-by-one bug LocalDate release = LocalDate.of(2024, Month.MARCH, 15); // hour, minute LocalTime startTime = LocalTime.of(9, 0); // hour, minute, second, nanosecond (trailing zeros optional) LocalTime precise = LocalTime.of(14, 30, 45, 500_000_000); // combine a LocalDate and LocalTime LocalDateTime mtg = LocalDateTime.of(release, startTime); // or pass all fields directly LocalDateTime launch = LocalDateTime.of(2024, Month.MARCH, 15, 9, 0); System.out.println(birthday); // 1990-06-15 System.out.println(precise); // 14:30:45.500 System.out.println(mtg); // 2024-03-15T09:00

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:

import java.time.LocalDate; import java.time.LocalTime; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; // ISO-8601 defaults LocalDate d = LocalDate.parse("2024-03-15"); LocalTime t = LocalTime.parse("14:30:00"); LocalDateTime dt = LocalDateTime.parse("2024-03-15T14:30:00"); // custom pattern — use DateTimeFormatter DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd/MM/yyyy"); LocalDate invoice = LocalDate.parse("15/03/2024", fmt); System.out.println(d); // 2024-03-15 System.out.println(invoice); // 2024-03-15
parse() throws DateTimeParseException on bad input. This is a 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:

LocalDate d = LocalDate.of(2024, Month.MARCH, 15); int year = d.getYear(); // 2024 Month month = d.getMonth(); // MARCH int monthNum = d.getMonthValue(); // 3 int day = d.getDayOfMonth(); // 15 int dayOfYear = d.getDayOfYear(); // 75 DayOfWeek dow = d.getDayOfWeek(); // FRIDAY LocalTime t = LocalTime.of(14, 30, 45, 500_000_000); int hour = t.getHour(); // 14 int minute = t.getMinute(); // 30 int second = t.getSecond(); // 45 int nano = t.getNano(); // 500000000

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 original = LocalDate.of(2024, 3, 15); // "adjust" a field — original is untouched LocalDate nextYear = original.withYear(2025); LocalDate firstDay = original.withDayOfMonth(1); System.out.println(original); // 2024-03-15 System.out.println(nextYear); // 2025-03-15 System.out.println(firstDay); // 2024-03-01
Why immutability matters in practice. Shared 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:

LocalDateTime dt = LocalDateTime.of(2024, 3, 15, 14, 30); // extract components LocalDate datePart = dt.toLocalDate(); // 2024-03-15 LocalTime timePart = dt.toLocalTime(); // 14:30 // reassemble with a different time LocalDateTime rescheduled = dt.with(LocalTime.of(10, 0)); System.out.println(rescheduled); // 2024-03-15T10:00

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.