How do Duration and Period behave differently across a Daylight Saving Time transition, and why does it matter?
answer
- DST: spring day = 23h, fall day = 25h (only ZonedDateTime sees it)
- Period/plusDays = same wall-clock time next day
- Duration.ofHours(24) = exactly 24 real hours → wall-clock shifts
- Duration.ofDays(1) is NOT a calendar day — it's 86,400s
- LocalDateTime has no zone → no DST divergence; Instant = UTC elapsed
basics
~20 sAdding a Period of 1 day to a ZonedDateTime moves to the same wall-clock time next day, even if that day was 23 or 25 hours long. Adding a Duration of 24 hours adds exactly 24 hours of real time, so the wall-clock time can shift across a DST change.
solid answer
~50 sAcross a DST transition the two types diverge because one is calendar-based and one is exact. With a ZonedDateTime in a zone that observes DST, adding Period.ofDays(1) is calendar arithmetic: you land on the same local wall-clock time the next day, even though that day may have been only 23 hours (spring forward) or 25 hours (fall back) of real elapsed time. Adding Duration.ofHours(24) — or ofDays(1), which is also exactly 24h — is physical-time arithmetic: you advance exactly 24 real hours, so the local wall-clock time shifts by an hour around the transition. Concretely, around a spring-forward Sunday, Period.ofDays(1) keeps 09:00 → 09:00, while Duration.ofHours(24) takes 09:00 → 10:00. This matters for correctness in scheduling, billing, and reminders: 'same time tomorrow' must use Period/plusDays, whereas 'exactly 24 hours of SLA' must use Duration. Using the wrong one produces off-by-an-hour bugs that only appear twice a year.
go deeper
Aware that DST can make adding 'a day' tricky and that there are two notions of a day.
Knows Period keeps wall-clock time while Duration adds exact hours, and that Duration.ofDays(1) is 24 hours.
Explains the 23h/25h transition, gives the 09:00→09:00 vs 09:00→10:00 example, and notes only ZonedDateTime exhibits the divergence (LocalDateTime/Instant don't).
Designs scheduling/billing systems around the wall-clock-vs-stopwatch distinction, anticipates the twice-a-year bug class, and chooses storage (instant+zone vs local+rule) accordingly, including ambiguous/gap local times at transitions.
## Background: what DST does **Daylight Saving Time (DST)** is a twice-a-year clock change in many regions. In spring, clocks 'spring forward' (e.g. 02:00 jumps to 03:00) so that calendar day has only **23 hours** of real time. In autumn, clocks 'fall back' (e.g. 02:00 returns to 01:00) so that day has **25 hours**. A region's DST rules live in the time-zone database, and in `java.time` they are applied by `ZonedDateTime` (which carries a `ZoneId`). `LocalDateTime`/`Instant` alone don't observe DST — `LocalDateTime` has no zone, and `Instant` is pure UTC elapsed time. ## Two kinds of arithmetic For a `ZonedDateTime` `zdt` in a DST-observing zone: ### Calendar (date-based) arithmetic — `Period` / `plusDays` ```java zdt.plus(Period.ofDays(1)); // or zdt.plusDays(1) ``` This means "the same **wall-clock** time on the next calendar day." The local time field is held constant; the engine recomputes the underlying instant according to the zone's rules. So if today is 09:00 and tomorrow is a spring-forward day, you still get **09:00** tomorrow — but only **23 real hours** elapsed. On a fall-back day, 09:00 → 09:00 spans **25 real hours**. ### Exact (time-based) arithmetic — `Duration` / `plusHours` ```java zdt.plus(Duration.ofHours(24)); // or zdt.plus(Duration.ofDays(1)) — also exactly 24h ``` This advances the **physical instant** by exactly 24 hours (86,400 seconds), then re-expresses it in the zone. Because the zone lost or gained an hour, the local wall-clock result **shifts**: - Spring forward: 09:00 + 24h of real time → **10:00** the next day (the day was short, so 24h overshoots midnight-to-09:00 by an hour). - Fall back: 09:00 + 24h → **08:00** the next day. ## A concrete example ```java ZoneId zone = ZoneId.of("America/New_York"); // US spring-forward 2026: March 8, 02:00 -> 03:00 ZonedDateTime z = ZonedDateTime.of(2026, 3, 8, 0, 30, 0, 0, zone); z.plusDays(1); // 2026-03-09T00:30-04:00 (same wall time) z.plus(Duration.ofHours(24)); // 2026-03-09T01:30-04:00 (one hour later, 24 real hours) ``` The `plusDays(1)` result spans only 23 real hours; the `Duration` result spans exactly 24. ## Why `Duration.ofDays(1)` is a trap name `Duration.ofDays(1)` does **not** mean "one calendar day." It is defined as exactly 86,400 seconds. Use `plusDays`/`Period` if you mean calendar days; reserve `Duration` for genuinely fixed physical spans. ## Note on `LocalDateTime` and `Instant` - `LocalDateTime` has no zone, so it never sees DST: `plusDays(1)` and `plus(Duration.ofHours(24))` both move the same way (24h == next-day-same-time) because there's no zone to bend. The divergence only appears once you attach a zone (`ZonedDateTime`). - `Instant` is pure UTC elapsed time; adding a `Period` to an `Instant` is unsupported (no calendar), and adding a `Duration` just advances real seconds. ## Why it matters in practice Getting this wrong yields bugs that surface **only twice a year** and are painful to reproduce: - "Send the reminder at 9am every day" → must be `plusDays`/`Period`, else it drifts by an hour after each transition. - "The trial expires exactly 30×24 hours after signup" → must be `Duration` if the SLA is in real time, or `Period.ofDays(30)` if it's a calendar offset; choose deliberately. - Billing periods, cron-like schedulers, and token TTLs all hinge on this distinction. The rule of thumb: **wall-clock intent → Period/plusX (calendar); stopwatch intent → Duration.**
- Why does plusDays(1) and Duration.ofHours(24) behave identically on a LocalDateTime but differently on a ZonedDateTime?A LocalDateTime carries no zone, so there are no DST rules to apply — both just move to the same time next day (24h). A ZonedDateTime applies the zone's offset rules, so calendar-day vs exact-24h diverge when a transition occurs.
- You must schedule a daily 9am reminder in a DST zone. Period or Duration?Period/plusDays (calendar). It keeps 09:00 wall-clock every day; a fixed 24h Duration would drift by an hour each transition.
saying these in an interview costs you the question
- Claiming Duration.ofDays(1) equals 'the next calendar day'
- Thinking LocalDateTime observes DST (it doesn't — no zone)
- Saying plusDays adds exactly 24 hours on a ZonedDateTime across DST
- Believing the choice never matters because 'a day is a day'