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-safeDate objects can be changed after creation, causing bugs in shared or cached state.
  • Poor API design — months in Calendar are 0-indexed (January = 0, December = 11), years in Date are relative to 1900, and years in Calendar are used normally — two inconsistent conventions in the same API.
  • No separation of conceptsDate represents 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-safeSimpleDateFormat is 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);
    }
}
Today: 2026-07-23
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