diff --git a/Cargo.lock b/Cargo.lock index 0cd54df878..9838693ac4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3200,8 +3200,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "temporal_rs" -version = "0.0.2" -source = "git+https://github.com/boa-dev/temporal.git?rev=cf70b50f90865d2c00fb994b7adf8b9e1bd837b8#cf70b50f90865d2c00fb994b7adf8b9e1bd837b8" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e6f351ef929946476b4107c09348c9e25ba1155ff8e3867b1914878a0fb553f" dependencies = [ "bitflags 2.6.0", "icu_calendar", diff --git a/Cargo.toml b/Cargo.toml index f46369a4e3..94ee7bdeec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -115,7 +115,7 @@ intrusive-collections = "0.9.6" cfg-if = "1.0.0" either = "1.13.0" sys-locale = "0.3.1" -temporal_rs = { git = "https://github.com/boa-dev/temporal.git", rev = "cf70b50f90865d2c00fb994b7adf8b9e1bd837b8" } +temporal_rs = "0.0.3" web-time = "1.1.0" criterion = "0.5.1" float-cmp = "0.9.0" diff --git a/core/engine/src/builtins/temporal/duration/mod.rs b/core/engine/src/builtins/temporal/duration/mod.rs index 9bf5d7b104..c5e07e116b 100644 --- a/core/engine/src/builtins/temporal/duration/mod.rs +++ b/core/engine/src/builtins/temporal/duration/mod.rs @@ -180,6 +180,7 @@ impl IntrinsicObject for Duration { None, Attribute::CONFIGURABLE, ) + .static_method(Self::from, js_string!("from"), 1) .method(Self::with, js_string!("with"), 1) .method(Self::negated, js_string!("negated"), 0) .method(Self::abs, js_string!("abs"), 0) @@ -412,7 +413,26 @@ impl Duration { } } -// -- Duration Method implementations -- +// ==== Duration methods implementations ==== + +impl Duration { + fn from(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + let item = args.get_or_undefined(0); + // 1. If item is an Object and item has an [[InitializedTemporalDuration]] internal slot, then + if let Some(duration) = item.as_object().and_then(JsObject::downcast_ref::) { + // a. Return ! CreateTemporalDuration(item.[[Years]], item.[[Months]], item.[[Weeks]], + // item.[[Days]], item.[[Hours]], item.[[Minutes]], item.[[Seconds]], item.[[Milliseconds]], + // item.[[Microseconds]], item.[[Nanoseconds]]). + return create_temporal_duration(duration.inner, None, context).map(Into::into); + } + + // 2. Return ? ToTemporalDuration(item). + create_temporal_duration(to_temporal_duration_record(item, context)?, None, context) + .map(Into::into) + } +} + +// ==== Duration.prototype method implementations ==== impl Duration { /// 7.3.15 `Temporal.Duration.prototype.with ( temporalDurationLike )` @@ -581,17 +601,45 @@ impl Duration { } /// 7.3.18 `Temporal.Duration.prototype.add ( other [ , options ] )` - pub(crate) fn add(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { - Err(JsNativeError::range() - .with_message("not yet implemented.") - .into()) + pub(crate) fn add( + this: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { + // 1.Let duration be the this value. + // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). + let duration = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("this value must be a Duration object.") + })?; + + // 3. Return ? AddDurations(add, duration, other). + let other = to_temporal_duration_record(args.get_or_undefined(0), context)?; + + create_temporal_duration(duration.inner.add(&other)?, None, context).map(Into::into) } /// 7.3.19 `Temporal.Duration.prototype.subtract ( other [ , options ] )` - pub(crate) fn subtract(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { - Err(JsNativeError::range() - .with_message("not yet implemented.") - .into()) + pub(crate) fn subtract( + this: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { + // 1.Let duration be the this value. + // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). + 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 other = to_temporal_duration_record(args.get_or_undefined(0), context)?; + + // 3. Return ? AddDurations(add, duration, other). + create_temporal_duration(duration.inner.subtract(&other)?, None, context).map(Into::into) } /// 7.3.20 `Temporal.Duration.prototype.round ( roundTo )` diff --git a/core/engine/src/builtins/temporal/mod.rs b/core/engine/src/builtins/temporal/mod.rs index 1988982929..085ebd2eb8 100644 --- a/core/engine/src/builtins/temporal/mod.rs +++ b/core/engine/src/builtins/temporal/mod.rs @@ -34,7 +34,7 @@ use crate::{ realm::Realm, string::StaticJsStrings, value::Type, - Context, JsBigInt, JsError, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, + Context, JsBigInt, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; use boa_macros::js_str; use boa_profiler::Profiler; @@ -250,15 +250,18 @@ pub(crate) fn to_relative_temporal_object( ) -> RelativeTemporalObjectResult { let relative_to = options.get(PropertyKey::from(js_str!("relativeTo")), context)?; let plain_date = match relative_to { - JsValue::String(relative_to_str) => Some(relative_to_str.into()), - JsValue::Object(relative_to_obj) => Some(relative_to_obj.into()), - _ => None, - } - .map(|plane_date| Ok::<_, JsError>(to_temporal_date(&plane_date, None, context)?.inner)) - .transpose()?; + JsValue::String(relative_to_str) => JsValue::from(relative_to_str), + JsValue::Object(relative_to_obj) => JsValue::from(relative_to_obj), + _ => { + return Err(JsNativeError::typ() + .with_message("Invalid type for converting to relativeTo object") + .into()) + } + }; + let plain_date = to_temporal_date(&plain_date, None, context)?; // TODO: Implement TemporalZonedDateTime conversion when ZonedDateTime is implemented - Ok((plain_date, None)) + Ok((Some(plain_date), None)) } // 13.22 `LargerOfTwoTemporalUnits ( u1, u2 )` diff --git a/core/engine/src/builtins/temporal/plain_date/mod.rs b/core/engine/src/builtins/temporal/plain_date/mod.rs index efbe2aa253..c05f33b1db 100644 --- a/core/engine/src/builtins/temporal/plain_date/mod.rs +++ b/core/engine/src/builtins/temporal/plain_date/mod.rs @@ -29,8 +29,8 @@ use temporal_rs::{ }; use super::{ - calendar, create_temporal_duration, options::get_difference_settings, - to_temporal_duration_record, PlainDateTime, ZonedDateTime, + calendar, create_temporal_datetime, create_temporal_duration, options::get_difference_settings, + to_temporal_duration_record, to_temporal_time, PlainDateTime, ZonedDateTime, }; /// The `Temporal.PlainDate` object. @@ -212,6 +212,7 @@ impl IntrinsicObject for PlainDate { None, Attribute::CONFIGURABLE, ) + .static_method(Self::from, js_string!("from"), 2) .method(Self::to_plain_year_month, js_string!("toPlainYearMonth"), 0) .method(Self::to_plain_month_day, js_string!("toPlainMonthDay"), 0) .method(Self::get_iso_fields, js_string!("getISOFields"), 0) @@ -222,6 +223,7 @@ impl IntrinsicObject for PlainDate { .method(Self::until, js_string!("until"), 2) .method(Self::since, js_string!("since"), 2) .method(Self::equals, js_string!("equals"), 1) + .method(Self::to_plain_datetime, js_string!("toPlainDateTime"), 1) .build(); } @@ -276,7 +278,7 @@ impl PlainDate { JsNativeError::typ().with_message("the this object must be a PlainDate object.") })?; - Ok(JsString::from(date.inner.calendar().identifier()?).into()) + Ok(JsString::from(date.inner.calendar().identifier()).into()) } /// 3.3.4 get `Temporal.PlainDate.prototype.year` @@ -483,6 +485,28 @@ impl PlainDate { } } +// ==== `PlainDate` method implementations ==== + +impl PlainDate { + fn from(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + let item = args.get_or_undefined(0); + let options = args.get(1); + + if let Some(date) = item.as_object().and_then(JsObject::downcast_ref::) { + let options = get_options_object(options.unwrap_or(&JsValue::undefined()))?; + let _ = get_option::(&options, js_str!("overflow"), context)?; + return create_temporal_date(date.inner.clone(), None, context).map(Into::into); + } + + create_temporal_date( + to_temporal_date(item, options.cloned(), context)?, + None, + context, + ) + .map(Into::into) + } +} + // ==== `PlainDate.prototype` method implementation ==== impl PlainDate { @@ -514,7 +538,7 @@ impl PlainDate { // 4. Perform ! CreateDataPropertyOrThrow(fields, "calendar", temporalDate.[[Calendar]]). fields.create_data_property_or_throw( js_str!("calendar"), - JsString::from(date.inner.calendar().identifier()?), + JsString::from(date.inner.calendar().identifier()), context, )?; // 5. Perform ! CreateDataPropertyOrThrow(fields, "isoDay", 𝔽(temporalDate.[[ISODay]])). @@ -606,8 +630,7 @@ impl PlainDate { let options = get_options_object(args.get_or_undefined(1))?; let settings = get_difference_settings(&options, context)?; - create_temporal_duration(date.inner.until(&other.inner, settings)?, None, context) - .map(Into::into) + create_temporal_duration(date.inner.until(&other, settings)?, None, context).map(Into::into) } fn since(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { @@ -626,14 +649,44 @@ impl PlainDate { let options = get_options_object(args.get_or_undefined(1))?; let settings = get_difference_settings(&options, context)?; - create_temporal_duration(date.inner.since(&other.inner, settings)?, None, context) - .map(Into::into) + create_temporal_duration(date.inner.since(&other, settings)?, None, context).map(Into::into) } - fn equals(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { - Err(JsNativeError::error() - .with_message("not yet implemented.") - .into()) + fn equals(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + let date = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a PlainDate object.") + })?; + + let other = to_temporal_date(args.get_or_undefined(0), None, context)?; + + Ok((date.inner == other).into()) + } + + /// 3.3.30 `Temporal.PlainDate.prototype.toPlainDateTime ( [ temporalTime ] )` + fn to_plain_datetime( + this: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { + // 1. Let temporalDate be the this value. + // 2. Perform ? RequireInternalSlot(temporalDate, [[InitializedTemporalDate]]). + let date = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a PlainDate object.") + })?; + + // 3. Set temporalTime to ? ToTemporalTimeOrMidnight(temporalTime). + let time = args + .first() + .map(|v| to_temporal_time(v, None, context)) + .transpose()?; + // 4. Return ? CreateTemporalDateTime(temporalDate.[[ISOYear]], temporalDate.[[ISOMonth]], temporalDate.[[ISODay]], temporalTime.[[ISOHour]], temporalTime.[[ISOMinute]], temporalTime.[[ISOSecond]], temporalTime.[[ISOMillisecond]], temporalTime.[[ISOMicrosecond]], temporalTime.[[ISONanosecond]], temporalDate.[[Calendar]]). + create_temporal_datetime(date.inner.to_date_time(time)?, None, context).map(Into::into) } } @@ -699,7 +752,7 @@ pub(crate) fn to_temporal_date( item: &JsValue, options: Option, context: &mut Context, -) -> JsResult { +) -> JsResult { // 1. If options is not present, set options to undefined. let options = options.unwrap_or(JsValue::undefined()); @@ -711,7 +764,7 @@ pub(crate) fn to_temporal_date( if let Some(object) = item.as_object() { // a. If item has an [[InitializedTemporalDate]] internal slot, then if let Some(date) = object.downcast_ref::() { - return Ok(PlainDate::new(date.inner.clone())); + return Ok(date.inner.clone()); // b. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then } else if let Some(data) = object.downcast_ref::() { return Err(JsNativeError::range() @@ -731,7 +784,7 @@ pub(crate) fn to_temporal_date( let date = InnerDate::from_datetime(date_time.inner()); // ii. Return ! CreateTemporalDate(item.[[ISOYear]], item.[[ISOMonth]], item.[[ISODay]], item.[[Calendar]]). - return Ok(PlainDate::new(date)); + return Ok(date); } // d. Let calendar be ? GetTemporalCalendarSlotValueWithISODefault(item). @@ -763,5 +816,5 @@ pub(crate) fn to_temporal_date( .parse::() .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; - Ok(PlainDate::new(result)) + Ok(result) } diff --git a/core/engine/src/builtins/temporal/plain_date_time/mod.rs b/core/engine/src/builtins/temporal/plain_date_time/mod.rs index 20937a32b0..9539a540d3 100644 --- a/core/engine/src/builtins/temporal/plain_date_time/mod.rs +++ b/core/engine/src/builtins/temporal/plain_date_time/mod.rs @@ -31,7 +31,7 @@ use temporal_rs::{ options::ArithmeticOverflow, }; -use super::to_temporal_duration_record; +use super::{to_temporal_duration_record, PlainDate, ZonedDateTime}; /// The `Temporal.PlainDateTime` object. #[derive(Debug, Clone, Trace, Finalize, JsData)] @@ -271,6 +271,7 @@ impl IntrinsicObject for PlainDateTime { None, Attribute::CONFIGURABLE, ) + .static_method(Self::from, js_string!("from"), 2) .method(Self::add, js_string!("add"), 2) .method(Self::subtract, js_string!("subtract"), 2) .build(); @@ -363,7 +364,7 @@ impl PlainDateTime { JsNativeError::typ().with_message("the this object must be a PlainDateTime object.") })?; - Ok(JsString::from(dt.inner.calendar().identifier()?).into()) + Ok(JsString::from(dt.inner.calendar().identifier()).into()) } /// 5.3.4 get `Temporal.PlainDatedt.prototype.year` @@ -621,7 +622,33 @@ impl PlainDateTime { } } -// ==== PlainDateTime method implementations ==== +// ==== PlainDateTime method implemenations ==== + +impl PlainDateTime { + fn from(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + let item = args.get_or_undefined(0); + // 1. Set options to ? GetOptionsObject(options). + let options = args.get(1); + // 2. If item is an Object and item has an [[InitializedTemporalDateTime]] internal slot, then + let dt = if let Some(pdt) = item.as_object().and_then(JsObject::downcast_ref::) { + // a. Perform ? GetTemporalOverflowOption(options). + let options = get_options_object(args.get_or_undefined(1))?; + let _ = get_option::(&options, js_str!("overflow"), context)?; + // b. Return ! CreateTemporalDateTime(item.[[ISOYear]], item.[[ISOMonth]], + // item.[[ISODay]], item.[[ISOHour]], item.[[ISOMinute]], item.[[ISOSecond]], + // item.[[ISOMillisecond]], item.[[ISOMicrosecond]], item.[[ISONanosecond]], + // item.[[Calendar]]). + pdt.inner.clone() + } else { + to_temporal_datetime(item, options.cloned(), context)? + }; + + // 3. Return ? ToTemporalDateTime(item, options). + create_temporal_datetime(dt, None, context).map(Into::into) + } +} + +// ==== PlainDateTime.prototype method implementations ==== impl PlainDateTime { fn add(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { @@ -669,6 +696,30 @@ impl PlainDateTime { create_temporal_datetime(dt.inner.subtract(&duration, overflow)?, None, context) .map(Into::into) } + + fn equals(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let dateTime be the this value. + // 2. Perform ? RequireInternalSlot(dateTime, [[InitializedTemporalDateTime]]). + let dt = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a PlainDateTime object.") + })?; + + // 3. Set other to ? ToTemporalDateTime(other). + let other = to_temporal_datetime(args.get_or_undefined(0), None, context)?; + + // 4. Let result be CompareISODateTime(dateTime.[[ISOYear]], dateTime.[[ISOMonth]], + // dateTime.[[ISODay]], dateTime.[[ISOHour]], dateTime.[[ISOMinute]], + // dateTime.[[ISOSecond]], dateTime.[[ISOMillisecond]], dateTime.[[ISOMicrosecond]], + // dateTime.[[ISONanosecond]], other.[[ISOYear]], other.[[ISOMonth]], other.[[ISODay]], + // other.[[ISOHour]], other.[[ISOMinute]], other.[[ISOSecond]], other.[[ISOMillisecond]], + // other.[[ISOMicrosecond]], other.[[ISONanosecond]]). + // 5. If result is not 0, return false. + // 6. Return ? CalendarEquals(dateTime.[[Calendar]], other.[[Calendar]]). + Ok((dt.inner == other).into()) + } } // ==== `PlainDateTime` Abstract Operations` ==== @@ -719,3 +770,80 @@ pub(crate) fn create_temporal_datetime( // 16. Return object. Ok(obj) } + +pub(crate) fn to_temporal_datetime( + value: &JsValue, + options: Option, + context: &mut Context, +) -> JsResult { + // 1. If options is not present, set options to undefined. + let options = get_options_object(&options.unwrap_or(JsValue::undefined()))?; + // 2. Let resolvedOptions be ? SnapshotOwnProperties(! GetOptionsObject(options), null). + // 3. If item is an Object, then + if let Some(object) = value.as_object() { + // a. If item has an [[InitializedTemporalDateTime]] internal slot, then + if let Some(dt) = object.downcast_ref::() { + // i. Return item. + return Ok(dt.inner.clone()); + // b. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then + } else if let Some(_zdt) = object.downcast_ref::() { + // i. Perform ? GetTemporalOverflowOption(resolvedOptions). + let _ = get_option::(&options, js_str!("overflow"), context)?; + // ii. Let instant be ! CreateTemporalInstant(item.[[Nanoseconds]]). + // iii. Let timeZoneRec be ? CreateTimeZoneMethodsRecord(item.[[TimeZone]], « get-offset-nanoseconds-for »). + // iv. Return ? GetPlainDateTimeFor(timeZoneRec, instant, item.[[Calendar]]). + return Err(JsNativeError::range() + .with_message("Not yet implemented.") + .into()); + // c. If item has an [[InitializedTemporalDate]] internal slot, then + } else if let Some(date) = object.downcast_ref::() { + // i. Perform ? GetTemporalOverflowOption(resolvedOptions). + let _ = get_option::(&options, js_str!("overflow"), context)?; + // ii. Return ? CreateTemporalDateTime(item.[[ISOYear]], item.[[ISOMonth]], item.[[ISODay]], 0, 0, 0, 0, 0, 0, item.[[Calendar]]). + return Ok(InnerDateTime::new( + date.inner.iso_year(), + date.inner.iso_month().into(), + date.inner.iso_day().into(), + 0, + 0, + 0, + 0, + 0, + 0, + date.inner.calendar().clone(), + )?); + } + // d. Let calendar be ? GetTemporalCalendarSlotValueWithISODefault(item). + // e. Let calendarRec be ? CreateCalendarMethodsRecord(calendar, « date-from-fields, fields »). + // f. Let fields be ? PrepareCalendarFields(calendarRec, item, « "day", "month", + // "monthCode", "year" », « "hour", "microsecond", "millisecond", "minute", + // "nanosecond", "second" », «»). + // g. Let result be ? InterpretTemporalDateTimeFields(calendarRec, fields, resolvedOptions). + // TODO: Implement d-g. + return Err(JsNativeError::range() + .with_message("Not yet implemented.") + .into()); + } + // 4. Else, + // a. If item is not a String, throw a TypeError exception. + let Some(string) = value.as_string() else { + return Err(JsNativeError::typ() + .with_message("Cannot convert unrecognized value to PlainDateTime.") + .into()); + }; + // b. Let result be ? ParseTemporalDateTimeString(item). + // c. Assert: IsValidISODate(result.[[Year]], result.[[Month]], result.[[Day]]) is true. + // d. Assert: IsValidTime(result.[[Hour]], result.[[Minute]], result.[[Second]], + // result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]]) is true. + // e. Let calendar be result.[[Calendar]]. + // f. If calendar is empty, set calendar to "iso8601". + // g. If IsBuiltinCalendar(calendar) is false, throw a RangeError exception. + // h. Set calendar to CanonicalizeUValue("ca", calendar). + let date = string.to_std_string_escaped().parse::()?; + // i. Perform ? GetTemporalOverflowOption(resolvedOptions). + let _ = get_option::(&options, js_str!("overflow"), context)?; + // 5. Return ? CreateTemporalDateTime(result.[[Year]], result.[[Month]], result.[[Day]], + // result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], + // result.[[Microsecond]], result.[[Nanosecond]], calendar). + Ok(date) +} diff --git a/core/engine/src/builtins/temporal/plain_time/mod.rs b/core/engine/src/builtins/temporal/plain_time/mod.rs index adba3b53ed..8f184c0d1e 100644 --- a/core/engine/src/builtins/temporal/plain_time/mod.rs +++ b/core/engine/src/builtins/temporal/plain_time/mod.rs @@ -23,7 +23,7 @@ use temporal_rs::{ use super::{ options::{get_temporal_unit, TemporalUnitGroup}, - to_integer_with_truncation, to_temporal_duration_record, + to_integer_with_truncation, to_temporal_duration_record, PlainDateTime, ZonedDateTime, }; /// The `Temporal.PlainTime` object. @@ -75,7 +75,7 @@ impl IntrinsicObject for PlainTime { js_string!("hour"), Some(get_hour), None, - Attribute::default(), + Attribute::CONFIGURABLE, ) .accessor( js_string!("minute"), @@ -107,9 +107,11 @@ impl IntrinsicObject for PlainTime { None, Attribute::CONFIGURABLE, ) + .static_method(Self::from, js_string!("from"), 2) .method(Self::add, js_string!("add"), 1) .method(Self::subtract, js_string!("subtract"), 1) .method(Self::round, js_string!("round"), 1) + .method(Self::equals, js_string!("equals"), 1) .method(Self::get_iso_fields, js_string!("getISOFields"), 0) .method(Self::value_of, js_string!("valueOf"), 0) .build(); @@ -287,6 +289,36 @@ impl PlainTime { // ==== PlainTime method implementations ==== +impl PlainTime { + fn from(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + let item = args.get_or_undefined(0); + // 1. Set options to ? GetOptionsObject(options). + // 2. Let overflow be ? GetTemporalOverflowOption(options). + let overflow = get_option::( + &get_options_object(args.get_or_undefined(1))?, + js_str!("overflow"), + context, + )?; + // 3. If item is an Object and item has an [[InitializedTemporalTime]] internal slot, then + let time = if let Some(time) = item + .as_object() + .and_then(JsObject::downcast_ref::) + { + // a. Return ! CreateTemporalTime(item.[[ISOHour]], item.[[ISOMinute]], + // item.[[ISOSecond]], item.[[ISOMillisecond]], item.[[ISOMicrosecond]], + // item.[[ISONanosecond]]). + time.inner + } else { + to_temporal_time(item, overflow, context)? + }; + + // 4. Return ? ToTemporalTime(item, overflow). + create_temporal_time(time, None, context).map(Into::into) + } +} + +// ==== PlainTime.prototype method implementations ==== + impl PlainTime { /// 4.3.9 Temporal.PlainTime.prototype.add ( temporalDurationLike ) fn add(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { @@ -394,6 +426,29 @@ impl PlainTime { create_temporal_time(result, None, context).map(Into::into) } + /// 4.3.15 Temporal.PlainTime.prototype.equals ( other ) + fn equals(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let temporalTime be the this value. + // 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]). + let time = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a PlainTime object.") + })?; + + // 3. Set other to ? ToTemporalTime(other). + let other = to_temporal_time(args.get_or_undefined(0), None, context)?; + // 4. If temporalTime.[[ISOHour]] ≠ other.[[ISOHour]], return false. + // 5. If temporalTime.[[ISOMinute]] ≠ other.[[ISOMinute]], return false. + // 6. If temporalTime.[[ISOSecond]] ≠ other.[[ISOSecond]], return false. + // 7. If temporalTime.[[ISOMillisecond]] ≠ other.[[ISOMillisecond]], return false. + // 8. If temporalTime.[[ISOMicrosecond]] ≠ other.[[ISOMicrosecond]], return false. + // 9. If temporalTime.[[ISONanosecond]] ≠ other.[[ISONanosecond]], return false. + // 10. Return true. + Ok((time.inner == other).into()) + } + /// 4.3.18 Temporal.PlainTime.prototype.getISOFields ( ) fn get_iso_fields(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { // 1. Let temporalTime be the this value. @@ -484,3 +539,68 @@ pub(crate) fn create_temporal_time( // 10. Return object. Ok(obj) } + +pub(crate) fn to_temporal_time( + value: &JsValue, + _overflow: Option, + _context: &mut Context, +) -> JsResult