Skip to content
Draft
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
2 changes: 1 addition & 1 deletion core/engine/src/builtins/date/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1556,7 +1556,7 @@ impl Date {
let locales = args.get_or_undefined(0);
let options = args.get_or_undefined(1);
let dtf = create_date_time_format(locales, options, required, defaults, context)?;
format_date_time(&dtf, x, context)
format_date_time(&dtf, &x.into(), context)
}

/// [`Date.prototype.toLocaleDateString()`][spec].
Expand Down
97 changes: 79 additions & 18 deletions core/engine/src/builtins/intl/date_time_format/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use crate::{
options::{IntlOptions, coerce_options_to_object},
},
options::get_option,
temporal,
},
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
error::JsNativeError,
Expand Down Expand Up @@ -252,6 +253,13 @@ impl BuiltInConstructor for DateTimeFormat {
}

impl DateTimeFormat {
#[inline]
#[must_use]
pub(crate) fn calendar_algorithm(&self) -> CalendarAlgorithm {
self.calendar_algorithm
.unwrap_or(CalendarAlgorithm::Iso8601)
}

fn get_format(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Let dtf be the this value.
// 2. If the implementation supports the normative optional constructor mode of 4.3 Note 1, then
Expand Down Expand Up @@ -288,7 +296,7 @@ impl DateTimeFormat {
};

// 5. Return ? FormatDateTime(dtf, x).
format_date_time(dtf.borrow().data(), x, context)
format_date_time(dtf.borrow().data(), &x.into(), context)
},
dtf_clone,
),
Expand Down Expand Up @@ -334,7 +342,7 @@ impl DateTimeFormat {
date.to_number(context)?
};
// 5. Return ? FormatDateTimeToParts(dtf, x).
format_date_time_to_parts(dtf.borrow().data(), x, context).map(JsValue::from)
format_date_time_to_parts(dtf.borrow().data(), &x.into(), context).map(JsValue::from)
}

// [`Intl.DateTimeFormat.prototype.formatRange ( startDate, endDate )`][spec]
Expand Down Expand Up @@ -924,11 +932,11 @@ pub(crate) fn create_date_time_format(
/// [spec]: https://tc39.es/ecma402/#sec-formatdatetime
pub(crate) fn format_date_time(
dtf: &DateTimeFormat,
timestamp: f64,
x: &JsValue,
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let parts be ? PartitionDateTimePattern(dateTimeFormat, x).
let parts = partition_date_time_pattern(dtf, timestamp, context)?;
let parts = partition_date_time_pattern(dtf, x, context)?;

// 2. Let result be the empty String.
// 3. For each Record { [[Type]], [[Value]] } part of parts, do
Expand All @@ -939,7 +947,7 @@ pub(crate) fn format_date_time(

pub(crate) fn format_date_time_to_parts(
dtf: &DateTimeFormat,
timestamp: f64,
x: &JsValue,
context: &mut Context,
) -> JsResult<JsArray> {
#[derive(Debug, Clone)]
Expand Down Expand Up @@ -974,7 +982,7 @@ pub(crate) fn format_date_time_to_parts(
}

// 1. Let parts be ? PartitionDateTimePattern(dateTimeFormat, x).
let parts = partition_date_time_pattern(dtf, timestamp, context)?;
let parts = partition_date_time_pattern(dtf, x, context)?;

let mut collector = PartsCollector(Vec::new());
parts
Expand Down Expand Up @@ -1016,25 +1024,21 @@ pub(crate) fn format_date_time_to_parts(
/// [spec]: https://tc39.es/ecma402/#sec-partitiondatetimepattern
fn partition_date_time_pattern<'a>(
dtf: &'a DateTimeFormat,
timestamp: f64,
x: &JsValue,
context: &mut Context,
) -> JsResult<FormattedDateTime<'a>> {
// 1. Set x to TimeClip(x).
let x = time_clip(timestamp);
let format_record = handle_date_time_value(dtf, x, context)?;

// 2. If x is `NaN`, throw a `RangeError` exception.
if x.is_nan() {
return Err(js_error!(RangeError: "formatted date cannot be NaN"));
}
// Let epochNanoseconds be ℤ(ℝ(x) × 10^6).
let epoch_ns = x as i128 * 1_000_000;
// 2. Let epochNanoseconds be formatRecord.[[EpochNanoseconds]].
let epoch_ns = format_record.epoch_nanoseconds;

// 4. Let format be dateTimeFormat.[[DateTimeFormat]].
// 5. If dateTimeFormat.[[HourCycle]] is "h11" or "h12", then
// 3. Let format be dateTimeFormat.[[DateTimeFormat]].
// 4. If dateTimeFormat.[[HourCycle]] is "h11" or "h12", then
// a. Let pattern be format.[[pattern12]].
// 6. Else,
// 5. Else,
// a. Let pattern be format.[[pattern]].
// 7. Let result be FormatDateTimePattern(dateTimeFormat, format, pattern, epochNanoseconds).
// 6. Let result be FormatDateTimePattern(dateTimeFormat, format, pattern, epochNanoseconds).
//
// NOTE: `FormatDateTimePattern` is entirely handled in icu_datetime, and the only step
// we really do is
Expand Down Expand Up @@ -1254,3 +1258,60 @@ fn unwrap_date_time_format(
.with_message("object was not an initialized `Intl.DateTimeFormat` object")
.into())
}

struct ValueFormatRecord<'a> {
_format: &'a DateTimeFormat,
epoch_nanoseconds: i128,
_is_plain: bool,
}

/// [`HandleDateTimeValue`](https://tc39.es/proposal-temporal/#sec-temporal-handledatetimevalue)
fn handle_date_time_value<'a>(
dtf: &'a DateTimeFormat,
x: &JsValue,
_: &mut Context,
) -> JsResult<ValueFormatRecord<'a>> {
let o = x.as_object().expect("value is a Temporal object");

// 1. If x is a Number, return ? HandleDateTimeOthers(dateTimeFormat, x).
let (epoch_nanoseconds, is_plain) = if let Some(timestamp) = x.as_number() {
// 1. Set x to TimeClip(x).
// 2. If x is `NaN`, throw a `RangeError` exception.
// 3. Let epochNanoseconds be ℤ(ℝ(x) × 10^6).
// 4. Let format be dateTimeFormat.[[DateTimeFormat]].
let x = time_clip(timestamp);
if x.is_nan() {
return Err(js_error!(RangeError: "formatted date cannot be NaN"));
}
(x as i128 * 1_000_000, false)
// 2. If x has an [[InitializedTemporalDate]] internal slot, return ? HandleDateTimeTemporalDate(dateTimeFormat, x).
} else if let Some(_to) = o.downcast_ref::<temporal::PlainDate>() {
unimplemented!()
// 3. If x has an [[InitializedTemporalYearMonth]] internal slot, return ? HandleDateTimeTemporalYearMonth(dateTimeFormat, x).
} else if let Some(_to) = o.downcast_ref::<temporal::PlainYearMonth>() {
unimplemented!()
// 4. If x has an [[InitializedTemporalMonthDay]] internal slot, return ? HandleDateTimeTemporalMonthDay(dateTimeFormat, x).
} else if let Some(_to) = o.downcast_ref::<temporal::PlainMonthDay>() {
unimplemented!()
// 5. If x has an [[InitializedTemporalTime]] internal slot, return ? HandleDateTimeTemporalTime(dateTimeFormat, x).
} else if let Some(_to) = o.downcast_ref::<temporal::PlainTime>() {
unimplemented!()
// 6. If x has an [[InitializedTemporalDateTime]] internal slot, return ? HandleDateTimeTemporalDateTime(dateTimeFormat, x).
} else if let Some(_to) = o.downcast_ref::<temporal::PlainDateTime>() {
unimplemented!()
// 7. If x has an [[InitializedTemporalInstant]] internal slot, return HandleDateTimeTemporalInstant(dateTimeFormat, x).
} else if let Some(to) = o.downcast_ref::<temporal::Instant>() {
// 1. Let format be dateTimeFormat.[[TemporalInstantFormat]].
(to.inner.epoch_nanoseconds().as_i128(), false)
// 8. Assert: x has an [[InitializedTemporalZonedDateTime]] internal slot.
// 9. Throw a TypeError exception.
} else {
return Err(js_error!(TypeError: "Object is ZonedDateTime"));
};

Ok(ValueFormatRecord {
_format: dtf,
epoch_nanoseconds,
_is_plain: is_plain,
})
}
68 changes: 54 additions & 14 deletions core/engine/src/builtins/temporal/zoneddatetime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::{
temporal::{calendar::to_temporal_calendar_identifier, options::get_digits_option},
},
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
js_string,
js_error, js_string,
object::internal_methods::get_prototype_from_constructor,
property::Attribute,
realm::Realm,
Expand Down Expand Up @@ -1612,25 +1612,65 @@ impl ZonedDateTime {
///
/// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.tolocalestring
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/ZonedDateTime/toLocaleString
fn to_locale_string(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// TODO: Update for ECMA-402 compliance
fn to_locale_string(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let zonedDateTime be the this value.
// 2. Perform ? RequireInternalSlot(zonedDateTime, [[InitializedTemporalZonedDateTime]]).
let object = this.as_object();
let zdt = object
.as_ref()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.")
})?;
.ok_or_else(
|| js_error!(TypeError: "the this object must be a ZonedDateTime object."),
)?;

let ixdtf = zdt.inner.to_ixdtf_string_with_provider(
DisplayOffset::Auto,
DisplayTimeZone::Auto,
DisplayCalendar::Auto,
ToStringRoundingOptions::default(),
context.timezone_provider(),
)?;
#[cfg(feature = "intl")]
{
use crate::builtins::intl::date_time_format::{
FormatDefaults, FormatType, create_date_time_format, format_date_time,
};
// 3. Let dateTimeFormat be ? CreateDateTimeFormat(%Intl.DateTimeFormat%, locales, options, any, all, zonedDateTime.[[TimeZone]]).
let locales = args.get_or_undefined(0);
let options = args.get_or_undefined(1);

let dtf = create_date_time_format(
locales,
options,
FormatType::Any,
FormatDefaults::All,
context,
)?;
// 4. If zonedDateTime.[[Calendar]] is not "iso8601" and CalendarEquals(zonedDateTime.[[Calendar]], dateTimeFormat.[[Calendar]]) is false,
// throw a RangeError exception.
let zdt_cal = zdt.inner.calendar();
let dtf_cal = dtf.calendar_algorithm();

if !zdt_cal.is_iso() && zdt_cal.identifier() != dtf_cal.as_str() {
return Err(
js_error!(RangeError: "calendars {} and {} aren't compatible", zdt_cal.identifier(), dtf_cal.as_str()),
);
}
// 5. Let instant be ! CreateTemporalInstant(zonedDateTime.[[EpochNanoseconds]]).
// 6. Return ? FormatDateTime(dateTimeFormat, instant).
let inst = create_temporal_instant(zdt.inner.to_instant(), None, context)?;
format_date_time(&dtf, &inst, context)
}

Ok(JsString::from(ixdtf).into())
#[cfg(not(feature = "intl"))]
{
let ixdtf = zdt.inner.to_ixdtf_string_with_provider(
DisplayOffset::Auto,
DisplayTimeZone::Auto,
DisplayCalendar::Auto,
ToStringRoundingOptions::default(),
context.timezone_provider(),
)?;

Ok(JsString::from(ixdtf).into())
}
}

/// 6.3.43 `Temporal.ZonedDateTime.prototype.toJSON ( )`
Expand Down
Loading