Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ intrusive-collections = "0.9.6"
cfg-if = "1.0.0"
either = "1.13.0"
sys-locale = "0.3.1"
temporal_rs = { git = "https://github.com/boa-dev/temporal.git", rev = "2e6750a16a46c321a1c7092def880c62f3d1ac91" }
temporal_rs = { git = "https://github.com/boa-dev/temporal.git", rev = "8ef18fedc401761875b815a692443bf54ce9bd96" }
web-time = "1.1.0"
criterion = "0.5.1"
float-cmp = "0.9.0"
Expand Down
160 changes: 141 additions & 19 deletions core/engine/src/builtins/temporal/instant/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::{
temporal::{
duration::{create_temporal_duration, to_temporal_duration_record},
options::{get_temporal_unit, TemporalUnitGroup},
ZonedDateTime,
},
BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject,
},
Expand All @@ -15,13 +16,18 @@ use crate::{
property::Attribute,
realm::Realm,
string::StaticJsStrings,
value::PreferredType,
Context, JsArgs, JsBigInt, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol,
JsValue,
};
use boa_gc::{Finalize, Trace};
use boa_macros::js_str;
use boa_profiler::Profiler;
use temporal_rs::{components::Instant as InnerInstant, options::TemporalRoundingMode};
use num_traits::ToPrimitive;
use temporal_rs::{
components::Instant as InnerInstant,
options::{RoundingIncrement, RoundingOptions, TemporalRoundingMode},
};

use super::options::get_difference_settings;

Expand Down Expand Up @@ -87,6 +93,18 @@ impl IntrinsicObject for Instant {
None,
Attribute::CONFIGURABLE,
)
.static_method(Self::from, js_string!("from"), 1)
.static_method(
Self::from_epoch_milliseconds,
js_string!("fromEpochMilliseconds"),
1,
)
.static_method(
Self::from_epoch_nanoseconds,
js_string!("fromEpochNanoseconds"),
1,
)
.static_method(Self::compare, js_string!("compare"), 1)
.method(Self::add, js_string!("add"), 1)
.method(Self::subtract, js_string!("subtract"), 1)
.method(Self::until, js_string!("until"), 2)
Expand Down Expand Up @@ -130,14 +148,80 @@ impl BuiltInConstructor for Instant {
let epoch_nanos = args.get_or_undefined(0).to_bigint(context)?;

// 3. If ! IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception.
// NOTE: boa_temporal::Instant asserts that the epochNanoseconds are valid.
let instant = InnerInstant::new(epoch_nanos.as_inner().clone())?;
// NOTE: temporal_rs::Instant asserts that the epochNanoseconds are valid.
let instant = InnerInstant::new(epoch_nanos.as_inner().to_i128().unwrap_or(i128::MAX))?;
// 4. Return ? CreateTemporalInstant(epochNanoseconds, NewTarget).
create_temporal_instant(instant, Some(new_target.clone()), context)
}
}

// -- Instant method implementations --
// ==== Instant Static method implementations ====

impl Instant {
pub(crate) fn from(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. If item is an Object and item has an [[InitializedTemporalInstant]] internal slot, then
// a. Return ! CreateTemporalInstant(item.[[Nanoseconds]]).
// 2. Return ? ToTemporalInstant(item).
create_temporal_instant(
to_temporal_instant(args.get_or_undefined(0), context)?,
None,
context,
)
.map(Into::into)
}

pub(crate) fn from_epoch_milliseconds(
_: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Set epochMilliseconds to ? ToNumber(epochMilliseconds).
let epoch_millis = args.get_or_undefined(0).to_number(context)?;
// 2. Set epochMilliseconds to ? NumberToBigInt(epochMilliseconds).
// 3. Let epochNanoseconds be epochMilliseconds × ℤ(10**6).
// 4. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception.
// 5. Return ! CreateTemporalInstant(epochNanoseconds).
create_temporal_instant(
InnerInstant::from_epoch_milliseconds(epoch_millis.to_i128().unwrap_or(i128::MAX))?,
None,
context,
)
.map(Into::into)
}

pub(crate) fn from_epoch_nanoseconds(
_: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Set epochNanoseconds to ? ToBigInt(epochNanoseconds).
let epoch_nanos = args.get_or_undefined(0).to_bigint(context)?;
// 2. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception.
// 3. Return ! CreateTemporalInstant(epochNanoseconds).
let nanos = epoch_nanos.as_inner().to_i128();
create_temporal_instant(
InnerInstant::new(nanos.unwrap_or(i128::MAX))?,
None,
context,
)
.map(Into::into)
}

pub(crate) fn compare(
_: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Set one to ? ToTemporalInstant(one).
let one = to_temporal_instant(args.get_or_undefined(0), context)?;
// 2. Set two to ? ToTemporalInstant(two).
let two = to_temporal_instant(args.get_or_undefined(1), context)?;
// 3. Return 𝔽(CompareEpochNanoseconds(one.[[Nanoseconds]], two.[[Nanoseconds]])).
Ok((one.cmp(&two) as i8).into())
}
}

// ==== Instant method implementations ====

impl Instant {
/// 8.3.3 get Temporal.Instant.prototype.epochSeconds
Expand Down Expand Up @@ -281,7 +365,7 @@ impl Instant {
})?;

// 3. Return ? DifferenceTemporalInstant(until, instant, other, options).
let other = to_temporal_instant(args.get_or_undefined(0))?;
let other = to_temporal_instant(args.get_or_undefined(0), context)?;

// Fetch the necessary options.
let settings =
Expand All @@ -306,7 +390,7 @@ impl Instant {
})?;

// 3. Return ? DifferenceTemporalInstant(since, instant, other, options).
let other = to_temporal_instant(args.get_or_undefined(0))?;
let other = to_temporal_instant(args.get_or_undefined(0), context)?;
let settings =
get_difference_settings(&get_options_object(args.get_or_undefined(1))?, context)?;
let result = instant.inner.since(&other, settings)?;
Expand Down Expand Up @@ -358,12 +442,13 @@ impl Instant {

// 6. NOTE: The following steps read options and perform independent validation in
// alphabetical order (ToTemporalRoundingIncrement reads "roundingIncrement" and ToTemporalRoundingMode reads "roundingMode").
let mut options = RoundingOptions::default();
// 7. Let roundingIncrement be ? ToTemporalRoundingIncrement(roundTo).
let rounding_increment =
get_option::<f64>(&round_to, js_str!("roundingIncrement"), context)?;
options.increment =
get_option::<RoundingIncrement>(&round_to, js_str!("roundingIncrement"), context)?;

// 8. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand").
let rounding_mode =
options.rounding_mode =
get_option::<TemporalRoundingMode>(&round_to, js_str!("roundingMode"), context)?;

// 9. Let smallestUnit be ? GetTemporalUnit(roundTo, "smallestUnit"), time, required).
Expand All @@ -376,6 +461,8 @@ impl Instant {
)?
.ok_or_else(|| JsNativeError::range().with_message("smallestUnit cannot be undefined."))?;

options.smallest_unit = Some(smallest_unit);

// 10. If smallestUnit is "hour"), then
// a. Let maximum be HoursPerDay.
// 11. Else if smallestUnit is "minute"), then
Expand All @@ -392,16 +479,18 @@ impl Instant {
// unreachable here functions as 15.a.
// 16. Perform ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, true).
// 17. Let roundedNs be RoundTemporalInstant(instant.[[Nanoseconds]], roundingIncrement, smallestUnit, roundingMode).
let result = instant
.inner
.round(rounding_increment, smallest_unit, rounding_mode)?;
let result = instant.inner.round(options)?;

// 18. Return ! CreateTemporalInstant(roundedNs).
create_temporal_instant(result, None, context)
}

/// 8.3.12 `Temporal.Instant.prototype.equals ( other )`
pub(crate) fn equals(this: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
pub(crate) fn equals(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let instant be the this value.
// 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
// 4. If instant.[[Nanoseconds]] ≠ other.[[Nanoseconds]], return false.
Expand All @@ -415,7 +504,7 @@ impl Instant {

// 3. Set other to ? ToTemporalInstant(other).
let other = args.get_or_undefined(0);
let other_instant = to_temporal_instant(other)?;
let other_instant = to_temporal_instant(other, context)?;

if instant.inner != other_instant {
return Ok(false.into());
Expand Down Expand Up @@ -484,9 +573,42 @@ fn create_temporal_instant(

/// 8.5.3 `ToTemporalInstant ( item )`
#[inline]
fn to_temporal_instant(_: &JsValue) -> JsResult<InnerInstant> {
// TODO: Need to implement parsing.
Err(JsNativeError::error()
.with_message("Instant parsing is not yet implemented.")
.into())
fn to_temporal_instant(item: &JsValue, context: &mut Context) -> JsResult<InnerInstant> {
// 1.If item is an Object, then
let item = if let Some(obj) = item.as_object() {
// a. If item has an [[InitializedTemporalInstant]] internal slot, then
// i. Return item.
// b. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then
// i. Return ! CreateTemporalInstant(item.[[Nanoseconds]]).
// c. NOTE: This use of ToPrimitive allows Instant-like objects to be converted.
// d. Set item to ? ToPrimitive(item, string).
if let Some(instant) = obj.downcast_ref::<Instant>() {
return Ok(instant.inner.clone());
} else if let Some(_zdt) = obj.downcast_ref::<ZonedDateTime>() {
return Err(JsNativeError::error()
.with_message("Not yet implemented.")
.into());
}
item.to_primitive(context, PreferredType::String)?
} else {
item.clone()
};

let Some(string_to_parse) = item.as_string() else {
return Err(JsNativeError::typ()
.with_message("Invalid type to convert to a Temporal.Instant.")
.into());
};

// 3. Let parsed be ? ParseTemporalInstantString(item).
// 4. If parsed.[[TimeZone]].[[Z]] is true, let offsetNanoseconds be 0; otherwise, let offsetNanoseconds be ! ParseDateTimeUTCOffset(parsed.[[TimeZone]].[[OffsetString]]).
// 5. If abs(ISODateToEpochDays(parsed.[[Year]], parsed.[[Month]] - 1, parsed.[[Day]])) > 10**8, throw a RangeError exception.
// 6. Let epochNanoseconds be GetUTCEpochNanoseconds(parsed.[[Year]], parsed.[[Month]], parsed.[[Day]], parsed.[[Hour]], parsed.[[Minute]], parsed.[[Second]], parsed.[[Millisecond]], parsed.[[Microsecond]], parsed.[[Nanosecond]], offsetNanoseconds).
// 7. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception.
// 8. Return ! CreateTemporalInstant(epochNanoseconds).
// 2. If item is not a String, throw a TypeError exception.
string_to_parse
.to_std_string_escaped()
.parse::<InnerInstant>()
.map_err(Into::into)
}