Rust编写的JavaScript引擎,该项目是一个试验性质的项目。
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

999 lines
40 KiB

// Boa's implementation of the `Temporal.Duration` Builtin Object.
use crate::{
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, utf16},
Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue,
};
use boa_gc::{Finalize, Trace};
use boa_profiler::Profiler;
use temporal_rs::{
components::Duration as InnerDuration,
options::{RelativeTo, TemporalRoundingMode, TemporalUnit},
};
use super::{
options::{get_temporal_rounding_increment, get_temporal_unit, TemporalUnitGroup},
to_integer_if_integral, DateTimeValues,
};
#[cfg(test)]
mod tests;
/// The `Temporal.Duration` object.
///
/// Per [spec], `Duration` records are float64-representable integers
///
/// [spec]: https://tc39.es/proposal-temporal/#sec-properties-of-temporal-duration-instances
#[derive(Debug, Clone, Copy, Trace, Finalize, JsData)]
#[boa_gc(empty_trace)]
pub struct Duration {
pub(crate) inner: InnerDuration,
}
impl Duration {
pub(crate) fn new(inner: InnerDuration) -> Self {
Self { inner }
}
}
impl BuiltInObject for Duration {
const NAME: JsString = StaticJsStrings::DURATION;
}
impl IntrinsicObject for Duration {
fn init(realm: &Realm) {
let _timer = Profiler::global().start_event(std::any::type_name::<Self>(), "init");
let get_years = BuiltInBuilder::callable(realm, Self::get_years)
.name(js_string!("get Years"))
.build();
let get_months = BuiltInBuilder::callable(realm, Self::get_months)
.name(js_string!("get Months"))
.build();
let get_weeks = BuiltInBuilder::callable(realm, Self::get_weeks)
.name(js_string!("get Weeks"))
.build();
let get_days = BuiltInBuilder::callable(realm, Self::get_days)
.name(js_string!("get Days"))
.build();
let get_hours = BuiltInBuilder::callable(realm, Self::get_hours)
.name(js_string!("get Hours"))
.build();
let get_minutes = BuiltInBuilder::callable(realm, Self::get_minutes)
.name(js_string!("get Minutes"))
.build();
let get_seconds = BuiltInBuilder::callable(realm, Self::get_seconds)
.name(js_string!("get Seconds"))
.build();
let get_milliseconds = BuiltInBuilder::callable(realm, Self::get_milliseconds)
.name(js_string!("get Milliseconds"))
.build();
let get_microseconds = BuiltInBuilder::callable(realm, Self::get_microseconds)
.name(js_string!("get Microseconds"))
.build();
let get_nanoseconds = BuiltInBuilder::callable(realm, Self::get_nanoseconds)
.name(js_string!("get Nanoseconds"))
.build();
let get_sign = BuiltInBuilder::callable(realm, Self::get_sign)
.name(js_string!("get Sign"))
.build();
let is_blank = BuiltInBuilder::callable(realm, Self::get_blank)
.name(js_string!("get blank"))
.build();
BuiltInBuilder::from_standard_constructor::<Self>(realm)
.property(
JsSymbol::to_string_tag(),
Self::NAME,
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.accessor(
utf16!("years"),
Some(get_years),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("months"),
Some(get_months),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("weeks"),
Some(get_weeks),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("days"),
Some(get_days),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("hours"),
Some(get_hours),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("minutes"),
Some(get_minutes),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("seconds"),
Some(get_seconds),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("milliseconds"),
Some(get_milliseconds),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("microseconds"),
Some(get_microseconds),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("nanoseconds"),
Some(get_nanoseconds),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("sign"),
Some(get_sign),
None,
Attribute::CONFIGURABLE,
)
.accessor(
utf16!("blank"),
Some(is_blank),
None,
Attribute::CONFIGURABLE,
)
.method(Self::with, js_string!("with"), 1)
.method(Self::negated, js_string!("negated"), 0)
.method(Self::abs, js_string!("abs"), 0)
.method(Self::add, js_string!("add"), 2)
.method(Self::subtract, js_string!("subtract"), 2)
.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_json, js_string!("toJSON"), 0)
.build();
}
fn get(intrinsics: &Intrinsics) -> JsObject {
Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor()
}
}
impl BuiltInConstructor for Duration {
const LENGTH: usize = 10;
const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor =
StandardConstructors::duration;
fn constructor(
new_target: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 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 for Temporal.Duration constructor.")
.into());
}
// 2. If years is undefined, let y be 0; else let y be ? ToIntegerIfIntegral(years).
let years = f64::from(
args.first()
.map_or(Ok(0), |y| to_integer_if_integral(y, context))?,
);
// 3. If months is undefined, let mo be 0; else let mo be ? ToIntegerIfIntegral(months).
let months = f64::from(
args.get(1)
.map_or(Ok(0), |mo| to_integer_if_integral(mo, context))?,
);
// 4. If weeks is undefined, let w be 0; else let w be ? ToIntegerIfIntegral(weeks).
let weeks = f64::from(
args.get(2)
.map_or(Ok(0), |wk| to_integer_if_integral(wk, context))?,
);
// 5. If days is undefined, let d be 0; else let d be ? ToIntegerIfIntegral(days).
let days = f64::from(
args.get(3)
.map_or(Ok(0), |d| to_integer_if_integral(d, context))?,
);
// 6. If hours is undefined, let h be 0; else let h be ? ToIntegerIfIntegral(hours).
let hours = f64::from(
args.get(4)
.map_or(Ok(0), |h| to_integer_if_integral(h, context))?,
);
// 7. If minutes is undefined, let m be 0; else let m be ? ToIntegerIfIntegral(minutes).
let minutes = f64::from(
args.get(5)
.map_or(Ok(0), |m| to_integer_if_integral(m, context))?,
);
// 8. If seconds is undefined, let s be 0; else let s be ? ToIntegerIfIntegral(seconds).
let seconds = f64::from(
args.get(6)
.map_or(Ok(0), |s| to_integer_if_integral(s, context))?,
);
// 9. If milliseconds is undefined, let ms be 0; else let ms be ? ToIntegerIfIntegral(milliseconds).
let milliseconds = f64::from(
args.get(7)
.map_or(Ok(0), |ms| to_integer_if_integral(ms, context))?,
);
// 10. If microseconds is undefined, let mis be 0; else let mis be ? ToIntegerIfIntegral(microseconds).
let microseconds = f64::from(
args.get(8)
.map_or(Ok(0), |mis| to_integer_if_integral(mis, context))?,
);
// 11. If nanoseconds is undefined, let ns be 0; else let ns be ? ToIntegerIfIntegral(nanoseconds).
let nanoseconds = f64::from(
args.get(9)
.map_or(Ok(0), |ns| to_integer_if_integral(ns, context))?,
);
let record = InnerDuration::new(
years,
months,
weeks,
days,
hours,
minutes,
seconds,
milliseconds,
microseconds,
nanoseconds,
)?;
// 12. Return ? CreateTemporalDuration(y, mo, w, d, h, m, s, ms, mis, ns, NewTarget).
create_temporal_duration(record, Some(new_target), context).map(Into::into)
}
}
// -- Duration accessor property implementations --
impl Duration {
// Internal utility function for getting `Duration` field values.
fn get_internal_field(this: &JsValue, field: &DateTimeValues) -> JsResult<JsValue> {
let duration = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("this value must be a Duration object.")
})?;
let inner = &duration.inner;
match field {
DateTimeValues::Year => Ok(JsValue::Rational(inner.years())),
DateTimeValues::Month => Ok(JsValue::Rational(inner.months())),
DateTimeValues::Week => Ok(JsValue::Rational(inner.weeks())),
DateTimeValues::Day => Ok(JsValue::Rational(inner.days())),
DateTimeValues::Hour => Ok(JsValue::Rational(inner.hours())),
DateTimeValues::Minute => Ok(JsValue::Rational(inner.minutes())),
DateTimeValues::Second => Ok(JsValue::Rational(inner.seconds())),
DateTimeValues::Millisecond => Ok(JsValue::Rational(inner.milliseconds())),
DateTimeValues::Microsecond => Ok(JsValue::Rational(inner.microseconds())),
DateTimeValues::Nanosecond => Ok(JsValue::Rational(inner.nanoseconds())),
DateTimeValues::MonthCode => unreachable!(
"Any other DateTimeValue fields on Duration would be an implementation error."
),
}
}
/// 7.3.3 get Temporal.Duration.prototype.years
fn get_years(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Self::get_internal_field(this, &DateTimeValues::Year)
}
// 7.3.4 get Temporal.Duration.prototype.months
fn get_months(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Self::get_internal_field(this, &DateTimeValues::Month)
}
/// 7.3.5 get Temporal.Duration.prototype.weeks
fn get_weeks(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Self::get_internal_field(this, &DateTimeValues::Week)
}
/// 7.3.6 get Temporal.Duration.prototype.days
fn get_days(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Self::get_internal_field(this, &DateTimeValues::Day)
}
/// 7.3.7 get Temporal.Duration.prototype.hours
fn get_hours(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Self::get_internal_field(this, &DateTimeValues::Hour)
}
/// 7.3.8 get Temporal.Duration.prototype.minutes
fn get_minutes(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Self::get_internal_field(this, &DateTimeValues::Minute)
}
/// 7.3.9 get Temporal.Duration.prototype.seconds
fn get_seconds(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Self::get_internal_field(this, &DateTimeValues::Second)
}
/// 7.3.10 get Temporal.Duration.prototype.milliseconds
fn get_milliseconds(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Self::get_internal_field(this, &DateTimeValues::Millisecond)
}
/// 7.3.11 get Temporal.Duration.prototype.microseconds
fn get_microseconds(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Self::get_internal_field(this, &DateTimeValues::Microsecond)
}
/// 7.3.12 get Temporal.Duration.prototype.nanoseconds
fn get_nanoseconds(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Self::get_internal_field(this, &DateTimeValues::Nanosecond)
}
/// 7.3.13 get Temporal.Duration.prototype.sign
fn get_sign(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
// 1. Let duration be the this value.
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
let duration = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("this value must be a Duration object.")
})?;
// 3. Return 𝔽(! DurationSign(duration.[[Years]], duration.[[Months]], duration.[[Weeks]],
// duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]],
// duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]])).
Ok(duration.inner.sign().into())
}
/// 7.3.14 get Temporal.Duration.prototype.blank
fn get_blank(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
// 1. Let duration be the this value.
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
let duration = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("this value must be a Duration object.")
})?;
// 3. Let sign be ! DurationSign(duration.[[Years]], duration.[[Months]], duration.[[Weeks]],
// duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]],
// duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]]).
// 4. If sign = 0, return true.
// 5. Return false.
Ok(duration.inner.is_zero().into())
}
}
// -- Duration Method implementations --
impl Duration {
/// 7.3.15 `Temporal.Duration.prototype.with ( temporalDurationLike )`
pub(crate) fn with(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let duration be the this value.
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
let duration = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("this value must be a Duration object.")
})?;
// 3. Let temporalDurationLike be ? ToTemporalPartialDurationRecord(temporalDurationLike).
let temporal_duration_like =
to_temporal_partial_duration(args.get_or_undefined(0), context)?;
// 4. If temporalDurationLike.[[Years]] is not undefined, then
// a. Let years be temporalDurationLike.[[Years]].
// 5. Else,
// a. Let years be duration.[[Years]].
let years = if temporal_duration_like.years().is_nan() {
duration.inner.years()
} else {
temporal_duration_like.years()
};
// 6. If temporalDurationLike.[[Months]] is not undefined, then
// a. Let months be temporalDurationLike.[[Months]].
// 7. Else,
// a. Let months be duration.[[Months]].
let months = if temporal_duration_like.months().is_nan() {
duration.inner.months()
} else {
temporal_duration_like.months()
};
// 8. If temporalDurationLike.[[Weeks]] is not undefined, then
// a. Let weeks be temporalDurationLike.[[Weeks]].
// 9. Else,
// a. Let weeks be duration.[[Weeks]].
let weeks = if temporal_duration_like.weeks().is_nan() {
duration.inner.weeks()
} else {
temporal_duration_like.weeks()
};
// 10. If temporalDurationLike.[[Days]] is not undefined, then
// a. Let days be temporalDurationLike.[[Days]].
// 11. Else,
// a. Let days be duration.[[Days]].
let days = if temporal_duration_like.days().is_nan() {
duration.inner.days()
} else {
temporal_duration_like.days()
};
// 12. If temporalDurationLike.[[Hours]] is not undefined, then
// a. Let hours be temporalDurationLike.[[Hours]].
// 13. Else,
// a. Let hours be duration.[[Hours]].
let hours = if temporal_duration_like.hours().is_nan() {
duration.inner.hours()
} else {
temporal_duration_like.hours()
};
// 14. If temporalDurationLike.[[Minutes]] is not undefined, then
// a. Let minutes be temporalDurationLike.[[Minutes]].
// 15. Else,
// a. Let minutes be duration.[[Minutes]].
let minutes = if temporal_duration_like.minutes().is_nan() {
duration.inner.minutes()
} else {
temporal_duration_like.minutes()
};
// 16. If temporalDurationLike.[[Seconds]] is not undefined, then
// a. Let seconds be temporalDurationLike.[[Seconds]].
// 17. Else,
// a. Let seconds be duration.[[Seconds]].
let seconds = if temporal_duration_like.seconds().is_nan() {
duration.inner.seconds()
} else {
temporal_duration_like.seconds()
};
// 18. If temporalDurationLike.[[Milliseconds]] is not undefined, then
// a. Let milliseconds be temporalDurationLike.[[Milliseconds]].
// 19. Else,
// a. Let milliseconds be duration.[[Milliseconds]].
let milliseconds = if temporal_duration_like.milliseconds().is_nan() {
duration.inner.milliseconds()
} else {
temporal_duration_like.milliseconds()
};
// 20. If temporalDurationLike.[[Microseconds]] is not undefined, then
// a. Let microseconds be temporalDurationLike.[[Microseconds]].
// 21. Else,
// a. Let microseconds be duration.[[Microseconds]].
let microseconds = if temporal_duration_like.microseconds().is_nan() {
duration.inner.microseconds()
} else {
temporal_duration_like.microseconds()
};
// 22. If temporalDurationLike.[[Nanoseconds]] is not undefined, then
// a. Let nanoseconds be temporalDurationLike.[[Nanoseconds]].
// 23. Else,
// a. Let nanoseconds be duration.[[Nanoseconds]].
let nanoseconds = if temporal_duration_like.nanoseconds().is_nan() {
duration.inner.nanoseconds()
} else {
temporal_duration_like.nanoseconds()
};
// 24. Return ? CreateTemporalDuration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds).
let new_duration = InnerDuration::new(
years,
months,
weeks,
days,
hours,
minutes,
seconds,
milliseconds,
microseconds,
nanoseconds,
)?;
create_temporal_duration(new_duration, None, context).map(Into::into)
}
/// 7.3.16 `Temporal.Duration.prototype.negated ( )`
pub(crate) fn negated(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
// 1. Let duration be the this value.
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
// 3. Return ! CreateNegatedTemporalDuration(duration).
Err(JsNativeError::range()
.with_message("not yet implemented.")
.into())
}
/// 7.3.17 `Temporal.Duration.prototype.abs ( )`
pub(crate) fn abs(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Let duration be the this value.
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
// 3. Return ! CreateTemporalDuration(abs(duration.[[Years]]), abs(duration.[[Months]]),
// abs(duration.[[Weeks]]), abs(duration.[[Days]]), abs(duration.[[Hours]]), abs(duration.[[Minutes]]),
// abs(duration.[[Seconds]]), abs(duration.[[Milliseconds]]), abs(duration.[[Microseconds]]), abs(duration.[[Nanoseconds]])).
let duration = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("this value must be a Duration object.")
})?;
let abs = duration.inner.abs();
create_temporal_duration(abs, None, context).map(Into::into)
}
/// 7.3.18 `Temporal.Duration.prototype.add ( other [ , options ] )`
pub(crate) fn add(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Err(JsNativeError::range()
.with_message("not yet implemented.")
.into())
}
/// 7.3.19 `Temporal.Duration.prototype.subtract ( other [ , options ] )`
pub(crate) fn subtract(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Err(JsNativeError::range()
.with_message("not yet implemented.")
.into())
}
/// 7.3.20 `Temporal.Duration.prototype.round ( roundTo )`
pub(crate) fn round(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let duration be the this value.
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
let duration = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("this value must be a Duration 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)?
}
};
// NOTE: 6 & 7 unused in favor of `is_none()`.
// 6. Let smallestUnitPresent be true.
// 7. Let largestUnitPresent be true.
// 8. NOTE: The following steps read options and perform independent validation in alphabetical order (ToRelativeTemporalObject reads "relativeTo", ToTemporalRoundingIncrement reads "roundingIncrement" and ToTemporalRoundingMode reads "roundingMode").
// 9. Let largestUnit be ? GetTemporalUnit(roundTo, "largestUnit", datetime, undefined, « "auto" »).
let largest_unit = get_temporal_unit(
&round_to,
utf16!("largestUnit"),
TemporalUnitGroup::DateTime,
Some([TemporalUnit::Auto].into()),
context,
)?;
// 10. Let relativeToRecord be ? ToRelativeTemporalObject(roundTo).
// 11. Let zonedRelativeTo be relativeToRecord.[[ZonedRelativeTo]].
// 12. Let plainRelativeTo be relativeToRecord.[[PlainRelativeTo]].
let (plain_relative_to, zoned_relative_to) =
super::to_relative_temporal_object(&round_to, context)?;
// 13. Let roundingIncrement be ? ToTemporalRoundingIncrement(roundTo).
let rounding_increment = get_temporal_rounding_increment(&round_to, context)?;
// 14. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand").
let rounding_mode =
get_option::<TemporalRoundingMode>(&round_to, utf16!("roundingMode"), context)?;
// 15. Let smallestUnit be ? GetTemporalUnit(roundTo, "smallestUnit", datetime, undefined).
let smallest_unit = get_temporal_unit(
&round_to,
utf16!("smallestUnit"),
TemporalUnitGroup::DateTime,
None,
context,
)?;
// NOTE: execute step 21 earlier before initial values are shadowed.
// 21. If smallestUnitPresent is false and largestUnitPresent is false, then
if smallest_unit.is_none() && largest_unit.is_none() {
// a. Throw a RangeError exception.
return Err(JsNativeError::range()
.with_message("smallestUnit or largestUnit must be present.")
.into());
}
let rounded_duration = duration.inner.round(
rounding_increment,
smallest_unit,
largest_unit,
rounding_mode,
&RelativeTo {
date: plain_relative_to.as_ref(),
zdt: zoned_relative_to.as_ref(),
},
context,
)?;
create_temporal_duration(rounded_duration, None, context).map(Into::into)
}
/// 7.3.21 `Temporal.Duration.prototype.total ( totalOf )`
pub(crate) fn total(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let duration be the this value.
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
let _duration = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("this value must be a Duration object.")
})?;
let total_of = args.get_or_undefined(0);
let total_of = match total_of {
// 3. If totalOf is undefined, throw a TypeError exception.
JsValue::Undefined => {
return Err(JsNativeError::typ()
.with_message("totalOf cannot be undefined.")
.into());
}
// 4. If Type(totalOf) is String, then
JsValue::String(param_string) => {
// a. Let paramString be totalOf.
// b. Set totalOf to OrdinaryObjectCreate(null).
let total_of = JsObject::with_null_proto();
// c. Perform ! CreateDataPropertyOrThrow(totalOf, "unit", paramString).
total_of.create_data_property_or_throw(
utf16!("unit"),
param_string.clone(),
context,
)?;
total_of
}
// 5. Else,
_ => {
// a. Set totalOf to ? GetOptionsObject(totalOf).
get_options_object(total_of)?
}
};
// 6. NOTE: The following steps read options and perform independent validation in alphabetical order (ToRelativeTemporalObject reads "relativeTo").
// 7. Let relativeToRecord be ? ToRelativeTemporalObject(totalOf).
// 8. Let zonedRelativeTo be relativeToRecord.[[ZonedRelativeTo]].
// 9. Let plainRelativeTo be relativeToRecord.[[PlainRelativeTo]].
let (_plain_relative_to, _zoned_relative_to) =
super::to_relative_temporal_object(&total_of, context)?;
// 10. Let unit be ? GetTemporalUnit(totalOf, "unit", datetime, required).
let _unit = get_temporal_unit(
&total_of,
utf16!("unit"),
TemporalUnitGroup::DateTime,
None,
context,
)?
.ok_or_else(|| JsNativeError::range().with_message("unit cannot be undefined."))?;
// TODO: Implement the rest of the new `Temporal.Duration.prototype.total`
Err(JsNativeError::range()
.with_message("not yet implemented.")
.into())
}
/// 7.3.22 `Temporal.Duration.prototype.toString ( [ options ] )`
pub(crate) fn to_string(_this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Err(JsNativeError::range()
.with_message("not yet implemented.")
.into())
}
/// 7.3.23 `Temporal.Duration.prototype.toJSON ( )`
pub(crate) fn to_json(_this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Err(JsNativeError::range()
.with_message("not yet implemented.")
.into())
}
}
// -- Duration Abstract Operations --
/// 7.5.8 `ToTemporalDuration ( item )`
pub(crate) fn to_temporal_duration(
item: &JsValue,
context: &mut Context,
) -> JsResult<InnerDuration> {
// 1a. If Type(item) is Object
// 1b. and item has an [[InitializedTemporalDuration]] internal slot, then
if let Some(duration) = item
.as_object()
.and_then(JsObject::downcast_ref::<Duration>)
{
return Ok(duration.inner);
}
// 2. Let result be ? ToTemporalDurationRecord(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,
context: &mut Context,
) -> JsResult<InnerDuration> {
// 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::<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 ] )`
pub(crate) fn create_temporal_duration(
inner: InnerDuration,
new_target: Option<&JsValue>,
context: &mut Context,
) -> JsResult<JsObject> {
// 1. If ! IsValidDuration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds) is false, throw a RangeError exception.
// 2. If newTarget is not present, set newTarget to %Temporal.Duration%.
let new_target = if let Some(target) = new_target {
target.clone()
} else {
context
.realm()
.intrinsics()
.constructors()
.duration()
.constructor()
.into()
};
// 3. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.Duration.prototype%", « [[InitializedTemporalDuration]], [[Years]], [[Months]], [[Weeks]], [[Days]], [[Hours]], [[Minutes]], [[Seconds]], [[Milliseconds]], [[Microseconds]], [[Nanoseconds]] »).
let prototype =
get_prototype_from_constructor(&new_target, StandardConstructors::duration, context)?;
// 4. Set object.[[Years]] to ℝ(𝔽(years)).
// 5. Set object.[[Months]] to ℝ(𝔽(months)).
// 6. Set object.[[Weeks]] to ℝ(𝔽(weeks)).
// 7. Set object.[[Days]] to ℝ(𝔽(days)).
// 8. Set object.[[Hours]] to ℝ(𝔽(hours)).
// 9. Set object.[[Minutes]] to ℝ(𝔽(minutes)).
// 10. Set object.[[Seconds]] to ℝ(𝔽(seconds)).
// 11. Set object.[[Milliseconds]] to ℝ(𝔽(milliseconds)).
// 12. Set object.[[Microseconds]] to ℝ(𝔽(microseconds)).
// 13. Set object.[[Nanoseconds]] to ℝ(𝔽(nanoseconds)).
let obj = JsObject::from_proto_and_data(prototype, Duration::new(inner));
// 14. Return object.
Ok(obj)
}
/// Equivalent to 7.5.13 `ToTemporalPartialDurationRecord ( temporalDurationLike )`
pub(crate) fn to_temporal_partial_duration(
duration_like: &JsValue,
context: &mut Context,
) -> JsResult<InnerDuration> {
// 1. If Type(temporalDurationLike) is not Object, then
let JsValue::Object(unknown_object) = duration_like else {
// a. Throw a TypeError exception.
return Err(JsNativeError::typ()
.with_message("temporalDurationLike must be an object.")
.into());
};
// 2. Let result be a new partial Duration Record with each field set to undefined.
let mut result = InnerDuration::partial();
// 3. NOTE: The following steps read properties and perform independent validation in alphabetical order.
// 4. Let days be ? Get(temporalDurationLike, "days").
let days = unknown_object.get(utf16!("days"), context)?;
if !days.is_undefined() {
// 5. If days is not undefined, set result.[[Days]] to ? ToIntegerIfIntegral(days).
result.set_days(f64::from(to_integer_if_integral(&days, context)?));
}
// 6. Let hours be ? Get(temporalDurationLike, "hours").
let hours = unknown_object.get(utf16!("hours"), context)?;
// 7. If hours is not undefined, set result.[[Hours]] to ? ToIntegerIfIntegral(hours).
if !hours.is_undefined() {
result.set_hours(f64::from(to_integer_if_integral(&hours, context)?));
}
// 8. Let microseconds be ? Get(temporalDurationLike, "microseconds").
let microseconds = unknown_object.get(utf16!("microseconds"), context)?;
// 9. If microseconds is not undefined, set result.[[Microseconds]] to ? ToIntegerIfIntegral(microseconds).
if !microseconds.is_undefined() {
result.set_microseconds(f64::from(to_integer_if_integral(&microseconds, context)?));
}
// 10. Let milliseconds be ? Get(temporalDurationLike, "milliseconds").
let milliseconds = unknown_object.get(utf16!("milliseconds"), context)?;
// 11. If milliseconds is not undefined, set result.[[Milliseconds]] to ? ToIntegerIfIntegral(milliseconds).
if !milliseconds.is_undefined() {
result.set_milliseconds(f64::from(to_integer_if_integral(&milliseconds, context)?));
}
// 12. Let minutes be ? Get(temporalDurationLike, "minutes").
let minutes = unknown_object.get(utf16!("minutes"), context)?;
// 13. If minutes is not undefined, set result.[[Minutes]] to ? ToIntegerIfIntegral(minutes).
if !minutes.is_undefined() {
result.set_minutes(f64::from(to_integer_if_integral(&minutes, context)?));
}
// 14. Let months be ? Get(temporalDurationLike, "months").
let months = unknown_object.get(utf16!("months"), context)?;
// 15. If months is not undefined, set result.[[Months]] to ? ToIntegerIfIntegral(months).
if !months.is_undefined() {
result.set_months(f64::from(to_integer_if_integral(&months, context)?));
}
// 16. Let nanoseconds be ? Get(temporalDurationLike, "nanoseconds").
let nanoseconds = unknown_object.get(utf16!("nanoseconds"), context)?;
// 17. If nanoseconds is not undefined, set result.[[Nanoseconds]] to ? ToIntegerIfIntegral(nanoseconds).
if !nanoseconds.is_undefined() {
result.set_nanoseconds(f64::from(to_integer_if_integral(&nanoseconds, context)?));
}
// 18. Let seconds be ? Get(temporalDurationLike, "seconds").
let seconds = unknown_object.get(utf16!("seconds"), context)?;
// 19. If seconds is not undefined, set result.[[Seconds]] to ? ToIntegerIfIntegral(seconds).
if !seconds.is_undefined() {
result.set_seconds(f64::from(to_integer_if_integral(&seconds, context)?));
}
// 20. Let weeks be ? Get(temporalDurationLike, "weeks").
let weeks = unknown_object.get(utf16!("weeks"), context)?;
// 21. If weeks is not undefined, set result.[[Weeks]] to ? ToIntegerIfIntegral(weeks).
if !weeks.is_undefined() {
result.set_weeks(f64::from(to_integer_if_integral(&weeks, context)?));
}
// 22. Let years be ? Get(temporalDurationLike, "years").
let years = unknown_object.get(utf16!("years"), context)?;
// 23. If years is not undefined, set result.[[Years]] to ? ToIntegerIfIntegral(years).
if !years.is_undefined() {
result.set_years(f64::from(to_integer_if_integral(&years, context)?));
}
// TODO: Implement this functionality better in `temporal_rs`.
// 24. If years is undefined, and months is undefined, and weeks is undefined, and days
// is undefined, and hours is undefined, and minutes is undefined, and seconds is
// undefined, and milliseconds is undefined, and microseconds is undefined, and
// nanoseconds is undefined, throw a TypeError exception.
if result.years().is_nan()
&& result.months().is_nan()
&& result.weeks().is_nan()
&& result.days().is_nan()
&& result.minutes().is_nan()
&& result.seconds().is_nan()
&& result.milliseconds().is_nan()
&& result.microseconds().is_nan()
&& result.nanoseconds().is_nan()
{
return Err(JsNativeError::typ()
.with_message("PartialDurationRecord must have a defined field.")
.into());
}
// 25. Return result.
Ok(result)
}