Java 8's java.time package replaced the broken Date and Calendar classes with immutable, thread-safe types that say exactly what they mean.
Why the old API was bad
The main types
| Type | Represents | Example |
|---|---|---|
LocalDate | a date without time or zone | 2024-03-15 — birthdays, holidays |
LocalTime | a time of day | 09:30 |
LocalDateTime | date + time, no zone | 2024-03-15T09:30 — a meeting "on the wall clock" |
ZonedDateTime | date + time + zone rules (DST aware) | 2024-03-15T09:30+05:30[Asia/Kolkata] |
Instant | a point on the UTC timeline (epoch nanos) | 2024-03-15T04:00:00Z — timestamps, logs |
Duration | a time-based amount (seconds/nanos) | PT1H30M |
Period | a date-based amount (years/months/days) | P1Y2M3D |
DateTimeFormatter | immutable, thread-safe formatter/parser | ofPattern("dd/MM/yyyy") |
Creating and manipulating (always returns a new object)
import java.time.*;
import java.time.temporal.ChronoUnit;
public class Main {
public static void main(String[] args) {
LocalDate d = LocalDate.of(2024, 1, 31);
System.out.println(d);
System.out.println(d.plusMonths(1)); // clamps to Feb 29 (leap year)
System.out.println(d.plusDays(1).getDayOfWeek());
System.out.println(d.isLeapYear() + " " + d.lengthOfMonth());
d.plusYears(1); // result ignored — d unchanged!
System.out.println(d);
LocalDateTime meeting = LocalDateTime.of(2024, 3, 15, 9, 30);
System.out.println(meeting.plusHours(2).withMinute(0));
LocalDate a = LocalDate.of(2024, 1, 1), b = LocalDate.of(2024, 12, 25);
System.out.println(ChronoUnit.DAYS.between(a, b));
System.out.println(Period.between(a, b));
System.out.println(a.isBefore(b));
}
}2024-01-31
2024-02-29
THURSDAY
true 31
2024-01-31
2024-03-15T11:00
359
P11M24D
trueZones, instants and durations
import java.time.*;
public class Main {
public static void main(String[] args) {
LocalDateTime local = LocalDateTime.of(2024, 3, 15, 9, 30);
ZonedDateTime india = local.atZone(ZoneId.of("Asia/Kolkata"));
ZonedDateTime ny = india.withZoneSameInstant(ZoneId.of("America/New_York"));
System.out.println(india);
System.out.println(ny);
Instant instant = india.toInstant();
System.out.println(instant);
System.out.println(instant.getEpochSecond());
System.out.println(Instant.ofEpochSecond(0));
Duration flight = Duration.ofMinutes(135);
System.out.println(flight + " = " + flight.toHours() + "h " + flight.toMinutesPart() + "m");
System.out.println(Duration.between(LocalTime.of(9, 0), LocalTime.of(17, 45)));
}
}2024-03-15T09:30+05:30[Asia/Kolkata]
2024-03-15T00:00-04:00[America/New_York]
2024-03-15T04:00:00Z
1710475200
1970-01-01T00:00:00Z
PT2H15M = 2h 15m
PT8H45MFormatting and parsing
import java.time.*;
import java.time.format.*;
public class Main {
public static void main(String[] args) {
DateTimeFormatter f = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm");
LocalDateTime t = LocalDateTime.of(2024, 7, 4, 18, 5);
System.out.println(t.format(f));
System.out.println(LocalDateTime.parse("25/12/2024 08:00", f));
System.out.println(LocalDate.parse("2024-02-29")); // ISO format by default
System.out.println(t.format(DateTimeFormatter.ISO_LOCAL_DATE));
try {
LocalDate.parse("2023-02-29");
} catch (DateTimeParseException e) {
System.out.println("invalid: " + e.getParsedString());
}
}
}04/07/2024 18:05
2024-12-25T08:00
2024-02-29
2024-07-04
invalid: 2023-02-29| Pattern letter | Meaning | Trap |
|---|---|---|
yyyy / uuuu | year | YYYY is the week-based year — wrong around New Year |
MM / mm | month / minute | mixing them up is the #1 formatting bug |
dd / DD | day of month / day of year | |
HH / hh + a | 24-hour / 12-hour + AM/PM |
Why is java.time better than java.util.Date?
LocalDateTime vs ZonedDateTime vs Instant?
How do you convert between old and new APIs?