From bb9d6920ab478c547b3bc10dcf443e8bb04474e8 Mon Sep 17 00:00:00 2001 From: Kevin <46825870+nekevss@users.noreply.github.com> Date: Wed, 31 Jan 2024 01:30:07 -0500 Subject: [PATCH] Build out PlainTime builtin (#3621) --- .../src/builtins/temporal/calendar/mod.rs | 2 +- .../src/builtins/temporal/duration/mod.rs | 62 ++- .../src/builtins/temporal/instant/mod.rs | 59 +-- .../src/builtins/temporal/plain_date/mod.rs | 6 +- .../src/builtins/temporal/plain_time/mod.rs | 443 +++++++++++++++++- core/temporal/src/components/duration.rs | 12 + core/temporal/src/components/duration/date.rs | 27 ++ core/temporal/src/components/duration/time.rs | 37 ++ 8 files changed, 590 insertions(+), 58 deletions(-) diff --git a/core/engine/src/builtins/temporal/calendar/mod.rs b/core/engine/src/builtins/temporal/calendar/mod.rs index 6f4a7f3a1f..df034de8d8 100644 --- a/core/engine/src/builtins/temporal/calendar/mod.rs +++ b/core/engine/src/builtins/temporal/calendar/mod.rs @@ -431,7 +431,7 @@ impl Calendar { // 5. Set duration to ? ToTemporalDuration(duration). let duration_like = args.get_or_undefined(1); - let duration = temporal::duration::to_temporal_duration(duration_like)?; + let duration = temporal::duration::to_temporal_duration(duration_like, context)?; // 6. Set options to ? GetOptionsObject(options). let options = args.get_or_undefined(2); diff --git a/core/engine/src/builtins/temporal/duration/mod.rs b/core/engine/src/builtins/temporal/duration/mod.rs index dd4b986ce9..0b22282e0f 100644 --- a/core/engine/src/builtins/temporal/duration/mod.rs +++ b/core/engine/src/builtins/temporal/duration/mod.rs @@ -584,16 +584,15 @@ impl Duration { JsNativeError::typ().with_message("this value must be a Duration object.") })?; - let round_to = args.get_or_undefined(0); - let round_to = match round_to { + let round_to = match args.first() { // 3. If roundTo is undefined, then - JsValue::Undefined => { + None | Some(JsValue::Undefined) => { return Err(JsNativeError::typ() .with_message("roundTo cannot be undefined.") .into()) } // 4. If Type(roundTo) is String, then - JsValue::String(rt) => { + Some(JsValue::String(rt)) => { // a. Let paramString be roundTo. let param_string = rt.clone(); // b. Set roundTo to OrdinaryObjectCreate(null). @@ -607,7 +606,7 @@ impl Duration { new_round_to } // 5. Else, - _ => { + Some(round_to) => { // a. Set roundTo to ? GetOptionsObject(roundTo). get_options_object(round_to)? } @@ -876,7 +875,10 @@ impl Duration { // -- Duration Abstract Operations -- /// 7.5.8 `ToTemporalDuration ( item )` -pub(crate) fn to_temporal_duration(item: &JsValue) -> JsResult { +pub(crate) fn to_temporal_duration( + item: &JsValue, + context: &mut Context, +) -> JsResult { // 1a. If Type(item) is Object // 1b. and item has an [[InitializedTemporalDuration]] internal slot, then if let Some(duration) = item @@ -887,18 +889,56 @@ pub(crate) fn to_temporal_duration(item: &JsValue) -> JsResult { } // 2. Let result be ? ToTemporalDurationRecord(item). - let result = to_temporal_duration_record(item)?; + let result = to_temporal_duration_record(item, context)?; // 3. Return ! CreateTemporalDuration(result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], result.[[Hours]], result.[[Minutes]], result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]). Ok(result) } /// 7.5.9 `ToTemporalDurationRecord ( temporalDurationLike )` pub(crate) fn to_temporal_duration_record( - _temporal_duration_like: &JsValue, + temporal_duration_like: &JsValue, + context: &mut Context, ) -> JsResult { - Err(JsNativeError::range() - .with_message("Duration Parsing is not yet complete.") - .into()) + // 1. If Type(temporalDurationLike) is not Object, then + let JsValue::Object(duration_obj) = temporal_duration_like else { + // a. If temporalDurationLike is not a String, throw a TypeError exception. + let JsValue::String(duration_string) = temporal_duration_like else { + return Err(JsNativeError::typ() + .with_message("Invalid TemporalDurationLike value.") + .into()); + }; + + // b. Return ? ParseTemporalDurationString(temporalDurationLike). + return duration_string + .to_std_string_escaped() + .parse::() + .map_err(Into::into); + }; + + // 2. If temporalDurationLike has an [[InitializedTemporalDuration]] internal slot, then + if let Some(duration) = duration_obj.downcast_ref::() { + // a. Return ! CreateDurationRecord(temporalDurationLike.[[Years]], temporalDurationLike.[[Months]], temporalDurationLike.[[Weeks]], temporalDurationLike.[[Days]], temporalDurationLike.[[Hours]], temporalDurationLike.[[Minutes]], temporalDurationLike.[[Seconds]], temporalDurationLike.[[Milliseconds]], temporalDurationLike.[[Microseconds]], temporalDurationLike.[[Nanoseconds]]). + return Ok(duration.inner); + } + + // 3. Let result be a new Duration Record with each field set to 0. + // 4. Let partial be ? ToTemporalPartialDurationRecord(temporalDurationLike). + let partial = to_temporal_partial_duration(temporal_duration_like, context)?; + + // 5. If partial.[[Years]] is not undefined, set result.[[Years]] to partial.[[Years]]. + // 6. If partial.[[Months]] is not undefined, set result.[[Months]] to partial.[[Months]]. + // 7. If partial.[[Weeks]] is not undefined, set result.[[Weeks]] to partial.[[Weeks]]. + // 8. If partial.[[Days]] is not undefined, set result.[[Days]] to partial.[[Days]]. + // 9. If partial.[[Hours]] is not undefined, set result.[[Hours]] to partial.[[Hours]]. + // 10. If partial.[[Minutes]] is not undefined, set result.[[Minutes]] to partial.[[Minutes]]. + // 11. If partial.[[Seconds]] is not undefined, set result.[[Seconds]] to partial.[[Seconds]]. + // 12. If partial.[[Milliseconds]] is not undefined, set result.[[Milliseconds]] to partial.[[Milliseconds]]. + // 13. If partial.[[Microseconds]] is not undefined, set result.[[Microseconds]] to partial.[[Microseconds]]. + // 14. If partial.[[Nanoseconds]] is not undefined, set result.[[Nanoseconds]] to partial.[[Nanoseconds]]. + // 15. If ! IsValidDuration(result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], result.[[Hours]], result.[[Minutes]], result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]) is false, then + // a. Throw a RangeError exception. + // 16. Return result. + InnerDuration::from_partial(&partial).map_err(Into::into) } /// 7.5.14 `CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds [ , newTarget ] )` diff --git a/core/engine/src/builtins/temporal/instant/mod.rs b/core/engine/src/builtins/temporal/instant/mod.rs index d1375f8031..777c8f53a6 100644 --- a/core/engine/src/builtins/temporal/instant/mod.rs +++ b/core/engine/src/builtins/temporal/instant/mod.rs @@ -237,7 +237,8 @@ impl Instant { })?; // 3. Return ? AddDurationToOrSubtractDurationFromInstant(add, instant, temporalDurationLike). - let temporal_duration_like = to_temporal_duration_record(args.get_or_undefined(0))?; + let temporal_duration_like = + to_temporal_duration_record(args.get_or_undefined(0), context)?; let result = instant.inner.add(temporal_duration_like)?; create_temporal_instant(result, None, context) } @@ -258,7 +259,8 @@ impl Instant { })?; // 3. Return ? AddDurationToOrSubtractDurationFromInstant(subtract, instant, temporalDurationLike). - let temporal_duration_like = to_temporal_duration_record(args.get_or_undefined(0))?; + let temporal_duration_like = + to_temporal_duration_record(args.get_or_undefined(0), context)?; let result = instant.inner.subtract(temporal_duration_like)?; create_temporal_instant(result, None, context) } @@ -336,33 +338,32 @@ impl Instant { JsNativeError::typ().with_message("the this object must be an instant object.") })?; - let round_to = args.get_or_undefined(0); - // 3. If roundTo is undefined, then - if round_to.is_undefined() { - // a. Throw a TypeError exception. - return Err(JsNativeError::typ() - .with_message("roundTo cannot be undefined.") - .into()); - }; - // 4. If Type(roundTo) is String, then - let round_to = if round_to.is_string() { - // a. Let paramString be roundTo. - let param_string = round_to - .as_string() - .expect("roundTo is confirmed to be a string here."); - // b. Set roundTo to OrdinaryObjectCreate(null). - let new_round_to = JsObject::with_null_proto(); - // c. Perform ! CreateDataPropertyOrThrow(roundTo, "smallestUnit"), paramString). - new_round_to.create_data_property_or_throw( - utf16!("smallestUnit"), - param_string.clone(), - context, - )?; - new_round_to - // 5. Else, - } else { - // a. Set roundTo to ? GetOptionsObject(roundTo). - get_options_object(round_to)? + let round_to = match args.first() { + // 3. If roundTo is undefined, then + None | Some(JsValue::Undefined) => { + return Err(JsNativeError::typ() + .with_message("roundTo cannot be undefined.") + .into()) + } + // 4. If Type(roundTo) is String, then + Some(JsValue::String(rt)) => { + // a. Let paramString be roundTo. + let param_string = rt.clone(); + // b. Set roundTo to OrdinaryObjectCreate(null). + let new_round_to = JsObject::with_null_proto(); + // c. Perform ! CreateDataPropertyOrThrow(roundTo, "smallestUnit", paramString). + new_round_to.create_data_property_or_throw( + utf16!("smallestUnit"), + param_string, + context, + )?; + new_round_to + } + // 5. Else, + Some(round_to) => { + // a. Set roundTo to ? GetOptionsObject(roundTo). + get_options_object(round_to)? + } }; // 6. NOTE: The following steps read options and perform independent validation in diff --git a/core/engine/src/builtins/temporal/plain_date/mod.rs b/core/engine/src/builtins/temporal/plain_date/mod.rs index 863a469c74..9fa62b5c0f 100644 --- a/core/engine/src/builtins/temporal/plain_date/mod.rs +++ b/core/engine/src/builtins/temporal/plain_date/mod.rs @@ -505,12 +505,8 @@ pub(crate) fn create_temporal_date( new_target: Option<&JsValue>, context: &mut Context, ) -> JsResult { + // NOTE (nekevss): The below should never trigger as `IsValidISODate` is enforced by Date. // 1. If IsValidISODate(isoYear, isoMonth, isoDay) is false, throw a RangeError exception. - if !inner.is_valid() { - return Err(JsNativeError::range() - .with_message("Date is not a valid ISO date.") - .into()); - }; // 2. If ISODateTimeWithinLimits(isoYear, isoMonth, isoDay, 12, 0, 0, 0, 0, 0) is false, throw a RangeError exception. if !DateTime::::validate(&inner) { diff --git a/core/engine/src/builtins/temporal/plain_time/mod.rs b/core/engine/src/builtins/temporal/plain_time/mod.rs index 895365bede..781db45587 100644 --- a/core/engine/src/builtins/temporal/plain_time/mod.rs +++ b/core/engine/src/builtins/temporal/plain_time/mod.rs @@ -1,25 +1,37 @@ //! Boa's implementation of the ECMAScript `Temporal.PlainTime` builtin object. -#![allow(dead_code, unused_variables)] use crate::{ - builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, + builtins::{ + options::{get_option, get_options_object}, + BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, + }, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, + js_string, + object::internal_methods::get_prototype_from_constructor, property::Attribute, realm::Realm, string::common::StaticJsStrings, - Context, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, + Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; +use boa_gc::{Finalize, Trace}; +use boa_macros::utf16; use boa_profiler::Profiler; +use boa_temporal::{ + components::Time, + options::{ArithmeticOverflow, TemporalRoundingMode}, +}; + +use super::{ + options::{get_temporal_unit, TemporalUnitGroup}, + to_integer_with_truncation, to_temporal_duration_record, +}; /// The `Temporal.PlainTime` object. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Trace, Finalize, JsData)] +// Safety: Time does not contain any traceable types. +#[boa_gc(unsafe_empty_trace)] pub struct PlainTime { - iso_hour: i32, // integer between 0-23 - iso_minute: i32, // integer between 0-59 - iso_second: i32, // integer between 0-59 - iso_millisecond: i32, // integer between 0-999 - iso_microsecond: i32, // integer between 0-999 - iso_nanosecond: i32, // integer between 0-999 + inner: Time, } impl BuiltInObject for PlainTime { @@ -29,6 +41,29 @@ impl BuiltInObject for PlainTime { impl IntrinsicObject for PlainTime { fn init(realm: &Realm) { let _timer = Profiler::global().start_event(std::any::type_name::(), "init"); + let get_hour = BuiltInBuilder::callable(realm, Self::get_hour) + .name(js_string!("get hour")) + .build(); + + let get_minute = BuiltInBuilder::callable(realm, Self::get_minute) + .name(js_string!("get minute")) + .build(); + + let get_second = BuiltInBuilder::callable(realm, Self::get_second) + .name(js_string!("get second")) + .build(); + + let get_millisecond = BuiltInBuilder::callable(realm, Self::get_millisecond) + .name(js_string!("get millisecond")) + .build(); + + let get_microsecond = BuiltInBuilder::callable(realm, Self::get_microsecond) + .name(js_string!("get microsecond")) + .build(); + + let get_nanosecond = BuiltInBuilder::callable(realm, Self::get_nanosecond) + .name(js_string!("get nanosecond")) + .build(); BuiltInBuilder::from_standard_constructor::(realm) .static_property( @@ -36,6 +71,42 @@ impl IntrinsicObject for PlainTime { Self::NAME, Attribute::CONFIGURABLE, ) + .accessor(utf16!("hour"), Some(get_hour), None, Attribute::default()) + .accessor( + utf16!("minute"), + Some(get_minute), + None, + Attribute::default(), + ) + .accessor( + utf16!("second"), + Some(get_second), + None, + Attribute::default(), + ) + .accessor( + utf16!("millisecond"), + Some(get_millisecond), + None, + Attribute::default(), + ) + .accessor( + utf16!("microsecond"), + Some(get_microsecond), + None, + Attribute::default(), + ) + .accessor( + utf16!("nanosecond"), + Some(get_nanosecond), + None, + Attribute::default(), + ) + .method(Self::add, js_string!("add"), 1) + .method(Self::subtract, js_string!("subtract"), 1) + .method(Self::round, js_string!("round"), 1) + .method(Self::get_iso_fields, js_string!("getISOFields"), 0) + .method(Self::value_of, js_string!("valueOf"), 0) .build(); } @@ -55,8 +126,356 @@ impl BuiltInConstructor for PlainTime { args: &[JsValue], context: &mut Context, ) -> JsResult { - Err(JsNativeError::range() - .with_message("Not yet implemented.") + // 1. If NewTarget is undefined, then + if new_target.is_undefined() { + // a. Throw a TypeError exception. + return Err(JsNativeError::typ() + .with_message("NewTarget cannot be undefined.") + .into()); + } + + // 2. If hour is undefined, set hour to 0; else set hour to ? ToIntegerWithTruncation(hour). + let hour = args + .first() + .map(|v| to_integer_with_truncation(v, context)) + .transpose()? + .unwrap_or(0); + // 3. If minute is undefined, set minute to 0; else set minute to ? ToIntegerWithTruncation(minute). + let minute = args + .get(1) + .map(|v| to_integer_with_truncation(v, context)) + .transpose()? + .unwrap_or(0); + // 4. If second is undefined, set second to 0; else set second to ? ToIntegerWithTruncation(second). + let second = args + .get(2) + .map(|v| to_integer_with_truncation(v, context)) + .transpose()? + .unwrap_or(0); + // 5. If millisecond is undefined, set millisecond to 0; else set millisecond to ? ToIntegerWithTruncation(millisecond). + let millisecond = args + .get(3) + .map(|v| to_integer_with_truncation(v, context)) + .transpose()? + .unwrap_or(0); + // 6. If microsecond is undefined, set microsecond to 0; else set microsecond to ? ToIntegerWithTruncation(microsecond). + let microsecond = args + .get(4) + .map(|v| to_integer_with_truncation(v, context)) + .transpose()? + .unwrap_or(0); + // 7. If nanosecond is undefined, set nanosecond to 0; else set nanosecond to ? ToIntegerWithTruncation(nanosecond). + let nanosecond = args + .get(5) + .map(|v| to_integer_with_truncation(v, context)) + .transpose()? + .unwrap_or(0); + + let inner = Time::new( + hour, + minute, + second, + millisecond, + microsecond, + nanosecond, + ArithmeticOverflow::Reject, + )?; + + // 8. Return ? CreateTemporalTime(hour, minute, second, millisecond, microsecond, nanosecond, NewTarget). + create_temporal_time(inner, Some(new_target), context).map(Into::into) + } +} + +// ==== PlainTime Accessor methods ==== + +impl PlainTime { + /// 4.3.3 get `Temporal.PlainTime.prototype.hour` + fn get_hour(this: &JsValue, _: &[JsValue], _: &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. Return ๐”ฝ(temporalTime.[[ISOHour]]). + Ok(time.inner.hour().into()) + } + + /// 4.3.4 get `Temporal.PlainTime.prototype.minute` + fn get_minute(this: &JsValue, _: &[JsValue], _: &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. Return ๐”ฝ(temporalTime.[[ISOMinute]]). + Ok(time.inner.minute().into()) + } + + /// 4.3.5 get `Temporal.PlainTime.prototype.second` + fn get_second(this: &JsValue, _: &[JsValue], _: &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. Return ๐”ฝ(temporalTime.[[ISOSecond]]). + Ok(time.inner.second().into()) + } + + /// 4.3.6 get `Temporal.PlainTime.prototype.millisecond` + fn get_millisecond(this: &JsValue, _: &[JsValue], _: &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. Return ๐”ฝ(temporalTime.[[ISOMillisecond]]). + Ok(time.inner.millisecond().into()) + } + + /// 4.3.7 get `Temporal.PlainTime.prototype.microsecond` + fn get_microsecond(this: &JsValue, _: &[JsValue], _: &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. Return ๐”ฝ(temporalTime.[[ISOMicrosecond]]). + Ok(time.inner.microsecond().into()) + } + + /// 4.3.8 get `Temporal.PlainTime.prototype.nanosecond` + fn get_nanosecond(this: &JsValue, _: &[JsValue], _: &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. Return ๐”ฝ(temporalTime.[[ISONanosecond]]). + Ok(time.inner.nanosecond().into()) + } +} + +// ==== PlainTime method implementations ==== + +impl PlainTime { + /// 4.3.9 Temporal.PlainTime.prototype.add ( temporalDurationLike ) + fn add(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.") + })?; + + let temporal_duration_like = args.get_or_undefined(0); + let duration = to_temporal_duration_record(temporal_duration_like, context)?; + + // 3. Return ? AddDurationToOrSubtractDurationFromPlainTime(add, temporalTime, temporalDurationLike). + create_temporal_time(time.inner.add(&duration)?, None, context).map(Into::into) + } + + /// 4.3.10 Temporal.PlainTime.prototype.subtract ( temporalDurationLike ) + fn subtract(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.") + })?; + + let temporal_duration_like = args.get_or_undefined(0); + let duration = to_temporal_duration_record(temporal_duration_like, context)?; + + // 3. Return ? AddDurationToOrSubtractDurationFromPlainTime(subtract, temporalTime, temporalDurationLike). + create_temporal_time(time.inner.subtract(&duration)?, None, context).map(Into::into) + } + + /// 4.3.14 Temporal.PlainTime.prototype.round ( roundTo ) + fn round(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.") + })?; + + let round_to = match args.first() { + // 3. If roundTo is undefined, then + None | Some(JsValue::Undefined) => { + return Err(JsNativeError::typ() + .with_message("roundTo cannot be undefined.") + .into()) + } + // 4. If Type(roundTo) is String, then + Some(JsValue::String(rt)) => { + // a. Let paramString be roundTo. + let param_string = rt.clone(); + // b. Set roundTo to OrdinaryObjectCreate(null). + let new_round_to = JsObject::with_null_proto(); + // c. Perform ! CreateDataPropertyOrThrow(roundTo, "smallestUnit", paramString). + new_round_to.create_data_property_or_throw( + utf16!("smallestUnit"), + param_string, + context, + )?; + new_round_to + } + // 5. Else, + Some(round_to) => { + // a. Set roundTo to ? GetOptionsObject(roundTo). + get_options_object(round_to)? + } + }; + + // 6. NOTE: The following steps read options and perform independent validation in alphabetical order (ToTemporalRoundingIncrement reads "roundingIncrement" and ToTemporalRoundingMode reads "roundingMode"). + // 7. Let roundingIncrement be ? ToTemporalRoundingIncrement(roundTo). + let rounding_increment = + get_option::(&round_to, utf16!("roundingIncrement"), context)?; + + // 8. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand"). + let rounding_mode = + get_option::(&round_to, utf16!("roundingMode"), context)?; + + // 9. Let smallestUnit be ? GetTemporalUnit(roundTo, "smallestUnit", time, required). + let smallest_unit = get_temporal_unit( + &round_to, + utf16!("smallestUnit"), + TemporalUnitGroup::Time, + None, + context, + )? + .ok_or_else(|| JsNativeError::range().with_message("smallestUnit cannot be undefined."))?; + + // 10. Let maximum be MaximumTemporalDurationRoundingIncrement(smallestUnit). + // 11. Assert: maximum is not undefined. + // 12. Perform ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false). + // 13. Let result be RoundTime(temporalTime.[[ISOHour]], temporalTime.[[ISOMinute]], temporalTime.[[ISOSecond]], temporalTime.[[ISOMillisecond]], temporalTime.[[ISOMicrosecond]], temporalTime.[[ISONanosecond]], roundingIncrement, smallestUnit, roundingMode). + let result = time + .inner + .round(smallest_unit, rounding_increment, rounding_mode)?; + + // 14. Return ! CreateTemporalTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]]). + create_temporal_time(result, None, context).map(Into::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. + // 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. Let fields be OrdinaryObjectCreate(%Object.prototype%). + let fields = JsObject::with_object_proto(context.intrinsics()); + + // 4. Perform ! CreateDataPropertyOrThrow(fields, "isoHour", ๐”ฝ(temporalTime.[[ISOHour]])). + fields.create_data_property_or_throw(utf16!("isoHour"), time.inner.hour(), context)?; + // 5. Perform ! CreateDataPropertyOrThrow(fields, "isoMicrosecond", ๐”ฝ(temporalTime.[[ISOMicrosecond]])). + fields.create_data_property_or_throw( + utf16!("isoMicrosecond"), + time.inner.microsecond(), + context, + )?; + // 6. Perform ! CreateDataPropertyOrThrow(fields, "isoMillisecond", ๐”ฝ(temporalTime.[[ISOMillisecond]])). + fields.create_data_property_or_throw( + utf16!("isoMillisecond"), + time.inner.millisecond(), + context, + )?; + // 7. Perform ! CreateDataPropertyOrThrow(fields, "isoMinute", ๐”ฝ(temporalTime.[[ISOMinute]])). + fields.create_data_property_or_throw(utf16!("isoMinute"), time.inner.minute(), context)?; + // 8. Perform ! CreateDataPropertyOrThrow(fields, "isoNanosecond", ๐”ฝ(temporalTime.[[ISONanosecond]])). + fields.create_data_property_or_throw( + utf16!("isoNanosecond"), + time.inner.nanosecond(), + context, + )?; + // 9. Perform ! CreateDataPropertyOrThrow(fields, "isoSecond", ๐”ฝ(temporalTime.[[ISOSecond]])). + fields.create_data_property_or_throw(utf16!("isoSecond"), time.inner.second(), context)?; + + // 10. Return fields. + Ok(fields.into()) + } + + /// 4.3.22 Temporal.PlainTime.prototype.valueOf ( ) + fn value_of(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { + // 1. Throw a TypeError exception. + Err(JsNativeError::typ() + .with_message("valueOf cannot be called on PlainTime.") .into()) } } + +// ==== PlainTime Abstract Operations ==== + +pub(crate) fn create_temporal_time( + inner: Time, + new_target: Option<&JsValue>, + context: &mut Context, +) -> JsResult { + // Note: IsValidTime is enforced by Time. + // 1. If IsValidTime(hour, minute, second, millisecond, microsecond, nanosecond) is false, throw a RangeError exception. + + // 2. If newTarget is not present, set newTarget to %Temporal.PlainTime%. + let new_target = if let Some(new_target) = new_target { + new_target.clone() + } else { + context + .realm() + .intrinsics() + .constructors() + .plain_time() + .constructor() + .into() + }; + + // 3. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.PlainTime.prototype%", ยซ [[InitializedTemporalTime]], [[ISOHour]], [[ISOMinute]], [[ISOSecond]], [[ISOMillisecond]], [[ISOMicrosecond]], [[ISONanosecond]] ยป). + let prototype = + get_prototype_from_constructor(&new_target, StandardConstructors::plain_time, context)?; + + // 4. Set object.[[ISOHour]] to hour. + // 5. Set object.[[ISOMinute]] to minute. + // 6. Set object.[[ISOSecond]] to second. + // 7. Set object.[[ISOMillisecond]] to millisecond. + // 8. Set object.[[ISOMicrosecond]] to microsecond. + // 9. Set object.[[ISONanosecond]] to nanosecond. + let obj = JsObject::from_proto_and_data(prototype, PlainTime { inner }); + + // 10. Return object. + Ok(obj) +} diff --git a/core/temporal/src/components/duration.rs b/core/temporal/src/components/duration.rs index 74193c5b2c..8486064bcf 100644 --- a/core/temporal/src/components/duration.rs +++ b/core/temporal/src/components/duration.rs @@ -126,6 +126,18 @@ impl Duration { } } + /// Creates a new valid `Duration` from a partial `Duration`. + pub fn from_partial(partial: &Duration) -> TemporalResult { + let duration = Self { + date: DateDuration::from_partial(partial.date()), + time: TimeDuration::from_partial(partial.time()), + }; + if !is_valid_duration(&duration.into_iter().collect()) { + return Err(TemporalError::range().with_message("Duration was not valid.")); + } + Ok(duration) + } + /// Return if the Durations values are within their valid ranges. #[inline] #[must_use] diff --git a/core/temporal/src/components/duration/date.rs b/core/temporal/src/components/duration/date.rs index e044aacfc0..2fec74995d 100644 --- a/core/temporal/src/components/duration/date.rs +++ b/core/temporal/src/components/duration/date.rs @@ -60,6 +60,33 @@ impl DateDuration { } } + /// Creates a `DateDuration` from a provided partial `DateDuration`. + #[must_use] + pub fn from_partial(partial: &DateDuration) -> Self { + Self { + years: if partial.years.is_nan() { + 0.0 + } else { + partial.years + }, + months: if partial.months.is_nan() { + 0.0 + } else { + partial.months + }, + weeks: if partial.weeks.is_nan() { + 0.0 + } else { + partial.weeks + }, + days: if partial.days.is_nan() { + 0.0 + } else { + partial.days + }, + } + } + /// Returns a new `DateDuration` representing the absolute value of the current. #[inline] #[must_use] diff --git a/core/temporal/src/components/duration/time.rs b/core/temporal/src/components/duration/time.rs index eb5ee90b19..9553de4ccf 100644 --- a/core/temporal/src/components/duration/time.rs +++ b/core/temporal/src/components/duration/time.rs @@ -278,6 +278,43 @@ impl TimeDuration { } } + /// Creates a `TimeDuration` from a provided partial `TimeDuration`. + #[must_use] + pub fn from_partial(partial: &TimeDuration) -> Self { + Self { + hours: if partial.hours.is_nan() { + 0.0 + } else { + partial.hours + }, + minutes: if partial.minutes.is_nan() { + 0.0 + } else { + partial.minutes + }, + seconds: if partial.seconds.is_nan() { + 0.0 + } else { + partial.seconds + }, + milliseconds: if partial.milliseconds.is_nan() { + 0.0 + } else { + partial.milliseconds + }, + microseconds: if partial.microseconds.is_nan() { + 0.0 + } else { + partial.microseconds + }, + nanoseconds: if partial.nanoseconds.is_nan() { + 0.0 + } else { + partial.nanoseconds + }, + } + } + /// Returns a new `TimeDuration` representing the absolute value of the current. #[inline] #[must_use]