Date and Timezone become load-bearing #2

Closed
opened 2026-08-26 09:12:54 +00:00 by GKaszewski · 0 comments
Owner

What to build

Implements ADR 0001. Date becomes the single day boundary in the system, resolved by converting an instant into the User's Timezone.

Today there are two contradictory definitions of "day": calendar grouping uses the local date implied by the offset the client sent, while streak calculation uses Utc::now(). For a User in a far-eastern offset these disagree for a large part of every day, and their streak silently breaks. Both must flow through one rule.

Timezone currently only checks that the string contains a /, so "Foo/Bar" stores fine and then fails at parse time in the application layer. Once the day boundary depends on it, an unparseable timezone means unjoinable data — validate against the real IANA database at construction. Domain already pulls email_address to validate the Email value object, so a validation crate in domain has precedent.

Timezone stops being decorative. Clients set it from the platform at register and login. Requests that need a day boundary are rejected with an explicit error when it is unset, rather than defaulting silently — misfiled data produces analytics that look correct and are not.

Acceptance criteria

  • Timezone::new rejects anything not in the IANA database
  • One shared rule resolves an instant to a Date using the User's Timezone
  • Calendar grouping and streak calculation both use it and agree for a non-UTC User
  • SPA sends the platform timezone on register and on login
  • A request needing a day boundary from a User with no Timezone fails with a clear, actionable error
  • Existing users with a null timezone have a documented path to set one
  • CONTEXT.md's User entry no longer describes timezone as optional for analytics display

Blocked by

None - can start immediately.

## What to build Implements ADR 0001. `Date` becomes the single day boundary in the system, resolved by converting an instant into the User's `Timezone`. Today there are two contradictory definitions of "day": calendar grouping uses the local date implied by the offset the client sent, while streak calculation uses `Utc::now()`. For a User in a far-eastern offset these disagree for a large part of every day, and their streak silently breaks. Both must flow through one rule. `Timezone` currently only checks that the string contains a `/`, so `"Foo/Bar"` stores fine and then fails at parse time in the application layer. Once the day boundary depends on it, an unparseable timezone means unjoinable data — validate against the real IANA database at construction. Domain already pulls `email_address` to validate the `Email` value object, so a validation crate in domain has precedent. `Timezone` stops being decorative. Clients set it from the platform at register and login. Requests that need a day boundary are rejected with an explicit error when it is unset, rather than defaulting silently — misfiled data produces analytics that look correct and are not. ## Acceptance criteria - [ ] `Timezone::new` rejects anything not in the IANA database - [ ] One shared rule resolves an instant to a `Date` using the User's `Timezone` - [ ] Calendar grouping and streak calculation both use it and agree for a non-UTC User - [ ] SPA sends the platform timezone on register and on login - [ ] A request needing a day boundary from a User with no `Timezone` fails with a clear, actionable error - [ ] Existing users with a null timezone have a documented path to set one - [ ] CONTEXT.md's `User` entry no longer describes timezone as optional for analytics display ## Blocked by None - can start immediately.
GKaszewski added the ready-for-agent label 2026-08-26 09:12:54 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: GKaszewski/k-mood#2