From 34d5c519eae74b8b1e3fb32e00bb9ea00ff1dc88 Mon Sep 17 00:00:00 2001 From: Kevin <46825870+nekevss@users.noreply.github.com> Date: Fri, 3 Nov 2023 19:57:16 -0400 Subject: [PATCH] Duration methods and some cleanup (#3443) --- .../src/builtins/temporal/calendar/iso.rs | 134 +-- .../src/builtins/temporal/calendar/mod.rs | 353 ++++--- .../src/builtins/temporal/duration/mod.rs | 241 +---- .../src/builtins/temporal/duration/record.rs | 951 +++++++++--------- boa_engine/src/builtins/temporal/fields.rs | 23 +- .../src/builtins/temporal/instant/mod.rs | 4 +- boa_engine/src/builtins/temporal/mod.rs | 2 +- .../src/builtins/temporal/plain_date/iso.rs | 30 +- .../src/builtins/temporal/plain_date/mod.rs | 115 ++- 9 files changed, 923 insertions(+), 930 deletions(-) diff --git a/boa_engine/src/builtins/temporal/calendar/iso.rs b/boa_engine/src/builtins/temporal/calendar/iso.rs index 4074aa3fa97..3abb440c777 100644 --- a/boa_engine/src/builtins/temporal/calendar/iso.rs +++ b/boa_engine/src/builtins/temporal/calendar/iso.rs @@ -2,18 +2,16 @@ use crate::{ builtins::temporal::{ - self, create_temporal_date, + self, date_equations::mathematical_days_in_year, + duration::DurationRecord, options::{ArithmeticOverflow, TemporalUnit}, plain_date::iso::IsoDateRecord, }, - js_string, - property::PropertyKey, - string::utf16, - Context, JsNativeError, JsResult, JsString, JsValue, + js_string, JsNativeError, JsResult, JsString, }; -use super::BuiltinCalendar; +use super::{BuiltinCalendar, FieldsType}; use icu_calendar::{ iso::Iso, @@ -31,8 +29,7 @@ impl BuiltinCalendar for IsoCalendar { &self, fields: &mut temporal::TemporalFields, overflow: ArithmeticOverflow, - context: &mut Context<'_>, - ) -> JsResult { + ) -> JsResult { // NOTE: we are in ISO by default here. // a. Perform ? ISOResolveMonth(fields). // b. Let result be ? ISODateFromFields(fields, overflow). @@ -49,13 +46,7 @@ impl BuiltinCalendar for IsoCalendar { .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; // 9. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], "iso8601"). - Ok(create_temporal_date( - IsoDateRecord::from_date_iso(date), - js_string!("iso8601").into(), - None, - context, - )? - .into()) + Ok(IsoDateRecord::from_date_iso(date)) } /// 12.5.5 `Temporal.Calendar.prototype.yearMonthFromFields ( fields [ , options ] )` @@ -65,8 +56,7 @@ impl BuiltinCalendar for IsoCalendar { &self, fields: &mut temporal::TemporalFields, overflow: ArithmeticOverflow, - context: &mut Context<'_>, - ) -> JsResult { + ) -> JsResult { // 9. If calendar.[[Identifier]] is "iso8601", then // a. Perform ? ISOResolveMonth(fields). fields.iso_resolve_month()?; @@ -82,12 +72,7 @@ impl BuiltinCalendar for IsoCalendar { .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; // 10. Return ? CreateTemporalYearMonth(result.[[Year]], result.[[Month]], "iso8601", result.[[ReferenceISODay]]). - temporal::create_temporal_year_month( - IsoDateRecord::from_date_iso(result), - js_string!("iso8601").into(), - None, - context, - ) + Ok(IsoDateRecord::from_date_iso(result)) } /// 12.5.6 `Temporal.Calendar.prototype.monthDayFromFields ( fields [ , options ] )` @@ -97,8 +82,7 @@ impl BuiltinCalendar for IsoCalendar { &self, fields: &mut temporal::TemporalFields, overflow: ArithmeticOverflow, - context: &mut Context<'_>, - ) -> JsResult { + ) -> JsResult { // 8. Perform ? ISOResolveMonth(fields). fields.iso_resolve_month()?; @@ -114,12 +98,7 @@ impl BuiltinCalendar for IsoCalendar { .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; // 10. Return ? CreateTemporalMonthDay(result.[[Month]], result.[[Day]], "iso8601", result.[[ReferenceISOYear]]). - temporal::create_temporal_month_day( - IsoDateRecord::from_date_iso(result), - js_string!("iso8601").into(), - None, - context, - ) + Ok(IsoDateRecord::from_date_iso(result)) } /// 12.5.7 `Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] )` @@ -128,10 +107,9 @@ impl BuiltinCalendar for IsoCalendar { fn date_add( &self, _date: &temporal::PlainDate, - _duration: &temporal::duration::DurationRecord, + _duration: &DurationRecord, _overflow: ArithmeticOverflow, - _context: &mut Context<'_>, - ) -> JsResult { + ) -> JsResult { // TODO: Not stable on `ICU4X`. Implement once completed. Err(JsNativeError::range() .with_message("feature not implemented.") @@ -149,8 +127,7 @@ impl BuiltinCalendar for IsoCalendar { _one: &temporal::PlainDate, _two: &temporal::PlainDate, _largest_unit: TemporalUnit, - _: &mut Context<'_>, - ) -> JsResult { + ) -> JsResult { // TODO: Not stable on `ICU4X`. Implement once completed. Err(JsNativeError::range() .with_message("Feature not yet implemented.") @@ -161,19 +138,19 @@ impl BuiltinCalendar for IsoCalendar { } /// `Temporal.Calendar.prototype.era( dateLike )` for iso8601 calendar. - fn era(&self, _: &IsoDateRecord, _: &mut Context<'_>) -> JsResult { + fn era(&self, _: &IsoDateRecord) -> JsResult> { // Returns undefined on iso8601. - Ok(JsValue::undefined()) + Ok(None) } /// `Temporal.Calendar.prototype.eraYear( dateLike )` for iso8601 calendar. - fn era_year(&self, _: &IsoDateRecord, _: &mut Context<'_>) -> JsResult { + fn era_year(&self, _: &IsoDateRecord) -> JsResult> { // Returns undefined on iso8601. - Ok(JsValue::undefined()) + Ok(None) } /// Returns the `year` for the `Iso` calendar. - fn year(&self, date_like: &IsoDateRecord, _: &mut Context<'_>) -> JsResult { + fn year(&self, date_like: &IsoDateRecord) -> JsResult { let date = Date::try_new_iso_date( date_like.year(), date_like.month() as u8, @@ -181,11 +158,11 @@ impl BuiltinCalendar for IsoCalendar { ) .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; - Ok(date.year().number.into()) + Ok(date.year().number) } /// Returns the `month` for the `Iso` calendar. - fn month(&self, date_like: &IsoDateRecord, _: &mut Context<'_>) -> JsResult { + fn month(&self, date_like: &IsoDateRecord) -> JsResult { let date = Date::try_new_iso_date( date_like.year(), date_like.month() as u8, @@ -193,11 +170,11 @@ impl BuiltinCalendar for IsoCalendar { ) .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; - Ok(date.month().ordinal.into()) + Ok(date.month().ordinal as i32) } /// Returns the `monthCode` for the `Iso` calendar. - fn month_code(&self, date_like: &IsoDateRecord, _: &mut Context<'_>) -> JsResult { + fn month_code(&self, date_like: &IsoDateRecord) -> JsResult { let date = Date::try_new_iso_date( date_like.year(), date_like.month() as u8, @@ -205,11 +182,11 @@ impl BuiltinCalendar for IsoCalendar { ) .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; - Ok(JsString::from(date.month().code.to_string()).into()) + Ok(JsString::from(date.month().code.to_string())) } /// Returns the `day` for the `Iso` calendar. - fn day(&self, date_like: &IsoDateRecord, _: &mut Context<'_>) -> JsResult { + fn day(&self, date_like: &IsoDateRecord) -> JsResult { let date = Date::try_new_iso_date( date_like.year(), date_like.month() as u8, @@ -217,11 +194,11 @@ impl BuiltinCalendar for IsoCalendar { ) .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; - Ok(date.day_of_month().0.into()) + Ok(date.day_of_month().0 as i32) } /// Returns the `dayOfWeek` for the `Iso` calendar. - fn day_of_week(&self, date_like: &IsoDateRecord, _: &mut Context<'_>) -> JsResult { + fn day_of_week(&self, date_like: &IsoDateRecord) -> JsResult { let date = Date::try_new_iso_date( date_like.year(), date_like.month() as u8, @@ -229,11 +206,11 @@ impl BuiltinCalendar for IsoCalendar { ) .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; - Ok((date.day_of_week() as u8).into()) + Ok(date.day_of_week() as i32) } /// Returns the `dayOfYear` for the `Iso` calendar. - fn day_of_year(&self, date_like: &IsoDateRecord, _: &mut Context<'_>) -> JsResult { + fn day_of_year(&self, date_like: &IsoDateRecord) -> JsResult { let date = Date::try_new_iso_date( date_like.year(), date_like.month() as u8, @@ -241,11 +218,11 @@ impl BuiltinCalendar for IsoCalendar { ) .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; - Ok(i32::from(date.day_of_year_info().day_of_year).into()) + Ok(i32::from(date.day_of_year_info().day_of_year)) } /// Returns the `weekOfYear` for the `Iso` calendar. - fn week_of_year(&self, date_like: &IsoDateRecord, _: &mut Context<'_>) -> JsResult { + fn week_of_year(&self, date_like: &IsoDateRecord) -> JsResult { // TODO: Determine `ICU4X` equivalent. let date = Date::try_new_iso_date( date_like.year(), @@ -260,11 +237,11 @@ impl BuiltinCalendar for IsoCalendar { .week_of_year(&week_calculator) .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; - Ok(week_of.week.into()) + Ok(i32::from(week_of.week)) } /// Returns the `yearOfWeek` for the `Iso` calendar. - fn year_of_week(&self, date_like: &IsoDateRecord, _: &mut Context<'_>) -> JsResult { + fn year_of_week(&self, date_like: &IsoDateRecord) -> JsResult { // TODO: Determine `ICU4X` equivalent. let date = Date::try_new_iso_date( date_like.year(), @@ -280,19 +257,19 @@ impl BuiltinCalendar for IsoCalendar { .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; match week_of.unit { - RelativeUnit::Previous => Ok((date.year().number - 1).into()), - RelativeUnit::Current => Ok(date.year().number.into()), - RelativeUnit::Next => Ok((date.year().number + 1).into()), + RelativeUnit::Previous => Ok(date.year().number - 1), + RelativeUnit::Current => Ok(date.year().number), + RelativeUnit::Next => Ok(date.year().number + 1), } } /// Returns the `daysInWeek` value for the `Iso` calendar. - fn days_in_week(&self, _: &IsoDateRecord, _: &mut Context<'_>) -> JsResult { - Ok(7.into()) + fn days_in_week(&self, _: &IsoDateRecord) -> JsResult { + Ok(7) } /// Returns the `daysInMonth` value for the `Iso` calendar. - fn days_in_month(&self, date_like: &IsoDateRecord, _: &mut Context<'_>) -> JsResult { + fn days_in_month(&self, date_like: &IsoDateRecord) -> JsResult { let date = Date::try_new_iso_date( date_like.year(), date_like.month() as u8, @@ -300,11 +277,11 @@ impl BuiltinCalendar for IsoCalendar { ) .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; - Ok(date.days_in_month().into()) + Ok(i32::from(date.days_in_month())) } /// Returns the `daysInYear` value for the `Iso` calendar. - fn days_in_year(&self, date_like: &IsoDateRecord, _: &mut Context<'_>) -> JsResult { + fn days_in_year(&self, date_like: &IsoDateRecord) -> JsResult { let date = Date::try_new_iso_date( date_like.year(), date_like.month() as u8, @@ -312,31 +289,31 @@ impl BuiltinCalendar for IsoCalendar { ) .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; - Ok(date.days_in_year().into()) + Ok(i32::from(date.days_in_year())) } /// Return the amount of months in an ISO Calendar. - fn months_in_year(&self, _: &IsoDateRecord, _: &mut Context<'_>) -> JsResult { - Ok(12.into()) + fn months_in_year(&self, _: &IsoDateRecord) -> JsResult { + Ok(12) } /// Returns whether provided date is in a leap year according to this calendar. - fn in_leap_year(&self, date_like: &IsoDateRecord, _: &mut Context<'_>) -> JsResult { + fn in_leap_year(&self, date_like: &IsoDateRecord) -> JsResult { // `ICU4X`'s `CalendarArithmetic` is currently private. if mathematical_days_in_year(date_like.year()) == 366 { - return Ok(true.into()); + return Ok(true); } - Ok(false.into()) + Ok(false) } // Resolve the fields for the iso calendar. - fn resolve_fields(&self, fields: &mut temporal::TemporalFields, _: &str) -> JsResult<()> { + fn resolve_fields(&self, fields: &mut temporal::TemporalFields, _: FieldsType) -> JsResult<()> { fields.iso_resolve_month()?; Ok(()) } /// Returns the ISO field descriptors, which is not called for the iso8601 calendar. - fn field_descriptors(&self, _: &[String]) -> Vec<(String, bool)> { + fn field_descriptors(&self, _: FieldsType) -> Vec<(JsString, bool)> { // NOTE(potential improvement): look into implementing field descriptors and call // ISO like any other calendar? // Field descriptors is unused on ISO8601. @@ -344,15 +321,14 @@ impl BuiltinCalendar for IsoCalendar { } /// Returns the `CalendarFieldKeysToIgnore` implementation for ISO. - fn field_keys_to_ignore(&self, additional_keys: Vec) -> Vec { + fn field_keys_to_ignore(&self, additional_keys: Vec) -> Vec { let mut result = Vec::new(); - for key in additional_keys { - let key_string = key.to_string(); - result.push(key); - if key_string.as_str() == "month" { - result.push(utf16!("monthCode").into()); - } else if key_string.as_str() == "monthCode" { - result.push(utf16!("month").into()); + for key in &additional_keys { + result.push(key.clone()); + if key.to_std_string_escaped().as_str() == "month" { + result.push(js_string!("monthCode")); + } else if key.to_std_string_escaped().as_str() == "monthCode" { + result.push(js_string!("month")); } } result diff --git a/boa_engine/src/builtins/temporal/calendar/mod.rs b/boa_engine/src/builtins/temporal/calendar/mod.rs index d58f35f6807..61d14189b9f 100644 --- a/boa_engine/src/builtins/temporal/calendar/mod.rs +++ b/boa_engine/src/builtins/temporal/calendar/mod.rs @@ -3,9 +3,11 @@ use self::iso::IsoCalendar; use super::{ + create_temporal_date, create_temporal_duration, create_temporal_month_day, + create_temporal_year_month, options::{ArithmeticOverflow, TemporalUnit, TemporalUnitGroup}, plain_date::iso::IsoDateRecord, - PlainDate, TemporalFields, + DurationRecord, PlainDate, TemporalFields, }; use crate::{ builtins::{ @@ -16,7 +18,7 @@ use crate::{ context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, object::{internal_methods::get_prototype_from_constructor, ObjectData}, - property::{Attribute, PropertyKey}, + property::Attribute, realm::Realm, string::{common::StaticJsStrings, utf16}, Context, JsArgs, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, @@ -30,6 +32,27 @@ pub(crate) mod utils; #[cfg(test)] mod tests; +pub(crate) enum FieldsType { + Date, + YearMonth, + MonthDay, +} + +impl From<&[JsString]> for FieldsType { + fn from(value: &[JsString]) -> Self { + let year_present = value.contains(&js_string!("year")); + let day_present = value.contains(&js_string!("day")); + + if year_present && day_present { + FieldsType::Date + } else if year_present { + FieldsType::YearMonth + } else { + FieldsType::MonthDay + } + } +} + // TODO: Determine how many methods actually need the context on them while using // `icu_calendar`. // @@ -42,111 +65,70 @@ pub(crate) trait BuiltinCalendar { &self, fields: &mut TemporalFields, overflow: ArithmeticOverflow, - context: &mut Context<'_>, - ) -> JsResult; + ) -> JsResult; /// Creates a `Temporal.PlainYearMonth` object from the provided fields. fn year_month_from_fields( &self, fields: &mut TemporalFields, overflow: ArithmeticOverflow, - context: &mut Context<'_>, - ) -> JsResult; + ) -> JsResult; /// Creates a `Temporal.PlainMonthDay` object from the provided fields. fn month_day_from_fields( &self, fields: &mut TemporalFields, overflow: ArithmeticOverflow, - context: &mut Context<'_>, - ) -> JsResult; + ) -> JsResult; /// Returns a `Temporal.PlainDate` based off an added date. fn date_add( &self, date: &PlainDate, - duration: &temporal::DurationRecord, + duration: &DurationRecord, overflow: ArithmeticOverflow, - context: &mut Context<'_>, - ) -> JsResult; + ) -> JsResult; /// Returns a `Temporal.Duration` representing the duration between two dates. fn date_until( &self, one: &PlainDate, two: &PlainDate, largest_unit: TemporalUnit, - context: &mut Context<'_>, - ) -> JsResult; + ) -> JsResult; /// Returns the era for a given `temporaldatelike`. - fn era(&self, date_like: &IsoDateRecord, context: &mut Context<'_>) -> JsResult; + fn era(&self, date_like: &IsoDateRecord) -> JsResult>; /// Returns the era year for a given `temporaldatelike` - fn era_year(&self, date_like: &IsoDateRecord, context: &mut Context<'_>) -> JsResult; + fn era_year(&self, date_like: &IsoDateRecord) -> JsResult>; /// Returns the `year` for a given `temporaldatelike` - fn year(&self, date_like: &IsoDateRecord, context: &mut Context<'_>) -> JsResult; + fn year(&self, date_like: &IsoDateRecord) -> JsResult; /// Returns the `month` for a given `temporaldatelike` - fn month(&self, date_like: &IsoDateRecord, context: &mut Context<'_>) -> JsResult; + fn month(&self, date_like: &IsoDateRecord) -> JsResult; + // Note: Best practice would probably be to switch to a MonthCode enum after extraction. /// Returns the `monthCode` for a given `temporaldatelike` - fn month_code(&self, date_like: &IsoDateRecord, context: &mut Context<'_>) - -> JsResult; + fn month_code(&self, date_like: &IsoDateRecord) -> JsResult; /// Returns the `day` for a given `temporaldatelike` - fn day(&self, date_like: &IsoDateRecord, context: &mut Context<'_>) -> JsResult; + fn day(&self, date_like: &IsoDateRecord) -> JsResult; /// Returns a value representing the day of the week for a date. - fn day_of_week( - &self, - date_like: &IsoDateRecord, - context: &mut Context<'_>, - ) -> JsResult; + fn day_of_week(&self, date_like: &IsoDateRecord) -> JsResult; /// Returns a value representing the day of the year for a given calendar. - fn day_of_year( - &self, - date_like: &IsoDateRecord, - context: &mut Context<'_>, - ) -> JsResult; + fn day_of_year(&self, date_like: &IsoDateRecord) -> JsResult; /// Returns a value representing the week of the year for a given calendar. - fn week_of_year( - &self, - date_like: &IsoDateRecord, - context: &mut Context<'_>, - ) -> JsResult; + fn week_of_year(&self, date_like: &IsoDateRecord) -> JsResult; /// Returns the year of a given week. - fn year_of_week( - &self, - date_like: &IsoDateRecord, - context: &mut Context<'_>, - ) -> JsResult; + fn year_of_week(&self, date_like: &IsoDateRecord) -> JsResult; /// Returns the days in a week for a given calendar. - fn days_in_week( - &self, - date_like: &IsoDateRecord, - context: &mut Context<'_>, - ) -> JsResult; + fn days_in_week(&self, date_like: &IsoDateRecord) -> JsResult; /// Returns the days in a month for a given calendar. - fn days_in_month( - &self, - date_like: &IsoDateRecord, - context: &mut Context<'_>, - ) -> JsResult; + fn days_in_month(&self, date_like: &IsoDateRecord) -> JsResult; /// Returns the days in a year for a given calendar. - fn days_in_year( - &self, - date_like: &IsoDateRecord, - context: &mut Context<'_>, - ) -> JsResult; + fn days_in_year(&self, date_like: &IsoDateRecord) -> JsResult; /// Returns the months in a year for a given calendar. - fn months_in_year( - &self, - date_like: &IsoDateRecord, - context: &mut Context<'_>, - ) -> JsResult; + fn months_in_year(&self, date_like: &IsoDateRecord) -> JsResult; /// Returns whether a value is within a leap year according to the designated calendar. - fn in_leap_year( - &self, - date_like: &IsoDateRecord, - context: &mut Context<'_>, - ) -> JsResult; + fn in_leap_year(&self, date_like: &IsoDateRecord) -> JsResult; /// Resolve the `TemporalFields` for the implemented Calendar - fn resolve_fields(&self, fields: &mut TemporalFields, r#type: &str) -> JsResult<()>; + fn resolve_fields(&self, fields: &mut TemporalFields, r#type: FieldsType) -> JsResult<()>; /// Return this calendar's a fieldName and whether it is required depending on type (date, day-month). - fn field_descriptors(&self, r#type: &[String]) -> Vec<(String, bool)>; + fn field_descriptors(&self, r#type: FieldsType) -> Vec<(JsString, bool)>; /// Return the fields to ignore for this Calendar based on provided keys. - fn field_keys_to_ignore(&self, additional_keys: Vec) -> Vec; + fn field_keys_to_ignore(&self, additional_keys: Vec) -> Vec; /// Debug name fn debug_name(&self) -> &str; } @@ -334,16 +316,16 @@ impl Calendar { // 5. Let relevantFieldNames be « "day", "month", "monthCode", "year" ». let mut relevant_field_names = Vec::from([ - "day".to_owned(), - "month".to_owned(), - "monthCode".to_owned(), - "year".to_owned(), + js_string!("day"), + js_string!("month"), + js_string!("monthCode"), + js_string!("year"), ]); // 6. If calendar.[[Identifier]] is "iso8601", then let mut fields = if calendar.identifier.as_slice() == ISO { // a. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « "year", "day" »). - let mut required_fields = Vec::from(["year".to_owned(), "day".to_owned()]); + let mut required_fields = Vec::from([js_string!("year"), js_string!("day")]); temporal::TemporalFields::from_js_object( fields_obj, &mut relevant_field_names, @@ -356,7 +338,7 @@ impl Calendar { // 7. Else, } else { // a. Let calendarRelevantFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], date). - let calendar_relevant_fields = this_calendar.field_descriptors(&["date".to_owned()]); + let calendar_relevant_fields = this_calendar.field_descriptors(FieldsType::Date); // b. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « », calendarRelevantFieldDescriptors). temporal::TemporalFields::from_js_object( fields_obj, @@ -381,7 +363,10 @@ impl Calendar { // a. Perform ? CalendarResolveFields(calendar.[[Identifier]], fields, date). // b. Let result be ? CalendarDateToISO(calendar.[[Identifier]], fields, overflow). - this_calendar.date_from_fields(&mut fields, overflow, context) + let result = this_calendar.date_from_fields(&mut fields, overflow)?; + + create_temporal_date(result, calendar.identifier.clone().into(), None, context) + .map(Into::into) } /// 15.8.2.2 `Temporal.Calendar.prototype.yearMonthFromFields ( fields [ , options ] )` - Supercedes 12.5.5 @@ -412,15 +397,15 @@ impl Calendar { let options = get_options_object(args.get_or_undefined(1))?; let mut relevant_field_names = Vec::from([ - "year".to_owned(), - "month".to_owned(), - "monthCode".to_owned(), + js_string!("year"), + js_string!("month"), + js_string!("monthCode"), ]); // 6. Set fields to ? PrepareTemporalFields(fields, « "month", "monthCode", "year" », « "year" »). let mut fields = if calendar.identifier.as_slice() == ISO { // a. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « "year" »). - let mut required_fields = Vec::from(["year".to_owned()]); + let mut required_fields = Vec::from([js_string!("year")]); temporal::TemporalFields::from_js_object( fields_obj, &mut relevant_field_names, @@ -434,8 +419,7 @@ impl Calendar { // a. Let calendarRelevantFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], year-month). // b. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « », calendarRelevantFieldDescriptors). - let calendar_relevant_fields = - this_calendar.field_descriptors(&["year-month".to_owned()]); + let calendar_relevant_fields = this_calendar.field_descriptors(FieldsType::YearMonth); temporal::TemporalFields::from_js_object( fields_obj, &mut relevant_field_names, @@ -454,7 +438,10 @@ impl Calendar { let overflow = get_option::(&options, utf16!("overflow"), context)? .unwrap_or(ArithmeticOverflow::Constrain); - this_calendar.year_month_from_fields(&mut fields, overflow, context) + let result = this_calendar.year_month_from_fields(&mut fields, overflow)?; + + create_temporal_year_month(result, calendar.identifier.clone().into(), None, context) + .map(Into::into) } /// 15.8.2.3 `Temporal.Calendar.prototype.monthDayFromFields ( fields [ , options ] )` - Supercedes 12.5.6 @@ -490,16 +477,16 @@ impl Calendar { // 5. Let relevantFieldNames be « "day", "month", "monthCode", "year" ». let mut relevant_field_names = Vec::from([ - "day".to_owned(), - "month".to_owned(), - "monthCode".to_owned(), - "year".to_owned(), + js_string!("day"), + js_string!("month"), + js_string!("monthCode"), + js_string!("year"), ]); // 6. If calendar.[[Identifier]] is "iso8601", then let mut fields = if calendar.identifier.as_slice() == ISO { // a. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « "day" »). - let mut required_fields = Vec::from(["day".to_owned()]); + let mut required_fields = Vec::from([js_string!("day")]); temporal::TemporalFields::from_js_object( fields_obj, &mut relevant_field_names, @@ -512,8 +499,7 @@ impl Calendar { // 7. Else, } else { // a. Let calendarRelevantFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], month-day). - let calendar_relevant_fields = - this_calendar.field_descriptors(&["month-day".to_owned()]); + let calendar_relevant_fields = this_calendar.field_descriptors(FieldsType::MonthDay); // b. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « », calendarRelevantFieldDescriptors). temporal::TemporalFields::from_js_object( fields_obj, @@ -530,7 +516,10 @@ impl Calendar { let overflow = get_option(&options, utf16!("overflow"), context)? .unwrap_or(ArithmeticOverflow::Constrain); - this_calendar.month_day_from_fields(&mut fields, overflow, context) + let result = this_calendar.month_day_from_fields(&mut fields, overflow)?; + + create_temporal_month_day(result, calendar.identifier.clone().into(), None, context) + .map(Into::into) } /// 15.8.2.4 `Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] )` - supercedes 12.5.7 @@ -571,7 +560,10 @@ impl Calendar { // 8. Let balanceResult be ? BalanceTimeDuration(duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], "day"). duration.balance_time_duration(TemporalUnit::Day, None)?; - this_calendar.date_add(&date, &duration, overflow, context) + let result = this_calendar.date_add(&date, &duration, overflow)?; + + create_temporal_date(result, calendar.identifier.clone().into(), None, context) + .map(Into::into) } ///15.8.2.5 `Temporal.Calendar.prototype.dateUntil ( one, two [ , options ] )` - Supercedes 12.5.8 @@ -616,7 +608,9 @@ impl Calendar { )? .unwrap_or(TemporalUnit::Day); - this_calendar.date_until(&one, &two, largest_unit, context) + let result = this_calendar.date_until(&one, &two, largest_unit)?; + + create_temporal_duration(result, None, context).map(Into::into) } /// 15.8.2.6 `Temporal.Calendar.prototype.era ( temporalDateLike )` @@ -662,7 +656,9 @@ impl Calendar { } }; - this_calendar.era(&date_info, context) + this_calendar + .era(&date_info) + .map(|r| r.map_or(JsValue::undefined(), Into::into)) } /// 15.8.2.7 `Temporal.Calendar.prototype.eraYear ( temporalDateLike )` @@ -708,7 +704,9 @@ impl Calendar { } }; - this_calendar.era_year(&date_info, context) + this_calendar + .era_year(&date_info) + .map(|r| r.map_or(JsValue::undefined(), JsValue::from)) } /// 15.8.2.8 `Temporal.Calendar.prototype.year ( temporalDateLike )` @@ -754,7 +752,7 @@ impl Calendar { } }; - this_calendar.year(&date_record, context) + this_calendar.year(&date_record).map(Into::into) } /// 15.8.2.9 `Temporal.Calendar.prototype.month ( temporalDateLike )` @@ -809,7 +807,7 @@ impl Calendar { } }; - this_calendar.month(&date_record, context) + this_calendar.month(&date_record).map(Into::into) } /// 15.8.2.10 `Temporal.Calendar.prototype.monthCode ( temporalDateLike )` @@ -865,7 +863,7 @@ impl Calendar { } }; - this_calendar.month_code(&date_record, context) + this_calendar.month_code(&date_record).map(Into::into) } /// 15.8.2.11 `Temporal.Calendar.prototype.day ( temporalDateLike )` @@ -911,7 +909,7 @@ impl Calendar { } }; - this_calendar.day(&date_record, context) + this_calendar.day(&date_record).map(Into::into) } /// 15.8.2.12 `Temporal.Calendar.prototype.dayOfWeek ( dateOrDateTime )` @@ -939,7 +937,9 @@ impl Calendar { // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike). let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; - this_calendar.day_of_week(&date.inner, context) + let result = this_calendar.day_of_week(&date.inner); + + result.map(Into::into) } /// 15.8.2.13 `Temporal.Calendar.prototype.dayOfYear ( temporalDateLike )` @@ -966,7 +966,9 @@ impl Calendar { // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike). let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; - this_calendar.day_of_year(&date.inner, context) + let result = this_calendar.day_of_year(&date.inner); + + result.map(Into::into) } /// 15.8.2.14 `Temporal.Calendar.prototype.weekOfYear ( temporalDateLike )` @@ -992,7 +994,9 @@ impl Calendar { // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike). let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; - this_calendar.week_of_year(&date.inner, context) + let result = this_calendar.week_of_year(&date.inner); + + result.map(Into::into) } /// 15.8.2.15 `Temporal.Calendar.prototype.yearOfWeek ( temporalDateLike )` @@ -1018,7 +1022,9 @@ impl Calendar { // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike). let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; - this_calendar.year_of_week(&date.inner, context) + let result = this_calendar.year_of_week(&date.inner); + + result.map(Into::into) } /// 15.8.2.16 `Temporal.Calendar.prototype.daysInWeek ( temporalDateLike )` @@ -1044,7 +1050,9 @@ impl Calendar { // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike). let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; - this_calendar.days_in_week(&date.inner, context) + let result = this_calendar.days_in_week(&date.inner); + + result.map(Into::into) } /// 15.8.2.17 `Temporal.Calendar.prototype.daysInMonth ( temporalDateLike )` @@ -1094,7 +1102,9 @@ impl Calendar { } }; - this_calendar.days_in_month(&date_record, context) + let result = this_calendar.days_in_month(&date_record); + + result.map(Into::into) } /// 15.8.2.18 `Temporal.Calendar.prototype.daysInYear ( temporalDateLike )` @@ -1144,7 +1154,9 @@ impl Calendar { } }; - this_calendar.days_in_year(&date_record, context) + let result = this_calendar.days_in_year(&date_record); + + result.map(Into::into) } /// 15.8.2.19 `Temporal.Calendar.prototype.monthsInYear ( temporalDateLike )` @@ -1194,7 +1206,9 @@ impl Calendar { } }; - this_calendar.months_in_year(&date_record, context) + let result = this_calendar.months_in_year(&date_record); + + result.map(Into::into) } /// 15.8.2.20 `Temporal.Calendar.prototype.inLeapYear ( temporalDateLike )` @@ -1244,7 +1258,9 @@ impl Calendar { } }; - this_calendar.in_leap_year(&date_record, context) + let result = this_calendar.in_leap_year(&date_record); + + result.map(Into::into) } /// 15.8.2.21 `Temporal.Calendar.prototype.fields ( fields )` @@ -1290,12 +1306,9 @@ impl Calendar { // 1. Let completion be ThrowCompletion(a newly created RangeError object). // 2. Return ? IteratorClose(iteratorRecord, completion). // v. Append nextValue to the end of the List fieldNames. - let this_name = value.to_std_string_escaped(); - match this_name.as_str() { - "year" | "month" | "monthCode" | "day" - if !fields_names.contains(&this_name) => - { - fields_names.push(this_name); + match value.to_std_string_escaped().as_str() { + "year" | "month" | "monthCode" | "day" if !fields_names.contains(&value) => { + fields_names.push(value); } _ => { let completion = Err(JsNativeError::range() @@ -1319,7 +1332,8 @@ impl Calendar { if calendar.identifier.as_slice() != ISO { // a. NOTE: Every built-in calendar preserves all input field names in output. // b. Let extraFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], fieldNames). - let extended_fields = this_calendar.field_descriptors(&fields_names); + let extended_fields = + this_calendar.field_descriptors(FieldsType::from(&fields_names[..])); // c. For each Calendar Field Descriptor Record desc of extraFieldDescriptors, do for descriptor in extended_fields { // i. Append desc.[[Property]] to result. @@ -1328,13 +1342,10 @@ impl Calendar { } // 9. Return CreateArrayFromList(result). - Ok(Array::create_array_from_list( - fields_names - .iter() - .map(|s| JsString::from(s.clone()).into()), - context, + Ok( + Array::create_array_from_list(fields_names.iter().map(|s| s.clone().into()), context) + .into(), ) - .into()) } /// 15.8.2.22 `Temporal.Calendar.prototype.mergeFields ( fields, additionalFields )` @@ -1380,7 +1391,11 @@ impl Calendar { // 5. NOTE: Every property of fieldsCopy and additionalFieldsCopy is an enumerable data property with non-undefined value, but some property keys may be Symbols. // 6. Let additionalKeys be ! additionalFieldsCopy.[[OwnPropertyKeys]](). - let add_keys = additional_fields_copy.__own_property_keys__(context)?; + let add_keys = additional_fields_copy + .__own_property_keys__(context)? + .iter() + .map(|k| JsString::from(k.to_string())) + .collect::>(); // 7. If calendar.[[Identifier]] is "iso8601", then // a. Let overriddenKeys be ISOFieldKeysToIgnore(additionalKeys). @@ -1395,23 +1410,28 @@ impl Calendar { // matches that of fields as modified by omitting overridden properties and // appending non-overlapping properties from additionalFields in iteration order. // 11. Let fieldsKeys be ! fieldsCopy.[[OwnPropertyKeys]](). - let field_keys = fields_copy.__own_property_keys__(context)?; + let field_keys = fields_copy + .__own_property_keys__(context)? + .iter() + .map(|k| JsString::from(k.to_string())) + .collect::>(); + // 12. For each element key of fieldsKeys, do for key in field_keys { // a. Let propValue be undefined. // b. If overriddenKeys contains key, then let prop_value = if overridden_keys.contains(&key) { // i. Set propValue to ! Get(additionalFieldsCopy, key). - additional_fields_copy.get(key.clone(), context)? + additional_fields_copy.get(key.as_slice(), context)? // c. Else, } else { // i. Set propValue to ! Get(fieldsCopy, key). - fields_copy.get(key.clone(), context)? + fields_copy.get(key.as_slice(), context)? }; // d. If propValue is not undefined, perform ! CreateDataPropertyOrThrow(merged, key, propValue). if !prop_value.is_undefined() { - merged.create_data_property_or_throw(key, prop_value, context)?; + merged.create_data_property_or_throw(key.as_slice(), prop_value, context)?; } } @@ -1590,7 +1610,7 @@ fn to_temporal_calendar_slot_value( Ok(js_string!(ISO).into()) } -// ---------------------------- AbstractCalendar Methods ---------------------------- +// ---------------------------- Native Abstract Calendar Methods ---------------------------- // // The above refers to the functions in the Abstract Operations section of the Calendar // spec takes either a calendar identifier or `Temporal.Calendar` and calls the a @@ -1625,7 +1645,8 @@ fn call_method_on_abstract_calendar( /// 12.2.2 `CalendarFields ( calendar, fieldNames )` /// -/// Returns either a normal completion containing a List of Strings, or a throw completion. +/// `CalendarFields` takes the input fields and adds the `extraFieldDescriptors` for +/// that specific calendar. #[allow(unused)] pub(crate) fn calendar_fields( calendar: &JsValue, @@ -1688,14 +1709,13 @@ pub(crate) fn calendar_merge_fields( #[allow(unused)] pub(crate) fn calendar_date_add( calendar: &JsValue, - date: &JsObject, - duration: &JsObject, - options: Option, + date: &PlainDate, + duration: &DurationRecord, + options: &JsValue, context: &mut Context<'_>, -) -> JsResult { +) -> JsResult { + // NOTE: The specification never calls CalendarDateAdd without an options argument provided. // 1. If options is not present, set options to undefined. - let options = options.unwrap_or(JsValue::undefined()); - // 2. If calendar is a String, then // a. Set calendar to ! CreateTemporalCalendar(calendar). // b. Return ? Call(%Temporal.Calendar.prototype.dateAdd%, calendar, « date, duration, options »). @@ -1704,14 +1724,22 @@ pub(crate) fn calendar_date_add( let added_date = call_method_on_abstract_calendar( calendar, &JsString::from("dateAdd"), - &[date.clone().into(), duration.clone().into(), options], + &[ + date.as_object(context)?.into(), + duration.as_object(context)?.into(), + options.clone(), + ], context, )?; // 5. Perform ? RequireInternalSlot(addedDate, [[InitializedTemporalDate]]). // 6. Return addedDate. match added_date { - JsValue::Object(o) if o.is_plain_date() => Ok(o), + JsValue::Object(o) if o.is_plain_date() => { + let obj = o.borrow(); + let result = obj.as_plain_date().expect("must be a plain date"); + Ok(result.clone()) + } _ => Err(JsNativeError::typ() .with_message("dateAdd returned a value other than a Temoporal.PlainDate") .into()), @@ -1724,11 +1752,11 @@ pub(crate) fn calendar_date_add( #[allow(unused)] pub(crate) fn calendar_date_until( calendar: &JsValue, - one: &JsObject, - two: &JsObject, + one: &PlainDate, + two: &PlainDate, options: &JsValue, context: &mut Context<'_>, -) -> JsResult { +) -> JsResult { // 1. If calendar is a String, then // a. Set calendar to ! CreateTemporalCalendar(calendar). // b. Return ? Call(%Temporal.Calendar.prototype.dateUntil%, calendar, « one, two, options »). @@ -1737,7 +1765,11 @@ pub(crate) fn calendar_date_until( let duration = call_method_on_abstract_calendar( calendar, &JsString::from("dateUntil"), - &[one.clone().into(), two.clone().into(), options.clone()], + &[ + one.as_object(context)?.into(), + two.as_object(context)?.into(), + options.clone(), + ], context, )?; @@ -1920,6 +1952,23 @@ pub(crate) fn calendar_day_of_week( ) -> JsResult { // 1. If calendar is a String, then // a. Set calendar to ! CreateTemporalCalendar(calendar). + let identifier = match calendar { + JsValue::String(s) => s.clone(), + JsValue::Object(o) if o.is_calendar() => { + let obj = o.borrow(); + let calendar = obj.as_calendar().expect("value must be a calendar"); + calendar.identifier.clone() + } + _ => unreachable!( + "A calendar slot value not being a calendar obj or string is an implementation error." + ), + }; + + let calendars = available_calendars(); + let this = calendars.get(identifier.as_slice()).ok_or_else(|| { + JsNativeError::range().with_message("calendar value was not an implemented calendar") + })?; + // b. Return ? Call(%Temporal.Calendar.prototype.dayOfWeek%, calendar, « dateLike »). // 2. Let result be ? Invoke(calendar, "dayOfWeek", « dateLike »). let result = call_method_on_abstract_calendar( @@ -1963,11 +2012,11 @@ pub(crate) fn calendar_day_of_year( ) -> JsResult { // 1. If calendar is a String, then // a. Set calendar to ! CreateTemporalCalendar(calendar). - // b. Return ? Call(%Temporal.Calendar.prototype.dayOfYear%, calendar, « dateLike »). - // 2. Let result be ? Invoke(calendar, "dayOfYear", « dateLike »). + // b. Return ? Call(%Temporal.Calendar.prototype.dayOfWeek%, calendar, « dateLike »). + // 2. Let result be ? Invoke(calendar, "dayOfWeek", « dateLike »). let result = call_method_on_abstract_calendar( calendar, - &JsString::from("dayOfYear"), + &JsString::from("dayOfWeek"), &[datelike.clone()], context, )?; @@ -1975,21 +2024,21 @@ pub(crate) fn calendar_day_of_year( // 3. If Type(result) is not Number, throw a TypeError exception. let Some(number) = result.as_number() else { return Err(JsNativeError::typ() - .with_message("CalendarDayOfYear result must be a number.") + .with_message("CalendarDayOfWeek result must be a number.") .into()); }; // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. if number.is_nan() || number.is_infinite() || number.fract() != 0.0 { return Err(JsNativeError::range() - .with_message("CalendarDayOfYear was not integral.") + .with_message("CalendarDayOfWeek was not integral.") .into()); } // 5. If result < 1𝔽, throw a RangeError exception. if number < 1.0 { return Err(JsNativeError::range() - .with_message("dayOfYear must be 1 or greater.") + .with_message("dayOfWeek must be 1 or greater.") .into()); } @@ -2006,11 +2055,11 @@ pub(crate) fn calendar_week_of_year( ) -> JsResult { // 1. If calendar is a String, then // a. Set calendar to ! CreateTemporalCalendar(calendar). - // b. Return ? Call(%Temporal.Calendar.prototype.weekOfYear%, calendar, « dateLike »). - // 2. Let result be ? Invoke(calendar, "weekOfYear", « dateLike »). + // b. Return ? Call(%Temporal.Calendar.prototype.dayOfYear%, calendar, « dateLike »). + // 2. Let result be ? Invoke(calendar, "dayOfYear", « dateLike »). let result = call_method_on_abstract_calendar( calendar, - &JsString::from("weekOfYear"), + &JsString::from("dayOfYear"), &[datelike.clone()], context, )?; @@ -2018,21 +2067,21 @@ pub(crate) fn calendar_week_of_year( // 3. If Type(result) is not Number, throw a TypeError exception. let Some(number) = result.as_number() else { return Err(JsNativeError::typ() - .with_message("CalendarWeekOfYear result must be a number.") + .with_message("CalendarDayOfYear result must be a number.") .into()); }; // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. if number.is_nan() || number.is_infinite() || number.fract() != 0.0 { return Err(JsNativeError::range() - .with_message("CalendarWeekOfYear was not integral.") + .with_message("CalendarDayOfYear was not integral.") .into()); } // 5. If result < 1𝔽, throw a RangeError exception. if number < 1.0 { return Err(JsNativeError::range() - .with_message("weekOfYear must be 1 or greater.") + .with_message("dayOfYear must be 1 or greater.") .into()); } @@ -2279,7 +2328,7 @@ pub(crate) fn calendar_in_lear_year( /// 12.2.24 `CalendarDateFromFields ( calendar, fields [ , options [ , dateFromFields ] ] )` #[allow(unused)] pub(crate) fn calendar_date_from_fields( - _calendar: &JsValue, + calendar: &JsValue, _fields: &JsObject, options: Option<&JsValue>, _date_from_fields: Option<&JsObject>, diff --git a/boa_engine/src/builtins/temporal/duration/mod.rs b/boa_engine/src/builtins/temporal/duration/mod.rs index 5319b96c078..1aa4b3ef217 100644 --- a/boa_engine/src/builtins/temporal/duration/mod.rs +++ b/boa_engine/src/builtins/temporal/duration/mod.rs @@ -17,10 +17,10 @@ use crate::{ use boa_profiler::Profiler; use super::{ - calendar, options::{ get_temporal_rounding_increment, get_temporal_unit, TemporalUnit, TemporalUnitGroup, }, + plain_date::{self, PlainDate}, to_integer_if_integral, DateTimeValues, }; @@ -565,6 +565,7 @@ impl Duration { .into()) } + // TODO: Update needed. /// 7.3.20 `Temporal.Duration.prototype.round ( roundTo )` pub(crate) fn round( this: &JsValue, @@ -613,9 +614,7 @@ impl Duration { // 6. Let smallestUnitPresent be true. // 7. Let largestUnitPresent be true. - // 8. NOTE: The following steps read options and perform independent validation in alphabetical order - // (ToRelativeTemporalObject reads "relativeTo", ToTemporalRoundingIncrement reads "roundingIncrement" and ToTemporalRoundingMode reads "roundingMode"). - + // 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( &round_to, @@ -625,17 +624,20 @@ impl Duration { context, )?; - // 10. Let relativeTo be ? ToRelativeTemporalObject(roundTo). - let relative_to = super::to_relative_temporal_object(&round_to, context)?; + // 10. Let relativeToRecord be ? ToRelativeTemporalObject(roundTo). + // 11. Let zonedRelativeTo be relativeToRecord.[[ZonedRelativeTo]]. + // 12. Let plainRelativeTo be relativeToRecord.[[PlainRelativeTo]]. + let (_plain_relative_to, _zoned_relative_to) = + super::to_relative_temporal_object(&round_to, context)?; - // 11. Let roundingIncrement be ? ToTemporalRoundingIncrement(roundTo). + // 13. Let roundingIncrement be ? ToTemporalRoundingIncrement(roundTo). let rounding_increment = get_temporal_rounding_increment(&round_to, context)?; - // 12. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand"). - let rounding_mode = get_option(&round_to, utf16!("roundingMode"), context)? + // 14. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand"). + let _rounding_mode = get_option(&round_to, utf16!("roundingMode"), context)? .unwrap_or(RoundingMode::HalfExpand); - // 13. Let smallestUnit be ? GetTemporalUnit(roundTo, "smallestUnit", datetime, undefined). + // 15. Let smallestUnit be ? GetTemporalUnit(roundTo, "smallestUnit", datetime, undefined). let smallest_unit = get_temporal_unit( &round_to, utf16!("smallestUnit"), @@ -644,8 +646,8 @@ impl Duration { context, )?; - // NOTE: execute step 19 earlier before initial values are shadowed. - // 19. If smallestUnitPresent is false and largestUnitPresent is false, then + // 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() @@ -653,7 +655,7 @@ impl Duration { .into()); } - // 14. If smallestUnit is undefined, then + // 16. If smallestUnit is undefined, then let smallest_unit = if let Some(unit) = smallest_unit { unit } else { @@ -662,14 +664,16 @@ impl Duration { TemporalUnit::Nanosecond }; - // 15. Let defaultLargestUnit be ! DefaultTemporalLargestUnit(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]]). - let mut default_largest_unit = duration.inner.default_temporal_largest_unit(); + // 17. Let existingLargestUnit be ! DefaultTemporalLargestUnit(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]]). + let existing_largest_unit = duration.inner.default_temporal_largest_unit(); - // 16. Set defaultLargestUnit to ! LargerOfTwoTemporalUnits(defaultLargestUnit, smallestUnit). - default_largest_unit = core::cmp::max(default_largest_unit, smallest_unit); + // 18. Set defaultLargestUnit to ! LargerOfTwoTemporalUnits(defaultLargestUnit, smallestUnit). + let default_largest_unit = core::cmp::max(existing_largest_unit, smallest_unit); - // 17. If largestUnit is undefined, then + // 19. If largestUnit is undefined, then let largest_unit = match largest_unit { + // 20. Else if largestUnit is "auto", then + // a. Set largestUnit to defaultLargestUnit. Some(TemporalUnit::Auto) => default_largest_unit, Some(u) => u, None => { @@ -679,60 +683,24 @@ impl Duration { } }; - // 20. If LargerOfTwoTemporalUnits(largestUnit, smallestUnit) is not largestUnit, throw a RangeError exception. + // 22. If LargerOfTwoTemporalUnits(largestUnit, smallestUnit) is not largestUnit, throw a RangeError exception. if core::cmp::max(largest_unit, smallest_unit) != largest_unit { return Err(JsNativeError::range() .with_message("largestUnit must be larger than smallestUnit") .into()); } - // 21. Let maximum be ! MaximumTemporalDurationRoundingIncrement(smallestUnit). + // 23. Let maximum be ! MaximumTemporalDurationRoundingIncrement(smallestUnit). let maximum = smallest_unit.to_maximum_rounding_increment(); - // 22. If maximum is not undefined, perform ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false). + // 24. If maximum is not undefined, perform ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false). if let Some(max) = maximum { validate_temporal_rounding_increment(rounding_increment, f64::from(max), false)?; } - let mut unbalance_duration = DurationRecord::from_date_duration(duration.inner.date()); - - // 23. Let unbalanceResult be ? UnbalanceDateDurationRelative(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], largestUnit, relativeTo). - unbalance_duration.unbalance_duration_relative(largest_unit, &relative_to, context)?; - - let mut roundable_duration = - DurationRecord::new(unbalance_duration.date(), duration.inner.time()); - - // 24. Let roundResult be (? RoundDuration(unbalanceResult.[[Years]], unbalanceResult.[[Months]], unbalanceResult.[[Weeks]], - // unbalanceResult.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], - // duration.[[Microseconds]], duration.[[Nanoseconds]], roundingIncrement, smallestUnit, roundingMode, relativeTo)).[[DurationRecord]]. - let _rem = roundable_duration.round_duration( - rounding_increment, - smallest_unit, - rounding_mode, - Some(&relative_to), - context, - )?; - - // 25. Let roundResult be roundRecord.[[DurationRecord]]. - // 26. If relativeTo is not undefined and relativeTo has an [[InitializedTemporalZonedDateTime]] internal slot, then - match relative_to { - JsValue::Object(o) if o.is_zoned_date_time() => { - // TODO: AdjustRoundedDurationDays requires 6.5.5 AddZonedDateTime. - // a. Set roundResult to ? AdjustRoundedDurationDays(roundResult.[[Years]], roundResult.[[Months]], roundResult.[[Weeks]], roundResult.[[Days]], roundResult.[[Hours]], roundResult.[[Minutes]], roundResult.[[Seconds]], roundResult.[[Milliseconds]], roundResult.[[Microseconds]], roundResult.[[Nanoseconds]], roundingIncrement, smallestUnit, roundingMode, relativeTo). - // b. Let balanceResult be ? BalanceTimeDurationRelative(roundResult.[[Days]], roundResult.[[Hours]], roundResult.[[Minutes]], roundResult.[[Seconds]], roundResult.[[Milliseconds]], roundResult.[[Microseconds]], roundResult.[[Nanoseconds]], largestUnit, relativeTo). - return Err(JsNativeError::range() - .with_message("not yet implemented.") - .into()); - } - // 27. Else, - _ => { - // a. Let balanceResult be ? BalanceTimeDuration(roundResult.[[Days]], roundResult.[[Hours]], roundResult.[[Minutes]], roundResult.[[Seconds]], roundResult.[[Milliseconds]], roundResult.[[Microseconds]], roundResult.[[Nanoseconds]], largestUnit). - roundable_duration.balance_time_duration(largest_unit, None)?; - } - } - // 28. Let result be ? BalanceDateDurationRelative(roundResult.[[Years]], roundResult.[[Months]], roundResult.[[Weeks]], balanceResult.[[Days]], largestUnit, relativeTo). - // 29. Return ! CreateTemporalDuration(result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], balanceResult.[[Hours]], balanceResult.[[Minutes]], balanceResult.[[Seconds]], balanceResult.[[Milliseconds]], balanceResult.[[Microseconds]], balanceResult.[[Nanoseconds]]). + // TODO: Complete the rest of the new `Temporal.Duration.prototype.round` impl. + // NOTE: Below is currently incorrect: Handling of zonedRelativeTo and precalculatedPlainDateTime is needed. Err(JsNativeError::range() .with_message("not yet implemented.") .into()) @@ -749,7 +717,7 @@ impl Duration { let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { JsNativeError::typ().with_message("this value of Duration must be an object.") })?; - let duration = o.as_duration().ok_or_else(|| { + let _duration = o.as_duration().ok_or_else(|| { JsNativeError::typ().with_message("the this object must be a Duration object.") })?; @@ -783,12 +751,14 @@ impl Duration { }; // 6. NOTE: The following steps read options and perform independent validation in alphabetical order (ToRelativeTemporalObject reads "relativeTo"). - // 7. Let relativeTo be ? ToRelativeTemporalObject(totalOf). - // NOTE TO SELF: Should relative_to_temporal_object just return a JsValue and we live with the expect? - let relative_to = super::to_relative_temporal_object(&total_of, context)?; - - // 8. Let unit be ? GetTemporalUnit(totalOf, "unit", datetime, required). - let unit = get_temporal_unit( + // 7. Let relativeToRecord be ? ToRelativeTemporalObject(totalOf). + // 8. Let zonedRelativeTo be relativeToRecord.[[ZonedRelativeTo]]. + // 9. Let plainRelativeTo be relativeToRecord.[[PlainRelativeTo]]. + let (_plain_relative_to, _zoned_relative_to) = + super::to_relative_temporal_object(&total_of, context)?; + + // 10. Let unit be ? GetTemporalUnit(totalOf, "unit", datetime, required). + let _unit = get_temporal_unit( &total_of, utf16!("unit"), TemporalUnitGroup::DateTime, @@ -797,106 +767,11 @@ impl Duration { )? .ok_or_else(|| JsNativeError::range().with_message("unit cannot be undefined."))?; - let mut unbalance_duration = DurationRecord::from_date_duration(duration.inner.date()); - - // 9. Let unbalanceResult be ? UnbalanceDurationRelative(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], unit, relativeTo). - unbalance_duration.unbalance_duration_relative(unit, &relative_to, context)?; + // TODO: Implement the rest of the new `Temporal.Duration.prototype.total` - // 10. Let intermediate be undefined. - let mut _intermediate = JsValue::undefined(); - - // 11. If Type(relativeTo) is Object and relativeTo has an [[InitializedTemporalZonedDateTime]] internal slot, then - if relative_to.is_object() - && relative_to - .as_object() - .expect("relative_to must be an object") - .is_zoned_date_time() - { - // a. Set intermediate to ? MoveRelativeZonedDateTime(relativeTo, unbalanceResult.[[Years]], unbalanceResult.[[Months]], unbalanceResult.[[Weeks]], 0). - return Err(JsNativeError::error() - .with_message("not yet implemented.") - .into()); - } - - let mut balance_duration = DurationRecord::new( - DateDuration::new(0.0, 0.0, 0.0, unbalance_duration.days()), - duration.inner.time(), - ); - // 12. Let balanceResult be ? BalancePossiblyInfiniteDuration(unbalanceResult.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], unit, intermediate). - balance_duration.balance_possibly_infinite_duration(unit, Some(&relative_to))?; - - // 13. If balanceResult is positive overflow, return +∞𝔽. - if balance_duration.is_positive_overflow() { - return Ok(f64::INFINITY.into()); - }; - - // 14. If balanceResult is negative overflow, return -∞𝔽. - if balance_duration.is_negative_overflow() { - return Ok(f64::NEG_INFINITY.into()); - } - - // TODO: determine whether and how to assert 15. - // 15. Assert: balanceResult is a Time Duration Record. - - // 16. Let roundRecord be ? RoundDuration(unbalanceResult.[[Years]], unbalanceResult.[[Months]], unbalanceResult.[[Weeks]], balanceResult.[[Days]], - // balanceResult.[[Hours]], balanceResult.[[Minutes]], balanceResult.[[Seconds]], balanceResult.[[Milliseconds]], balanceResult.[[Microseconds]], - // balanceResult.[[Nanoseconds]], 1, unit, "trunc", relativeTo). - // 17. Let roundResult be roundRecord.[[DurationRecord]]. - let mut round_record = DurationRecord::new( - DateDuration::new( - unbalance_duration.years(), - unbalance_duration.months(), - unbalance_duration.weeks(), - balance_duration.days(), - ), - balance_duration.time(), - ); - - let remainder = round_record.round_duration( - 1_f64, - unit, - RoundingMode::Trunc, - Some(&relative_to), - context, - )?; - - let whole = match unit { - // 18. If unit is "year", then - // a. Let whole be roundResult.[[Years]]. - TemporalUnit::Year => round_record.years(), - // 19. Else if unit is "month", then - // a. Let whole be roundResult.[[Months]]. - TemporalUnit::Month => round_record.months(), - // 20. Else if unit is "week", then - // a. Let whole be roundResult.[[Weeks]]. - TemporalUnit::Week => round_record.weeks(), - // 21. Else if unit is "day", then - // a. Let whole be roundResult.[[Days]]. - TemporalUnit::Day => round_record.days(), - // 22. Else if unit is "hour", then - // a. Let whole be roundResult.[[Hours]]. - TemporalUnit::Hour => round_record.hours(), - // 23. Else if unit is "minute", then - // a. Let whole be roundResult.[[Minutes]]. - TemporalUnit::Minute => round_record.minutes(), - // 24. Else if unit is "second", then - // a. Let whole be roundResult.[[Seconds]]. - TemporalUnit::Second => round_record.seconds(), - // 25. Else if unit is "millisecond", then - // a. Let whole be roundResult.[[Milliseconds]]. - TemporalUnit::Millisecond => round_record.milliseconds(), - // 26. Else if unit is "microsecond", then - // a. Let whole be roundResult.[[Microseconds]]. - TemporalUnit::Microsecond => round_record.microseconds(), - // 27. Else, - // b. Let whole be roundResult.[[Nanoseconds]]. - TemporalUnit::Nanosecond => round_record.nanoseconds(), - // a. Assert: unit is "nanosecond". - TemporalUnit::Auto=> unreachable!("Unit must be a valid temporal unit. Any other value would be an implementation error."), - }; - - // 28. Return 𝔽(whole + roundRecord.[[Remainder]]). - Ok((whole + remainder).into()) + Err(JsNativeError::range() + .with_message("not yet implemented.") + .into()) } /// 7.3.22 `Temporal.Duration.prototype.toString ( [ options ] )` @@ -1003,24 +878,12 @@ pub(crate) fn create_temporal_duration( } /// 7.5.23 `DaysUntil ( earlier, later )` -fn days_until(earlier: &JsObject, later: &JsObject) -> i32 { +pub(crate) fn days_until(earlier: &PlainDate, later: &PlainDate) -> i32 { // 1. Let epochDays1 be ISODateToEpochDays(earlier.[[ISOYear]], earlier.[[ISOMonth]] - 1, earlier.[[ISODay]]). - let obj = earlier.borrow(); - let date_one = obj - .as_plain_date() - .expect("earlier must be a PlainDate obj."); - - let epoch_days_one = date_one.inner.as_epoch_days(); - - drop(obj); + let epoch_days_one = earlier.inner.as_epoch_days(); // 2. Let epochDays2 be ISODateToEpochDays(later.[[ISOYear]], later.[[ISOMonth]] - 1, later.[[ISODay]]). - let obj = later.borrow(); - let date_two = obj - .as_plain_date() - .expect("earlier must be a PlainDate obj."); - - let epoch_days_two = date_two.inner.as_epoch_days(); + let epoch_days_two = later.inner.as_epoch_days(); // 3. Return epochDays2 - epochDays1. epoch_days_two - epoch_days_one @@ -1029,11 +892,17 @@ fn days_until(earlier: &JsObject, later: &JsObject) -> i32 { /// Abstract Operation 7.5.24 `MoveRelativeDate ( calendar, relativeTo, duration, dateAdd )` fn move_relative_date( calendar: &JsValue, - relative_to: &JsObject, - duration: &JsObject, + relative_to: &PlainDate, + duration: &DurationRecord, context: &mut Context<'_>, -) -> JsResult<(JsObject, f64)> { - let new_date = calendar::calendar_date_add(calendar, relative_to, duration, None, context)?; - let days = f64::from(days_until(relative_to, &new_date)); - Ok((new_date, days)) +) -> JsResult<(PlainDate, f64)> { + let new_date = plain_date::add_date( + calendar, + relative_to, + duration, + &JsValue::undefined(), + context, + )?; + let days = days_until(relative_to, &new_date); + Ok((new_date, f64::from(days))) } diff --git a/boa_engine/src/builtins/temporal/duration/record.rs b/boa_engine/src/builtins/temporal/duration/record.rs index d05f07aecb1..2cb688e1b67 100644 --- a/boa_engine/src/builtins/temporal/duration/record.rs +++ b/boa_engine/src/builtins/temporal/duration/record.rs @@ -1,14 +1,22 @@ +//! The `DurationRecord` implements the internal representation of a Temporal Duration. + use crate::{ builtins::{ options::RoundingMode, - temporal::{self, create_temporal_date, options::TemporalUnit, to_temporal_date}, + temporal::{ + self, + options::{ArithmeticOverflow, TemporalUnit}, + round_number_to_increment, to_temporal_date, NS_PER_DAY, + }, }, js_string, string::utf16, Context, JsNativeError, JsObject, JsResult, JsValue, }; -use super::super::{calendar, to_integer_if_integral, zoned_date_time}; +use super::super::{ + calendar, plain_date, to_integer_if_integral, PlainDate, PlainDateTime, ZonedDateTime, +}; // ==== `DateDuration` ==== @@ -44,6 +52,22 @@ impl DateDuration { days: f64::NAN, } } + + pub(crate) const fn years(&self) -> f64 { + self.years + } + + pub(crate) const fn months(&self) -> f64 { + self.months + } + + pub(crate) const fn weeks(&self) -> f64 { + self.weeks + } + + pub(crate) const fn days(&self) -> f64 { + self.days + } } impl<'a> IntoIterator for &'a DateDuration { @@ -551,11 +575,13 @@ impl DurationRecord { } #[inline] + #[allow(unused)] pub(crate) fn is_positive_overflow(&self) -> bool { self.years().is_infinite() && self.years().is_sign_positive() } #[inline] + #[allow(unused)] pub(crate) fn is_negative_overflow(&self) -> bool { self.years().is_infinite() && self.years().is_sign_negative() } @@ -842,6 +868,7 @@ impl DurationRecord { } /// 7.5.20 `UnbalanceDurationRelative ( years, months, weeks, days, largestUnit, relativeTo )` + #[allow(dead_code)] pub(crate) fn unbalance_duration_relative( &mut self, largest_unit: TemporalUnit, @@ -1003,31 +1030,20 @@ impl DurationRecord { // 5. Let sign be ! DurationSign(years, months, weeks, days, 0, 0, 0, 0, 0, 0). // 6. Assert: sign ≠ 0. - let sign = self.duration_sign(); + let sign = f64::from(self.duration_sign()); // 7. Let oneYear be ! CreateTemporalDuration(sign, 0, 0, 0, 0, 0, 0, 0, 0, 0). - let one_year = Self::new( - DateDuration::new(f64::from(sign), 0.0, 0.0, 0.0), - TimeDuration::default(), - ); + let one_year = Self::from_date_duration(DateDuration::new(sign, 0.0, 0.0, 0.0)); // 8. Let oneMonth be ! CreateTemporalDuration(0, sign, 0, 0, 0, 0, 0, 0, 0, 0). - let one_month = Self::new( - DateDuration::new(0.0, f64::from(sign), 0.0, 0.0), - TimeDuration::default(), - ); + let one_month = Self::from_date_duration(DateDuration::new(0.0, sign, 0.0, 0.0)); // 9. Let oneWeek be ! CreateTemporalDuration(0, 0, sign, 0, 0, 0, 0, 0, 0, 0). - let _one_week = Self::new( - DateDuration::new(0.0, 0.0, f64::from(sign), 0.0), - TimeDuration::default(), - ); + let one_week = Self::from_date_duration(DateDuration::new(0.0, 0.0, sign, 0.0)); // 10. Set relativeTo to ? ToTemporalDate(relativeTo). - let date = to_temporal_date(relative_to, None, context)?; + let mut relative_to = to_temporal_date(relative_to, None, context)?; // 11. Let calendar be relativeTo.[[Calendar]]. - let calendar = &date.calendar.clone(); - - let relative_to = create_temporal_date(date.inner, date.calendar, None, context)?; + let calendar = &relative_to.calendar.clone(); match largest_unit { // 12. If largestUnit is "year", then @@ -1037,12 +1053,8 @@ impl DurationRecord { // b. Else, // i. Let dateAdd be unused. // c. Let moveResult be ? MoveRelativeDate(calendar, relativeTo, oneYear, dateAdd). - let move_result = super::move_relative_date( - calendar, - &relative_to, - &one_year.as_object(context)?, - context, - )?; + let move_result = + super::move_relative_date(calendar, &relative_to, &one_year, context)?; // d. Let newRelativeTo be moveResult.[[RelativeTo]]. let mut new_relative = move_result.0; @@ -1055,17 +1067,13 @@ impl DurationRecord { self.set_days(self.days() - one_year_days); // ii. Set years to years + sign. - self.set_years(self.years() + f64::from(sign)); + self.set_years(self.years() + sign); // iii. Set relativeTo to newRelativeTo. let relative_to = new_relative; // iv. Set moveResult to ? MoveRelativeDate(calendar, relativeTo, oneYear, dateAdd). - let move_result = super::move_relative_date( - calendar, - &relative_to, - &one_year.as_object(context)?, - context, - )?; + let move_result = + super::move_relative_date(calendar, &relative_to, &one_year, context)?; // v. Set newRelativeTo to moveResult.[[RelativeTo]]. new_relative = move_result.0; @@ -1074,59 +1082,103 @@ impl DurationRecord { } // g. Set moveResult to ? MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd). - let move_result = super::move_relative_date( - calendar, - &relative_to, - &one_month.as_object(context)?, - context, - )?; - // h. Set newRelativeTo to moveResult.[[RelativeTo]]. - let mut new_relative = move_result.0; // i. Let oneMonthDays be moveResult.[[Days]]. - let mut one_month_days = move_result.1; + let (mut new_relative_to, mut one_month_days) = + super::move_relative_date(calendar, &relative_to, &one_month, context)?; // j. Repeat, while abs(days) ≥ abs(oneMonthDays), while self.days().abs() >= one_month_days.abs() { // i. Set days to days - oneMonthDays. self.set_days(self.days() - one_month_days); // ii. Set months to months + sign. - self.set_months(self.months() + f64::from(sign)); + self.set_months(self.months() + sign); // iii. Set relativeTo to newRelativeTo. - let relative_to = new_relative; + let relative_to = new_relative.clone(); // iv. Set moveResult to ? MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd). - let move_result = super::move_relative_date( - calendar, - &relative_to, - &one_month.as_object(context)?, - context, - )?; + let move_result = + super::move_relative_date(calendar, &relative_to, &one_month, context)?; // v. Set newRelativeTo to moveResult.[[RelativeTo]]. - new_relative = move_result.0; + new_relative_to = move_result.0; // vi. Set oneMonthDays to moveResult.[[Days]]. one_month_days = move_result.1; } // k. Set newRelativeTo to ? CalendarDateAdd(calendar, relativeTo, oneYear, undefined, dateAdd). + new_relative_to = calendar::calendar_date_add( + calendar, + &relative_to, + &one_year, + &JsValue::undefined(), + context, + )?; + // l. If calendar is an Object, then // i. Let dateUntil be ? GetMethod(calendar, "dateUntil"). // m. Else, // i. Let dateUntil be unused. + // n. Let untilOptions be OrdinaryObjectCreate(null). + let until_options = JsObject::with_null_proto(); // o. Perform ! CreateDataPropertyOrThrow(untilOptions, "largestUnit", "month"). + until_options.create_data_property_or_throw( + utf16!("largestUnit"), + js_string!("month"), + context, + )?; + // p. Let untilResult be ? CalendarDateUntil(calendar, relativeTo, newRelativeTo, untilOptions, dateUntil). + let until_result = calendar::calendar_date_until( + calendar, + &relative_to, + &new_relative_to, + &until_options.into(), + context, + )?; + // q. Let oneYearMonths be untilResult.[[Months]]. + let mut one_year_months = until_result.months(); + // r. Repeat, while abs(months) ≥ abs(oneYearMonths), - // i. Set months to months - oneYearMonths. - // ii. Set years to years + sign. - // iii. Set relativeTo to newRelativeTo. - // iv. Set newRelativeTo to ? CalendarDateAdd(calendar, relativeTo, oneYear, undefined, dateAdd). - // v. Set untilOptions to OrdinaryObjectCreate(null). - // vi. Perform ! CreateDataPropertyOrThrow(untilOptions, "largestUnit", "month"). - // vii. Set untilResult to ? CalendarDateUntil(calendar, relativeTo, newRelativeTo, untilOptions, dateUntil). - // viii. Set oneYearMonths to untilResult.[[Months]]. + while self.months().abs() >= one_year_months.abs() { + // i. Set months to months - oneYearMonths. + self.set_months(self.months() - one_year_months); + // ii. Set years to years + sign. + self.set_years(self.years() + sign); + + // iii. Set relativeTo to newRelativeTo. + relative_to = new_relative_to; + + // iv. Set newRelativeTo to ? CalendarDateAdd(calendar, relativeTo, oneYear, undefined, dateAdd). + new_relative_to = calendar::calendar_date_add( + calendar, + &relative_to, + &one_year, + &JsValue::undefined(), + context, + )?; + + // v. Set untilOptions to OrdinaryObjectCreate(null). + let until_options = JsObject::with_null_proto(); + // vi. Perform ! CreateDataPropertyOrThrow(untilOptions, "largestUnit", "month"). + until_options.create_data_property_or_throw( + utf16!("largestUnit"), + js_string!("month"), + context, + )?; + // vii. Set untilResult to ? CalendarDateUntil(calendar, relativeTo, newRelativeTo, untilOptions, dateUntil). + let until_result = calendar::calendar_date_until( + calendar, + &relative_to, + &new_relative_to, + &until_options.into(), + context, + )?; + // viii. Set oneYearMonths to untilResult.[[Months]]. + one_year_months = until_result.months(); + } } // 13. Else if largestUnit is "month", then TemporalUnit::Month => { @@ -1134,16 +1186,32 @@ impl DurationRecord { // i. Let dateAdd be ? GetMethod(calendar, "dateAdd"). // b. Else, // i. Let dateAdd be unused. + // c. Let moveResult be ? MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd). // d. Let newRelativeTo be moveResult.[[RelativeTo]]. // e. Let oneMonthDays be moveResult.[[Days]]. + let (mut new_relative_to, mut one_month_days) = + super::move_relative_date(calendar, &relative_to, &one_month, context)?; + // f. Repeat, while abs(days) ≥ abs(oneMonthDays), - // i. Set days to days - oneMonthDays. - // ii. Set months to months + sign. - // iii. Set relativeTo to newRelativeTo. - // iv. Set moveResult to ? MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd). - // v. Set newRelativeTo to moveResult.[[RelativeTo]]. - // vi. Set oneMonthDays to moveResult.[[Days]]. + while self.days().abs() >= one_month_days.abs() { + // i. Set days to days - oneMonthDays. + self.set_days(self.days() - one_month_days); + + // ii. Set months to months + sign. + self.set_months(self.months() + sign); + + // iii. Set relativeTo to newRelativeTo. + relative_to = new_relative_to; + + // iv. Set moveResult to ? MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd). + let move_result = + super::move_relative_date(calendar, &relative_to, &one_month, context)?; + // v. Set newRelativeTo to moveResult.[[RelativeTo]]. + new_relative_to = move_result.0; + // vi. Set oneMonthDays to moveResult.[[Days]]. + one_month_days = move_result.1; + } } // 14. Else, TemporalUnit::Week => { @@ -1152,600 +1220,531 @@ impl DurationRecord { // i. Let dateAdd be ? GetMethod(calendar, "dateAdd"). // c. Else, // i. Let dateAdd be unused. + // d. Let moveResult be ? MoveRelativeDate(calendar, relativeTo, oneWeek, dateAdd). // e. Let newRelativeTo be moveResult.[[RelativeTo]]. // f. Let oneWeekDays be moveResult.[[Days]]. + let (mut new_relative_to, mut one_week_days) = + super::move_relative_date(calendar, &relative_to, &one_week, context)?; + // g. Repeat, while abs(days) ≥ abs(oneWeekDays), - // i. Set days to days - oneWeekDays. - // ii. Set weeks to weeks + sign. - // iii. Set relativeTo to newRelativeTo. - // iv. Set moveResult to ? MoveRelativeDate(calendar, relativeTo, oneWeek, dateAdd). - // v. Set newRelativeTo to moveResult.[[RelativeTo]]. - // vi. Set oneWeekDays to moveResult.[[Days]]. - todo!("week not implemented yet.") + while self.days().abs() >= one_week_days.abs() { + // i. Set days to days - oneWeekDays. + self.set_days(self.days() - one_week_days); + // ii. Set weeks to weeks + sign. + self.set_weeks(self.weeks() + sign); + // iii. Set relativeTo to newRelativeTo. + relative_to = new_relative_to; + // iv. Set moveResult to ? MoveRelativeDate(calendar, relativeTo, oneWeek, dateAdd). + let move_result = + super::move_relative_date(calendar, &relative_to, &one_week, context)?; + // v. Set newRelativeTo to moveResult.[[RelativeTo]]. + new_relative_to = move_result.0; + // vi. Set oneWeekDays to moveResult.[[Days]]. + one_week_days = move_result.1; + } } _ => unreachable!(), } // 15. Return ! CreateDateDurationRecord(years, months, weeks, days). - - Err(JsNativeError::range() - .with_message("not yet implemented.") - .into()) + Ok(()) } + // TODO: Refactor relative_to's into a RelativeTo struct? /// Abstract Operation 7.5.26 `RoundDuration ( years, months, weeks, days, hours, minutes, /// seconds, milliseconds, microseconds, nanoseconds, increment, unit, - /// roundingMode [ , relativeTo ] )` + /// roundingMode [ , plainRelativeTo [, zonedRelativeTo [, precalculatedDateTime]]] )` + #[allow(clippy::too_many_arguments)] pub(crate) fn round_duration( &mut self, increment: f64, unit: TemporalUnit, rounding_mode: RoundingMode, - relative_to: Option<&JsValue>, + plain_relative_to: Option<&PlainDate>, + zoned_relative_to: Option<&ZonedDateTime>, + _precalc_datetime: Option<&PlainDateTime>, context: &mut Context<'_>, ) -> JsResult { - // 1. If relativeTo is not present, set relativeTo to undefined. - let relative_to = if let Some(val) = relative_to { - val.clone() - } else { - JsValue::undefined() - }; - - // 2. If unit is "year", "month", or "week", and relativeTo is undefined, then - if relative_to.is_undefined() - && (unit == TemporalUnit::Year - || unit == TemporalUnit::Month - || unit == TemporalUnit::Week) - { - // a. Throw a RangeError exception. - return Err(JsNativeError::range() - .with_message("relativeTo was out of range while rounding self.") - .into()); - } - - // TODO: Handle `ZonedDateTime` - // 3. Let zonedRelativeTo be undefined. - let zoned_relative_to = JsValue::undefined(); - - // 4. If relativeTo is not undefined, then - let (calendar, relative_to) = if relative_to.is_object() { - let relative_to_obj = relative_to.as_object().expect( - "relativeTo must be a Temporal.ZonedDateTime or Temporal.PlainDate object if defined.", - ); - // a. If relativeTo has an [[InitializedTemporalZonedDateTime]] internal slot, then - if relative_to_obj.is_zoned_date_time() { - // i. Set zonedRelativeTo to relativeTo. - // TODO: ii. Set relativeTo to ? ToTemporalDate(relativeTo). + // 1. If plainRelativeTo is not present, set plainRelativeTo to undefined. + // 2. If zonedRelativeTo is not present, set zonedRelativeTo to undefined. + // 3. If precalculatedPlainDateTime is not present, set precalculatedPlainDateTime to undefined. + + let (frac_days, frac_secs) = match unit { + // 4. If unit is "year", "month", or "week", and plainRelativeTo is undefined, then + TemporalUnit::Year | TemporalUnit::Month | TemporalUnit::Week + if plain_relative_to.is_none() => + { + // a. Throw a RangeError exception. return Err(JsNativeError::range() - .with_message("ZonedDateTime is not yet implemented.") + .with_message("plainRelativeTo canot be undefined with given TemporalUnit") .into()); - // b. Else, - }; - - let obj = relative_to_obj.borrow(); - let plain_date = obj.as_plain_date().expect("object must be a PlainDate"); - - // c. Let calendar be relativeTo.[[Calendar]]. - let calendar = plain_date.calendar.clone(); - - drop(obj); - - (Some(calendar), Some(relative_to_obj)) - // 5. Else, - } else { - // a. NOTE: calendar will not be used below. - (None, None) - }; - - // 6. If unit is one of "year", "month", "week", or "day", then - let fractional_secs = match unit { + } + // 5. If unit is one of "year", "month", "week", or "day", then TemporalUnit::Year | TemporalUnit::Month | TemporalUnit::Week | TemporalUnit::Day => { - // a. Let nanoseconds be ! TotalDurationNanoseconds(0, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, 0). - let nanoseconds = + // a. Let nanoseconds be TotalDurationNanoseconds(hours, minutes, seconds, milliseconds, microseconds, nanoseconds). + let nanos = Self::from_day_and_time(0.0, self.time()).total_duration_nanoseconds(0.0); - // b. Let intermediate be undefined. - let intermediate = JsValue::undefined(); - // c. If zonedRelativeTo is not undefined, then - if !zoned_relative_to.is_undefined() { - // i. Let intermediate be ? MoveRelativeZonedDateTime(zonedRelativeTo, years, months, weeks, days). - return Err(JsNativeError::error() - .with_message("not yet implemented.") + // b. If zonedRelativeTo is not undefined, then + // i. Let intermediate be ? MoveRelativeZonedDateTime(zonedRelativeTo, years, months, weeks, days, precalculatedPlainDateTime). + // ii. Let result be ? NanosecondsToDays(nanoseconds, intermediate). + // iii. Let fractionalDays be days + result.[[Days]] + result.[[Nanoseconds]] / result.[[DayLength]]. + // c. Else, + // i. Let fractionalDays be days + nanoseconds / nsPerDay. + let frac_days = if zoned_relative_to.is_none() { + self.days() + nanos / NS_PER_DAY as f64 + } else { + // implementation of b: i-iii needed. + return Err(JsNativeError::range() + .with_message("Not yet implemented.") .into()); - } - // d. Let result be ? NanosecondsToDays(nanoseconds, intermediate). - let result = zoned_date_time::nanoseconds_to_days(nanoseconds, &intermediate)?; - - // e. Set days to days + result.[[Days]] + result.[[Nanoseconds]] / result.[[DayLength]]. - let days = self.days() as i32; - self.set_days(f64::from(days + result.0 + result.1 / result.2)); - - // f. Set hours, minutes, seconds, milliseconds, microseconds, and nanoseconds to 0. + }; + // d. Set days, hours, minutes, seconds, milliseconds, microseconds, and nanoseconds to 0. + self.set_days(0.0); self.set_time_duration(TimeDuration::default()); - - 0_f64 + // e. Assert: fractionalSeconds is not used below. + (Some(frac_days), None) } - // 7. Else, + // 6. Else, _ => { // a. Let fractionalSeconds be nanoseconds × 10-9 + microseconds × 10-6 + milliseconds × 10-3 + seconds. - self.seconds().mul_add( - 1000_f64, - self.nanoseconds() - .mul_add(1_000_000_000_f64, self.microseconds() * 1_000_000_f64), - ) + let frac_secs = self.nanoseconds().mul_add( + 1_000_000_000f64, + self.microseconds().mul_add( + 1_000_000f64, + self.milliseconds().mul_add(1_000f64, self.seconds()), + ), + ); + + // b. Assert: fractionalDays is not used below. + (None, Some(frac_secs)) } }; - // 8. Let remainder be undefined. + // 7. let total be unset. // We begin matching against unit and return the remainder value. - let remainder = match unit { - // 9. If unit is "year", then + let total = match unit { + // 8. If unit is "year", then TemporalUnit::Year => { - // This should be safe as we throw a range error if relative_to does not exist. - assert!(calendar.is_some() && relative_to.is_some()); - let calendar_obj = calendar.expect("calendar must exist at this point."); - let relative_to = relative_to.expect("relative_to must exist at this point."); - - // a. Let yearsDuration be ! CreateTemporalDuration(years, 0, 0, 0, 0, 0, 0, 0, 0, 0). - let years_duration = super::create_temporal_duration( - Self::new( - DateDuration::new(self.years(), 0.0, 0.0, 0.0), - TimeDuration::default(), - ), - None, - context, - )?; + let mut frac_days = + frac_days.expect("assert that fractionalDays exists for TemporalUnit == year"); - // b. If calendar is an Object, then + let plain_relative_to = plain_relative_to.expect("this must exist."); + // a. Let calendar be plainRelativeTo.[[Calendar]]. + let calendar = &plain_relative_to.calendar; + + // b. Let yearsDuration be ! CreateTemporalDuration(years, 0, 0, 0, 0, 0, 0, 0, 0, 0). + let years = DateDuration::new(self.years(), 0.0, 0.0, 0.0); + let years_duration = DurationRecord::new(years, TimeDuration::default()); + + // c. If calendar is an Object, then // i. Let dateAdd be ? GetMethod(calendar, "dateAdd"). - // c. Else, + // d. Else, // i. Let dateAdd be unused. - // d. Let yearsLater be ? CalendarDateAdd(calendar, relativeTo, yearsDuration, undefined, dateAdd). - let years_later = calendar::calendar_date_add( - &calendar_obj, - relative_to, + // e. Let yearsLater be ? AddDate(calendar, plainRelativeTo, yearsDuration, undefined, dateAdd). + let years_later = plain_date::add_date( + calendar, + plain_relative_to, &years_duration, - None, + &JsValue::undefined(), context, )?; - // e. Let yearsMonthsWeeks be ! CreateTemporalDuration(years, months, weeks, 0, 0, 0, 0, 0, 0, 0). - let years_months_weeks = super::create_temporal_duration( - Self::new( - DateDuration::new(self.years(), self.months(), self.weeks(), 0.0), - TimeDuration::default(), - ), - None, - context, - )?; + // f. Let yearsMonthsWeeks be ! CreateTemporalDuration(years, months, weeks, 0, 0, 0, 0, 0, 0, 0). + let years_months_weeks = Self::new( + DateDuration::new(self.years(), self.months(), self.weeks(), 0.0), + TimeDuration::default(), + ); - // f. Let yearsMonthsWeeksLater be ? CalendarDateAdd(calendar, relativeTo, yearsMonthsWeeks, undefined, dateAdd). - let years_months_weeks_later = calendar::calendar_date_add( - &calendar_obj, - relative_to, + // g. Let yearsMonthsWeeksLater be ? AddDate(calendar, plainRelativeTo, yearsMonthsWeeks, undefined, dateAdd). + let years_months_weeks_later = plain_date::add_date( + calendar, + plain_relative_to, &years_months_weeks, - None, + &JsValue::undefined(), context, )?; - // g. Let monthsWeeksInDays be DaysUntil(yearsLater, yearsMonthsWeeksLater). + // h. Let monthsWeeksInDays be DaysUntil(yearsLater, yearsMonthsWeeksLater). let months_weeks_in_days = super::days_until(&years_later, &years_months_weeks_later); - // h. Set relativeTo to yearsLater. - let relative_to = years_later; - - // i. Let days be days + monthsWeeksInDays. - self.set_days(self.days() + f64::from(months_weeks_in_days)); + // i. Set plainRelativeTo to yearsLater. + let plain_relative_to = years_later; - // j. Let wholeDaysDuration be ? CreateTemporalDuration(0, 0, 0, truncate(days), 0, 0, 0, 0, 0, 0). - let whole_days_duration = super::create_temporal_duration( - Self::new( - DateDuration::new(0.0, 0.0, 0.0, self.days().trunc()), - TimeDuration::default(), - ), - None, - context, - )?; + // j. Set fractionalDays to fractionalDays + monthsWeeksInDays. + frac_days += f64::from(months_weeks_in_days); - // k. Let wholeDaysLater be ? CalendarDateAdd(calendar, relativeTo, wholeDaysDuration, undefined, dateAdd). - let whole_days_later = calendar::calendar_date_add( - &calendar_obj, - &relative_to, - &whole_days_duration, - None, - context, + // k. Let isoResult be ! AddISODate(plainRelativeTo.[[ISOYear]]. plainRelativeTo.[[ISOMonth]], plainRelativeTo.[[ISODay]], 0, 0, 0, truncate(fractionalDays), "constrain"). + let iso_result = plain_relative_to.inner.add_iso_date( + DateDuration::new(0.0, 0.0, 0.0, frac_days.trunc()), + ArithmeticOverflow::Constrain, )?; - // l. Let untilOptions be OrdinaryObjectCreate(null). - let until_options = JsObject::with_null_proto(); - // m. Perform ! CreateDataPropertyOrThrow(untilOptions, "largestUnit", "year"). - until_options.create_data_property_or_throw( - utf16!("largestUnit"), - js_string!("year"), - context, - )?; + // l. Let wholeDaysLater be ? CreateTemporalDate(isoResult.[[Year]], isoResult.[[Month]], isoResult.[[Day]], calendar). + let whole_days_later = PlainDate::new(iso_result, calendar.clone()); - // n. Let timePassed be ? CalendarDateUntil(calendar, relativeTo, wholeDaysLater, untilOptions). - let time_passed = calendar::calendar_date_until( - &calendar_obj, - &relative_to, + // m. Let untilOptions be OrdinaryObjectCreate(null). + // n. Perform ! CreateDataPropertyOrThrow(untilOptions, "largestUnit", "year"). + // o. Let timePassed be ? DifferenceDate(calendar, plainRelativeTo, wholeDaysLater, untilOptions). + let time_passed = plain_date::difference_date( + calendar, + &plain_relative_to, &whole_days_later, - &until_options.into(), + TemporalUnit::Year, context, )?; - // o. Let yearsPassed be timePassed.[[Years]]. + // p. Let yearsPassed be timePassed.[[Years]]. let years_passed = time_passed.years(); - // p. Set years to years + yearsPassed. - self.set_years(self.years() + years_passed); - // q. Let oldRelativeTo be relativeTo. - let old_relative_to = relative_to.clone(); + // q. Set years to years + yearsPassed. + self.set_years(self.years() + years_passed); // r. Let yearsDuration be ! CreateTemporalDuration(yearsPassed, 0, 0, 0, 0, 0, 0, 0, 0, 0). - let years_duration = super::create_temporal_duration( - Self::new( - DateDuration::new(years_passed, 0.0, 0.0, 0.0), - TimeDuration::default(), - ), - None, - context, - )?; + let years_duration = Self::new( + DateDuration::new(years_passed, 0.0, 0.0, 0.0), + TimeDuration::default(), + ); - // s. Set relativeTo to ? CalendarDateAdd(calendar, relativeTo, yearsDuration, undefined, dateAdd). - let relative_to = calendar::calendar_date_add( - &calendar_obj, - &relative_to, + // s. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, yearsDuration, dateAdd). + // t. Set plainRelativeTo to moveResult.[[RelativeTo]]. + // u. Let daysPassed be moveResult.[[Days]]. + let (plain_relative_to, days_passed) = super::move_relative_date( + calendar, + &plain_relative_to, &years_duration, - None, context, )?; - // t. Let daysPassed be DaysUntil(oldRelativeTo, relativeTo). - let days_passed = super::days_until(&old_relative_to, &relative_to); - - // u. Set days to days - daysPassed. - self.set_days(self.days() - f64::from(days_passed)); + // v. Set fractionalDays to fractionalDays - daysPassed. + frac_days -= days_passed; - // v. If days < 0, let sign be -1; else, let sign be 1. - let sign = if self.days() < 0_f64 { -1 } else { 1 }; + // w. If fractionalDays < 0, let sign be -1; else, let sign be 1. + let sign = if frac_days < 0.0 { -1 } else { 1 }; - // w. Let oneYear be ! CreateTemporalDuration(sign, 0, 0, 0, 0, 0, 0, 0, 0, 0). - let one_year = super::create_temporal_duration( - Self::new( - DateDuration::new(f64::from(sign), 0.0, 0.0, 0.0), - TimeDuration::default(), - ), - None, - context, - )?; + // x. Let oneYear be ! CreateTemporalDuration(sign, 0, 0, 0, 0, 0, 0, 0, 0, 0). + let one_year = Self::new( + DateDuration::new(f64::from(sign), 0.0, 0.0, 0.0), + TimeDuration::default(), + ); - // x. Let moveResult be ? MoveRelativeDate(calendar, relativeTo, oneYear, dateAdd). - let move_result = - super::move_relative_date(&calendar_obj, &relative_to, &one_year, context)?; + // y. Set moveResult to ? MoveRelativeDate(calendar, plainRelativeTo, oneYear, dateAdd). + // z. Let oneYearDays be moveResult.[[Days]]. + let (_, one_year_days) = + super::move_relative_date(calendar, &plain_relative_to, &one_year, context)?; - // y. Let oneYearDays be moveResult.[[Days]]. - let one_year_days = move_result.1; - // z. Let fractionalYears be years + days / abs(oneYearDays). - let fractional_years = self.years() + self.days() / one_year_days.abs(); + // aa. Let fractionalYears be years + fractionalDays / abs(oneYearDays). + let frac_years = self.years() + (frac_days / one_year_days.abs()); - // ?. Set years to RoundNumberToIncrement(fractionalYears, increment, roundingMode). - self.set_years(temporal::round_number_to_increment( - fractional_years, + // ab. Set years to RoundNumberToIncrement(fractionalYears, increment, roundingMode). + self.set_years(round_number_to_increment( + frac_years, increment, rounding_mode, )); - // ?. Set months, weeks, and days to 0. - self.set_months(0_f64); - self.set_weeks(0_f64); - self.set_days(0_f64); + // ac. Set total to fractionalYears. + // ad. Set months and weeks to 0. + self.set_months(0.0); + self.set_weeks(0.0); - fractional_years - self.years() + frac_years } - // 10. Else if unit is "month", then + // 9. Else if unit is "month", then TemporalUnit::Month => { - let mut relative_to = relative_to - .expect("relative_to must exist if unit is a month") - .clone(); - let calendar_obj = calendar.expect("calendar must exist at this point."); - - // a. Let yearsMonths be ! CreateTemporalDuration(years, months, 0, 0, 0, 0, 0, 0, 0, 0). - let years_month = super::create_temporal_duration( - Self::new( - DateDuration::new(self.years(), self.months(), 0.0, 0.0), - TimeDuration::default(), - ), - None, - context, - )?; + let mut frac_days = + frac_days.expect("assert that fractionalDays exists for TemporalUnit::Month"); - // b. If calendar is an Object, then + // a. Let calendar be plainRelativeTo.[[Calendar]]. + let plain_relative_to = plain_relative_to.expect("this must exist."); + let calendar = &plain_relative_to.calendar; + + // b. Let yearsMonths be ! CreateTemporalDuration(years, months, 0, 0, 0, 0, 0, 0, 0, 0). + let years_months = Self::new( + DateDuration::new(self.years(), self.months(), 0.0, 0.0), + TimeDuration::default(), + ); + + // c. If calendar is an Object, then // i. Let dateAdd be ? GetMethod(calendar, "dateAdd"). - // c. Else, + // d. Else, // i. Let dateAdd be unused. - // d. Let yearsMonthsLater be ? CalendarDateAdd(calendar, relativeTo, yearsMonths, undefined, dateAdd). - let years_months_later = calendar::calendar_date_add( - &calendar_obj, - &relative_to, - &years_month, - None, + // e. Let yearsMonthsLater be ? AddDate(calendar, plainRelativeTo, yearsMonths, undefined, dateAdd). + let years_months_later = plain_date::add_date( + calendar, + plain_relative_to, + &years_months, + &JsValue::undefined(), context, )?; - // e. Let yearsMonthsWeeks be ! CreateTemporalDuration(years, months, weeks, 0, 0, 0, 0, 0, 0, 0). - let years_months_weeks = super::create_temporal_duration( - Self::new( - DateDuration::new(self.years(), self.months(), self.weeks(), 0.0), - TimeDuration::default(), - ), - None, - context, - )?; + // f. Let yearsMonthsWeeks be ! CreateTemporalDuration(years, months, weeks, 0, 0, 0, 0, 0, 0, 0). + let years_months_weeks = Self::new( + DateDuration::new(self.years(), self.months(), self.weeks(), 0.0), + TimeDuration::default(), + ); - // f. Let yearsMonthsWeeksLater be ? CalendarDateAdd(calendar, relativeTo, yearsMonthsWeeks, undefined, dateAdd). - let years_months_weeks_later = calendar::calendar_date_add( - &calendar_obj, - &relative_to, + // g. Let yearsMonthsWeeksLater be ? AddDate(calendar, plainRelativeTo, yearsMonthsWeeks, undefined, dateAdd). + let years_months_weeks_later = plain_date::add_date( + calendar, + plain_relative_to, &years_months_weeks, - None, + &JsValue::undefined(), context, )?; - // g. Let weeksInDays be DaysUntil(yearsMonthsLater, yearsMonthsWeeksLater). + + // h. Let weeksInDays be DaysUntil(yearsMonthsLater, yearsMonthsWeeksLater). let weeks_in_days = super::days_until(&years_months_later, &years_months_weeks_later); - // h. Set relativeTo to yearsMonthsLater. - relative_to = years_months_later; + // i. Set plainRelativeTo to yearsMonthsLater. + let plain_relative_to = years_months_later; - // i. Let days be days + weeksInDays. - self.set_days(self.days() + f64::from(weeks_in_days)); + // j. Set fractionalDays to fractionalDays + weeksInDays. + frac_days += f64::from(weeks_in_days); - // j. If days < 0, let sign be -1; else, let sign be 1. - let sign = if self.days() < 0_f64 { -1_f64 } else { 1_f64 }; + // k. If fractionalDays < 0, let sign be -1; else, let sign be 1. + let sign = if frac_days < 0.0 { -1f64 } else { 1f64 }; - // k. Let oneMonth be ! CreateTemporalDuration(0, sign, 0, 0, 0, 0, 0, 0, 0, 0). - let one_month = super::create_temporal_duration( - Self::new( - DateDuration::new(0.0, sign, 0.0, 0.0), - TimeDuration::default(), - ), - None, - context, - )?; - - // l. Let moveResult be ? MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd). - let move_result = - super::move_relative_date(&calendar_obj, &relative_to, &one_month, context)?; + // l. Let oneMonth be ! CreateTemporalDuration(0, sign, 0, 0, 0, 0, 0, 0, 0, 0). + let one_month = Self::new( + DateDuration::new(0.0, sign, 0.0, 0.0), + TimeDuration::default(), + ); - // m. Set relativeTo to moveResult.[[RelativeTo]]. - relative_to = move_result.0; - // n. Let oneMonthDays be moveResult.[[Days]]. - let mut one_month_days = move_result.1; + // m. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, oneMonth, dateAdd). + // n. Set plainRelativeTo to moveResult.[[RelativeTo]]. + // o. Let oneMonthDays be moveResult.[[Days]]. + let (mut plain_relative_to, mut one_month_days) = + super::move_relative_date(calendar, &plain_relative_to, &one_month, context)?; - // o. Repeat, while abs(days) ≥ abs(oneMonthDays), - while self.days().abs() >= one_month_days.abs() { + // p. Repeat, while abs(fractionalDays) ≥ abs(oneMonthDays), + while frac_days.abs() >= one_month_days.abs() { // i. Set months to months + sign. self.set_months(self.months() + sign); - // ii. Set days to days - oneMonthDays. - self.set_days(self.days() - one_month_days); - // iii. Set moveResult to ? MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd). + + // ii. Set fractionalDays to fractionalDays - oneMonthDays. + frac_days -= one_month_days; + + // iii. Set moveResult to ? MoveRelativeDate(calendar, plainRelativeTo, oneMonth, dateAdd). let move_result = super::move_relative_date( - &calendar_obj, - &relative_to, - &one_month.clone(), + calendar, + &plain_relative_to, + &one_month, context, )?; - - // iv. Set relativeTo to moveResult.[[RelativeTo]]. - relative_to = move_result.0; + // iv. Set plainRelativeTo to moveResult.[[RelativeTo]]. + plain_relative_to = move_result.0; // v. Set oneMonthDays to moveResult.[[Days]]. one_month_days = move_result.1; } - // p. Let fractionalMonths be months + days / abs(oneMonthDays). - let fractional_months = self.months() + (self.days() / one_month_days.abs()); - // q. Set months to RoundNumberToIncrement(fractionalMonths, increment, roundingMode). - self.set_months(temporal::round_number_to_increment( - fractional_months, + // q. Let fractionalMonths be months + fractionalDays / abs(oneMonthDays). + let frac_months = self.months() + frac_days / one_month_days.abs(); + + // r. Set months to RoundNumberToIncrement(fractionalMonths, increment, roundingMode). + self.set_months(round_number_to_increment( + frac_months, increment, rounding_mode, )); - // r. Set remainder to fractionalMonths - months. - // s. Set weeks and days to 0. - self.set_weeks(0_f64); - self.set_days(0_f64); - fractional_months - self.months() + + // s. Set total to fractionalMonths. + // t. Set weeks to 0. + self.set_weeks(0.0); + frac_months } - // 11. Else if unit is "week", then + // 10. Else if unit is "week", then TemporalUnit::Week => { - let mut relative_to = relative_to - .expect("relative_to must exist if unit is a month") - .clone(); - let calendar_obj = calendar.expect("calendar must exist at this point."); - // a. If days < 0, let sign be -1; else, let sign be 1. - let sign = if self.days() < 0_f64 { -1_f64 } else { 1_f64 }; - // b. Let oneWeek be ! CreateTemporalDuration(0, 0, sign, 0, 0, 0, 0, 0, 0, 0). - let one_week = super::create_temporal_duration( - Self::new( - DateDuration::new(0.0, 0.0, sign, 0.0), - TimeDuration::default(), - ), - None, - context, - )?; + let mut frac_days = + frac_days.expect("assert that fractionalDays exists for TemporalUnit::Month"); + // a. Let calendar be plainRelativeTo.[[Calendar]]. + let plain_relative_to = plain_relative_to.expect("date must exist given Week"); + let calendar = &plain_relative_to.calendar; + + // b. If fractionalDays < 0, let sign be -1; else, let sign be 1. + let sign = if frac_days < 0.0 { -1f64 } else { 1f64 }; + + // c. Let oneWeek be ! CreateTemporalDuration(0, 0, sign, 0, 0, 0, 0, 0, 0, 0). + let one_week = Self::new( + DateDuration::new(0.0, 0.0, sign, 0.0), + TimeDuration::default(), + ); - // c. If calendar is an Object, then + // d. If calendar is an Object, then // i. Let dateAdd be ? GetMethod(calendar, "dateAdd"). - // d. Else, + // e. Else, // i. Let dateAdd be unused. - // e. Let moveResult be ? MoveRelativeDate(calendar, relativeTo, oneWeek, dateAdd). - let move_result = - super::move_relative_date(&calendar_obj, &relative_to, &one_week, context)?; - - // f. Set relativeTo to moveResult.[[RelativeTo]]. - relative_to = move_result.0; - // g. Let oneWeekDays be moveResult.[[Days]]. - let mut one_week_days = move_result.1; + // f. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, oneWeek, dateAdd). + // g. Set plainRelativeTo to moveResult.[[RelativeTo]]. + // h. Let oneWeekDays be moveResult.[[Days]]. + let (mut plain_relative_to, mut one_week_days) = + super::move_relative_date(calendar, plain_relative_to, &one_week, context)?; - // h. Repeat, while abs(days) ≥ abs(oneWeekDays), - while one_week_days.abs() <= self.days().abs() { + // i. Repeat, while abs(fractionalDays) ≥ abs(oneWeekDays), + while frac_days.abs() >= one_week_days.abs() { // i. Set weeks to weeks + sign. self.set_weeks(self.weeks() + sign); - // ii. Set days to days - oneWeekDays. - self.set_days(self.days() - one_week_days); - // iii. Set moveResult to ? MoveRelativeDate(calendar, relativeTo, oneWeek, dateAdd). + + // ii. Set fractionalDays to fractionalDays - oneWeekDays. + frac_days -= one_week_days; + + // iii. Set moveResult to ? MoveRelativeDate(calendar, plainRelativeTo, oneWeek, dateAdd). let move_result = super::move_relative_date( - &calendar_obj, - &relative_to, - &one_week.clone(), + calendar, + &plain_relative_to, + &one_week, context, )?; - // iv. Set relativeTo to moveResult.[[RelativeTo]]. - relative_to = move_result.0; + + // iv. Set plainRelativeTo to moveResult.[[RelativeTo]]. + plain_relative_to = move_result.0; // v. Set oneWeekDays to moveResult.[[Days]]. one_week_days = move_result.1; } - // i. Let fractionalWeeks be weeks + days / abs(oneWeekDays). - let fractional_weeks = self.weeks() + (self.days() / one_week_days.abs()); + // j. Let fractionalWeeks be weeks + fractionalDays / abs(oneWeekDays). + let frac_weeks = self.weeks() + frac_days / one_week_days.abs(); - // j. Set weeks to RoundNumberToIncrement(fractionalWeeks, increment, roundingMode). - self.set_weeks(temporal::round_number_to_increment( - fractional_weeks, + // k. Set weeks to RoundNumberToIncrement(fractionalWeeks, increment, roundingMode). + self.set_weeks(round_number_to_increment( + frac_weeks, increment, rounding_mode, )); - // k. Set remainder to fractionalWeeks - weeks. - // l. Set days to 0. - self.set_days(0_f64); - fractional_weeks - self.weeks() + // l. Set total to fractionalWeeks. + frac_weeks } - // 12. Else if unit is "day", then + // 11. Else if unit is "day", then TemporalUnit::Day => { - // a. Let fractionalDays be days. - let fractional_days = self.days(); - // b. Set days to RoundNumberToIncrement(days, increment, roundingMode). - self.set_days(temporal::round_number_to_increment( - self.days(), + let frac_days = + frac_days.expect("assert that fractionalDays exists for TemporalUnit::Day"); + + // a. Set days to RoundNumberToIncrement(fractionalDays, increment, roundingMode). + self.set_days(round_number_to_increment( + frac_days, increment, rounding_mode, )); - // c. Set remainder to fractionalDays - days. - fractional_days - self.days() + // b. Set total to fractionalDays. + frac_days } - // 13. Else if unit is "hour", then + // 12. Else if unit is "hour", then TemporalUnit::Hour => { + let frac_secs = + frac_secs.expect("Assert fractionSeconds exists for Temporal::Hour"); // a. Let fractionalHours be (fractionalSeconds / 60 + minutes) / 60 + hours. - let fractional_hours = - (fractional_secs / (60_f64 + self.minutes())) / 60_f64 + self.hours(); + let frac_hours = (frac_secs / 60f64 + self.minutes()) / 60f64 + self.hours(); // b. Set hours to RoundNumberToIncrement(fractionalHours, increment, roundingMode). - self.set_hours(temporal::round_number_to_increment( - fractional_hours, - increment, - rounding_mode, - )); + let rounded_hours = round_number_to_increment(frac_hours, increment, rounding_mode); + // c. Set total to fractionalHours. // d. Set minutes, seconds, milliseconds, microseconds, and nanoseconds to 0. - self.set_time_duration(TimeDuration::new(self.hours(), 0.0, 0.0, 0.0, 0.0, 0.0)); - - // c. Set remainder to fractionalHours - hours. - fractional_hours - self.hours() + self.set_time_duration(TimeDuration::new(rounded_hours, 0.0, 0.0, 0.0, 0.0, 0.0)); + frac_hours } - // 14. Else if unit is "minute", then + // 13. Else if unit is "minute", then TemporalUnit::Minute => { + let frac_secs = + frac_secs.expect("Assert fractionSeconds exists for Temporal::Hour"); // a. Let fractionalMinutes be fractionalSeconds / 60 + minutes. - let fraction_minutes = fractional_secs / 60_f64 + self.minutes(); + let frac_minutes = frac_secs / 60f64 + self.minutes(); // b. Set minutes to RoundNumberToIncrement(fractionalMinutes, increment, roundingMode). - self.set_minutes(temporal::round_number_to_increment( - fraction_minutes, - increment, - rounding_mode, - )); + let rounded_minutes = + round_number_to_increment(frac_minutes, increment, rounding_mode); + // c. Set total to fractionalMinutes. // d. Set seconds, milliseconds, microseconds, and nanoseconds to 0. - self.set_seconds(0_f64); - self.set_milliseconds(0_f64); - self.set_microseconds(0_f64); - self.set_nanoseconds(0_f64); - // c. Set remainder to fractionalMinutes - minutes. - fraction_minutes - self.minutes() + self.set_time_duration(TimeDuration::new( + self.hours(), + rounded_minutes, + 0.0, + 0.0, + 0.0, + 0.0, + )); + + frac_minutes } - // 15. Else if unit is "second", then + // 14. Else if unit is "second", then TemporalUnit::Second => { + let frac_secs = + frac_secs.expect("Assert fractionSeconds exists for Temporal::Second"); // a. Set seconds to RoundNumberToIncrement(fractionalSeconds, increment, roundingMode). - self.set_seconds(temporal::round_number_to_increment( - fractional_secs, + self.set_seconds(round_number_to_increment( + frac_secs, increment, rounding_mode, )); + // b. Set total to fractionalSeconds. // c. Set milliseconds, microseconds, and nanoseconds to 0. - self.set_milliseconds(0_f64); - self.set_microseconds(0_f64); - self.set_nanoseconds(0_f64); - // b. Set remainder to fractionalSeconds - seconds. - fractional_secs - self.seconds() + self.set_milliseconds(0.0); + self.set_microseconds(0.0); + self.set_nanoseconds(0.0); + + frac_secs } - // 16. Else if unit is "millisecond", then + // 15. Else if unit is "millisecond", then TemporalUnit::Millisecond => { // a. Let fractionalMilliseconds be nanoseconds × 10-6 + microseconds × 10-3 + milliseconds. - let fractional_millis = self - .nanoseconds() - .mul_add(1_000_000_f64, self.microseconds() * 1_000_f64) - + self.milliseconds(); + let fraction_millis = self.nanoseconds().mul_add( + 1_000_000f64, + self.microseconds().mul_add(1_000f64, self.milliseconds()), + ); + // b. Set milliseconds to RoundNumberToIncrement(fractionalMilliseconds, increment, roundingMode). - self.set_milliseconds(temporal::round_number_to_increment( - fractional_millis, + self.set_milliseconds(round_number_to_increment( + fraction_millis, increment, rounding_mode, )); + + // c. Set total to fractionalMilliseconds. // d. Set microseconds and nanoseconds to 0. - self.set_microseconds(0_f64); - self.set_nanoseconds(0_f64); - // c. Set remainder to fractionalMilliseconds - milliseconds. - fractional_millis - self.milliseconds() + self.set_microseconds(0.0); + self.set_nanoseconds(0.0); + fraction_millis } - // 17. Else if unit is "microsecond", then + // 16. Else if unit is "microsecond", then TemporalUnit::Microsecond => { // a. Let fractionalMicroseconds be nanoseconds × 10-3 + microseconds. - let fractional_micros = self.nanoseconds().mul_add(1_000_f64, self.microseconds()); + let frac_micros = self.nanoseconds().mul_add(1_000f64, self.microseconds()); + // b. Set microseconds to RoundNumberToIncrement(fractionalMicroseconds, increment, roundingMode). - self.set_microseconds(temporal::round_number_to_increment( - fractional_micros, + self.set_microseconds(round_number_to_increment( + frac_micros, increment, rounding_mode, )); + + // c. Set total to fractionalMicroseconds. // d. Set nanoseconds to 0. - self.set_nanoseconds(0_f64); - // c. Set remainder to fractionalMicroseconds - microseconds. - fractional_micros - self.microseconds() + self.set_nanoseconds(0.0); + frac_micros } - // 18. Else, + // 17. Else, TemporalUnit::Nanosecond => { // a. Assert: unit is "nanosecond". - // b. Set remainder to nanoseconds. - let remainder = self.nanoseconds(); + // b. Set total to nanoseconds. + let total = self.nanoseconds(); // c. Set nanoseconds to RoundNumberToIncrement(nanoseconds, increment, roundingMode). - self.set_nanoseconds(temporal::round_number_to_increment( + self.set_nanoseconds(round_number_to_increment( self.nanoseconds(), increment, rounding_mode, )); - // d. Set remainder to remainder - nanoseconds. - remainder - self.nanoseconds() + + total } TemporalUnit::Auto => unreachable!(), }; - // 19. Assert: days is an integer. - assert!(self.days().fract() == 0.0); - - // 20. Let duration be ? CreateDurationRecord(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds). - // 21. Return the Record { [[DurationRecord]]: duration, [[Remainder]]: remainder }. - Ok(remainder) + // 18. Let duration be ? CreateDurationRecord(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds). + // 19. Return the Record { [[DurationRecord]]: duration, [[Total]]: total }. + Ok(total) } /// 7.5.27 `AdjustRoundedDurationDays ( years, months, weeks, days, hours, minutes, seconds, milliseconds, diff --git a/boa_engine/src/builtins/temporal/fields.rs b/boa_engine/src/builtins/temporal/fields.rs index 39c60911ab4..3d03a0cb334 100644 --- a/boa_engine/src/builtins/temporal/fields.rs +++ b/boa_engine/src/builtins/temporal/fields.rs @@ -39,8 +39,8 @@ bitflags! { /// /// ## Table 17: Temporal field requirements /// -/// | Property | Conversion | Default | -/// | -------------|---------------------------------|------------| +/// | Property | Conversion | Default | +/// | -------------|-----------------------------------|------------| /// | "year" | `ToIntegerWithTruncation` | undefined | /// | "month" | `ToPositiveIntegerWithTruncation` | undefined | /// | "monthCode" | `ToPrimitiveAndRequireString` | undefined | @@ -54,7 +54,7 @@ bitflags! { /// | "offset" | `ToPrimitiveAndRequireString` | undefined | /// | "era" | `ToPrimitiveAndRequireString` | undefined | /// | "eraYear" | `ToIntegerWithTruncation` | undefined | -/// | "timeZone" | | undefined | +/// | "timeZone" | `None` | undefined | #[derive(Debug)] pub(crate) struct TemporalFields { bit_map: FieldMap, @@ -114,11 +114,11 @@ impl TemporalFields { #[inline] fn set_field_value( &mut self, - field: &str, + field: &JsString, value: &JsValue, context: &mut Context<'_>, ) -> JsResult<()> { - match field { + match field.to_std_string_escaped().as_str() { "year" => self.set_year(value, context)?, "month" => self.set_month(value, context)?, "monthCode" => self.set_month_code(value, context)?, @@ -281,9 +281,9 @@ impl TemporalFields { /// This is the equivalant to Abstract Operation 13.46 `PrepareTemporalFields` pub(crate) fn from_js_object( fields: &JsObject, - field_names: &mut Vec, - required_fields: &mut Vec, // None when Partial - extended_fields: Option>, + field_names: &mut Vec, + required_fields: &mut Vec, // None when Partial + extended_fields: Option>, partial: bool, dup_behaviour: Option, context: &mut Context<'_>, @@ -319,7 +319,9 @@ impl TemporalFields { // 7. For each property name property of sortedFieldNames, do for field in &*field_names { // a. If property is one of "constructor" or "__proto__", then - if field.as_str() == "constructor" || field.as_str() == "__proto__" { + if field.to_std_string_escaped().as_str() == "constructor" + || field.to_std_string_escaped().as_str() == "__proto__" + { // i. Throw a RangeError exception. return Err(JsNativeError::range() .with_message("constructor or proto is out of field range.") @@ -331,8 +333,7 @@ impl TemporalFields { // b. If property is not equal to previousProperty, then if new_value { // i. Let value be ? Get(fields, property). - let value = - fields.get(PropertyKey::from(JsString::from(field.clone())), context)?; + let value = fields.get(PropertyKey::from(field.clone()), context)?; // ii. If value is not undefined, then if !value.is_undefined() { // 1. Set any to true. diff --git a/boa_engine/src/builtins/temporal/instant/mod.rs b/boa_engine/src/builtins/temporal/instant/mod.rs index 00a1b01a376..6c5c842be28 100644 --- a/boa_engine/src/builtins/temporal/instant/mod.rs +++ b/boa_engine/src/builtins/temporal/instant/mod.rs @@ -606,11 +606,13 @@ fn diff_instant( nanoseconds.to_f64(), ), ); - let _rem = roundable_duration.round_duration( + let _total = roundable_duration.round_duration( rounding_increment, smallest_unit, rounding_mode, None, + None, + None, context, )?; diff --git a/boa_engine/src/builtins/temporal/mod.rs b/boa_engine/src/builtins/temporal/mod.rs index 133b35cb7c9..b36d901b7fc 100644 --- a/boa_engine/src/builtins/temporal/mod.rs +++ b/boa_engine/src/builtins/temporal/mod.rs @@ -294,7 +294,7 @@ pub(crate) fn validate_temporal_rounding_increment( pub(crate) fn to_relative_temporal_object( _options: &JsObject, _context: &mut Context<'_>, -) -> JsResult { +) -> JsResult<(Option, Option)> { Err(JsNativeError::range() .with_message("not yet implemented.") .into()) diff --git a/boa_engine/src/builtins/temporal/plain_date/iso.rs b/boa_engine/src/builtins/temporal/plain_date/iso.rs index c85dd1f7cd8..52eccc4fb13 100644 --- a/boa_engine/src/builtins/temporal/plain_date/iso.rs +++ b/boa_engine/src/builtins/temporal/plain_date/iso.rs @@ -1,7 +1,7 @@ //! An `IsoDateRecord` that represents the `[[ISOYear]]`, `[[ISOMonth]]`, and `[[ISODay]]` internal slots. use crate::{ - builtins::temporal::{self, TemporalFields}, + builtins::temporal::{self, options::ArithmeticOverflow, DateDuration, TemporalFields}, JsNativeError, JsResult, JsString, }; @@ -59,16 +59,16 @@ impl IsoDateRecord { year: i32, month: i32, day: i32, - overflow: &JsString, + overflow: ArithmeticOverflow, ) -> JsResult { - match overflow.to_std_string_escaped().as_str() { - "constrain" => { + match overflow { + ArithmeticOverflow::Constrain => { let m = month.clamp(1, 12); let days_in_month = temporal::calendar::utils::iso_days_in_month(year, month); let d = day.clamp(1, days_in_month); Ok(Self::new(year, m, d)) } - "reject" => { + ArithmeticOverflow::Reject => { let date = Self::new(year, month, day); if !date.is_valid() { return Err(JsNativeError::range() @@ -77,7 +77,6 @@ impl IsoDateRecord { } Ok(date) } - _ => unreachable!(), } } @@ -86,7 +85,7 @@ impl IsoDateRecord { /// Note: fields.month must be resolved prior to using `from_temporal_fields` pub(crate) fn from_temporal_fields( fields: &TemporalFields, - overflow: &JsString, + overflow: ArithmeticOverflow, ) -> JsResult { Self::from_unregulated( fields.year().expect("Cannot fail per spec"), @@ -99,7 +98,7 @@ impl IsoDateRecord { /// Create a Month-Day record from a `TemporalFields` object. pub(crate) fn month_day_from_temporal_fields( fields: &TemporalFields, - overflow: &JsString, + overflow: ArithmeticOverflow, ) -> JsResult { match fields.year() { Some(year) => Self::from_unregulated( @@ -202,15 +201,16 @@ impl IsoDateRecord { /// 3.5.11 `AddISODate ( year, month, day, years, months, weeks, days, overflow )` pub(crate) fn add_iso_date( &self, - years: i32, - months: i32, - weeks: i32, - days: i32, - overflow: &JsString, + date_duration: DateDuration, + overflow: ArithmeticOverflow, ) -> JsResult { // 1. Assert: year, month, day, years, months, weeks, and days are integers. // 2. Assert: overflow is either "constrain" or "reject". - let mut intermediate = Self::new(self.year + years, self.month + months, 0); + let mut intermediate = Self::new( + self.year + date_duration.years() as i32, + self.month + date_duration.months() as i32, + 0, + ); // 3. Let intermediate be ! BalanceISOYearMonth(year + years, month + months). intermediate.balance_year_month(); @@ -225,7 +225,7 @@ impl IsoDateRecord { // 5. Set days to days + 7 × weeks. // 6. Let d be intermediate.[[Day]] + days. - let additional_days = days + (weeks * 7); + let additional_days = date_duration.days() as i32 + (date_duration.weeks() as i32 * 7); new_date.day += additional_days; // 7. Return BalanceISODate(intermediate.[[Year]], intermediate.[[Month]], d). diff --git a/boa_engine/src/builtins/temporal/plain_date/mod.rs b/boa_engine/src/builtins/temporal/plain_date/mod.rs index 5fe1de68473..441be37ee0e 100644 --- a/boa_engine/src/builtins/temporal/plain_date/mod.rs +++ b/boa_engine/src/builtins/temporal/plain_date/mod.rs @@ -4,6 +4,7 @@ use crate::{ builtins::{ options::{get_option, get_options_object}, + temporal::options::TemporalUnit, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, }, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, @@ -17,7 +18,10 @@ use crate::{ use boa_parser::temporal::{IsoCursor, TemporalDateTimeString}; use boa_profiler::Profiler; -use super::{options::ArithmeticOverflow, plain_date::iso::IsoDateRecord, plain_date_time}; +use super::{ + calendar, duration::DurationRecord, options::ArithmeticOverflow, + plain_date::iso::IsoDateRecord, plain_date_time, DateDuration, TimeDuration, +}; pub(crate) mod iso; @@ -28,6 +32,15 @@ pub struct PlainDate { pub(crate) calendar: JsValue, // Calendar can probably be stored as a JsObject. } +impl PlainDate { + pub(crate) fn new(record: IsoDateRecord, calendar: JsValue) -> Self { + Self { + inner: record, + calendar, + } + } +} + impl BuiltInObject for PlainDate { const NAME: JsString = StaticJsStrings::PLAIN_DATE; } @@ -378,6 +391,13 @@ impl PlainDate { // -- `PlainDate` Abstract Operations -- +impl PlainDate { + /// Utitily function for translating a `Temporal.PlainDate` into a `JsObject`. + pub(crate) fn as_object(&self, context: &mut Context<'_>) -> JsResult { + create_temporal_date(self.inner, self.calendar.clone(), None, context) + } +} + // 3.5.2 `CreateIsoDateRecord` // Implemented on `IsoDateRecord` @@ -429,10 +449,7 @@ pub(crate) fn create_temporal_date( // 8. Set object.[[Calendar]] to calendar. let obj = JsObject::from_proto_and_data( prototype, - ObjectData::plain_date(PlainDate { - inner: iso_date, - calendar, - }), + ObjectData::plain_date(PlainDate::new(iso_date, calendar)), ); // 9. Return object. @@ -554,14 +571,94 @@ pub(crate) fn to_temporal_date( // 3.5.5. DifferenceIsoDate // Implemented on IsoDateRecord. -// 3.5.6 RegulateIsoDate +/// 3.5.6 `DifferenceDate ( calendar, one, two, options )` +pub(crate) fn difference_date( + calendar: &JsValue, + one: &PlainDate, + two: &PlainDate, + largest_unit: TemporalUnit, + context: &mut Context<'_>, +) -> JsResult { + // 1. Assert: one.[[Calendar]] and two.[[Calendar]] have been determined to be equivalent as with CalendarEquals. + // 2. Assert: options is an ordinary Object. + // 3. Assert: options.[[Prototype]] is null. + // 4. Assert: options has a "largestUnit" data property. + // 5. If one.[[ISOYear]] = two.[[ISOYear]] and one.[[ISOMonth]] = two.[[ISOMonth]] and one.[[ISODay]] = two.[[ISODay]], then + if one.inner.year() == two.inner.year() + && one.inner.month() == two.inner.month() + && one.inner.day() == two.inner.day() + { + // a. Return ! CreateTemporalDuration(0, 0, 0, 0, 0, 0, 0, 0, 0, 0). + return Ok(DurationRecord::default()); + } + // 6. If ! Get(options, "largestUnit") is "day", then + if largest_unit == TemporalUnit::Day { + // a. Let days be DaysUntil(one, two). + let days = super::duration::days_until(one, two); + // b. Return ! CreateTemporalDuration(0, 0, 0, days, 0, 0, 0, 0, 0, 0). + return Ok(DurationRecord::new( + DateDuration::new(0.0, 0.0, 0.0, f64::from(days)), + TimeDuration::default(), + )); + } + + // Create the options object prior to sending it to the calendars. + let options_obj = JsObject::with_null_proto(); + + options_obj.create_data_property_or_throw( + utf16!("largestUnit"), + JsString::from(largest_unit.to_string()), + context, + )?; + + // 7. Return ? CalendarDateUntil(calendar, one, two, options). + calendar::calendar_date_until(calendar, one, two, &options_obj.into(), context) +} + +// 3.5.7 RegulateIsoDate // Implemented on IsoDateRecord. -// 3.5.7 IsValidIsoDate +// 3.5.8 IsValidIsoDate // Implemented on IsoDateRecord. -// 3.5.8 BalanceIsoDate +// 3.5.9 BalanceIsoDate // Implemented on IsoDateRecord. -// 3.5.11 AddISODate ( year, month, day, years, months, weeks, days, overflow ) +// 3.5.12 AddISODate ( year, month, day, years, months, weeks, days, overflow ) // Implemented on IsoDateRecord + +/// 3.5.13 `AddDate ( calendar, plainDate, duration [ , options [ , dateAdd ]] )` +pub(crate) fn add_date( + calendar: &JsValue, + plain_date: &PlainDate, + duration: &DurationRecord, + options: &JsValue, + context: &mut Context<'_>, +) -> JsResult { + // 1. If options is not present, set options to undefined. + // 2. If duration.[[Years]] ≠ 0, or duration.[[Months]] ≠ 0, or duration.[[Weeks]] ≠ 0, then + if duration.years() != 0.0 || duration.months() != 0.0 || duration.weeks() != 0.0 { + // a. If dateAdd is not present, then + // i. Set dateAdd to unused. + // ii. If calendar is an Object, set dateAdd to ? GetMethod(calendar, "dateAdd"). + // b. Return ? CalendarDateAdd(calendar, plainDate, duration, options, dateAdd). + return calendar::calendar_date_add(calendar, plain_date, duration, options, context); + } + + // 3. Let overflow be ? ToTemporalOverflow(options). + let options_obj = get_options_object(options)?; + let overflow = get_option(&options_obj, utf16!("overflow"), context)? + .unwrap_or(ArithmeticOverflow::Constrain); + + let mut intermediate = *duration; + // 4. Let days be ? BalanceTimeDuration(duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], "day").[[Days]]. + intermediate.balance_time_duration(TemporalUnit::Day, None)?; + + // 5. Let result be ? AddISODate(plainDate.[[ISOYear]], plainDate.[[ISOMonth]], plainDate.[[ISODay]], 0, 0, 0, days, overflow). + let result = plain_date + .inner + .add_iso_date(intermediate.date(), overflow)?; + + // 6. Return ! CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], calendar). + Ok(PlainDate::new(result, plain_date.calendar.clone())) +}