Move an event by the days the hand moved it, not to the date dropped on #214

Closed
opened 2026-09-02 08:07:26 +00:00 by jcoffey-dev · 0 comments
Owner

A real bug in #210, which I merged. Found while trying to root-cause the two-hour shift I reported there.

What was wrong

Dragging an event across the month grid wrote the date of the cell it landed on into the event's stored start. Those are the same date only while the event's time zone is the reader's.

An event kept in Asia/Tokyo at 15:00 is drawn to a reader in America/Phoenix at 23:00 the previous evening. Dropped on the 11th, it was written as the 11th in Tokyo — which is the 10th on screen. It went where its own calendar said rather than where the pointer did: one day short, every time, silently.

The fix

Shift by the difference between the two local days. That moves it exactly as far as the hand did, and adding whole days to a stored wall clock leaves the time of day alone without touching the zone — so the frame the rest of that path is careful about is still not crossed.

How it was found, which is the part worth keeping

Every calendar fixture in the mock used Intl.DateTimeFormat().resolvedOptions().timeZone — the machine's own zone. A fixture in the reader's own zone cannot tell a correct conversion from no conversion at all. The case that works was the only case being tested.

This PR adds one fixture in a zone that is not the machine's, which is what made the bug visible in a single drag.

On the two-hour shift from #210

I could not root-cause it, and I want to be straight about that rather than let this fix imply I did.

The system zone here is America/Phoenix (UTC−7) and the mock set every event's timeZone to that same zone, so the double conversion I hypothesised would have cancelled exactly. My explanation was wrong. The code that produced it was rewritten before it was ever committed, so there is nothing to diff and no captured payload.

What I can say: the day-boundary bug above is real, reproducible, and in the same code path — and it is now fixed and covered.

Testing

29 tests on the drag arithmetic (up from 27): the day delta ignoring time of day on either side, negative and month-crossing deltas, a NaN delta refused, and the Tokyo case asserted directly.

Verified in the browser both ways: the Tokyo event now lands on the cell it was dropped on and keeps 23:00; a same-zone event still lands correctly keeping 12:00.

Suite green: web 862 across 87 files, server 122, typecheck clean.

Merged 2026-09-02 as coffey-labs/ihasmail@b0edce1d9b

Rebuilt from: git history, session transcript.

A real bug in #210, which I merged. Found while trying to root-cause the two-hour shift I reported there. ## What was wrong Dragging an event across the month grid wrote the date of the **cell it landed on** into the event's stored start. Those are the same date only while the event's time zone is the reader's. An event kept in `Asia/Tokyo` at 15:00 is drawn to a reader in `America/Phoenix` at **23:00 the previous evening**. Dropped on the 11th, it was written as the 11th *in Tokyo* — which is the 10th on screen. It went where its own calendar said rather than where the pointer did: one day short, every time, silently. ## The fix Shift by the difference between the two **local** days. That moves it exactly as far as the hand did, and adding whole days to a stored wall clock leaves the time of day alone without touching the zone — so the frame the rest of that path is careful about is still not crossed. ## How it was found, which is the part worth keeping Every calendar fixture in the mock used `Intl.DateTimeFormat().resolvedOptions().timeZone` — the machine's own zone. **A fixture in the reader's own zone cannot tell a correct conversion from no conversion at all.** The case that works was the only case being tested. This PR adds one fixture in a zone that is not the machine's, which is what made the bug visible in a single drag. ## On the two-hour shift from #210 I could not root-cause it, and I want to be straight about that rather than let this fix imply I did. The system zone here is `America/Phoenix` (UTC−7) and the mock set every event's `timeZone` to that same zone, so the double conversion I hypothesised would have cancelled exactly. My explanation was wrong. The code that produced it was rewritten before it was ever committed, so there is nothing to diff and no captured payload. What I can say: the day-boundary bug above is real, reproducible, and in the same code path — and it is now fixed and covered. ## Testing 29 tests on the drag arithmetic (up from 27): the day delta ignoring time of day on either side, negative and month-crossing deltas, a NaN delta refused, and the Tokyo case asserted directly. Verified in the browser both ways: the Tokyo event now lands on the cell it was dropped on and keeps 23:00; a same-zone event still lands correctly keeping 12:00. Suite green: web 862 across 87 files, server 122, typecheck clean. **Merged** 2026-09-02 as coffey-labs/ihasmail@b0edce1d9bf6 <sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.