Browse Source

Build out PlainTime builtin (#3621)

pull/3637/head
Kevin 10 months ago committed by GitHub
parent
commit
bb9d6920ab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      core/engine/src/builtins/temporal/calendar/mod.rs
  2. 62
      core/engine/src/builtins/temporal/duration/mod.rs
  3. 29
      core/engine/src/builtins/temporal/instant/mod.rs
  4. 6
      core/engine/src/builtins/temporal/plain_date/mod.rs
  5. 443
      core/engine/src/builtins/temporal/plain_time/mod.rs
  6. 12
      core/temporal/src/components/duration.rs
  7. 27
      core/temporal/src/components/duration/date.rs
  8. 37
      core/temporal/src/components/duration/time.rs

2
core/engine/src/builtins/temporal/calendar/mod.rs

@ -431,7 +431,7 @@ impl Calendar {
// 5. Set duration to ? ToTemporalDuration(duration). // 5. Set duration to ? ToTemporalDuration(duration).
let duration_like = args.get_or_undefined(1); 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). // 6. Set options to ? GetOptionsObject(options).
let options = args.get_or_undefined(2); let options = args.get_or_undefined(2);

62
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.") JsNativeError::typ().with_message("this value must be a Duration object.")
})?; })?;
let round_to = args.get_or_undefined(0); let round_to = match args.first() {
let round_to = match round_to {
// 3. If roundTo is undefined, then // 3. If roundTo is undefined, then
JsValue::Undefined => { None | Some(JsValue::Undefined) => {
return Err(JsNativeError::typ() return Err(JsNativeError::typ()
.with_message("roundTo cannot be undefined.") .with_message("roundTo cannot be undefined.")
.into()) .into())
} }
// 4. If Type(roundTo) is String, then // 4. If Type(roundTo) is String, then
JsValue::String(rt) => { Some(JsValue::String(rt)) => {
// a. Let paramString be roundTo. // a. Let paramString be roundTo.
let param_string = rt.clone(); let param_string = rt.clone();
// b. Set roundTo to OrdinaryObjectCreate(null). // b. Set roundTo to OrdinaryObjectCreate(null).
@ -607,7 +606,7 @@ impl Duration {
new_round_to new_round_to
} }
// 5. Else, // 5. Else,
_ => { Some(round_to) => {
// a. Set roundTo to ? GetOptionsObject(roundTo). // a. Set roundTo to ? GetOptionsObject(roundTo).
get_options_object(round_to)? get_options_object(round_to)?
} }
@ -876,7 +875,10 @@ impl Duration {
// -- Duration Abstract Operations -- // -- Duration Abstract Operations --
/// 7.5.8 `ToTemporalDuration ( item )` /// 7.5.8 `ToTemporalDuration ( item )`
pub(crate) fn to_temporal_duration(item: &JsValue) -> JsResult<InnerDuration> { pub(crate) fn to_temporal_duration(
item: &JsValue,
context: &mut Context,
) -> JsResult<InnerDuration> {
// 1a. If Type(item) is Object // 1a. If Type(item) is Object
// 1b. and item has an [[InitializedTemporalDuration]] internal slot, then // 1b. and item has an [[InitializedTemporalDuration]] internal slot, then
if let Some(duration) = item if let Some(duration) = item
@ -887,18 +889,56 @@ pub(crate) fn to_temporal_duration(item: &JsValue) -> JsResult<InnerDuration> {
} }
// 2. Let result be ? ToTemporalDurationRecord(item). // 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]]). // 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) Ok(result)
} }
/// 7.5.9 `ToTemporalDurationRecord ( temporalDurationLike )` /// 7.5.9 `ToTemporalDurationRecord ( temporalDurationLike )`
pub(crate) fn to_temporal_duration_record( pub(crate) fn to_temporal_duration_record(
_temporal_duration_like: &JsValue, temporal_duration_like: &JsValue,
context: &mut Context,
) -> JsResult<InnerDuration> { ) -> JsResult<InnerDuration> {
Err(JsNativeError::range() // 1. If Type(temporalDurationLike) is not Object, then
.with_message("Duration Parsing is not yet complete.") let JsValue::Object(duration_obj) = temporal_duration_like else {
.into()) // 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::<InnerDuration>()
.map_err(Into::into);
};
// 2. If temporalDurationLike has an [[InitializedTemporalDuration]] internal slot, then
if let Some(duration) = duration_obj.downcast_ref::<Duration>() {
// 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 ] )` /// 7.5.14 `CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds [ , newTarget ] )`

29
core/engine/src/builtins/temporal/instant/mod.rs

@ -237,7 +237,8 @@ impl Instant {
})?; })?;
// 3. Return ? AddDurationToOrSubtractDurationFromInstant(add, instant, temporalDurationLike). // 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)?; let result = instant.inner.add(temporal_duration_like)?;
create_temporal_instant(result, None, context) create_temporal_instant(result, None, context)
} }
@ -258,7 +259,8 @@ impl Instant {
})?; })?;
// 3. Return ? AddDurationToOrSubtractDurationFromInstant(subtract, instant, temporalDurationLike). // 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)?; let result = instant.inner.subtract(temporal_duration_like)?;
create_temporal_instant(result, None, context) create_temporal_instant(result, None, context)
} }
@ -336,33 +338,32 @@ impl Instant {
JsNativeError::typ().with_message("the this object must be an instant object.") JsNativeError::typ().with_message("the this object must be an instant object.")
})?; })?;
let round_to = args.get_or_undefined(0); let round_to = match args.first() {
// 3. If roundTo is undefined, then // 3. If roundTo is undefined, then
if round_to.is_undefined() { None | Some(JsValue::Undefined) => {
// a. Throw a TypeError exception.
return Err(JsNativeError::typ() return Err(JsNativeError::typ()
.with_message("roundTo cannot be undefined.") .with_message("roundTo cannot be undefined.")
.into()); .into())
}; }
// 4. If Type(roundTo) is String, then // 4. If Type(roundTo) is String, then
let round_to = if round_to.is_string() { Some(JsValue::String(rt)) => {
// a. Let paramString be roundTo. // a. Let paramString be roundTo.
let param_string = round_to let param_string = rt.clone();
.as_string()
.expect("roundTo is confirmed to be a string here.");
// b. Set roundTo to OrdinaryObjectCreate(null). // b. Set roundTo to OrdinaryObjectCreate(null).
let new_round_to = JsObject::with_null_proto(); let new_round_to = JsObject::with_null_proto();
// c. Perform ! CreateDataPropertyOrThrow(roundTo, "smallestUnit"), paramString). // c. Perform ! CreateDataPropertyOrThrow(roundTo, "smallestUnit", paramString).
new_round_to.create_data_property_or_throw( new_round_to.create_data_property_or_throw(
utf16!("smallestUnit"), utf16!("smallestUnit"),
param_string.clone(), param_string,
context, context,
)?; )?;
new_round_to new_round_to
}
// 5. Else, // 5. Else,
} else { Some(round_to) => {
// a. Set roundTo to ? GetOptionsObject(roundTo). // a. Set roundTo to ? GetOptionsObject(roundTo).
get_options_object(round_to)? get_options_object(round_to)?
}
}; };
// 6. NOTE: The following steps read options and perform independent validation in // 6. NOTE: The following steps read options and perform independent validation in

6
core/engine/src/builtins/temporal/plain_date/mod.rs

@ -505,12 +505,8 @@ pub(crate) fn create_temporal_date(
new_target: Option<&JsValue>, new_target: Option<&JsValue>,
context: &mut Context, context: &mut Context,
) -> JsResult<JsObject> { ) -> JsResult<JsObject> {
// 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. // 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. // 2. If ISODateTimeWithinLimits(isoYear, isoMonth, isoDay, 12, 0, 0, 0, 0, 0) is false, throw a RangeError exception.
if !DateTime::<JsCustomCalendar>::validate(&inner) { if !DateTime::<JsCustomCalendar>::validate(&inner) {

443
core/engine/src/builtins/temporal/plain_time/mod.rs

@ -1,25 +1,37 @@
//! Boa's implementation of the ECMAScript `Temporal.PlainTime` builtin object. //! Boa's implementation of the ECMAScript `Temporal.PlainTime` builtin object.
#![allow(dead_code, unused_variables)]
use crate::{ use crate::{
builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, builtins::{
options::{get_option, get_options_object},
BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject,
},
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
js_string,
object::internal_methods::get_prototype_from_constructor,
property::Attribute, property::Attribute,
realm::Realm, realm::Realm,
string::common::StaticJsStrings, 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_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. /// 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 { pub struct PlainTime {
iso_hour: i32, // integer between 0-23 inner: Time,
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
} }
impl BuiltInObject for PlainTime { impl BuiltInObject for PlainTime {
@ -29,6 +41,29 @@ impl BuiltInObject for PlainTime {
impl IntrinsicObject for PlainTime { impl IntrinsicObject for PlainTime {
fn init(realm: &Realm) { fn init(realm: &Realm) {
let _timer = Profiler::global().start_event(std::any::type_name::<Self>(), "init"); let _timer = Profiler::global().start_event(std::any::type_name::<Self>(), "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::<Self>(realm) BuiltInBuilder::from_standard_constructor::<Self>(realm)
.static_property( .static_property(
@ -36,6 +71,42 @@ impl IntrinsicObject for PlainTime {
Self::NAME, Self::NAME,
Attribute::CONFIGURABLE, 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(); .build();
} }
@ -55,8 +126,356 @@ impl BuiltInConstructor for PlainTime {
args: &[JsValue], args: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
Err(JsNativeError::range() // 1. If NewTarget is undefined, then
.with_message("Not yet implemented.") 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<JsValue> {
// 1. Let temporalTime be the this value.
// 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]).
let time = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.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<JsValue> {
// 1. Let temporalTime be the this value.
// 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]).
let time = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.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<JsValue> {
// 1. Let temporalTime be the this value.
// 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]).
let time = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.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<JsValue> {
// 1. Let temporalTime be the this value.
// 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]).
let time = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.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<JsValue> {
// 1. Let temporalTime be the this value.
// 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]).
let time = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.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<JsValue> {
// 1. Let temporalTime be the this value.
// 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]).
let time = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.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<JsValue> {
// 1. Let temporalTime be the this value.
// 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]).
let time = this
.as_object()
.and_then(JsObject::downcast_ref::<PlainTime>)
.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<JsValue> {
// 1. Let temporalTime be the this value.
// 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]).
let time = this
.as_object()
.and_then(JsObject::downcast_ref::<PlainTime>)
.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<JsValue> {
// 1. Let temporalTime be the this value.
// 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]).
let time = this
.as_object()
.and_then(JsObject::downcast_ref::<PlainTime>)
.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::<f64>(&round_to, utf16!("roundingIncrement"), context)?;
// 8. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand").
let rounding_mode =
get_option::<TemporalRoundingMode>(&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<JsValue> {
// 1. Let temporalTime be the this value.
// 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]).
let time = this
.as_object()
.and_then(JsObject::downcast_ref::<PlainTime>)
.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<JsValue> {
// 1. Throw a TypeError exception.
Err(JsNativeError::typ()
.with_message("valueOf cannot be called on PlainTime.")
.into()) .into())
} }
} }
// ==== PlainTime Abstract Operations ====
pub(crate) fn create_temporal_time(
inner: Time,
new_target: Option<&JsValue>,
context: &mut Context,
) -> JsResult<JsObject> {
// 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)
}

12
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<Self> {
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. /// Return if the Durations values are within their valid ranges.
#[inline] #[inline]
#[must_use] #[must_use]

27
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. /// Returns a new `DateDuration` representing the absolute value of the current.
#[inline] #[inline]
#[must_use] #[must_use]

37
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. /// Returns a new `TimeDuration` representing the absolute value of the current.
#[inline] #[inline]
#[must_use] #[must_use]

Loading…
Cancel
Save