Skip to content

fix(intl): #5582 — Temporal/DTF no-overlap TypeError + formatRange respects DTF options#5782

Merged
proggeramlug merged 1 commit into
mainfrom
fix/intl-datetimeformat-5582
Jun 29, 2026
Merged

fix(intl): #5582 — Temporal/DTF no-overlap TypeError + formatRange respects DTF options#5782
proggeramlug merged 1 commit into
mainfrom
fix/intl-datetimeformat-5582

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Fixes test262 failures in intl402/DateTimeFormat tracked in #5582.

Root cause

Two distinct bugs in date_collator.rs:

  1. Missing no-overlap TypeError. ECMA-402 §11.5.5 HandleDateTimeValue requires a TypeError when the options explicitly configured on an Intl.DateTimeFormat have no field in common with the Temporal type's data model — e.g. {timeStyle: "short"} with a Temporal.PlainDate (which carries no time fields), or {dateStyle: "long"} with a Temporal.PlainTime. DTFs constructed with no options (where ToDateTimeOptions silently applies year/month/day defaults) must not raise this error. Perry had no such check at all, so all combinations silently succeeded or hit date_arg_to_clipped_ms's ZonedDateTime guard.

  2. formatRange ignored DTF options. date_time_format_range_value / date_time_format_range_parts_value called date_short_utc_from_ms (hardcoded M/D/YYYY) and compared raw milliseconds for the same-date collapse, both ignoring dateStyle, timeStyle, and component options entirely. The four range thunks retrieved the DTF object but discarded it (_obj).

Changes

  • intl.rs: add KEY_DT_IS_DEFAULT constant; write the flag in make_instance when ToDateTimeOptions falls back to the numeric year/month/day defaults (no explicit style or component requested).

  • date_collator.rs:

    • dtf_primary_mask(obj) — bitmask of year/month/day/time dimensions present in the DTF's explicit options.
    • temporal_primary_mask(kind) — bitmask of dimensions a Temporal type actually carries.
    • validate_temporal_dtf_overlap(kind, obj) — skips when KEY_DT_IS_DEFAULT is set; otherwise throws TypeError when the two masks have no bits in common.
    • All four format/formatToParts thunks call validate_temporal_dtf_overlap before date_arg_to_clipped_ms.
    • date_time_format_range_value and date_time_format_range_parts_value now accept an obj: *const ObjectHeader and format via format_ms_with_dtf_obj (respecting dateStyle, timeStyle, component options). Same-date collapse compares formatted strings rather than raw milliseconds.
    • All four range thunks drop the _obj suppression and pass obj through to the value functions.

Before / after (test262 intl402/DateTimeFormat)

Count
Before ~78 failures
Expected after ~73 failures (5 overlap-TypeError tests fixed; formatRange fidelity improved)

The remaining failures are split between tests that require real IANA timezone support (e.g. temporal-objects-not-overlapping-options.js uses America/Vancouver for local-noon vs UTC-noon comparison) and other unrelated gaps tracked separately in #5582.

Checklist

  • cargo fmt --all -- --check passes
  • bash scripts/check_file_size.sh passes (no file exceeds 2000 lines)
  • cargo build --release -p perry-runtime succeeds (warnings only, no errors)
  • No Cargo.toml version bump, no CLAUDE.md or CHANGELOG.md edits (external contributor PR)

Generated by Claude Code

Summary by CodeRabbit

  • New Features

    • Date and date-time formatting now handles Temporal values more consistently, with clearer validation when the requested format doesn’t match the value type.
    • Range formatting now better matches the active date-time settings, including improved output when both endpoints are the same moment.
  • Bug Fixes

    • Fixed fallback date formatting to correctly track when default date fields are being used.
    • Improved formatRange and formatRangeToParts so single-value results use the same formatting style as regular date-time output.

…options

ECMA-402 HandleDateTimeValue requires a TypeError when the options explicitly
set on an Intl.DateTimeFormat have no field in common with the Temporal type's
data model (e.g. {timeStyle: "short"} + PlainDate, or {dateStyle: "long"} +
PlainTime). DTFs constructed with no options (where ToDateTimeOptions applies
the year/month/day defaults) must NOT raise this error.

Changes:
- intl.rs: add KEY_DT_IS_DEFAULT flag written when the constructor falls back
  to the default year/month/day components (no explicit style or component).
- date_collator.rs: add dtf_primary_mask() / temporal_primary_mask() / validate_
  temporal_dtf_overlap() helpers; call the overlap check in all four
  format/formatToParts thunks before date_arg_to_clipped_ms.
- date_collator.rs: date_time_format_range_value and range_parts_value now
  accept an obj parameter and format via format_ms_with_dtf_obj (respecting
  dateStyle, timeStyle, component options) instead of the fixed M/D/YYYY
  date_short_utc_from_ms path. The same-date collapse now compares formatted
  strings rather than raw milliseconds. All four range thunks pass obj through.

Fixes temporal-plain{date,time,yearmonth,monthday}-formatting-datetime-style.js
TypeError branches and improves formatRange DTF-option fidelity (#5582).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013gZGFwJdk83JqXRt8KYWyX
@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a KEY_DT_IS_DEFAULT internal boolean slot set during DateTimeFormat construction when no explicit style or component options are given. Uses this slot in new bitmask-based overlap validation helpers that throw TypeError when a Temporal value's primary fields don't overlap with the DTF's explicit fields. Wires validation into all six formatting thunks and updates range formatting to pass the DTF object for endpoint formatting.

Changes

Temporal DTF overlap validation and range formatting

Layer / File(s) Summary
KEY_DT_IS_DEFAULT internal slot
crates/perry-runtime/src/intl.rs
Declares KEY_DT_IS_DEFAULT ("__intlDtIsDefault") constant and writes it as true in the DateTimeFormat constructor's default-fallback branch.
Bitmask overlap validation helpers
crates/perry-runtime/src/intl/date_collator.rs
Adds dtf_primary_mask, temporal_primary_mask, and validate_temporal_dtf_overlap; throws TypeError on zero overlap for explicitly-constructed DTF instances.
Overlap validation in format/formatToParts thunks
crates/perry-runtime/src/intl/date_collator.rs
All four format and formatToParts thunks (bound and unbound) call validate_temporal_dtf_overlap for Temporal inputs before formatting.
DTF-aware range endpoint formatting and thunk wiring
crates/perry-runtime/src/intl/date_collator.rs
date_time_format_range_value and date_time_format_range_parts_value accept the DTF object and use format_ms_with_dtf_obj; all four range thunks gain overlap validation and pass the DTF object.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • PerryTS/perry#5649: Established the default year/month/day = "numeric" fallback behavior in the DateTimeFormat constructor that this PR extends with the KEY_DT_IS_DEFAULT marker slot.
  • PerryTS/perry#5765: Introduced format_ms_with_dtf_obj and DTF-context-driven Temporal formatting in date_collator.rs, which this PR builds on for range endpoint formatting.
  • PerryTS/perry#5770: Modified the same format/formatToParts and range thunk code paths in date_collator.rs for Temporal input handling.

Poem

🐇 A slot called "default" now marks my DTF,
So Temporal fields know when to overlap or fret.
Range endpoints now dress in the formatter's own style,
No more UTC-short when the times reconcile.
Hop hop — type errors bloom where fields don't align! 🌸

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main changes: Temporal overlap TypeError handling and formatRange option respect.
Description check ✅ Passed The description covers the summary, root cause, changes, issue reference, validation, and checklist, matching the template well enough.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/intl-datetimeformat-5582

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/perry-runtime/src/intl/date_collator.rs (1)

810-831: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

formatRangeToParts part values still ignore DTF options.

sx/sy are formatted via format_ms_with_dtf_obj and used only for the sx == sy collapse decision, but the emitted parts still come from date_range_parts_from_ms, which is the fixed month/day/year (M/D/YYYY) representation. So while date_time_format_range_value now honors dateStyle/timeStyle/component options, the parts path does not — e.g. with { timeStyle: "short" }, formatRange returns time strings but formatRangeToParts returns date parts. Worse, two distinct times on the same date take the non-collapsed branch (sx != sy) yet emit identical startRange/endRange date parts.

Derive the emitted parts from the DTF object's resolved options so both APIs stay consistent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-runtime/src/intl/date_collator.rs` around lines 810 - 831, The
`date_time_format_range_parts_value` path still emits fixed
`date_range_parts_from_ms` date parts instead of using the `DateTimeFormat`
options, so `formatRangeToParts` diverges from `formatRange`. Update the part
generation in `date_time_format_range_parts_value` to derive tokens from the
same DTF configuration used by
`format_ms_with_dtf_obj`/`date_time_format_range_value`, and keep the `sx == sy`
collapse logic while ensuring both the shared and start/end branches emit
option-resolved parts.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@crates/perry-runtime/src/intl/date_collator.rs`:
- Around line 810-831: The `date_time_format_range_parts_value` path still emits
fixed `date_range_parts_from_ms` date parts instead of using the
`DateTimeFormat` options, so `formatRangeToParts` diverges from `formatRange`.
Update the part generation in `date_time_format_range_parts_value` to derive
tokens from the same DTF configuration used by
`format_ms_with_dtf_obj`/`date_time_format_range_value`, and keep the `sx == sy`
collapse logic while ensuring both the shared and start/end branches emit
option-resolved parts.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 10101fa7-bf2f-4816-b43d-c54f9659f3b6

📥 Commits

Reviewing files that changed from the base of the PR and between ffb0d6a and f31bb44.

📒 Files selected for processing (2)
  • crates/perry-runtime/src/intl.rs
  • crates/perry-runtime/src/intl/date_collator.rs

@proggeramlug proggeramlug merged commit 1a94d95 into main Jun 29, 2026
15 checks passed
@proggeramlug proggeramlug deleted the fix/intl-datetimeformat-5582 branch June 29, 2026 00:15
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.

2 participants