V
Vel·ToolKit
Simple · Fast · Ready to use
EN
Chapter 17 of 20

Date & Time API

java.time: Local/Zoned/Instant, Duration vs Period, formatting and parsing

Why Not Date / Calendar

Pre-Java-8 Date / Calendar are historical baggage: mutable (not thread-safe), months start at 0, messy design. New projects always use java.time (JSR-310, inspired by Joda-Time): immutable, thread-safe, clean API. All examples below use java.time.

  • Use cases: user signup time, order countdown, scheduled tasks, cross-timezone meetings, billing cycles
  • Core classes at a glance: LocalDate (date) / LocalTime (time) / LocalDateTime (zoneless date+time) / ZonedDateTime (with zone) / Instant (epoch timestamp) / Duration (length in seconds/nanos) / Period (length in years/months/days)

LocalDate / LocalTime / LocalDateTime

Three "zoneless" classes: LocalDate has only a date, LocalTime only a time, LocalDateTime both. Good for "business dates" (birthdays, business hours) independent of any specific timezone.

// LocalDemo.java
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;

public class LocalDemo {
    public static void main(String[] args) {
        LocalDate today = LocalDate.now();
        LocalDate birthday = LocalDate.of(1990, Month.MAY, 12);
        System.out.println(today + " " + birthday);

        LocalTime noon = LocalTime.of(12, 0);
        LocalTime now  = LocalTime.now();
        System.out.println(noon + " " + now);

        LocalDateTime meeting = LocalDateTime.of(2026, 5, 12, 14, 30);
        System.out.println(meeting);

        // add/subtract: returns a new object
        System.out.println(today.plusDays(7));
        System.out.println(today.minusMonths(1));
        System.out.println(today.withDayOfMonth(1));  // the 1st of this month

        // field access
        System.out.println("year=" + today.getYear() + " month=" + today.getMonthValue() + " day=" + today.getDayOfMonth());
        System.out.println("weekday=" + today.getDayOfWeek());
    }
}

ZonedDateTime and Time Zones

Use case: show "what time it is in Shanghai now", "the time a London colleague sees", cross-timezone meetings. ZonedDateTime = LocalDateTime + ZoneId. The same instant shows different clock numbers under different ZoneIds.

// ZonedDemo.java
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class ZonedDemo {
    public static void main(String[] args) {
        ZoneId sh = ZoneId.of("Asia/Shanghai");
        ZoneId ny = ZoneId.of("America/New_York");

        ZonedDateTime nowSh = ZonedDateTime.now(sh);
        ZonedDateTime nowNy = nowSh.withZoneSameInstant(ny);  // same instant, switch time zone
        System.out.println(nowSh);
        System.out.println(nowNy);

        // their Instants are equal
        System.out.println(nowSh.toInstant().equals(nowNy.toInstant())); // true
    }
}

Instant: the epoch Timestamp

Use cases: passing values to external systems, timestamp columns in a DB, log ordering, computing time differences. Instant is "seconds/nanos since 1970-01-01 UTC" — zoneless, an absolute instant.

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

public class InstantDemo {
    public static void main(String[] args) {
        Instant now = Instant.now();
        System.out.println(now);                  // 2026-05-12T08:30:00.123Z
        System.out.println(now.toEpochMilli());   // Unix millis
        System.out.println(now.getEpochSecond()); // Unix seconds

        // Instant <-> ZonedDateTime
        ZonedDateTime sh = now.atZone(ZoneId.of("Asia/Shanghai"));
        System.out.println(sh);

        // reconstruct from epoch millis
        Instant fromMs = Instant.ofEpochMilli(1_715_000_000_000L);
        System.out.println(fromMs);
    }
}

Duration vs Period

Duration expresses "a precise length based on seconds/nanos" (5 hours, 200 ms); Period expresses "a calendar-level length in years/months/days" (2 months, 3 years). They are not interchangeable: "one month" has no fixed length (28~31 days) and cannot be expressed with Duration.

// DurationPeriod.java
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.Period;

public class DurationPeriod {
    public static void main(String[] args) {
        // Duration: seconds/nanos
        Duration d = Duration.ofMinutes(90).plusSeconds(30);
        System.out.println(d);                 // PT1H30M30S
        System.out.println(d.toMinutes() + "m"); // 90m

        LocalTime t1 = LocalTime.of(9, 0);
        LocalTime t2 = LocalTime.of(17, 30);
        Duration worked = Duration.between(t1, t2);
        System.out.println(worked.toHours() + "h");

        // Period: years/months/days
        Period p = Period.of(2, 6, 0);            // 2 years 6 months
        LocalDate from = LocalDate.of(2020, 1, 1);
        LocalDate to   = LocalDate.of(2026, 5, 12);
        Period age = Period.between(from, to);
        System.out.printf("%dy %dm %dd%n", age.getYears(), age.getMonths(), age.getDays());
    }
}

DateTimeFormatter: Formatting and Parsing

Use cases: API time fields, log timestamps, CSV/Excel export. Provides common formats (ISO_DATE / ISO_DATE_TIME / RFC_1123) and custom patterns. **Unlike SimpleDateFormat, DateTimeFormatter is thread-safe and can be a shared static final field.**

// FormatDate.java
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class FormatDate {
    static final DateTimeFormatter FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");

    public static void main(String[] args) {
        LocalDateTime now = LocalDateTime.of(2026, 5, 12, 14, 30);

        // standard ISO
        System.out.println(now.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
        // custom
        System.out.println(now.format(FMT));
        // short form
        System.out.println(now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));

        // parse
        LocalDate d = LocalDate.parse("2026-05-12");
        System.out.println(d);

        LocalDateTime dt = LocalDateTime.parse(
            "2026-05-12 14:30",
            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
        );
        System.out.println(dt);
    }
}

ChronoUnit: Precise-Unit Calculation

Use case: compute days / hours / months between two dates. ChronoUnit.X.between(a, b) is clearer than hand-computing Duration / Period fields.

// ChronoBetween.java
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;

public class ChronoBetween {
    public static void main(String[] args) {
        LocalDate signup  = LocalDate.of(2024, 3, 1);
        LocalDate today   = LocalDate.of(2026, 5, 12);
        System.out.println(ChronoUnit.DAYS.between(signup, today));   // total days
        System.out.println(ChronoUnit.MONTHS.between(signup, today)); // 26

        LocalDateTime start = LocalDateTime.of(2026, 5, 12, 9,  0);
        LocalDateTime end   = LocalDateTime.of(2026, 5, 12, 14, 30);
        System.out.println(ChronoUnit.MINUTES.between(start, end));   // 330
    }
}

Common Date Calculations

// DateMath.java
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;

public class DateMath {
    public static void main(String[] args) {
        LocalDate today = LocalDate.of(2026, 5, 12);

        System.out.println(today.with(TemporalAdjusters.firstDayOfMonth()));      // first day of month
        System.out.println(today.with(TemporalAdjusters.lastDayOfMonth()));       // last day of month
        System.out.println(today.with(TemporalAdjusters.next(DayOfWeek.MONDAY))); // next Monday
        System.out.println(today.with(TemporalAdjusters.firstDayOfNextMonth()));  // 1st of next month

        // leap year
        System.out.println(today.isLeapYear());
    }
}