Dates and Times

This section documents the current Java release line, with Java 25 LTS as the reference point (no specific patch version is pinned), as published at the Java developer portal, the Java Tutorials, and the Java SE API specification — which are the references these pages are written and verified against.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

The modern date-and-time API lives in the java.time package: a set of immutable, thread-safe value types that separate human calendar concepts (LocalDate) from machine timestamps (Instant) and from amounts of elapsed time (Duration, Period). This page walks the core types, conversions between them, and formatting. The narrative overview is at dev.java: The Date Time API and the Java Tutorials Date-Time trail; the package contract is java.time package-summary.

The Local Types: LocalDate, LocalTime, LocalDateTime

These three carry no time zone. A LocalDate is a date with no time-of-day, a LocalTime is a time-of-day with no date, and a LocalDateTime pairs the two. Each offers now(…​), several of(…​) factories, and parse(…​) (ISO-8601 by default). See the tutorial pages Date Classes and Date and Time Classes.

import java.time.*;

LocalDate today   = LocalDate.now();                 // system clock + default zone
LocalDate release = LocalDate.of(2025, Month.MARCH, 18);
LocalDate parsed  = LocalDate.parse("2025-03-18");   // ISO-8601: yyyy-MM-dd

LocalTime noon    = LocalTime.of(12, 0);
LocalTime precise = LocalTime.parse("09:41:30.500");

LocalDateTime meeting = LocalDateTime.of(2025, 3, 18, 9, 41);
LocalDateTime combined = release.atTime(noon);        // 2025-03-18T12:00

Fields, plus/minus, and comparisons

Every accessor is a simple getter; every arithmetic method returns a new instance, so calls chain fluently and the original is never touched.

import java.time.*;
import java.time.temporal.ChronoUnit;

LocalDate d = LocalDate.of(2025, 3, 18);

int year        = d.getYear();          // 2025
Month month     = d.getMonth();         // MARCH
int dayOfMonth  = d.getDayOfMonth();    // 18
DayOfWeek dow   = d.getDayOfWeek();     // TUESDAY
boolean leap    = d.isLeapYear();       // false

LocalDate later = d.plusWeeks(2).minusDays(1);   // 2025-03-31, original d unchanged
LocalDate q3    = d.plus(2, ChronoUnit.MONTHS);  // 2025-05-18

boolean before  = d.isBefore(later);    // true
boolean after   = d.isAfter(later);     // false
int cmp         = d.compareTo(later);   // negative

LocalDateTime start = LocalDateTime.of(2025, 3, 18, 9, 0);
LocalDateTime end   = start.plusHours(1).plusMinutes(30);   // 2025-03-18T10:30

Because the types are immutable, ignoring the return value is a common bug: d.plusDays(1); on its own does nothing. Always assign the result.

Machine Time and Zones: Instant, ZonedDateTime, OffsetDateTime

An Instant is a point on the UTC timeline, counted in seconds and nanoseconds from the 1970 epoch — the right type for timestamps, logging, and durations between events. See The Instant Class.

import java.time.*;

Instant now   = Instant.now();
Instant epoch = Instant.ofEpochSecond(0);              // 1970-01-01T00:00:00Z
Instant later = now.plusSeconds(90);
long millis   = now.toEpochMilli();
boolean b     = epoch.isBefore(now);                   // true

To attach a zone, combine an Instant (or a LocalDateTime) with a ZoneId to get a ZonedDateTime (full time-zone rules, including daylight saving), or with a fixed ZoneOffset to get an OffsetDateTime (a constant +hh:mm from UTC, common in wire formats). See Time Zone and Offset Classes.

import java.time.*;

ZoneId madrid = ZoneId.of("Europe/Madrid");
ZoneId utc    = ZoneOffset.UTC;

Instant instant = Instant.parse("2025-07-15T10:00:00Z");

ZonedDateTime inMadrid = instant.atZone(madrid);        // 2025-07-15T12:00+02:00[Europe/Madrid] (DST)
ZonedDateTime inTokyo   = inMadrid.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));
                                                         // 2025-07-15T19:00+09:00[Asia/Tokyo]

OffsetDateTime odt = OffsetDateTime.of(2025, 7, 15, 10, 0, 0, 0, ZoneOffset.ofHours(-5));

// conversions
Instant back        = inMadrid.toInstant();             // same point on the timeline
LocalDateTime wall  = inMadrid.toLocalDateTime();       // drops the zone
LocalDate onlyDate  = inMadrid.toLocalDate();

withZoneSameInstant keeps the same moment and changes the wall-clock reading; withZoneSameLocal keeps the wall-clock reading and changes the moment. Use Instant for storage and comparison, and convert to a zoned type only for display.

Amounts of Time: Duration, Period, ChronoUnit, TemporalAdjusters

Duration is a time-based amount (seconds and nanos — "36 hours"); it fits Instant and LocalTime. Period is a date-based amount (years, months, days — "2 months and 3 days"); it fits LocalDate. They are not interchangeable. See Period and Duration.

import java.time.*;
import java.time.temporal.ChronoUnit;

Duration d = Duration.ofHours(36);
d.toDays();          // 1
d.toHours();         // 36
Duration between = Duration.between(
        LocalTime.of(9, 0), LocalTime.of(17, 30));       // PT8H30M

Period p = Period.of(1, 2, 3);                            // 1 year, 2 months, 3 days
Period age = Period.between(
        LocalDate.of(2000, 1, 1), LocalDate.of(2025, 3, 18));   // P25Y2M17D
age.getYears();      // 25

LocalDate start = LocalDate.of(2025, 1, 1);
LocalDate end   = LocalDate.of(2025, 3, 18);
long days = ChronoUnit.DAYS.between(start, end);          // 76
long months = ChronoUnit.MONTHS.between(start, end);      // 2

ChronoUnit is an enum of units (DAYS, HOURS, MONTHS, …​) with a between(start, end) method that returns a plain long — handy when you want a single number rather than a Period/Duration object.

TemporalAdjusters supplies ready-made "move to…​" operations for with(…​); see Temporal Adjusters.

import java.time.*;
import static java.time.temporal.TemporalAdjusters.*;

LocalDate d = LocalDate.of(2025, 3, 18);

d.with(firstDayOfMonth());          // 2025-03-01
d.with(lastDayOfMonth());           // 2025-03-31
d.with(next(DayOfWeek.MONDAY));     // 2025-03-24
d.with(firstDayOfNextYear());       // 2026-01-01

// a custom adjuster as a lambda: the next working day
TemporalAdjuster nextWorkday = temporal -> {
    LocalDate result = LocalDate.from(temporal);
    do { result = result.plusDays(1); }
    while (result.getDayOfWeek().getValue() >= 6);   // skip SAT (6) and SUN (7)
    return result;
};
LocalDate friday = LocalDate.of(2025, 3, 21);
friday.with(nextWorkday);           // 2025-03-24 (Monday)

Parsing and Formatting: DateTimeFormatter

DateTimeFormatter is immutable and thread-safe (unlike the legacy SimpleDateFormat). Every java.time type has format(formatter) and a static parse(text, formatter). See Parsing and Formatting.

import java.time.*;
import java.time.format.*;
import java.util.Locale;

LocalDateTime dt = LocalDateTime.of(2025, 3, 18, 9, 41);

// 1. predefined ISO constants
dt.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);       // 2025-03-18T09:41:00

// 2. explicit pattern
DateTimeFormatter pattern = DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm");
dt.format(pattern);                                     // 18/03/2025 09:41
LocalDateTime round = LocalDateTime.parse("18/03/2025 09:41", pattern);

// 3. localized, locale-sensitive
DateTimeFormatter localized = DateTimeFormatter
        .ofLocalizedDateTime(FormatStyle.MEDIUM)
        .withLocale(Locale.of("es", "ES"));
dt.format(localized);                                   // 18 mar 2025, 9:41:00

// zoned parsing/formatting needs a zone-aware formatter
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Europe/Madrid"));
zdt.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);     // 2025-03-18T09:41:00+01:00

Prefer the pattern letter u (year) over y (year-of-era) unless you deliberately need era handling. A parse failure throws DateTimeParseException.

Interop with legacy java.util.Date and Calendar

Older code and some libraries still hand you a java.util.Date (an unfortunate name — it is really an instant). Bridge with Date.toInstant() and Date.from(Instant), then stay in java.time. See Legacy Date-Time Code.

import java.time.*;
import java.util.Date;
import java.util.GregorianCalendar;

// legacy -> modern
Date legacy = new Date();
Instant instant = legacy.toInstant();
ZonedDateTime zdt = instant.atZone(ZoneId.systemDefault());

// modern -> legacy (only at an API boundary that demands it)
Date backToLegacy = Date.from(Instant.now());

// GregorianCalendar has a direct bridge
ZonedDateTime fromCal = new GregorianCalendar().toZonedDateTime();

Older Java material predates java.time; where it uses Date, Calendar, or SimpleDateFormat, use the types on this page instead — they are immutable, thread-safe, and far clearer about what a value represents.

See Also

  • Numbers and Math — the numeric types Duration and Instant are built on.

  • Strings and Text — format/parse round-trips and text blocks for sample data.

  • Optional — modelling a "maybe no date" result without null.

  • Regular Expressions — validating loose date input before handing it to a DateTimeFormatter.