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 likeEurope/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) andPeriod(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));
}
}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));
}
}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));
}
}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());
}
}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());
}
}
}Wed, 23 Sep 2026 14:05 2026-09-23 2026-11-05 NOVEMBER invalid date: 31/02/2026
Recap
- Use
java.time, neverDateorCalendar, in new code. LocalDate/LocalDateTimehave no zone;Instantis a UTC timestamp;ZonedDateTimeadds zone rules.- All types are immutable: always use the returned value from
plus/minus/with. Periodmeasures calendar amounts,Durationexact time,ChronoUnit.betweensingle 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.
