Skip to content

fix(util): describe dates independently of the host timezone - #1666

Closed
ssalbdivad wants to merge 1 commit into
mainfrom
utc-date-descriptions
Closed

ssalbdivad wants to merge 1 commit into
mainfrom
utc-date-descriptions

Conversation

@ssalbdivad

@ssalbdivad ssalbdivad commented Sep 29, 2026 •

Copy link
Copy Markdown
Member

Follow-up to #1603. Date bound descriptions, error messages and printable() output depended on the host's timezone, because describeCollapsibleDate read local date fields.

Before, in New York:

type("Date >= d'2023-01-01'").description
// "a Date and 7:00 PM, December 31, 2022 or later"

Now, in any timezone:

definition description
Date >= d'2023-01-01' a Date and 2023 or later
Date >= d'2023/1/15' a Date and January 15, 2023 or later
Date >= d'2023-01-01T14:30Z' a Date and January 1, 2023, 2:30 PM UTC or later
  • A Date at UTC or local midnight is described as just its calendar date. ISO date-only strings parse as UTC midnight, while d'2023/1/1' and new Date(y, m, d) parse as local midnight, so both collapse as written.
  • Anything with a time is described in UTC, labeled, and placed after the date.

Alternative considered: echoing the literal's source. This would need to be stored as meta, which is serialized into the parent's innerHash, so Date >= d'2023-01-01' would stop equaling Date >= d'2023-01-01T00:00:00.000Z'.

Known gaps:

  • Exclusive bounds are still described as 1ms past the limit (Date > d'2023-01-01' → …12:00:00.001 AM UTC or later).
  • A local time that happens to fall on UTC midnight collapses and loses its time. For example, new Date(2023, 0, 15, 19) in New York prints as January 16, 2023 (correct in UTC, but unlabeled). A Date can'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

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>

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ 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/localFields helpers replace the direct local getters; non-midnight dates render as January 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' and d'2023/1/1' both describe as 2023.
  • Tests: bounds, range, collapsibleDate, and printable snapshots 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.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Comment thread ark/util/serialize.ts
if (hours === 0 && minutes === 0 && seconds === 0 && milliseconds === 0)
return datePortion
const utc = utcFields(date)
if (isMidnight(utc)) return describeCalendarDate(utc)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@ssalbdivad ssalbdivad closed this Oct 1, 2026
@ssalbdivad
ssalbdivad deleted the utc-date-descriptions branch October 1, 2026 15:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done (merged or closed)

Development

Successfully merging this pull request may close these issues.

1 participant