Skip to content

feat(intl): implement Date.prototype.toLocaleString, toLocaleDateStri…#5080

Open
alienx5499 wants to merge 3 commits intoboa-dev:mainfrom
alienx5499:feat/5075-date-tolocalestring
Open

feat(intl): implement Date.prototype.toLocaleString, toLocaleDateStri…#5080
alienx5499 wants to merge 3 commits intoboa-dev:mainfrom
alienx5499:feat/5075-date-tolocalestring

Conversation

@alienx5499
Copy link
Contributor

This Pull Request fixes/closes #5075.

It changes the following:

  • Implement Date.prototype.toLocaleString, toLocaleDateString, and toLocaleTimeString using Intl.DateTimeFormat when the intl feature is enabled (ECMA-402 20.4.1–20.4.3).
  • Add shared helper format_date_time_locale in intl/date_time_format; expose create_date_time_format as pub(crate); add FormatDefaults::All and fix default-application logic for it.
  • Add this_date_value helper for the RequireInternalSlot / [[DateValue]] step in the three methods.
  • Return "Invalid Date" for NaN in all three methods; when intl is disabled, fall back to Date.prototype.toString.
  • Add #[cfg(feature = "intl")] tests: invalid receiver (TypeError), NaN → "Invalid Date", typeof checks, locale differences (en-US vs de-DE) for all three methods, and options pipeline (dateStyle: 'short').

Testing

  • cargo test -p boa_engine --lib -- date::tests::date_proto_to_locale_string_intl (without intl; test is skipped)
  • cargo test -p boa_engine --features intl_bundled --lib -- date::tests::date_proto_to_locale_string_intl (with intl)

@alienx5499 alienx5499 requested review from a team, jedel1043 and nekevss as code owners March 15, 2026 00:29
@github-actions github-actions bot added C-Tests Issues and PRs related to the tests. C-Builtins PRs and Issues related to builtins/intrinsics C-Intl Changes related to the `Intl` implementation Waiting On Review Waiting on reviews from the maintainers labels Mar 15, 2026
@github-actions github-actions bot added this to the v1.0.0 milestone Mar 15, 2026
@github-actions
Copy link

github-actions bot commented Mar 15, 2026

Test262 conformance changes

Test result main count PR count difference
Total 52,963 52,963 0
Passed 50,070 49,941 -129
Ignored 2,072 2,207 +135
Failed 821 815 -6
Panics 0 0 0
Conformance 94.54% 94.29% -0.24%
Fixed tests (6):
test/intl402/Date/prototype/this-value-invalid-date.js (previously Failed)
test/intl402/Date/prototype/this-value-non-date.js (previously Failed)
test/intl402/Date/prototype/taint-Intl-DateTimeFormat.js (previously Failed)
test/intl402/Date/prototype/throws-same-exceptions-as-DateTimeFormat.js (previously Failed)
test/intl402/Date/prototype/toLocaleString/default-options-object-prototype.js (previously Failed)
test/staging/sm/misc/builtin-methods-reject-null-undefined-this.js (previously Failed)

Tested main commit: 965f38c66d22dbcda4746d9b79c607189530287d
Tested PR commit: 755481e9dac8aa567a1642d658a1537444b27515
Compare commits: 965f38c...755481e

Comment on lines +988 to +1027
let options = coerce_options_to_object(options, context)?;
if format_type != FormatType::Time
&& get_option::<DateStyle>(&options, js_string!("dateStyle"), context)?.is_none()
{
options.create_data_property_or_throw(
js_string!("dateStyle"),
JsValue::from(js_string!("long")),
context,
)?;
}
if format_type != FormatType::Date
&& get_option::<TimeStyle>(&options, js_string!("timeStyle"), context)?.is_none()
{
options.create_data_property_or_throw(
js_string!("timeStyle"),
JsValue::from(js_string!("long")),
context,
)?;
}
let new_target = context
.intrinsics()
.constructors()
.date_time_format()
.constructor()
.into();
let options_value = options.into();
let dtf = create_date_time_format(
&new_target,
locales,
&options_value,
format_type,
defaults,
context,
)?;
let format_val = dtf.get(js_string!("format"), context)?;
let format_fn = format_val
.as_callable()
.ok_or_else(|| JsNativeError::typ().with_message("format is not callable"))?;
format_fn.call(&dtf.into(), &[JsValue::from(timestamp)], context)
}
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this is the correct approach. This is interacting with DateTimeFormat as if it was an external crate to be consumed, but since the creation of the formatter is not visible to the external world, we can get away with calling create_date_time_format directly.

My suggestion here would be to tweak create_date_time_format such that it returns DateTimeFormatter directly, then use that here to avoid creating a new JsObject, and move the object creation logic to the constructor instead

Copy link
Member

Choose a reason for hiding this comment

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

Kinda weird to do it this way in here but use the correct approach in Number.prototype.toLocaleString by the way

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the suggestion! I refactored create_date_time_format to return the internal DateTimeFormat and moved the JsObject creation into the constructor. Date.prototype.toLocaleString and related methods now format directly using the internal formatter, similar to Number.prototype.toLocaleString. Addressed in 725441e.

@jedel1043 jedel1043 added Waiting On Author Waiting on PR changes from the author and removed Waiting On Review Waiting on reviews from the maintainers labels Mar 15, 2026
@github-actions github-actions bot added the Waiting On Review Waiting on reviews from the maintainers label Mar 15, 2026
@codecov
Copy link

codecov bot commented Mar 15, 2026

Codecov Report

❌ Patch coverage is 94.89796% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 59.02%. Comparing base (6ddc2b4) to head (755481e).
⚠️ Report is 857 commits behind head on main.

Files with missing lines Patch % Lines
...e/engine/src/builtins/intl/date_time_format/mod.rs 94.64% 3 Missing ⚠️
core/engine/src/builtins/date/mod.rs 95.23% 2 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##             main    #5080       +/-   ##
===========================================
+ Coverage   47.24%   59.02%   +11.78%     
===========================================
  Files         476      563       +87     
  Lines       46892    62725    +15833     
===========================================
+ Hits        22154    37026    +14872     
- Misses      24738    25699      +961     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Member

@nekevss nekevss left a comment

Choose a reason for hiding this comment

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

Noticed a couple things while reviewing this.

func.call(this, &[], context)
}

/// Returns the `[[DateValue]]` internal slot (RequireInternalSlot(dateObject, `[[DateValue]]`)).
Copy link
Member

Choose a reason for hiding this comment

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

nit: accessing the inner field should remain as the current style.

// Let dateObject be the this value.
// Perform ? RequireInternalSlot(dateObject, [[DateValue]]).
// Let x be dateObject.[[DateValue]].
let t = Self::this_date_value(this)?;
Copy link
Member

Choose a reason for hiding this comment

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

issue: these steps are only applicable to the intl flagged implementation.

Err(JsError::from_opaque(JsValue::new(js_string!(
"Function Unimplemented]"
))))
// Let dateObject be the this value.
Copy link
Member

Choose a reason for hiding this comment

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

nit: style, include the step numbers alongside the spec steps

let needs_defaults = format_options.check_dtf_type(date_time_format_type);
// d. If needDefaults is true and defaults is either date or all, then
if needs_defaults && defaults != FormatDefaults::Time {
if needs_defaults && (defaults == FormatDefaults::All || defaults == FormatDefaults::Date) {
Copy link
Member

Choose a reason for hiding this comment

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

question: why was this changed? The previous looks correct and acts in less steps

context: &mut Context,
) -> JsResult<JsString> {
// FormatDateTime / PartitionDateTimePattern: TimeClip, then ToLocalTime, then format.
let x = time_clip(timestamp);
Copy link
Member

Choose a reason for hiding this comment

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

issue: we should not be handling this logic again.

We already handle this logic in two different places. There should not be a reason to implement it a second time. Maybe you're looking for ToLocalTime?

@alienx5499 alienx5499 force-pushed the feat/5075-date-tolocalestring branch from fa1bd92 to 755481e Compare March 15, 2026 21:49
@alienx5499 alienx5499 requested a review from nekevss March 15, 2026 21:52
@alienx5499
Copy link
Contributor Author

Hi @nekevss,

I've pushed updates addressing the review comments and resolved the outdated threads. Could you please take another look when you have time?

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

C-Builtins PRs and Issues related to builtins/intrinsics C-Intl Changes related to the `Intl` implementation C-Tests Issues and PRs related to the tests. Waiting On Author Waiting on PR changes from the author Waiting On Review Waiting on reviews from the maintainers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement Date.prototype.toLocaleString and friends

3 participants