diff --git a/Cargo.lock b/Cargo.lock index 2a4d53881f..f1dc8007e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3201,7 +3201,7 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "temporal_rs" version = "0.0.2" -source = "git+https://github.com/boa-dev/temporal.git?rev=c658ac7db4701822cc179d04b56bb9c8fb7e954c#c658ac7db4701822cc179d04b56bb9c8fb7e954c" +source = "git+https://github.com/boa-dev/temporal.git?rev=cf70b50f90865d2c00fb994b7adf8b9e1bd837b8#cf70b50f90865d2c00fb994b7adf8b9e1bd837b8" dependencies = [ "bitflags 2.6.0", "icu_calendar", diff --git a/Cargo.toml b/Cargo.toml index 2e287af24d..23162901dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -115,7 +115,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 = "c658ac7db4701822cc179d04b56bb9c8fb7e954c" } +temporal_rs = { git = "https://github.com/boa-dev/temporal.git", rev = "cf70b50f90865d2c00fb994b7adf8b9e1bd837b8" } web-time = "1.1.0" criterion = "0.5.1" float-cmp = "0.9.0" diff --git a/core/engine/src/builtins/temporal/duration/mod.rs b/core/engine/src/builtins/temporal/duration/mod.rs index 100884b2b3..9bf5d7b104 100644 --- a/core/engine/src/builtins/temporal/duration/mod.rs +++ b/core/engine/src/builtins/temporal/duration/mod.rs @@ -18,7 +18,7 @@ use boa_macros::js_str; use boa_profiler::Profiler; use temporal_rs::{ components::Duration as InnerDuration, - options::{RelativeTo, RoundingIncrement, TemporalRoundingMode, TemporalUnit}, + options::{RelativeTo, RoundingIncrement, RoundingOptions, TemporalRoundingMode, TemporalUnit}, }; use super::{ @@ -389,7 +389,7 @@ impl Duration { // 3. Return 𝔽(! DurationSign(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], // duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], // duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]])). - Ok(duration.inner.sign().into()) + Ok((duration.inner.sign() as i8).into()) } /// 7.3.14 get Temporal.Duration.prototype.blank @@ -640,10 +640,11 @@ impl Duration { // NOTE: 6 & 7 unused in favor of `is_none()`. // 6. Let smallestUnitPresent be true. // 7. Let largestUnitPresent be true. + let mut options = RoundingOptions::default(); // 8. NOTE: The following steps read options and perform independent validation in alphabetical order (ToRelativeTemporalObject reads "relativeTo", ToTemporalRoundingIncrement reads "roundingIncrement" and ToTemporalRoundingMode reads "roundingMode"). // 9. Let largestUnit be ? GetTemporalUnit(roundTo, "largestUnit", datetime, undefined, « "auto" »). - let largest_unit = get_temporal_unit( + options.largest_unit = get_temporal_unit( &round_to, js_str!("largestUnit"), TemporalUnitGroup::DateTime, @@ -658,15 +659,15 @@ impl Duration { super::to_relative_temporal_object(&round_to, context)?; // 13. Let roundingIncrement be ? ToTemporalRoundingIncrement(roundTo). - let rounding_increment = + options.increment = get_option::(&round_to, js_str!("roundingIncrement"), context)?; // 14. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand"). - let rounding_mode = + options.rounding_mode = get_option::(&round_to, js_str!("roundingMode"), context)?; // 15. Let smallestUnit be ? GetTemporalUnit(roundTo, "smallestUnit", datetime, undefined). - let smallest_unit = get_temporal_unit( + options.smallest_unit = get_temporal_unit( &round_to, js_str!("smallestUnit"), TemporalUnitGroup::DateTime, @@ -676,17 +677,9 @@ impl Duration { // NOTE: execute step 21 earlier before initial values are shadowed. // 21. If smallestUnitPresent is false and largestUnitPresent is false, then - if smallest_unit.is_none() && largest_unit.is_none() { - // a. Throw a RangeError exception. - return Err(JsNativeError::range() - .with_message("smallestUnit or largestUnit must be present.") - .into()); - } + let rounded_duration = duration.inner.round( - rounding_increment, - smallest_unit, - largest_unit, - rounding_mode, + options, &RelativeTo { date: plain_relative_to.as_ref(), zdt: zoned_relative_to.as_ref(), diff --git a/core/engine/src/builtins/temporal/instant/mod.rs b/core/engine/src/builtins/temporal/instant/mod.rs index 3c02f38be5..3dbe6654d6 100644 --- a/core/engine/src/builtins/temporal/instant/mod.rs +++ b/core/engine/src/builtins/temporal/instant/mod.rs @@ -21,10 +21,9 @@ use crate::{ use boa_gc::{Finalize, Trace}; use boa_macros::js_str; use boa_profiler::Profiler; -use temporal_rs::{ - components::Instant as InnerInstant, - options::{RoundingIncrement, TemporalRoundingMode, TemporalUnit}, -}; +use temporal_rs::{components::Instant as InnerInstant, options::TemporalRoundingMode}; + +use super::options::get_difference_settings; /// The `Temporal.Instant` object. #[derive(Debug, Clone, Trace, Finalize, JsData)] @@ -285,15 +284,9 @@ impl Instant { let other = to_temporal_instant(args.get_or_undefined(0))?; // Fetch the necessary options. - let options = get_options_object(args.get_or_undefined(1))?; - let mode = get_option::(&options, js_str!("roundingMode"), context)?; - let increment = - get_option::(&options, js_str!("roundingIncrement"), context)?; - let smallest_unit = get_option::(&options, js_str!("smallestUnit"), context)?; - let largest_unit = get_option::(&options, js_str!("largestUnit"), context)?; - let result = instant - .inner - .until(&other, mode, increment, smallest_unit, largest_unit)?; + let settings = + get_difference_settings(&get_options_object(args.get_or_undefined(1))?, context)?; + let result = instant.inner.until(&other, settings)?; create_temporal_duration(result.into(), None, context).map(Into::into) } @@ -314,15 +307,9 @@ impl Instant { // 3. Return ? DifferenceTemporalInstant(since, instant, other, options). let other = to_temporal_instant(args.get_or_undefined(0))?; - let options = get_options_object(args.get_or_undefined(1))?; - let mode = get_option::(&options, js_str!("roundingMode"), context)?; - let increment = - get_option::(&options, js_str!("roundingIncrement"), context)?; - let smallest_unit = get_option::(&options, js_str!("smallestUnit"), context)?; - let largest_unit = get_option::(&options, js_str!("largestUnit"), context)?; - let result = instant - .inner - .since(&other, mode, increment, smallest_unit, largest_unit)?; + let settings = + get_difference_settings(&get_options_object(args.get_or_undefined(1))?, context)?; + let result = instant.inner.since(&other, settings)?; create_temporal_duration(result.into(), None, context).map(Into::into) } diff --git a/core/engine/src/builtins/temporal/options.rs b/core/engine/src/builtins/temporal/options.rs index 4de73d7fd9..8f7a5df0c7 100644 --- a/core/engine/src/builtins/temporal/options.rs +++ b/core/engine/src/builtins/temporal/options.rs @@ -13,9 +13,10 @@ use crate::{ string::JsStr, Context, JsNativeError, JsObject, JsResult, JsValue, }; +use boa_macros::js_str; use temporal_rs::options::{ - ArithmeticOverflow, DurationOverflow, InstantDisambiguation, OffsetDisambiguation, - RoundingIncrement, TemporalRoundingMode, TemporalUnit, + ArithmeticOverflow, DifferenceSettings, DurationOverflow, InstantDisambiguation, + OffsetDisambiguation, RoundingIncrement, TemporalRoundingMode, TemporalUnit, }; // TODO: Expand docs on the below options. @@ -46,6 +47,21 @@ pub(crate) fn get_temporal_unit( Ok(unit) } +#[inline] +pub(crate) fn get_difference_settings( + options: &JsObject, + context: &mut Context, +) -> JsResult { + let mut settings = DifferenceSettings::default(); + settings.rounding_mode = + get_option::(options, js_str!("roundingMode"), context)?; + settings.increment = + get_option::(options, js_str!("roundingIncrement"), context)?; + settings.smallest_unit = get_option::(options, js_str!("smallestUnit"), context)?; + settings.largest_unit = get_option::(options, js_str!("largestUnit"), context)?; + Ok(settings) +} + #[derive(Debug, Clone, Copy)] #[allow(unused)] pub(crate) enum TemporalUnitGroup { diff --git a/core/engine/src/builtins/temporal/plain_date/mod.rs b/core/engine/src/builtins/temporal/plain_date/mod.rs index a07fa7d87d..efbe2aa253 100644 --- a/core/engine/src/builtins/temporal/plain_date/mod.rs +++ b/core/engine/src/builtins/temporal/plain_date/mod.rs @@ -28,7 +28,10 @@ use temporal_rs::{ options::ArithmeticOverflow, }; -use super::{calendar, PlainDateTime, ZonedDateTime}; +use super::{ + calendar, create_temporal_duration, options::get_difference_settings, + to_temporal_duration_record, PlainDateTime, ZonedDateTime, +}; /// The `Temporal.PlainDate` object. #[derive(Debug, Clone, Trace, Finalize, JsData)] @@ -495,22 +498,84 @@ impl PlainDate { .into()) } - fn get_iso_fields(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { - Err(JsNativeError::error() - .with_message("not yet implemented.") - .into()) + fn get_iso_fields(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let temporalDate be the this value. + // 2. Perform ? RequireInternalSlot(temporalDate, [[InitializedTemporalDate]]). + let date = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a PlainDate object.") + })?; + + // 3. Let fields be OrdinaryObjectCreate(%Object.prototype%). + let fields = JsObject::with_object_proto(context.intrinsics()); + + // 4. Perform ! CreateDataPropertyOrThrow(fields, "calendar", temporalDate.[[Calendar]]). + fields.create_data_property_or_throw( + js_str!("calendar"), + JsString::from(date.inner.calendar().identifier()?), + context, + )?; + // 5. Perform ! CreateDataPropertyOrThrow(fields, "isoDay", 𝔽(temporalDate.[[ISODay]])). + fields.create_data_property_or_throw(js_str!("isoDay"), date.inner.iso_day(), context)?; + // 6. Perform ! CreateDataPropertyOrThrow(fields, "isoMonth", 𝔽(temporalDate.[[ISOMonth]])). + fields.create_data_property_or_throw( + js_str!("isoMonth"), + date.inner.iso_month(), + context, + )?; + // 7. Perform ! CreateDataPropertyOrThrow(fields, "isoYear", 𝔽(temporalDate.[[ISOYear]])). + fields.create_data_property_or_throw(js_str!("isoYear"), date.inner.iso_year(), context)?; + // 8. Return fields. + Ok(fields.into()) } - fn add(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { - Err(JsNativeError::error() - .with_message("not yet implemented.") - .into()) + fn add(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let temporalDate be the this value. + // 2. Perform ? RequireInternalSlot(temporalDate, [[InitializedTemporalDate]]). + let date = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a PlainDate object.") + })?; + + // 3. Let duration be ? ToTemporalDuration(temporalDurationLike). + let duration = to_temporal_duration_record(args.get_or_undefined(0), context)?; + + // 4. Set options to ? GetOptionsObject(options). + let options = get_options_object(args.get_or_undefined(1))?; + + let overflow = get_option::(&options, js_str!("overflow"), context)?; + + // 5. Let calendarRec be ? CreateCalendarMethodsRecord(temporalDate.[[Calendar]], « date-add »). + // 6. Return ? AddDate(calendarRec, temporalDate, duration, options). + create_temporal_date(date.inner.add(&duration, overflow)?, None, context).map(Into::into) } - fn subtract(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { - Err(JsNativeError::error() - .with_message("not yet implemented.") - .into()) + fn subtract(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let temporalDate be the this value. + // 2. Perform ? RequireInternalSlot(temporalDate, [[InitializedTemporalDate]]). + let date = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a PlainDate object.") + })?; + + // 3. Let duration be ? ToTemporalDuration(temporalDurationLike). + let duration = to_temporal_duration_record(args.get_or_undefined(0), context)?; + + // 4. Set options to ? GetOptionsObject(options). + let options = get_options_object(args.get_or_undefined(1))?; + let overflow = get_option::(&options, js_str!("overflow"), context)?; + + // 5. Let negatedDuration be CreateNegatedTemporalDuration(duration). + // 6. Let calendarRec be ? CreateCalendarMethodsRecord(temporalDate.[[Calendar]], « date-add »). + // 7. Return ? AddDate(calendarRec, temporalDate, negatedDuration, options). + create_temporal_date(date.inner.subtract(&duration, overflow)?, None, context) + .map(Into::into) } fn with(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { @@ -525,16 +590,44 @@ impl PlainDate { .into()) } - fn until(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { - Err(JsNativeError::error() - .with_message("not yet implemented.") - .into()) + fn until(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let temporalDate be the this value. + // 2. Perform ? RequireInternalSlot(temporalDate, [[InitializedTemporalDate]]). + let date = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a PlainDate object.") + })?; + + let other = to_temporal_date(args.get_or_undefined(0), None, context)?; + + // 3. Return ? DifferenceTemporalPlainDate(until, temporalDate, other, options). + let options = get_options_object(args.get_or_undefined(1))?; + let settings = get_difference_settings(&options, context)?; + + create_temporal_duration(date.inner.until(&other.inner, settings)?, None, context) + .map(Into::into) } - fn since(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { - Err(JsNativeError::error() - .with_message("not yet implemented.") - .into()) + fn since(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let temporalDate be the this value. + // 2. Perform ? RequireInternalSlot(temporalDate, [[InitializedTemporalDate]]). + let date = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a PlainDate object.") + })?; + + // 3. Return ? DifferenceTemporalPlainDate(since, temporalDate, other, options). + let other = to_temporal_date(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(date.inner.since(&other.inner, settings)?, None, context) + .map(Into::into) } fn equals(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { diff --git a/core/engine/src/builtins/temporal/plain_date_time/mod.rs b/core/engine/src/builtins/temporal/plain_date_time/mod.rs index 28b312ab53..20937a32b0 100644 --- a/core/engine/src/builtins/temporal/plain_date_time/mod.rs +++ b/core/engine/src/builtins/temporal/plain_date_time/mod.rs @@ -3,6 +3,7 @@ use crate::{ builtins::{ + options::{get_option, get_options_object}, temporal::{calendar, to_integer_with_truncation}, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, }, @@ -27,8 +28,11 @@ use temporal_rs::{ DateTime as InnerDateTime, }, iso::{IsoDate, IsoDateSlots}, + options::ArithmeticOverflow, }; +use super::to_temporal_duration_record; + /// The `Temporal.PlainDateTime` object. #[derive(Debug, Clone, Trace, Finalize, JsData)] #[boa_gc(unsafe_empty_trace)] // TODO: Remove this!!! `InnerDateTime` could contain `Trace` types. @@ -267,6 +271,8 @@ impl IntrinsicObject for PlainDateTime { None, Attribute::CONFIGURABLE, ) + .method(Self::add, js_string!("add"), 2) + .method(Self::subtract, js_string!("subtract"), 2) .build(); } @@ -290,7 +296,7 @@ impl BuiltInConstructor for PlainDateTime { if new_target.is_undefined() { // a. Throw a TypeError exception. return Err(JsNativeError::typ() - .with_message("NewTarget cannot be undefined when contructing PlainDateTime.") + .with_message("NewTarget cannot be undefined when contructing PlainDatedt.") .into()); }; @@ -348,309 +354,320 @@ impl BuiltInConstructor for PlainDateTime { // ==== `PlainDateTimeTime` accessor implmentations ==== impl PlainDateTime { - /// 5.3.3 get `Temporal.PlainDateTime.prototype.calendarId` + /// 5.3.3 get `Temporal.PlainDatedt.prototype.calendarId` fn get_calendar_id(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let date = this + let dt = this .as_object() .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("the this object must be a PlainDateTime object.") })?; - Ok(JsString::from(date.inner.calendar().identifier()?).into()) + Ok(JsString::from(dt.inner.calendar().identifier()?).into()) } - /// 5.3.4 get `Temporal.PlainDateTime.prototype.year` + /// 5.3.4 get `Temporal.PlainDatedt.prototype.year` fn get_year(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let obj = this + let dt = this .as_object() - .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - - let Some(date) = obj.downcast_ref::() else { - return Err(JsNativeError::typ() - .with_message("the this object must be a PlainDateTime object.") - .into()); - }; + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a PlainDateTime object.") + })?; - Ok(date.inner.year()?.into()) + Ok(dt.inner.year()?.into()) } - /// 5.3.5 get `Temporal.PlainDateTime.prototype.month` + /// 5.3.5 get `Temporal.PlainDatedt.prototype.month` fn get_month(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let obj = this + let dt = this .as_object() - .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - - let Some(date) = obj.downcast_ref::() else { - return Err(JsNativeError::typ() - .with_message("the this object must be a PlainDateTime object.") - .into()); - }; + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a PlainDateTime object.") + })?; - Ok(date.inner.month()?.into()) + Ok(dt.inner.month()?.into()) } - /// 5.3.6 get Temporal.PlainDateTime.prototype.monthCode + /// 5.3.6 get Temporal.PlainDatedt.prototype.monthCode fn get_month_code(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let obj = this + let dt = this .as_object() - .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - - let Some(date) = obj.downcast_ref::() else { - return Err(JsNativeError::typ() - .with_message("the this object must be a PlainDateTime object.") - .into()); - }; + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a PlainDateTime object.") + })?; - Ok(JsString::from(date.inner.month_code()?.as_str()).into()) + Ok(JsString::from(dt.inner.month_code()?.as_str()).into()) } - /// 5.3.7 get `Temporal.PlainDateTime.prototype.day` + /// 5.3.7 get `Temporal.PlainDatedt.prototype.day` fn get_day(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let obj = this + let dt = this .as_object() - .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - - let Some(date) = obj.downcast_ref::() else { - return Err(JsNativeError::typ() - .with_message("the this object must be a PlainDateTime object.") - .into()); - }; + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a PlainDateTime object.") + })?; - Ok(date.inner.day()?.into()) + Ok(dt.inner.day()?.into()) } - /// 5.3.8 get `Temporal.PlainDateTime.prototype.hour` + /// 5.3.8 get `Temporal.PlainDatedt.prototype.hour` fn get_hour(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { // 1. Let dateTime be the this value. // 2. Perform ? RequireInternalSlot(dateTime, [[InitializedTemporalDateTime]]). - let time = this + let dt = this .as_object() .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("the this object must be a PlainDateTime object.") })?; - // 3. Return 𝔽(dateTime.[[ISOHour]]). - Ok(time.inner.hour().into()) + // 3. Return 𝔽(datedt.[[ISOHour]]). + Ok(dt.inner.hour().into()) } - /// 5.3.9 get `Temporal.PlainDateTime.prototype.minute` + /// 5.3.9 get `Temporal.PlainDatedt.prototype.minute` fn get_minute(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { // 1. Let dateTime be the this value. // 2. Perform ? RequireInternalSlot(dateTime, [[InitializedTemporalDateTime]]). - let time = this + let dt = this .as_object() .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("the this object must be a PlainDateTime object.") })?; - // 3. Return 𝔽(dateTime.[[ISOMinute]]). - Ok(time.inner.minute().into()) + // 3. Return 𝔽(datedt.[[ISOMinute]]). + Ok(dt.inner.minute().into()) } - /// 5.3.10 get `Temporal.PlainDateTime.prototype.second` + /// 5.3.10 get `Temporal.PlainDatedt.prototype.second` fn get_second(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { // 1. Let dateTime be the this value. // 2. Perform ? RequireInternalSlot(dateTime, [[InitializedTemporalDateTime]]). - let time = this + let dt = this .as_object() .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("the this object must be a PlainDateTime object.") })?; - // 3. Return 𝔽(dateTime.[[ISOSecond]]). - Ok(time.inner.second().into()) + // 3. Return 𝔽(datedt.[[ISOSecond]]). + Ok(dt.inner.second().into()) } - /// 5.3.11 get `Temporal.PlainDateTime.prototype.millisecond` + /// 5.3.11 get `Temporal.PlainDatedt.prototype.millisecond` fn get_millisecond(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { // 1. Let dateTime be the this value. // 2. Perform ? RequireInternalSlot(dateTime, [[InitializedTemporalDateTime]]). - let time = this + let dt = this .as_object() .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("the this object must be a PlainDateTime object.") })?; - // 3. Return 𝔽(dateTime.[[ISOMillisecond]]). - Ok(time.inner.millisecond().into()) + // 3. Return 𝔽(datedt.[[ISOMillisecond]]). + Ok(dt.inner.millisecond().into()) } - /// 5.3.12 get `Temporal.PlainDateTime.prototype.microsecond` + /// 5.3.12 get `Temporal.PlainDatedt.prototype.microsecond` fn get_microsecond(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { // 1. Let dateTime be the this value. // 2. Perform ? RequireInternalSlot(dateTime, [[InitializedTemporalDateTime]]). - let time = this + let dt = this .as_object() .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("the this object must be a PlainDateTime object.") })?; - // 3. Return 𝔽(dateTime.[[ISOMicrosecond]]). - Ok(time.inner.microsecond().into()) + // 3. Return 𝔽(datedt.[[ISOMicrosecond]]). + Ok(dt.inner.microsecond().into()) } - /// 5.3.13 get `Temporal.PlainDateTime.prototype.nanosecond` + /// 5.3.13 get `Temporal.PlainDatedt.prototype.nanosecond` fn get_nanosecond(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { // 1. Let dateTime be the this value. // 2. Perform ? RequireInternalSlot(dateTime, [[InitializedTemporalDateTime]]). - let time = this + let dt = this .as_object() .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("the this object must be a PlainDateTime object.") })?; - // 3. Return 𝔽(dateTime.[[ISONanosecond]]). - Ok(time.inner.nanosecond().into()) + // 3. Return 𝔽(datedt.[[ISONanosecond]]). + Ok(dt.inner.nanosecond().into()) } - /// 5.3.14 get `Temporal.PlainDateTime.prototype.dayOfWeek` + /// 5.3.14 get `Temporal.PlainDatedt.prototype.dayOfWeek` fn get_day_of_week(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let obj = this + let dt = this .as_object() - .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - - let Some(date) = obj.downcast_ref::() else { - return Err(JsNativeError::typ() - .with_message("the this object must be a PlainDateTime object.") - .into()); - }; + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a PlainDateTime object.") + })?; - Ok(date.inner.day_of_week()?.into()) + Ok(dt.inner.day_of_week()?.into()) } - /// 5.3.15 get `Temporal.PlainDateTime.prototype.dayOfYear` + /// 5.3.15 get `Temporal.PlainDatedt.prototype.dayOfYear` fn get_day_of_year(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let obj = this + let dt = this .as_object() - .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - - let Some(date) = obj.downcast_ref::() else { - return Err(JsNativeError::typ() - .with_message("the this object must be a PlainDateTime object.") - .into()); - }; + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a PlainDateTime object.") + })?; - Ok(date.inner.day_of_year()?.into()) + Ok(dt.inner.day_of_year()?.into()) } - /// 5.3.16 get `Temporal.PlainDateTime.prototype.weekOfYear` + /// 5.3.16 get `Temporal.PlainDatedt.prototype.weekOfYear` fn get_week_of_year(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let obj = this + let dt = this .as_object() - .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - - let Some(date) = obj.downcast_ref::() else { - return Err(JsNativeError::typ() - .with_message("the this object must be a PlainDateTime object.") - .into()); - }; + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a PlainDateTime object.") + })?; - Ok(date.inner.week_of_year()?.into()) + Ok(dt.inner.week_of_year()?.into()) } - /// 5.3.17 get `Temporal.PlainDateTime.prototype.yearOfWeek` + /// 5.3.17 get `Temporal.PlainDatedt.prototype.yearOfWeek` fn get_year_of_week(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let obj = this + let dt = this .as_object() - .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - - let Some(date) = obj.downcast_ref::() else { - return Err(JsNativeError::typ() - .with_message("the this object must be a PlainDateTime object.") - .into()); - }; + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a PlainDateTime object.") + })?; - Ok(date.inner.year_of_week()?.into()) + Ok(dt.inner.year_of_week()?.into()) } - /// 5.3.18 get `Temporal.PlainDateTime.prototype.daysInWeek` + /// 5.3.18 get `Temporal.PlainDatedt.prototype.daysInWeek` fn get_days_in_week(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let obj = this + let dt = this .as_object() - .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - - let Some(date) = obj.downcast_ref::() else { - return Err(JsNativeError::typ() - .with_message("the this object must be a PlainDateTime object.") - .into()); - }; + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a PlainDateTime object.") + })?; - Ok(date.inner.days_in_week()?.into()) + Ok(dt.inner.days_in_week()?.into()) } - /// 5.3.19 get `Temporal.PlainDateTime.prototype.daysInMonth` + /// 5.3.19 get `Temporal.PlainDatedt.prototype.daysInMonth` fn get_days_in_month( this: &JsValue, _: &[JsValue], context: &mut Context, ) -> JsResult { - let obj = this + let dt = this .as_object() - .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - - let Some(date) = obj.downcast_ref::() else { - return Err(JsNativeError::typ() - .with_message("the this object must be a PlainDateTime object.") - .into()); - }; + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a PlainDateTime object.") + })?; - Ok(date.inner.days_in_month()?.into()) + Ok(dt.inner.days_in_month()?.into()) } - /// 5.3.20 get `Temporal.PlainDateTime.prototype.daysInYear` + /// 5.3.20 get `Temporal.PlainDatedt.prototype.daysInYear` fn get_days_in_year(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let obj = this + let dt = this .as_object() - .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - - let Some(date) = obj.downcast_ref::() else { - return Err(JsNativeError::typ() - .with_message("the this object must be a PlainDateTime object.") - .into()); - }; + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a PlainDateTime object.") + })?; - Ok(date.inner.days_in_year()?.into()) + Ok(dt.inner.days_in_year()?.into()) } - /// 5.3.21 get `Temporal.PlainDateTime.prototype.monthsInYear` + /// 5.3.21 get `Temporal.PlainDatedt.prototype.monthsInYear` fn get_months_in_year( this: &JsValue, _: &[JsValue], context: &mut Context, ) -> JsResult { - let obj = this + let dt = this .as_object() - .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - - let Some(date) = obj.downcast_ref::() else { - return Err(JsNativeError::typ() - .with_message("the this object must be a PlainDateTime object.") - .into()); - }; + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a PlainDateTime object.") + })?; - Ok(date.inner.months_in_year()?.into()) + Ok(dt.inner.months_in_year()?.into()) } - /// 5.3.22 get `Temporal.PlainDateTime.prototype.inLeapYear` + /// 5.3.22 get `Temporal.PlainDatedt.prototype.inLeapYear` fn get_in_leap_year(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let obj = this + let dt = this .as_object() - .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a PlainDateTime object.") + })?; - let Some(date) = obj.downcast_ref::() else { - return Err(JsNativeError::typ() - .with_message("the this object must be a PlainDateTime object.") - .into()); - }; + Ok(dt.inner.in_leap_year()?.into()) + } +} + +// ==== PlainDateTime method implementations ==== + +impl PlainDateTime { + fn add(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let temporalDate be the this value. + // 2. Perform ? RequireInternalSlot(temporalDate, [[InitializedTemporalDate]]). + let dt = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a PlainDateTime object.") + })?; + + // 3. Let duration be ? ToTemporalDuration(temporalDurationLike). + let duration = to_temporal_duration_record(args.get_or_undefined(0), context)?; + + // 4. Set options to ? GetOptionsObject(options). + let options = get_options_object(args.get_or_undefined(1))?; + let overflow = get_option::(&options, js_str!("overflow"), context)?; + + // 5. Let calendarRec be ? CreateCalendarMethodsRecord(temporalDate.[[Calendar]], « date-add »). + // 6. Return ? AddDate(calendarRec, temporalDate, duration, options). + create_temporal_datetime(dt.inner.add(&duration, overflow)?, None, context).map(Into::into) + } + + fn subtract(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let temporalDate be the this value. + // 2. Perform ? RequireInternalSlot(temporalDate, [[InitializedTemporalDate]]). + let dt = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be a PlainDateTime object.") + })?; + + // 3. Let duration be ? ToTemporalDuration(temporalDurationLike). + let duration = to_temporal_duration_record(args.get_or_undefined(0), context)?; + + // 4. Set options to ? GetOptionsObject(options). + let options = get_options_object(args.get_or_undefined(1))?; + let overflow = get_option::(&options, js_str!("overflow"), context)?; - Ok(date.inner.in_leap_year()?.into()) + // 5. Let negatedDuration be CreateNegatedTemporalDuration(duration). + // 6. Let calendarRec be ? CreateCalendarMethodsRecord(temporalDate.[[Calendar]], « date-add »). + // 7. Return ? AddDate(calendarRec, temporalDate, negatedDuration, options). + create_temporal_datetime(dt.inner.subtract(&duration, overflow)?, None, context) + .map(Into::into) } } @@ -680,7 +697,7 @@ pub(crate) fn create_temporal_datetime( .into() }; - // 5. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.PlainDateTime.prototype%", « [[InitializedTemporalDateTime]], [[ISOYear]], [[ISOMonth]], [[ISODay]], [[ISOHour]], [[ISOMinute]], [[ISOSecond]], [[ISOMillisecond]], [[ISOMicrosecond]], [[ISONanosecond]], [[Calendar]] »). + // 5. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.PlainDatedt.prototype%", « [[InitializedTemporalDateTime]], [[ISOYear]], [[ISOMonth]], [[ISODay]], [[ISOHour]], [[ISOMinute]], [[ISOSecond]], [[ISOMillisecond]], [[ISOMicrosecond]], [[ISONanosecond]], [[Calendar]] »). let prototype = get_prototype_from_constructor( &new_target, StandardConstructors::plain_date_time,