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
DurationandInstantare built on. -
Strings and Text —
format/parseround-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.