Skip to content

feat(ui): release 5.4.1 with a time zone for DateRangePicker - #224

Merged
tmccoy14 merged 4 commits into
mainfrom
feat/ui-date-range-picker-time-zone
Nov 9, 2026
Merged

tmccoy14 merged 4 commits into
mainfrom
feat/ui-date-range-picker-time-zone

Conversation

@tmccoy14

@tmccoy14 tmccoy14 commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Need

Guardian shows dates in each user's own time zone, a Settings preference. Its Credentials and agent Memberships tables filter by date with DateRangePicker, which can only cut days in the browser's zone. Near midnight, the range a user picks lands a day apart from the dates in the rows it filters. For now Guardian works around this by showing the filter chip in the browser's zone too.

Problem

DateRangePicker builds every day on the browser's clock: new Date(y, m, d) for the cells, setHours(0…) and setHours(23, 59…) for the range it hands back, and the browser's today. It takes no zone.

Change

  • New optional prop timeZone, an IANA name. Without it, days follow the browser's zone as before.
  • The calendar's cells are wall-clock days (year, month, day), laid out with UTC calendar maths, so no clock change can shift them.
  • Today, the selected and in-range days, the trigger's label, and the from and to handed back all follow the zone.
  • from is the day's first instant there. to is a millisecond before the next day's first instant. On a day whose midnight a clock change skips, such as Havana's on 8 March, the day starts at its first time.
  • The day maths lives in lib/zoned-day.ts. It uses Intl only, with no new dependency, and finds a day's first instant by binary search over the window every zone sits in.
  • Picking an end date with no start set now begins a new range, where it threw on dateRange.from!.
  • An invalid from or to counts as unset rather than throwing from Intl (5.4.0 labelled it "Invalid Date").
  • The trigger's label is built from the same days as the calendar, so the two can't disagree.
  • Today's button carries aria-current="date".
  • Doc comments on every prop, with @default for timeZone.
  • Docs: a Time Zone section, which says an unknown zone throws, and a timeZone row in the props table. Minor in effect, released as 5.4.1.

Evidence

  • New tests for zoned-day: the day an instant falls on, midnight in Auckland and Kathmandu (+5:45), Havana's skipped midnight, and the end of a day.
  • New tests for the picker in Auckland: picking 10 to 12 March hands back 2026-03-09T11:00Z to 2026-03-12T10:59:59.999Z, and the label reads those days. Picking the earlier day second swaps the ends, and today is the 16th while UTC is still on the 15th.
  • Without a zone, picking the 10th hands back the browser's local midnight. That test pins the runner to Kolkata, and fails if the no-zone path falls back to UTC maths. An invalid date is treated as unset, and an unknown zone throws a RangeError.
  • addDays rolls across a month and a year.
  • Both picker tests fail on main's picker. All the new tests pass with the test runner in UTC, New York, Kolkata and Auckland.
  • UI tests (211), lint, Prettier and the build pass, and the built types document timeZone.

Not covered

  • An unknown zone name throws from Intl, as FormatDate does, rather than falling back silently and hiding a typo. Documented and tested; Guardian passes a validated preference.
  • The month shown is set when the picker mounts, so a timeZone changed afterwards doesn't move it. Guardian's zone is set before mount, and Settings, where it changes, remounts the picker.
  • With no timeZone, the browser's zone is read once per session, so it won't follow a mid-session change of the OS zone. Guardian always passes a zone.
  • No live time zone demo: it would look the same as the default one. The section sits beside Usage, as FormatDate's time zones section does.
  • A day a zone skipped entirely, such as 30 December 2011 in Samoa and Tokelau, starts at the next day's first instant.
  • The Guardian bump follows the release: it passes the user's zone to the picker in Credentials and Memberships, and drops the chip's browser-zone workaround.

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tmccoy14 tmccoy14 self-assigned this Nov 9, 2026
tmccoy14 and others added 3 commits November 9, 2026 16:22
…its zone paths

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nge from its days

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ight

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tmccoy14
tmccoy14 merged commit 593929f into main Nov 9, 2026
1 check passed
@tmccoy14
tmccoy14 deleted the feat/ui-date-range-picker-time-zone branch November 9, 2026 21:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant