Java · lesson 62 of 66
Beginner
17- 1 Introduction to Java
- 2 Setting Up Java
- 3 Data Types in Java
- 4 Operators in Java
- 5 Variables and Operators in Java
- 6 Control Flow in Java
- 7 Arrays in Java
- 8 Functions (Methods) in Java
- 9 Strings in Java
- 10 Methods in Java
- 11 Java OOP — Complete Guide
- 12 Introduction to Object-Oriented Programming in Java
- 13 Encapsulation in Java
- 14 Inheritance in Java
- 15 Polymorphism in Java
- 17 Abstraction in Java
- 18 Error Types in Java
Intermediate
38- 16 Regular Expressions in Java
- 19 Interfaces in Java — A Deep Dive
- 20 Abstract Classes in Java
- 21 Method Overloading in Java
- 22 Composition vs Inheritance in Java
- 23 Generics in Java
- 27 Exception Handling in Java
- 28 Java Streams API
- 29 Collections Framework in Java
- 30 Optional in Java
- 31 File Handling in Java
- 32 Records in Java
- 33 Java 8 Features
- 34 Sealed Classes and Interfaces in Java
- 35 Multithreading in Java
- 37 JDBC — Database Access in Java
- 39 Maven — Java Build and Dependency Management
- 42 Gradle — Modern Java Build Tool
- 44 Unit Testing with JUnit 5 and Mockito
- 45 Logging in Java with SLF4J and Logback
- 49 Spring Boot — Introduction
- 50 Dependency Injection in Spring Boot
- 51 Java Project Ideas
- 52 Building REST APIs with Spring Boot
- 53 Validation in Spring Boot
- 54 Spring Data JPA — Database Access
- 55 Exception Handling in Spring Boot
- 56 Spring Security — Authentication and Authorization
- 57 JWT Authentication in Spring Boot
- 58 Dockerizing Spring Boot Applications
- 59 Java Enums: Beyond Simple Constants
- 60 Inner and Anonymous Classes in Java
- 61 Java Generics: Bounded Types, Wildcards, and Generic Methods
- 62 Java Date/Time API: LocalDate, LocalDateTime, and Duration
- 63 Java NIO.2: Paths, Files, and WatchService
- 64 Java Annotations: Custom Types and Meta-Information
- 65 Java Reflection: Dynamic Type Inspection and Method Invocation
- 66 Java Localization: ResourceBundle and Locale Support
Advanced
11Java Date/Time API: LocalDate, LocalDateTime, and Duration
Master java.time — LocalDate, LocalTime, LocalDateTime, Duration, and Period. Replace the old Date/Calendar mess with immutable, fluent time handling.
By SkillByExample · Updated
The old java.util.Date and Calendar classes are mutable, thread-unsafe and confusing
(January is month 0). The java.time package (Java 8+) replaces them with immutable types:
LocalDate for dates, LocalTime for times, LocalDateTime for both, ZonedDateTime for a
moment in a time zone, Duration for elapsed time and Period for calendar amounts. Examples
are single files run with the JDK 21 source launcher (java File.java), and they use fixed dates
so your output matches — except where now() is the point.
LocalDate
A LocalDate is a year, month and day with no time and no zone.
import java.time.LocalDate;
import java.time.Month;
public class Dates {
public static void main(String[] args) {
LocalDate birthday = LocalDate.of(1990, 6, 15);
LocalDate leapDay = LocalDate.of(2024, Month.FEBRUARY, 29);
System.out.println(birthday + " was a " + birthday.getDayOfWeek());
System.out.println(leapDay + " leap year? " + leapDay.isLeapYear());
System.out.println("day of year: " + leapDay.getDayOfYear());
System.out.println("today: " + LocalDate.now());
LocalDate.of(2023, 2, 29);
}
}
$ java Dates.java
1990-06-15 was a FRIDAY
2024-02-29 leap year? true
day of year: 60
today: 2026-10-07
Exception in thread "main" java.time.DateTimeException: Invalid date 'February 29' as '2023' is not a leap year
at java.base/java.time.LocalDate.create(LocalDate.java:457)
at java.base/java.time.LocalDate.of(LocalDate.java:275)
at Dates.main(Dates.java:12)
Months count from 1, so of(1990, 6, 15) is June. The today line depends on when you run it.
Invalid dates fail immediately with a clear message instead of silently rolling over to March 1,
as the old Calendar would in lenient mode.
Immutability: the result must be kept
Every “modifying” method returns a new object. Forgetting to keep it is the most common
java.time bug.
import java.time.LocalDate;
public class Immutable {
public static void main(String[] args) {
LocalDate due = LocalDate.of(2026, 1, 31);
due.plusDays(10); // result thrown away
System.out.println("after plusDays, ignored: " + due);
LocalDate later = due.plusDays(10);
System.out.println("kept: " + later);
System.out.println("plus 1 month: " + due.plusMonths(1));
}
}
$ java Immutable.java
after plusDays, ignored: 2026-01-31
kept: 2026-02-10
plus 1 month: 2026-02-28
due never changes. Adding a month to January 31 gives February 28, not an invalid February 31:
plusMonths clamps to the last valid day.
LocalTime and LocalDateTime
import java.time.*;
public class Times {
public static void main(String[] args) {
LocalTime meeting = LocalTime.of(14, 30);
System.out.println(meeting + ", hour " + meeting.getHour());
System.out.println("plus 10 hours: " + meeting.plusHours(10));
LocalDateTime appointment = LocalDateTime.of(2026, 12, 25, 9, 0);
System.out.println(appointment);
System.out.println(appointment.toLocalDate() + " / " + appointment.toLocalTime());
System.out.println("before New Year? " + appointment.isBefore(LocalDateTime.of(2027, 1, 1, 0, 0)));
}
}
$ java Times.java
14:30, hour 14
plus 10 hours: 00:30
2026-12-25T09:00
2026-12-25 / 09:00
before New Year? true
LocalTime has no date, so adding 10 hours to 14:30 wraps to 00:30 with no “next day” anywhere.
LocalDateTime prints with a T between date and time, and seconds are omitted when they are zero.
Duration and Period
Duration is an exact amount of time (seconds and nanoseconds). Period is years, months and days
on the calendar.
import java.time.*;
public class Amounts {
public static void main(String[] args) {
Duration workDay = Duration.between(LocalTime.of(9, 0), LocalTime.of(17, 30));
System.out.println(workDay + " = " + workDay.toHours() + "h " + workDay.toMinutesPart() + "m");
Period p = Period.between(LocalDate.of(2020, 1, 15), LocalDate.of(2026, 9, 14));
System.out.println(p + " = " + p.getYears() + "y " + p.getMonths() + "m " + p.getDays() + "d");
LocalDate jan31 = LocalDate.of(2026, 1, 31);
System.out.println("Period.ofMonths(1): " + jan31.plus(Period.ofMonths(1)));
System.out.println("Period.ofDays(30): " + jan31.plus(Period.ofDays(30)));
}
}
$ java Amounts.java
PT8H30M = 8h 30m
P6Y7M30D = 6y 7m 30d
Period.ofMonths(1): 2026-02-28
Period.ofDays(30): 2026-03-02
Both print in ISO-8601 duration format (PT8H30M, P6Y7M30D). “One month” and “30 days” are
different amounts: a month follows the calendar, so the two results land on different dates.
Counting between dates with ChronoUnit
Period.getDays() is only the leftover-days part. For a total count, use ChronoUnit.between.
import java.time.LocalDate;
import java.time.Period;
import java.time.temporal.ChronoUnit;
public class Between {
public static void main(String[] args) {
LocalDate start = LocalDate.of(2026, 1, 1);
LocalDate end = LocalDate.of(2026, 3, 15);
System.out.println("Period: " + Period.between(start, end));
System.out.println("getDays(): " + Period.between(start, end).getDays());
System.out.println("total days: " + ChronoUnit.DAYS.between(start, end));
System.out.println("total weeks: " + ChronoUnit.WEEKS.between(start, end));
}
}
$ java Between.java
Period: P2M14D
getDays(): 14
total days: 73
total weeks: 10
Asking a Period for “days” gives 14 — the remainder after two whole months — not the 73 days
between the dates. ChronoUnit counts whole units and truncates, so 73 days is 10 whole weeks.
Time zones: ZonedDateTime and Instant
A LocalDateTime doesn’t know where it is. Attach a zone to get a real moment; then daylight
saving time is handled for you.
import java.time.*;
public class Zones {
public static void main(String[] args) {
ZoneId ny = ZoneId.of("America/New_York");
ZonedDateTime beforeDst = ZonedDateTime.of(2026, 3, 8, 1, 30, 0, 0, ny);
System.out.println(beforeDst);
System.out.println(beforeDst.plusHours(1));
ZonedDateTime tokyo = beforeDst.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));
System.out.println(tokyo);
System.out.println(beforeDst.toInstant() + " (same instant in UTC)");
}
}
$ java Zones.java
2026-03-08T01:30-05:00[America/New_York]
2026-03-08T03:30-04:00[America/New_York]
2026-03-08T15:30+09:00[Asia/Tokyo]
2026-03-08T06:30:00Z (same instant in UTC)
On 8 March 2026 New York clocks jump from 2:00 to 3:00, so one hour after 01:30 is 03:30 and the
offset changes from -05:00 to -04:00. withZoneSameInstant shows the same moment on a Tokyo
clock, and Instant is that moment in UTC.
Formatting and parsing
DateTimeFormatter.ofPattern converts both ways. Text such as month names depends on the locale,
so pass one explicitly.
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class Formatting {
public static void main(String[] args) {
LocalDate date = LocalDate.of(2026, 9, 14);
DateTimeFormatter us = DateTimeFormatter.ofPattern("MMM d, yyyy", Locale.US);
DateTimeFormatter full = DateTimeFormatter.ofPattern("EEEE, MMMM d, yyyy", Locale.US);
System.out.println(date.format(us));
System.out.println(date.format(full));
System.out.println(date.format(DateTimeFormatter.ofPattern("dd/MM/yyyy")));
System.out.println(LocalDate.parse("Sep 14, 2026", us));
System.out.println(LocalDate.parse("September 14, 2026", us));
}
}
$ java Formatting.java
Sep 14, 2026
Monday, September 14, 2026
14/09/2026
2026-09-14
Exception in thread "main" java.time.format.DateTimeParseException: Text 'September 14, 2026' could not be parsed at index 3
at java.base/java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:2108)
at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:2010)
at java.base/java.time.LocalDate.parse(LocalDate.java:435)
at Formatting.main(Formatting.java:14)
MMM is the short month name and MMMM the full one, and parsing is strict about which: MMM
matches the “Sep” in “September”, then fails at index 3 where it expected a space. Parse with the same pattern that produced the text.
Practice
- Write
age(LocalDate birth, LocalDate on)returning aPeriod, and print the age of someone born 1990-06-15 on 2026-09-14.
Solution
import java.time.LocalDate;
import java.time.Period;
public class Age {
static Period age(LocalDate birth, LocalDate on) { return Period.between(birth, on); }
public static void main(String[] args) {
Period a = age(LocalDate.of(1990, 6, 15), LocalDate.of(2026, 9, 14));
System.out.printf("%d years, %d months, %d days%n", a.getYears(), a.getMonths(), a.getDays());
}
}
$ java Age.java
36 years, 2 months, 30 days
Passing the “on” date in, rather than calling LocalDate.now() inside, makes the method testable
and its output repeatable.
- Print the date of the next Friday after 2026-09-14, and the last day of that month, using
TemporalAdjusters.
Solution
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;
public class Adjusters {
public static void main(String[] args) {
LocalDate d = LocalDate.of(2026, 9, 14);
System.out.println(d.with(TemporalAdjusters.next(DayOfWeek.FRIDAY)));
System.out.println(d.with(TemporalAdjusters.lastDayOfMonth()));
System.out.println(d.with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY)));
}
}
$ java Adjusters.java
2026-09-18
2026-09-30
2026-09-07
with(adjuster) returns a new date computed by the adjuster; the original is unchanged.
- A flight leaves London at 2026-07-01T10:00 and lands in New York 8 hours later. Print the local arrival time in New York.
Solution
import java.time.*;
public class Flight {
public static void main(String[] args) {
ZonedDateTime departs = ZonedDateTime.of(2026, 7, 1, 10, 0, 0, 0, ZoneId.of("Europe/London"));
ZonedDateTime arrives = departs.plusHours(8).withZoneSameInstant(ZoneId.of("America/New_York"));
System.out.println("departs " + departs);
System.out.println("arrives " + arrives);
}
}
$ java Flight.java
departs 2026-07-01T10:00+01:00[Europe/London]
arrives 2026-07-01T13:00-04:00[America/New_York]
Eight hours in the air but only three on the clock, because New York is five hours behind London in summer.
- Parse
"14/09/2026"into aLocalDateand print how many days remain until 2026-12-25.
Solution
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
public class Countdown {
public static void main(String[] args) {
LocalDate d = LocalDate.parse("14/09/2026", DateTimeFormatter.ofPattern("dd/MM/yyyy"));
System.out.println(d + ": " + ChronoUnit.DAYS.between(d, LocalDate.of(2026, 12, 25)) + " days to go");
}
}
$ java Countdown.java
2026-09-14: 102 days to go
The parsed date prints in ISO format regardless of the input pattern: a LocalDate stores the
value, not the text it came from.