Command Palette

Search for a command to run...

PHASE 12Intermediate Java 8+ ~25 min· topic 5 of 5

Topic 12.5

Dates and Times with java.time

In one line

java.time (Java 8) replaced the old, error-prone Date and Calendar with clear immutable types: LocalDate and LocalDateTime for calendar values, Instant for a moment on the timeline, ZonedDateTime for a moment in a particular time zone, and Duration/Period for amounts of time.

Think of it like this

A birthday, a train ticket and a stopwatch. Your birthday is just a date: it doesn't matter which city you're in (LocalDate). A flight departure happens at one exact moment for the whole world, but the clock on the wall shows a different time in Mumbai and New York (Instant plus a ZoneId gives a ZonedDateTime). A stopwatch measures how long something took, in seconds (Duration), while "3 months and 2 days" is a calendar amount (Period).

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Time zone
A region's rules for its clocks, like Asia/Kolkata or America/New_York, including when daylight saving starts and ends.
UTC
Coordinated Universal Time: the world's reference clock. Other zones are written as an offset from it, like +05:30.
Offset
How far a local clock is ahead of or behind UTC at a given moment, like +05:30 or -04:00.
Daylight saving time (DST)
Moving clocks forward in spring and back in autumn in some countries. India doesn't use it; the USA does.
Instant
One exact moment on the world timeline, the same everywhere.
Local date-time
A calendar date and clock time with no zone attached, like what's written in a diary.
Duration
An exact amount of time, like 90 minutes.
Period
A calendar amount, like 2 months and 3 days, whose length in seconds depends on which months.

Step by step

01Why the old API was replaced

java.util.Date was mutable (anyone holding it could change it), its toString used the computer's time zone, years started at 1900 and months at 0. SimpleDateFormat wasn't thread-safe. Calendar was verbose and had the same 0-based months.

JSR 310, based on the Joda-Time library, gave Java 8 a new package where every type is immutable, each concept has its own type, and names say what they mean.

02Pick the right type

Ask: is this a calendar value people write down (a birthday: LocalDate), a moment (a payment's timestamp: Instant), or a moment as seen in a place (a meeting at 10:00 in Mumbai: ZonedDateTime)?

Store moments as Instant (or UTC) and convert to a zone only to display them. Store "10:00 every Monday in the Pune office" as LocalTime plus ZoneId, because the matching instants change when DST rules change.

Pick the right typediagram
Rendering diagram…

03Immutable: every change makes a new object

LocalDate d = LocalDate.of(2026, 3, 15); d.plusDays(1); leaves d unchanged: plusDays returns a new date and the code throws it away. Write d = d.plusDays(1);.

Immutability is what makes these types safe to share between threads and to use as map keys.

04Calendar maths has rules

Adding a month to January 31st gives the last day of February, not March 3rd. Period.between counts whole years, then months, then days, so 2000-05-20 to 2026-03-15 is 25 years, 9 months and 23 days.

Leap years: divisible by 4, except centuries, except centuries divisible by 400. So 2024 and 2000 are leap years, but 2100 is not. Year.isLeap() and LocalDate.isLeapYear() know this.

05Zones, offsets and daylight saving

The same Instant is 15:00 in Mumbai (+05:30) and 05:30 in New York (-04:00 on 8 March 2026, when the USA has just moved to summer time). Converting between zones never changes the instant, only how it's displayed.

On 8 March 2026 New York clocks jump from 02:00 to 03:00, so 02:30 doesn't exist. ZonedDateTime.of moves a non-existent time forward by the length of the gap (to 03:30) instead of failing, and that day is 23 hours long.

06Formatting and parsing

LocalDate.parse("05/10/2026", DateTimeFormatter.ofPattern("dd/MM/yyyy")) reads a date; date.format(...) writes one. Parsing is strict about the pattern: "5/10/2026" fails with dd, which needs two digits (use d to allow one).

Text like month and day names depends on the Locale. Pass one explicitly (ofPattern("d MMMM yyyy", Locale.ENGLISH)) so output doesn't change with the computer's settings.

07Testable code: inject a Clock

LocalDate.now() reads the system clock, which makes code hard to test (the answer changes every day). Pass a java.time.Clock into your class and call LocalDate.now(clock); tests use Clock.fixed(...) to freeze time.

Try it yourself

  1. 1

    End of month again

    In the first example, change today to LocalDate.of(2024, 1, 31) and run it. February 2024 has 29 days, so one month later is 2024-02-29.

  2. 2

    Pick another city

    In "One instant, two cities", add Asia/Tokyo (UTC+9, no DST). Predict the time before running: 09:30Z plus 9 hours is 18:30.

Code & diagrams

Dates, leap years and Period Java 8+ New tab
Sign in to run this example in your browser.

Expected output

exam: 2026-03-15 (SUNDAY)
days to go: 43
one month after Jan 31: 2026-02-28
2024 leap year? true, 2100? false
age on exam day: 25 years 9 months 23 days
One instant, two cities Java 8+ New tab
Sign in to run this example in your browser.

Expected output

Mumbai:   2026-03-08 15:00 +05:30
New York: 2026-03-08 05:30 -04:00
same instant? true
The hour that doesn't exist Java 8+ New tab
Sign in to run this example in your browser.

Expected output

2026-03-08T03:30-04:00[America/New_York]
hours in that day: 23
Parsing and formatting Java 8+ New tab
Sign in to run this example in your browser.

Expected output

2026-10-05 is a MONDAY
2026-10-05
2026-10-31 is the last day of the month
Inject a Clock to make time testablejava
class Greeter {
    private final Clock clock;
    Greeter(Clock clock) { this.clock = clock; }

    String greet() {
        int hour = LocalTime.now(clock).getHour();
        return hour < 12 ? "Good morning" : "Good afternoon";
    }
}

// production: new Greeter(Clock.systemDefaultZone())
// test:       new Greeter(Clock.fixed(Instant.parse("2026-01-01T03:00:00Z"), ZoneOffset.UTC))

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Forgetting that dates are immutable

Write LocalDate d = LocalDate.of(2026, 3, 15); d.plusDays(1); System.out.println(d);.

terminal
$ java Main.java
── what you'll see ──
2026-03-15

Break #2

Parsing with the wrong pattern

Parse "2026-10-05" with DateTimeFormatter.ofPattern("dd/MM/yyyy").

terminal
$ java Main.java
── what you'll see ──
Exception in thread "main" java.time.format.DateTimeParseException: Text '2026-10-05' could not be parsed at index 2

Myth vs fact

Myth

A day is always 24 hours.

Fact

In zones with daylight saving, one day a year has 23 hours and one has 25. Use ZonedDateTime and Duration.between, not hours times 24.

Myth

LocalDateTime is fine for timestamps.

Fact

It has no zone, so it can't say which moment it was. Use Instant (or a UTC time) for events and logs.

Myth

YYYY in a pattern means the year.

Fact

YYYY is the week-based year, which differs from the calendar year in the last days of December and first days of January. Use yyyy (or uuuu).

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Time-zone rules come from the IANA tz database bundled in the JDK (tzdb.dat). Governments change rules often, so long-running services need JDK updates (or the TZUpdater tool) to keep conversions correct.

  • ▸

    Store future appointments as LocalDateTime plus ZoneId, not as an Instant: if a country changes its DST rules before the date, the wall-clock time people agreed on stays correct while the instant moves.

  • ▸

    Instant has nanosecond fields, but Instant.now() resolution depends on the OS clock (often microseconds). For measuring elapsed time inside a program use System.nanoTime(), which is monotonic; wall clocks can jump when NTP adjusts them.

  • ▸

    For overlapping wall-clock times (autumn), ZonedDateTime picks the earlier offset by default; withLaterOffsetAtOverlap() and withEarlierOffsetAtOverlap() choose explicitly.

Remember this

  1. 1

    Local types have no time zone: LocalDate (2026-03-15), LocalTime (09:30), LocalDateTime (2026-03-15T09:30). Use them for birthdays, opening hours and things people write on a calendar. They can't tell you the exact moment, because 09:30 happens at different moments in different places.

  2. 2

    **Instant is a point on the global timeline, stored as seconds and nanoseconds since 1970-01-01T00:00Z (UTC). Use it for timestamps: logs, created-at columns, event times. ZonedDateTime** combines a local date-time with a ZoneId such as Asia/Kolkata, and knows that zone's rules, including daylight saving time (DST). OffsetDateTime has a fixed offset like +05:30 but no DST rules.

  3. 3

    All java.time types are immutable and thread-safe. Methods like plusDays return a new object; date.plusDays(1); on its own line does nothing useful. Months are numbered 1 to 12 (the old Calendar used 0 to 11, a famous source of bugs).

  4. 4

    Calendar maths is not simple maths. LocalDate.of(2026, 1, 31).plusMonths(1) is 2026-02-28 (clamped to the month's last day). On the day a zone switches to summer time, one local hour doesn't exist (it's skipped) and a day is only 23 hours long; in autumn one local hour happens twice. ZonedDateTime handles this; plain arithmetic on hours does not.

  5. 5

    **Duration is an exact amount of time in seconds and nanoseconds (2 hours 30 minutes). Period** is a calendar amount in years, months and days (1 year 2 months). ChronoUnit.DAYS.between(a, b) counts whole units between two values.

  6. 6

    Formatting and parsing use **DateTimeFormatter**: predefined ones like ISO_LOCAL_DATE, or patterns like "dd/MM/yyyy". Formatters are immutable and thread-safe, unlike the old SimpleDateFormat, which broke when shared between threads. Be careful with pattern letters: MM is month and mm is minutes; yyyy is year-of-era and YYYY is week-based year, which gives wrong years around New Year.

Explain it without notes

01

When do you use LocalDate, LocalDateTime, Instant and ZonedDateTime?

02

What is the difference between Duration and Period?

03

Why was java.util.Date replaced, and why is java.time safer?

Practice

01

Print how many days are left until 31 December 2026 from 5 October 2026.

02

Given a flight that departs Mumbai on 2026-11-20 at 23:15 local time and lasts 9 hours 40 minutes, print the arrival time in London.

03

Print the dates of every Monday in November 2026.

Trade-offs

  • ↔

    Instant is unambiguous for storage but needs a zone to be shown to people; zoned types are human-friendly but depend on changing zone rules.

  • ↔

    Local types are simple but can't represent a moment; using them for timestamps causes bugs as soon as two zones are involved.

Done when you can

  • Done when you can pick between LocalDate, LocalDateTime, Instant and ZonedDateTime for a given requirement.

  • Done when you remember that java.time types are immutable and keep the results of plus and minus methods.

  • Done when you can explain the DST gap and overlap and why a day isn't always 24 hours.

  • Done when you can parse and format with DateTimeFormatter and avoid the YYYY and mm traps.

  • Done when you can make time-dependent code testable with Clock.