Skip to main content

10.1 Date & Time API Introduction

Why a New Date/Time API?

Before Java 8, working with dates and times was notoriously painful. The java.util.Date and java.util.Calendar classes had serious design flaws: mutable state, confusing month numbering (0 = January), thread-unsafety, and a chaotic API. Java 8 introduced the java.time package — a clean, immutable, and intuitive replacement inspired by the Joda-Time library.

// Old way (problematic — don't use)
import java.util.Date;
import java.util.Calendar;

Date now = new Date(); // mutable, deprecated methods
Calendar cal = Calendar.getInstance();
cal.set(2024, 0, 1); // month 0 = January (confusing!)
int month = cal.get(Calendar.MONTH); // returns 0, not 1

// Modern way (Java 8+)
import java.time.LocalDate;
LocalDate today = LocalDate.now(); // immutable, clear, thread-safe
System.out.println(today); // 2026-03-23

1. Core Classes Overview

The java.time package provides dedicated classes for different use cases:

ClassDescriptionExample
LocalDateDate only (no time, no timezone)2026-03-23
LocalTimeTime only (no date, no timezone)14:30:00
LocalDateTimeDate + Time (no timezone)2026-03-23T14:30:00
ZonedDateTimeDate + Time + Timezone2026-03-23T14:30:00+09:00[Asia/Seoul]
InstantMachine timestamp (UTC epoch)2026-03-23T05:30:00Z
DurationTime-based amount (hours, minutes, seconds)PT2H30M
PeriodDate-based amount (years, months, days)P1Y2M3D

2. LocalDate — Date Without Time

LocalDate represents a date without any time or timezone information. Perfect for birthdays, holidays, and deadlines.

import java.time.LocalDate;
import java.time.Month;

// Creating LocalDate
LocalDate today = LocalDate.now(); // current date
LocalDate specificDate = LocalDate.of(2026, 3, 23); // March 23, 2026
LocalDate usingMonth = LocalDate.of(2026, Month.MARCH, 23); // same, using enum

System.out.println(today); // 2026-03-23
System.out.println(specificDate); // 2026-03-23

// Extracting fields
System.out.println(today.getYear()); // 2026
System.out.println(today.getMonthValue()); // 3 (not 2 like old Calendar!)
System.out.println(today.getMonth()); // MARCH
System.out.println(today.getDayOfMonth()); // 23
System.out.println(today.getDayOfWeek()); // MONDAY
System.out.println(today.getDayOfYear()); // 82
System.out.println(today.isLeapYear()); // false

3. LocalTime — Time Without Date

import java.time.LocalTime;

// Creating LocalTime
LocalTime now = LocalTime.now(); // current time
LocalTime specific = LocalTime.of(14, 30); // 14:30:00
LocalTime withSeconds = LocalTime.of(14, 30, 45); // 14:30:45
LocalTime withNanos = LocalTime.of(14, 30, 45, 500_000_000); // 14:30:45.5

System.out.println(now); // e.g., 14:30:22.123456789
System.out.println(specific); // 14:30

// Extracting fields
System.out.println(specific.getHour()); // 14
System.out.println(specific.getMinute()); // 30
System.out.println(specific.getSecond()); // 0

// Useful constants
System.out.println(LocalTime.MIN); // 00:00
System.out.println(LocalTime.MAX); // 23:59:59.999999999
System.out.println(LocalTime.NOON); // 12:00

4. LocalDateTime — Date and Time Combined

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;

// Creating LocalDateTime
LocalDateTime now = LocalDateTime.now();
LocalDateTime specific = LocalDateTime.of(2026, 3, 23, 14, 30, 0);

// Combining LocalDate and LocalTime
LocalDate date = LocalDate.of(2026, 3, 23);
LocalTime time = LocalTime.of(14, 30);
LocalDateTime combined = LocalDateTime.of(date, time);
// Also: date.atTime(time) or time.atDate(date)

System.out.println(now); // 2026-03-23T14:30:22.123
System.out.println(specific); // 2026-03-23T14:30

// Splitting back
LocalDate extractedDate = specific.toLocalDate(); // 2026-03-23
LocalTime extractedTime = specific.toLocalTime(); // 14:30

5. ZonedDateTime — With Timezone

When you need to handle users in different timezones, use ZonedDateTime.

import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.time.LocalDateTime;

// Current time in a specific timezone
ZonedDateTime seoulTime = ZonedDateTime.now(ZoneId.of("Asia/Seoul"));
ZonedDateTime nyTime = ZonedDateTime.now(ZoneId.of("America/New_York"));
ZonedDateTime utcTime = ZonedDateTime.now(ZoneId.of("UTC"));

System.out.println(seoulTime); // 2026-03-23T14:30:00+09:00[Asia/Seoul]
System.out.println(nyTime); // 2026-03-23T01:30:00-04:00[America/New_York]
System.out.println(utcTime); // 2026-03-23T05:30:00Z[UTC]

// Converting between timezones
ZonedDateTime converted = seoulTime.withZoneSameInstant(ZoneId.of("America/New_York"));
System.out.println(converted); // same moment, different local time

// Available zone IDs
ZoneId.getAvailableZoneIds().stream()
.filter(id -> id.startsWith("Asia"))
.sorted()
.limit(5)
.forEach(System.out::println);
// Asia/Aden, Asia/Almaty, Asia/Amman, Asia/Anadyr, Asia/Aqtau

6. Instant — Machine Timestamp

Instant represents a point on the timeline in UTC — useful for logging, storing to databases, and API timestamps.

import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;

Instant now = Instant.now();
System.out.println(now); // 2026-03-23T05:30:00.123456789Z

// Epoch seconds (milliseconds since 1970-01-01T00:00:00Z)
long epochSeconds = now.getEpochSecond(); // e.g., 1742714200
long epochMillis = now.toEpochMilli(); // e.g., 1742714200123

// Creating from epoch
Instant fromEpoch = Instant.ofEpochSecond(1742714200);
Instant fromMillis = Instant.ofEpochMilli(1742714200123L);

// Converting Instant to ZonedDateTime (for display)
ZonedDateTime display = now.atZone(ZoneId.of("Asia/Seoul"));
System.out.println(display); // 2026-03-23T14:30:00.123456789+09:00[Asia/Seoul]

// Measuring elapsed time
Instant start = Instant.now();
// ... some work ...
Instant end = Instant.now();
long elapsed = end.toEpochMilli() - start.toEpochMilli();
System.out.println("Elapsed: " + elapsed + "ms");

7. Modifying Dates: plus, minus, with

All java.time objects are immutable. Every modification returns a new object— the original is never changed.

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.DayOfWeek;
import java.time.temporal.TemporalAdjusters;

LocalDate today = LocalDate.of(2026, 3, 23);

// Adding and subtracting
LocalDate nextWeek = today.plusDays(7); // 2026-03-30
LocalDate nextMonth = today.plusMonths(1); // 2026-04-23
LocalDate nextYear = today.plusYears(1); // 2027-03-23
LocalDate twoWeeksAgo = today.minusWeeks(2); // 2026-03-09
LocalDate threeMonthsAgo = today.minusMonths(3); // 2025-12-23

System.out.println("Next week: " + nextWeek);
System.out.println("Next month: " + nextMonth);
System.out.println("3 months ago: " + threeMonthsAgo);

// with(): set a specific field to a fixed value
LocalDate firstOfMonth = today.withDayOfMonth(1); // 2026-03-01
LocalDate lastOfYear = today.withDayOfYear(365); // 2026-12-31
LocalDate differentYear = today.withYear(2025); // 2025-03-23

// Chaining modifications
LocalDate result = today
.plusMonths(2)
.withDayOfMonth(1)
.minusDays(1); // last day of April 2026 → 2026-04-30
System.out.println(result);

8. Comparing Dates: isBefore, isAfter, isEqual

import java.time.LocalDate;

LocalDate date1 = LocalDate.of(2026, 1, 1);
LocalDate date2 = LocalDate.of(2026, 6, 15);
LocalDate date3 = LocalDate.of(2026, 1, 1);

System.out.println(date1.isBefore(date2)); // true
System.out.println(date2.isBefore(date1)); // false
System.out.println(date1.isAfter(date2)); // false
System.out.println(date2.isAfter(date1)); // true
System.out.println(date1.isEqual(date3)); // true
System.out.println(date1.equals(date3)); // true (same as isEqual for LocalDate)

// compareTo: negative if before, 0 if equal, positive if after
int comparison = date1.compareTo(date2); // negative value

// Checking a range
LocalDate start = LocalDate.of(2026, 1, 1);
LocalDate end = LocalDate.of(2026, 12, 31);
LocalDate check = LocalDate.of(2026, 6, 15);

boolean inRange = !check.isBefore(start) && !check.isAfter(end);
System.out.println("In range: " + inRange); // true

9. Practical Example: Age Calculator

import java.time.LocalDate;
import java.time.Period;
import java.time.format.DateTimeFormatter;

public class AgeCalculator {

public static int calculateAge(LocalDate birthDate) {
LocalDate today = LocalDate.now();
if (birthDate.isAfter(today)) {
throw new IllegalArgumentException("Birth date cannot be in the future.");
}
return Period.between(birthDate, today).getYears();
}

public static String getDetailedAge(LocalDate birthDate) {
LocalDate today = LocalDate.now();
Period age = Period.between(birthDate, today);
return String.format("%d years, %d months, %d days",
age.getYears(), age.getMonths(), age.getDays());
}

public static LocalDate nextBirthday(LocalDate birthDate) {
LocalDate today = LocalDate.now();
LocalDate thisYear = birthDate.withYear(today.getYear());
return thisYear.isBefore(today) || thisYear.isEqual(today)
? thisYear.plusYears(1)
: thisYear;
}

public static void main(String[] args) {
LocalDate birthDate = LocalDate.of(1995, 8, 15);

System.out.println("Age: " + calculateAge(birthDate) + " years old");
System.out.println("Detailed age: " + getDetailedAge(birthDate));

LocalDate next = nextBirthday(birthDate);
long daysUntil = LocalDate.now().until(next, java.time.temporal.ChronoUnit.DAYS);
System.out.println("Next birthday: " + next
+ " (in " + daysUntil + " days)");
}
}

Output:

Age: 30 years old
Detailed age: 30 years, 7 months, 8 days
Next birthday: 2026-08-15 (in 145 days)

Pro Tips

Key principles of the java.time API:

  1. Immutability: Every method that "modifies" a date returns a new object. Always capture the return value.

    LocalDate date = LocalDate.of(2026, 1, 1);
    date.plusDays(7); // BUG: result is discarded!
    date = date.plusDays(7); // CORRECT
  2. Choose the right class:

    • No timezone needed → LocalDate, LocalTime, LocalDateTime
    • Cross-timezone communication → ZonedDateTime
    • Store in DB / log timestamps → Instant (convert to UTC)
  3. Avoid Date and Calendar: In any new code using Java 8+, use java.time exclusively. Legacy interop only when required by external APIs.

  4. Month is 1-based: LocalDate.of(2026, 3, 23) means March 23. No more off-by-one month errors!