ReviewOS

zig-utils/zig-js

Intl.DateTimeFormat: retain pattern-added dayPeriod state

#773
Closed chris opened this 9 days ago ·
9 days ago

Problem

Intl.DateTimeFormat currently preserves only the user-provided dayPeriod option. It does not retain the dayPeriod field selected by the locale date-time pattern.

The current engine result for:

new Intl.DateTimeFormat("en-US", {
  timeZone: "UTC",
  hour: "numeric",
  hourCycle: "h11"
}).resolvedOptions()

has keys:

locale,calendar,numberingSystem,timeZone,hourCycle,hour12,hour

System JavaScriptCore selects the 12-hour pattern and returns:

locale,calendar,numberingSystem,timeZone,hourCycle,hour12,dayPeriod,hour

with dayPeriod: "short". The same distinction holds for h12 versus h23/h24 in the audited en-US matrix.

ECMA-402 DateTime Format Records require the selected record to have [[dayPeriod]] when its pattern contains {dayPeriod}; DateTimeFormat resolvedOptions then projects that field in table order.

The source cause is structural: dtfProcessOptionsKind records only the requested component strings, and dtfStoreOptions publishes resolved_day_period directly from that request. No exact selected-pattern record projects pattern-added fields into immutable native state.

Scope

Model the selected BasicFormatMatcher/BestFitFormatMatcher result as exact native formatter state, including pattern-added fields and widths. The selected record must drive both formatting and reflection.

This must not be implemented by:

  • hardcoding en, h11, or h12 behavior;
  • assuming every 12-hour pattern has the same day-period width;
  • adding dayPeriod only inside resolvedOptions();
  • changing only key order or manufacturing a property without formatter state;
  • hiding an engine-specific normalization in the benchmark;
  • retaining movable JavaScript strings or ordinary hidden properties as native state.

Acceptance criteria

  • A benchmark-first differential row freezes h11/h12/h23/h24, locale-extension resolution, hour12 precedence, exact key order, day-period width, omission, fresh-result identity, and complete checksums before runtime changes.
  • The selected native DateTime Format Record retains every pattern field required by ECMA-402, including pattern-added dayPeriod, with a closed enum/static-cell representation where the value set is closed.
  • resolvedOptions() matches system JSC for representative 12-hour and 24-hour locales, explicit component/basic and best-fit matching, style patterns, hourCycle/locale-extension/hour12 precedence, and no-hour omission.
  • format, formatToParts, formatRange, and formatRangeToParts consume the same selected record, so reflection cannot diverge from emitted parts.
  • Constructor observation order, OOM/fault injection, precise and moving GC, no-GIL/TSan publication, and formatter reclamation remain exact.
  • Focused units, exact Intl.DateTimeFormat Test262 accounting, full units, and benchmark differential/checksum validation pass with quantified before → after results.

Relationship

Discovered while freezing #772. The #772 hour-cycle enum/allocation change remains a separate performance boundary; its benchmark must expose this checksum divergence and link this issue rather than normalizing it away.

9 days ago

The exact divergence is now preserved by a landed representative benchmark rather than normalized away: https://github.com/zig-utils/zig-js/commit/dcee39153fcb8ff7ebec7a28f3a26027c2d5bec9

For h11/h12, zig-js originally produced checksum 705814248 while system JSC produced 242469243 because JSC includes the selected pattern dayPeriod field. #772 optimized closed hour-cycle reflection without changing that checksum; #773 remained the semantic owner of the pattern-state fix.

9 days ago

#772 completed without changing this issue's semantic boundary: https://github.com/zig-utils/zig-js/commit/66c150fd0afe8806380a6d295e8ab3cca9b7da61

The effective hourCycle projects from immutable static cells, while #773 owns the selected pattern's dayPeriod state. The frozen benchmark and exact key-shape controls remained available for this fix. #775 separately owns a tooling improvement and does not alter DateTimeFormat semantics.

9 days ago

Implemented and pushed to main: 08a37fe9.

The selected native DateTime Format Record now projects h/hm/hms, H/Hm/Hms, and ms fields from a revision-pinned, deduplicated CLDR table. Closed enums distinguish AM/PM markers from flexible day periods, and the same immutable record drives resolvedOptions, format, formatToParts, formatRange, and formatRangeToParts. The implementation does not retain JS strings or infer the property inside reflection.

Validation:

  • frozen checksum: zig-js 705814248 -> 242469243, now exactly system JSC 242469243
  • Intl.DateTimeFormat Test262: 244 -> 244, 0 regressions
  • focused units: 10/10; TSan publication witness: 1/1
  • full units: 1,940 passed, 1 skipped, 0 failed, 0 leaked
  • representative matrix: 58/58; benchmark harness: 18/18 plus audit/adapters/quickening
  • three attribution replays were identical; the increased work is the intentional semantic result shape, so no speed claim is made

#772 remains the closed hour-cycle allocation boundary. #775 owns mixed-tier exact-parent replay tooling.

Sign in to comment on this issue.