Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions core/engine/src/builtins/intl/date_time_format/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1053,3 +1053,70 @@ pub(crate) fn format_date_time_locale(
let result = format_timestamp_with_dtf(&dtf, x, context)?;
Ok(JsValue::from(result))
}
/// Formats a `Temporal.PlainMonthDay` using locale-sensitive formatting.
///
/// Used by `Temporal.PlainMonthDay.prototype.toLocaleString`.
/// Unlike `format_date_time_locale`, this does not use a Unix timestamp —
/// `PlainMonthDay` has no year or timezone, so we build an ICU4X `Date<Iso>`
/// directly using the Temporal spec's reference year (1972).
pub(crate) fn format_plain_month_day_locale(
locales: &JsValue,
options: &JsValue,
month: u8,
day: u8,
context: &mut Context,
) -> JsResult<JsValue> {
let options_obj = coerce_options_to_object(options, context)?;

// Per ECMA-402 ToDateTimeOptions with type=date, defaults=date:
// if neither dateStyle nor individual month/day fields are set, default to
// { month: "long", day: "numeric" } — which is the most natural for a month-day.
let has_date_style =
get_option::<DateStyle>(&options_obj, js_string!("dateStyle"), context)?.is_some();
let has_month = options_obj
.has_own_property(js_string!("month"), context)?;
let has_day = options_obj
.has_own_property(js_string!("day"), context)?;

if !has_date_style && !has_month && !has_day {
options_obj.create_data_property_or_throw(
js_string!("month"),
JsValue::from(js_string!("long")),
context,
)?;
options_obj.create_data_property_or_throw(
js_string!("day"),
JsValue::from(js_string!("numeric")),
context,
)?;
}

let options_value = JsValue::from(options_obj);
let dtf = create_date_time_format(
locales,
&options_value,
FormatType::Date,
FormatDefaults::Date,
context,
)?;

// Use 1972 as the reference year — same year the Temporal spec uses for
// PlainMonthDay's internal ISODate when calendar is iso8601.
let date = Date::try_new_iso(1972, month, day)
.map_err(|e| JsNativeError::range().with_message(e.to_string()))?;
let time = Time::try_new(12, 0, 0, 0)
.map_err(|e| JsNativeError::range().with_message(e.to_string()))?;

// We need a ZonedDateTime for the formatter — use UTC offset zero.
let tz_info = FormatTimeZone::UtcOffset(UtcOffset::zero());
let dt = DateTime { date, time };
let tz_info_at_time = tz_info.to_time_zone_info().at_date_time_iso(dt);
let zdt = ZonedDateTime {
date,
time,
zone: tz_info_at_time,
};

let result = dtf.formatter.format(&zdt).to_string();
Ok(JsValue::from(JsString::from(result)))
}
52 changes: 36 additions & 16 deletions core/engine/src/builtins/temporal/plain_month_day/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,22 +374,42 @@ impl PlainMonthDay {
///
/// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.tolocalestring
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainMonthDay/toLocaleString
pub(crate) fn to_locale_string(
this: &JsValue,
_: &[JsValue],
_: &mut Context,
) -> JsResult<JsValue> {
// TODO: Update for ECMA-402 compliance
let object = this.as_object();
let month_day = object
.as_ref()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("this value must be a PlainMonthDay object.")
})?;

Ok(JsString::from(month_day.inner.to_string()).into())
}
/// 10.3.9 `Temporal.PlainMonthDay.prototype.toLocaleString ( [ locales [ , options ] ] )`
///
/// More information:
///
/// - [ECMAScript Temporal proposal][spec]
/// - [MDN reference][mdn]
///
/// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.tolocalestring
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainMonthDay/toLocaleString
pub(crate) fn to_locale_string(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let monthDay be the this value.
// 2. Perform ? RequireInternalSlot(monthDay, [[InitializedTemporalMonthDay]]).
let object = this.as_object();
let month_day = object
.as_ref()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("this value must be a PlainMonthDay object.")
})?;

// 3. Return ? FormatDateTimeLocaleString(monthDay, locales, options).
let locales = args.get_or_undefined(0);
let options = args.get_or_undefined(1);

crate::builtins::intl::date_time_format::format_plain_month_day_locale(
locales,
options,
month_day.inner.month(),
month_day.inner.day(),
context,
)
}

/// 10.3.10 `Temporal.PlainMonthDay.prototype.toJSON ( )`
///
Expand Down