Instant, ZonedDateTime & time zones in Java
Machine time vs human time, ZoneId, DST.
Machine time vs human time
**Instant is machine time: a single point on the UTC timeline, stored as seconds and nanoseconds since 1970-01-01T00:00Z. It's the same moment everywhere. ZonedDateTime is human time: a local date-time in a region identified by a ZoneId** like "Europe/Paris".
The same moment, two views
What does this print?
Instant t = Instant.ofEpochSecond(0);
System.out.println(t);
ZoneId tokyo = ZoneId.of("Asia/Tokyo");
System.out.println(
t.atZone(tokyo).toLocalTime());1970-01-01T00:00:00Z 09:001970-01-01T00:00:00Z 00:001970-01-01T09:00:00Z 09:00
Show the answer
The instant is midnight UTC (the Z). Viewed from Tokyo (UTC+9) the very same moment reads 09:00 on the wall clock.
Regions, not offsets
A **ZoneId region** (America/New_York) knows the area's daylight saving (DST) rules. An **OffsetDateTime carries a fixed offset** like +02:00, which can't follow DST changes. For people and places, use region IDs.
DST gaps and overlaps
In spring, New York clocks jump from 02:00 straight to 03:00: 02:30 doesn't exist. Ask for it and java.time shifts forward by the gap, giving 03:30. In autumn, 01:30 happens twice.
var z = ZonedDateTime.of(2024, 3, 10,
2, 30, 0, 0,
ZoneId.of("America/New_York"));
z.toLocalTime(); // 03:30A 25-hour day
Clocks fall back on Nov 3, 2024 in New York. What prints?
ZoneId ny = ZoneId.of("America/New_York");
var z = ZonedDateTime.of(
2024, 11, 2, 12, 0, 0, 0, ny);
System.out.println(z.plusDays(1).getHour());
System.out.println(z.plusHours(24).getHour());12 1212 1112 13
Show the answer
12 then 11. That night lasts 25 hours. **plusDays keeps the wall-clock time; plusHours(24) adds exact hours**, landing an hour "early". (In spring's 23-hour day it lands at 13.)
Storing when orders happened
LocalDateTime placedAt =
LocalDateTime.now();Servers in different countries write different local times; sorting them globally gives nonsense.
Instant placedAt = Instant.now();Zone-independent, so values from any server compare correctly. Convert to ZonedDateTime only for display.
DST in production
Every year, DST breaks something: a nightly job that runs twice in autumn or never in spring, an hourly report missing a row, a flight time off by an hour. The battle-tested rule: store Instants (UTC), convert to the user's ZoneId only to display.
Key takeaways
- Store and compare moments as Instant (UTC)
- Convert to ZonedDateTime only for display or local rules
- Use region IDs (America/New_York), not fixed offsets, for DST
- plusDays keeps the local time; plusHours adds exact hours
Time zone rules change by politics, not physics — so the JDK ships the IANA time zone database and updates it in regular releases.
Practice questions
Servers in several countries record when each order was placed, and you must sort orders globally. Which type should you store?
- LocalDateTime
- Instant
- A String like "03/10/2024 14:00"
- ZonedDateTime in each server's default zone
Check your answer
Instant. Instant is zone-independent, so values from any server compare correctly. LocalDateTime loses which zone it was in, and per-server zones make comparisons error-prone.
What does this print?
ZoneId ny = ZoneId.of("America/New_York");
ZonedDateTime z = ZonedDateTime.of(
2024, 3, 10, 2, 30, 0, 0, ny);
System.out.println(z.toLocalTime());- 02:30
- 03:30
- 01:30
- Throws DateTimeException
Check your answer
03:30. On 10 March 2024 New York clocks jumped from 02:00 straight to 03:00, so 02:30 never existed. java.time shifts it forward by the length of the gap, to 03:30.