Time Zones and Daylight Saving Time
Overview
LocalDate, LocalTime, and LocalDateTime have no concept of time zone. When time-zone awareness is needed, use:
| Class | Description |
|---|---|
ZoneId |
Identifier for a time zone (e.g., "America/New_York") |
ZoneOffset |
A fixed UTC offset (e.g., +05:30) |
ZonedDateTime |
Date and time with a time zone |
OffsetDateTime |
Date and time with a fixed UTC offset (no DST rules) |
ZoneId
ZoneId names a region-based time zone that follows the IANA Time Zone Database (also called the tz database).
javaZoneId newYork = ZoneId.of("America/New_York");
ZoneId london = ZoneId.of("Europe/London");
ZoneId utc = ZoneId.of("UTC");
ZoneId system = ZoneId.systemDefault();
// List available IDs
Set<String> allIds = ZoneId.getAvailableZoneIds();ZoneOffset represents a fixed offset from UTC — it does not adjust for DST:
javaZoneOffset plus5 = ZoneOffset.of("+05:30"); // India Standard Time offset
ZoneOffset utc = ZoneOffset.UTC; // +00:00
ZoneOffset minus5 = ZoneOffset.ofHours(-5); // EST offset (no DST)ZonedDateTime
ZonedDateTime holds a LocalDateTime, a ZoneId, and the effective ZoneOffset. It is the full date-time with time-zone rules applied.
Creating a ZonedDateTime
java// Now in a specific zone
ZonedDateTime now = ZonedDateTime.now(ZoneId.of("America/New_York"));
// From a LocalDateTime
LocalDateTime ldt = LocalDateTime.of(2025, 6, 15, 10, 30);
ZonedDateTime zdt = ldt.atZone(ZoneId.of("America/Chicago")); // CDT = UTC-5
// From an Instant
Instant ts = Instant.now();
ZonedDateTime fromInstant = ts.atZone(ZoneId.of("Europe/Paris"));
// Explicit factory
ZonedDateTime explicit = ZonedDateTime.of(2025, 6, 15, 10, 30, 0, 0,
ZoneId.of("Asia/Tokyo"));Reading a ZonedDateTime
javaZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("America/New_York"));
zdt.toLocalDate(); // LocalDate
zdt.toLocalTime(); // LocalTime
zdt.toLocalDateTime(); // LocalDateTime (no zone)
zdt.getZone(); // ZoneId
zdt.getOffset(); // ZoneOffset (e.g., -04:00 during EDT)
zdt.toInstant(); // Instant (UTC equivalent)Converting Between Time Zones
javaZonedDateTime nyTime = ZonedDateTime.of(
LocalDateTime.of(2025, 6, 15, 9, 0),
ZoneId.of("America/New_York"));
// Convert to another zone — same instant, different local time
ZonedDateTime londonTime = nyTime.withZoneSameInstant(ZoneId.of("Europe/London"));
ZonedDateTime tokyoTime = nyTime.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));
System.out.println(nyTime); // 2025-06-15T09:00-04:00[America/New_York]
System.out.println(londonTime); // 2025-06-15T14:00+01:00[Europe/London]
System.out.println(tokyoTime); // 2025-06-15T22:00+09:00[Asia/Tokyo]Exam tip:
withZoneSameInstant()converts the moment to another zone — the underlyingInstantstays the same.withZoneSameLocal()keeps the same local clock reading but in a different zone — theInstantchanges.
Daylight Saving Time (DST)
Daylight Saving Time is a seasonal adjustment where clocks are moved forward in spring and back in autumn. ZonedDateTime applies DST rules automatically.
Spring-Forward (Gap)
When clocks spring forward, a range of local times is skipped (a gap). Constructing a ZonedDateTime in the gap adjusts it forward automatically:
java// In America/New_York, clocks spring forward on 2025-03-09 at 02:00 → 03:00
ZonedDateTime inGap = LocalDateTime.of(2025, 3, 9, 2, 30)
.atZone(ZoneId.of("America/New_York"));
System.out.println(inGap);
// 2025-03-09T03:30-04:00[America/New_York] (adjusted forward)Fall-Back (Overlap)
When clocks fall back, a range of local times is repeated (an overlap). The earlier offset (DST) is used by default when constructing a ZonedDateTime in the overlap:
java// In America/New_York, clocks fall back on 2025-11-02 at 02:00 → 01:00
// 01:30 occurs twice: once in EDT (-04:00) and once in EST (-05:00)
ZonedDateTime overlap = LocalDateTime.of(2025, 11, 2, 1, 30)
.atZone(ZoneId.of("America/New_York"));
System.out.println(overlap);
// 2025-11-02T01:30-04:00[America/New_York] (earlier, DST offset used by default)
// Move to the later offset (standard time):
ZonedDateTime later = overlap.withLaterOffsetAtOverlap();
System.out.println(later);
// 2025-11-02T01:30-05:00[America/New_York]Arithmetic Across DST Transitions
ZonedDateTime arithmetic respects DST transitions. Adding Duration preserves the elapsed time; adding Period or using date-based methods preserves the local clock reading:
javaZonedDateTime beforeSpring = ZonedDateTime.of(
LocalDateTime.of(2025, 3, 9, 1, 30),
ZoneId.of("America/New_York")); // EST: -05:00
// Add 1 hour via Duration — spans the gap, 1 real hour elapsed
ZonedDateTime afterDuration = beforeSpring.plus(Duration.ofHours(1));
System.out.println(afterDuration);
// 2025-03-09T03:30-04:00 (1 hour later in clock time is 3:30 EDT)
// Add 1 day — same local time the next day
ZonedDateTime nextDay = beforeSpring.plusDays(1);
System.out.println(nextDay);
// 2025-03-10T01:30-04:00 (same clock reading, now EDT)OffsetDateTime
OffsetDateTime stores date, time, and a fixed UTC offset (ZoneOffset). Unlike ZonedDateTime, it does not follow DST rules:
javaOffsetDateTime odt = OffsetDateTime.of(
LocalDateTime.of(2025, 6, 15, 10, 30),
ZoneOffset.of("+05:30"));
System.out.println(odt); // 2025-06-15T10:30+05:30
odt.toInstant(); // Instant (UTC equivalent)
odt.toZonedDateTime(); // ZonedDateTime with a fixed offset zoneKey Points for the Exam
ZoneIduses IANA names (e.g.,"America/New_York");ZoneOffsetis a fixed offset (e.g.,"+05:30").ZonedDateTimeapplies DST rules automatically;OffsetDateTimeuses a fixed offset.withZoneSameInstant()— same moment, different local time;withZoneSameLocal()— same local time, different moment.- Spring-forward gaps: a local time in the gap is adjusted forward to the first valid time after the transition.
- Fall-back overlaps: by default, the earlier (DST) offset is used; use
withLaterOffsetAtOverlap()for the later (standard) offset. - Adding
DurationtoZonedDateTimemeasures elapsed wall-clock seconds; adding date/time fields preserves local clock reading. Instantis always UTC — it has no concept of time zone or DST.