fix(util): describe dates independently of the host timezone - #1666
ssalbdivad wants to merge 1 commit into
Conversation
describeCollapsibleDate read local date fields, so Date bound descriptions,
error messages and printable() output depended on the host's timezone.
Date-only ISO literals like d'2023-01-01' parse as UTC midnight, so west of
UTC they were described as the evening before ("7:00 PM, December 31, 2022")
and never collapsed to "2023" as intended.
A Date can't tell us whether its author meant a calendar date in UTC or in
local time: d'2023/1/1' and new Date(2023, 0, 1) parse as local midnight.
So a Date at midnight in either UTC or local time is described as just its
calendar date, and both forms collapse as written. Anything with a time is
described in UTC with a "UTC" label, after the date so it reads naturally:
"January 15, 2023, 2:30 PM UTC".
Echoing the original literal source was also considered, but it needs
somewhere to store the source that doesn't affect node identity. Meta
doesn't qualify, since it's serialized into the parent's innerHash, so
equivalent bounds written differently would stop comparing equal.
Known gap: exclusive bounds are normalized to a limit 1ms away, so
Date > d'2023-01-01' is still described as
"January 1, 2023, 12:00:00.001 AM UTC or later".
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
ℹ️ No critical issues — one inherent edge case noted inline.
Reviewed changes
This PR makes describeCollapsibleDate (and therefore date-bound descriptions, Date error messages, and printable) independent of the host timezone by describing times in UTC.
- UTC field extraction:
utcFields/localFieldshelpers replace the direct local getters; non-midnight dates render asJanuary 1, 2023, 2:30 PM UTC. - Midnight collapse: an instant at UTC or local midnight collapses to its calendar date (year-only on January 1), so
d'2023-01-01'andd'2023/1/1'both describe as2023. - Tests:
bounds,range,collapsibleDate, andprintablesnapshots updated to the UTC form, plus new coverage for local midnight and local→UTC conversion.
I verified the full suite (pnpm test, 1755 passing) and confirmed the new descriptions under TZ=America/New_York with an ad-hoc script.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
| if (hours === 0 && minutes === 0 && seconds === 0 && milliseconds === 0) | ||
| return datePortion | ||
| const utc = utcFields(date) | ||
| if (isMidnight(utc)) return describeCalendarDate(utc) |
There was a problem hiding this comment.
This collapses any instant that lands on UTC midnight, even when it was built from a local time. new Date(2023, 0, 15, 19) in New York (or d'2023-01-15T19:00') renders as January 16, 2023, and new Date(2023, 0, 15, 9) in Tokyo renders as January 15, 2023 — both drop the time, and the first shifts the calendar date. As the doc comment notes, a Date can't distinguish this from a genuine UTC date-only input, so there's no clean fix; it's just a second known gap (alongside exclusive bounds) worth calling out.

Follow-up to #1603. Date bound descriptions, error messages and
printable()output depended on the host's timezone, becausedescribeCollapsibleDateread local date fields.Before, in New York:
Now, in any timezone:
Date >= d'2023-01-01'a Date and 2023 or laterDate >= d'2023/1/15'a Date and January 15, 2023 or laterDate >= d'2023-01-01T14:30Z'a Date and January 1, 2023, 2:30 PM UTC or laterd'2023/1/1'andnew Date(y, m, d)parse as local midnight, so both collapse as written.Alternative considered: echoing the literal's source. This would need to be stored as meta, which is serialized into the parent's
innerHash, soDate >= d'2023-01-01'would stop equalingDate >= d'2023-01-01T00:00:00.000Z'.Known gaps:
Date > d'2023-01-01'→…12:00:00.001 AM UTC or later).new Date(2023, 0, 15, 19)in New York prints asJanuary 16, 2023(correct in UTC, but unlabeled). ADatecan't be told apart from a UTC date-only input, so this is inherent to the heuristic.Tests:
pnpm test(1755 passing),pnpm lint,tsc. Descriptions also checked under UTC, America/Los_Angeles and Asia/Tokyo.🤖 Generated with Claude Code