mirror of https://github.com/boa-dev/boa.git
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
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(µseconds, 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) |
|
}
|
|
|