Browse Source

Implement more Temporal functionality (#3924)

* 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
pull/3928/head
Kevin Ness 5 months ago committed by GitHub
parent
commit
7d025bc6ee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      Cargo.lock
  2. 2
      Cargo.toml
  3. 20
      core/engine/src/builtins/temporal/duration/mod.rs
  4. 35
      core/engine/src/builtins/temporal/plain_date/mod.rs
  5. 170
      core/engine/src/builtins/temporal/plain_date_time/mod.rs
  6. 73
      core/engine/src/builtins/temporal/plain_time/mod.rs

3
Cargo.lock generated

@ -3200,8 +3200,7 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "temporal_rs"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e6f351ef929946476b4107c09348c9e25ba1155ff8e3867b1914878a0fb553f"
source = "git+https://github.com/boa-dev/temporal.git?rev=2e6750a16a46c321a1c7092def880c62f3d1ac91#2e6750a16a46c321a1c7092def880c62f3d1ac91"
dependencies = [
"bitflags 2.6.0",
"icu_calendar",

2
Cargo.toml

@ -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"

20
core/engine/src/builtins/temporal/duration/mod.rs

@ -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 ( )`
@ -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 ] )`

35
core/engine/src/builtins/temporal/plain_date/mod.rs

@ -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.
@ -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)
@ -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,
@ -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);
@ -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 ====
@ -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> {
@ -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);

170
core/engine/src/builtins/temporal/plain_date_time/mod.rs

@ -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},
@ -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)]
@ -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();
}
@ -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,
@ -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).
@ -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]]).
@ -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]]).
@ -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]]).

73
core/engine/src/builtins/temporal/plain_time/mod.rs

@ -22,7 +22,8 @@ use temporal_rs::{
};
use super::{
options::{get_temporal_unit, TemporalUnitGroup},
create_temporal_duration,
options::{get_difference_settings, get_temporal_unit, TemporalUnitGroup},
to_integer_with_truncation, to_temporal_duration_record, PlainDateTime, ZonedDateTime,
};
@ -108,8 +109,11 @@ impl IntrinsicObject for PlainTime {
Attribute::CONFIGURABLE,
)
.static_method(Self::from, js_string!("from"), 2)
.static_method(Self::compare, js_string!("compare"), 2)
.method(Self::add, js_string!("add"), 1)
.method(Self::subtract, js_string!("subtract"), 1)
.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)
.method(Self::get_iso_fields, js_string!("getISOFields"), 0)
@ -290,6 +294,7 @@ impl PlainTime {
// ==== PlainTime method implementations ====
impl PlainTime {
/// 4.2.2 Temporal.PlainTime.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).
@ -315,6 +320,19 @@ impl PlainTime {
// 4. Return ? ToTemporalTime(item, overflow).
create_temporal_time(time, None, context).map(Into::into)
}
/// 4.2.3 Temporal.PlainTime.compare ( one, two )
fn compare(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Set one to ? ToTemporalTime(one).
let one = to_temporal_time(args.get_or_undefined(0), None, context)?;
// 2. Set two to ? ToTemporalTime(two).
let two = to_temporal_time(args.get_or_undefined(1), None, context)?;
// 3. Return 𝔽(CompareTemporalTime(one.[[ISOHour]], one.[[ISOMinute]], one.[[ISOSecond]],
// one.[[ISOMillisecond]], one.[[ISOMicrosecond]], one.[[ISONanosecond]], two.[[ISOHour]],
// two.[[ISOMinute]], two.[[ISOSecond]], two.[[ISOMillisecond]], two.[[ISOMicrosecond]],
// two.[[ISONanosecond]])).
Ok((one.cmp(&two) as i8).into())
}
}
// ==== PlainTime.prototype method implementations ====
@ -326,7 +344,7 @@ impl PlainTime {
// 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]).
let time = this
.as_object()
.and_then(JsObject::downcast_ref::<PlainTime>)
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("the this object must be a PlainTime object.")
})?;
@ -344,7 +362,7 @@ impl PlainTime {
// 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]).
let time = this
.as_object()
.and_then(JsObject::downcast_ref::<PlainTime>)
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("the this object must be a PlainTime object.")
})?;
@ -356,13 +374,51 @@ impl PlainTime {
create_temporal_time(time.inner.subtract(&duration)?, None, context).map(Into::into)
}
/// 4.3.12 Temporal.PlainTime.prototype.until ( other [ , options ] )
fn until(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let time = 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 other = to_temporal_time(args.get_or_undefined(0), None, context)?;
let settings =
get_difference_settings(&get_options_object(args.get_or_undefined(1))?, context)?;
let result = time.inner.until(&other, settings)?;
create_temporal_duration(result, None, context).map(Into::into)
}
/// 4.3.13 Temporal.PlainTime.prototype.since ( other [ , options ] )
fn since(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let time = 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 other = to_temporal_time(args.get_or_undefined(0), None, context)?;
let settings =
get_difference_settings(&get_options_object(args.get_or_undefined(1))?, context)?;
let result = time.inner.since(&other, settings)?;
create_temporal_duration(result, None, context).map(Into::into)
}
/// 4.3.14 Temporal.PlainTime.prototype.round ( roundTo )
fn round(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Let temporalTime be the this value.
// 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]).
let time = this
.as_object()
.and_then(JsObject::downcast_ref::<PlainTime>)
.and_then(JsObject::downcast_ref::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("the this object must be a PlainTime object.")
})?;
@ -588,13 +644,12 @@ pub(crate) fn to_temporal_time(
.into())
}
// 3. Else,
JsValue::String(_str) => {
JsValue::String(str) => {
// b. Let result be ? ParseTemporalTimeString(item).
// c. Assert: IsValidTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]]) is true.
// TODO: Add time parsing to `temporal_rs`
Err(JsNativeError::typ()
.with_message("Invalid value for converting to PlainTime.")
.into())
str.to_std_string_escaped()
.parse::<Time>()
.map_err(Into::into)
}
// a. If item is not a String, throw a TypeError exception.
_ => Err(JsNativeError::typ()

Loading…
Cancel
Save