Java Date and Time API - LocalDate, LocalDateTime, ZonedDateTime | Java Codeex
1. Problems with the Old Date API
Before Java 8, date and time handling relied on
java.util.Date and
java.util.Calendar. These classes had
severe design problems that caused bugs and confusion for years:
-
Mutable and not thread-safe —
Dateobjects can be changed after creation, causing bugs in shared or cached state. -
Poor API design — months in
Calendarare 0-indexed (January = 0, December = 11), years inDateare relative to 1900, and years inCalendarare used normally — two inconsistent conventions in the same API. -
No separation of concepts —
Daterepresents a point in time (timestamp) but is also used as a calendar date, with no clean way to represent just a date, just a time, or a date+time without a timezone. - Confusing timezone handling — mixing of local and UTC times leads to subtle off-by-hours bugs.
-
Formatting is not thread-safe —
SimpleDateFormatis stateful and must not be shared across threads.
// Old API — full of traps
import java.util.*;
public class OldDateProblems {
public static void main(String[] args) {
// java.util.Date — year is offset from 1900, month is 0-indexed!
Date d = new Date(2024 - 1900, 0, 15); // Jan 15, 2024
System.out.println("Old Date: " + d); // Confusing output
// java.util.Calendar — months still 0-indexed
Calendar cal = Calendar.getInstance();
cal.set(2024, Calendar.JANUARY, 15); // Must use constant or risk off-by-one
System.out.println("Year: " + cal.get(Calendar.YEAR));
System.out.println("Month: " + cal.get(Calendar.MONTH)); // 0 = January!
System.out.println("Day: " + cal.get(Calendar.DAY_OF_MONTH));
// Mutable — dangerous when shared
Date original = new Date();
Date copy = original; // not a copy — same reference
copy.setTime(0); // accidentally mutates 'original' too
System.out.println("original == copy? " + (original.getTime() == copy.getTime()));
}
}
Best practice: Avoid
java.util.Date and
java.util.Calendar in new code. Use
java.time classes introduced in Java
8 instead.
2. Overview of java.time Package
The java.time package (inspired by the
Joda-Time library) provides a clean, immutable, and thread-safe
date/time API. All objects are immutable —
operations return new instances rather than modifying existing
ones.
| Class | Represents | Example |
|---|---|---|
LocalDate |
Date without time or timezone | 2026-07-23 |
LocalTime |
Time without date or timezone | 14:30:00.000 |
LocalDateTime |
Date + time without timezone | 2026-07-23T14:30:00 |
ZonedDateTime |
Date + time + timezone | 2026-07-23T14:30:00-05:00[America/New_York] |
Instant |
Machine timestamp (epoch seconds) | 2026-07-23T19:30:00Z |
Duration |
Time-based amount (hours, minutes, seconds) | PT2H30M |
Period |
Date-based amount (years, months, days) | P1Y6M10D |
DateTimeFormatter |
Format/parse date-time strings | "dd-MM-yyyy HH:mm" |
Immutability: All
java.time classes are immutable and
thread-safe. Methods like
plusDays() return a
new object — the original is unchanged.
3. LocalDate
LocalDate represents a date (year,
month, day) without any time-of-day or timezone information. It is
ideal for representing birthdays, anniversaries, deadlines, or any
concept that is just a calendar date.
import java.time.*;
import java.time.temporal.ChronoUnit;
public class LocalDateDemo {
public static void main(String[] args) {
// Create LocalDate instances
LocalDate today = LocalDate.now(); // current date
LocalDate specific = LocalDate.of(2026, 7, 23); // July 23, 2026
LocalDate parsed = LocalDate.parse("2025-12-25"); // ISO-8601 string
System.out.println("Today: " + today);
System.out.println("Specific: " + specific);
System.out.println("Parsed: " + parsed);
// Accessing fields
System.out.println("Year: " + today.getYear());
System.out.println("Month: " + today.getMonth()); // JULY
System.out.println("MonthValue: " + today.getMonthValue()); // 7 (1-indexed!)
System.out.println("Day: " + today.getDayOfMonth());
System.out.println("DayOfWeek: " + today.getDayOfWeek()); // WEDNESDAY
System.out.println("DayOfYear: " + today.getDayOfYear());
System.out.println("IsLeapYear: " + today.isLeapYear());
// Arithmetic — returns new instances (immutable)
LocalDate nextWeek = today.plusDays(7);
LocalDate prevMonth = today.minusMonths(1);
LocalDate nextYear = today.plusYears(1);
System.out.println("Next week: " + nextWeek);
System.out.println("Prev month: " + prevMonth);
System.out.println("Next year: " + nextYear);
// Comparison
LocalDate date1 = LocalDate.of(2024, 1, 1);
LocalDate date2 = LocalDate.of(2025, 6, 15);
System.out.println("date1 isBefore date2? " + date1.isBefore(date2));
System.out.println("date2 isAfter date1? " + date2.isAfter(date1));
System.out.println("Same date? " + date1.isEqual(date1));
// Days between two dates
long daysBetween = ChronoUnit.DAYS.between(date1, date2);
System.out.println("Days between: " + daysBetween);
}
}
Specific: 2026-07-23
Parsed: 2025-12-25
Year: 2026
Month: JULY
MonthValue: 7
Day: 23
DayOfWeek: THURSDAY
DayOfYear: 204
IsLeapYear: false
Next week: 2026-07-30
Prev month: 2026-06-23
Next year: 2027-07-23
date1 isBefore date2? true
date2 isAfter date1? true
Same date? true