Appearance
Lesson 04 · Dates & Times — the java.time API
Objectives
After this lesson you will be able to:
- Choose the right
java.timetype:LocalDate,LocalTime,LocalDateTime,Instant,ZonedDateTime. - Create and fluently manipulate them, remembering they are immutable.
- Tell
Period(date-based) fromDuration(time-based) and use each correctly. - Parse and format with
DateTimeFormatter, and avoid the exam's date-math traps.
The core types
The modern API lives in java.time (Java 8+, JSR-310) and replaces the error-prone legacy java.util.Date/Calendar. All its types are immutable and thread-safe. There are no public constructors — you create values with static factory methods (now, of, parse).
| Type | Holds | Example |
|---|---|---|
LocalDate | a date, no time, no zone | 2026-06-19 |
LocalTime | a time, no date, no zone | 10:15:30 |
LocalDateTime | date + time, no zone | 2026-06-19T10:15 |
ZonedDateTime | date + time + zone | 2026-06-19T10:15+02:00[Europe/Paris] |
Instant | a point on the UTC timeline (epoch) | 2026-06-19T08:15:30Z |
java
LocalDate d = LocalDate.of(2026, 6, 19); // month is 1-based: 6 == June
LocalDate d2 = LocalDate.of(2026, Month.JUNE, 19);
LocalTime t = LocalTime.of(10, 15, 30);
LocalDateTime dt = LocalDateTime.of(d, t);
Instant now = Instant.now(); // always UTCExam trap
Unlike the legacy Calendar, months are 1-based here (1 = January, 12 = December). Passing an out-of-range value throws DateTimeException at runtime:
java
LocalDate.of(2026, 13, 1); // DateTimeException: Invalid value for MonthOfYear
LocalTime.of(24, 0); // DateTimeException: Invalid value for HourOfDay (0–23)Immutability & fluent manipulation
Every "change" returns a new object; the original is untouched. Forgetting to assign the result is the #1 mistake.
java
LocalDate d = LocalDate.of(2026, 1, 31);
d.plusDays(1); // returns 2026-02-01 — but result is DISCARDED
LocalDate next = d.plusDays(1);// correct: capture the new valueThe fluent methods come in families: plusX / minusX, withX (set one field), and getX:
java
LocalDate end = LocalDate.of(2026, 1, 31)
.plusMonths(1) // 2026-02-28 — clamps to a valid day, no Feb 31!
.withDayOfMonth(15); // 2026-02-15
boolean leap = LocalDate.of(2024, 1, 1).isLeapYear(); // true
DayOfWeek dow = end.getDayOfWeek();Gotcha
plusMonths/plusYears clamp to the last valid day of the target month rather than overflowing: Jan 31 + 1 month is Feb 28 (or Feb 29 in a leap year), not Mar 3.
Period vs Duration
Both implement TemporalAmount, but they measure different things:
Period— a date-based amount in years / months / days. Use withLocalDate.Duration— a time-based amount in seconds / nanos (also exposes hours/minutes). Use with time-bearing types (LocalTime,Instant,LocalDateTime).
java
Period p = Period.of(1, 2, 3); // 1 year, 2 months, 3 days
Period gap = Period.between(LocalDate.of(2026,1,1), LocalDate.of(2026,3,15)); // P2M14D
Duration dur = Duration.ofHours(2).plusMinutes(30); // PT2H30M
Duration elapsed = Duration.between(LocalTime.of(8,0), LocalTime.of(10,30)); // PT2H30MExam trap
Adding a Duration to a LocalDate does not compile — a date has no time fields. And a Period of months added to a date is calendar-aware, not a fixed day count:
java
LocalDate.of(2026,1,1).plus(Duration.ofDays(1)); // does NOT compile
Period.between(LocalDate.of(2026,1,31), LocalDate.of(2026,3,1)).getDays(); // 1, not 29Beyond the exam
Instant, ZonedDateTime, and zone conversion (ZoneId, atZone, withZoneSameInstant) are worth knowing for real apps, but the 1Z0-830 focuses on LocalDate/LocalTime/LocalDateTime, Period/Duration, and formatting. Know that an Instant is UTC and that ZonedDateTime accounts for daylight-saving offsets.
Parsing & formatting
parse reads a string; format writes one. Both default to ISO-8601 and use a DateTimeFormatter for custom patterns.
java
LocalDate d = LocalDate.parse("2026-06-19"); // ISO by default
DateTimeFormatter f = DateTimeFormatter.ofPattern("dd/MM/yyyy");
String s = d.format(f); // "19/06/2026"
LocalDate back = LocalDate.parse("19/06/2026", f);Exam trap
Pattern letters are case-sensitive and easy to mix up: MM = month, mm = minute; yyyy = year, hh = 12-hour clock, HH = 24-hour. A value that does not match the pattern (or the wrong type for the formatter) throws DateTimeParseException:
java
LocalDate.parse("2026-06-19", DateTimeFormatter.ofPattern("dd/MM/yyyy")); // DateTimeParseExceptionSDET note
Never call LocalDate.now()/Instant.now() directly in code you want to test — inject a java.time.Clock (e.g. Clock.fixed(...)) so "now" is deterministic. Tests that depend on the real clock are flaky and fail at midnight, month boundaries, or in other time zones.
Key Takeaways
java.timetypes are immutable and built via static factories (now/of/parse) — there are no public constructors. Always assign the result ofplus/minus/with.- Months are 1-based; invalid fields throw
DateTimeException.plusMonthsclamps to a valid day (Jan 31 + 1 month → Feb 28/29). LocalDate/LocalTime/LocalDateTimecarry no zone;Instantis UTC;ZonedDateTimeadds a zone.Periodis date-based (Y/M/D, withLocalDate);Durationis time-based (H/M/S, with time types). Mixing them with the wrong type fails to compile.- Format/parse with
DateTimeFormatter; pattern letters are case-sensitive (MMvsmm,HHvshh); a mismatch throwsDateTimeParseException.
Lesson Quiz
What does this print?
LocalDate d = LocalDate.of(2026, 1, 31); d.plusDays(1); System.out.println(d);
What is the result of LocalDate.of(2026, 1, 31).plusMonths(1) ?
Which line does NOT compile?
What does LocalTime.of(24, 0) do?
Which formatter pattern formats June as the month and 30 as minutes correctly?
// time 14:30 on 2026-06-19
What does Period.between(LocalDate.of(2026,1,1), LocalDate.of(2026,3,15)) produce?
Next: Module 01 Mini-Exam — the timed, whole-module quiz on the module landing page. Run the matching code in labs/src/main/java/com/jse21/m01_values/DateTimes.java.