Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement more Temporal functionality #3924

Merged
merged 7 commits into from
Jul 18, 2024
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
3 changes: 1 addition & 2 deletions 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 = "0.0.3"
temporal_rs = { git = "https://github.com/boa-dev/temporal.git", rev = "2e6750a16a46c321a1c7092def880c62f3d1ac91" }
web-time = "1.1.0"
criterion = "0.5.1"
float-cmp = "0.9.0"
Expand Down
20 changes: 13 additions & 7 deletions core/engine/src/builtins/temporal/duration/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -571,14 +571,22 @@ impl Duration {
}

/// 7.3.16 `Temporal.Duration.prototype.negated ( )`
pub(crate) fn negated(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
pub(crate) fn negated(
this: &JsValue,
_: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let duration be the this value.
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
// 3. Return ! CreateNegatedTemporalDuration(duration).
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.")
})?;

Err(JsNativeError::range()
.with_message("not yet implemented.")
.into())
create_temporal_duration(duration.inner.negated(), None, context).map(Into::into)
}

/// 7.3.17 `Temporal.Duration.prototype.abs ( )`
Expand All @@ -595,9 +603,7 @@ impl Duration {
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)
create_temporal_duration(duration.inner.abs(), None, context).map(Into::into)
}

/// 7.3.18 `Temporal.Duration.prototype.add ( other [ , options ] )`
Expand Down
35 changes: 27 additions & 8 deletions core/engine/src/builtins/temporal/plain_date/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ use temporal_rs::{
};

use super::{
calendar, create_temporal_datetime, create_temporal_duration, options::get_difference_settings,
to_temporal_duration_record, to_temporal_time, PlainDateTime, ZonedDateTime,
calendar::to_temporal_calendar_slot_value, create_temporal_datetime, create_temporal_duration,
options::get_difference_settings, to_temporal_duration_record, to_temporal_time, PlainDateTime,
ZonedDateTime,
};

/// The `Temporal.PlainDate` object.
Expand Down Expand Up @@ -213,6 +214,7 @@ impl IntrinsicObject for PlainDate {
Attribute::CONFIGURABLE,
)
.static_method(Self::from, js_string!("from"), 2)
.static_method(Self::compare, js_string!("compare"), 2)
.method(Self::to_plain_year_month, js_string!("toPlainYearMonth"), 0)
.method(Self::to_plain_month_day, js_string!("toPlainMonthDay"), 0)
.method(Self::get_iso_fields, js_string!("getISOFields"), 0)
Expand Down Expand Up @@ -252,7 +254,7 @@ impl BuiltInConstructor for PlainDate {
let iso_year = super::to_integer_with_truncation(args.get_or_undefined(0), context)?;
let iso_month = super::to_integer_with_truncation(args.get_or_undefined(1), context)?;
let iso_day = super::to_integer_with_truncation(args.get_or_undefined(2), context)?;
let calendar_slot = calendar::to_temporal_calendar_slot_value(args.get_or_undefined(3))?;
let calendar_slot = to_temporal_calendar_slot_value(args.get_or_undefined(3))?;

let date = InnerDate::new(
iso_year,
Expand Down Expand Up @@ -488,6 +490,7 @@ impl PlainDate {
// ==== `PlainDate` method implementations ====

impl PlainDate {
/// 3.2.2 Temporal.PlainDate.from ( item [ , options ] )
fn from(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let item = args.get_or_undefined(0);
let options = args.get(1);
Expand All @@ -505,6 +508,14 @@ impl PlainDate {
)
.map(Into::into)
}

/// 3.2.3 Temporal.PlainDate.compare ( one, two )
fn compare(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let one = to_temporal_date(args.get_or_undefined(0), None, context)?;
let two = to_temporal_date(args.get_or_undefined(1), None, context)?;

Ok((one.cmp(&two) as i8).into())
}
}

// ==== `PlainDate.prototype` method implementation ====
Expand Down Expand Up @@ -608,10 +619,18 @@ impl PlainDate {
.into())
}

fn with_calendar(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Err(JsNativeError::error()
.with_message("not yet implemented.")
.into())
/// 3.3.26 Temporal.PlainDate.prototype.withCalendar ( calendarLike )
fn with_calendar(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let date = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("the this object must be a PlainDate object.")
})?;

let calendar = to_temporal_calendar_slot_value(args.get_or_undefined(0))?;

create_temporal_date(date.inner.with_calendar(calendar)?, None, context).map(Into::into)
}

fn until(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
Expand Down Expand Up @@ -781,7 +800,7 @@ pub(crate) fn to_temporal_date(
let _o = get_option(&options_obj, js_str!("overflow"), context)?
.unwrap_or(ArithmeticOverflow::Constrain);

let date = InnerDate::from_datetime(date_time.inner());
let date = InnerDate::from(date_time.inner().clone());

// ii. Return ! CreateTemporalDate(item.[[ISOYear]], item.[[ISOMonth]], item.[[ISODay]], item.[[Calendar]]).
return Ok(date);
Expand Down
170 changes: 166 additions & 4 deletions core/engine/src/builtins/temporal/plain_date_time/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
use crate::{
builtins::{
options::{get_option, get_options_object},
temporal::{calendar, to_integer_with_truncation},
temporal::to_integer_with_truncation,
BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject,
},
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
Expand All @@ -28,10 +28,15 @@ use temporal_rs::{
DateTime as InnerDateTime,
},
iso::{IsoDate, IsoDateSlots},
options::ArithmeticOverflow,
options::{ArithmeticOverflow, RoundingIncrement, RoundingOptions, TemporalRoundingMode},
};

use super::{to_temporal_duration_record, PlainDate, ZonedDateTime};
use super::{
calendar::to_temporal_calendar_slot_value,
create_temporal_duration,
options::{get_difference_settings, get_temporal_unit, TemporalUnitGroup},
to_temporal_duration_record, to_temporal_time, PlainDate, ZonedDateTime,
};

/// The `Temporal.PlainDateTime` object.
#[derive(Debug, Clone, Trace, Finalize, JsData)]
Expand Down Expand Up @@ -272,8 +277,15 @@ impl IntrinsicObject for PlainDateTime {
Attribute::CONFIGURABLE,
)
.static_method(Self::from, js_string!("from"), 2)
.static_method(Self::compare, js_string!("compare"), 2)
.method(Self::with_plain_time, js_string!("withPlainTime"), 1)
.method(Self::with_calendar, js_string!("withCalendar"), 1)
.method(Self::add, js_string!("add"), 2)
.method(Self::subtract, js_string!("subtract"), 2)
.method(Self::until, js_string!("until"), 2)
.method(Self::since, js_string!("since"), 2)
.method(Self::round, js_string!("round"), 1)
.method(Self::equals, js_string!("equals"), 1)
.build();
}

Expand Down Expand Up @@ -332,7 +344,7 @@ impl BuiltInConstructor for PlainDateTime {
.get(8)
.map_or(Ok(0), |v| to_integer_with_truncation(v, context))?;
// 11. Let calendar be ? ToTemporalCalendarSlotValue(calendarLike, "iso8601").
let calendar_slot = calendar::to_temporal_calendar_slot_value(args.get_or_undefined(9))?;
let calendar_slot = to_temporal_calendar_slot_value(args.get_or_undefined(9))?;

let dt = InnerDateTime::new(
iso_year,
Expand Down Expand Up @@ -625,6 +637,7 @@ impl PlainDateTime {
// ==== PlainDateTime method implemenations ====

impl PlainDateTime {
/// 5.2.2 Temporal.PlainDateTime.from ( item [ , options ] )
fn from(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let item = args.get_or_undefined(0);
// 1. Set options to ? GetOptionsObject(options).
Expand All @@ -646,11 +659,59 @@ impl PlainDateTime {
// 3. Return ? ToTemporalDateTime(item, options).
create_temporal_datetime(dt, None, context).map(Into::into)
}

/// 5.2.3 Temporal.PlainDateTime.compare ( one, two )
fn compare(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Set one to ? ToTemporalDateTime(one).
let one = to_temporal_datetime(args.get_or_undefined(0), None, context)?;
// 2. Set two to ? ToTemporalDateTime(two).
let two = to_temporal_datetime(args.get_or_undefined(1), None, context)?;

// 3. Return 𝔽(CompareISODateTime(one.[[ISOYear]], one.[[ISOMonth]], one.[[ISODay]],
// one.[[ISOHour]], one.[[ISOMinute]], one.[[ISOSecond]], one.[[ISOMillisecond]],
// one.[[ISOMicrosecond]], one.[[ISONanosecond]], two.[[ISOYear]], two.[[ISOMonth]],
// two.[[ISODay]], two.[[ISOHour]], two.[[ISOMinute]], two.[[ISOSecond]],
// two.[[ISOMillisecond]], two.[[ISOMicrosecond]], two.[[ISONanosecond]])).
Ok((one.cmp(&two) as i8).into())
}
}

// ==== PlainDateTime.prototype method implementations ====

impl PlainDateTime {
/// 5.3.26 Temporal.PlainDateTime.prototype.withPlainTime ( `[ plainTimeLike ]` )
fn with_plain_time(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
let dt = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("the this object must be a PlainDateTime object.")
})?;

let time = to_temporal_time(args.get_or_undefined(0), None, context)?;

create_temporal_datetime(dt.inner.with_time(time)?, None, context).map(Into::into)
}

/// 5.3.27 Temporal.PlainDateTime.prototype.withCalendar ( calendarLike )
fn with_calendar(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let dt = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("the this object must be a PlainDateTime object.")
})?;

let calendar = to_temporal_calendar_slot_value(args.get_or_undefined(0))?;

create_temporal_datetime(dt.inner.with_calendar(calendar)?, None, context).map(Into::into)
}

/// 5.3.28 Temporal.PlainDateTime.prototype.add ( temporalDurationLike [ , options ] )
fn add(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Let temporalDate be the this value.
// 2. Perform ? RequireInternalSlot(temporalDate, [[InitializedTemporalDate]]).
Expand All @@ -673,6 +734,7 @@ impl PlainDateTime {
create_temporal_datetime(dt.inner.add(&duration, overflow)?, None, context).map(Into::into)
}

/// 5.3.29 Temporal.PlainDateTime.prototype.subtract ( temporalDurationLike [ , options ] )
fn subtract(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Let temporalDate be the this value.
// 2. Perform ? RequireInternalSlot(temporalDate, [[InitializedTemporalDate]]).
Expand All @@ -697,6 +759,106 @@ impl PlainDateTime {
.map(Into::into)
}

/// 5.3.30 Temporal.PlainDateTime.prototype.until ( other [ , options ] )
fn until(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let dt = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("the this object must be a PlainDateTime object.")
})?;

let other = to_temporal_datetime(args.get_or_undefined(0), None, context)?;

let options = get_options_object(args.get_or_undefined(1))?;
let settings = get_difference_settings(&options, context)?;

create_temporal_duration(dt.inner.until(&other, settings)?, None, context).map(Into::into)
}

/// 5.3.31 Temporal.PlainDateTime.prototype.since ( other [ , options ] )
fn since(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let dt = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("the this object must be a PlainDateTime object.")
})?;

let other = to_temporal_datetime(args.get_or_undefined(0), None, context)?;

let options = get_options_object(args.get_or_undefined(1))?;
let settings = get_difference_settings(&options, context)?;

create_temporal_duration(dt.inner.until(&other, settings)?, None, context).map(Into::into)
}

// TODO(nekevss): finish after temporal_rs impl
/// 5.3.32 Temporal.PlainDateTime.prototype.round ( roundTo )
fn round(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let dt = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("the this object must be a PlainTime object.")
})?;

let round_to = match args.first() {
// 3. If roundTo is undefined, then
None | Some(JsValue::Undefined) => {
return Err(JsNativeError::typ()
.with_message("roundTo cannot be undefined.")
.into())
}
// 4. If Type(roundTo) is String, then
Some(JsValue::String(rt)) => {
// a. Let paramString be roundTo.
let param_string = rt.clone();
// b. Set roundTo to OrdinaryObjectCreate(null).
let new_round_to = JsObject::with_null_proto();
// c. Perform ! CreateDataPropertyOrThrow(roundTo, "smallestUnit", paramString).
new_round_to.create_data_property_or_throw(
js_str!("smallestUnit"),
param_string,
context,
)?;
new_round_to
}
// 5. Else,
Some(round_to) => {
// a. Set roundTo to ? GetOptionsObject(roundTo).
get_options_object(round_to)?
}
};

let (plain_relative_to, zoned_relative_to) =
super::to_relative_temporal_object(&round_to, context)?;

let mut options = RoundingOptions::default();

options.increment =
get_option::<RoundingIncrement>(&round_to, js_str!("roundingIncrement"), context)?;

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

// 9. Let smallestUnit be ? GetTemporalUnit(roundTo, "smallestUnit", TIME, REQUIRED, undefined).
options.smallest_unit = get_temporal_unit(
&round_to,
js_str!("smallestUnit"),
TemporalUnitGroup::Time,
None,
context,
)?;

// TODO: implement in temporal_rs
Err(JsNativeError::range()
.with_message("not yet implemented.")
.into())
}

/// 5.3.33 Temporal.PlainDateTime.prototype.equals ( other )
fn equals(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Let dateTime be the this value.
// 2. Perform ? RequireInternalSlot(dateTime, [[InitializedTemporalDateTime]]).
Expand Down
Loading
Loading