31
docs/adr/0001-day-boundary-from-user-timezone.md
Normal file
31
docs/adr/0001-day-boundary-from-user-timezone.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# The day boundary is resolved from the User's timezone
|
||||
|
||||
Day-scoped data (DailyMetric) needs a single, unambiguous notion of "which day". We resolve it by converting an instant into the User's IANA timezone and taking the local date, rather than using the offset carried on `logged_at` or a UTC date.
|
||||
|
||||
## Considered Options
|
||||
|
||||
- **Bare local date from `logged_at`** — what `get_calendar` did originally. No server-side authority: two clients in different offsets disagree about the same instant's date.
|
||||
- **UTC date** — unambiguous but wrong for most humans; evening entries in eastern offsets land on the following day and fail to line up with the health data describing them.
|
||||
|
||||
## Consequences
|
||||
|
||||
`User.timezone` stops being optional in practice. Clients must set it from the platform at register/login, and requests that need a day boundary are rejected when it is unset rather than silently defaulting — silently misfiled data produces analytics that look correct and are not. `Timezone` must therefore validate against the real IANA database, not just check for a `/`.
|
||||
|
||||
All day grouping in the system flows through this one rule, including `get_calendar` and streak calculation, which previously used two different and mutually inconsistent definitions.
|
||||
|
||||
## An imported wall clock time is the User's own wall clock
|
||||
|
||||
Daylio's export records `2026-08-25` and `8:00 PM` and says nothing about which zone that was. The importer originally read it as UTC, so an entry logged at 8 PM in Warsaw was stored as `20:00Z` and displayed at 10 PM — every imported entry off by the User's offset, silently, and worse the further from UTC they live.
|
||||
|
||||
A wall clock time with no zone is the User's own wall clock. It is now placed in their Timezone using the offset in force **on that entry's own date**, not a single offset taken from today, so a year of history spanning a clock change gets both offsets. A Warsaw import shows `+01:00` in winter and `+02:00` in summer, and every entry reads as the 8 PM it was.
|
||||
|
||||
This makes the Timezone a precondition for importing, exactly as it already is for the calendar and for DailyMetrics: an account with none is refused rather than filed an hour or ten out of place. That is worth an error in the one place a User can act on it.
|
||||
|
||||
Two edge cases the clocks create:
|
||||
|
||||
- **An hour that never happened.** In Warsaw the clocks jump from 02:00 to 03:00 on the last Sunday in March, so a Daylio row reading 2:30 AM that day names a time that did not exist. The entry lands an hour later rather than being dropped, with a warning — a mood that was logged is not made up, and losing it to arithmetic is worse than moving it sixty minutes.
|
||||
- **An hour that happened twice.** In autumn 02:30 occurs twice; the earlier of the two is used. Either is defensible and the difference is an hour once a year, so the choice is recorded rather than agonised over.
|
||||
|
||||
## A second bug the same file exposed
|
||||
|
||||
The Daylio adapter read its columns by position, and index 7 is `note_title`, not `note`. Every imported note was silently dropped and the always-empty title kept in its place. Columns are now looked up by header name, so a column added, removed or reordered upstream cannot quietly shift the meaning of the data — and an export missing `full_date`, `time` or `mood` is refused by name rather than parsed into nonsense. Where a row has both a title and a note, both are kept.
|
||||
Reference in New Issue
Block a user