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:
| Class | Description | Example |
|---|---|---|
LocalDate | Date only (no time, no timezone) | 2026-03-23 |
LocalTime | Time only (no date, no timezone) | 14:30:00 |
LocalDateTime | Date + Time (no timezone) | 2026-03-23T14:30:00 |
ZonedDateTime | Date + Time + Timezone | 2026-03-23T14:30:00+09:00[Asia/Seoul] |
Instant | Machine timestamp (UTC epoch) | 2026-03-23T05:30:00Z |
Duration | Time-based amount (hours, minutes, seconds) | PT2H30M |
Period | Date-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)
Key principles of the java.time API:
-
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 -
Choose the right class:
- No timezone needed →
LocalDate,LocalTime,LocalDateTime - Cross-timezone communication →
ZonedDateTime - Store in DB / log timestamps →
Instant(convert to UTC)
- No timezone needed →
-
Avoid
DateandCalendar: In any new code using Java 8+, usejava.timeexclusively. Legacy interop only when required by external APIs. -
Month is 1-based:
LocalDate.of(2026, 3, 23)means March 23. No more off-by-one month errors!