Skip to content

Commit

Permalink
Implement more Temporal functionality (#3924)
Browse files Browse the repository at this point in the history
* Implement more Temporal functionality

* Correct equals method length

* patch withPlainTime docs

* Correct time method binding

* Add ParseTemporalTimeString handling

* cargo fmt

* Update temporal_rs and add compare methods
  • Loading branch information
nekevss authored Jul 18, 2024
1 parent b38b365 commit 7d025bc
Show file tree
Hide file tree
Showing 6 changed files with 272 additions and 31 deletions.
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

0 comments on commit 7d025bc

Please sign in to comment.