Java · Lesson 16 of 20

Dates and Times with java.time

Work with dates and times in Java using java.time: LocalDate, LocalDateTime, Instant, ZonedDateTime, Duration, Period, formatting and parsing.

  • Intermediate
  • 17 min read
  • 4 objectives

Before this lessonLesson 15: Modern Java: var, switch expressions, text blocks, pattern matching, sealed classes

What you will learn

  • Pick the right java.time type for each situation
  • Do date arithmetic with plus, minus, Period and Duration
  • Convert between time zones with ZonedDateTime and Instant
  • Format and parse dates with DateTimeFormatter

Your Progress

0 of 20 lessons 0%

  • Lessons0 / 20
  • Completed0
  • Est. time left~ 5 hours

Create a free account to keep your progress on every device.

Tip: pressing Next marks this lesson complete automatically.

Dates look simple until you meet leap years, months of different lengths, daylight saving time and users in different time zones. Java's original java.util.Date and Calendar classes were mutable, confusing (months started at 0) and not thread-safe. Since Java 8 the java.time package has replaced them with clear, immutable types that each model one idea.

To keep the output on this page repeatable, the examples use fixed dates instead of "now". In real code you would call LocalDate.now() or Instant.now().

Choosing the right type

  • LocalDate: a calendar date with no time or zone, like a birthday (2026-09-23).
  • LocalTime: a wall-clock time, like opening hours (09:30).
  • LocalDateTime: date plus time but no zone; fine for "the meeting is at 10:00" in one known place.
  • ZonedDateTime: date, time and a zone like Europe/London; needed for scheduling across regions.
  • Instant: an exact point on the global timeline in UTC; use it for timestamps, logs and databases.
  • Duration (hours, minutes, seconds) and Period (years, months, days) measure amounts of time.

LocalDate and LocalTime

Create dates with factory methods such as of and parse (ISO format, yyyy-MM-dd). Months are 1 to 12, and there is a Month enum if you prefer names. Getter methods read each part, and the class knows about leap years and weekdays for you.

import java.time.*;

public class Main {
    public static void main(String[] args) {
        LocalDate launch = LocalDate.of(2026, 9, 23);
        LocalDate parsed = LocalDate.parse("2028-02-29");
        LocalTime opens = LocalTime.of(9, 30);

        System.out.println(launch + " is a " + launch.getDayOfWeek());
        System.out.println(launch.getMonth() + " has " + launch.lengthOfMonth() + " days");
        System.out.println(parsed.isLeapYear() + " " + parsed.getDayOfYear());
        System.out.println(opens.plusHours(8).plusMinutes(15));
        System.out.println(launch.isBefore(parsed));
    }
}
Output
2026-09-23 is a WEDNESDAY
SEPTEMBER has 30 days
true 60
17:45
true

Date arithmetic is immutable

Methods like plusDays, minusMonths and withDayOfMonth return a new object; the original never changes. Month arithmetic is clever: adding one month to January 31 gives the last valid day of February rather than overflowing. TemporalAdjusters answers questions like "the next Monday" or "the last day of this month".

import java.time.*;
import java.time.temporal.TemporalAdjusters;

public class Main {
    public static void main(String[] args) {
        LocalDate invoice = LocalDate.of(2026, 1, 31);
        LocalDate due = invoice.plusMonths(1);
        System.out.println("invoice " + invoice + ", due " + due);

        invoice.plusDays(10);                     // result thrown away!
        System.out.println("still " + invoice);

        LocalDate d = LocalDate.of(2026, 9, 23);
        System.out.println(d.with(TemporalAdjusters.next(DayOfWeek.MONDAY)));
        System.out.println(d.with(TemporalAdjusters.lastDayOfMonth()));
        System.out.println(d.withDayOfMonth(1).minusYears(1));
    }
}
Output
invoice 2026-01-31, due 2026-02-28
still 2026-01-31
2026-09-28
2026-09-30
2025-09-01

Period, Duration and ChronoUnit

Use Period for calendar amounts ("2 years, 3 months") and Duration for exact time amounts ("90 minutes"). ChronoUnit.X.between(a, b) gives a single total, such as the number of days until a deadline.

import java.time.*;
import java.time.temporal.ChronoUnit;

public class Main {
    public static void main(String[] args) {
        LocalDate born = LocalDate.of(1990, 12, 10);
        LocalDate today = LocalDate.of(2026, 9, 23);
        Period age = Period.between(born, today);
        System.out.println(age.getYears() + " years, " + age.getMonths() + " months, " + age.getDays() + " days");
        System.out.println(ChronoUnit.DAYS.between(today, LocalDate.of(2026, 12, 25)) + " days to go");

        LocalDateTime start = LocalDateTime.of(2026, 9, 23, 9, 15);
        LocalDateTime end = LocalDateTime.of(2026, 9, 23, 17, 45);
        Duration worked = Duration.between(start, end);
        System.out.println(worked + " = " + worked.toHours() + "h " + worked.toMinutesPart() + "m");
        System.out.println(Duration.ofMinutes(150).plusSeconds(30));
    }
}
Output
35 years, 9 months, 13 days
93 days to go
PT8H30M = 8h 30m
PT2H30M30S

PT8H30M is ISO-8601 notation for a duration: P for period, T for time, then hours and minutes. Period prints similarly, for example P35Y9M13D.

Time zones: ZonedDateTime and Instant

An Instant is the same moment everywhere. A ZonedDateTime is that moment viewed from a particular zone. Store and compare Instants; convert to a zone only when showing times to a person. Always use region IDs like America/New_York rather than fixed offsets, because the region's rules include daylight saving changes.

import java.time.*;

public class Main {
    public static void main(String[] args) {
        ZonedDateTime meeting = ZonedDateTime.of(
            LocalDateTime.of(2026, 9, 23, 10, 0), ZoneId.of("America/New_York"));

        System.out.println(meeting);
        System.out.println("London:  " + meeting.withZoneSameInstant(ZoneId.of("Europe/London")).toLocalTime());
        System.out.println("Kolkata: " + meeting.withZoneSameInstant(ZoneId.of("Asia/Kolkata")).toLocalTime());

        Instant stamp = meeting.toInstant();
        System.out.println(stamp);
        System.out.println(stamp.toEpochMilli());

        // daylight saving: clocks in New York jump forward on 2026-03-08
        ZonedDateTime before = ZonedDateTime.of(LocalDateTime.of(2026, 3, 8, 1, 30), ZoneId.of("America/New_York"));
        System.out.println(before.plusHours(1).toLocalTime());
    }
}
Output
2026-09-23T10:00-04:00[America/New_York]
London:  15:00
Kolkata: 19:30
2026-09-23T14:00:00Z
1790172000000
03:30

The last line shows why zones matter: one hour after 01:30 on the day clocks spring forward is 03:30, not 02:30. LocalDateTime would have got this wrong.

Formatting and parsing

toString() always gives ISO-8601, which is ideal for APIs and storage. For people, use a DateTimeFormatter, either a pattern (dd MMM yyyy) or a localised style. The same formatter parses text back into a date. Formatters are immutable and thread-safe, so store them in constants.

import java.time.*;
import java.time.format.*;
import java.util.Locale;

public class Main {
    static final DateTimeFormatter DISPLAY = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm", Locale.ENGLISH);
    static final DateTimeFormatter INPUT = DateTimeFormatter.ofPattern("dd/MM/yyyy");

    public static void main(String[] args) {
        LocalDateTime order = LocalDateTime.of(2026, 9, 23, 14, 5);
        System.out.println(order.format(DISPLAY));
        System.out.println(order.format(DateTimeFormatter.ISO_LOCAL_DATE));

        LocalDate fromUser = LocalDate.parse("05/11/2026", INPUT);
        System.out.println(fromUser + " " + fromUser.getMonth());

        try {
            LocalDate.parse("31/02/2026", INPUT.withResolverStyle(ResolverStyle.STRICT));
        } catch (DateTimeParseException e) {
            System.out.println("invalid date: " + e.getParsedString());
        }
    }
}
Output
Wed, 23 Sep 2026 14:05
2026-09-23
2026-11-05 NOVEMBER
invalid date: 31/02/2026

Recap

  • Use java.time, never Date or Calendar, in new code.
  • LocalDate/LocalDateTime have no zone; Instant is a UTC timestamp; ZonedDateTime adds zone rules.
  • All types are immutable: always use the returned value from plus/minus/with.
  • Period measures calendar amounts, Duration exact time, ChronoUnit.between single totals.
  • Format for humans with DateTimeFormatter, and store or exchange ISO-8601 strings or Instants.
// Write your solution here

Finished reading? Mark this lesson complete to track your progress.

Up next · Lesson 17Files and I/ORead and write files with java.nio and handle resources with try-with-resources.