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.
347 lines
12 KiB
347 lines
12 KiB
//! The ECMAScript `Temporal` stage 3 built-in implementation. |
|
//! |
|
//! More information: |
|
//! |
|
//! [spec]: https://tc39.es/proposal-temporal/ |
|
|
|
mod calendar; |
|
mod duration; |
|
mod error; |
|
mod fields; |
|
mod instant; |
|
mod now; |
|
mod options; |
|
mod plain_date; |
|
mod plain_date_time; |
|
mod plain_month_day; |
|
mod plain_time; |
|
mod plain_year_month; |
|
mod time_zone; |
|
mod zoned_date_time; |
|
|
|
#[cfg(test)] |
|
mod tests; |
|
|
|
pub use self::{ |
|
calendar::*, duration::*, instant::*, now::*, plain_date::*, plain_date_time::*, |
|
plain_month_day::*, plain_time::*, plain_year_month::*, time_zone::*, zoned_date_time::*, |
|
}; |
|
|
|
use crate::{ |
|
builtins::{iterable::IteratorRecord, BuiltInBuilder, BuiltInObject, IntrinsicObject}, |
|
context::intrinsics::Intrinsics, |
|
js_string, |
|
property::{Attribute, PropertyKey}, |
|
realm::Realm, |
|
string::common::StaticJsStrings, |
|
value::Type, |
|
Context, JsBigInt, JsError, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, |
|
}; |
|
use boa_macros::utf16; |
|
use boa_profiler::Profiler; |
|
use temporal_rs::{ |
|
components::{Date as TemporalDate, ZonedDateTime as TemporalZonedDateTime}, |
|
NS_PER_DAY, |
|
}; |
|
|
|
// TODO: Remove in favor of `temporal_rs` |
|
pub(crate) fn ns_max_instant() -> JsBigInt { |
|
JsBigInt::from(i128::from(NS_PER_DAY) * 100_000_000_i128) |
|
} |
|
|
|
// TODO: Remove in favor of `temporal_rs` |
|
pub(crate) fn ns_min_instant() -> JsBigInt { |
|
JsBigInt::from(i128::from(NS_PER_DAY) * -100_000_000_i128) |
|
} |
|
|
|
// An enum representing common fields across `Temporal` objects. |
|
#[allow(unused)] |
|
pub(crate) enum DateTimeValues { |
|
Year, |
|
Month, |
|
MonthCode, |
|
Week, |
|
Day, |
|
Hour, |
|
Minute, |
|
Second, |
|
Millisecond, |
|
Microsecond, |
|
Nanosecond, |
|
} |
|
|
|
/// The [`Temporal`][spec] builtin object. |
|
/// |
|
/// [spec]: https://tc39.es/proposal-temporal/#sec-temporal-objects |
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] |
|
pub(crate) struct Temporal; |
|
|
|
impl BuiltInObject for Temporal { |
|
const NAME: JsString = StaticJsStrings::TEMPORAL; |
|
} |
|
|
|
impl IntrinsicObject for Temporal { |
|
fn init(realm: &Realm) { |
|
let _timer = Profiler::global().start_event(std::any::type_name::<Self>(), "init"); |
|
|
|
BuiltInBuilder::with_intrinsic::<Self>(realm) |
|
.static_property( |
|
JsSymbol::to_string_tag(), |
|
Self::NAME, |
|
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, |
|
) |
|
.static_property( |
|
js_string!("Now"), |
|
realm.intrinsics().objects().now(), |
|
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, |
|
) |
|
.static_property( |
|
js_string!("Calendar"), |
|
realm.intrinsics().constructors().calendar().constructor(), |
|
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, |
|
) |
|
.static_property( |
|
js_string!("Duration"), |
|
realm.intrinsics().constructors().duration().constructor(), |
|
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, |
|
) |
|
.static_property( |
|
js_string!("Instant"), |
|
realm.intrinsics().constructors().instant().constructor(), |
|
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, |
|
) |
|
.static_property( |
|
js_string!("PlainDate"), |
|
realm.intrinsics().constructors().plain_date().constructor(), |
|
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, |
|
) |
|
.static_property( |
|
js_string!("PlainDateTime"), |
|
realm |
|
.intrinsics() |
|
.constructors() |
|
.plain_date_time() |
|
.constructor(), |
|
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, |
|
) |
|
.static_property( |
|
js_string!("PlainMonthDay"), |
|
realm |
|
.intrinsics() |
|
.constructors() |
|
.plain_month_day() |
|
.constructor(), |
|
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, |
|
) |
|
.static_property( |
|
js_string!("PlainTime"), |
|
realm.intrinsics().constructors().plain_time().constructor(), |
|
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, |
|
) |
|
.static_property( |
|
js_string!("PlainYearMonth"), |
|
realm |
|
.intrinsics() |
|
.constructors() |
|
.plain_year_month() |
|
.constructor(), |
|
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, |
|
) |
|
.static_property( |
|
js_string!("TimeZone"), |
|
realm.intrinsics().constructors().time_zone().constructor(), |
|
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, |
|
) |
|
.static_property( |
|
js_string!("ZonedDateTime"), |
|
realm |
|
.intrinsics() |
|
.constructors() |
|
.zoned_date_time() |
|
.constructor(), |
|
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, |
|
) |
|
.build(); |
|
} |
|
|
|
fn get(intrinsics: &Intrinsics) -> JsObject { |
|
intrinsics.objects().temporal() |
|
} |
|
} |
|
|
|
// -- Temporal Abstract Operations -- |
|
|
|
/// Abstract operation `ToZeroPaddedDecimalString ( n, minLength )` |
|
/// |
|
/// The abstract operation `ToZeroPaddedDecimalString` takes arguments `n` (a non-negative integer) |
|
/// and `minLength` (a non-negative integer) and returns a String. |
|
fn to_zero_padded_decimal_string(n: u64, min_length: usize) -> String { |
|
format!("{n:0min_length$}") |
|
} |
|
|
|
/// Abstract Operation 13.1 [`IteratorToListOfType`][proposal] |
|
/// |
|
/// [proposal]: https://tc39.es/proposal-temporal/#sec-iteratortolistoftype |
|
pub(crate) fn _iterator_to_list_of_types( |
|
iterator: &mut IteratorRecord, |
|
element_types: &[Type], |
|
context: &mut Context, |
|
) -> JsResult<Vec<JsValue>> { |
|
// 1. Let values be a new empty List. |
|
let mut values = Vec::new(); |
|
|
|
// 2. Let next be true. |
|
// 3. Repeat, while next is not false, |
|
// a. Set next to ? IteratorStep(iteratorRecord). |
|
// b. If next is not false, then |
|
while iterator.step(context)? { |
|
// i. Let nextValue be ? IteratorValue(next). |
|
let next_value = iterator.value(context)?; |
|
// ii. If Type(nextValue) is not an element of elementTypes, then |
|
if element_types.contains(&next_value.get_type()) { |
|
// 1. Let completion be ThrowCompletion(a newly created TypeError object). |
|
let completion = JsNativeError::typ() |
|
.with_message("IteratorNext is not within allowed type values."); |
|
|
|
// NOTE: The below should return as we are forcing a ThrowCompletion. |
|
// 2. Return ? IteratorClose(iteratorRecord, completion). |
|
let _never = iterator.close(Err(completion.into()), context)?; |
|
} |
|
// iii. Append nextValue to the end of the List values. |
|
values.push(next_value); |
|
} |
|
|
|
// 4. Return values. |
|
Ok(values) |
|
} |
|
|
|
// Abstract Operation 13.3 `EpochDaysToEpochMs` |
|
// Migrated to `temporal_rs` |
|
|
|
// 13.4 Date Equations |
|
// implemented in temporal/date_equations.rs |
|
|
|
// Abstract Operation 13.5 `GetOptionsObject ( options )` |
|
// Implemented in builtin/options.rs |
|
|
|
// 13.6 `GetOption ( options, property, type, values, default )` |
|
// Implemented in builtin/options.rs |
|
|
|
/// 13.7 `ToTemporalOverflow (options)` |
|
// Now implemented in temporal/options.rs |
|
|
|
/// 13.10 `ToTemporalRoundingMode ( normalizedOptions, fallback )` |
|
// Now implemented in builtin/options.rs |
|
|
|
// 13.11 `NegateTemporalRoundingMode ( roundingMode )` |
|
// Now implemented in builtin/options.rs |
|
|
|
// 13.16 `ToTemporalRoundingIncrement ( normalizedOptions )` |
|
// Now implemented in temporal/options.rs |
|
|
|
// 13.17 `ValidateTemporalRoundingIncrement ( increment, dividend, inclusive )` |
|
// Moved to temporal_rs |
|
|
|
type RelativeTemporalObjectResult = JsResult<( |
|
Option<TemporalDate<JsObject>>, |
|
Option<TemporalZonedDateTime<JsObject, JsCustomTimeZone>>, |
|
)>; |
|
|
|
/// 13.21 `ToRelativeTemporalObject ( options )` |
|
pub(crate) fn to_relative_temporal_object( |
|
options: &JsObject, |
|
context: &mut Context, |
|
) -> RelativeTemporalObjectResult { |
|
let relative_to = options.get(PropertyKey::from(utf16!("relativeTo")), context)?; |
|
let plain_date = match relative_to { |
|
JsValue::String(relative_to_str) => Some(relative_to_str.into()), |
|
JsValue::Object(relative_to_obj) => Some(relative_to_obj.into()), |
|
_ => None, |
|
} |
|
.map(|plane_date| Ok::<_, JsError>(to_temporal_date(&plane_date, None, context)?.inner)) |
|
.transpose()?; |
|
|
|
// TODO: Implement TemporalZonedDateTime conversion when ZonedDateTime is implemented |
|
Ok((plain_date, None)) |
|
} |
|
|
|
// 13.22 `LargerOfTwoTemporalUnits ( u1, u2 )` |
|
// use core::cmp::max |
|
|
|
// 13.23 `MaximumTemporalDurationRoundingIncrement ( unit )` |
|
// Implemented on TemporalUnit in temporal/options.rs |
|
|
|
// 13.26 `GetUnsignedRoundingMode ( roundingMode, isNegative )` |
|
// Implemented on RoundingMode in builtins/options.rs |
|
|
|
// 13.27 `ApplyUnsignedRoundingMode ( x, r1, r2, unsignedRoundingMode )` |
|
// Migrated to `temporal_rs` |
|
|
|
// 13.28 `RoundNumberToIncrement ( x, increment, roundingMode )` |
|
// Migrated to `temporal_rs` |
|
|
|
// 13.29 `RoundNumberToIncrementAsIfPositive ( x, increment, roundingMode )` |
|
// Migrated to `temporal_rs` |
|
|
|
/// 13.43 `ToPositiveIntegerWithTruncation ( argument )` |
|
#[inline] |
|
pub(crate) fn to_positive_integer_with_trunc( |
|
value: &JsValue, |
|
context: &mut Context, |
|
) -> JsResult<i32> { |
|
// 1. Let integer be ? ToIntegerWithTruncation(argument). |
|
let int = to_integer_with_truncation(value, context)?; |
|
// 2. If integer ≤ 0, throw a RangeError exception. |
|
if int <= 0 { |
|
return Err(JsNativeError::range() |
|
.with_message("value is not a positive integer") |
|
.into()); |
|
} |
|
// 3. Return integer. |
|
Ok(int) |
|
} |
|
|
|
/// 13.44 `ToIntegerWithTruncation ( argument )` |
|
#[inline] |
|
pub(crate) fn to_integer_with_truncation(value: &JsValue, context: &mut Context) -> JsResult<i32> { |
|
// 1. Let number be ? ToNumber(argument). |
|
let number = value.to_number(context)?; |
|
// 2. If number is NaN, +∞𝔽 or -∞𝔽, throw a RangeError exception. |
|
if number.is_nan() || number.is_infinite() { |
|
return Err(JsNativeError::range() |
|
.with_message("truncation target must be an integer.") |
|
.into()); |
|
} |
|
// 3. Return truncate(ℝ(number)). |
|
Ok(number.trunc() as i32) |
|
} |
|
|
|
/// Abstract operation 13.45 `ToIntegerIfIntegral( argument )` |
|
#[inline] |
|
pub(crate) fn to_integer_if_integral(arg: &JsValue, context: &mut Context) -> JsResult<i32> { |
|
// 1. Let number be ? ToNumber(argument). |
|
// 2. If IsIntegralNumber(number) is false, throw a RangeError exception. |
|
// 3. Return ℝ(number). |
|
if !arg.is_integral_number() { |
|
return Err(JsNativeError::range() |
|
.with_message("value to convert is not an integral number.") |
|
.into()); |
|
} |
|
|
|
arg.to_i32(context) |
|
} |
|
|
|
// 13.46 `PrepareTemporalFields ( fields, fieldNames, requiredFields [ , duplicateBehaviour ] )` |
|
// See fields.rs |
|
|
|
// NOTE: op -> true == until | false == since |
|
// 13.47 `GetDifferenceSettings ( operation, options, unitGroup, disallowedUnits, fallbackSmallestUnit, smallestLargestDefaultUnit )` |
|
// Migrated to `temporal_rs` |
|
|
|
// NOTE: used for MergeFields methods. Potentially can be omitted in favor of `TemporalFields`. |
|
// 14.6 `CopyDataProperties ( target, source, excludedKeys [ , excludedValues ] )` |
|
// Migrated or repurposed to `temporal_rs`/`fields.rs` |
|
|
|
// Note: Deviates from Proposal spec -> proto appears to be always null across the specification. |
|
// 14.7 `SnapshotOwnProperties ( source, proto [ , excludedKeys [ , excludedValues ] ] )` |
|
// Migrated or repurposed to `temporal_rs`/`fields.rs`
|
|
|