diff --git a/Cargo.lock b/Cargo.lock index 9b255729085..6639c118a89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2287,8 +2287,7 @@ checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "ixdtf" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb6cd1080e64f68b07c577e3c687f4a894b3d1bd6093cb36b55c7bd07675aa3a" +source = "git+https://github.com/unicode-org/icu4x.git?rev=3d187da4d3f05b7e37603c4be3f2c1ce45100e03#3d187da4d3f05b7e37603c4be3f2c1ce45100e03" dependencies = [ "displaydoc", "utf8_iter", @@ -3552,7 +3551,7 @@ checksum = "42a4d50cdb458045afc8131fd91b64904da29548bcb63c7236e0844936c13078" [[package]] name = "temporal_rs" version = "0.0.4" -source = "git+https://github.com/boa-dev/temporal.git?rev=53fc1fc11f039574000d3d22a5d06d75836a4494#53fc1fc11f039574000d3d22a5d06d75836a4494" +source = "git+https://github.com/boa-dev/temporal.git?rev=c61468264e27bed14bd7717f2153a7178e2dfe5f#c61468264e27bed14bd7717f2153a7178e2dfe5f" dependencies = [ "combine", "iana-time-zone", diff --git a/Cargo.toml b/Cargo.toml index 8ceb2ee1742..d0d42315ac9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -113,7 +113,7 @@ intrusive-collections = "0.9.7" cfg-if = "1.0.0" either = "1.13.0" sys-locale = "0.3.2" -temporal_rs = { git = "https://github.com/boa-dev/temporal.git", rev = "53fc1fc11f039574000d3d22a5d06d75836a4494", features = ["tzdb"] } +temporal_rs = { git = "https://github.com/boa-dev/temporal.git", rev = "c61468264e27bed14bd7717f2153a7178e2dfe5f", features = ["tzdb"] } web-time = "1.1.0" criterion = "0.5.1" float-cmp = "0.10.0" diff --git a/core/engine/src/builtins/temporal/duration/mod.rs b/core/engine/src/builtins/temporal/duration/mod.rs index f214acdd760..5bd4fd89505 100644 --- a/core/engine/src/builtins/temporal/duration/mod.rs +++ b/core/engine/src/builtins/temporal/duration/mod.rs @@ -2,7 +2,7 @@ use super::{ get_relative_to_option, - options::{get_temporal_unit, TemporalUnitGroup}, + options::{get_digits_option, get_temporal_unit, TemporalUnitGroup}, DateTimeValues, }; use crate::value::JsVariant; @@ -23,7 +23,10 @@ use crate::{ use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; use temporal_rs::{ - options::{RoundingIncrement, RoundingOptions, TemporalRoundingMode, TemporalUnit}, + options::{ + RoundingIncrement, RoundingOptions, TemporalRoundingMode, TemporalUnit, + ToStringRoundingOptions, + }, partial::PartialDuration, Duration as InnerDuration, }; @@ -190,7 +193,7 @@ impl IntrinsicObject for Duration { .method(Self::subtract, js_string!("subtract"), 1) .method(Self::round, js_string!("round"), 1) .method(Self::total, js_string!("total"), 1) - .method(Self::to_string, js_string!("toString"), 1) + .method(Self::to_string, js_string!("toString"), 0) .method(Self::to_json, js_string!("toJSON"), 0) .method(Self::value_of, js_string!("valueOf"), 0) .build(); @@ -811,17 +814,48 @@ impl Duration { } /// 7.3.22 `Temporal.Duration.prototype.toString ( [ options ] )` - pub(crate) fn to_string(_this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { - Err(JsNativeError::error() - .with_message("not yet implemented.") - .into()) + pub(crate) fn to_string( + this: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { + let duration = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("this value must be a Duration object.") + })?; + + let options = get_options_object(args.get_or_undefined(0))?; + let precision = get_digits_option(&options, context)?; + let rounding_mode = + get_option::(&options, js_string!("roundingMode"), context)?; + let smallest_unit = + get_option::(&options, js_string!("smallestUnit"), context)?; + + let result = duration.inner.to_temporal_string(ToStringRoundingOptions { + precision, + smallest_unit, + rounding_mode, + })?; + + Ok(JsString::from(result).into()) } /// 7.3.23 `Temporal.Duration.prototype.toJSON ( )` - pub(crate) fn to_json(_this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { - Err(JsNativeError::error() - .with_message("not yet implemented.") - .into()) + pub(crate) fn to_json(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { + let duration = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("this value must be a Duration object.") + })?; + + let result = duration + .inner + .to_temporal_string(ToStringRoundingOptions::default())?; + + Ok(JsString::from(result).into()) } pub(crate) fn value_of(_this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { diff --git a/core/engine/src/builtins/temporal/now.rs b/core/engine/src/builtins/temporal/now.rs index 68e19f196cb..bfccbf60c86 100644 --- a/core/engine/src/builtins/temporal/now.rs +++ b/core/engine/src/builtins/temporal/now.rs @@ -68,7 +68,7 @@ impl Now { fn time_zone_id(_: &JsValue, _args: &[JsValue], context: &mut Context) -> JsResult { // 1. Return ! SystemTimeZone(). system_time_zone(context)? - .id() + .identifier() .map(|s| JsValue::from(js_string!(s.as_str()))) .map_err(Into::into) } diff --git a/core/engine/src/builtins/temporal/plain_month_day/mod.rs b/core/engine/src/builtins/temporal/plain_month_day/mod.rs index 8030d1cf07a..b874fb5cf35 100644 --- a/core/engine/src/builtins/temporal/plain_month_day/mod.rs +++ b/core/engine/src/builtins/temporal/plain_month_day/mod.rs @@ -1,5 +1,4 @@ //! Boa's implementation of the ECMAScript `Temporal.PlainMonthDay` builtin object. -#![allow(dead_code, unused_variables)] use std::str::FromStr; use crate::{ @@ -40,89 +39,6 @@ impl PlainMonthDay { } } -// ==== `Temporal.PlainMonthDay` static Methods ==== -impl PlainMonthDay { - // 10.2.2 Temporal.PlainMonthDay.from ( item [ , options ] ) - fn from(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let options = get_options_object(args.get_or_undefined(1))?; - let item = args.get_or_undefined(0); - to_temporal_month_day(item, &options, context) - } -} - -// === `PlainMonthDay` Accessor Implementations ===== / - -impl PlainMonthDay { - fn get_internal_field(this: &JsValue, field: &DateTimeValues) -> JsResult { - let month_day = this - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ().with_message("this value must be a PlainMonthDay object.") - })?; - let inner = &month_day.inner; - match field { - DateTimeValues::Day => Ok(inner.iso_day().into()), - DateTimeValues::MonthCode => Ok(js_string!(inner.month_code()?.to_string()).into()), - _ => unreachable!(), - } - } - - fn get_day(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - Self::get_internal_field(this, &DateTimeValues::Day) - } - - fn get_year(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - Self::get_internal_field(this, &DateTimeValues::Year) - } - - fn get_month_code(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - Self::get_internal_field(this, &DateTimeValues::MonthCode) - } - - fn get_calendar_id(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let month_day = this - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ().with_message("this value must be a PlainMonthDay object.") - })?; - let inner = &month_day.inner; - Ok(js_string!(inner.calendar().identifier()).into()) - } -} - -// ==== `Temporal.PlainMonthDay` Methods ==== -impl PlainMonthDay { - // 10.3.7 Temporal.PlainMonthDay.prototype.toString ( [ options ] ) - fn to_string(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - // 1. Let monthDay be the this value. - // 2. Perform ? RequireInternalSlot(monthDay, [[InitializedTemporalMonthDay]]). - let month_day = this - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ().with_message("this value must be a PlainMonthDay object.") - })?; - let inner = &month_day.inner; - // 3. Set options to ? NormalizeOptionsObject(options). - let options = get_options_object(args.get_or_undefined(0))?; - // 4. Let showCalendar be ? ToShowCalendarOption(options). - // Get calendarName from the options object - let show_calendar = - get_option::(&options, js_string!("calendarName"), context)? - .unwrap_or(DisplayCalendar::Auto); - - Ok(month_day_to_string(inner, show_calendar)) - } - - pub(crate) fn value_of(_this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { - Err(JsNativeError::typ() - .with_message("`valueOf` not supported by Temporal built-ins. See 'compare', 'equals', or `toString`") - .into()) - } -} - impl BuiltInObject for PlainMonthDay { const NAME: JsString = StaticJsStrings::PLAIN_MD_NAME; } @@ -166,9 +82,10 @@ impl IntrinsicObject for PlainMonthDay { None, Attribute::CONFIGURABLE, ) - .method(Self::to_string, js_string!("toString"), 1) + .static_method(Self::from, js_string!("from"), 1) + .method(Self::to_string, js_string!("toString"), 0) + .method(Self::to_json, js_string!("toJSON"), 0) .method(Self::value_of, js_string!("valueOf"), 0) - .static_method(Self::from, js_string!("from"), 2) .build(); } @@ -229,44 +146,102 @@ impl BuiltInConstructor for PlainMonthDay { } } -// ==== `PlainMonthDay` Abstract Operations ==== +// ==== `Temporal.PlainMonthDay` static Methods ==== -fn month_day_to_string(inner: &InnerMonthDay, show_calendar: DisplayCalendar) -> JsValue { - // Let month be monthDay.[[ISOMonth]] formatted as a two-digit decimal number, padded to the left with a zero if necessary - let month = inner.iso_month().to_string(); - - // 2. Let day be ! FormatDayOfMonth(monthDay.[[ISODay]]). - let day = inner.iso_day().to_string(); - - // 3. Let result be the string-concatenation of month and the code unit 0x002D (HYPHEN-MINUS). - let mut result = format!("{month:0>2}-{day:0>2}"); - - // 4. Let calendarId be monthDay.[[Calendar]].[[id]]. - let calendar_id = inner.calendar().identifier(); - - // 5. Let calendar be monthDay.[[Calendar]]. - // 6. If showCalendar is "auto", then - // a. Set showCalendar to "always". - // 7. If showCalendar is "always", then - // a. Let calendarString be ! FormatCalendarAnnotation(calendar). - // b. Set result to the string-concatenation of result, the code unit 0x0040 (COMMERCIAL AT), and calendarString. - if (matches!( - show_calendar, - DisplayCalendar::Critical | DisplayCalendar::Always | DisplayCalendar::Auto - )) && !(matches!(show_calendar, DisplayCalendar::Auto) && calendar_id == "iso8601") - { - let year = inner.iso_year().to_string(); - let flag = if matches!(show_calendar, DisplayCalendar::Critical) { - "!" - } else { - "" - }; - result = format!("{year}-{result}[{flag}u-ca={calendar_id}]"); +impl PlainMonthDay { + // 10.2.2 Temporal.PlainMonthDay.from ( item [ , options ] ) + fn from(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + let options = get_options_object(args.get_or_undefined(1))?; + let item = args.get_or_undefined(0); + to_temporal_month_day(item, &options, context) + } +} + +// ==== `PlainMonthDay` Accessor Implementations ==== + +impl PlainMonthDay { + fn get_internal_field(this: &JsValue, field: &DateTimeValues) -> JsResult { + let month_day = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("this value must be a PlainMonthDay object.") + })?; + let inner = &month_day.inner; + match field { + DateTimeValues::Day => Ok(inner.iso_day().into()), + DateTimeValues::MonthCode => Ok(js_string!(inner.month_code()?.to_string()).into()), + _ => unreachable!(), + } + } + + fn get_calendar_id(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { + let month_day = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("this value must be a PlainMonthDay object.") + })?; + Ok(js_string!(month_day.inner.calendar().identifier()).into()) + } + + fn get_day(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { + Self::get_internal_field(this, &DateTimeValues::Day) + } + + fn get_month_code(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { + Self::get_internal_field(this, &DateTimeValues::MonthCode) + } +} + +// ==== `Temporal.PlainMonthDay` Methods ==== + +impl PlainMonthDay { + /// 10.3.8 `Temporal.PlainMonthDay.prototype.toString ( [ options ] )` + fn to_string(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let monthDay be the this value. + // 2. Perform ? RequireInternalSlot(monthDay, [[InitializedTemporalMonthDay]]). + let month_day = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("this value must be a PlainMonthDay object.") + })?; + + // 3. Set options to ? NormalizeOptionsObject(options). + let options = get_options_object(args.get_or_undefined(0))?; + // 4. Let showCalendar be ? ToShowCalendarOption(options). + // Get calendarName from the options object + let show_calendar = + get_option::(&options, js_string!("calendarName"), context)? + .unwrap_or(DisplayCalendar::Auto); + + let ixdtf = month_day.inner.to_ixdtf_string(show_calendar); + Ok(JsString::from(ixdtf).into()) + } + + /// 10.3.10 Temporal.PlainMonthDay.prototype.toJSON ( ) + pub(crate) fn to_json(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { + let month_day = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("this value must be a PlainMonthDay object.") + })?; + + Ok(JsString::from(month_day.inner.to_string()).into()) + } + + /// 9.3.11 Temporal.PlainMonthDay.prototype.valueOf ( ) + pub(crate) fn value_of(_this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { + Err(JsNativeError::typ() + .with_message("`valueOf` not supported by Temporal built-ins. See 'compare', 'equals', or `toString`") + .into()) } - // 8. Return result. - js_string!(result).into() } +// ==== `PlainMonthDay` Abstract Operations ==== + pub(crate) fn create_temporal_month_day( inner: InnerMonthDay, new_target: Option<&JsValue>, diff --git a/core/engine/src/builtins/temporal/plain_year_month/mod.rs b/core/engine/src/builtins/temporal/plain_year_month/mod.rs index b06cbfa1403..859d7caa193 100644 --- a/core/engine/src/builtins/temporal/plain_year_month/mod.rs +++ b/core/engine/src/builtins/temporal/plain_year_month/mod.rs @@ -134,13 +134,14 @@ impl IntrinsicObject for PlainYearMonth { Attribute::CONFIGURABLE, ) .static_method(Self::from, js_string!("from"), 2) - .method(Self::with, js_string!("with"), 2) - .method(Self::add, js_string!("add"), 2) - .method(Self::subtract, js_string!("subtract"), 2) - .method(Self::until, js_string!("until"), 2) - .method(Self::since, js_string!("since"), 2) + .method(Self::with, js_string!("with"), 1) + .method(Self::add, js_string!("add"), 1) + .method(Self::subtract, js_string!("subtract"), 1) + .method(Self::until, js_string!("until"), 1) + .method(Self::since, js_string!("since"), 1) .method(Self::equals, js_string!("equals"), 1) - .method(Self::to_string, js_string!("toString"), 1) + .method(Self::to_string, js_string!("toString"), 0) + .method(Self::to_json, js_string!("toJSON"), 0) .method(Self::value_of, js_string!("valueOf"), 0) .build(); } @@ -417,6 +418,7 @@ impl PlainYearMonth { .into()) } + /// `9.3.19 Temporal.PlainYearMonth.prototype.toString ( [ options ] )` fn to_string(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let YearMonth be the this value. // 2. Perform ? RequireInternalSlot(yearMonth, [[InitializedTemporalYearMonth]]). @@ -427,7 +429,6 @@ impl PlainYearMonth { JsNativeError::typ().with_message("this value must be a PlainYearMonth object.") })?; - let inner = &year_month.inner; // 3. Set options to ? NormalizeOptionsObject(options). let options = get_options_object(args.get_or_undefined(0))?; // 4. Let showCalendar be ? ToShowCalendarOption(options). @@ -436,9 +437,23 @@ impl PlainYearMonth { get_option::(&options, js_string!("calendarName"), context)? .unwrap_or(DisplayCalendar::Auto); - Ok(year_month_to_string(inner, show_calendar)) + let ixdtf = year_month.inner.to_ixdtf_string(show_calendar); + Ok(JsString::from(ixdtf).into()) } + /// `9.3.21 Temporal.PlainYearMonth.prototype.toJSON ( )` + pub(crate) fn to_json(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { + let year_month = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("this value must be a PlainYearMonth object.") + })?; + + Ok(JsString::from(year_month.inner.to_string()).into()) + } + + /// `9.3.22 Temporal.PlainYearMonth.prototype.valueOf ( )` pub(crate) fn value_of(_this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { Err(JsNativeError::typ() .with_message("`valueOf` not supported by Temporal built-ins. See 'compare', 'equals', or `toString`") @@ -528,35 +543,3 @@ fn add_or_subtract_duration( create_temporal_year_month(year_month_result, None, context) } - -fn year_month_to_string(inner: &InnerYearMonth, show_calendar: DisplayCalendar) -> JsValue { - // Let year be PadISOYear(yearMonth.[[ISOYear]]). - let year = inner.padded_iso_year_string(); - // Let month be ToZeroPaddedDecimalString(yearMonth.[[ISOMonth]], 2). - let month = inner.iso_month().to_string(); - - // Let result be the string-concatenation of year, the code unit 0x002D (HYPHEN-MINUS), and month. - let mut result = format!("{year}-{month:0>2}"); - - // 5. If showCalendar is one of "always" or "critical", or if calendarIdentifier is not "iso8601", then - // a. Let day be ToZeroPaddedDecimalString(yearMonth.[[ISODay]], 2). - // b. Set result to the string-concatenation of result, the code unit 0x002D (HYPHEN-MINUS), and day. - // 6. Let calendarString be FormatCalendarAnnotation(calendarIdentifier, showCalendar). - // 7. Set result to the string-concatenation of result and calendarString. - if matches!( - show_calendar, - DisplayCalendar::Critical | DisplayCalendar::Always | DisplayCalendar::Auto - ) && !(matches!(show_calendar, DisplayCalendar::Auto) && inner.calendar_id() == "iso8601") - { - let calendar = inner.calendar_id(); - let calendar_string = calendar.to_string(); - let flag = if matches!(show_calendar, DisplayCalendar::Critical) { - "!" - } else { - "" - }; - result.push_str(&format!("[{flag}c={calendar_string}]",)); - } - // 8. Return result. - js_string!(result).into() -} diff --git a/core/engine/src/builtins/temporal/zoneddatetime/mod.rs b/core/engine/src/builtins/temporal/zoneddatetime/mod.rs index dcad7549da7..8f5d374e5c8 100644 --- a/core/engine/src/builtins/temporal/zoneddatetime/mod.rs +++ b/core/engine/src/builtins/temporal/zoneddatetime/mod.rs @@ -444,7 +444,7 @@ impl ZonedDateTime { JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") })?; - let tz_id = zdt.inner.timezone().id()?; + let tz_id = zdt.inner.timezone().identifier()?; Ok(JsString::from(tz_id).into()) } @@ -960,7 +960,7 @@ impl ZonedDateTime { get_option::(&options, js_string!("smallestUnit"), context)?; // NOTE: There may be an order-of-operations here due to a check on Unit groups and smallest_unit value. let display_timezone = - get_option::(&options, js_string!("offset"), context)? + get_option::(&options, js_string!("timeZoneName"), context)? .unwrap_or(DisplayTimeZone::Auto); let options = ToStringRoundingOptions {