mikeb14 Jun 2026 00:24

Hit a scheduling bug that only reproduces around the daylight saving changeover. We add a day to a DateTimeImmutable to compute the next run, and on the night the clocks change, the result is either an hour off or lands on a wall-clock time that does not exist. Most of the year the code is fine, which is exactly why it slipped through.

What is the correct mental model for adding intervals across a DST boundary, and where do people usually go wrong, modifying with a P1D interval versus adding 24 hours versus working in UTC?

Replies (5)
petr_sys14 Jun 2026 00:54

The core distinction: adding P1D means add one calendar day keeping the wall-clock time, while adding PT24H means add exactly 24 hours of elapsed time. On a DST night those differ by an hour. If you want the same local time tomorrow, P1D is correct and the one-hour wall-clock shift is the timezone doing its job. If you want a fixed elapsed duration, PT24H. Picking the wrong one is the entire bug.

0
jnovak14 Jun 2026 02:24

The other half is the nonexistent and ambiguous wall-clock times. In spring the clocks jump and a time like 02:30 simply does not exist that night, in autumn 02:30 happens twice. If your computed next run lands in the gap, you get surprising normalization. The defensive pattern is to do the arithmetic in UTC where every hour exists exactly once, and only convert to local for display, never for the math.

0
dmitry_kv14 Jun 2026 03:54

Make sure your DateTimeImmutable actually carries the intended timezone and not the server default. A lot of these bugs are really that the object was created in UTC or the server zone but everyone assumed it was the user local zone, so the DST rules being applied are the wrong ones entirely. Set the zone explicitly at construction and assert it, because a silently-wrong zone produces exactly this twice-a-year ghost.

0
mikeb14 Jun 2026 05:24

Doing the arithmetic in UTC and converting only for display is the fix I am taking. Our objects did carry the right local zone, but we were adding P1D when we actually wanted a fixed 24-hour cadence, so on the changeover night the run drifted by an hour. Switching to explicit UTC math with PT24H makes the cadence stable and side-steps the nonexistent-time problem entirely. Classic two-a-year bug.

0
mykolap14 Jun 2026 07:04

Add a test that pins the clock to a known DST transition for your zone and asserts the next-run calculation, both the spring-forward and the autumn-back night. These bugs hide because nobody tests the two specific nights they occur. Once you have a frozen-clock test on those exact dates, the regression cannot come back silently. We keep one per timezone we support.

0
Write a reply
Markdown. ```php blocks are runnable.