From f3af32913ff7294f0b19a6cbcd84872e2967b0b8 Mon Sep 17 00:00:00 2001 From: nekevss Date: Thu, 2 Nov 2023 22:05:49 -0400 Subject: [PATCH 1/9] Update round and total method --- .../src/builtins/temporal/duration/mod.rs | 36 +------------------ 1 file changed, 1 insertion(+), 35 deletions(-) diff --git a/boa_engine/src/builtins/temporal/duration/mod.rs b/boa_engine/src/builtins/temporal/duration/mod.rs index cf67023e51e..ce1f9420eec 100644 --- a/boa_engine/src/builtins/temporal/duration/mod.rs +++ b/boa_engine/src/builtins/temporal/duration/mod.rs @@ -691,41 +691,7 @@ impl Duration { // 24. If maximum is not undefined, perform ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false). if let Some(max) = maximum { - validate_temporal_rounding_increment(rounding_increment.into(), max.into(), false)?; - } - - // 25. Let hoursToDaysConversionMayOccur be false. - // 26. If duration.[[Days]] ≠ 0 and zonedRelativeTo is not undefined, set hoursToDaysConversionMayOccur to true. - // 27. Else if abs(duration.[[Hours]]) ≥ 24, set hoursToDaysConversionMayOccur to true. - let conversion_may_occur = if duration.inner.days() != 0.0 && zoned_relative_to.is_some() { - true - } else { - 24f64 <= duration.inner.hours().abs() - }; - - // 28. If smallestUnit is "nanosecond" and roundingIncrement = 1, let roundingGranularityIsNoop be true; else let roundingGranularityIsNoop be false. - let is_noop = smallest_unit == TemporalUnit::Nanosecond && rounding_increment == 1; - - // 29. If duration.[[Years]] = 0 and duration.[[Months]] = 0 and duration.[[Weeks]] = 0, let calendarUnitsPresent be false; else let calendarUnitsPresent be true. - let calendar_units_present = !(duration.inner.years() == 0f64 - || duration.inner.months() == 0f64 - || duration.inner.weeks() == 0f64); - - // 30. If roundingGranularityIsNoop is true, and largestUnit is existingLargestUnit, - // and calendarUnitsPresent is false, and hoursToDaysConversionMayOccur is false, - // and abs(duration.[[Minutes]]) < 60, and abs(duration.[[Seconds]]) < 60, - // and abs(duration.[[Milliseconds]]) < 1000, and abs(duration.[[Microseconds]]) < 1000, - // and abs(duration.[[Nanoseconds]]) < 1000, then - if is_noop - && largest_unit == existing_largest_unit - && !calendar_units_present - && !conversion_may_occur - && duration.inner.is_time_within_range() - { - // a. NOTE: The above conditions mean that the operation will have no effect: the smallest unit and - // rounding increment will leave the total duration unchanged, and it can be determined without - // calling a calendar or time zone method that no balancing will take place. - // b. Return ! CreateTemporalDuration(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]]). + validate_temporal_rounding_increment(rounding_increment, f64::from(max), false)?; } // 31. Let precalculatedPlainDateTime be undefined. From 5e6e2617d8c6b27fcbdc10456e521503510cd0a4 Mon Sep 17 00:00:00 2001 From: nekevss Date: Fri, 3 Nov 2023 20:47:01 -0400 Subject: [PATCH 2/9] Begin Temporal crate migration --- Cargo.lock | 13 + Cargo.toml | 2 + boa_engine/Cargo.toml | 1 + .../src/builtins/temporal/calendar/iso.rs | 343 ---- .../src/builtins/temporal/calendar/mod.rs | 1563 +++------------- .../src/builtins/temporal/calendar/object.rs | 299 +++ .../src/builtins/temporal/calendar/tests.rs | 39 + .../src/builtins/temporal/calendar/utils.rs | 107 -- .../src/builtins/temporal/date_equations.rs | 121 -- .../src/builtins/temporal/duration/mod.rs | 348 ++-- .../src/builtins/temporal/duration/record.rs | 3 + boa_engine/src/builtins/temporal/error.rs | 20 + boa_engine/src/builtins/temporal/fields.rs | 689 ++----- .../src/builtins/temporal/instant/mod.rs | 111 +- boa_engine/src/builtins/temporal/mod.rs | 15 +- boa_engine/src/builtins/temporal/options.rs | 257 +-- .../src/builtins/temporal/plain_date/iso.rs | 3 +- .../src/builtins/temporal/plain_date/mod.rs | 238 +-- .../builtins/temporal/plain_date_time/mod.rs | 17 +- .../builtins/temporal/plain_month_day/mod.rs | 31 +- .../builtins/temporal/plain_year_month/mod.rs | 48 +- boa_engine/src/builtins/temporal/tests.rs | 17 - .../builtins/temporal/zoned_date_time/mod.rs | 3 +- boa_temporal/Cargo.toml | 21 + boa_temporal/README.md | 5 + boa_temporal/src/calendar.rs | 478 +++++ boa_temporal/src/calendar/iso.rs | 274 +++ boa_temporal/src/date.rs | 157 ++ boa_temporal/src/datetime.rs | 87 + boa_temporal/src/duration.rs | 1622 +++++++++++++++++ boa_temporal/src/error.rs | 92 + boa_temporal/src/fields.rs | 756 ++++++++ boa_temporal/src/iso.rs | 527 ++++++ boa_temporal/src/lib.rs | 119 ++ boa_temporal/src/month_day.rs | 42 + boa_temporal/src/options.rs | 355 ++++ boa_temporal/src/time.rs | 30 + boa_temporal/src/utils.rs | 352 ++++ boa_temporal/src/year_month.rs | 42 + boa_temporal/src/zoneddatetime.rs | 6 + 40 files changed, 6128 insertions(+), 3125 deletions(-) delete mode 100644 boa_engine/src/builtins/temporal/calendar/iso.rs create mode 100644 boa_engine/src/builtins/temporal/calendar/object.rs delete mode 100644 boa_engine/src/builtins/temporal/calendar/utils.rs delete mode 100644 boa_engine/src/builtins/temporal/date_equations.rs create mode 100644 boa_engine/src/builtins/temporal/error.rs create mode 100644 boa_temporal/Cargo.toml create mode 100644 boa_temporal/README.md create mode 100644 boa_temporal/src/calendar.rs create mode 100644 boa_temporal/src/calendar/iso.rs create mode 100644 boa_temporal/src/date.rs create mode 100644 boa_temporal/src/datetime.rs create mode 100644 boa_temporal/src/duration.rs create mode 100644 boa_temporal/src/error.rs create mode 100644 boa_temporal/src/fields.rs create mode 100644 boa_temporal/src/iso.rs create mode 100644 boa_temporal/src/lib.rs create mode 100644 boa_temporal/src/month_day.rs create mode 100644 boa_temporal/src/options.rs create mode 100644 boa_temporal/src/time.rs create mode 100644 boa_temporal/src/utils.rs create mode 100644 boa_temporal/src/year_month.rs create mode 100644 boa_temporal/src/zoneddatetime.rs diff --git a/Cargo.lock b/Cargo.lock index c6ab9477d1c..88a60f4b510 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -442,6 +442,7 @@ dependencies = [ "boa_macros", "boa_parser", "boa_profiler", + "boa_temporal", "bytemuck", "cfg-if", "chrono", @@ -606,6 +607,18 @@ dependencies = [ "textwrap", ] +[[package]] +name = "boa_temporal" +version = "0.17.0" +dependencies = [ + "bitflags 2.4.1", + "icu_calendar", + "num-bigint", + "num-traits", + "rustc-hash", + "tinystr", +] + [[package]] name = "boa_tester" version = "0.17.0" diff --git a/Cargo.toml b/Cargo.toml index ead4e6dfca4..87c9f6eba6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ members = [ "boa_parser", "boa_profiler", "boa_runtime", + "boa_temporal", "boa_tester", "boa_wasm", ] @@ -38,6 +39,7 @@ boa_macros = { version = "~0.17.0", path = "boa_macros" } boa_parser = { version = "~0.17.0", path = "boa_parser" } boa_profiler = { version = "~0.17.0", path = "boa_profiler" } boa_runtime = { version = "~0.17.0", path = "boa_runtime" } +boa_temporal = {version = "~0.17.0", path = "boa_temporal" } # Shared deps arbitrary = "1" diff --git a/boa_engine/Cargo.toml b/boa_engine/Cargo.toml index 288238c9099..5eff59ba3e3 100644 --- a/boa_engine/Cargo.toml +++ b/boa_engine/Cargo.toml @@ -62,6 +62,7 @@ boa_profiler.workspace = true boa_macros.workspace = true boa_ast.workspace = true boa_parser.workspace = true +boa_temporal.workspace = true serde = { workspace = true, features = ["derive", "rc"] } serde_json.workspace = true rand = "0.8.5" diff --git a/boa_engine/src/builtins/temporal/calendar/iso.rs b/boa_engine/src/builtins/temporal/calendar/iso.rs deleted file mode 100644 index 3abb440c777..00000000000 --- a/boa_engine/src/builtins/temporal/calendar/iso.rs +++ /dev/null @@ -1,343 +0,0 @@ -//! Implementation of the "iso8601" `BuiltinCalendar`. - -use crate::{ - builtins::temporal::{ - self, - date_equations::mathematical_days_in_year, - duration::DurationRecord, - options::{ArithmeticOverflow, TemporalUnit}, - plain_date::iso::IsoDateRecord, - }, - js_string, JsNativeError, JsResult, JsString, -}; - -use super::{BuiltinCalendar, FieldsType}; - -use icu_calendar::{ - iso::Iso, - week::{RelativeUnit, WeekCalculator}, - Calendar, Date, -}; - -pub(crate) struct IsoCalendar; - -impl BuiltinCalendar for IsoCalendar { - /// Temporal 15.8.2.1 `Temporal.prototype.dateFromFields( fields [, options])` - Supercedes 12.5.4 - /// - /// This is a basic implementation for an iso8601 calendar's `dateFromFields` method. - fn date_from_fields( - &self, - fields: &mut temporal::TemporalFields, - overflow: ArithmeticOverflow, - ) -> JsResult { - // NOTE: we are in ISO by default here. - // a. Perform ? ISOResolveMonth(fields). - // b. Let result be ? ISODateFromFields(fields, overflow). - fields.iso_resolve_month()?; - - // Extra: handle reulating/overflow until implemented on `icu_calendar` - fields.regulate(overflow)?; - - let date = Date::try_new_iso_date( - fields.year().unwrap_or(0), - fields.month().unwrap_or(250) as u8, - fields.day().unwrap_or(250) as u8, - ) - .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; - - // 9. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], "iso8601"). - Ok(IsoDateRecord::from_date_iso(date)) - } - - /// 12.5.5 `Temporal.Calendar.prototype.yearMonthFromFields ( fields [ , options ] )` - /// - /// This is a basic implementation for an iso8601 calendar's `yearMonthFromFields` method. - fn year_month_from_fields( - &self, - fields: &mut temporal::TemporalFields, - overflow: ArithmeticOverflow, - ) -> JsResult { - // 9. If calendar.[[Identifier]] is "iso8601", then - // a. Perform ? ISOResolveMonth(fields). - fields.iso_resolve_month()?; - - // b. Let result be ? ISOYearMonthFromFields(fields, overflow). - fields.regulate_year_month(overflow); - - let result = Date::try_new_iso_date( - fields.year().unwrap_or(0), - fields.month().unwrap_or(250) as u8, - fields.day().unwrap_or(20) as u8, - ) - .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; - - // 10. Return ? CreateTemporalYearMonth(result.[[Year]], result.[[Month]], "iso8601", result.[[ReferenceISODay]]). - Ok(IsoDateRecord::from_date_iso(result)) - } - - /// 12.5.6 `Temporal.Calendar.prototype.monthDayFromFields ( fields [ , options ] )` - /// - /// This is a basic implementation for an iso8601 calendar's `monthDayFromFields` method. - fn month_day_from_fields( - &self, - fields: &mut temporal::TemporalFields, - overflow: ArithmeticOverflow, - ) -> JsResult { - // 8. Perform ? ISOResolveMonth(fields). - fields.iso_resolve_month()?; - - fields.regulate(overflow)?; - - // TODO: double check error mapping is correct for specifcation/test262. - // 9. Let result be ? ISOMonthDayFromFields(fields, overflow). - let result = Date::try_new_iso_date( - fields.year().unwrap_or(1972), - fields.month().unwrap_or(250) as u8, - fields.day().unwrap_or(250) as u8, - ) - .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; - - // 10. Return ? CreateTemporalMonthDay(result.[[Month]], result.[[Day]], "iso8601", result.[[ReferenceISOYear]]). - Ok(IsoDateRecord::from_date_iso(result)) - } - - /// 12.5.7 `Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] )` - /// - /// Below implements the basic implementation for an iso8601 calendar's `dateAdd` method. - fn date_add( - &self, - _date: &temporal::PlainDate, - _duration: &DurationRecord, - _overflow: ArithmeticOverflow, - ) -> JsResult { - // TODO: Not stable on `ICU4X`. Implement once completed. - Err(JsNativeError::range() - .with_message("feature not implemented.") - .into()) - - // 9. Let result be ? AddISODate(date.[[ISOYear]], date.[[ISOMonth]], date.[[ISODay]], duration.[[Years]], duration.[[Months]], duration.[[Weeks]], balanceResult.[[Days]], overflow). - // 10. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], "iso8601"). - } - - /// 12.5.8 `Temporal.Calendar.prototype.dateUntil ( one, two [ , options ] )` - /// - /// Below implements the basic implementation for an iso8601 calendar's `dateUntil` method. - fn date_until( - &self, - _one: &temporal::PlainDate, - _two: &temporal::PlainDate, - _largest_unit: TemporalUnit, - ) -> JsResult { - // TODO: Not stable on `ICU4X`. Implement once completed. - Err(JsNativeError::range() - .with_message("Feature not yet implemented.") - .into()) - - // 9. Let result be DifferenceISODate(one.[[ISOYear]], one.[[ISOMonth]], one.[[ISODay]], two.[[ISOYear]], two.[[ISOMonth]], two.[[ISODay]], largestUnit). - // 10. Return ! CreateTemporalDuration(result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], 0, 0, 0, 0, 0, 0). - } - - /// `Temporal.Calendar.prototype.era( dateLike )` for iso8601 calendar. - fn era(&self, _: &IsoDateRecord) -> JsResult> { - // Returns undefined on iso8601. - Ok(None) - } - - /// `Temporal.Calendar.prototype.eraYear( dateLike )` for iso8601 calendar. - fn era_year(&self, _: &IsoDateRecord) -> JsResult> { - // Returns undefined on iso8601. - Ok(None) - } - - /// Returns the `year` for the `Iso` calendar. - fn year(&self, date_like: &IsoDateRecord) -> JsResult { - let date = Date::try_new_iso_date( - date_like.year(), - date_like.month() as u8, - date_like.day() as u8, - ) - .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; - - Ok(date.year().number) - } - - /// Returns the `month` for the `Iso` calendar. - fn month(&self, date_like: &IsoDateRecord) -> JsResult { - let date = Date::try_new_iso_date( - date_like.year(), - date_like.month() as u8, - date_like.day() as u8, - ) - .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; - - Ok(date.month().ordinal as i32) - } - - /// Returns the `monthCode` for the `Iso` calendar. - fn month_code(&self, date_like: &IsoDateRecord) -> JsResult { - let date = Date::try_new_iso_date( - date_like.year(), - date_like.month() as u8, - date_like.day() as u8, - ) - .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; - - Ok(JsString::from(date.month().code.to_string())) - } - - /// Returns the `day` for the `Iso` calendar. - fn day(&self, date_like: &IsoDateRecord) -> JsResult { - let date = Date::try_new_iso_date( - date_like.year(), - date_like.month() as u8, - date_like.day() as u8, - ) - .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; - - Ok(date.day_of_month().0 as i32) - } - - /// Returns the `dayOfWeek` for the `Iso` calendar. - fn day_of_week(&self, date_like: &IsoDateRecord) -> JsResult { - let date = Date::try_new_iso_date( - date_like.year(), - date_like.month() as u8, - date_like.day() as u8, - ) - .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; - - Ok(date.day_of_week() as i32) - } - - /// Returns the `dayOfYear` for the `Iso` calendar. - fn day_of_year(&self, date_like: &IsoDateRecord) -> JsResult { - let date = Date::try_new_iso_date( - date_like.year(), - date_like.month() as u8, - date_like.day() as u8, - ) - .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; - - 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) -> JsResult { - // TODO: Determine `ICU4X` equivalent. - let date = Date::try_new_iso_date( - date_like.year(), - date_like.month() as u8, - date_like.day() as u8, - ) - .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; - - let week_calculator = WeekCalculator::default(); - - let week_of = date - .week_of_year(&week_calculator) - .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; - - Ok(i32::from(week_of.week)) - } - - /// Returns the `yearOfWeek` for the `Iso` calendar. - fn year_of_week(&self, date_like: &IsoDateRecord) -> JsResult { - // TODO: Determine `ICU4X` equivalent. - let date = Date::try_new_iso_date( - date_like.year(), - date_like.month() as u8, - date_like.day() as u8, - ) - .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; - - let week_calculator = WeekCalculator::default(); - - let week_of = date - .week_of_year(&week_calculator) - .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; - - match week_of.unit { - 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) -> JsResult { - Ok(7) - } - - /// Returns the `daysInMonth` value for the `Iso` calendar. - fn days_in_month(&self, date_like: &IsoDateRecord) -> JsResult { - let date = Date::try_new_iso_date( - date_like.year(), - date_like.month() as u8, - date_like.day() as u8, - ) - .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; - - Ok(i32::from(date.days_in_month())) - } - - /// Returns the `daysInYear` value for the `Iso` calendar. - fn days_in_year(&self, date_like: &IsoDateRecord) -> JsResult { - let date = Date::try_new_iso_date( - date_like.year(), - date_like.month() as u8, - date_like.day() as u8, - ) - .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; - - Ok(i32::from(date.days_in_year())) - } - - /// Return the amount of months in an ISO Calendar. - 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) -> JsResult { - // `ICU4X`'s `CalendarArithmetic` is currently private. - if mathematical_days_in_year(date_like.year()) == 366 { - return Ok(true); - } - Ok(false) - } - - // Resolve the fields for the iso calendar. - 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, _: 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. - unreachable!() - } - - /// Returns the `CalendarFieldKeysToIgnore` implementation for ISO. - fn field_keys_to_ignore(&self, additional_keys: Vec) -> Vec { - let mut result = Vec::new(); - 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 - } - - // NOTE: This is currently not a name that is compliant with - // the Temporal proposal. For debugging purposes only. - /// Returns the debug name. - fn debug_name(&self) -> &str { - Iso.debug_name() - } -} diff --git a/boa_engine/src/builtins/temporal/calendar/mod.rs b/boa_engine/src/builtins/temporal/calendar/mod.rs index f1e39422fc2..2ba39e612dd 100644 --- a/boa_engine/src/builtins/temporal/calendar/mod.rs +++ b/boa_engine/src/builtins/temporal/calendar/mod.rs @@ -1,13 +1,10 @@ //! An implementation of the `Temporal` proposal's Calendar builtin. -use self::iso::IsoCalendar; +use std::str::FromStr; use super::{ create_temporal_date, create_temporal_duration, create_temporal_month_day, - create_temporal_year_month, - options::{ArithmeticOverflow, TemporalUnit, TemporalUnitGroup}, - plain_date::iso::IsoDateRecord, - DurationRecord, PlainDate, TemporalFields, + create_temporal_year_month, fields, options::TemporalUnitGroup, }; use crate::{ builtins::{ @@ -24,147 +21,32 @@ use crate::{ Context, JsArgs, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; use boa_profiler::Profiler; -use rustc_hash::FxHashMap; +use boa_temporal::{ + calendar::{ + AvailableCalendars, CalendarDateLike, CalendarFieldsType, CalendarSlot, + CALENDAR_PROTOCOL_METHODS, + }, + options::{ArithmeticOverflow, TemporalUnit}, +}; -mod iso; -pub(crate) mod utils; +mod object; +use object::CustomRuntimeCalendar; + +#[cfg(feature = "experimental")] #[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`. -// -// NOTE (re above's TODO): Most likely context is only going to be needed for `dateFromFields`, -// `yearMonthFromFields`, `monthDayFromFields`, `dateAdd`, and `dateUntil`. -/// A trait for implementing a Builtin Calendar's Calendar Protocol in Rust. -pub(crate) trait BuiltinCalendar { - /// Creates a `Temporal.PlainDate` object from provided fields. - fn date_from_fields( - &self, - fields: &mut TemporalFields, - overflow: ArithmeticOverflow, - ) -> JsResult; - /// Creates a `Temporal.PlainYearMonth` object from the provided fields. - fn year_month_from_fields( - &self, - fields: &mut TemporalFields, - overflow: ArithmeticOverflow, - ) -> JsResult; - /// Creates a `Temporal.PlainMonthDay` object from the provided fields. - fn month_day_from_fields( - &self, - fields: &mut TemporalFields, - overflow: ArithmeticOverflow, - ) -> JsResult; - /// Returns a `Temporal.PlainDate` based off an added date. - fn date_add( - &self, - date: &PlainDate, - duration: &DurationRecord, - overflow: ArithmeticOverflow, - ) -> JsResult; - /// Returns a `Temporal.Duration` representing the duration between two dates. - fn date_until( - &self, - one: &PlainDate, - two: &PlainDate, - largest_unit: TemporalUnit, - ) -> JsResult; - /// Returns the era for a given `temporaldatelike`. - fn era(&self, date_like: &IsoDateRecord) -> JsResult>; - /// Returns the era year for a given `temporaldatelike` - fn era_year(&self, date_like: &IsoDateRecord) -> JsResult>; - /// Returns the `year` for a given `temporaldatelike` - fn year(&self, date_like: &IsoDateRecord) -> JsResult; - /// Returns the `month` for a given `temporaldatelike` - 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) -> JsResult; - /// Returns the `day` for a given `temporaldatelike` - 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) -> JsResult; - /// Returns a value representing the day of the year for a given calendar. - 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) -> JsResult; - /// Returns the year of a given week. - 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) -> JsResult; - /// Returns the days in a month for a given calendar. - 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) -> JsResult; - /// Returns the months in a year for a given calendar. - 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) -> JsResult; - /// Resolve the `TemporalFields` for the implemented Calendar - 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: 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; - /// Debug name - fn debug_name(&self) -> &str; -} - -impl core::fmt::Debug for dyn BuiltinCalendar { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{}", self.debug_name()) - } -} - -// ==== Calendar Abstractions ==== - -const ISO: &[u16] = utf16!("iso8601"); - -// NOTE: potentially move these to `Realm`, so that there can be -// host defined calendars. -// Returns a map of all available calendars. -fn available_calendars() -> FxHashMap<&'static [u16], Box> { - let mut map = FxHashMap::default(); - let iso: Box = Box::new(IsoCalendar); - map.insert(ISO, iso); - - map -} - -// Returns if an identifier is a builtin calendar. -pub(crate) fn is_builtin_calendar(identifier: &JsString) -> bool { - let calendars = available_calendars(); - // TODO: Potentially implement `to_ascii_lowercase`. - calendars.contains_key(identifier.as_slice()) -} - /// The `Temporal.Calendar` object. #[derive(Debug)] pub struct Calendar { - identifier: JsString, + slot: CalendarSlot, +} + +impl Calendar { + pub(crate) fn new(slot: CalendarSlot) -> Self { + Self { slot } + } } impl BuiltInObject for Calendar { @@ -186,6 +68,7 @@ impl IntrinsicObject for Calendar { Attribute::CONFIGURABLE, ) .accessor(utf16!("id"), Some(get_id), None, Attribute::default()) + .static_method(Self::from, js_string!("from"), 1) .method(Self::date_from_fields, js_string!("dateFromFields"), 2) .method( Self::year_month_from_fields, @@ -257,19 +140,25 @@ impl BuiltInConstructor for Calendar { }; // 3. If IsBuiltinCalendar(id) is false, then - if !is_builtin_calendar(id) { - // a. Throw a RangeError exception. - return Err(JsNativeError::range() - .with_message("Calendar ID must be a valid builtin calendar.") - .into()); - } + // a. Throw a RangeError exception. + let _ = AvailableCalendars::from_str(&id.to_std_string_escaped())?; // 4. Return ? CreateTemporalCalendar(id, NewTarget). - create_temporal_calendar(id, Some(new_target.clone()), context) + create_temporal_calendar( + CalendarSlot::Identifier(id.to_std_string_escaped()), + Some(new_target.clone()), + context, + ) } } impl Calendar { + fn from(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + let calendar_like = args.get_or_undefined(0); + let slot = to_temporal_calendar_slot_value(calendar_like, context)?; + create_temporal_calendar(slot, None, context) + } + fn get_id(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { let o = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("this value of Calendar must be an object.") @@ -280,7 +169,12 @@ impl Calendar { .with_message("the this value of Calendar must be a Calendar object.") })?; - Ok(calendar.identifier.clone().into()) + let protocol = match &calendar.slot { + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Protocol(proto) => proto.clone(), + }; + + Ok(JsString::from(protocol.identifier()?.as_str()).into()) } /// 15.8.2.1 `Temporal.Calendar.prototype.dateFromFields ( fields [ , options ] )` - Supercedes 12.5.4 @@ -300,10 +194,10 @@ impl Calendar { })?; // Retrieve the current CalendarProtocol. - let available_calendars = available_calendars(); - let this_calendar = available_calendars - .get(calendar.identifier.as_slice()) - .expect("builtin must exist"); + let protocol = match &calendar.slot { + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Protocol(proto) => proto.clone(), + }; // 3. If Type(fields) is not Object, throw a TypeError exception. let fields = args.get_or_undefined(0); @@ -323,11 +217,11 @@ impl Calendar { ]); // 6. If calendar.[[Identifier]] is "iso8601", then - let mut fields = if calendar.identifier.as_slice() == ISO { + let mut fields = if protocol.identifier()?.as_str() == "iso8601" { // a. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « "year", "day" »). let mut required_fields = Vec::from([js_string!("year"), js_string!("day")]); - temporal::TemporalFields::from_js_object( - fields_obj, + fields::prepare_temporal_fields( + &fields_obj, &mut relevant_field_names, &mut required_fields, None, @@ -338,10 +232,10 @@ impl Calendar { // 7. Else, } else { // a. Let calendarRelevantFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], date). - let calendar_relevant_fields = this_calendar.field_descriptors(FieldsType::Date); + let calendar_relevant_fields = protocol.field_descriptors(CalendarFieldsType::Date); // b. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « », calendarRelevantFieldDescriptors). - temporal::TemporalFields::from_js_object( - fields_obj, + fields::prepare_temporal_fields( + &fields_obj, &mut relevant_field_names, &mut Vec::new(), Some(calendar_relevant_fields), @@ -363,10 +257,9 @@ impl Calendar { // a. Perform ? CalendarResolveFields(calendar.[[Identifier]], fields, date). // b. Let result be ? CalendarDateToISO(calendar.[[Identifier]], fields, overflow). - let result = this_calendar.date_from_fields(&mut fields, overflow)?; + let result = protocol.date_from_fields(&mut fields, overflow)?; - create_temporal_date(result, calendar.identifier.clone().into(), None, context) - .map(Into::into) + create_temporal_date(result, None, context).map(Into::into) } /// 15.8.2.2 `Temporal.Calendar.prototype.yearMonthFromFields ( fields [ , options ] )` - Supercedes 12.5.5 @@ -383,11 +276,11 @@ impl Calendar { .with_message("the this value of Calendar must be a Calendar object.") })?; - let available_calendars = available_calendars(); + let protocol = match &calendar.slot { + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Protocol(proto) => proto.clone(), + }; - let this_calendar = available_calendars - .get(calendar.identifier.as_slice()) - .expect("builtin must exist"); let fields = args.get_or_undefined(0); let fields_obj = fields.as_object().ok_or_else(|| { JsNativeError::typ().with_message("fields parameter must be an object.") @@ -403,11 +296,11 @@ impl Calendar { ]); // 6. Set fields to ? PrepareTemporalFields(fields, « "month", "monthCode", "year" », « "year" »). - let mut fields = if calendar.identifier.as_slice() == ISO { + let mut fields = if protocol.identifier()?.as_str() == "iso8601" { // a. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « "year" »). let mut required_fields = Vec::from([js_string!("year")]); - temporal::TemporalFields::from_js_object( - fields_obj, + fields::prepare_temporal_fields( + &fields_obj, &mut relevant_field_names, &mut required_fields, None, @@ -419,9 +312,10 @@ 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(FieldsType::YearMonth); - temporal::TemporalFields::from_js_object( - fields_obj, + let calendar_relevant_fields = + protocol.field_descriptors(CalendarFieldsType::YearMonth); + fields::prepare_temporal_fields( + &fields_obj, &mut relevant_field_names, &mut Vec::new(), Some(calendar_relevant_fields), @@ -429,6 +323,7 @@ impl Calendar { None, context, )? + // TODO: figure out the below. Maybe a method on fields? // c. Let firstDayIndex be the 1-based index of the first day of the month described by fields (i.e., 1 unless the month's first day is skipped by this calendar.) // d. Perform ! CreateDataPropertyOrThrow(fields, "day", 𝔽(firstDayIndex)). @@ -438,10 +333,9 @@ impl Calendar { let overflow = get_option::(&options, utf16!("overflow"), context)? .unwrap_or(ArithmeticOverflow::Constrain); - let result = this_calendar.year_month_from_fields(&mut fields, overflow)?; + let result = protocol.year_month_from_fields(&mut fields, overflow)?; - create_temporal_year_month(result, calendar.identifier.clone().into(), None, context) - .map(Into::into) + create_temporal_year_month(result, None, context) } /// 15.8.2.3 `Temporal.Calendar.prototype.monthDayFromFields ( fields [ , options ] )` - Supercedes 12.5.6 @@ -460,11 +354,10 @@ impl Calendar { .with_message("the this value of Calendar must be a Calendar object.") })?; - let available_calendars = available_calendars(); - - let this_calendar = available_calendars - .get(calendar.identifier.as_slice()) - .expect("builtin must exist"); + let protocol = match &calendar.slot { + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Protocol(proto) => proto.clone(), + }; // 3. If Type(fields) is not Object, throw a TypeError exception. let fields = args.get_or_undefined(0); @@ -484,11 +377,11 @@ impl Calendar { ]); // 6. If calendar.[[Identifier]] is "iso8601", then - let mut fields = if calendar.identifier.as_slice() == ISO { + let mut fields = if protocol.identifier()?.as_str() == "iso8601" { // a. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « "day" »). let mut required_fields = Vec::from([js_string!("day")]); - temporal::TemporalFields::from_js_object( - fields_obj, + fields::prepare_temporal_fields( + &fields_obj, &mut relevant_field_names, &mut required_fields, None, @@ -499,10 +392,10 @@ impl Calendar { // 7. Else, } else { // a. Let calendarRelevantFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], month-day). - let calendar_relevant_fields = this_calendar.field_descriptors(FieldsType::MonthDay); + let calendar_relevant_fields = protocol.field_descriptors(CalendarFieldsType::MonthDay); // b. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « », calendarRelevantFieldDescriptors). - temporal::TemporalFields::from_js_object( - fields_obj, + fields::prepare_temporal_fields( + &fields_obj, &mut relevant_field_names, &mut Vec::new(), Some(calendar_relevant_fields), @@ -516,10 +409,9 @@ impl Calendar { let overflow = get_option(&options, utf16!("overflow"), context)? .unwrap_or(ArithmeticOverflow::Constrain); - let result = this_calendar.month_day_from_fields(&mut fields, overflow)?; + let result = protocol.month_day_from_fields(&mut fields, overflow)?; - create_temporal_month_day(result, calendar.identifier.clone().into(), None, context) - .map(Into::into) + create_temporal_month_day(result, None, context) } /// 15.8.2.4 `Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] )` - supercedes 12.5.7 @@ -535,11 +427,10 @@ impl Calendar { .with_message("the this value of Calendar must be a Calendar object.") })?; - let available_calendars = available_calendars(); - - let this_calendar = available_calendars - .get(calendar.identifier.as_slice()) - .expect("builtin must exist"); + let protocol = match &calendar.slot { + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Protocol(proto) => proto.clone(), + }; // 4. Set date to ? ToTemporalDate(date). let date_like = args.get_or_undefined(0); @@ -547,7 +438,7 @@ impl Calendar { // 5. Set duration to ? ToTemporalDuration(duration). let duration_like = args.get_or_undefined(1); - let mut duration = temporal::duration::to_temporal_duration(duration_like)?; + let duration = temporal::duration::to_temporal_duration(duration_like)?; // 6. Set options to ? GetOptionsObject(options). let options = args.get_or_undefined(2); @@ -558,12 +449,11 @@ impl Calendar { .unwrap_or(ArithmeticOverflow::Constrain); // 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)?; + duration.balance_time_duration(TemporalUnit::Day)?; - let result = this_calendar.date_add(&date, &duration, overflow)?; + let result = protocol.date_add(&date.inner, &duration, overflow)?; - create_temporal_date(result, calendar.identifier.clone().into(), None, context) - .map(Into::into) + create_temporal_date(result, None, context).map(Into::into) } ///15.8.2.5 `Temporal.Calendar.prototype.dateUntil ( one, two [ , options ] )` - Supercedes 12.5.8 @@ -579,11 +469,10 @@ impl Calendar { .with_message("the this value of Calendar must be a Calendar object.") })?; - let available_calendars = available_calendars(); - - let this_calendar = available_calendars - .get(calendar.identifier.as_slice()) - .expect("builtin must exist"); + let protocol = match &calendar.slot { + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Protocol(proto) => proto.clone(), + }; // 4. Set one to ? ToTemporalDate(one). let one = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; @@ -604,7 +493,7 @@ impl Calendar { )? .unwrap_or(TemporalUnit::Day); - let result = this_calendar.date_until(&one, &two, largest_unit)?; + let result = protocol.date_until(&one.inner, &two.inner, largest_unit)?; create_temporal_duration(result, None, context).map(Into::into) } @@ -619,42 +508,18 @@ impl Calendar { .with_message("the this value of Calendar must be a Calendar object.") })?; - let available_calendars = available_calendars(); - - let this_calendar = available_calendars - .get(calendar.identifier.as_slice()) - .expect("builtin must exist"); - - let date_like = args.get_or_undefined(0); - - let date_info = match date_like { - JsValue::Object(o) if o.is_plain_date_time() => { - let obj = o.borrow(); - let date_time = obj.as_plain_date_time().expect("obj must be a DateTime."); - - date_time.inner.iso_date() - } - JsValue::Object(o) if o.is_plain_date() => { - let obj = o.borrow(); - let date = obj.as_plain_date().expect("Must be a Date"); + let protocol = match &calendar.slot { + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Protocol(proto) => proto.clone(), + }; - date.inner - } - JsValue::Object(o) if o.is_plain_year_month() => { - let obj = o.borrow(); - let ym = obj.as_plain_year_month().expect("must be a YearMonth."); + let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; - ym.inner - } - _ => { - let date = temporal::plain_date::to_temporal_date(date_like, None, context)?; - date.inner - } - }; + let result = protocol + .era(&date_like)? + .map_or(JsValue::undefined(), |r| JsString::from(r.as_str()).into()); - this_calendar - .era(&date_info) - .map(|r| r.map_or(JsValue::undefined(), Into::into)) + Ok(result) } /// 15.8.2.7 `Temporal.Calendar.prototype.eraYear ( temporalDateLike )` @@ -667,42 +532,18 @@ impl Calendar { .with_message("the this value of Calendar must be a Calendar object.") })?; - let available_calendars = available_calendars(); - - let this_calendar = available_calendars - .get(calendar.identifier.as_slice()) - .expect("builtin must exist"); - - let date_like = args.get_or_undefined(0); - - let date_info = match date_like { - JsValue::Object(o) if o.is_plain_date_time() => { - let obj = o.borrow(); - let date_time = obj.as_plain_date_time().expect("obj must be a DateTime."); - - date_time.inner.iso_date() - } - JsValue::Object(o) if o.is_plain_date() => { - let obj = o.borrow(); - let date = obj.as_plain_date().expect("Must be a Date"); + let protocol = match &calendar.slot { + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Protocol(proto) => proto.clone(), + }; - date.inner - } - JsValue::Object(o) if o.is_plain_year_month() => { - let obj = o.borrow(); - let ym = obj.as_plain_year_month().expect("must be a YearMonth."); + let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; - ym.inner - } - _ => { - let date = temporal::plain_date::to_temporal_date(date_like, None, context)?; - date.inner - } - }; + let result = protocol + .era_year(&date_like)? + .map_or(JsValue::undefined(), JsValue::from); - this_calendar - .era_year(&date_info) - .map(|r| r.map_or(JsValue::undefined(), JsValue::from)) + Ok(result) } /// 15.8.2.8 `Temporal.Calendar.prototype.year ( temporalDateLike )` @@ -715,40 +556,16 @@ impl Calendar { .with_message("the this value of Calendar must be a Calendar object.") })?; - let available_calendars = available_calendars(); - - let this_calendar = available_calendars - .get(calendar.identifier.as_slice()) - .expect("builtin must exist"); - - let date_like = args.get_or_undefined(0); - - let date_record = match date_like { - JsValue::Object(o) if o.is_plain_date_time() => { - let obj = o.borrow(); - let date_time = obj.as_plain_date_time().expect("obj must be a DateTime."); - - date_time.inner.iso_date() - } - JsValue::Object(o) if o.is_plain_date() => { - let obj = o.borrow(); - let date = obj.as_plain_date().expect("Must be a Date"); + let protocol = match &calendar.slot { + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Protocol(proto) => proto.clone(), + }; - date.inner - } - JsValue::Object(o) if o.is_plain_year_month() => { - let obj = o.borrow(); - let ym = obj.as_plain_year_month().expect("must be a YearMonth."); + let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; - ym.inner - } - _ => { - let date = temporal::plain_date::to_temporal_date(date_like, None, context)?; - date.inner - } - }; + let result = protocol.year(&date_like)?; - this_calendar.year(&date_record).map(Into::into) + Ok(result.into()) } /// 15.8.2.9 `Temporal.Calendar.prototype.month ( temporalDateLike )` @@ -761,49 +578,21 @@ impl Calendar { .with_message("the this value of Calendar must be a Calendar object.") })?; - let available_calendars = available_calendars(); - - let this_calendar = available_calendars - .get(calendar.identifier.as_slice()) - .expect("builtin must exist"); + let protocol = match &calendar.slot { + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Protocol(proto) => proto.clone(), + }; - let date_like = args.get_or_undefined(0); + let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; // 3. If Type(temporalDateLike) is Object and temporalDateLike has an [[InitializedTemporalMonthDay]] internal slot, then // 3.a. Throw a TypeError exception. // 4. If Type(temporalDateLike) is not Object or temporalDateLike does not have an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], or [[InitializedTemporalYearMonth]] internal slot, then // 4.a. Set temporalDateLike to ? ToTemporalDate(temporalDateLike). - let date_record = match date_like { - JsValue::Object(o) if o.is_plain_date_time() => { - let obj = o.borrow(); - let date_time = obj.as_plain_date_time().expect("obj must be a DateTime."); - - date_time.inner.iso_date() - } - JsValue::Object(o) if o.is_plain_date() => { - let obj = o.borrow(); - let date = obj.as_plain_date().expect("Must be a Date"); - - date.inner - } - JsValue::Object(o) if o.is_plain_year_month() => { - let obj = o.borrow(); - let ym = obj.as_plain_year_month().expect("must be a YearMonth."); - ym.inner - } - JsValue::Object(o) if o.is_plain_month_day() => { - return Err(JsNativeError::typ() - .with_message("month cannot be called with PlainMonthDay object.") - .into()) - } - _ => { - let date = temporal::plain_date::to_temporal_date(date_like, None, context)?; - date.inner - } - }; + let result = protocol.month(&date_like)?; - this_calendar.month(&date_record).map(Into::into) + Ok(result.into()) } /// 15.8.2.10 `Temporal.Calendar.prototype.monthCode ( temporalDateLike )` @@ -816,46 +605,16 @@ impl Calendar { .with_message("the this value of Calendar must be a Calendar object.") })?; - let available_calendars = available_calendars(); - - let this_calendar = available_calendars - .get(calendar.identifier.as_slice()) - .expect("builtin must exist"); - - let date_like = args.get_or_undefined(0); - - let date_record = match date_like { - JsValue::Object(o) if o.is_plain_date_time() => { - let obj = o.borrow(); - let date_time = obj.as_plain_date_time().expect("obj must be a DateTime."); - - date_time.inner.iso_date() - } - JsValue::Object(o) if o.is_plain_date() => { - let obj = o.borrow(); - let date = obj.as_plain_date().expect("Must be a Date"); - - date.inner - } - JsValue::Object(o) if o.is_plain_year_month() => { - let obj = o.borrow(); - let ym = obj.as_plain_year_month().expect("must be a YearMonth."); + let protocol = match &calendar.slot { + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Protocol(proto) => proto.clone(), + }; - ym.inner - } - JsValue::Object(o) if o.is_plain_month_day() => { - let obj = o.borrow(); - let md = obj.as_plain_month_day().expect("must be a MonthDay."); + let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; - md.inner - } - _ => { - let date = temporal::plain_date::to_temporal_date(date_like, None, context)?; - date.inner - } - }; + let result = protocol.month_code(&date_like)?; - this_calendar.month_code(&date_record).map(Into::into) + Ok(JsString::from(result.as_str()).into()) } /// 15.8.2.11 `Temporal.Calendar.prototype.day ( temporalDateLike )` @@ -868,40 +627,16 @@ impl Calendar { .with_message("the this value of Calendar must be a Calendar object.") })?; - let available_calendars = available_calendars(); - - let this_calendar = available_calendars - .get(calendar.identifier.as_slice()) - .expect("builtin must exist"); - - let date_like = args.get_or_undefined(0); - - let date_record = match date_like { - JsValue::Object(o) if o.is_plain_date_time() => { - let obj = o.borrow(); - let date_time = obj.as_plain_date_time().expect("obj must be a DateTime."); - - date_time.inner.iso_date() - } - JsValue::Object(o) if o.is_plain_date() => { - let obj = o.borrow(); - let date = obj.as_plain_date().expect("Must be a Date"); + let protocol = match &calendar.slot { + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Protocol(proto) => proto.clone(), + }; - date.inner - } - JsValue::Object(o) if o.is_plain_month_day() => { - let obj = o.borrow(); - let md = obj.as_plain_month_day().expect("must be a MonthDay."); + let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; - md.inner - } - _ => { - let date = temporal::plain_date::to_temporal_date(date_like, None, context)?; - date.inner - } - }; + let result = protocol.day(&date_like)?; - this_calendar.day(&date_record).map(Into::into) + Ok(result.into()) } /// 15.8.2.12 `Temporal.Calendar.prototype.dayOfWeek ( dateOrDateTime )` @@ -916,18 +651,17 @@ impl Calendar { .with_message("the this value of Calendar must be a Calendar object.") })?; - let available_calendars = available_calendars(); - - let this_calendar = available_calendars - .get(calendar.identifier.as_slice()) - .expect("builtin must exist"); + let protocol = match &calendar.slot { + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Protocol(proto) => proto.clone(), + }; // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike). let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; - let result = this_calendar.day_of_week(&date.inner); + let result = protocol.day_of_week(&CalendarDateLike::Date(date.inner.clone()))?; - result.map(Into::into) + Ok(result.into()) } /// 15.8.2.13 `Temporal.Calendar.prototype.dayOfYear ( temporalDateLike )` @@ -941,18 +675,17 @@ impl Calendar { .with_message("the this value of Calendar must be a Calendar object.") })?; - let available_calendars = available_calendars(); - - let this_calendar = available_calendars - .get(calendar.identifier.as_slice()) - .expect("builtin must exist"); + let protocol = match &calendar.slot { + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Protocol(proto) => proto.clone(), + }; // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike). let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; - let result = this_calendar.day_of_year(&date.inner); + let result = protocol.day_of_year(&CalendarDateLike::Date(date.inner.clone()))?; - result.map(Into::into) + Ok(result.into()) } /// 15.8.2.14 `Temporal.Calendar.prototype.weekOfYear ( temporalDateLike )` @@ -965,18 +698,17 @@ impl Calendar { .with_message("the this value of Calendar must be a Calendar object.") })?; - let available_calendars = available_calendars(); - - let this_calendar = available_calendars - .get(calendar.identifier.as_slice()) - .expect("builtin must exist"); + let protocol = match &calendar.slot { + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Protocol(proto) => proto.clone(), + }; // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike). let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; - let result = this_calendar.week_of_year(&date.inner); + let result = protocol.week_of_year(&CalendarDateLike::Date(date.inner.clone()))?; - result.map(Into::into) + Ok(result.into()) } /// 15.8.2.15 `Temporal.Calendar.prototype.yearOfWeek ( temporalDateLike )` @@ -989,18 +721,17 @@ impl Calendar { .with_message("the this value of Calendar must be a Calendar object.") })?; - let available_calendars = available_calendars(); - - let this_calendar = available_calendars - .get(calendar.identifier.as_slice()) - .expect("builtin must exist"); + let protocol = match &calendar.slot { + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Protocol(proto) => proto.clone(), + }; // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike). let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; - let result = this_calendar.year_of_week(&date.inner); + let result = protocol.year_of_week(&CalendarDateLike::Date(date.inner.clone()))?; - result.map(Into::into) + Ok(result.into()) } /// 15.8.2.16 `Temporal.Calendar.prototype.daysInWeek ( temporalDateLike )` @@ -1013,18 +744,17 @@ impl Calendar { .with_message("the this value of Calendar must be a Calendar object.") })?; - let available_calendars = available_calendars(); - - let this_calendar = available_calendars - .get(calendar.identifier.as_slice()) - .expect("builtin must exist"); + let protocol = match &calendar.slot { + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Protocol(proto) => proto.clone(), + }; // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike). let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; - let result = this_calendar.days_in_week(&date.inner); + let result = protocol.days_in_week(&CalendarDateLike::Date(date.inner.clone()))?; - result.map(Into::into) + Ok(result.into()) } /// 15.8.2.17 `Temporal.Calendar.prototype.daysInMonth ( temporalDateLike )` @@ -1037,42 +767,16 @@ impl Calendar { .with_message("the this value of Calendar must be a Calendar object.") })?; - let available_calendars = available_calendars(); - - let this_calendar = available_calendars - .get(calendar.identifier.as_slice()) - .expect("builtin must exist"); - - let date_like = args.get_or_undefined(0); - - let date_record = match date_like { - JsValue::Object(o) if o.is_plain_date_time() => { - let obj = o.borrow(); - let date_time = obj.as_plain_date_time().expect("obj must be a DateTime."); - - date_time.inner.iso_date() - } - JsValue::Object(o) if o.is_plain_date() => { - let obj = o.borrow(); - let date = obj.as_plain_date().expect("Must be a Date"); - - date.inner - } - JsValue::Object(o) if o.is_plain_year_month() => { - let obj = o.borrow(); - let ym = obj.as_plain_year_month().expect("must be a YearMonth."); - - ym.inner - } - _ => { - let date = temporal::plain_date::to_temporal_date(date_like, None, context)?; - date.inner - } + let protocol = match &calendar.slot { + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Protocol(proto) => proto.clone(), }; - let result = this_calendar.days_in_month(&date_record); + let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; - result.map(Into::into) + let result = protocol.days_in_month(&date_like)?; + + Ok(result.into()) } /// 15.8.2.18 `Temporal.Calendar.prototype.daysInYear ( temporalDateLike )` @@ -1085,42 +789,15 @@ impl Calendar { .with_message("the this value of Calendar must be a Calendar object.") })?; - let available_calendars = available_calendars(); - - let this_calendar = available_calendars - .get(calendar.identifier.as_slice()) - .expect("builtin must exist"); - - let date_like = args.get_or_undefined(0); - - let date_record = match date_like { - JsValue::Object(o) if o.is_plain_date_time() => { - let obj = o.borrow(); - let date_time = obj.as_plain_date_time().expect("obj must be a DateTime."); - - date_time.inner.iso_date() - } - JsValue::Object(o) if o.is_plain_date() => { - let obj = o.borrow(); - let date = obj.as_plain_date().expect("Must be a Date"); - - date.inner - } - JsValue::Object(o) if o.is_plain_year_month() => { - let obj = o.borrow(); - let ym = obj.as_plain_year_month().expect("must be a YearMonth."); - - ym.inner - } - _ => { - let date = temporal::plain_date::to_temporal_date(date_like, None, context)?; - date.inner - } + let protocol = match &calendar.slot { + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Protocol(proto) => proto.clone(), }; - let result = this_calendar.days_in_year(&date_record); + let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; + let result = protocol.days_in_year(&date_like)?; - result.map(Into::into) + Ok(result.into()) } /// 15.8.2.19 `Temporal.Calendar.prototype.monthsInYear ( temporalDateLike )` @@ -1137,42 +814,16 @@ impl Calendar { .with_message("the this value of Calendar must be a Calendar object.") })?; - let available_calendars = available_calendars(); - - let this_calendar = available_calendars - .get(calendar.identifier.as_slice()) - .expect("builtin must exist"); - - let date_like = args.get_or_undefined(0); - - let date_record = match date_like { - JsValue::Object(o) if o.is_plain_date_time() => { - let obj = o.borrow(); - let date_time = obj.as_plain_date_time().expect("obj must be a DateTime."); - - date_time.inner.iso_date() - } - JsValue::Object(o) if o.is_plain_date() => { - let obj = o.borrow(); - let date = obj.as_plain_date().expect("Must be a Date"); - - date.inner - } - JsValue::Object(o) if o.is_plain_year_month() => { - let obj = o.borrow(); - let ym = obj.as_plain_year_month().expect("must be a YearMonth."); - - ym.inner - } - _ => { - let date = temporal::plain_date::to_temporal_date(date_like, None, context)?; - date.inner - } + let protocol = match &calendar.slot { + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Protocol(proto) => proto.clone(), }; - let result = this_calendar.months_in_year(&date_record); + let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; + + let result = protocol.months_in_year(&date_like)?; - result.map(Into::into) + Ok(result.into()) } /// 15.8.2.20 `Temporal.Calendar.prototype.inLeapYear ( temporalDateLike )` @@ -1185,42 +836,16 @@ impl Calendar { .with_message("the this value of Calendar must be a Calendar object.") })?; - let available_calendars = available_calendars(); - - let this_calendar = available_calendars - .get(calendar.identifier.as_slice()) - .expect("builtin must exist"); - - let date_like = args.get_or_undefined(0); - - let date_record = match date_like { - JsValue::Object(o) if o.is_plain_date_time() => { - let obj = o.borrow(); - let date_time = obj.as_plain_date_time().expect("obj must be a DateTime."); - - date_time.inner.iso_date() - } - JsValue::Object(o) if o.is_plain_date() => { - let obj = o.borrow(); - let date = obj.as_plain_date().expect("Must be a Date"); - - date.inner - } - JsValue::Object(o) if o.is_plain_year_month() => { - let obj = o.borrow(); - let ym = obj.as_plain_year_month().expect("must be a YearMonth."); - - ym.inner - } - _ => { - let date = temporal::plain_date::to_temporal_date(date_like, None, context)?; - date.inner - } + let protocol = match &calendar.slot { + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Protocol(proto) => proto.clone(), }; - let result = this_calendar.in_leap_year(&date_record); + let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; + + let result = protocol.in_leap_year(&date_like)?; - result.map(Into::into) + Ok(result.into()) } /// 15.8.2.21 `Temporal.Calendar.prototype.fields ( fields )` @@ -1235,11 +860,10 @@ impl Calendar { .with_message("the this value of Calendar must be a Calendar object.") })?; - let available_calendars = available_calendars(); - - let this_calendar = available_calendars - .get(calendar.identifier.as_slice()) - .expect("builtin must exist"); + let protocol = match &calendar.slot { + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Protocol(proto) => proto.clone(), + }; // 3. Let iteratorRecord be ? GetIterator(fields, sync). let mut iterator_record = @@ -1266,9 +890,12 @@ 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. - match value.to_std_string_escaped().as_str() { - "year" | "month" | "monthCode" | "day" if !fields_names.contains(&value) => { - fields_names.push(value); + let this_field = value.to_std_string_escaped(); + match this_field.as_str() { + "year" | "month" | "monthCode" | "day" + if !fields_names.contains(&this_field) => + { + fields_names.push(this_field); } _ => { let completion = Err(JsNativeError::range() @@ -1289,11 +916,11 @@ impl Calendar { // 7. Let result be fieldNames. // 8. If calendar.[[Identifier]] is not "iso8601", then - if calendar.identifier.as_slice() != ISO { + if protocol.identifier()?.as_str() != "iso8601" { // 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(FieldsType::from(&fields_names[..])); + protocol.field_descriptors(CalendarFieldsType::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. @@ -1302,10 +929,13 @@ impl Calendar { } // 9. Return CreateArrayFromList(result). - Ok( - Array::create_array_from_list(fields_names.iter().map(|s| s.clone().into()), context) - .into(), + Ok(Array::create_array_from_list( + fields_names + .iter() + .map(|s| JsString::from(s.clone()).into()), + context, ) + .into()) } /// 15.8.2.22 `Temporal.Calendar.prototype.mergeFields ( fields, additionalFields )` @@ -1320,11 +950,10 @@ impl Calendar { .with_message("the this value of Calendar must be a Calendar object.") })?; - let available_calendars = available_calendars(); - - let this_calendar = available_calendars - .get(calendar.identifier.as_slice()) - .expect("builtin must exist"); + let protocol = match &calendar.slot { + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Protocol(proto) => proto.clone(), + }; let fields = args.get_or_undefined(0).to_object(context)?; let additional_fields = args.get_or_undefined(1).to_object(context)?; @@ -1350,14 +979,14 @@ impl Calendar { let add_keys = additional_fields_copy .__own_property_keys__(context)? .iter() - .map(|k| JsString::from(k.to_string())) + .map(|k| k.to_string()) .collect::>(); // 7. If calendar.[[Identifier]] is "iso8601", then // a. Let overriddenKeys be ISOFieldKeysToIgnore(additionalKeys). // 8. Else, // a. Let overriddenKeys be CalendarFieldKeysToIgnore(calendar, additionalKeys). - let overridden_keys = this_calendar.field_keys_to_ignore(add_keys); + let overridden_keys = protocol.field_keys_to_ignore(add_keys); // 9. Let merged be OrdinaryObjectCreate(null). let merged = JsObject::with_null_proto(); @@ -1376,7 +1005,7 @@ impl Calendar { for key in field_keys { // a. Let propValue be undefined. // b. If overriddenKeys contains key, then - let prop_value = if overridden_keys.contains(&key) { + let prop_value = if overridden_keys.contains(&key.to_std_string_escaped()) { // i. Set propValue to ! Get(additionalFieldsCopy, key). additional_fields_copy.get(key.as_slice(), context)? // c. Else, @@ -1409,16 +1038,11 @@ impl Calendar { /// 12.2.1 `CreateTemporalCalendar ( identifier [ , newTarget ] )` pub(crate) fn create_temporal_calendar( - identifier: &JsString, + identifier: CalendarSlot, new_target: Option, context: &mut Context, ) -> JsResult { // 1. Assert: IsBuiltinCalendar(identifier) is true. - assert!(is_builtin_calendar(identifier)); - - let calendar = Calendar { - identifier: identifier.clone(), - }; // 2. If newTarget is not provided, set newTarget to %Temporal.Calendar%. let new_target = new_target.unwrap_or_else(|| { context @@ -1434,7 +1058,7 @@ pub(crate) fn create_temporal_calendar( let proto = get_prototype_from_constructor(&new_target, StandardConstructors::calendar, context)?; - let obj = JsObject::from_proto_and_data(proto, ObjectData::calendar(calendar)); + let obj = JsObject::from_proto_and_data(proto, ObjectData::calendar(Calendar::new(identifier))); // 4. Set object.[[Identifier]] to the ASCII-lowercase of identifier. // 5. Return object. @@ -1446,14 +1070,14 @@ pub(crate) fn create_temporal_calendar( pub(crate) fn get_temporal_calendar_slot_value_with_default( item: &JsObject, context: &mut Context, -) -> JsResult { +) -> JsResult { // 1. If item has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal slot, then // a. Return item.[[Calendar]]. if item.is_plain_date() { let obj = item.borrow(); let date = obj.as_plain_date(); if let Some(date) = date { - let calendar = date.calendar.clone(); + let calendar = date.inner.calendar().clone(); drop(obj); return Ok(calendar); } @@ -1461,7 +1085,7 @@ pub(crate) fn get_temporal_calendar_slot_value_with_default( let obj = item.borrow(); let date_time = obj.as_plain_date_time(); if let Some(dt) = date_time { - let calendar = dt.calendar.clone(); + let calendar = dt.inner.calendar().clone(); drop(obj); return Ok(calendar); } @@ -1469,7 +1093,7 @@ pub(crate) fn get_temporal_calendar_slot_value_with_default( let obj = item.borrow(); let year_month = obj.as_plain_year_month(); if let Some(ym) = year_month { - let calendar = ym.calendar.clone(); + let calendar = ym.inner.calendar().clone(); drop(obj); return Ok(calendar); } @@ -1477,7 +1101,7 @@ pub(crate) fn get_temporal_calendar_slot_value_with_default( let obj = item.borrow(); let month_day = obj.as_plain_month_day(); if let Some(md) = month_day { - let calendar = md.calendar.clone(); + let calendar = md.inner.calendar().clone(); drop(obj); return Ok(calendar); } @@ -1491,23 +1115,19 @@ pub(crate) fn get_temporal_calendar_slot_value_with_default( let calendar_like = item.get(utf16!("calendar"), context)?; // 3. Return ? ToTemporalCalendarSlotValue(calendarLike, "iso8601"). - to_temporal_calendar_slot_value(&calendar_like, Some(ISO.into())) + to_temporal_calendar_slot_value(&calendar_like, context) } -#[allow(unused)] -fn to_temporal_calendar_slot_value( +/// `12.2.20 ToTemporalCalendarSlotValue ( temporalCalendarLike [ , default ] )` +pub(crate) fn to_temporal_calendar_slot_value( calendar_like: &JsValue, - default: Option, -) -> JsResult { + context: &mut Context, +) -> JsResult { // 1. If temporalCalendarLike is undefined and default is present, then + // a. Assert: IsBuiltinCalendar(default) is true. + // b. Return default. if calendar_like.is_undefined() { - if let Some(default) = default { - // a. Assert: IsBuiltinCalendar(default) is true. - if is_builtin_calendar(&default) { - // b. Return default. - return Ok(default.into()); - } - } + return Ok(CalendarSlot::Identifier("iso8601".to_owned())); // 2. If Type(temporalCalendarLike) is Object, then } else if let Some(calendar_like) = calendar_like.as_object() { // a. If temporalCalendarLike has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal slot, then @@ -1516,30 +1136,15 @@ fn to_temporal_calendar_slot_value( let obj = calendar_like.borrow(); let date = obj.as_plain_date(); if let Some(date) = date { - let calendar = date.calendar.clone(); + let calendar = date.inner.calendar().clone(); return Ok(calendar); } } else if calendar_like.is_plain_date_time() { - let obj = calendar_like.borrow(); - let date_time = obj.as_plain_date_time(); - if let Some(dt) = date_time { - let calendar = dt.calendar.clone(); - return Ok(calendar); - } + todo!() } else if calendar_like.is_plain_year_month() { - let obj = calendar_like.borrow(); - let year_month = obj.as_plain_year_month(); - if let Some(ym) = year_month { - let calendar = ym.calendar.clone(); - return Ok(calendar); - } + todo!() } else if calendar_like.is_plain_month_day() { - let obj = calendar_like.borrow(); - let month_day = obj.as_plain_month_day(); - if let Some(md) = month_day { - let calendar = md.calendar.clone(); - return Ok(calendar); - } + todo!() } else if calendar_like.is_zoned_date_time() { return Err(JsNativeError::range() .with_message("Not yet implemented.") @@ -1548,8 +1153,16 @@ fn to_temporal_calendar_slot_value( // TODO: implement ObjectImplementsTemporalCalendarProtocol // b. If ? ObjectImplementsTemporalCalendarProtocol(temporalCalendarLike) is false, throw a TypeError exception. + if !object_implements_calendar_protocol(calendar_like, context) { + return Err(JsNativeError::typ() + .with_message("CalendarLike does not implement the CalendarProtocol.") + .into()); + } + + // Types: Box <- UserCalendar + let protocol = Box::new(CustomRuntimeCalendar::new(calendar_like, context)); // c. Return temporalCalendarLike. - return Ok(calendar_like.clone().into()); + return Ok(CalendarSlot::Protocol(protocol)); } // 3. If temporalCalendarLike is not a String, throw a TypeError exception. @@ -1563,7 +1176,42 @@ fn to_temporal_calendar_slot_value( // 4. Let identifier be ? ParseTemporalCalendarString(temporalCalendarLike). // 5. If IsBuiltinCalendar(identifier) is false, throw a RangeError exception. // 6. Return the ASCII-lowercase of identifier. - Ok(js_string!(ISO).into()) + Ok(CalendarSlot::Identifier("iso8601".to_owned())) +} + +fn object_implements_calendar_protocol(calendar_like: &JsObject, context: &mut Context) -> bool { + CALENDAR_PROTOCOL_METHODS.into_iter().all(|method| { + calendar_like + .__has_property__(&JsString::from(method).into(), context) + .unwrap_or(false) + }) +} + +fn to_calendar_date_like(date_like: &JsValue, context: &mut Context) -> JsResult { + match date_like { + JsValue::Object(o) if o.is_plain_date_time() => { + let obj = o.borrow(); + let date_time = obj.as_plain_date_time().expect("obj must be a DateTime."); + + Ok(CalendarDateLike::DateTime(date_time.inner.clone())) + } + JsValue::Object(o) if o.is_plain_date() => { + let obj = o.borrow(); + let date = obj.as_plain_date().expect("Must be a Date"); + + Ok(CalendarDateLike::Date(date.inner.clone())) + } + JsValue::Object(o) if o.is_plain_year_month() => { + let obj = o.borrow(); + let ym = obj.as_plain_year_month().expect("must be a YearMonth."); + + Ok(CalendarDateLike::YearMonth(ym.inner.clone())) + } + _ => { + let date = temporal::plain_date::to_temporal_date(date_like, None, context)?; + Ok(CalendarDateLike::Date(date.inner.clone())) + } + } } // ---------------------------- Native Abstract Calendar Methods ---------------------------- @@ -1578,27 +1226,7 @@ fn to_temporal_calendar_slot_value( // NOTE: Instead of creating temporal calendar it may be more efficient to retrieve // the protocol and call the value directly in rust, something to consider. -/// A helper method to assess a identifier vs Calendar and calling a designated method. -fn call_method_on_abstract_calendar( - calendar: &JsValue, - method: &JsString, - args: &[JsValue], - context: &mut Context, -) -> JsResult { - // If Calendar is a string - let this_calendar = match calendar { - JsValue::String(id) => create_temporal_calendar(id, None, context)? - .as_object() - .expect("CreateTemporalCalendar must return JsObject.") - .clone(), - JsValue::Object(calendar) => calendar.clone(), - _ => unreachable!(), - }; - - let method = this_calendar.get(method.as_ref(), context)?; - method.call(&this_calendar.into(), args, context) -} - +/* /// 12.2.2 `CalendarFields ( calendar, fieldNames )` /// /// `CalendarFields` takes the input fields and adds the `extraFieldDescriptors` for @@ -1659,628 +1287,6 @@ pub(crate) fn calendar_merge_fields( } } -/// 12.2.4 `CalendarDateAdd ( calendar, date, duration [ , options [ , dateAdd ] ] )` -/// -/// Returns either a normal completion containing a `Temporal.PlainDate`, or an abrupt completion. -#[allow(unused)] -pub(crate) fn calendar_date_add( - calendar: &JsValue, - date: &PlainDate, - duration: &DurationRecord, - options: &JsValue, - context: &mut Context, -) -> JsResult { - // NOTE: The specification never calls CalendarDateAdd without an options argument provided. - // 1. If options is not present, set options to 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 »). - // 3. If dateAdd is not present, set dateAdd to ? GetMethod(calendar, "dateAdd"). - // 4. Let addedDate be ? Call(dateAdd, calendar, « date, duration, options »). - let added_date = call_method_on_abstract_calendar( - calendar, - &JsString::from("dateAdd"), - &[ - 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() => { - 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()), - } -} - -/// 12.2.5 `CalendarDateUntil ( calendar, one, two, options [ , dateUntil ] )` -/// -/// Returns either a normal completion containing a `Temporal.Duration`, or an abrupt completion. -#[allow(unused)] -pub(crate) fn calendar_date_until( - calendar: &JsValue, - one: &PlainDate, - two: &PlainDate, - options: &JsValue, - context: &mut Context, -) -> 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 »). - // 2. If dateUntil is not present, set dateUntil to ? GetMethod(calendar, "dateUntil"). - // 3. Let duration be ? Call(dateUntil, calendar, « one, two, options »). - let duration = call_method_on_abstract_calendar( - calendar, - &JsString::from("dateUntil"), - &[ - one.as_object(context)?.into(), - two.as_object(context)?.into(), - options.clone(), - ], - context, - )?; - - // 4. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). - // 5. Return duration. - match duration { - JsValue::Object(o) if o.is_duration() => { - let obj = o.borrow(); - let dur = obj - .as_duration() - .expect("Value is confirmed to be a duration."); - let record = dur.inner; - drop(obj); - Ok(record) - } - _ => Err(JsNativeError::typ() - .with_message("Calendar dateUntil must return a Duration") - .into()), - } -} - -/// 12.2.6 `CalendarYear ( calendar, dateLike )` -/// -/// Returns either a normal completion containing an integer, or an abrupt completion. -#[allow(unused)] -pub(crate) fn calendar_year( - calendar: &JsValue, - datelike: &JsValue, - context: &mut Context, -) -> JsResult { - // 1. If calendar is a String, then - // a. Set calendar to ! CreateTemporalCalendar(calendar). - // b. Return ? Call(%Temporal.Calendar.prototype.year%, calendar, « dateLike »). - // 2. Let result be ? Invoke(calendar, "year", « dateLike »). - let result = call_method_on_abstract_calendar( - calendar, - &JsString::from("year"), - &[datelike.clone()], - context, - )?; - - // 3. If Type(result) is not Number, throw a TypeError exception. - let Some(number) = result.as_number() else { - return Err(JsNativeError::typ() - .with_message("CalendarYear 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("CalendarYear was not integral.") - .into()); - } - - // 5. Return ℝ(result). - Ok(number) -} - -/// 12.2.7 `CalendarMonth ( calendar, dateLike )` -#[allow(unused)] -pub(crate) fn calendar_month( - calendar: &JsValue, - datelike: &JsValue, - context: &mut Context, -) -> JsResult { - // 1. If calendar is a String, then - // a. Set calendar to ! CreateTemporalCalendar(calendar). - // b. Return ? Call(%Temporal.Calendar.prototype.month%, calendar, « dateLike »). - // 2. Let result be ? Invoke(calendar, "month", « dateLike »). - let result = call_method_on_abstract_calendar( - calendar, - &JsString::from("month"), - &[datelike.clone()], - context, - )?; - - // 3. If Type(result) is not Number, throw a TypeError exception. - let Some(number) = result.as_number() else { - return Err(JsNativeError::typ() - .with_message("CalendarYear 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("CalendarMonth was not integral.") - .into()); - } - - // 5. If result < 1𝔽, throw a RangeError exception. - if number < 1.0 { - return Err(JsNativeError::range() - .with_message("month must be 1 or greater.") - .into()); - } - - // 6. Return ℝ(result). - Ok(number) -} - -/// 12.2.8 `CalendarMonthCode ( calendar, dateLike )` -#[allow(unused)] -pub(crate) fn calendar_month_code( - calendar: &JsValue, - datelike: &JsValue, - context: &mut Context, -) -> JsResult { - // 1. If calendar is a String, then - // a. Set calendar to ! CreateTemporalCalendar(calendar). - // b. Return ? Call(%Temporal.Calendar.prototype.monthCode%, calendar, « dateLike »). - // 2. Let result be ? Invoke(calendar, "monthCode", « dateLike »). - let result = call_method_on_abstract_calendar( - calendar, - &JsString::from("monthCode"), - &[datelike.clone()], - context, - )?; - - // 3. If Type(result) is not String, throw a TypeError exception. - // 4. Return result. - match result { - JsValue::String(s) => Ok(s), - _ => Err(JsNativeError::typ() - .with_message("monthCode must be a String.") - .into()), - } -} - -/// 12.2.9 `CalendarDay ( calendar, dateLike )` -#[allow(unused)] -pub(crate) fn calendar_day( - calendar: &JsValue, - datelike: &JsValue, - context: &mut Context, -) -> JsResult { - // 1. If calendar is a String, then - // a. Set calendar to ! CreateTemporalCalendar(calendar). - // b. Return ? Call(%Temporal.Calendar.prototype.day%, calendar, « dateLike »). - // 2. Let result be ? Invoke(calendar, "day", « dateLike »). - let result = call_method_on_abstract_calendar( - calendar, - &JsString::from("day"), - &[datelike.clone()], - context, - )?; - - // 3. If Type(result) is not Number, throw a TypeError exception. - let Some(number) = result.as_number() else { - return Err(JsNativeError::typ() - .with_message("CalendarYear 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("CalendarDay was not integral.") - .into()); - } - - // 5. If result < 1𝔽, throw a RangeError exception. - if number < 1.0 { - return Err(JsNativeError::range() - .with_message("day must be 1 or greater.") - .into()); - } - - // 6. Return ℝ(result). - Ok(number) -} - -/// 12.2.10 `CalendarDayOfWeek ( calendar, dateLike )` -#[allow(unused)] -pub(crate) fn calendar_day_of_week( - calendar: &JsValue, - datelike: &JsValue, - context: &mut Context, -) -> 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( - calendar, - &JsString::from("dayOfWeek"), - &[datelike.clone()], - context, - )?; - - // 3. If Type(result) is not Number, throw a TypeError exception. - let Some(number) = result.as_number() else { - return Err(JsNativeError::typ() - .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("CalendarDayOfWeek was not integral.") - .into()); - } - - // 5. If result < 1𝔽, throw a RangeError exception. - if number < 1.0 { - return Err(JsNativeError::range() - .with_message("dayOfWeek must be 1 or greater.") - .into()); - } - - // 6. Return ℝ(result). - Ok(number) -} - -/// 12.2.11 `CalendarDayOfYear ( calendar, dateLike )` -#[allow(unused)] -pub(crate) fn calendar_day_of_year( - calendar: &JsValue, - datelike: &JsValue, - context: &mut Context, -) -> JsResult { - // 1. If calendar is a String, then - // a. Set calendar to ! CreateTemporalCalendar(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( - calendar, - &JsString::from("dayOfWeek"), - &[datelike.clone()], - context, - )?; - - // 3. If Type(result) is not Number, throw a TypeError exception. - let Some(number) = result.as_number() else { - return Err(JsNativeError::typ() - .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("CalendarDayOfWeek was not integral.") - .into()); - } - - // 5. If result < 1𝔽, throw a RangeError exception. - if number < 1.0 { - return Err(JsNativeError::range() - .with_message("dayOfWeek must be 1 or greater.") - .into()); - } - - // 6. Return ℝ(result). - Ok(number) -} - -/// 12.2.12 `CalendarWeekOfYear ( calendar, dateLike )` -#[allow(unused)] -pub(crate) fn calendar_week_of_year( - calendar: &JsValue, - datelike: &JsValue, - context: &mut Context, -) -> 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 »). - let result = call_method_on_abstract_calendar( - calendar, - &JsString::from("dayOfYear"), - &[datelike.clone()], - context, - )?; - - // 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.") - .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.") - .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.") - .into()); - } - - // 6. Return ℝ(result). - Ok(number) -} - -/// 12.2.13 `CalendarYearOfWeek ( calendar, dateLike )` -#[allow(unused)] -pub(crate) fn calendar_year_of_week( - calendar: &JsValue, - datelike: &JsValue, - context: &mut Context, -) -> JsResult { - // 1. If calendar is a String, then - // a. Set calendar to ! CreateTemporalCalendar(calendar). - // b. Return ? Call(%Temporal.Calendar.prototype.yearOfWeek%, calendar, « dateLike »). - // 2. Let result be ? Invoke(calendar, "yearOfWeek", « dateLike »). - let result = call_method_on_abstract_calendar( - calendar, - &JsString::from("yearOfWeek"), - &[datelike.clone()], - context, - )?; - - // 3. If Type(result) is not Number, throw a TypeError exception. - let Some(number) = result.as_number() else { - return Err(JsNativeError::typ() - .with_message("CalendarYearOfWeek 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("CalendarYearOfWeek was not integral.") - .into()); - } - - // 5. Return ℝ(result). - Ok(number) -} - -/// 12.2.14 `CalendarDaysInWeek ( calendar, dateLike )` -#[allow(unused)] -pub(crate) fn calendar_days_in_week( - calendar: &JsValue, - datelike: &JsValue, - context: &mut Context, -) -> JsResult { - // 1. If calendar is a String, then - // a. Set calendar to ! CreateTemporalCalendar(calendar). - // b. Return ? Call(%Temporal.Calendar.prototype.daysInWeek%, calendar, « dateLike »). - // 2. Let result be ? Invoke(calendar, "daysInWeek", « dateLike »). - let result = call_method_on_abstract_calendar( - calendar, - &JsString::from("daysInWeek"), - &[datelike.clone()], - context, - )?; - - // 3. If Type(result) is not Number, throw a TypeError exception. - let Some(number) = result.as_number() else { - return Err(JsNativeError::typ() - .with_message("CalendarDaysInWeek 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("CalendarDaysInWeek was not integral.") - .into()); - } - - // 5. If result < 1𝔽, throw a RangeError exception. - if number < 1.0 { - return Err(JsNativeError::range() - .with_message("daysInWeek must be 1 or greater.") - .into()); - } - - // 6. Return ℝ(result). - Ok(number) -} - -/// 12.2.15 `CalendarDaysInMonth ( calendar, dateLike )` -#[allow(unused)] -pub(crate) fn calendar_days_in_month( - calendar: &JsValue, - datelike: &JsValue, - context: &mut Context, -) -> JsResult { - // 1. If calendar is a String, then - // a. Set calendar to ! CreateTemporalCalendar(calendar). - // b. Return ? Call(%Temporal.Calendar.prototype.daysInMonth%, calendar, « dateLike »). - // 2. Let result be ? Invoke(calendar, "daysInMonth", « dateLike »). - let result = call_method_on_abstract_calendar( - calendar, - &JsString::from("daysInMonth"), - &[datelike.clone()], - context, - )?; - - // 3. If Type(result) is not Number, throw a TypeError exception. - let Some(number) = result.as_number() else { - return Err(JsNativeError::typ() - .with_message("CalendarDaysInMonth 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("CalendarDaysInMonth was not integral.") - .into()); - } - - // 5. If result < 1𝔽, throw a RangeError exception. - if number < 1.0 { - return Err(JsNativeError::range() - .with_message("daysInMonth must be 1 or greater.") - .into()); - } - - // 6. Return ℝ(result). - Ok(number) -} - -/// 12.2.16 `CalendarDaysInYear ( calendar, dateLike )` -#[allow(unused)] -pub(crate) fn calendar_days_in_year( - calendar: &JsValue, - datelike: &JsValue, - context: &mut Context, -) -> JsResult { - // 1. If calendar is a String, then - // a. Set calendar to ! CreateTemporalCalendar(calendar). - // b. Return ? Call(%Temporal.Calendar.prototype.daysInYear%, calendar, « dateLike »). - // 2. Let result be ? Invoke(calendar, "daysInYear", « dateLike »). - let result = call_method_on_abstract_calendar( - calendar, - &JsString::from("daysInYear"), - &[datelike.clone()], - context, - )?; - - // 3. If Type(result) is not Number, throw a TypeError exception. - let Some(number) = result.as_number() else { - return Err(JsNativeError::typ() - .with_message("CalendarDaysInYear 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("CalendarDaysInYear was not integral.") - .into()); - } - - // 5. If result < 1𝔽, throw a RangeError exception. - if number < 1.0 { - return Err(JsNativeError::range() - .with_message("daysInYear must be 1 or greater.") - .into()); - } - - // 6. Return ℝ(result). - Ok(number) -} - -/// 12.2.17 `CalendarMonthsInYear ( calendar, dateLike )` -#[allow(unused)] -pub(crate) fn calendar_months_in_year( - calendar: &JsValue, - datelike: &JsValue, - context: &mut Context, -) -> JsResult { - // 1. If calendar is a String, then - // a. Set calendar to ! CreateTemporalCalendar(calendar). - // b. Return ? Call(%Temporal.Calendar.prototype.monthsInYear%, calendar, « dateLike »). - // 2. Let result be ? Invoke(calendar, "monthsInYear", « dateLike »). - let result = call_method_on_abstract_calendar( - calendar, - &JsString::from("monthsInYear"), - &[datelike.clone()], - context, - )?; - - // 3. If Type(result) is not Number, throw a TypeError exception. - let Some(number) = result.as_number() else { - return Err(JsNativeError::typ() - .with_message("CalendarMonthsInYear 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("CalendarMonthsInYear was not integral.") - .into()); - } - - // 5. If result < 1𝔽, throw a RangeError exception. - if number < 1.0 { - return Err(JsNativeError::range() - .with_message("monthsInYear must be 1 or greater.") - .into()); - } - - // 6. Return ℝ(result). - Ok(number) -} - -/// 12.2.18 `CalendarInLeapYear ( calendar, dateLike )` -#[allow(unused)] -pub(crate) fn calendar_in_lear_year( - calendar: &JsValue, - datelike: &JsValue, - context: &mut Context, -) -> JsResult { - // 1. If calendar is a String, then - // a. Set calendar to ! CreateTemporalCalendar(calendar). - // b. Return ? Call(%Temporal.Calendar.prototype.inLeapYear%, calendar, « dateLike »). - // 2. Let result be ? Invoke(calendar, "inLeapYear", « dateLike »). - let result = call_method_on_abstract_calendar( - calendar, - &JsString::from("inLeapYear"), - &[datelike.clone()], - context, - )?; - - // 3. If Type(result) is not Boolean, throw a TypeError exception. - // 4. Return result. - match result { - JsValue::Boolean(b) => Ok(b), - _ => Err(JsNativeError::typ() - .with_message("inLeapYear result must be a boolean.") - .into()), - } -} - /// 12.2.24 `CalendarDateFromFields ( calendar, fields [ , options [ , dateFromFields ] ] )` #[allow(unused)] pub(crate) fn calendar_date_from_fields( @@ -2306,3 +1312,4 @@ pub(crate) fn calendar_date_from_fields( .with_message("not yet implemented.") .into()) } +*/ diff --git a/boa_engine/src/builtins/temporal/calendar/object.rs b/boa_engine/src/builtins/temporal/calendar/object.rs new file mode 100644 index 00000000000..90368afa06d --- /dev/null +++ b/boa_engine/src/builtins/temporal/calendar/object.rs @@ -0,0 +1,299 @@ +//! Boa's implementation of a user-defined Anonymous Calendar. + +use crate::{ + builtins::temporal::{plain_date, plain_date_time, plain_month_day, plain_year_month}, + object::ObjectKind, + property::PropertyKey, + Context, JsObject, JsValue, +}; + +use boa_macros::utf16; +use boa_temporal::{ + calendar::{CalendarDateLike, CalendarProtocol}, + date::TemporalDate, + duration::Duration, + error::TemporalError, + fields::TemporalFields, + month_day::TemporalMonthDay, + options::ArithmeticOverflow, + year_month::TemporalYearMonth, + TemporalResult, TinyStr4, TinyStr8, +}; + +/// A user-defined, custom calendar that is only known at runtime +/// and executed at runtime. +/// +/// A user-defined calendar implements all methods of the CalendarProtocol, +/// and therefore satisfies the requirements to be used as a calendar. +#[derive(Debug, Clone)] +pub(crate) struct CustomRuntimeCalendar { + calendar: JsObject, + ctx: *mut Context, +} + +impl CustomRuntimeCalendar { + pub(crate) fn new(calendar: &JsObject, context: &mut Context) -> Self { + Self { + calendar: calendar.clone(), + ctx: context, + } + } +} + +impl CalendarProtocol for CustomRuntimeCalendar { + fn date_from_fields( + &self, + fields: &mut TemporalFields, + overflow: ArithmeticOverflow, + ) -> TemporalResult { + // Safety: Context lives for the life of the program and execution, so + // this should, in theory, be valid. + let context = unsafe { &mut *self.ctx }; + + let method = self + .calendar + .get(utf16!("dateFromFields"), context) + .expect("method must exist on a object that implements the CalendarProtocol."); + + let fields = JsObject::from_temporal_fields(&fields, context) + .map_err(|_| TemporalError::range().with_message("Need new"))?; + + let value = method + .call(&self.calendar.clone().into(), &[fields.into()], context) + .map_err(|e| { + TemporalError::range().with_message("Update error to handle error conversions") + })?; + + let obj = value + .as_object() + .map(JsObject::borrow) + .ok_or_else(|| TemporalError::r#type().with_message("could not borrow object"))?; + + let ObjectKind::PlainDate(pd) = obj.kind() else { + return Err(TemporalError::r#type().with_message("Object returned was not a PlainDate")); + }; + + Ok(pd.inner.clone()) + } + + fn year_month_from_fields( + &self, + fields: &mut TemporalFields, + overflow: ArithmeticOverflow, + ) -> TemporalResult { + todo!() + } + + fn month_day_from_fields( + &self, + fields: &mut TemporalFields, + overflow: ArithmeticOverflow, + ) -> TemporalResult { + todo!() + } + + fn date_add( + &self, + date: &TemporalDate, + duration: &Duration, + overflow: ArithmeticOverflow, + ) -> TemporalResult { + todo!() + } + + fn date_until( + &self, + one: &TemporalDate, + two: &TemporalDate, + largest_unit: boa_temporal::options::TemporalUnit, + ) -> TemporalResult { + todo!() + } + + // TODO: Determine validity of below errors. + fn era(&self, date_like: &CalendarDateLike) -> TemporalResult> { + Err(TemporalError::range().with_message("Objects do not implement era")) + } + + fn era_year(&self, date_like: &CalendarDateLike) -> TemporalResult> { + Err(TemporalError::range().with_message("Objects do not implement eraYear.")) + } + + fn year(&self, date_like: &CalendarDateLike) -> TemporalResult { + todo!() + } + + fn month(&self, date_like: &CalendarDateLike) -> TemporalResult { + todo!() + } + + fn month_code(&self, date_like: &CalendarDateLike) -> TemporalResult { + todo!() + } + + fn day(&self, date_like: &CalendarDateLike) -> TemporalResult { + todo!() + } + + fn day_of_week(&self, date_like: &CalendarDateLike) -> TemporalResult { + todo!() + } + + fn day_of_year(&self, date_like: &CalendarDateLike) -> TemporalResult { + todo!() + } + + fn week_of_year(&self, date_like: &CalendarDateLike) -> TemporalResult { + todo!() + } + + fn year_of_week(&self, date_like: &CalendarDateLike) -> TemporalResult { + todo!() + } + + fn days_in_week(&self, date_like: &CalendarDateLike) -> TemporalResult { + // Safety: Context lives for the lifetime of the program's execution, so + // this should, in theory, be safe memory to access. + let context = unsafe { &mut *self.ctx }; + + let date_like = date_like_to_object(date_like, context)?; + + let method = self + .calendar + .get(PropertyKey::from(utf16!("daysInWeek")), context) + .expect("method must exist on a object that implements the CalendarProtocol."); + + let val = method + .as_callable() + .expect("is method") + .call(&method, &[date_like], context) + .map_err(|err| TemporalError::general(err.to_string()))?; + + let JsValue::Integer(integral) = val else { + return Err(TemporalError::range().with_message("Invalid CustomCalendarReturn")); + }; + + Ok(integral) + } + + fn days_in_month(&self, date_like: &CalendarDateLike) -> TemporalResult { + // Safety: Context lives for the lifetime of the program's execution, so + // this should, in theory, be safe memory to access. + let context = unsafe { &mut *self.ctx }; + + let date_like = date_like_to_object(date_like, context)?; + + let method = self + .calendar + .get(PropertyKey::from(utf16!("daysInMonth")), context) + .expect("method must exist on a object that implements the CalendarProtocol."); + let val = method + .as_callable() + .expect("is method") + .call(&method, &[date_like], context) + .map_err(|err| TemporalError::general(err.to_string()))?; + + let JsValue::Integer(integral) = val else { + return Err(TemporalError::range().with_message("Invalid CustomCalendarReturn")); + }; + + Ok(integral) + } + + fn days_in_year(&self, date_like: &CalendarDateLike) -> TemporalResult { + // Safety: Context lives for the lifetime of the program's execution, so + // this should, in theory, be safe memory to access. + let context = unsafe { &mut *self.ctx }; + + let date_like = date_like_to_object(date_like, context)?; + + let method = self + .calendar + .get(PropertyKey::from(utf16!("daysInYear")), context) + .expect("method must exist on a object that implements the CalendarProtocol."); + + let val = method + .as_callable() + .expect("is method") + .call(&method, &[date_like], context) + .map_err(|err| TemporalError::general(err.to_string()))?; + + let JsValue::Integer(integral) = val else { + return Err(TemporalError::range().with_message("Invalid CustomCalendarReturn")); + }; + + Ok(integral) + } + + fn months_in_year(&self, date_like: &CalendarDateLike) -> TemporalResult { + todo!() + } + + fn in_leap_year(&self, date_like: &CalendarDateLike) -> TemporalResult { + todo!() + } + + // TODO: Determine fate of fn fields() + + fn field_descriptors( + &self, + r#type: boa_temporal::calendar::CalendarFieldsType, + ) -> Vec<(String, bool)> { + Vec::default() + } + + fn field_keys_to_ignore(&self, additional_keys: Vec) -> Vec { + Vec::default() + } + + fn resolve_fields( + &self, + fields: &mut TemporalFields, + r#type: boa_temporal::calendar::CalendarFieldsType, + ) -> TemporalResult<()> { + todo!() + } + + fn identifier(&self) -> TemporalResult { + // Safety: Context lives for the lifetime of the program's execution, so + // this should, in theory, be safe memory to access. + let context = unsafe { &mut *self.ctx }; + + let identifier = self + .calendar + .__get__( + &PropertyKey::from(utf16!("id")), + JsValue::undefined(), + context, + ) + .expect("method must exist on a object that implements the CalendarProtocol."); + + let JsValue::String(s) = identifier else { + return Err(TemporalError::range().with_message("Identifier was not a string")); + }; + + Ok(s.to_std_string_escaped()) + } +} + +pub(crate) fn date_like_to_object( + date_like: &CalendarDateLike, + context: &mut Context, +) -> TemporalResult { + match date_like { + CalendarDateLike::Date(d) => plain_date::create_temporal_date(d.clone(), None, context) + .map_err(|e| TemporalError::general(e.to_string())) + .map(Into::into), + CalendarDateLike::DateTime(dt) => { + todo!() + } + CalendarDateLike::MonthDay(md) => { + plain_month_day::create_temporal_month_day(md.clone(), None, context) + .map_err(|e| TemporalError::general(e.to_string())) + } + CalendarDateLike::YearMonth(ym) => { + plain_year_month::create_temporal_year_month(ym.clone(), None, context) + .map_err(|e| TemporalError::general(e.to_string())) + } + } +} diff --git a/boa_engine/src/builtins/temporal/calendar/tests.rs b/boa_engine/src/builtins/temporal/calendar/tests.rs index de24f03efc2..07da841ea3e 100644 --- a/boa_engine/src/builtins/temporal/calendar/tests.rs +++ b/boa_engine/src/builtins/temporal/calendar/tests.rs @@ -20,3 +20,42 @@ fn calendar_methods() { TestAction::assert_eq("iso.daysInWeek('2021-11-20')", 7), ]); } + +#[test] +fn run_custom_calendar() { + run_test_actions([ + TestAction::run( + r#"const custom = { + dateAdd() {}, + dateFromFields() {}, + dateUntil() {}, + day() {}, + dayOfWeek() {}, + dayOfYear() {}, + daysInMonth() { return 14 }, + daysInWeek() {return 6}, + daysInYear() {return 360}, + fields() {}, + id: "custom-calendar", + inLeapYear() {}, + mergeFields() {}, + month() {}, + monthCode() {}, + monthDayFromFields() {}, + monthsInYear() {}, + weekOfYear() {}, + year() {}, + yearMonthFromFields() {}, + yearOfWeek() {}, + }; + + let cal = Temporal.Calendar.from(custom); + let date = "1972-05-01"; + "#, + ), + TestAction::assert_eq("cal.id", js_string!("custom-calendar")), + TestAction::assert_eq("cal.daysInMonth(date)", 14), + TestAction::assert_eq("cal.daysInWeek(date)", 6), + TestAction::assert_eq("cal.daysInYear(date)", 360), + ]) +} diff --git a/boa_engine/src/builtins/temporal/calendar/utils.rs b/boa_engine/src/builtins/temporal/calendar/utils.rs deleted file mode 100644 index 2e7b6f0eba0..00000000000 --- a/boa_engine/src/builtins/temporal/calendar/utils.rs +++ /dev/null @@ -1,107 +0,0 @@ -//! Calendar utility calculations - -// TODO: determine if any of the below are needed. - -use crate::builtins::temporal::{self, date_equations, plain_date::iso::IsoDateRecord}; -use crate::JsString; - -/// 12.2.31 `ISODaysInMonth ( year, month )` -pub(crate) fn iso_days_in_month(year: i32, month: i32) -> i32 { - match month { - 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31, - 4 | 6 | 9 | 11 => 30, - 2 => { - 28 + temporal::date_equations::mathematical_in_leap_year( - temporal::date_equations::epoch_time_for_year(year), - ) - } - _ => unreachable!("an invalid month value is an implementation error."), - } -} - -/// 12.2.32 `ToISOWeekOfYear ( year, month, day )` -/// -/// Takes an `[[IsoYear]]`, `[[IsoMonth]]`, and `[[IsoDay]]` and returns a (week, year) record. -#[allow(unused)] -pub(crate) fn to_iso_week_of_year(year: i32, month: i32, day: i32) -> (i32, i32) { - // Function constants - // 2. Let wednesday be 3. - // 3. Let thursday be 4. - // 4. Let friday be 5. - // 5. Let saturday be 6. - // 6. Let daysInWeek be 7. - // 7. Let maxWeekNumber be 53. - let day_of_year = to_iso_day_of_year(year, month, day); - let day_of_week = to_iso_day_of_week(year, month, day); - let week = (day_of_week + 7 - day_of_week + 3) / 7; - - if week < 1 { - let first_day_of_year = to_iso_day_of_week(year, 1, 1); - if first_day_of_year == 5 { - return (53, year - 1); - } else if first_day_of_year == 6 - && date_equations::mathematical_in_leap_year(date_equations::epoch_time_for_year( - year - 1, - )) == 1 - { - return (52, year - 1); - } - return (52, year - 1); - } else if week == 53 { - let days_in_year = date_equations::mathematical_days_in_year(year); - let days_later_in_year = days_in_year - day_of_year; - let days_after_thursday = 4 - day_of_week; - if days_later_in_year < days_after_thursday { - return (1, year - 1); - } - } - (week, year) -} - -/// 12.2.33 `ISOMonthCode ( month )` -#[allow(unused)] -fn iso_month_code(month: i32) -> JsString { - // TODO: optimize - if month < 10 { - JsString::from(format!("M0{month}")) - } else { - JsString::from(format!("M{month}")) - } -} - -// 12.2.34 `ISOResolveMonth ( fields )` -// Note: currently implemented on TemporalFields -> implement in this mod? - -// 12.2.35 ISODateFromFields ( fields, overflow ) -// Note: implemented on IsoDateRecord. - -// 12.2.36 ISOYearMonthFromFields ( fields, overflow ) -// TODO: implement on a IsoYearMonthRecord - -// 12.2.37 ISOMonthDayFromFields ( fields, overflow ) -// TODO: implement as method on IsoDateRecord. - -// 12.2.38 IsoFieldKeysToIgnore -// TODO: determine usefulness. - -/// 12.2.39 `ToISODayOfYear ( year, month, day )` -#[allow(unused)] -fn to_iso_day_of_year(year: i32, month: i32, day: i32) -> i32 { - // TODO: update fn parameter to take IsoDateRecord. - let iso = IsoDateRecord::new(year, month - 1, day); - let epoch_days = iso.as_epoch_days(); - date_equations::epoch_time_to_day_in_year(temporal::epoch_days_to_epoch_ms(epoch_days, 0)) + 1 -} - -/// 12.2.40 `ToISODayOfWeek ( year, month, day )` -#[allow(unused)] -pub(crate) fn to_iso_day_of_week(year: i32, month: i32, day: i32) -> i32 { - let iso = IsoDateRecord::new(year, month - 1, day); - let epoch_days = iso.as_epoch_days(); - let day_of_week = - date_equations::epoch_time_to_week_day(temporal::epoch_days_to_epoch_ms(epoch_days, 0)); - if day_of_week == 0 { - return 7; - } - day_of_week -} diff --git a/boa_engine/src/builtins/temporal/date_equations.rs b/boa_engine/src/builtins/temporal/date_equations.rs deleted file mode 100644 index 201a90386dc..00000000000 --- a/boa_engine/src/builtins/temporal/date_equations.rs +++ /dev/null @@ -1,121 +0,0 @@ -//! This file represents all equations listed under section 13.4 of the [Temporal Specification][spec] -//! -//! [spec]: https://tc39.es/proposal-temporal/#sec-date-equations - -use std::ops::Mul; - -pub(crate) fn epoch_time_to_day_number(t: f64) -> i32 { - (t / f64::from(super::MS_PER_DAY)).floor() as i32 -} - -pub(crate) fn mathematical_days_in_year(y: i32) -> i32 { - if y % 4 != 0 { - 365 - } else if y % 4 == 0 && y % 100 != 0 { - 366 - } else if y % 100 == 0 && y % 400 != 0 { - 365 - } else { - // Assert that y is divisble by 400 to ensure we are returning the correct result. - assert_eq!(y % 400, 0); - 366 - } -} - -pub(crate) fn epoch_day_number_for_year(y: f64) -> f64 { - 365.0f64.mul_add(y - 1970.0, ((y - 1969.0) / 4.0).floor()) - ((y - 1901.0) / 100.0).floor() - + ((y - 1601.0) / 400.0).floor() -} - -pub(crate) fn epoch_time_for_year(y: i32) -> f64 { - f64::from(super::MS_PER_DAY) * epoch_day_number_for_year(f64::from(y)) -} - -// NOTE: The below returns the epoch years (years since 1970). The spec -// appears to assume the below returns with the epoch applied. -pub(crate) fn epoch_time_to_epoch_year(t: f64) -> i32 { - // roughly calculate the largest possible year given the time t, - // then check and refine the year. - let day_count = epoch_time_to_day_number(t); - let mut year = day_count / 365; - loop { - if epoch_time_for_year(year) <= t { - break; - } - year -= 1; - } - - year + 1970 -} - -/// Returns either 1 (true) or 0 (false) -pub(crate) fn mathematical_in_leap_year(t: f64) -> i32 { - mathematical_days_in_year(epoch_time_to_epoch_year(t)) - 365 -} - -pub(crate) fn epoch_time_to_month_in_year(t: f64) -> i32 { - const DAYS: [i32; 11] = [30, 58, 89, 120, 150, 181, 212, 242, 272, 303, 333]; - const LEAP_DAYS: [i32; 11] = [30, 59, 90, 121, 151, 182, 213, 242, 272, 303, 334]; - - let in_leap_year = mathematical_in_leap_year(t) == 1; - let day = epoch_time_to_day_in_year(t); - - let result = if in_leap_year { - LEAP_DAYS.binary_search(&day) - } else { - DAYS.binary_search(&day) - }; - - match result { - Ok(i) | Err(i) => i as i32, - } -} - -pub(crate) fn epoch_time_for_month_given_year(m: i32, y: i32) -> f64 { - let leap_day = mathematical_days_in_year(y) - 365; - - let days = match m { - 0 => 1, - 1 => 31, - 2 => 59 + leap_day, - 3 => 90 + leap_day, - 4 => 121 + leap_day, - 5 => 151 + leap_day, - 6 => 182 + leap_day, - 7 => 213 + leap_day, - 8 => 243 + leap_day, - 9 => 273 + leap_day, - 10 => 304 + leap_day, - 11 => 334 + leap_day, - _ => unreachable!(), - }; - - (super::NS_PER_DAY as f64).mul(f64::from(days)) -} - -pub(crate) fn epoch_time_to_date(t: f64) -> i32 { - const OFFSETS: [i16; 12] = [ - 1, -30, -58, -89, -119, -150, -180, -211, -242, -272, -303, -333, - ]; - let day_in_year = epoch_time_to_day_in_year(t); - let in_leap_year = mathematical_in_leap_year(t); - let month = epoch_time_to_month_in_year(t); - - // Cast from i32 to usize should be safe as the return must be 0-11 - let mut date = day_in_year + i32::from(OFFSETS[month as usize]); - - if month >= 2 { - date -= in_leap_year; - } - - date -} - -pub(crate) fn epoch_time_to_day_in_year(t: f64) -> i32 { - epoch_time_to_day_number(t) - - (epoch_day_number_for_year(f64::from(epoch_time_to_epoch_year(t))) as i32) -} - -pub(crate) fn epoch_time_to_week_day(t: f64) -> i32 { - (epoch_time_to_day_number(t) + 4) % 7 -} diff --git a/boa_engine/src/builtins/temporal/duration/mod.rs b/boa_engine/src/builtins/temporal/duration/mod.rs index ce1f9420eec..343daac8f11 100644 --- a/boa_engine/src/builtins/temporal/duration/mod.rs +++ b/boa_engine/src/builtins/temporal/duration/mod.rs @@ -15,22 +15,17 @@ use crate::{ Context, JsArgs, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; use boa_profiler::Profiler; +use boa_temporal::{duration::Duration as InnerDuration, options::TemporalUnit}; use super::{ - options::{ - get_temporal_rounding_increment, get_temporal_unit, TemporalUnit, TemporalUnitGroup, - }, - plain_date::{self, PlainDate}, + options::{get_temporal_rounding_increment, get_temporal_unit, TemporalUnitGroup}, + plain_date::PlainDate, to_integer_if_integral, DateTimeValues, PlainDateTime, }; -mod record; - #[cfg(test)] mod tests; -pub(crate) use record::{DateDuration, DurationRecord, TimeDuration}; - /// The `Temporal.Duration` object. /// /// Per [spec], `Duration` records are float64-representable integers @@ -38,7 +33,13 @@ pub(crate) use record::{DateDuration, DurationRecord, TimeDuration}; /// [spec]: https://tc39.es/proposal-temporal/#sec-properties-of-temporal-duration-instances #[derive(Debug, Clone, Copy)] pub struct Duration { - pub(crate) inner: DurationRecord, + pub(crate) inner: InnerDuration, +} + +impl Duration { + pub(crate) fn new(inner: InnerDuration) -> Self { + Self { inner } + } } impl BuiltInObject for Duration { @@ -241,17 +242,18 @@ impl BuiltInConstructor for Duration { .map_or(Ok(0), |ns| to_integer_if_integral(ns, context))?, ); - let record = DurationRecord::new( - DateDuration::new(years, months, weeks, days), - TimeDuration::new( - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, - ), - ); + let record = InnerDuration::new( + years, + months, + weeks, + days, + hours, + minutes, + seconds, + milliseconds, + microseconds, + nanoseconds, + )?; // 12. Return ? CreateTemporalDuration(y, mo, w, d, h, m, s, ms, mis, ns, NewTarget). create_temporal_duration(record, Some(new_target), context).map(Into::into) @@ -270,17 +272,19 @@ impl Duration { JsNativeError::typ().with_message("the this object must be a Duration object.") })?; + let inner = &duration.inner; + match field { - DateTimeValues::Year => Ok(JsValue::Rational(duration.inner.years())), - DateTimeValues::Month => Ok(JsValue::Rational(duration.inner.months())), - DateTimeValues::Week => Ok(JsValue::Rational(duration.inner.weeks())), - DateTimeValues::Day => Ok(JsValue::Rational(duration.inner.days())), - DateTimeValues::Hour => Ok(JsValue::Rational(duration.inner.hours())), - DateTimeValues::Minute => Ok(JsValue::Rational(duration.inner.minutes())), - DateTimeValues::Second => Ok(JsValue::Rational(duration.inner.seconds())), - DateTimeValues::Millisecond => Ok(JsValue::Rational(duration.inner.milliseconds())), - DateTimeValues::Microsecond => Ok(JsValue::Rational(duration.inner.microseconds())), - DateTimeValues::Nanosecond => Ok(JsValue::Rational(duration.inner.nanoseconds())), + DateTimeValues::Year => Ok(JsValue::Rational(inner.date().years())), + DateTimeValues::Month => Ok(JsValue::Rational(inner.date().months())), + DateTimeValues::Week => Ok(JsValue::Rational(inner.date().weeks())), + DateTimeValues::Day => Ok(JsValue::Rational(inner.date().days())), + DateTimeValues::Hour => Ok(JsValue::Rational(inner.time().hours())), + DateTimeValues::Minute => Ok(JsValue::Rational(inner.time().minutes())), + DateTimeValues::Second => Ok(JsValue::Rational(inner.time().seconds())), + DateTimeValues::Millisecond => Ok(JsValue::Rational(inner.time().milliseconds())), + DateTimeValues::Microsecond => Ok(JsValue::Rational(inner.time().microseconds())), + DateTimeValues::Nanosecond => Ok(JsValue::Rational(inner.time().nanoseconds())), DateTimeValues::MonthCode => unreachable!( "Any other DateTimeValue fields on Duration would be an implementation error." ), @@ -399,122 +403,123 @@ impl Duration { // 3. Let temporalDurationLike be ? ToTemporalPartialDurationRecord(temporalDurationLike). let temporal_duration_like = - DurationRecord::from_partial_js_object(args.get_or_undefined(0), context)?; + to_temporal_partial_duration(args.get_or_undefined(0), context)?; // 4. If temporalDurationLike.[[Years]] is not undefined, then // a. Let years be temporalDurationLike.[[Years]]. // 5. Else, // a. Let years be duration.[[Years]]. - let years = if temporal_duration_like.years().is_nan() { - duration.inner.years() + let years = if temporal_duration_like.date().years().is_nan() { + duration.inner.date().years() } else { - temporal_duration_like.years() + temporal_duration_like.date().years() }; // 6. If temporalDurationLike.[[Months]] is not undefined, then // a. Let months be temporalDurationLike.[[Months]]. // 7. Else, // a. Let months be duration.[[Months]]. - let months = if temporal_duration_like.months().is_nan() { - duration.inner.months() + let months = if temporal_duration_like.date().months().is_nan() { + duration.inner.date().months() } else { - temporal_duration_like.months() + temporal_duration_like.date().months() }; // 8. If temporalDurationLike.[[Weeks]] is not undefined, then // a. Let weeks be temporalDurationLike.[[Weeks]]. // 9. Else, // a. Let weeks be duration.[[Weeks]]. - let weeks = if temporal_duration_like.weeks().is_nan() { - duration.inner.weeks() + let weeks = if temporal_duration_like.date().weeks().is_nan() { + duration.inner.date().weeks() } else { - temporal_duration_like.weeks() + temporal_duration_like.date().weeks() }; // 10. If temporalDurationLike.[[Days]] is not undefined, then // a. Let days be temporalDurationLike.[[Days]]. // 11. Else, // a. Let days be duration.[[Days]]. - let days = if temporal_duration_like.days().is_nan() { - duration.inner.days() + let days = if temporal_duration_like.date().days().is_nan() { + duration.inner.date().days() } else { - temporal_duration_like.days() + temporal_duration_like.date().days() }; // 12. If temporalDurationLike.[[Hours]] is not undefined, then // a. Let hours be temporalDurationLike.[[Hours]]. // 13. Else, // a. Let hours be duration.[[Hours]]. - let hours = if temporal_duration_like.hours().is_nan() { - duration.inner.hours() + let hours = if temporal_duration_like.time().hours().is_nan() { + duration.inner.time().hours() } else { - temporal_duration_like.hours() + temporal_duration_like.time().hours() }; // 14. If temporalDurationLike.[[Minutes]] is not undefined, then // a. Let minutes be temporalDurationLike.[[Minutes]]. // 15. Else, // a. Let minutes be duration.[[Minutes]]. - let minutes = if temporal_duration_like.minutes().is_nan() { - duration.inner.minutes() + let minutes = if temporal_duration_like.time().minutes().is_nan() { + duration.inner.time().minutes() } else { - temporal_duration_like.minutes() + temporal_duration_like.time().minutes() }; // 16. If temporalDurationLike.[[Seconds]] is not undefined, then // a. Let seconds be temporalDurationLike.[[Seconds]]. // 17. Else, // a. Let seconds be duration.[[Seconds]]. - let seconds = if temporal_duration_like.seconds().is_nan() { - duration.inner.seconds() + let seconds = if temporal_duration_like.time().seconds().is_nan() { + duration.inner.time().seconds() } else { - temporal_duration_like.seconds() + temporal_duration_like.time().seconds() }; // 18. If temporalDurationLike.[[Milliseconds]] is not undefined, then // a. Let milliseconds be temporalDurationLike.[[Milliseconds]]. // 19. Else, // a. Let milliseconds be duration.[[Milliseconds]]. - let milliseconds = if temporal_duration_like.milliseconds().is_nan() { - duration.inner.milliseconds() + let milliseconds = if temporal_duration_like.time().milliseconds().is_nan() { + duration.inner.time().milliseconds() } else { - temporal_duration_like.milliseconds() + temporal_duration_like.time().milliseconds() }; // 20. If temporalDurationLike.[[Microseconds]] is not undefined, then // a. Let microseconds be temporalDurationLike.[[Microseconds]]. // 21. Else, // a. Let microseconds be duration.[[Microseconds]]. - let microseconds = if temporal_duration_like.microseconds().is_nan() { - duration.inner.microseconds() + let microseconds = if temporal_duration_like.time().microseconds().is_nan() { + duration.inner.time().microseconds() } else { - temporal_duration_like.microseconds() + temporal_duration_like.time().microseconds() }; // 22. If temporalDurationLike.[[Nanoseconds]] is not undefined, then // a. Let nanoseconds be temporalDurationLike.[[Nanoseconds]]. // 23. Else, // a. Let nanoseconds be duration.[[Nanoseconds]]. - let nanoseconds = if temporal_duration_like.nanoseconds().is_nan() { - duration.inner.nanoseconds() + let nanoseconds = if temporal_duration_like.time().nanoseconds().is_nan() { + duration.inner.time().nanoseconds() } else { - temporal_duration_like.nanoseconds() + temporal_duration_like.time().nanoseconds() }; // 24. Return ? CreateTemporalDuration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds). - let new_duration = DurationRecord::new( - DateDuration::new(years, months, weeks, days), - TimeDuration::new( - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, - ), - ); + let new_duration = InnerDuration::new( + years, + months, + weeks, + days, + hours, + minutes, + seconds, + milliseconds, + microseconds, + nanoseconds, + )?; - new_duration.as_object(context).map(Into::into) + create_temporal_duration(new_duration, None, context).map(Into::into) } /// 7.3.16 `Temporal.Duration.prototype.negated ( )` @@ -544,7 +549,7 @@ impl Duration { let abs = duration.inner.abs(); - abs.as_object(context).map(Into::into) + create_temporal_duration(abs, None, context).map(Into::into) } /// 7.3.18 `Temporal.Duration.prototype.add ( other [ , options ] )` @@ -630,7 +635,7 @@ impl Duration { let rounding_increment = get_temporal_rounding_increment(&round_to, context)?; // 14. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand"). - let rounding_mode = get_option(&round_to, utf16!("roundingMode"), context)? + let _rounding_mode = get_option(&round_to, utf16!("roundingMode"), context)? .unwrap_or(RoundingMode::HalfExpand); // 15. Let smallestUnit be ? GetTemporalUnit(roundTo, "smallestUnit", datetime, undefined). @@ -691,7 +696,42 @@ impl Duration { // 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)?; + validate_temporal_rounding_increment(rounding_increment.into(), max.into(), false)?; + } + + // 25. Let hoursToDaysConversionMayOccur be false. + // 26. If duration.[[Days]] ≠ 0 and zonedRelativeTo is not undefined, set hoursToDaysConversionMayOccur to true. + // 27. Else if abs(duration.[[Hours]]) ≥ 24, set hoursToDaysConversionMayOccur to true. + let conversion_may_occur = + if duration.inner.date().days() != 0.0 && zoned_relative_to.is_some() { + true + } else { + 24f64 <= duration.inner.time().hours().abs() + }; + + // 28. If smallestUnit is "nanosecond" and roundingIncrement = 1, let roundingGranularityIsNoop be true; else let roundingGranularityIsNoop be false. + let is_noop = smallest_unit == TemporalUnit::Nanosecond && rounding_increment == 1; + + // 29. If duration.[[Years]] = 0 and duration.[[Months]] = 0 and duration.[[Weeks]] = 0, let calendarUnitsPresent be false; else let calendarUnitsPresent be true. + let calendar_units_present = !(duration.inner.date().years() == 0f64 + || duration.inner.date().months() == 0f64 + || duration.inner.date().weeks() == 0f64); + + // 30. If roundingGranularityIsNoop is true, and largestUnit is existingLargestUnit, + // and calendarUnitsPresent is false, and hoursToDaysConversionMayOccur is false, + // and abs(duration.[[Minutes]]) < 60, and abs(duration.[[Seconds]]) < 60, + // and abs(duration.[[Milliseconds]]) < 1000, and abs(duration.[[Microseconds]]) < 1000, + // and abs(duration.[[Nanoseconds]]) < 1000, then + if is_noop + && largest_unit == existing_largest_unit + && !calendar_units_present + && !conversion_may_occur + && duration.inner.is_time_within_range() + { + // a. NOTE: The above conditions mean that the operation will have no effect: the smallest unit and + // rounding increment will leave the total duration unchanged, and it can be determined without + // calling a calendar or time zone method that no balancing will take place. + // b. Return ! CreateTemporalDuration(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]]). } // 31. Let precalculatedPlainDateTime be undefined. @@ -706,10 +746,10 @@ impl Duration { || largest_unit == TemporalUnit::Week || largest_unit == TemporalUnit::Day || calendar_units_present - || duration.inner.days() != 0f64; + || duration.inner.date().days() != 0f64; // 33. If zonedRelativeTo is not undefined and plainDateTimeOrRelativeToWillBeUsed is true, then - let (plain_relative_to, precalc_pdt) = if zoned_relative_to.is_some() + let (_plain_relative_to, _precalc_pdt) = if zoned_relative_to.is_some() && pdt_or_rel_will_be_used { // TODO(TimeZone): Implement GetPlainDateTimeFor @@ -729,29 +769,10 @@ impl Duration { }; // 34. Let unbalanceResult be ? UnbalanceDateDurationRelative(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], largestUnit, plainRelativeTo). - let unbalance_result = duration.inner.unbalance_duration_relative( - largest_unit, - plain_relative_to.as_ref(), - context, - )?; - // 35. Let roundRecord 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, plainRelativeTo, zonedRelativeTo, precalculatedPlainDateTime). - let (_round_result, _) = duration.inner.round_duration( - unbalance_result, - rounding_increment.into(), - smallest_unit, - rounding_mode, - ( - plain_relative_to.as_ref(), - zoned_relative_to.as_ref(), - precalc_pdt.as_ref(), - ), - context, - )?; - // 36. Let roundResult be roundRecord.[[DurationRecord]]. // 37. If zonedRelativeTo is not undefined, then // 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, zonedRelativeTo, precalculatedPlainDateTime). @@ -853,7 +874,7 @@ impl Duration { // -- Duration Abstract Operations -- /// 7.5.8 `ToTemporalDuration ( item )` -pub(crate) fn to_temporal_duration(item: &JsValue) -> JsResult { +pub(crate) fn to_temporal_duration(item: &JsValue) -> JsResult { // 1a. If Type(item) is Object if item.is_object() { // 1b. and item has an [[InitializedTemporalDuration]] internal slot, then @@ -877,7 +898,7 @@ pub(crate) fn to_temporal_duration(item: &JsValue) -> JsResult { /// 7.5.9 `ToTemporalDurationRecord ( temporalDurationLike )` pub(crate) fn to_temporal_duration_record( _temporal_duration_like: &JsValue, -) -> JsResult { +) -> JsResult { Err(JsNativeError::range() .with_message("Duration Parsing is not yet complete.") .into()) @@ -885,16 +906,11 @@ pub(crate) fn to_temporal_duration_record( /// 7.5.14 `CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds [ , newTarget ] )` pub(crate) fn create_temporal_duration( - record: DurationRecord, + inner: InnerDuration, new_target: Option<&JsValue>, context: &mut Context, ) -> JsResult { // 1. If ! IsValidDuration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds) is false, throw a RangeError exception. - if !record.is_valid_duration() { - return Err(JsNativeError::range() - .with_message("Duration values are not valid.") - .into()); - } // 2. If newTarget is not present, set newTarget to %Temporal.Duration%. let new_target = if let Some(target) = new_target { @@ -924,38 +940,106 @@ pub(crate) fn create_temporal_duration( // 12. Set object.[[Microseconds]] to ℝ(𝔽(microseconds)). // 13. Set object.[[Nanoseconds]] to ℝ(𝔽(nanoseconds)). - let obj = - JsObject::from_proto_and_data(prototype, ObjectData::duration(Duration { inner: record })); + let obj = JsObject::from_proto_and_data(prototype, ObjectData::duration(Duration::new(inner))); // 14. Return object. Ok(obj) } -/// 7.5.23 `DaysUntil ( earlier, later )` -pub(crate) fn days_until(earlier: &PlainDate, later: &PlainDate) -> i32 { - // 1. Let epochDays1 be ISODateToEpochDays(earlier.[[ISOYear]], earlier.[[ISOMonth]] - 1, earlier.[[ISODay]]). - let epoch_days_one = earlier.inner.as_epoch_days(); +/// Equivalent to 7.5.13 `ToTemporalPartialDurationRecord ( temporalDurationLike )` +/// +pub(crate) fn to_temporal_partial_duration( + duration_like: &JsValue, + context: &mut Context, +) -> JsResult { + // 1. If Type(temporalDurationLike) is not Object, then + let JsValue::Object(unknown_object) = duration_like else { + // a. Throw a TypeError exception. + return Err(JsNativeError::typ() + .with_message("temporalDurationLike must be an object.") + .into()); + }; - // 2. Let epochDays2 be ISODateToEpochDays(later.[[ISOYear]], later.[[ISOMonth]] - 1, later.[[ISODay]]). - let epoch_days_two = later.inner.as_epoch_days(); + // 2. Let result be a new partial Duration Record with each field set to undefined. + let mut result = InnerDuration::partial(); - // 3. Return epochDays2 - epochDays1. - epoch_days_two - epoch_days_one -} + // 3. NOTE: The following steps read properties and perform independent validation in alphabetical order. + // 4. Let days be ? Get(temporalDurationLike, "days"). + let days = unknown_object.get(utf16!("days"), context)?; + if !days.is_undefined() { + // 5. If days is not undefined, set result.[[Days]] to ? ToIntegerIfIntegral(days). + result.set_days(f64::from(to_integer_if_integral(&days, context)?)); + } -/// Abstract Operation 7.5.24 `MoveRelativeDate ( calendar, relativeTo, duration, dateAdd )` -fn move_relative_date( - calendar: &JsValue, - relative_to: &PlainDate, - duration: &DurationRecord, - context: &mut Context, -) -> 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))) + // 6. Let hours be ? Get(temporalDurationLike, "hours"). + let hours = unknown_object.get(utf16!("hours"), context)?; + // 7. If hours is not undefined, set result.[[Hours]] to ? ToIntegerIfIntegral(hours). + if !hours.is_undefined() { + result.set_days(f64::from(to_integer_if_integral(&hours, context)?)); + } + + // 8. Let microseconds be ? Get(temporalDurationLike, "microseconds"). + let microseconds = unknown_object.get(utf16!("microseconds"), context)?; + // 9. If microseconds is not undefined, set result.[[Microseconds]] to ? ToIntegerIfIntegral(microseconds). + if !microseconds.is_undefined() { + result.set_days(f64::from(to_integer_if_integral(µseconds, context)?)); + } + + // 10. Let milliseconds be ? Get(temporalDurationLike, "milliseconds"). + let milliseconds = unknown_object.get(utf16!("milliseconds"), context)?; + // 11. If milliseconds is not undefined, set result.[[Milliseconds]] to ? ToIntegerIfIntegral(milliseconds). + if !milliseconds.is_undefined() { + result.set_days(f64::from(to_integer_if_integral(&milliseconds, context)?)); + } + + // 12. Let minutes be ? Get(temporalDurationLike, "minutes"). + let minutes = unknown_object.get(utf16!("minutes"), context)?; + // 13. If minutes is not undefined, set result.[[Minutes]] to ? ToIntegerIfIntegral(minutes). + if !minutes.is_undefined() { + result.set_days(f64::from(to_integer_if_integral(&minutes, context)?)); + } + + // 14. Let months be ? Get(temporalDurationLike, "months"). + let months = unknown_object.get(utf16!("months"), context)?; + // 15. If months is not undefined, set result.[[Months]] to ? ToIntegerIfIntegral(months). + if !months.is_undefined() { + result.set_days(f64::from(to_integer_if_integral(&months, context)?)); + } + + // 16. Let nanoseconds be ? Get(temporalDurationLike, "nanoseconds"). + let nanoseconds = unknown_object.get(utf16!("nanoseconds"), context)?; + // 17. If nanoseconds is not undefined, set result.[[Nanoseconds]] to ? ToIntegerIfIntegral(nanoseconds). + if !nanoseconds.is_undefined() { + result.set_days(f64::from(to_integer_if_integral(&nanoseconds, context)?)); + } + + // 18. Let seconds be ? Get(temporalDurationLike, "seconds"). + let seconds = unknown_object.get(utf16!("seconds"), context)?; + // 19. If seconds is not undefined, set result.[[Seconds]] to ? ToIntegerIfIntegral(seconds). + if !seconds.is_undefined() { + result.set_days(f64::from(to_integer_if_integral(&seconds, context)?)); + } + + // 20. Let weeks be ? Get(temporalDurationLike, "weeks"). + let weeks = unknown_object.get(utf16!("weeks"), context)?; + // 21. If weeks is not undefined, set result.[[Weeks]] to ? ToIntegerIfIntegral(weeks). + if !weeks.is_undefined() { + result.set_days(f64::from(to_integer_if_integral(&weeks, context)?)); + } + + // 22. Let years be ? Get(temporalDurationLike, "years"). + let years = unknown_object.get(utf16!("years"), context)?; + // 23. If years is not undefined, set result.[[Years]] to ? ToIntegerIfIntegral(years). + if !years.is_undefined() { + result.set_days(f64::from(to_integer_if_integral(&years, context)?)); + } + + // 24. If years is undefined, and months is undefined, and weeks is undefined, and days is undefined, and hours is undefined, and minutes is undefined, and seconds is undefined, and milliseconds is undefined, and microseconds is undefined, and nanoseconds is undefined, throw a TypeError exception. + if result.into_iter().all(f64::is_nan) { + return Err(JsNativeError::typ() + .with_message("no valid Duration fields on temporalDurationLike.") + .into()); + } + + // 25. Return result. + Ok(result) } diff --git a/boa_engine/src/builtins/temporal/duration/record.rs b/boa_engine/src/builtins/temporal/duration/record.rs index cd29c089a6e..8b2f2f7d0d1 100644 --- a/boa_engine/src/builtins/temporal/duration/record.rs +++ b/boa_engine/src/builtins/temporal/duration/record.rs @@ -700,6 +700,7 @@ impl DurationRecord { .mul_add(1_000_f64, self.microseconds()) .mul_add(1_000_f64, nanoseconds) } +<<<<<<< HEAD /// Abstract Operation 7.5.18 `BalanceTimeDuration ( days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, largestUnit [ , relativeTo ] )` pub(crate) fn balance_time_duration( @@ -1969,4 +1970,6 @@ impl DurationRecord { .with_message("not yet implemented.") .into()) } +======= +>>>>>>> b5192f7fe7 (Begin Temporal crate migration) } diff --git a/boa_engine/src/builtins/temporal/error.rs b/boa_engine/src/builtins/temporal/error.rs new file mode 100644 index 00000000000..3b86b21a17d --- /dev/null +++ b/boa_engine/src/builtins/temporal/error.rs @@ -0,0 +1,20 @@ +use boa_temporal::error::{ErrorKind, TemporalError}; + +use crate::{JsError, JsNativeError}; + +impl From for JsNativeError { + fn from(value: TemporalError) -> Self { + match value.kind() { + ErrorKind::Range => JsNativeError::range().with_message(value.message()), + ErrorKind::Type => JsNativeError::typ().with_message(value.message()), + ErrorKind::Generic => JsNativeError::error().with_message(value.message()), + } + } +} + +impl From for JsError { + fn from(value: TemporalError) -> Self { + let native: JsNativeError = value.into(); + native.into() + } +} diff --git a/boa_engine/src/builtins/temporal/fields.rs b/boa_engine/src/builtins/temporal/fields.rs index e3fc21e76d0..3dc3c4214a1 100644 --- a/boa_engine/src/builtins/temporal/fields.rs +++ b/boa_engine/src/builtins/temporal/fields.rs @@ -1,587 +1,172 @@ //! A Rust native implementation of the `fields` object used in `Temporal`. +use std::str::FromStr; + use crate::{ js_string, property::PropertyKey, value::PreferredType, Context, JsNativeError, JsObject, JsResult, JsString, JsValue, }; -use super::options::ArithmeticOverflow; - -use bitflags::bitflags; use rustc_hash::FxHashSet; -bitflags! { - #[derive(Debug, PartialEq, Eq)] - pub struct FieldMap: u16 { - const YEAR = 0b0000_0000_0000_0001; - const MONTH = 0b0000_0000_0000_0010; - const MONTH_CODE = 0b0000_0000_0000_0100; - const DAY = 0b0000_0000_0000_1000; - const HOUR = 0b0000_0000_0001_0000; - const MINUTE = 0b0000_0000_0010_0000; - const SECOND = 0b0000_0000_0100_0000; - const MILLISECOND = 0b0000_0000_1000_0000; - const MICROSECOND = 0b0000_0001_0000_0000; - const NANOSECOND = 0b0000_0010_0000_0000; - const OFFSET = 0b0000_0100_0000_0000; - const ERA = 0b0000_1000_0000_0000; - const ERA_YEAR = 0b0001_0000_0000_0000; - const TIME_ZONE = 0b0010_0000_0000_0000; - } -} - -/// The temporal fields are laid out in the Temporal proposal under section 13.46 `PrepareTemporalFields` -/// with conversion and defaults laid out by Table 17 (displayed below). -/// -/// `TemporalFields` is meant to act as a native Rust implementation -/// of the fields. -/// -/// -/// ## Table 17: Temporal field requirements -/// -/// | Property | Conversion | Default | -/// | -------------|-----------------------------------|------------| -/// | "year" | `ToIntegerWithTruncation` | undefined | -/// | "month" | `ToPositiveIntegerWithTruncation` | undefined | -/// | "monthCode" | `ToPrimitiveAndRequireString` | undefined | -/// | "day" | `ToPositiveIntegerWithTruncation` | undefined | -/// | "hour" | `ToIntegerWithTruncation` | +0𝔽 | -/// | "minute" | `ToIntegerWithTruncation` | +0𝔽 | -/// | "second" | `ToIntegerWithTruncation` | +0𝔽 | -/// | "millisecond"| `ToIntegerWithTruncation` | +0𝔽 | -/// | "microsecond"| `ToIntegerWithTruncation` | +0𝔽 | -/// | "nanosecond" | `ToIntegerWithTruncation` | +0𝔽 | -/// | "offset" | `ToPrimitiveAndRequireString` | undefined | -/// | "era" | `ToPrimitiveAndRequireString` | undefined | -/// | "eraYear" | `ToIntegerWithTruncation` | undefined | -/// | "timeZone" | `None` | undefined | -#[derive(Debug)] -pub(crate) struct TemporalFields { - bit_map: FieldMap, - year: Option, - month: Option, - month_code: Option, // TODO: Switch to icu compatible value. - day: Option, - hour: i32, - minute: i32, - second: i32, - millisecond: i32, - microsecond: i32, - nanosecond: i32, - offset: Option, - era: Option, // TODO: switch to icu compatible value. - era_year: Option, // TODO: switch to icu compatible value. - time_zone: Option, // TODO: figure out the identifier for TimeZone. -} - -impl Default for TemporalFields { - fn default() -> Self { - Self { - bit_map: FieldMap::empty(), - year: None, - month: None, - month_code: None, - day: None, - hour: 0, - minute: 0, - second: 0, - millisecond: 0, - microsecond: 0, - nanosecond: 0, - offset: None, - era: None, - era_year: None, - time_zone: None, - } - } -} - -impl TemporalFields { - pub(crate) const fn year(&self) -> Option { - self.year - } - - pub(crate) const fn month(&self) -> Option { - self.month - } - - pub(crate) const fn day(&self) -> Option { - self.day - } -} - -impl TemporalFields { - #[inline] - fn set_field_value( - &mut self, - field: &JsString, - value: &JsValue, - context: &mut Context, - ) -> JsResult<()> { - 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)?, - "day" => self.set_day(value, context)?, - "hour" => self.set_hour(value, context)?, - "minute" => self.set_minute(value, context)?, - "second" => self.set_second(value, context)?, - "millisecond" => self.set_milli(value, context)?, - "microsecond" => self.set_micro(value, context)?, - "nanosecond" => self.set_nano(value, context)?, - "offset" => self.set_offset(value, context)?, - "era" => self.set_era(value, context)?, - "eraYear" => self.set_era_year(value, context)?, - "timeZone" => self.set_time_zone(value), - _ => unreachable!(), - } - - Ok(()) - } - - #[inline] - fn set_year(&mut self, value: &JsValue, context: &mut Context) -> JsResult<()> { - let y = super::to_integer_with_truncation(value, context)?; - self.year = Some(y); - self.bit_map.set(FieldMap::YEAR, true); - Ok(()) - } - - #[inline] - fn set_month(&mut self, value: &JsValue, context: &mut Context) -> JsResult<()> { - let mo = super::to_positive_integer_with_trunc(value, context)?; - self.year = Some(mo); - self.bit_map.set(FieldMap::MONTH, true); - Ok(()) - } - - #[inline] - fn set_month_code(&mut self, value: &JsValue, context: &mut Context) -> JsResult<()> { - let mc = value.to_primitive(context, PreferredType::String)?; - if let Some(string) = mc.as_string() { - self.month_code = Some(string.clone()); - } else { - return Err(JsNativeError::typ() - .with_message("ToPrimativeAndRequireString must be of type String.") - .into()); +use boa_temporal::fields::{FieldConversion, FieldValue, TemporalFields}; + +use super::{to_integer_with_truncation, to_positive_integer_with_trunc}; + +// TODO: Move extended and required fields into the temporal library? +/// `PrepareTemporalFeilds` +pub(crate) fn prepare_temporal_fields( + fields: &JsObject, + field_names: &mut Vec, + required_fields: &mut Vec, + extended_fields: Option>, + partial: bool, + dup_behaviour: Option, + context: &mut Context, +) -> JsResult { + // 1. If duplicateBehaviour is not present, set duplicateBehaviour to throw. + let dup_option = dup_behaviour.unwrap_or_else(|| js_string!("throw")); + + // 2. Let result be OrdinaryObjectCreate(null). + let mut result = TemporalFields::default(); + + // 3. Let any be false. + let mut any = false; + // 4. If extraFieldDescriptors is present, then + if let Some(extra_fields) = extended_fields { + for (field_name, required) in extra_fields { + // a. For each Calendar Field Descriptor Record desc of extraFieldDescriptors, do + // i. Assert: fieldNames does not contain desc.[[Property]]. + // ii. Append desc.[[Property]] to fieldNames. + field_names.push(JsString::from(field_name.clone())); + + // iii. If desc.[[Required]] is true and requiredFields is a List, then + if required && !partial { + // 1. Append desc.[[Property]] to requiredFields. + required_fields.push(JsString::from(field_name)); + } } - - self.bit_map.set(FieldMap::MONTH_CODE, true); - - Ok(()) } - #[inline] - fn set_day(&mut self, value: &JsValue, context: &mut Context) -> JsResult<()> { - let d = super::to_positive_integer_with_trunc(value, context)?; - self.day = Some(d); - self.bit_map.set(FieldMap::DAY, true); - Ok(()) - } - - #[inline] - fn set_hour(&mut self, value: &JsValue, context: &mut Context) -> JsResult<()> { - let h = super::to_integer_with_truncation(value, context)?; - self.hour = h; - self.bit_map.set(FieldMap::HOUR, true); - Ok(()) - } - - #[inline] - fn set_minute(&mut self, value: &JsValue, context: &mut Context) -> JsResult<()> { - let m = super::to_integer_with_truncation(value, context)?; - self.minute = m; - self.bit_map.set(FieldMap::MINUTE, true); - Ok(()) - } - - #[inline] - fn set_second(&mut self, value: &JsValue, context: &mut Context) -> JsResult<()> { - let sec = super::to_integer_with_truncation(value, context)?; - self.second = sec; - self.bit_map.set(FieldMap::SECOND, true); - Ok(()) - } - - #[inline] - fn set_milli(&mut self, value: &JsValue, context: &mut Context) -> JsResult<()> { - let milli = super::to_integer_with_truncation(value, context)?; - self.millisecond = milli; - self.bit_map.set(FieldMap::MILLISECOND, true); - Ok(()) - } - - #[inline] - fn set_micro(&mut self, value: &JsValue, context: &mut Context) -> JsResult<()> { - let micro = super::to_integer_with_truncation(value, context)?; - self.microsecond = micro; - self.bit_map.set(FieldMap::MICROSECOND, true); - Ok(()) - } - - #[inline] - fn set_nano(&mut self, value: &JsValue, context: &mut Context) -> JsResult<()> { - let nano = super::to_integer_with_truncation(value, context)?; - self.nanosecond = nano; - self.bit_map.set(FieldMap::NANOSECOND, true); - Ok(()) - } - - #[inline] - fn set_offset(&mut self, value: &JsValue, context: &mut Context) -> JsResult<()> { - let mc = value.to_primitive(context, PreferredType::String)?; - if let Some(string) = mc.as_string() { - self.offset = Some(string.clone()); - } else { - return Err(JsNativeError::typ() - .with_message("ToPrimativeAndRequireString must be of type String.") - .into()); - } - self.bit_map.set(FieldMap::OFFSET, true); + // 5. Let sortedFieldNames be SortStringListByCodeUnit(fieldNames). + // 6. Let previousProperty be undefined. + let mut dups_map = FxHashSet::default(); - Ok(()) - } - - #[inline] - fn set_era(&mut self, value: &JsValue, context: &mut Context) -> JsResult<()> { - let mc = value.to_primitive(context, PreferredType::String)?; - if let Some(string) = mc.as_string() { - self.era = Some(string.clone()); - } else { - return Err(JsNativeError::typ() - .with_message("ToPrimativeAndRequireString must be of type String.") + // 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.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.") .into()); } - self.bit_map.set(FieldMap::ERA, true); - - Ok(()) - } - - #[inline] - fn set_era_year(&mut self, value: &JsValue, context: &mut Context) -> JsResult<()> { - let ey = super::to_integer_with_truncation(value, context)?; - self.era_year = Some(ey); - self.bit_map.set(FieldMap::ERA_YEAR, true); - Ok(()) - } - - #[inline] - fn set_time_zone(&mut self, value: &JsValue) { - let tz = value.as_string().cloned(); - self.time_zone = tz; - self.bit_map.set(FieldMap::TIME_ZONE, true); - } -} - -impl TemporalFields { - // TODO: Shift to JsString or utf16 over String. - /// A method for creating a Native representation for `TemporalFields` from - /// a `JsObject`. - /// - /// 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>, - partial: bool, - dup_behaviour: Option, - context: &mut Context, - ) -> JsResult { - // 1. If duplicateBehaviour is not present, set duplicateBehaviour to throw. - let dup_option = dup_behaviour.unwrap_or_else(|| js_string!("throw")); - - // 2. Let result be OrdinaryObjectCreate(null). - let mut result = Self::default(); - - // 3. Let any be false. - let mut any = false; - // 4. If extraFieldDescriptors is present, then - if let Some(extra_fields) = extended_fields { - for (field_name, required) in extra_fields { - // a. For each Calendar Field Descriptor Record desc of extraFieldDescriptors, do - // i. Assert: fieldNames does not contain desc.[[Property]]. - // ii. Append desc.[[Property]] to fieldNames. - field_names.push(field_name.clone()); - // iii. If desc.[[Required]] is true and requiredFields is a List, then - if required && !partial { - // 1. Append desc.[[Property]] to requiredFields. - required_fields.push(field_name); - } - } - } - - // 5. Let sortedFieldNames be SortStringListByCodeUnit(fieldNames). - // 6. Let previousProperty be undefined. - let mut dups_map = FxHashSet::default(); - - // 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.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.") - .into()); - } - - let new_value = dups_map.insert(field); - - // 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(field.clone()), context)?; - // ii. If value is not undefined, then - if !value.is_undefined() { - // 1. Set any to true. - any = true; - - // 2. If property is in the Property column of Table 17 and there is a Conversion value in the same row, then - // a. Let Conversion be the Conversion value of the same row. - // b. If Conversion is ToIntegerWithTruncation, then - // i. Set value to ? ToIntegerWithTruncation(value). - // ii. Set value to 𝔽(value). + let new_value = dups_map.insert(field); + + // 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(field.clone()), context)?; + // ii. If value is not undefined, then + if !value.is_undefined() { + // 1. Set any to true. + any = true; + + // 2. If property is in the Property column of Table 17 and there is a Conversion value in the same row, then + // a. Let Conversion be the Conversion value of the same row. + + // TODO: Conversion from TemporalError -> JsError + let conversion = FieldConversion::from_str(field.to_std_string_escaped().as_str()) + .map_err(|_| JsNativeError::range().with_message("wrong field value"))?; + // b. If Conversion is ToIntegerWithTruncation, then + let converted_value = match conversion { + FieldConversion::ToIntegerWithTruncation => { + // i. Set value to ? ToIntegerWithTruncation(value). + let v = to_integer_with_truncation(&value, context)?; + // ii. Set value to 𝔽(value). + FieldValue::Integer(v) + } // c. Else if Conversion is ToPositiveIntegerWithTruncation, then - // i. Set value to ? ToPositiveIntegerWithTruncation(value). - // ii. Set value to 𝔽(value). + FieldConversion::ToPositiveIntegerWithTruncation => { + // i. Set value to ? ToPositiveIntegerWithTruncation(value). + let v = to_positive_integer_with_trunc(&value, context)?; + // ii. Set value to 𝔽(value). + FieldValue::Integer(v) + } // d. Else, // i. Assert: Conversion is ToPrimitiveAndRequireString. - // ii. NOTE: Non-primitive values are supported here for consistency with other fields, but such values must coerce to Strings. - // iii. Set value to ? ToPrimitive(value, string). - // iv. If value is not a String, throw a TypeError exception. - // 3. Perform ! CreateDataPropertyOrThrow(result, property, value). - result.set_field_value(field, &value, context)?; - // iii. Else if requiredFields is a List, then - } else if !partial { - // 1. If requiredFields contains property, then - if required_fields.contains(field) { - // a. Throw a TypeError exception. - return Err(JsNativeError::typ() - .with_message("A required TemporalField was not provided.") - .into()); + FieldConversion::ToPrimativeAndRequireString => { + // ii. NOTE: Non-primitive values are supported here for consistency with other fields, but such values must coerce to Strings. + // iii. Set value to ? ToPrimitive(value, string). + let primitive = value.to_primitive(context, PreferredType::String)?; + // iv. If value is not a String, throw a TypeError exception. + FieldValue::String(primitive.to_string(context)?.to_std_string_escaped()) } - - // NOTE: Values set to a default on init. - // 2. If property is in the Property column of Table 17, then - // a. Set value to the corresponding Default value of the same row. - // 3. Perform ! CreateDataPropertyOrThrow(result, property, value). - } - // c. Else if duplicateBehaviour is throw, then - } else if dup_option.to_std_string_escaped() == "throw" { - // i. Throw a RangeError exception. - return Err(JsNativeError::range() - .with_message("Cannot have a duplicate field") - .into()); + _ => unreachable!("todo need to implement conversion handling for tz."), + }; + + // 3. Perform ! CreateDataPropertyOrThrow(result, property, value). + result + .set_field_value(field.to_std_string_escaped(), converted_value) + .expect("FieldConversion enforces the appropriate type"); + // iii. Else if requiredFields is a List, then + } else if !partial { + // 1. If requiredFields contains property, then + if required_fields.contains(field) { + // a. Throw a TypeError exception. + return Err(JsNativeError::typ() + .with_message("A required TemporalField was not provided.") + .into()); + } + + // NOTE: flag that the value is active and the default should be used. + // 2. If property is in the Property column of Table 17, then + // a. Set value to the corresponding Default value of the same row. + // 3. Perform ! CreateDataPropertyOrThrow(result, property, value). + result.require_field(field.to_std_string_escaped()); } - // d. Set previousProperty to property. - } - - // 8. If requiredFields is partial and any is false, then - if partial && !any { - // a. Throw a TypeError exception. + // c. Else if duplicateBehaviour is throw, then + } else if dup_option.to_std_string_escaped() == "throw" { + // i. Throw a RangeError exception. return Err(JsNativeError::range() - .with_message("requiredFields cannot be partial when any is false") + .with_message("Cannot have a duplicate field") .into()); } - - // 9. Return result. - Ok(result) + // d. Set previousProperty to property. } - /// Convert a `TemporalFields` struct into a `JsObject`. - pub(crate) fn as_object(&self, context: &mut Context) -> JsResult { - let obj = JsObject::with_null_proto(); - - for bit in self.bit_map.iter() { - match bit { - FieldMap::YEAR => { - obj.create_data_property_or_throw( - js_string!("year"), - self.year.map_or(JsValue::undefined(), JsValue::from), - context, - )?; - } - FieldMap::MONTH => { - obj.create_data_property_or_throw( - js_string!("month"), - self.month.map_or(JsValue::undefined(), JsValue::from), - context, - )?; - } - FieldMap::MONTH_CODE => { - obj.create_data_property_or_throw( - js_string!("monthCode"), - self.month_code - .as_ref() - .map_or(JsValue::undefined(), |f| f.clone().into()), - context, - )?; - } - FieldMap::DAY => { - obj.create_data_property( - js_string!("day"), - self.day().map_or(JsValue::undefined(), JsValue::from), - context, - )?; - } - FieldMap::HOUR => { - obj.create_data_property(js_string!("hour"), self.hour, context)?; - } - FieldMap::MINUTE => { - obj.create_data_property(js_string!("minute"), self.minute, context)?; - } - FieldMap::SECOND => { - obj.create_data_property_or_throw(js_string!("second"), self.second, context)?; - } - FieldMap::MILLISECOND => { - obj.create_data_property_or_throw( - js_string!("millisecond"), - self.millisecond, - context, - )?; - } - FieldMap::MICROSECOND => { - obj.create_data_property_or_throw( - js_string!("microsecond"), - self.microsecond, - context, - )?; - } - FieldMap::NANOSECOND => { - obj.create_data_property_or_throw( - js_string!("nanosecond"), - self.nanosecond, - context, - )?; - } - FieldMap::OFFSET => { - obj.create_data_property_or_throw( - js_string!("offset"), - self.offset - .as_ref() - .map_or(JsValue::undefined(), |s| s.clone().into()), - context, - )?; - } - FieldMap::ERA => { - obj.create_data_property_or_throw( - js_string!("era"), - self.era - .as_ref() - .map_or(JsValue::undefined(), |s| s.clone().into()), - context, - )?; - } - FieldMap::ERA_YEAR => { - obj.create_data_property_or_throw( - js_string!("eraYear"), - self.era_year.map_or(JsValue::undefined(), JsValue::from), - context, - )?; - } - FieldMap::TIME_ZONE => { - obj.create_data_property_or_throw( - js_string!("timeZone"), - self.time_zone - .as_ref() - .map_or(JsValue::undefined(), |s| s.clone().into()), - context, - )?; - } - _ => unreachable!(), - } - } - - Ok(obj) + // 8. If requiredFields is partial and any is false, then + if partial && !any { + // a. Throw a TypeError exception. + return Err(JsNativeError::range() + .with_message("requiredFields cannot be partial when any is false") + .into()); } - // Note placeholder until overflow is implemented on `ICU4x`'s Date. - /// A function to regulate the current `TemporalFields` according to the overflow value - pub(crate) fn regulate(&mut self, overflow: ArithmeticOverflow) -> JsResult<()> { - if let (Some(year), Some(month), Some(day)) = (self.year(), self.month(), self.day()) { - match overflow { - ArithmeticOverflow::Constrain => { - let m = month.clamp(1, 12); - let days_in_month = super::calendar::utils::iso_days_in_month(year, month); - let d = day.clamp(1, days_in_month); - - self.month = Some(m); - self.day = Some(d); - } - ArithmeticOverflow::Reject => { - return Err(JsNativeError::range() - .with_message("TemporalFields is out of a valid range.") - .into()) - } - } - } - Ok(()) - } + // 9. Return result. + Ok(result) +} - pub(crate) fn regulate_year_month(&mut self, overflow: ArithmeticOverflow) { - match self.month { - Some(month) if overflow == ArithmeticOverflow::Constrain => { - let m = month.clamp(1, 12); - self.month = Some(m); - } - _ => {} - } - } +impl JsObject { + pub(crate) fn from_temporal_fields( + fields: &TemporalFields, + context: &mut Context, + ) -> JsResult { + let obj = JsObject::with_null_proto(); - /// Resolve the month and monthCode on this `TemporalFields`. - pub(crate) fn iso_resolve_month(&mut self) -> JsResult<()> { - if self.month_code.is_none() { - if self.month.is_some() { - return Ok(()); - } + for (key, value) in fields.active_kvs() { + let js_value = match value { + FieldValue::Undefined => JsValue::undefined(), + FieldValue::Integer(x) => JsValue::Integer(x), + FieldValue::String(s) => JsValue::String(s.into()), + }; - return Err(JsNativeError::range() - .with_message("month and MonthCode values cannot both be undefined.") - .into()); + obj.create_data_property_or_throw(JsString::from(key), js_value, context)?; } - let unresolved_month_code = self - .month_code - .as_ref() - .expect("monthCode must exist at this point."); - - let month_code_integer = month_code_to_integer(unresolved_month_code)?; - - let new_month = match self.month { - Some(month) if month != month_code_integer => { - return Err(JsNativeError::range() - .with_message("month and monthCode cannot be resolved.") - .into()) - } - _ => month_code_integer, - }; - - self.month = Some(new_month); - - Ok(()) - } -} - -fn month_code_to_integer(mc: &JsString) -> JsResult { - match mc.to_std_string_escaped().as_str() { - "M01" => Ok(1), - "M02" => Ok(2), - "M03" => Ok(3), - "M04" => Ok(4), - "M05" => Ok(5), - "M06" => Ok(6), - "M07" => Ok(7), - "M08" => Ok(8), - "M09" => Ok(9), - "M10" => Ok(10), - "M11" => Ok(11), - "M12" => Ok(12), - "M13" => Ok(13), - _ => Err(JsNativeError::range() - .with_message("monthCode is not within the valid values.") - .into()), + Ok(obj) } } diff --git a/boa_engine/src/builtins/temporal/instant/mod.rs b/boa_engine/src/builtins/temporal/instant/mod.rs index 4d3e8b6e653..498496dd263 100644 --- a/boa_engine/src/builtins/temporal/instant/mod.rs +++ b/boa_engine/src/builtins/temporal/instant/mod.rs @@ -4,11 +4,8 @@ use crate::{ builtins::{ options::{get_option, get_options_object, RoundingMode}, - temporal::{ - duration::{DateDuration, DurationRecord, TimeDuration}, - options::{ - get_temporal_rounding_increment, get_temporal_unit, TemporalUnit, TemporalUnitGroup, - }, + temporal::options::{ + get_temporal_rounding_increment, get_temporal_unit, TemporalUnitGroup, }, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, }, @@ -21,8 +18,9 @@ use crate::{ Context, JsArgs, JsBigInt, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; use boa_profiler::Profiler; +use boa_temporal::{duration::Duration, options::TemporalUnit}; -use super::{duration, ns_max_instant, ns_min_instant, MIS_PER_DAY, MS_PER_DAY, NS_PER_DAY}; +use super::{ns_max_instant, ns_min_instant, MIS_PER_DAY, MS_PER_DAY, NS_PER_DAY}; const NANOSECONDS_PER_SECOND: i64 = 10_000_000_000; const NANOSECONDS_PER_MINUTE: i64 = 600_000_000_000; @@ -567,70 +565,41 @@ fn add_instant( fn diff_instant( ns1: &JsBigInt, ns2: &JsBigInt, - rounding_increment: f64, - smallest_unit: TemporalUnit, - largest_unit: TemporalUnit, - rounding_mode: RoundingMode, - context: &mut Context, -) -> JsResult { + _rounding_increment: f64, + _smallest_unit: TemporalUnit, + _largest_unit: TemporalUnit, + _rounding_mode: RoundingMode, + _context: &mut Context, +) -> JsResult { // 1. Let difference be ℝ(ns2) - ℝ(ns1). let difference = JsBigInt::sub(ns1, ns2); // 2. Let nanoseconds be remainder(difference, 1000). - let nanoseconds = JsBigInt::rem(&difference, &JsBigInt::from(1000)); + let _nanoseconds = JsBigInt::rem(&difference, &JsBigInt::from(1000)); // 3. Let microseconds be remainder(truncate(difference / 1000), 1000). let truncated_micro = JsBigInt::try_from((&difference.to_f64() / 1000_f64).trunc()) .map_err(|e| JsNativeError::typ().with_message(e.to_string()))?; - let microseconds = JsBigInt::rem(&truncated_micro, &JsBigInt::from(1000)); + let _microseconds = JsBigInt::rem(&truncated_micro, &JsBigInt::from(1000)); // 4. Let milliseconds be remainder(truncate(difference / 106), 1000). let truncated_milli = JsBigInt::try_from((&difference.to_f64() / 1_000_000_f64).trunc()) .map_err(|e| JsNativeError::typ().with_message(e.to_string()))?; - let milliseconds = JsBigInt::rem(&truncated_milli, &JsBigInt::from(1000)); + let _milliseconds = JsBigInt::rem(&truncated_milli, &JsBigInt::from(1000)); // 5. Let seconds be truncate(difference / 10^9). - let seconds = (&difference.to_f64() / 1_000_000_000_f64).trunc(); - - // Create TimeDuration - let mut time_duration = DurationRecord::from_day_and_time( - 0f64, - TimeDuration::new( - 0f64, - 0f64, - seconds, - milliseconds.to_f64(), - microseconds.to_f64(), - nanoseconds.to_f64(), - ), - ); + let _seconds = (&difference.to_f64() / 1_000_000_000_f64).trunc(); + // TODO: Update to new Temporal library // 6. If smallestUnit is "nanosecond" and roundingIncrement is 1, then - if smallest_unit == TemporalUnit::Nanosecond && (rounding_increment - 1f64).abs() < f64::EPSILON - { - // a. Return ! BalanceTimeDuration(0, 0, 0, seconds, milliseconds, microseconds, nanoseconds, largestUnit). - time_duration.balance_time_duration(largest_unit, None)?; - return Ok(time_duration); - } - + // a. Return ! BalanceTimeDuration(0, 0, 0, seconds, milliseconds, microseconds, nanoseconds, largestUnit). // 7. Let roundResult be ! RoundDuration(0, 0, 0, 0, 0, 0, seconds, milliseconds, microseconds, nanoseconds, roundingIncrement, smallestUnit, largestUnit, roundingMode). - - let (mut round_result, _total) = time_duration.round_duration( - DateDuration::default(), - rounding_increment, - smallest_unit, - rounding_mode, - (None, None, None), - context, - )?; - // 8. Assert: roundResult.[[Days]] is 0. - assert_eq!(round_result.days() as i32, 0); - // 9. Return ! BalanceTimeDuration(0, roundResult.[[Hours]], roundResult.[[Minutes]], // roundResult.[[Seconds]], roundResult.[[Milliseconds]], roundResult.[[Microseconds]], // roundResult.[[Nanoseconds]], largestUnit). - round_result.balance_time_duration(largest_unit, None)?; - Ok(round_result) + Err(JsNativeError::error() + .with_message("not yet implemented.") + .into()) } /// 8.5.8 `RoundTemporalInstant ( ns, increment, unit, roundingMode )` @@ -691,7 +660,7 @@ fn diff_temporal_instant( context: &mut Context, ) -> JsResult { // 1. If operation is since, let sign be -1. Otherwise, let sign be 1. - let sign = if op { 1_f64 } else { -1_f64 }; + let _sign = if op { 1_f64 } else { -1_f64 }; // 2. Set other to ? ToTemporalInstant(other). let other = to_temporal_instant(other)?; // 3. Let resolvedOptions be ? CopyOptions(options). @@ -710,7 +679,7 @@ fn diff_temporal_instant( )?; // 5. Let result be DifferenceInstant(instant.[[Nanoseconds]], other.[[Nanoseconds]], settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[LargestUnit]], settings.[[RoundingMode]]). - let result = diff_instant( + let _result = diff_instant( &instant.nanoseconds, &other.nanoseconds, settings.3, @@ -720,23 +689,9 @@ fn diff_temporal_instant( context, )?; + // TODO: diff_instant will error so this shouldn't run. + unimplemented!(); // 6. Return ! CreateTemporalDuration(0, 0, 0, 0, sign × result.[[Hours]], sign × result.[[Minutes]], sign × result.[[Seconds]], sign × result.[[Milliseconds]], sign × result.[[Microseconds]], sign × result.[[Nanoseconds]]). - Ok(duration::create_temporal_duration( - duration::DurationRecord::new( - DateDuration::default(), - TimeDuration::new( - sign * result.hours(), - sign * result.minutes(), - sign * result.seconds(), - sign * result.milliseconds(), - sign * result.microseconds(), - sign * result.nanoseconds(), - ), - ), - None, - context, - )? - .into()) } /// 8.5.11 `AddDurationToOrSubtractDurationFromInstant ( operation, instant, temporalDurationLike )` @@ -752,25 +707,25 @@ fn add_or_subtract_duration_from_instant( // 2. Let duration be ? ToTemporalDurationRecord(temporalDurationLike). let duration = super::to_temporal_duration_record(temporal_duration_like)?; // 3. If duration.[[Days]] is not 0, throw a RangeError exception. - if duration.days() != 0_f64 { + if duration.date().days() != 0_f64 { return Err(JsNativeError::range() .with_message("DurationDays cannot be 0") .into()); } // 4. If duration.[[Months]] is not 0, throw a RangeError exception. - if duration.months() != 0_f64 { + if duration.date().months() != 0_f64 { return Err(JsNativeError::range() .with_message("DurationMonths cannot be 0") .into()); } // 5. If duration.[[Weeks]] is not 0, throw a RangeError exception. - if duration.weeks() != 0_f64 { + if duration.date().weeks() != 0_f64 { return Err(JsNativeError::range() .with_message("DurationWeeks cannot be 0") .into()); } // 6. If duration.[[Years]] is not 0, throw a RangeError exception. - if duration.years() != 0_f64 { + if duration.date().years() != 0_f64 { return Err(JsNativeError::range() .with_message("DurationYears cannot be 0") .into()); @@ -780,12 +735,12 @@ fn add_or_subtract_duration_from_instant( // sign × duration.[[Microseconds]], sign × duration.[[Nanoseconds]]). let new = add_instant( &instant.nanoseconds, - sign * duration.hours() as i32, - sign * duration.minutes() as i32, - sign * duration.seconds() as i32, - sign * duration.milliseconds() as i32, - sign * duration.microseconds() as i32, - sign * duration.nanoseconds() as i32, + sign * duration.time().hours() as i32, + sign * duration.time().minutes() as i32, + sign * duration.time().seconds() as i32, + sign * duration.time().milliseconds() as i32, + sign * duration.time().microseconds() as i32, + sign * duration.time().nanoseconds() as i32, )?; // 8. Return ! CreateTemporalInstant(ns). create_temporal_instant(new, None, context) diff --git a/boa_engine/src/builtins/temporal/mod.rs b/boa_engine/src/builtins/temporal/mod.rs index ecc28d60199..a4955f35409 100644 --- a/boa_engine/src/builtins/temporal/mod.rs +++ b/boa_engine/src/builtins/temporal/mod.rs @@ -5,8 +5,8 @@ //! [spec]: https://tc39.es/proposal-temporal/ mod calendar; -mod date_equations; mod duration; +mod error; mod fields; mod instant; mod now; @@ -22,11 +22,7 @@ mod zoned_date_time; #[cfg(test)] mod tests; -pub(crate) use fields::TemporalFields; - -use self::options::{ - get_temporal_rounding_increment, get_temporal_unit, TemporalUnit, TemporalUnitGroup, -}; +use self::options::{get_temporal_rounding_increment, get_temporal_unit, TemporalUnitGroup}; pub use self::{ calendar::*, duration::*, instant::*, now::*, plain_date::*, plain_date_time::*, plain_month_day::*, plain_time::*, plain_year_month::*, time_zone::*, zoned_date_time::*, @@ -47,6 +43,7 @@ use crate::{ Context, JsBigInt, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; use boa_profiler::Profiler; +use boa_temporal::options::TemporalUnit; // Relavant numeric constants /// Nanoseconds per day constant: 8.64e+13 @@ -192,7 +189,7 @@ fn to_zero_padded_decimal_string(n: u64, min_length: usize) -> String { /// Abstract Operation 13.1 [`IteratorToListOfType`][proposal] /// /// [proposal]: https://tc39.es/proposal-temporal/#sec-iteratortolistoftype -pub(crate) fn iterator_to_list_of_types( +pub(crate) fn _iterator_to_list_of_types( iterator: &mut IteratorRecord, element_types: &[Type], context: &mut Context, @@ -229,7 +226,7 @@ pub(crate) fn iterator_to_list_of_types( // Note: implemented on IsoDateRecord. // Abstract Operation 13.3 `EpochDaysToEpochMs` -pub(crate) fn epoch_days_to_epoch_ms(day: i32, time: i32) -> f64 { +pub(crate) fn _epoch_days_to_epoch_ms(day: i32, time: i32) -> f64 { f64::from(day).mul_add(f64::from(MS_PER_DAY), f64::from(time)) } @@ -370,7 +367,7 @@ fn apply_unsigned_rounding_mode( } /// 13.28 `RoundNumberToIncrement ( x, increment, roundingMode )` -pub(crate) fn round_number_to_increment( +pub(crate) fn _round_number_to_increment( x: f64, increment: f64, rounding_mode: RoundingMode, diff --git a/boa_engine/src/builtins/temporal/options.rs b/boa_engine/src/builtins/temporal/options.rs index 92f1cf45f24..b881cdbbf7d 100644 --- a/boa_engine/src/builtins/temporal/options.rs +++ b/boa_engine/src/builtins/temporal/options.rs @@ -12,7 +12,9 @@ use crate::{ builtins::options::{get_option, ParsableOptionType}, js_string, Context, JsNativeError, JsObject, JsResult, }; -use std::{fmt, str::FromStr}; +use boa_temporal::options::{ + ArithmeticOverflow, DurationOverflow, InstantDisambiguation, OffsetDisambiguation, TemporalUnit, +}; // TODO: Expand docs on the below options. @@ -123,262 +125,9 @@ fn date_units() -> impl Iterator { fn datetime_units() -> impl Iterator { date_units().chain(time_units()) } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub(crate) enum TemporalUnit { - Auto = 0, - Nanosecond, - Microsecond, - Millisecond, - Second, - Minute, - Hour, - Day, - Week, - Month, - Year, -} - -impl TemporalUnit { - pub(crate) fn to_maximum_rounding_increment(self) -> Option { - use TemporalUnit::{ - Auto, Day, Hour, Microsecond, Millisecond, Minute, Month, Nanosecond, Second, Week, - Year, - }; - // 1. If unit is "year", "month", "week", or "day", then - // a. Return undefined. - // 2. If unit is "hour", then - // a. Return 24. - // 3. If unit is "minute" or "second", then - // a. Return 60. - // 4. Assert: unit is one of "millisecond", "microsecond", or "nanosecond". - // 5. Return 1000. - match self { - Year | Month | Week | Day => None, - Hour => Some(24), - Minute | Second => Some(60), - Millisecond | Microsecond | Nanosecond => Some(1000), - Auto => unreachable!(), - } - } -} - -#[derive(Debug)] -pub(crate) struct ParseTemporalUnitError; - -impl fmt::Display for ParseTemporalUnitError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("provided string was not a valid TemporalUnit") - } -} - -impl FromStr for TemporalUnit { - type Err = ParseTemporalUnitError; - - fn from_str(s: &str) -> Result { - match s { - "auto" => Ok(Self::Auto), - "year" | "years" => Ok(Self::Year), - "month" | "months" => Ok(Self::Month), - "week" | "weeks" => Ok(Self::Week), - "day" | "days" => Ok(Self::Day), - "hour" | "hours" => Ok(Self::Hour), - "minute" | "minutes" => Ok(Self::Minute), - "second" | "seconds" => Ok(Self::Second), - "millisecond" | "milliseconds" => Ok(Self::Millisecond), - "microsecond" | "microseconds" => Ok(Self::Microsecond), - "nanosecond" | "nanoseconds" => Ok(Self::Nanosecond), - _ => Err(ParseTemporalUnitError), - } - } -} impl ParsableOptionType for TemporalUnit {} - -impl fmt::Display for TemporalUnit { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Auto => "auto", - Self::Year => "constrain", - Self::Month => "month", - Self::Week => "week", - Self::Day => "day", - Self::Hour => "hour", - Self::Minute => "minute", - Self::Second => "second", - Self::Millisecond => "millsecond", - Self::Microsecond => "microsecond", - Self::Nanosecond => "nanosecond", - } - .fmt(f) - } -} - -/// `ArithmeticOverflow` can also be used as an -/// assignment overflow and consists of the "constrain" -/// and "reject" options. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) enum ArithmeticOverflow { - Constrain, - Reject, -} - -#[derive(Debug)] -pub(crate) struct ParseArithmeticOverflowError; - -impl fmt::Display for ParseArithmeticOverflowError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("provided string was not a valid overflow value") - } -} - -impl FromStr for ArithmeticOverflow { - type Err = ParseArithmeticOverflowError; - - fn from_str(s: &str) -> Result { - match s { - "constrain" => Ok(Self::Constrain), - "reject" => Ok(Self::Reject), - _ => Err(ParseArithmeticOverflowError), - } - } -} - impl ParsableOptionType for ArithmeticOverflow {} - -impl fmt::Display for ArithmeticOverflow { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Constrain => "constrain", - Self::Reject => "reject", - } - .fmt(f) - } -} - -/// `Duration` overflow options. -pub(crate) enum DurationOverflow { - Constrain, - Balance, -} - -#[derive(Debug)] -pub(crate) struct ParseDurationOverflowError; - -impl fmt::Display for ParseDurationOverflowError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("provided string was not a valid duration overflow value") - } -} - -impl FromStr for DurationOverflow { - type Err = ParseDurationOverflowError; - - fn from_str(s: &str) -> Result { - match s { - "constrain" => Ok(Self::Constrain), - "balance" => Ok(Self::Balance), - _ => Err(ParseDurationOverflowError), - } - } -} - impl ParsableOptionType for DurationOverflow {} - -impl fmt::Display for DurationOverflow { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Constrain => "constrain", - Self::Balance => "balance", - } - .fmt(f) - } -} - -/// The disambiguation options for an instant. -pub(crate) enum InstantDisambiguation { - Compatible, - Earlier, - Later, - Reject, -} - -#[derive(Debug)] -pub(crate) struct ParseInstantDisambiguationError; - -impl fmt::Display for ParseInstantDisambiguationError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("provided string was not a valid instant disambiguation value") - } -} -impl FromStr for InstantDisambiguation { - type Err = ParseInstantDisambiguationError; - - fn from_str(s: &str) -> Result { - match s { - "compatible" => Ok(Self::Compatible), - "earlier" => Ok(Self::Earlier), - "later" => Ok(Self::Later), - "reject" => Ok(Self::Reject), - _ => Err(ParseInstantDisambiguationError), - } - } -} - impl ParsableOptionType for InstantDisambiguation {} - -impl fmt::Display for InstantDisambiguation { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Compatible => "compatible", - Self::Earlier => "earlier", - Self::Later => "later", - Self::Reject => "reject", - } - .fmt(f) - } -} - -/// Offset disambiguation options. -pub(crate) enum OffsetDisambiguation { - Use, - Prefer, - Ignore, - Reject, -} - -#[derive(Debug)] -pub(crate) struct ParseOffsetDisambiguationError; - -impl fmt::Display for ParseOffsetDisambiguationError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("provided string was not a valid offset disambiguation value") - } -} - -impl FromStr for OffsetDisambiguation { - type Err = ParseOffsetDisambiguationError; - - fn from_str(s: &str) -> Result { - match s { - "use" => Ok(Self::Use), - "prefer" => Ok(Self::Prefer), - "ignore" => Ok(Self::Ignore), - "reject" => Ok(Self::Reject), - _ => Err(ParseOffsetDisambiguationError), - } - } -} - impl ParsableOptionType for OffsetDisambiguation {} - -impl fmt::Display for OffsetDisambiguation { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Use => "use", - Self::Prefer => "prefer", - Self::Ignore => "ignore", - Self::Reject => "reject", - } - .fmt(f) - } -} diff --git a/boa_engine/src/builtins/temporal/plain_date/iso.rs b/boa_engine/src/builtins/temporal/plain_date/iso.rs index 52eccc4fb13..895188f0ded 100644 --- a/boa_engine/src/builtins/temporal/plain_date/iso.rs +++ b/boa_engine/src/builtins/temporal/plain_date/iso.rs @@ -1,10 +1,11 @@ //! An `IsoDateRecord` that represents the `[[ISOYear]]`, `[[ISOMonth]]`, and `[[ISODay]]` internal slots. use crate::{ - builtins::temporal::{self, options::ArithmeticOverflow, DateDuration, TemporalFields}, + builtins::temporal::{self, DateDuration }, JsNativeError, JsResult, JsString, }; +use boa_temporal::options::ArithmeticOverflow; use icu_calendar::{Date, Iso}; // TODO: Move ISODateRecord to a more generalized location. diff --git a/boa_engine/src/builtins/temporal/plain_date/mod.rs b/boa_engine/src/builtins/temporal/plain_date/mod.rs index d43245cec46..531cb5af835 100644 --- a/boa_engine/src/builtins/temporal/plain_date/mod.rs +++ b/boa_engine/src/builtins/temporal/plain_date/mod.rs @@ -1,10 +1,11 @@ //! Boa's implementation of the ECMAScript `Temporal.PlainDate` builtin object. #![allow(dead_code, unused_variables)] +use std::str::FromStr; + use crate::{ builtins::{ options::{get_option, get_options_object}, - temporal::options::TemporalUnit, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, }, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, @@ -17,27 +18,24 @@ use crate::{ }; use boa_parser::temporal::{IsoCursor, TemporalDateTimeString}; use boa_profiler::Profiler; - -use super::{ - calendar, duration::DurationRecord, options::ArithmeticOverflow, - plain_date::iso::IsoDateRecord, plain_date_time, DateDuration, TimeDuration, +use boa_temporal::{ + calendar::{AvailableCalendars, CalendarSlot}, + date::TemporalDate as InnerDate, + datetime::TemporalDateTime, + options::ArithmeticOverflow, }; -pub(crate) mod iso; +use super::calendar; /// The `Temporal.PlainDate` object. #[derive(Debug, Clone)] pub struct PlainDate { - pub(crate) inner: IsoDateRecord, - pub(crate) calendar: JsValue, // Calendar can probably be stored as a JsObject. + pub(crate) inner: InnerDate, } impl PlainDate { - pub(crate) fn new(record: IsoDateRecord, calendar: JsValue) -> Self { - Self { - inner: record, - calendar, - } + pub(crate) fn new(inner: InnerDate) -> Self { + Self { inner } } } @@ -219,12 +217,18 @@ impl BuiltInConstructor for PlainDate { let iso_year = super::to_integer_with_truncation(args.get_or_undefined(0), context)?; let iso_month = super::to_integer_with_truncation(args.get_or_undefined(1), context)?; let iso_day = super::to_integer_with_truncation(args.get_or_undefined(2), context)?; - let default_calendar = JsValue::from(js_string!("iso8601")); - let calendar_like = args.get(3).unwrap_or(&default_calendar); + let calendar_slot = + calendar::to_temporal_calendar_slot_value(args.get_or_undefined(3), context)?; - let iso = IsoDateRecord::new(iso_year, iso_month, iso_day); + let date = InnerDate::new( + iso_year, + iso_month, + iso_day, + calendar_slot, + ArithmeticOverflow::Reject, + )?; - Ok(create_temporal_date(iso, calendar_like.clone(), Some(new_target), context)?.into()) + Ok(create_temporal_date(date, Some(new_target), context)?.into()) } } @@ -390,7 +394,7 @@ impl PlainDate { 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) + create_temporal_date(self.inner.clone(), None, context) } } @@ -399,24 +403,19 @@ impl PlainDate { /// 3.5.3 `CreateTemporalDate ( isoYear, isoMonth, isoDay, calendar [ , newTarget ] )` pub(crate) fn create_temporal_date( - iso_date: IsoDateRecord, - calendar: JsValue, + inner: InnerDate, new_target: Option<&JsValue>, context: &mut Context, ) -> JsResult { // 1. If IsValidISODate(isoYear, isoMonth, isoDay) is false, throw a RangeError exception. - if !iso_date.is_valid() { + if !inner.is_valid() { return Err(JsNativeError::range() .with_message("Date is not a valid ISO date.") .into()); }; - let iso_date_time = plain_date_time::iso::IsoDateTimeRecord::default() - .with_date(iso_date.year(), iso_date.month(), iso_date.day()) - .with_time(12, 0, 0, 0, 0, 0); - // 2. If ISODateTimeWithinLimits(isoYear, isoMonth, isoDay, 12, 0, 0, 0, 0, 0) is false, throw a RangeError exception. - if iso_date_time.is_valid() { + if !TemporalDateTime::validate_date(&inner) { return Err(JsNativeError::range() .with_message("Date is not within ISO date time limits.") .into()); @@ -443,10 +442,8 @@ pub(crate) fn create_temporal_date( // 6. Set object.[[ISOMonth]] to isoMonth. // 7. Set object.[[ISODay]] to isoDay. // 8. Set object.[[Calendar]] to calendar. - let obj = JsObject::from_proto_and_data( - prototype, - ObjectData::plain_date(PlainDate::new(iso_date, calendar)), - ); + let obj = + JsObject::from_proto_and_data(prototype, ObjectData::plain_date(PlainDate::new(inner))); // 9. Return object. Ok(obj) @@ -474,10 +471,7 @@ pub(crate) fn to_temporal_date( // i. Return item. let obj = object.borrow(); let date = obj.as_plain_date().expect("obj must be a PlainDate."); - return Ok(PlainDate { - inner: date.inner, - calendar: date.calendar.clone(), - }); + return Ok(PlainDate::new(date.inner.clone())); // b. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then } else if object.is_zoned_date_time() { return Err(JsNativeError::range() @@ -499,16 +493,11 @@ pub(crate) fn to_temporal_date( .as_plain_date_time() .expect("obj must be a PlainDateTime"); - let iso = date_time.inner.iso_date(); - let calendar = date_time.calendar.clone(); - + let date = InnerDate::from_datetime(date_time.inner()); drop(obj); // ii. Return ! CreateTemporalDate(item.[[ISOYear]], item.[[ISOMonth]], item.[[ISODay]], item.[[Calendar]]). - return Ok(PlainDate { - inner: iso, - calendar, - }); + return Ok(PlainDate::new(date)); } // d. Let calendar be ? GetTemporalCalendarSlotValueWithISODefault(item). @@ -521,140 +510,39 @@ pub(crate) fn to_temporal_date( } // 5. If item is not a String, throw a TypeError exception. - match item { - JsValue::String(s) => { - // 6. Let result be ? ParseTemporalDateString(item). - let result = TemporalDateTimeString::parse( - false, - &mut IsoCursor::new(&s.to_std_string_escaped()), - ) - .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; - - // 7. Assert: IsValidISODate(result.[[Year]], result.[[Month]], result.[[Day]]) is true. - // 8. Let calendar be result.[[Calendar]]. - // 9. If calendar is undefined, set calendar to "iso8601". - let identifier = result - .date - .calendar - .map_or_else(|| js_string!("iso8601"), JsString::from); - - // 10. If IsBuiltinCalendar(calendar) is false, throw a RangeError exception. - if !super::calendar::is_builtin_calendar(&identifier) { - return Err(JsNativeError::range() - .with_message("not a valid calendar identifier.") - .into()); - } - - // TODO: impl to ASCII-lowercase on JsStirng - // 11. Set calendar to the ASCII-lowercase of calendar. - - // 12. Perform ? ToTemporalOverflow(options). - let _result = get_option(&options_obj, utf16!("overflow"), context)? - .unwrap_or(ArithmeticOverflow::Constrain); - - // 13. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], calendar). - Ok(PlainDate { - inner: IsoDateRecord::new(result.date.year, result.date.month, result.date.day), - calendar: identifier.into(), - }) - } - _ => Err(JsNativeError::typ() + let JsValue::String(date_like_string) = item else { + return Err(JsNativeError::typ() .with_message("ToTemporalDate item must be an object or string.") - .into()), - } -} - -// 3.5.5. DifferenceIsoDate -// Implemented on IsoDateRecord. - -/// 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.8 IsValidIsoDate -// Implemented on IsoDateRecord. - -// 3.5.9 BalanceIsoDate -// Implemented on IsoDateRecord. - -// 3.5.12 AddISODate ( year, month, day, years, months, weeks, days, overflow ) -// Implemented on IsoDateRecord + .into()); + }; -/// 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())) + // 6. Let result be ? ParseTemporalDateString(item). + let result = TemporalDateTimeString::parse( + false, + &mut IsoCursor::new(&date_like_string.to_std_string_escaped()), + ) + .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; + + // 7. Assert: IsValidISODate(result.[[Year]], result.[[Month]], result.[[Day]]) is true. + // 8. Let calendar be result.[[Calendar]]. + // 9. If calendar is undefined, set calendar to "iso8601". + let identifier = result.date.calendar.unwrap_or("iso8601".to_string()); + + // 10. If IsBuiltinCalendar(calendar) is false, throw a RangeError exception. + let _ = AvailableCalendars::from_str(identifier.to_ascii_lowercase().as_str())?; + + // 11. Set calendar to the ASCII-lowercase of calendar. + let calendar = CalendarSlot::Identifier(identifier.to_ascii_lowercase()); + + // 12. Perform ? ToTemporalOverflow(options). + let _ = get_option::(&options_obj, utf16!("overflow"), context)?; + + // 13. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], calendar). + Ok(PlainDate::new(InnerDate::new( + result.date.year, + result.date.month, + result.date.day, + calendar, + ArithmeticOverflow::Reject, + )?)) } diff --git a/boa_engine/src/builtins/temporal/plain_date_time/mod.rs b/boa_engine/src/builtins/temporal/plain_date_time/mod.rs index 83aa5b4c80b..805015c3bde 100644 --- a/boa_engine/src/builtins/temporal/plain_date_time/mod.rs +++ b/boa_engine/src/builtins/temporal/plain_date_time/mod.rs @@ -11,15 +11,22 @@ use crate::{ }; use boa_profiler::Profiler; -use self::iso::IsoDateTimeRecord; - -pub(crate) mod iso; +use boa_temporal::datetime::TemporalDateTime as InnerDateTime; /// The `Temporal.PlainDateTime` object. #[derive(Debug, Clone)] pub struct PlainDateTime { - pub(crate) inner: IsoDateTimeRecord, - pub(crate) calendar: JsValue, + pub(crate) inner: InnerDateTime, +} + +impl PlainDateTime { + fn new(inner: InnerDateTime) -> Self { + Self { inner } + } + + pub(crate) fn inner(&self) -> &InnerDateTime { + &self.inner + } } impl BuiltInObject for PlainDateTime { diff --git a/boa_engine/src/builtins/temporal/plain_month_day/mod.rs b/boa_engine/src/builtins/temporal/plain_month_day/mod.rs index 4571914311c..01fbe38c5f2 100644 --- a/boa_engine/src/builtins/temporal/plain_month_day/mod.rs +++ b/boa_engine/src/builtins/temporal/plain_month_day/mod.rs @@ -11,13 +11,18 @@ use crate::{ }; use boa_profiler::Profiler; -use super::{plain_date::iso::IsoDateRecord, plain_date_time::iso::IsoDateTimeRecord}; +use boa_temporal::{datetime::TemporalDateTime, month_day::TemporalMonthDay as InnerMonthDay}; /// The `Temporal.PlainMonthDay` object. #[derive(Debug, Clone)] pub struct PlainMonthDay { - pub(crate) inner: IsoDateRecord, - pub(crate) calendar: JsValue, + pub(crate) inner: InnerMonthDay, +} + +impl PlainMonthDay { + fn new(inner: InnerMonthDay) -> Self { + Self { inner } + } } impl BuiltInObject for PlainMonthDay { @@ -62,24 +67,13 @@ impl BuiltInConstructor for PlainMonthDay { // ==== `PlainMonthDay` Abstract Operations ==== pub(crate) fn create_temporal_month_day( - iso: IsoDateRecord, - calendar: JsValue, + inner: InnerMonthDay, new_target: Option<&JsValue>, context: &mut Context, ) -> JsResult { // 1. If IsValidISODate(referenceISOYear, isoMonth, isoDay) is false, throw a RangeError exception. - if iso.is_valid() { - return Err(JsNativeError::range() - .with_message("PlainMonthDay is not a valid ISO date.") - .into()); - } - // 2. If ISODateTimeWithinLimits(referenceISOYear, isoMonth, isoDay, 12, 0, 0, 0, 0, 0) is false, throw a RangeError exception. - let iso_date_time = IsoDateTimeRecord::default() - .with_date(iso.year(), iso.month(), iso.day()) - .with_time(12, 0, 0, 0, 0, 0); - - if !iso_date_time.is_valid() { + if TemporalDateTime::validate_month_day(&inner) { return Err(JsNativeError::range() .with_message("PlainMonthDay is not a valid ISO date time.") .into()); @@ -111,10 +105,7 @@ pub(crate) fn create_temporal_month_day( // 8. Set object.[[ISOYear]] to referenceISOYear. let obj = JsObject::from_proto_and_data( proto, - ObjectData::plain_month_day(PlainMonthDay { - inner: iso, - calendar, - }), + ObjectData::plain_month_day(PlainMonthDay::new(inner)), ); // 9. Return object. diff --git a/boa_engine/src/builtins/temporal/plain_year_month/mod.rs b/boa_engine/src/builtins/temporal/plain_year_month/mod.rs index 885cf1a8171..0c7beb85217 100644 --- a/boa_engine/src/builtins/temporal/plain_year_month/mod.rs +++ b/boa_engine/src/builtins/temporal/plain_year_month/mod.rs @@ -12,13 +12,19 @@ use crate::{ }; use boa_profiler::Profiler; -use super::plain_date::iso::IsoDateRecord; +use super::calendar::to_temporal_calendar_slot_value; +use boa_temporal::{options::ArithmeticOverflow, year_month::TemporalYearMonth as InnerYearMonth}; /// The `Temporal.PlainYearMonth` object. #[derive(Debug, Clone)] pub struct PlainYearMonth { - pub(crate) inner: IsoDateRecord, - pub(crate) calendar: JsValue, + pub(crate) inner: InnerYearMonth, +} + +impl PlainYearMonth { + pub(crate) fn new(inner: InnerYearMonth) -> Self { + Self { inner } + } } impl BuiltInObject for PlainYearMonth { @@ -152,18 +158,13 @@ impl BuiltInConstructor for PlainYearMonth { let y = super::to_integer_with_truncation(args.get_or_undefined(0), context)?; // 4. Let m be ? ToIntegerWithTruncation(isoMonth). let m = super::to_integer_with_truncation(args.get_or_undefined(1), context)?; - - // TODO: calendar handling. // 5. Let calendar be ? ToTemporalCalendarSlotValue(calendarLike, "iso8601"). + let calendar = to_temporal_calendar_slot_value(args.get_or_undefined(2), context)?; // 7. Return ? CreateTemporalYearMonth(y, m, calendar, ref, NewTarget). - let record = IsoDateRecord::new(y, m, ref_day); - create_temporal_year_month( - record, - JsValue::from(js_string!("iso8601")), - Some(new_target), - context, - ) + let inner = InnerYearMonth::new(y, m, calendar, ArithmeticOverflow::Reject)?; + + create_temporal_year_month(inner, Some(new_target), context) } } @@ -266,24 +267,12 @@ impl PlainYearMonth { // 9.5.5 `CreateTemporalYearMonth ( isoYear, isoMonth, calendar, referenceISODay [ , newTarget ] )` pub(crate) fn create_temporal_year_month( - year_month_record: IsoDateRecord, - calendar: JsValue, + ym: InnerYearMonth, new_target: Option<&JsValue>, context: &mut Context, ) -> JsResult { // 1. If IsValidISODate(isoYear, isoMonth, referenceISODay) is false, throw a RangeError exception. - if !year_month_record.is_valid() { - return Err(JsNativeError::range() - .with_message("PlainYearMonth values are not a valid ISO date.") - .into()); - } - // 2. If ! ISOYearMonthWithinLimits(isoYear, isoMonth) is false, throw a RangeError exception. - if year_month_record.within_year_month_limits() { - return Err(JsNativeError::range() - .with_message("PlainYearMonth values are not a valid ISO date.") - .into()); - } // 3. If newTarget is not present, set newTarget to %Temporal.PlainYearMonth%. let new_target = if let Some(target) = new_target { @@ -310,13 +299,8 @@ pub(crate) fn create_temporal_year_month( // 7. Set object.[[Calendar]] to calendar. // 8. Set object.[[ISODay]] to referenceISODay. - let obj = JsObject::from_proto_and_data( - proto, - ObjectData::plain_year_month(PlainYearMonth { - inner: year_month_record, - calendar, - }), - ); + let obj = + JsObject::from_proto_and_data(proto, ObjectData::plain_year_month(PlainYearMonth::new(ym))); // 9. Return object. Ok(obj.into()) diff --git a/boa_engine/src/builtins/temporal/tests.rs b/boa_engine/src/builtins/temporal/tests.rs index 5cd24b57f4b..58dde9b608e 100644 --- a/boa_engine/src/builtins/temporal/tests.rs +++ b/boa_engine/src/builtins/temporal/tests.rs @@ -1,4 +1,3 @@ -use super::date_equations::{epoch_time_to_month_in_year, mathematical_in_leap_year}; use crate::{js_string, run_test_actions, JsValue, TestAction}; // Temporal Object tests. @@ -34,19 +33,3 @@ fn now_object() { } // Date Equations - -#[test] -fn time_to_month() { - let oct_2023 = 1_696_459_917_000_f64; - let mar_1_2020 = 1_583_020_800_000_f64; - let feb_29_2020 = 1_582_934_400_000_f64; - let mar_1_2021 = 1_614_556_800_000_f64; - - assert_eq!(epoch_time_to_month_in_year(oct_2023), 9); - assert_eq!(epoch_time_to_month_in_year(mar_1_2020), 2); - assert_eq!(mathematical_in_leap_year(mar_1_2020), 1); - assert_eq!(epoch_time_to_month_in_year(feb_29_2020), 1); - assert_eq!(mathematical_in_leap_year(feb_29_2020), 1); - assert_eq!(epoch_time_to_month_in_year(mar_1_2021), 2); - assert_eq!(mathematical_in_leap_year(mar_1_2021), 0); -} diff --git a/boa_engine/src/builtins/temporal/zoned_date_time/mod.rs b/boa_engine/src/builtins/temporal/zoned_date_time/mod.rs index 98be2226e09..8988c6e79e5 100644 --- a/boa_engine/src/builtins/temporal/zoned_date_time/mod.rs +++ b/boa_engine/src/builtins/temporal/zoned_date_time/mod.rs @@ -8,6 +8,7 @@ use crate::{ Context, JsBigInt, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; use boa_profiler::Profiler; +use boa_temporal::duration::Duration as TemporalDuration; /// The `Temporal.ZonedDateTime` object. #[derive(Debug, Clone)] @@ -64,7 +65,7 @@ pub(crate) fn add_zoned_date_time( epoch_nanos: &JsBigInt, time_zone: &JsObject, calendar: &JsObject, - duration: super::duration::DurationRecord, + duration: TemporalDuration, options: Option<&JsObject>, ) -> JsResult { // 1. If options is not present, set options to undefined. diff --git a/boa_temporal/Cargo.toml b/boa_temporal/Cargo.toml new file mode 100644 index 00000000000..b93d0f3bf45 --- /dev/null +++ b/boa_temporal/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "boa_temporal" +keywords = ["javascript", "js", "compiler", "temporal", "calendar", "date", "time"] +categories = ["date", "time", "calendars"] +readme = "./README.md" +description.workspace = true +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + + +[dependencies] +icu_calendar = { version = "~1.3.2", default-features = false } +tinystr = "0.7.4" +bitflags.workspace = true +rustc-hash = { workspace = true, features = ["std"] } +num-bigint = { workspace = true, features = ["serde"] } +num-traits.workspace = true diff --git a/boa_temporal/README.md b/boa_temporal/README.md new file mode 100644 index 00000000000..4fc79874203 --- /dev/null +++ b/boa_temporal/README.md @@ -0,0 +1,5 @@ + +# Temporal Crate + +The intended goal of this crate is to provide an engine agnostic +implementation of `ECMAScript`'s Temporal algorithms. diff --git a/boa_temporal/src/calendar.rs b/boa_temporal/src/calendar.rs new file mode 100644 index 00000000000..b2892bce818 --- /dev/null +++ b/boa_temporal/src/calendar.rs @@ -0,0 +1,478 @@ +//! Temporal calendar traits and implementations. +//! +//! The goal of the calendar module of `boa_temporal` is to provide +//! Temporal compatible calendar implementations. +//! +//! The implementation will only be of calendar's prexisting calendars. That is to say, +//! it is up to implementers to implement an engine specific impl for handling JavaScript +//! objects that may return true on `ImplementsCalendarProtocol`. + +use std::str::FromStr; + +use crate::{ + date::TemporalDate, + datetime::TemporalDateTime, + duration::Duration, + fields::TemporalFields, + iso::IsoDate, + month_day::TemporalMonthDay, + options::{ArithmeticOverflow, TemporalUnit}, + year_month::TemporalYearMonth, + TemporalError, TemporalResult, +}; + +use tinystr::{TinyStr4, TinyStr8}; + +use self::iso::IsoCalendar; + +pub mod iso; + +/// The ECMAScript defined protocol methods +pub const CALENDAR_PROTOCOL_METHODS: [&str; 21] = [ + "dateAdd", + "dateFromFields", + "dateUntil", + "day", + "dayOfWeek", + "dayOfYear", + "daysInMonth", + "daysInWeek", + "daysInYear", + "fields", + "id", + "inLeapYear", + "mergeFields", + "month", + "monthCode", + "monthDayFromFields", + "monthsInYear", + "weekOfYear", + "year", + "yearMonthFromFields", + "yearOfWeek", +]; + +/// Designate the type of `CalendarFields` needed +#[derive(Debug, Clone, Copy)] +pub enum CalendarFieldsType { + /// Whether the Fields should return for a Date. + Date, + /// Whether the Fields should return for a YearMonth. + YearMonth, + /// Whether the Fields should return for a MonthDay. + MonthDay, +} + +// TODO: Optimize to TinyStr or &str. +impl From<&[String]> for CalendarFieldsType { + fn from(value: &[String]) -> Self { + let year_present = value.contains(&"year".to_string()); + let day_present = value.contains(&"day".to_string()); + + if year_present && day_present { + CalendarFieldsType::Date + } else if year_present { + CalendarFieldsType::YearMonth + } else { + CalendarFieldsType::MonthDay + } + } +} + +/// AvailableCalendars lists the currently implemented `CalendarProtocols` +#[derive(Debug, Clone, Copy)] +pub enum AvailableCalendars { + /// The ISO8601 calendar. + Iso, +} + +// NOTE: Should `s` be forced to lowercase or should the user be expected to provide the lowercase. +impl FromStr for AvailableCalendars { + type Err = TemporalError; + fn from_str(s: &str) -> Result { + match s { + "iso8601" => Ok(Self::Iso), + _ => { + Err(TemporalError::range().with_message("CalendarId is not an available Calendar")) + } + } + } +} + +impl AvailableCalendars { + /// Returns the `CalendarProtocol` for the `AvailableCalendar` + pub fn to_protocol(&self) -> Box { + match self { + Self::Iso => Box::new(IsoCalendar), + } + } +} + +/// The `DateLike` objects that can be provided to the CalendarProtocol. +#[derive(Debug)] +pub enum CalendarDateLike { + /// Represents a `Date` datelike + Date(TemporalDate), + /// Represents a `DateTime` datelike + DateTime(TemporalDateTime), + /// Represents a `YearMonth` datelike + YearMonth(TemporalYearMonth), + /// Represents a `MonthDay` datelike + MonthDay(TemporalMonthDay), +} + +impl CalendarDateLike { + /// Retrieves the internal `IsoDate` field. + pub fn as_iso_date(&self) -> IsoDate { + match self { + CalendarDateLike::Date(d) => d.iso_date(), + CalendarDateLike::DateTime(dt) => dt.iso_date(), + CalendarDateLike::MonthDay(md) => md.iso_date(), + CalendarDateLike::YearMonth(ym) => ym.iso_date(), + } + } +} + +// ==== CalendarProtocol trait ==== + +/// The `CalendarProtocol`'s Clone supertrait. +pub trait CalendarProtocolClone { + /// Clone's the current `CalendarProtocol` + fn clone_box(&self) -> Box; +} + +impl

CalendarProtocolClone for P +where + P: 'static + CalendarProtocol + Clone, +{ + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} + +// TODO: Determine how many methods actually need the context on them while using +// `icu_calendar`. +// +// NOTE (re above's TODO): Most likely context is only going to be needed for `dateFromFields`, +// `yearMonthFromFields`, `monthDayFromFields`, `dateAdd`, and `dateUntil`. +/// A trait for implementing a Builtin Calendar's Calendar Protocol in Rust. +pub trait CalendarProtocol: CalendarProtocolClone { + /// Creates a `Temporal.PlainDate` object from provided fields. + fn date_from_fields( + &self, + fields: &mut TemporalFields, + overflow: ArithmeticOverflow, + ) -> TemporalResult; + /// Creates a `Temporal.PlainYearMonth` object from the provided fields. + fn year_month_from_fields( + &self, + fields: &mut TemporalFields, + overflow: ArithmeticOverflow, + ) -> TemporalResult; + /// Creates a `Temporal.PlainMonthDay` object from the provided fields. + fn month_day_from_fields( + &self, + fields: &mut TemporalFields, + overflow: ArithmeticOverflow, + ) -> TemporalResult; + /// Returns a `Temporal.PlainDate` based off an added date. + fn date_add( + &self, + date: &TemporalDate, + duration: &Duration, + overflow: ArithmeticOverflow, + ) -> TemporalResult; + /// Returns a `Temporal.Duration` representing the duration between two dates. + fn date_until( + &self, + one: &TemporalDate, + two: &TemporalDate, + largest_unit: TemporalUnit, + ) -> TemporalResult; + /// Returns the era for a given `temporaldatelike`. + fn era(&self, date_like: &CalendarDateLike) -> TemporalResult>; + /// Returns the era year for a given `temporaldatelike` + fn era_year(&self, date_like: &CalendarDateLike) -> TemporalResult>; + /// Returns the `year` for a given `temporaldatelike` + fn year(&self, date_like: &CalendarDateLike) -> TemporalResult; + /// Returns the `month` for a given `temporaldatelike` + fn month(&self, date_like: &CalendarDateLike) -> TemporalResult; + // 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: &CalendarDateLike) -> TemporalResult; + /// Returns the `day` for a given `temporaldatelike` + fn day(&self, date_like: &CalendarDateLike) -> TemporalResult; + /// Returns a value representing the day of the week for a date. + fn day_of_week(&self, date_like: &CalendarDateLike) -> TemporalResult; + /// Returns a value representing the day of the year for a given calendar. + fn day_of_year(&self, date_like: &CalendarDateLike) -> TemporalResult; + /// Returns a value representing the week of the year for a given calendar. + fn week_of_year(&self, date_like: &CalendarDateLike) -> TemporalResult; + /// Returns the year of a given week. + fn year_of_week(&self, date_like: &CalendarDateLike) -> TemporalResult; + /// Returns the days in a week for a given calendar. + fn days_in_week(&self, date_like: &CalendarDateLike) -> TemporalResult; + /// Returns the days in a month for a given calendar. + fn days_in_month(&self, date_like: &CalendarDateLike) -> TemporalResult; + /// Returns the days in a year for a given calendar. + fn days_in_year(&self, date_like: &CalendarDateLike) -> TemporalResult; + /// Returns the months in a year for a given calendar. + fn months_in_year(&self, date_like: &CalendarDateLike) -> TemporalResult; + /// Returns whether a value is within a leap year according to the designated calendar. + fn in_leap_year(&self, date_like: &CalendarDateLike) -> TemporalResult; + /// Resolve the `TemporalFields` for the implemented Calendar + fn resolve_fields( + &self, + fields: &mut TemporalFields, + r#type: CalendarFieldsType, + ) -> TemporalResult<()>; + /// Return this calendar's a fieldName and whether it is required depending on type (date, day-month). + fn field_descriptors(&self, r#type: CalendarFieldsType) -> Vec<(String, bool)>; + /// Return the fields to ignore for this Calendar based on provided keys. + fn field_keys_to_ignore(&self, additional_keys: Vec) -> Vec; + /// Debug name + fn identifier(&self) -> TemporalResult; +} + +impl core::fmt::Debug for dyn CalendarProtocol { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self.identifier().unwrap_or_default().as_str()) + } +} + +/// The [[Calendar]] field slot of a Temporal Object. +#[derive(Debug)] +pub enum CalendarSlot { + /// The calendar identifier string. + Identifier(String), + /// A `CalendarProtocol` implementation. + Protocol(Box), +} + +impl Clone for CalendarSlot { + fn clone(&self) -> Self { + match self { + Self::Identifier(s) => Self::Identifier(s.clone()), + Self::Protocol(b) => Self::Protocol(b.clone_box()), + } + } +} + +impl Clone for Box { + fn clone(&self) -> Self { + self.clone_box() + } +} + +impl Default for CalendarSlot { + fn default() -> Self { + Self::Identifier("iso8601".to_owned()) + } +} + +impl CalendarSlot { + /// `CalendarDateAdd` + /// + /// TODO: More Docs + pub fn date_add( + &self, + date: &TemporalDate, + duration: &Duration, + overflow: ArithmeticOverflow, + ) -> TemporalResult { + match self { + Self::Identifier(id) => { + let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); + protocol.date_add(date, duration, overflow) + } + Self::Protocol(protocol) => protocol.date_add(date, duration, overflow), + } + } + + /// `CalendarDateUntil` + /// + /// TODO: More Docs + pub fn date_until( + &self, + one: &TemporalDate, + two: &TemporalDate, + largest_unit: TemporalUnit, + ) -> TemporalResult { + match self { + Self::Identifier(id) => { + let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); + protocol.date_until(one, two, largest_unit) + } + Self::Protocol(protocol) => protocol.date_until(one, two, largest_unit), + } + } + + /// `CalendarYear` + /// + /// TODO: More docs. + pub fn year(&self, date_like: &CalendarDateLike) -> TemporalResult { + match self { + Self::Identifier(id) => { + let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); + protocol.year(date_like) + } + Self::Protocol(protocol) => protocol.year(date_like), + } + } + + /// `CalendarMonth` + /// + /// TODO: More docs. + pub fn month(&self, date_like: &CalendarDateLike) -> TemporalResult { + match self { + Self::Identifier(id) => { + let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); + protocol.month(date_like) + } + Self::Protocol(protocol) => protocol.month(date_like), + } + } + + /// `CalendarMonthCode` + /// + /// TODO: More docs. + pub fn month_code(&self, date_like: &CalendarDateLike) -> TemporalResult { + match self { + Self::Identifier(id) => { + let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); + protocol.month_code(date_like) + } + Self::Protocol(protocol) => protocol.month_code(date_like), + } + } + + /// `CalendarDay` + /// + /// TODO: More docs. + pub fn day(&self, date_like: &CalendarDateLike) -> TemporalResult { + match self { + Self::Identifier(id) => { + let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); + protocol.day(date_like) + } + Self::Protocol(protocol) => protocol.day(date_like), + } + } + + /// `CalendarDayOfWeek` + /// + /// TODO: More docs. + pub fn day_of_week(&self, date_like: &CalendarDateLike) -> TemporalResult { + match self { + Self::Identifier(id) => { + let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); + protocol.day_of_week(date_like) + } + Self::Protocol(protocol) => protocol.day_of_week(date_like), + } + } + + /// `CalendarDayOfYear` + /// + /// TODO: More docs. + pub fn day_of_year(&self, date_like: &CalendarDateLike) -> TemporalResult { + match self { + Self::Identifier(id) => { + let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); + protocol.day_of_year(date_like) + } + Self::Protocol(protocol) => protocol.day_of_year(date_like), + } + } + + /// `CalendarWeekOfYear` + /// + /// TODO: More docs. + pub fn week_of_year(&self, date_like: &CalendarDateLike) -> TemporalResult { + match self { + Self::Identifier(id) => { + let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); + protocol.week_of_year(date_like) + } + Self::Protocol(protocol) => protocol.week_of_year(date_like), + } + } + + /// `CalendarYearOfWeek` + /// + /// TODO: More docs. + pub fn year_of_week(&self, date_like: &CalendarDateLike) -> TemporalResult { + match self { + Self::Identifier(id) => { + let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); + protocol.year_of_week(date_like) + } + Self::Protocol(protocol) => protocol.year_of_week(date_like), + } + } + + /// `CalendarDaysInWeek` + /// + /// TODO: More docs. + pub fn days_in_week(&self, date_like: &CalendarDateLike) -> TemporalResult { + match self { + Self::Identifier(id) => { + let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); + protocol.days_in_week(date_like) + } + Self::Protocol(protocol) => protocol.days_in_week(date_like), + } + } + + /// `CalendarDaysInMonth` + /// + /// TODO: More docs. + pub fn days_in_month(&self, date_like: &CalendarDateLike) -> TemporalResult { + match self { + Self::Identifier(id) => { + let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); + protocol.days_in_month(date_like) + } + Self::Protocol(protocol) => protocol.days_in_month(date_like), + } + } + + /// `CalendarDaysInYear` + /// + /// TODO: More docs. + pub fn days_in_year(&self, date_like: &CalendarDateLike) -> TemporalResult { + match self { + Self::Identifier(id) => { + let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); + protocol.days_in_year(date_like) + } + Self::Protocol(protocol) => protocol.days_in_year(date_like), + } + } + + /// `CalendarMonthsInYear` + /// + /// TODO: More docs. + pub fn months_in_year(&self, date_like: &CalendarDateLike) -> TemporalResult { + match self { + Self::Identifier(id) => { + let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); + protocol.months_in_year(date_like) + } + Self::Protocol(protocol) => protocol.months_in_year(date_like), + } + } + + /// `CalendarInLeapYear` + /// + /// TODO: More docs. + pub fn in_leap_year(&self, date_like: &CalendarDateLike) -> TemporalResult { + match self { + Self::Identifier(id) => { + let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); + protocol.in_leap_year(date_like) + } + Self::Protocol(protocol) => protocol.in_leap_year(date_like), + } + } +} diff --git a/boa_temporal/src/calendar/iso.rs b/boa_temporal/src/calendar/iso.rs new file mode 100644 index 00000000000..cb02415262f --- /dev/null +++ b/boa_temporal/src/calendar/iso.rs @@ -0,0 +1,274 @@ +//! Implementation of the "iso8601" calendar. + +use crate::{ + date::TemporalDate, + duration::Duration, + error::TemporalError, + fields::TemporalFields, + month_day::TemporalMonthDay, + options::{ArithmeticOverflow, TemporalUnit}, + utils, + year_month::TemporalYearMonth, + TemporalResult, +}; + +use tinystr::{tinystr, TinyStr4, TinyStr8}; + +use super::{CalendarDateLike, CalendarFieldsType, CalendarProtocol, CalendarSlot}; + +use icu_calendar::week::{RelativeUnit, WeekCalculator}; + +/// This represents the implementation of the `ISO8601` +/// calendar for Temporal. +#[derive(Debug, Clone, Copy)] +pub struct IsoCalendar; + +impl CalendarProtocol for IsoCalendar { + /// Temporal 15.8.2.1 `Temporal.prototype.dateFromFields( fields [, options])` - Supercedes 12.5.4 + /// + /// This is a basic implementation for an iso8601 calendar's `dateFromFields` method. + fn date_from_fields( + &self, + fields: &mut TemporalFields, + overflow: ArithmeticOverflow, + ) -> TemporalResult { + // NOTE: we are in ISO by default here. + // a. Perform ? ISOResolveMonth(fields). + // b. Let result be ? ISODateFromFields(fields, overflow). + fields.iso_resolve_month()?; + + // 9. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], "iso8601"). + TemporalDate::new( + fields.year().unwrap_or(0), + fields.month().unwrap_or(0), + fields.day().unwrap_or(0), + CalendarSlot::Identifier("iso8601".to_string()), + overflow, + ) + } + + /// 12.5.5 `Temporal.Calendar.prototype.yearMonthFromFields ( fields [ , options ] )` + /// + /// This is a basic implementation for an iso8601 calendar's `yearMonthFromFields` method. + fn year_month_from_fields( + &self, + fields: &mut TemporalFields, + overflow: ArithmeticOverflow, + ) -> TemporalResult { + // 9. If calendar.[[Identifier]] is "iso8601", then + // a. Perform ? ISOResolveMonth(fields). + fields.iso_resolve_month()?; + + // TODO: Do we even need ISOYearMonthFromFields? YearMonth would should pass as a valid date + // b. Let result be ? ISOYearMonthFromFields(fields, overflow). + // 10. Return ? CreateTemporalYearMonth(result.[[Year]], result.[[Month]], "iso8601", result.[[ReferenceISODay]]). + TemporalYearMonth::new( + fields.year().unwrap_or(0), + fields.month().unwrap_or(0), + CalendarSlot::Identifier("iso8601".to_string()), + overflow, + ) + } + + /// 12.5.6 `Temporal.Calendar.prototype.monthDayFromFields ( fields [ , options ] )` + /// + /// This is a basic implementation for an iso8601 calendar's `monthDayFromFields` method. + fn month_day_from_fields( + &self, + fields: &mut TemporalFields, + overflow: ArithmeticOverflow, + ) -> TemporalResult { + // 8. Perform ? ISOResolveMonth(fields). + fields.iso_resolve_month()?; + + // TODO: double check error mapping is correct for specifcation/test262. + // 9. Let result be ? ISOMonthDayFromFields(fields, overflow). + // 10. Return ? CreateTemporalMonthDay(result.[[Month]], result.[[Day]], "iso8601", result.[[ReferenceISOYear]]). + TemporalMonthDay::new( + fields.month().unwrap_or(0), + fields.month().unwrap_or(0), + CalendarSlot::Identifier("iso8601".to_string()), + overflow, + ) + } + + /// 12.5.7 `Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] )` + /// + /// Below implements the basic implementation for an iso8601 calendar's `dateAdd` method. + fn date_add( + &self, + _date: &TemporalDate, + _duration: &Duration, + _overflow: ArithmeticOverflow, + ) -> TemporalResult { + // TODO: Not stable on `ICU4X`. Implement once completed. + Err(TemporalError::range() + .with_message("feature not implemented.") + .into()) + + // 9. Let result be ? AddISODate(date.[[ISOYear]], date.[[ISOMonth]], date.[[ISODay]], duration.[[Years]], duration.[[Months]], duration.[[Weeks]], balanceResult.[[Days]], overflow). + // 10. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], "iso8601"). + } + + /// 12.5.8 `Temporal.Calendar.prototype.dateUntil ( one, two [ , options ] )` + /// + /// Below implements the basic implementation for an iso8601 calendar's `dateUntil` method. + fn date_until( + &self, + _one: &TemporalDate, + _two: &TemporalDate, + _largest_unit: TemporalUnit, + ) -> TemporalResult { + // TODO: Not stable on `ICU4X`. Implement once completed. + Err(TemporalError::range() + .with_message("Feature not yet implemented.") + .into()) + + // 9. Let result be DifferenceISODate(one.[[ISOYear]], one.[[ISOMonth]], one.[[ISODay]], two.[[ISOYear]], two.[[ISOMonth]], two.[[ISODay]], largestUnit). + // 10. Return ! CreateTemporalDuration(result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], 0, 0, 0, 0, 0, 0). + } + + /// `Temporal.Calendar.prototype.era( dateLike )` for iso8601 calendar. + fn era(&self, _: &CalendarDateLike) -> TemporalResult> { + // Returns undefined on iso8601. + Ok(None) + } + + /// `Temporal.Calendar.prototype.eraYear( dateLike )` for iso8601 calendar. + fn era_year(&self, _: &CalendarDateLike) -> TemporalResult> { + // Returns undefined on iso8601. + Ok(None) + } + + /// Returns the `year` for the `Iso` calendar. + fn year(&self, date_like: &CalendarDateLike) -> TemporalResult { + Ok(date_like.as_iso_date().year()) + } + + /// Returns the `month` for the `Iso` calendar. + fn month(&self, date_like: &CalendarDateLike) -> TemporalResult { + Ok(date_like.as_iso_date().month()) + } + + /// Returns the `monthCode` for the `Iso` calendar. + fn month_code(&self, date_like: &CalendarDateLike) -> TemporalResult { + let date = date_like.as_iso_date().as_icu4x()?; + Ok(date.month().code.0) + } + + /// Returns the `day` for the `Iso` calendar. + fn day(&self, date_like: &CalendarDateLike) -> TemporalResult { + Ok(date_like.as_iso_date().day()) + } + + /// Returns the `dayOfWeek` for the `Iso` calendar. + fn day_of_week(&self, date_like: &CalendarDateLike) -> TemporalResult { + let date = date_like.as_iso_date().as_icu4x()?; + Ok(date.day_of_week() as i32) + } + + /// Returns the `dayOfYear` for the `Iso` calendar. + fn day_of_year(&self, date_like: &CalendarDateLike) -> TemporalResult { + let date = date_like.as_iso_date().as_icu4x()?; + 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: &CalendarDateLike) -> TemporalResult { + let date = date_like.as_iso_date().as_icu4x()?; + + let week_calculator = WeekCalculator::default(); + + let week_of = date + .week_of_year(&week_calculator) + .map_err(|err| TemporalError::range().with_message(err.to_string()))?; + + Ok(i32::from(week_of.week)) + } + + /// Returns the `yearOfWeek` for the `Iso` calendar. + fn year_of_week(&self, date_like: &CalendarDateLike) -> TemporalResult { + let date = date_like.as_iso_date().as_icu4x()?; + + let week_calculator = WeekCalculator::default(); + + let week_of = date + .week_of_year(&week_calculator) + .map_err(|err| TemporalError::range().with_message(err.to_string()))?; + + // TODO: Reach out and see about RelativeUnit starting at -1 + // Ok(date.year().number - week_of.unit) + match week_of.unit { + 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, _: &CalendarDateLike) -> TemporalResult { + Ok(7) + } + + /// Returns the `daysInMonth` value for the `Iso` calendar. + fn days_in_month(&self, date_like: &CalendarDateLike) -> TemporalResult { + let date = date_like.as_iso_date().as_icu4x()?; + Ok(i32::from(date.days_in_month())) + } + + /// Returns the `daysInYear` value for the `Iso` calendar. + fn days_in_year(&self, date_like: &CalendarDateLike) -> TemporalResult { + let date = date_like.as_iso_date().as_icu4x()?; + Ok(i32::from(date.days_in_year())) + } + + /// Return the amount of months in an ISO Calendar. + fn months_in_year(&self, _: &CalendarDateLike) -> TemporalResult { + Ok(12) + } + + /// Returns whether provided date is in a leap year according to this calendar. + fn in_leap_year(&self, date_like: &CalendarDateLike) -> TemporalResult { + // `ICU4X`'s `CalendarArithmetic` is currently private. + Ok(utils::mathematical_days_in_year(date_like.as_iso_date().year()) == 366) + } + + // Resolve the fields for the iso calendar. + fn resolve_fields( + &self, + fields: &mut TemporalFields, + _: CalendarFieldsType, + ) -> TemporalResult<()> { + fields.iso_resolve_month()?; + Ok(()) + } + + /// Returns the ISO field descriptors, which is not called for the iso8601 calendar. + fn field_descriptors(&self, _: CalendarFieldsType) -> Vec<(String, bool)> { + // NOTE(potential improvement): look into implementing field descriptors and call + // ISO like any other calendar? + // Field descriptors is unused on ISO8601. + unreachable!() + } + + /// Returns the `CalendarFieldKeysToIgnore` implementation for ISO. + fn field_keys_to_ignore(&self, additional_keys: Vec) -> Vec { + let mut result = Vec::new(); + for key in &additional_keys { + result.push(key.clone()); + if key.as_str() == "month" { + result.push("monthCode".to_string()); + } else if key.as_str() == "monthCode" { + result.push("month".to_string()); + } + } + result + } + + // NOTE: This is currently not a name that is compliant with + // the Temporal proposal. For debugging purposes only. + /// Returns the debug name. + fn identifier(&self) -> TemporalResult { + Ok("iso8601".to_string()) + } +} diff --git a/boa_temporal/src/date.rs b/boa_temporal/src/date.rs new file mode 100644 index 00000000000..286a1bbc588 --- /dev/null +++ b/boa_temporal/src/date.rs @@ -0,0 +1,157 @@ +//! The `PlainDate` representation. + +use crate::{ + calendar::CalendarSlot, + datetime::TemporalDateTime, + duration::{DateDuration, Duration}, + iso::IsoDate, + options::{ArithmeticOverflow, TemporalUnit}, + TemporalResult, +}; + +/// The `Temporal.PlainDate` equivalent +#[derive(Debug, Default, Clone)] +pub struct TemporalDate { + iso: IsoDate, + calendar: CalendarSlot, +} + +// ==== Private API ==== + +impl TemporalDate { + /// Create a new TemporalDate with the date values and calendar slot. + pub(crate) fn new_unchecked(iso: IsoDate, calendar: CalendarSlot) -> Self { + Self { iso, calendar } + } +} + +// ==== Public API ==== + +impl TemporalDate { + /// Creates a new `TemporalDate` while checking for validity. + pub fn new( + year: i32, + month: i32, + day: i32, + calendar: CalendarSlot, + overflow: ArithmeticOverflow, + ) -> TemporalResult { + let iso = IsoDate::new(year, month, day, overflow)?; + Ok(Self::new_unchecked(iso, calendar)) + } + + /// Creates a `TemporalDate` from a `TemporalDateTime`. + pub fn from_datetime(dt: &TemporalDateTime) -> Self { + Self { + iso: dt.iso_date(), + calendar: dt.calendar().clone(), + } + } + + #[inline] + /// Returns this `TemporalDate`'s year value. + pub const fn year(&self) -> i32 { + self.iso.year() + } + + #[inline] + /// Returns this `TemporalDate`'s month value. + pub const fn month(&self) -> u8 { + self.iso.month() + } + + #[inline] + /// Returns this `TemporalDate`'s day value. + pub const fn day(&self) -> u8 { + self.iso.day() + } + + #[inline] + /// Returns the `TemporalDate`'s inner `IsoDate` record. + pub const fn iso_date(&self) -> IsoDate { + self.iso + } + + #[inline] + /// Returns a reference to this `TemporalDate`'s calendar slot. + pub fn calendar(&self) -> &CalendarSlot { + &self.calendar + } + + /// 3.5.7 `IsValidISODate` + /// + /// Checks if the current date is a valid ISODate. + pub fn is_valid(&self) -> bool { + self.iso.is_valid() + } + + #[inline] + /// Returns a new moved date and the days associated with that adjustment + pub fn move_relative_date(&self, duration: &Duration) -> TemporalResult<(TemporalDate, f64)> { + let new_date = self.add_to_date(duration, ArithmeticOverflow::Constrain)?; + let days = f64::from(self.days_until(&new_date)); + Ok((new_date, days)) + } + + #[inline] + /// `AddDate` + pub fn add_to_date( + &self, + duration: &Duration, + overflow: ArithmeticOverflow, + ) -> TemporalResult { + // 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.date().years() != 0.0 + || duration.date().months() != 0.0 + || duration.date().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 self.calendar().date_add(&self, duration, overflow); + } + + // 3. Let overflow be ? ToTemporalOverflow(options). + // 4. Let days be ? BalanceTimeDuration(duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], "day").[[Days]]. + let (days, _) = duration.balance_time_duration(TemporalUnit::Day)?; + + // 5. Let result be ? AddISODate(plainDate.[[ISOYear]], plainDate.[[ISOMonth]], plainDate.[[ISODay]], 0, 0, 0, days, overflow). + let result = self + .iso + .add_iso_date(&DateDuration::new(0f64, 0f64, 0f64, days), overflow)?; + + Ok(Self::new_unchecked(result, self.calendar().clone())) + } + + /// `DifferenceDate` + pub fn diff_date(&self, other: &Self, largest_unit: TemporalUnit) -> TemporalResult { + if self.iso.year() == other.iso.year() + && self.iso.month() == other.iso.month() + && self.iso.day() == other.iso.day() + { + return Ok(Duration::default()); + } + + if largest_unit == TemporalUnit::Day { + let days = self.days_until(other); + return Ok(Duration::from_date_duration(DateDuration::new( + 0f64, + 0f64, + 0f64, + f64::from(days), + ))); + } + + self.calendar().date_until(self, other, largest_unit) + } + + #[inline] + /// `DaysUntil` + /// + /// Calculates the epoch days between two `TemporalDate`s + pub fn days_until(&self, other: &Self) -> i32 { + other.iso.to_epoch_days() - self.iso.to_epoch_days() + } +} diff --git a/boa_temporal/src/datetime.rs b/boa_temporal/src/datetime.rs new file mode 100644 index 00000000000..c90b6cdfc04 --- /dev/null +++ b/boa_temporal/src/datetime.rs @@ -0,0 +1,87 @@ +//! Temporal implementation of `DateTime` + +use crate::{ + calendar::CalendarSlot, + date::TemporalDate, + iso::{IsoDate, IsoDateTime, IsoTime}, + month_day::TemporalMonthDay, + options::ArithmeticOverflow, + TemporalResult, +}; + +/// The `TemporalDateTime` struct. +#[derive(Debug, Default, Clone)] +pub struct TemporalDateTime { + iso: IsoDateTime, + calendar: CalendarSlot, +} + +impl TemporalDateTime { + /// Creates a new unchecked `TemporalDateTime`. + pub(crate) fn new_unchecked(date: IsoDate, time: IsoTime, calendar: CalendarSlot) -> Self { + Self { + iso: IsoDateTime::new_unchecked(date, time), + calendar, + } + } + + /// Creates a new validated `TemporalDateTime`. + #[allow(clippy::too_many_arguments)] + pub fn new( + year: i32, + month: i32, + day: i32, + hour: i32, + minute: i32, + second: i32, + millisecond: i32, + microsecond: i32, + nanosecond: i32, + calendar: CalendarSlot, + ) -> TemporalResult { + let iso_date = IsoDate::new(year, month, day, ArithmeticOverflow::Reject)?; + let iso_time = IsoTime::new( + hour, + minute, + second, + millisecond, + microsecond, + nanosecond, + ArithmeticOverflow::Reject, + )?; + Ok(Self::new_unchecked(iso_date, iso_time, calendar)) + } + + #[inline] + /// Utility function for validating `IsoDate`s + fn validate_iso(iso: IsoDate) -> bool { + IsoDateTime::new_unchecked(iso, IsoTime::noon()).is_within_limits() + } + + #[inline] + /// Validates that the provide `TemporalDate` is within iso limits at noon. + pub fn validate_date(date: &TemporalDate) -> bool { + Self::validate_iso(date.iso_date()) + } + + #[inline] + /// Validates that the provided `TemporalMonthDay` is within limits. + pub fn validate_month_day(month_day: &TemporalMonthDay) -> bool { + Self::validate_iso(month_day.iso_date()) + } + + /// Returns the inner `IsoDate` value. + pub fn iso_date(&self) -> IsoDate { + self.iso.iso_date() + } + + /// Returns the inner `IsoTime` value. + pub fn iso_time(&self) -> IsoTime { + self.iso.iso_time() + } + + /// Returns the Calendar value. + pub fn calendar(&self) -> &CalendarSlot { + &self.calendar + } +} diff --git a/boa_temporal/src/duration.rs b/boa_temporal/src/duration.rs new file mode 100644 index 00000000000..c1264c0101d --- /dev/null +++ b/boa_temporal/src/duration.rs @@ -0,0 +1,1622 @@ +//! The Temporal Duration. +//! +//! TODO: Docs + +use crate::{ + date::TemporalDate, + datetime::TemporalDateTime, + options::{ArithmeticOverflow, TemporalRoundingMode, TemporalUnit}, + utils, + zoneddatetime::TemporalZonedDateTime, + TemporalError, TemporalResult, NS_PER_DAY, +}; + +// ==== `DateDuration` ==== + +/// `DateDuration` represents the [date duration record][spec] of the `Duration.` +/// +/// These fields are laid out in the [Temporal Proposal][field spec] as 64-bit floating point numbers. +/// +/// [spec]: https://tc39.es/proposal-temporal/#sec-temporal-date-duration-records +/// [field spec]: https://tc39.es/proposal-temporal/#sec-properties-of-temporal-duration-instances +#[derive(Debug, Default, Clone, Copy)] +pub struct DateDuration { + years: f64, + months: f64, + weeks: f64, + days: f64, +} + +impl DateDuration { + /// Creates a new `DateDuration` with provided values. + pub const fn new(years: f64, months: f64, weeks: f64, days: f64) -> Self { + Self { + years, + months, + weeks, + days, + } + } + + /// Returns a `PartialDateDuration` with all fields set to NaN. + pub const fn partial() -> Self { + Self { + years: f64::NAN, + months: f64::NAN, + weeks: f64::NAN, + days: f64::NAN, + } + } + + /// Returns the `[[years]]` value. + pub const fn years(&self) -> f64 { + self.years + } + + /// Returns the `[[months]]` value. + pub const fn months(&self) -> f64 { + self.months + } + + /// Returns the `[[weeks]]` value. + pub const fn weeks(&self) -> f64 { + self.weeks + } + + /// Returns the `[[days]]` value. + pub const fn days(&self) -> f64 { + self.days + } +} + +impl<'a> IntoIterator for &'a DateDuration { + type Item = f64; + type IntoIter = DateIter<'a>; + + fn into_iter(self) -> Self::IntoIter { + DateIter { + date: self, + index: 0, + } + } +} + +/// An iterator over the `DateDuration` +#[derive(Debug)] +pub struct DateIter<'a> { + date: &'a DateDuration, + index: usize, +} + +impl Iterator for DateIter<'_> { + type Item = f64; + + fn next(&mut self) -> Option { + let result = match self.index { + 0 => Some(self.date.years), + 1 => Some(self.date.months), + 2 => Some(self.date.weeks), + 3 => Some(self.date.days), + _ => None, + }; + self.index += 1; + result + } +} + +// ==== `TimeDuration` ==== + +/// `TimeDuration` represents the [Time Duration record][spec] of the `Duration.` +/// +/// These fields are laid out in the [Temporal Proposal][field spec] as 64-bit floating point numbers. +/// +/// [spec]: https://tc39.es/proposal-temporal/#sec-temporal-time-duration-records +/// [field spec]: https://tc39.es/proposal-temporal/#sec-properties-of-temporal-duration-instances +#[derive(Debug, Default, Clone, Copy)] +pub struct TimeDuration { + hours: f64, + minutes: f64, + seconds: f64, + milliseconds: f64, + microseconds: f64, + nanoseconds: f64, +} + +impl TimeDuration { + /// Creates a new `TimeDuration`. + pub const fn new( + hours: f64, + minutes: f64, + seconds: f64, + milliseconds: f64, + microseconds: f64, + nanoseconds: f64, + ) -> Self { + Self { + hours, + minutes, + seconds, + milliseconds, + microseconds, + nanoseconds, + } + } + + /// Creates a partial `TimeDuration` with all values set to NaN. + pub const fn partial() -> Self { + Self { + hours: f64::NAN, + minutes: f64::NAN, + seconds: f64::NAN, + milliseconds: f64::NAN, + microseconds: f64::NAN, + nanoseconds: f64::NAN, + } + } + + /// Utility function for returning if values in a valid range. + #[inline] + pub fn is_within_range(&self) -> bool { + self.hours.abs() < 24f64 + && self.minutes.abs() < 60f64 + && self.seconds.abs() < 60f64 + && self.milliseconds.abs() < 1000f64 + && self.milliseconds.abs() < 1000f64 + && self.milliseconds.abs() < 1000f64 + } + + /// Returns the `[[hours]]` value. + pub const fn hours(&self) -> f64 { + self.hours + } + + /// Returns the `[[minutes]]` value. + pub const fn minutes(&self) -> f64 { + self.minutes + } + + /// Returns the `[[seconds]]` value. + pub const fn seconds(&self) -> f64 { + self.seconds + } + + /// Returns the `[[milliseconds]]` value. + pub const fn milliseconds(&self) -> f64 { + self.milliseconds + } + + /// Returns the `[[microseconds]]` value. + pub const fn microseconds(&self) -> f64 { + self.microseconds + } + + /// Returns the `[[nanoseconds]]` value. + pub const fn nanoseconds(&self) -> f64 { + self.nanoseconds + } +} + +impl<'a> IntoIterator for &'a TimeDuration { + type Item = f64; + type IntoIter = TimeIter<'a>; + + fn into_iter(self) -> Self::IntoIter { + TimeIter { + time: self, + index: 0, + } + } +} + +/// An iterator over a `TimeDuration`. +#[derive(Debug)] +pub struct TimeIter<'a> { + time: &'a TimeDuration, + index: usize, +} + +impl Iterator for TimeIter<'_> { + type Item = f64; + + fn next(&mut self) -> Option { + let result = match self.index { + 0 => Some(self.time.hours), + 1 => Some(self.time.minutes), + 2 => Some(self.time.seconds), + 3 => Some(self.time.milliseconds), + 4 => Some(self.time.microseconds), + 5 => Some(self.time.nanoseconds), + _ => None, + }; + self.index += 1; + result + } +} + +// ==== `Duration` ==== + +/// The `Duration` is a native Rust implementation of the `Duration` builtin +/// object internal fields and is primarily defined by Abtract Operation 7.5.1-5. +#[derive(Debug, Clone, Copy, Default)] +pub struct Duration { + date: DateDuration, + time: TimeDuration, +} + +impl Duration { + /// Creates a new `Duration` from a `DateDuration` and `TimeDuration`. + pub(crate) const fn new_unchecked(date: DateDuration, time: TimeDuration) -> Self { + Self { date, time } + } + + /// Creates a new validated `Duration`. + #[allow(clippy::too_many_arguments)] + pub fn new( + years: f64, + months: f64, + weeks: f64, + days: f64, + hours: f64, + minutes: f64, + seconds: f64, + milliseconds: f64, + microseconds: f64, + nanoseconds: f64, + ) -> TemporalResult { + let duration = Self::new_unchecked( + DateDuration::new(years, months, weeks, days), + TimeDuration::new( + hours, + minutes, + seconds, + milliseconds, + microseconds, + nanoseconds, + ), + ); + if !duration.is_valid() { + return Err(TemporalError::range().with_message("Duration was not valid.")); + } + Ok(duration) + } + + /// Creates a partial `Duration` with all fields set to NaN. + pub const fn partial() -> Self { + Self { + date: DateDuration::partial(), + time: TimeDuration::partial(), + } + } + + /// Creates a `Duration` from only a DateDuration. + pub fn from_date_duration(date: DateDuration) -> Self { + Self { + date, + time: TimeDuration::default(), + } + } + + /// Creates a `Duration` from a provided a day and a `TimeDuration`. + /// + /// Note: `TimeDuration` records can store a day value to deal with overflow. + pub const fn from_day_and_time(day: f64, time: TimeDuration) -> Self { + Self { + date: DateDuration::new(0.0, 0.0, 0.0, day), + time, + } + } + + /// Utility function to create a one year duration. + pub(crate) fn one_year(year_value: f64) -> Self { + Self::from_date_duration(DateDuration::new(year_value, 0f64, 0f64, 0f64)) + } + + /// Utility function to create a one month duration. + pub(crate) fn one_month(month_value: f64) -> Self { + Self::from_date_duration(DateDuration::new(0f64, month_value, 0f64, 0f64)) + } + + /// Utility function to create a one week duration. + pub(crate) fn one_week(week_value: f64) -> Self { + Self::from_date_duration(DateDuration::new(0f64, 0f64, week_value, 0f64)) + } + + /// Utility function to return if the Durations values are within their valid ranges. + #[inline] + pub fn is_time_within_range(&self) -> bool { + self.time.is_within_range() + } +} + +impl Duration { + /// Returns a reference to the inner `TimeDuration` + pub fn time(&self) -> &TimeDuration { + &self.time + } + + /// Returns a reference to the inner `DateDuration` + pub fn date(&self) -> &DateDuration { + &self.date + } + + /// Set this `DurationRecord`'s `TimeDuration`. + pub fn set_time_duration(&mut self, time: TimeDuration) { + self.time = time; + } + + /// Set the value for `years`. + pub fn set_years(&mut self, y: f64) { + self.date.years = y; + } + + /// Set the value for `months`. + pub fn set_months(&mut self, mo: f64) { + self.date.months = mo; + } + + /// Set the value for `weeks`. + pub fn set_weeks(&mut self, w: f64) { + self.date.weeks = w; + } + + /// Set the value for `days`. + pub fn set_days(&mut self, d: f64) { + self.date.days = d; + } + + /// Set the value for `hours`. + pub fn set_hours(&mut self, h: f64) { + self.time.hours = h; + } + + /// Set the value for `minutes`. + pub fn set_minutes(&mut self, m: f64) { + self.time.minutes = m; + } + + /// Set the value for `seconds`. + pub fn set_seconds(&mut self, s: f64) { + self.time.seconds = s; + } + + /// Set the value for `milliseconds`. + pub fn set_milliseconds(&mut self, ms: f64) { + self.time.milliseconds = ms; + } + + /// Set the value for `microseconds`. + pub fn set_microseconds(&mut self, mis: f64) { + self.time.microseconds = mis; + } + + /// Set the value for `nanoseconds`. + pub fn set_nanoseconds(&mut self, ns: f64) { + self.time.nanoseconds = ns; + } +} + +impl<'a> IntoIterator for &'a Duration { + type Item = f64; + type IntoIter = DurationIter<'a>; + + fn into_iter(self) -> Self::IntoIter { + DurationIter { + duration: self, + index: 0, + } + } +} + +/// A Duration iterator that iterates through all duration fields. +#[derive(Debug)] +pub struct DurationIter<'a> { + duration: &'a Duration, + index: usize, +} + +impl Iterator for DurationIter<'_> { + type Item = f64; + + fn next(&mut self) -> Option { + let result = match self.index { + 0 => Some(self.duration.date.years()), + 1 => Some(self.duration.date.months()), + 2 => Some(self.duration.date.weeks()), + 3 => Some(self.duration.date.days()), + 4 => Some(self.duration.time.hours), + 5 => Some(self.duration.time.minutes), + 6 => Some(self.duration.time.seconds), + 7 => Some(self.duration.time.milliseconds), + 8 => Some(self.duration.time.microseconds), + 9 => Some(self.duration.time.nanoseconds), + _ => None, + }; + self.index += 1; + result + } +} + +// ==== Duration method ==== + +impl Duration { + /// Returns the absolute value of `Duration`. + pub fn abs(&self) -> Self { + Self { + date: DateDuration::new( + self.date.years.abs(), + self.date.months.abs(), + self.date.weeks.abs(), + self.date.days.abs(), + ), + time: TimeDuration::new( + self.time.hours.abs(), + self.time.minutes.abs(), + self.time.seconds.abs(), + self.time.milliseconds.abs(), + self.time.microseconds.abs(), + self.time.nanoseconds.abs(), + ), + } + } +} + +impl Duration { + /// Returns the duration time values as a vec + fn time_values(&self) -> Vec { + let mut values = Vec::from([self.date.days]); + values.extend(self.time.into_iter()); + values + } + + /// 7.5.10 `DurationSign ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds )` + /// + /// Determines the sign for the current self. + pub fn duration_sign(&self) -> i32 { + // 1. For each value v of « years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds », do + for v in self { + // a. If v < 0, return -1. + if v < 0_f64 { + return -1; + // b. If v > 0, return 1. + } else if v > 0_f64 { + return 1; + } + } + // 2. Return 0. + 0 + } + + /// 7.5.11 `IsValidDuration ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds )` + /// + /// Checks if the current `DurationRecord` is a valid self. + pub(crate) fn is_valid(&self) -> bool { + // 1. Let sign be ! DurationSign(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds). + let sign = self.duration_sign(); + // 2. For each value v of « years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds », do + for v in self { + // a. If 𝔽(v) is not finite, return false. + if !v.is_finite() { + return false; + } + // b. If v < 0 and sign > 0, return false. + if v < 0_f64 && sign > 0 { + return false; + } + // c. If v > 0 and sign < 0, return false. + if v > 0_f64 && sign < 0 { + return false; + } + } + // 3. Return true. + true + } + + /// 7.5.12 `DefaultTemporalLargestUnit ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds )` + pub fn default_temporal_largest_unit(&self) -> TemporalUnit { + for (index, value) in self.into_iter().enumerate() { + if value != 0.0 { + match index { + 0 => return TemporalUnit::Year, + 1 => return TemporalUnit::Month, + 2 => return TemporalUnit::Week, + 3 => return TemporalUnit::Day, + 4 => return TemporalUnit::Hour, + 5 => return TemporalUnit::Minute, + 6 => return TemporalUnit::Second, + 7 => return TemporalUnit::Millisecond, + 8 => return TemporalUnit::Microsecond, + _ => {} + } + } + } + + TemporalUnit::Nanosecond + } + + /// 7.5.17 `TotalDurationNanoseconds ( days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, offsetShift )` + fn total_duration_nanoseconds(&self, offset_shift: f64) -> f64 { + let nanoseconds = if self.date.days == 0_f64 { + self.time.nanoseconds + } else { + self.time.nanoseconds - offset_shift + }; + + self.date + .days + .mul_add(24_f64, self.time.hours) + .mul_add(60_f64, self.time.minutes) + .mul_add(60_f64, self.time.seconds) + .mul_add(1_000_f64, self.time.milliseconds) + .mul_add(1_000_f64, self.time.microseconds) + .mul_add(1_000_f64, nanoseconds) + } + + /// Abstract Operation 7.5.17 `BalanceTimeDuration ( days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, largestUnit )` + pub fn balance_time_duration( + &self, + largest_unit: TemporalUnit, + ) -> TemporalResult<(f64, TimeDuration)> { + // 1. Let balanceResult be ? BalancePossiblyInfiniteDuration(days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, largestUnit, relativeTo). + let balance_result = self.balance_possibly_infinite_time_duration(largest_unit)?; + + // 2. If balanceResult is positive overflow or negative overflow, then + let Some(result) = balance_result else { + // a. Throw a RangeError exception. + return Err(TemporalError::range() + .with_message("duration overflowed viable range.") + .into()); + }; + // 3. Else, + // a. Return balanceResult. + Ok(result) + } + + /// Abstract Operation 7.5.18 `BalancePossiblyInfiniteDuration ( days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, largestUnit )` + pub(crate) fn balance_possibly_infinite_time_duration( + &self, + largest_unit: TemporalUnit, + ) -> TemporalResult> { + let mut result = TimeDuration::default(); + let mut result_days = 0f64; + + // 1. Set hours to hours + days × 24. + result.hours = self.time.hours + (self.date.days * 24f64); + + // 2. Set nanoseconds to TotalDurationNanoseconds(hours, minutes, seconds, milliseconds, microseconds, nanoseconds). + result.nanoseconds = self.total_duration_nanoseconds(0f64); + + // 3. Set days, hours, minutes, seconds, milliseconds, and microseconds to 0. + + // 4. If nanoseconds < 0, let sign be -1; else, let sign be 1. + let sign = if result.nanoseconds < 0f64 { -1 } else { 1 }; + // 5. Set nanoseconds to abs(nanoseconds). + result.nanoseconds = result.nanoseconds.abs(); + + match largest_unit { + // 9. If largestUnit is "year", "month", "week", "day", or "hour", then + TemporalUnit::Year + | TemporalUnit::Month + | TemporalUnit::Week + | TemporalUnit::Day + | TemporalUnit::Hour => { + // a. Set microseconds to floor(nanoseconds / 1000). + result.microseconds = (result.nanoseconds / 1000f64).floor(); + // b. Set nanoseconds to nanoseconds modulo 1000. + result.nanoseconds = result.nanoseconds % 1000f64; + + // c. Set milliseconds to floor(microseconds / 1000). + result.milliseconds = (result.microseconds / 1000f64).floor(); + // d. Set microseconds to microseconds modulo 1000. + result.microseconds = result.microseconds % 1000f64; + + // e. Set seconds to floor(milliseconds / 1000). + result.seconds = (result.milliseconds / 1000f64).floor(); + // f. Set milliseconds to milliseconds modulo 1000. + result.milliseconds = result.milliseconds % 1000f64; + + // g. Set minutes to floor(seconds / 60). + result.minutes = (result.seconds / 60f64).floor(); + // h. Set seconds to seconds modulo 60. + result.seconds = result.seconds % 60f64; + + // i. Set hours to floor(minutes / 60). + result.hours = (result.minutes / 60f64).floor(); + // j. Set minutes to minutes modulo 60. + result.minutes = result.minutes % 60f64; + + // k. Set days to floor(hours / 24). + result_days = (result.hours / 24f64).floor(); + // l. Set hours to hours modulo 24. + result.hours = result.hours % 24f64; + } + // 10. Else if largestUnit is "minute", then + TemporalUnit::Minute => { + // a. Set microseconds to floor(nanoseconds / 1000). + // b. Set nanoseconds to nanoseconds modulo 1000. + result.microseconds = (result.nanoseconds / 1000f64).floor(); + result.nanoseconds = result.nanoseconds % 1000f64; + + // c. Set milliseconds to floor(microseconds / 1000). + // d. Set microseconds to microseconds modulo 1000. + result.milliseconds = (result.microseconds / 1000f64).floor(); + result.microseconds = result.microseconds % 1000f64; + + // e. Set seconds to floor(milliseconds / 1000). + // f. Set milliseconds to milliseconds modulo 1000. + result.minutes = (result.seconds / 60f64).floor(); + result.seconds = result.seconds % 60f64; + + // g. Set minutes to floor(seconds / 60). + // h. Set seconds to seconds modulo 60. + result.minutes = (result.seconds / 60f64).floor(); + result.seconds = result.seconds % 60f64; + } + // 11. Else if largestUnit is "second", then + TemporalUnit::Second => { + // a. Set microseconds to floor(nanoseconds / 1000). + // b. Set nanoseconds to nanoseconds modulo 1000. + result.microseconds = (result.nanoseconds / 1000f64).floor(); + result.nanoseconds = result.nanoseconds % 1000f64; + + // c. Set milliseconds to floor(microseconds / 1000). + // d. Set microseconds to microseconds modulo 1000. + result.milliseconds = (result.microseconds / 1000f64).floor(); + result.microseconds = result.microseconds % 1000f64; + + // e. Set seconds to floor(milliseconds / 1000). + // f. Set milliseconds to milliseconds modulo 1000. + result.minutes = (result.seconds / 60f64).floor(); + result.seconds = result.seconds % 60f64; + } + // 12. Else if largestUnit is "millisecond", then + TemporalUnit::Millisecond => { + // a. Set microseconds to floor(nanoseconds / 1000). + // b. Set nanoseconds to nanoseconds modulo 1000. + result.microseconds = (result.nanoseconds / 1000f64).floor(); + result.nanoseconds = result.nanoseconds % 1000f64; + + // c. Set milliseconds to floor(microseconds / 1000). + // d. Set microseconds to microseconds modulo 1000. + result.milliseconds = (result.microseconds / 1000f64).floor(); + result.microseconds = result.microseconds % 1000f64; + } + // 13. Else if largestUnit is "microsecond", then + TemporalUnit::Microsecond => { + // a. Set microseconds to floor(nanoseconds / 1000). + // b. Set nanoseconds to nanoseconds modulo 1000. + result.microseconds = (result.nanoseconds / 1000f64).floor(); + result.nanoseconds = result.nanoseconds % 1000f64; + } + // 14. Else, + // a. Assert: largestUnit is "nanosecond". + _ => debug_assert!(largest_unit == TemporalUnit::Nanosecond), + } + + // 15. For each value v of « days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds », do + for value in self.time_values() { + // a. If 𝔽(v) is not finite, then + if !value.is_finite() { + // i. If sign = 1, then + if sign == 1 { + // 1. Return positive overflow. + return Ok(None); + } + // ii. Else if sign = -1, then + // 1. Return negative overflow. + return Ok(None); + } + } + + let sign = f64::from(sign); + + // 16. Return ? CreateTimeDurationRecord(days, hours × sign, minutes × sign, seconds × sign, milliseconds × sign, microseconds × sign, nanoseconds × sign). + result.hours *= sign; + result.minutes *= sign; + result.seconds *= sign; + result.milliseconds *= sign; + result.microseconds *= sign; + result.nanoseconds *= sign; + + // `CreateTimeDurationRecord` validates that the record that would be created is a valid duration, so validate here + if !self.is_valid() { + return Err(TemporalError::range() + .with_message("TimeDurationRecord is not a valid duration.") + .into()); + } + + Ok(Some((result_days, result))) + } + + // TODO: move to DateDuration. + /// 7.5.21 UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit, plainRelativeTo )` + #[allow(dead_code)] + pub fn unbalance_duration_relative( + &self, + largest_unit: TemporalUnit, + plain_relative_to: Option<&TemporalDate>, + ) -> TemporalResult { + // 1. Let allZero be false. + // 2. If years = 0, and months = 0, and weeks = 0, and days = 0, set allZero to true. + let all_zero = self.date.years == 0_f64 + && self.date.months == 0_f64 + && self.date.weeks == 0_f64 + && self.date.days == 0_f64; + + // 3. If largestUnit is "year" or allZero is true, then + if largest_unit == TemporalUnit::Year || all_zero { + // a. Return ! CreateDateDurationRecord(years, months, weeks, days). + return Ok(self.date); + }; + + // 4. Let sign be ! DurationSign(years, months, weeks, days, 0, 0, 0, 0, 0, 0). + // 5. Assert: sign ≠ 0. + let sign = f64::from(self.duration_sign()); + + // 6. Let oneYear be ! CreateTemporalDuration(sign, 0, 0, 0, 0, 0, 0, 0, 0, 0). + let one_year = Self::one_year(sign); + // 7. Let oneMonth be ! CreateTemporalDuration(0, sign, 0, 0, 0, 0, 0, 0, 0, 0). + let one_month = Self::one_month(sign); + + // 9. If plainRelativeTo is not undefined, then + // a. Let calendar be plainRelativeTo.[[Calendar]]. + // 10. Else, + // a. Let calendar be undefined. + + // 11. If largestUnit is "month", then + if largest_unit == TemporalUnit::Month { + // a. If years = 0, return ! CreateDateDurationRecord(0, months, weeks, days). + if self.date.years == 0f64 { + return Ok(DateDuration::new( + 0f64, + self.date.months, + self.date.weeks, + self.date.days, + )); + } + + // b. If calendar is undefined, then + let Some(mut plain_relative_to) = plain_relative_to.map(Clone::clone) else { + // i. Throw a RangeError exception. + return Err(TemporalError::range() + .with_message("Calendar cannot be undefined.") + .into()); + }; + + // c. If calendar is an Object, then + // i. Let dateAdd be ? GetMethod(calendar, "dateAdd"). + // ii. Let dateUntil be ? GetMethod(calendar, "dateUntil"). + // d. Else, + // i. Let dateAdd be unused. + // ii. Let dateUntil be unused. + + let mut years = self.date.years; + let mut months = self.date.months; + // e. Repeat, while years ≠ 0, + while years != 0f64 { + // i. Let newRelativeTo be ? CalendarDateAdd(calendar, plainRelativeTo, oneYear, undefined, dateAdd). + let new_relative_to = plain_relative_to.calendar().date_add( + &plain_relative_to, + &one_year, + ArithmeticOverflow::Constrain, + )?; + + // ii. Let untilOptions be OrdinaryObjectCreate(null). + // iii. Perform ! CreateDataPropertyOrThrow(untilOptions, "largestUnit", "month"). + // iv. Let untilResult be ? CalendarDateUntil(calendar, plainRelativeTo, newRelativeTo, untilOptions, dateUntil). + let until_result = plain_relative_to.calendar().date_until( + &plain_relative_to, + &new_relative_to, + TemporalUnit::Month, + )?; + + // v. Let oneYearMonths be untilResult.[[Months]]. + let one_year_months = until_result.date.months; + + // vi. Set plainRelativeTo to newRelativeTo. + plain_relative_to = new_relative_to; + + // vii. Set years to years - sign. + years -= sign; + // viii. Set months to months + oneYearMonths. + months += one_year_months; + } + // f. Return ? CreateDateDurationRecord(0, months, weeks, days). + return Ok(DateDuration::new( + years, + months, + self.date.weeks, + self.date.days, + )); + + // 12. If largestUnit is "week", then + } else if largest_unit == TemporalUnit::Week { + // a. If years = 0 and months = 0, return ! CreateDateDurationRecord(0, 0, weeks, days). + if self.date.years == 0f64 && self.date.months == 0f64 { + return Ok(DateDuration::new( + 0f64, + 0f64, + self.date.weeks, + self.date.days, + )); + } + + // b. If calendar is undefined, then + let Some(mut plain_relative_to) = plain_relative_to.map(Clone::clone) else { + // i. Throw a RangeError exception. + return Err(TemporalError::range() + .with_message("Calendar cannot be undefined.") + .into()); + }; + + // c. If calendar is an Object, then + // i. Let dateAdd be ? GetMethod(calendar, "dateAdd"). + // d. Else, + // i. Let dateAdd be unused. + + let mut years = self.date.years; + let mut days = self.date.days; + // e. Repeat, while years ≠ 0, + while years != 0f64 { + // i. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, oneYear, dateAdd). + let move_result = plain_relative_to.move_relative_date(&one_year)?; + + // ii. Set plainRelativeTo to moveResult.[[RelativeTo]]. + plain_relative_to = move_result.0; + // iii. Set days to days + moveResult.[[Days]]. + days += move_result.1; + // iv. Set years to years - sign. + years -= sign; + } + + let mut months = self.date.months; + // f. Repeat, while months ≠ 0, + while months != 0f64 { + // i. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, oneMonth, dateAdd). + let move_result = plain_relative_to.move_relative_date(&one_month)?; + // ii. Set plainRelativeTo to moveResult.[[RelativeTo]]. + plain_relative_to = move_result.0; + // iii. Set days to days + moveResult.[[Days]]. + days += move_result.1; + // iv. Set months to months - sign. + months -= sign; + } + // g. Return ? CreateDateDurationRecord(0, 0, weeks, days). + return Ok(DateDuration::new(0f64, 0f64, self.date.weeks(), days)); + } + + // 13. If years = 0, and months = 0, and weeks = 0, return ! CreateDateDurationRecord(0, 0, 0, days). + if self.date.years == 0f64 && self.date.months == 0f64 && self.date.weeks == 0f64 { + return Ok(DateDuration::new(0f64, 0f64, 0f64, self.date.days)); + } + + // NOTE: Move 8 down to past 13 as we only use one_week after making it past 13. + // 8. Let oneWeek be ! CreateTemporalDuration(0, 0, sign, 0, 0, 0, 0, 0, 0, 0). + let one_week = Self::one_week(sign); + + // 14. If calendar is undefined, then + let Some(mut plain_relative_to) = plain_relative_to.map(Clone::clone) else { + // a. Throw a RangeError exception. + return Err(TemporalError::range() + .with_message("Calendar cannot be undefined.") + .into()); + }; + + // 15. If calendar is an Object, then + // a. Let dateAdd be ? GetMethod(calendar, "dateAdd"). + // 16. Else, + // a. Let dateAdd be unused. + + let mut years = self.date.years; + let mut days = self.date.days; + // a. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, oneYear, dateAdd). + while years != 0f64 { + // a. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, oneYear, dateAdd). + let move_result = plain_relative_to.move_relative_date(&one_year)?; + + // b. Set plainRelativeTo to moveResult.[[RelativeTo]]. + plain_relative_to = move_result.0; + // c. Set days to days + moveResult.[[Days]]. + days += move_result.1; + // d. Set years to years - sign. + years -= sign; + } + + let mut months = self.date.months; + // 18. Repeat, while months ≠ 0, + while months != 0f64 { + // a. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, oneMonth, dateAdd). + let move_result = plain_relative_to.move_relative_date(&one_month)?; + // b. Set plainRelativeTo to moveResult.[[RelativeTo]]. + plain_relative_to = move_result.0; + // c. Set days to days +moveResult.[[Days]]. + days += move_result.1; + // d. Set months to months - sign. + months -= sign; + } + + let mut weeks = self.date.weeks; + // 19. Repeat, while weeks ≠ 0, + while weeks != 0f64 { + // a. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, oneWeek, dateAdd). + let move_result = plain_relative_to.move_relative_date(&one_week)?; + // b. Set plainRelativeTo to moveResult.[[RelativeTo]]. + plain_relative_to = move_result.0; + // c. Set days to days + moveResult.[[Days]]. + days += move_result.1; + // d. Set weeks to weeks - sign. + weeks -= sign; + } + + // 20. Return ? CreateDateDurationRecord(0, 0, 0, days). + Ok(DateDuration::new(0f64, 0f64, 0f64, days)) + } + + // TODO: Move to DateDuration + /// `BalanceDateDurationRelative` + #[allow(unused)] + pub fn balance_date_duration_relative( + &self, + largest_unit: TemporalUnit, + plain_relative_to: Option<&TemporalDate>, + ) -> TemporalResult { + let mut result = DateDuration::from(self.date); + + // 1. Let allZero be false. + // 2. If years = 0, and months = 0, and weeks = 0, and days = 0, set allZero to true. + let all_zero = self.date.years == 0.0 + && self.date.months == 0.0 + && self.date.weeks == 0.0 + && self.date.days == 0.0; + + // 3. If largestUnit is not one of "year", "month", or "week", or allZero is true, then + match largest_unit { + TemporalUnit::Year | TemporalUnit::Month | TemporalUnit::Week if !all_zero => {} + _ => { + // a. Return ! CreateDateDurationRecord(years, months, weeks, days). + return Ok(result); + } + } + + // 4. If plainRelativeTo is undefined, then + let Some(mut plain_relative_to) = plain_relative_to.map(Clone::clone) else { + // a. Throw a RangeError exception. + return Err(TemporalError::range() + .with_message("relativeTo cannot be undefined.") + .into()); + }; + + // 5. Let sign be ! DurationSign(years, months, weeks, days, 0, 0, 0, 0, 0, 0). + // 6. Assert: sign ≠ 0. + 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::one_year(sign); + // 8. Let oneMonth be ! CreateTemporalDuration(0, sign, 0, 0, 0, 0, 0, 0, 0, 0). + let one_month = Self::one_month(sign); + // 9. Let oneWeek be ! CreateTemporalDuration(0, 0, sign, 0, 0, 0, 0, 0, 0, 0). + let one_week = Self::one_week(sign); + + // 10. Let calendar be relativeTo.[[Calendar]]. + + match largest_unit { + // 12. If largestUnit is "year", then + TemporalUnit::Year => { + // a. If calendar is an Object, then + // i. Let dateAdd be ? GetMethod(calendar, "dateAdd"). + // b. Else, + // i. Let dateAdd be unused. + + // c. Let moveResult be ? MoveRelativeDate(calendar, relativeTo, oneYear, dateAdd). + // d. Let newRelativeTo be moveResult.[[RelativeTo]]. + // e. Let oneYearDays be moveResult.[[Days]]. + let (mut new_relative_to, mut one_year_days) = + plain_relative_to.move_relative_date(&one_year)?; + + // f. Repeat, while abs(days) ≥ abs(oneYearDays), + while result.days().abs() >= one_year_days.abs() { + // i. Set days to days - oneYearDays. + result.days -= one_year_days; + + // ii. Set years to years + sign. + result.years += sign; + + // iii. Set relativeTo to newRelativeTo. + plain_relative_to = new_relative_to; + // iv. Set moveResult to ? MoveRelativeDate(calendar, relativeTo, oneYear, dateAdd). + let move_result = plain_relative_to.move_relative_date(&one_year)?; + + // v. Set newRelativeTo to moveResult.[[RelativeTo]]. + new_relative_to = move_result.0; + // vi. Set oneYearDays to moveResult.[[Days]]. + one_year_days = move_result.1; + } + + // g. Set moveResult to ? MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd). + // h. Set newRelativeTo to moveResult.[[RelativeTo]]. + // i. Let oneMonthDays be moveResult.[[Days]]. + let (mut new_relative_to, mut one_month_days) = + plain_relative_to.move_relative_date(&one_month)?; + + // j. Repeat, while abs(days) ≥ abs(oneMonthDays), + while result.days().abs() >= one_month_days.abs() { + // i. Set days to days - oneMonthDays. + result.days -= one_month_days; + // ii. Set months to months + sign. + result.months += sign; + + // iii. Set relativeTo to newRelativeTo. + plain_relative_to = new_relative_to; + // iv. Set moveResult to ? MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd). + let move_result = plain_relative_to.move_relative_date(&one_month)?; + + // v. Set newRelativeTo to moveResult.[[RelativeTo]]. + 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 = plain_relative_to.calendar().date_add( + &plain_relative_to, + &one_year, + ArithmeticOverflow::Constrain, + )?; + + // 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). + // o. Perform ! CreateDataPropertyOrThrow(untilOptions, "largestUnit", "month"). + + // p. Let untilResult be ? CalendarDateUntil(calendar, relativeTo, newRelativeTo, untilOptions, dateUntil). + let mut until_result = plain_relative_to.calendar().date_until( + &plain_relative_to, + &new_relative_to, + TemporalUnit::Month, + )?; + + // q. Let oneYearMonths be untilResult.[[Months]]. + let mut one_year_months = until_result.date.months(); + + // r. Repeat, while abs(months) ≥ abs(oneYearMonths), + while result.months().abs() >= one_year_months.abs() { + // i. Set months to months - oneYearMonths. + result.months -= one_year_months; + // ii. Set years to years + sign. + result.years += sign; + + // iii. Set relativeTo to newRelativeTo. + plain_relative_to = new_relative_to; + + // iv. Set newRelativeTo to ? CalendarDateAdd(calendar, relativeTo, oneYear, undefined, dateAdd). + new_relative_to = plain_relative_to.calendar().date_add( + &plain_relative_to, + &one_year, + ArithmeticOverflow::Constrain, + )?; + + // v. Set untilOptions to OrdinaryObjectCreate(null). + // vi. Perform ! CreateDataPropertyOrThrow(untilOptions, "largestUnit", "month"). + // vii. Set untilResult to ? CalendarDateUntil(calendar, relativeTo, newRelativeTo, untilOptions, dateUntil). + until_result = plain_relative_to.calendar().date_until( + &plain_relative_to, + &new_relative_to, + TemporalUnit::Month, + )?; + + // viii. Set oneYearMonths to untilResult.[[Months]]. + one_year_months = until_result.date.months(); + } + } + // 13. Else if largestUnit is "month", then + TemporalUnit::Month => { + // a. If calendar is an Object, then + // 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) = + plain_relative_to.move_relative_date(&one_month)?; + + // f. Repeat, while abs(days) ≥ abs(oneMonthDays), + while result.days().abs() >= one_month_days.abs() { + // i. Set days to days - oneMonthDays. + result.days -= one_month_days; + + // ii. Set months to months + sign. + result.months += sign; + + // iii. Set relativeTo to newRelativeTo. + plain_relative_to = new_relative_to; + + // iv. Set moveResult to ? MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd). + let move_result = plain_relative_to.move_relative_date(&one_month)?; + + // 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 => { + // a. Assert: largestUnit is "week". + // b. If calendar is an Object, then + // 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) = + plain_relative_to.move_relative_date(&one_week)?; + + // g. Repeat, while abs(days) ≥ abs(oneWeekDays), + while result.days().abs() >= one_week_days.abs() { + // i. Set days to days - oneWeekDays. + result.days -= one_week_days; + // ii. Set weeks to weeks + sign. + result.weeks += sign; + // iii. Set relativeTo to newRelativeTo. + plain_relative_to = new_relative_to; + // iv. Set moveResult to ? MoveRelativeDate(calendar, relativeTo, oneWeek, dateAdd). + let move_result = plain_relative_to.move_relative_date(&one_week)?; + + // 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). + Ok(result) + } + + // 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 [ , plainRelativeTo [, zonedRelativeTo [, precalculatedDateTime]]] )` + pub fn round_duration( + &self, + unbalance_date_duration: DateDuration, + increment: f64, + unit: TemporalUnit, + rounding_mode: TemporalRoundingMode, + relative_targets: ( + Option<&TemporalDate>, + Option<&TemporalZonedDateTime>, + Option<&TemporalDateTime>, + ), + ) -> TemporalResult<(Self, f64)> { + let mut result = Duration::new_unchecked(unbalance_date_duration, self.time); + + // 1. If plainRelativeTo is not present, set plainRelativeTo to undefined. + let plain_relative_to = relative_targets.0; + // 2. If zonedRelativeTo is not present, set zonedRelativeTo to undefined. + let zoned_relative_to = relative_targets.1; + // 3. If precalculatedPlainDateTime is not present, set precalculatedPlainDateTime to undefined. + let _precalc_pdt = relative_targets.2; + + 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(TemporalError::range() + .with_message("plainRelativeTo canot be undefined with given TemporalUnit") + .into()); + } + // 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(hours, minutes, seconds, milliseconds, microseconds, nanoseconds). + let nanos = + Self::from_day_and_time(0.0, *result.time()).total_duration_nanoseconds(0.0); + + // 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() { + result.date.days + nanos / NS_PER_DAY as f64 + } else { + // implementation of b: i-iii needed. + return Err(TemporalError::range() + .with_message("Not yet implemented.") + .into()); + }; + // d. Set days, hours, minutes, seconds, milliseconds, microseconds, and nanoseconds to 0. + result.date.days = 0f64; + result.time = TimeDuration::default(); + // e. Assert: fractionalSeconds is not used below. + (Some(frac_days), None) + } + // 6. Else, + _ => { + // a. Let fractionalSeconds be nanoseconds × 10-9 + microseconds × 10-6 + milliseconds × 10-3 + seconds. + let frac_secs = result.time.nanoseconds.mul_add( + 1_000_000_000f64, + result.time.microseconds.mul_add( + 1_000_000f64, + result + .time + .milliseconds + .mul_add(1_000f64, result.time.seconds), + ), + ); + + // b. Assert: fractionalDays is not used below. + (None, Some(frac_secs)) + } + }; + + // 7. let total be unset. + // We begin matching against unit and return the remainder value. + let total = match unit { + // 8. If unit is "year", then + TemporalUnit::Year => { + let mut frac_days = + frac_days.expect("assert that fractionalDays exists for TemporalUnit == year"); + + 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(result.date.years, 0.0, 0.0, 0.0); + let years_duration = Duration::new_unchecked(years, TimeDuration::default()); + + // c. If calendar is an Object, then + // i. Let dateAdd be ? GetMethod(calendar, "dateAdd"). + // d. Else, + // i. Let dateAdd be unused. + + // e. Let yearsLater be ? AddDate(calendar, plainRelativeTo, yearsDuration, undefined, dateAdd). + let years_later = plain_relative_to + .add_to_date(&years_duration, ArithmeticOverflow::Constrain)?; + + // f. Let yearsMonthsWeeks be ! CreateTemporalDuration(years, months, weeks, 0, 0, 0, 0, 0, 0, 0). + let years_months_weeks = Self::new_unchecked( + DateDuration::new( + result.date.years, + result.date.months, + result.date.weeks, + 0.0, + ), + TimeDuration::default(), + ); + + // g. Let yearsMonthsWeeksLater be ? AddDate(calendar, plainRelativeTo, yearsMonthsWeeks, undefined, dateAdd). + let years_months_weeks_later = plain_relative_to + .add_to_date(&years_months_weeks, ArithmeticOverflow::Constrain)?; + + // h. Let monthsWeeksInDays be DaysUntil(yearsLater, yearsMonthsWeeksLater). + let months_weeks_in_days = years_later.days_until(&years_months_weeks_later); + + // i. Set plainRelativeTo to yearsLater. + let plain_relative_to = years_later; + + // j. Set fractionalDays to fractionalDays + monthsWeeksInDays. + frac_days += f64::from(months_weeks_in_days); + + // k. Let isoResult be ! AddISODate(plainRelativeTo.[[ISOYear]]. plainRelativeTo.[[ISOMonth]], plainRelativeTo.[[ISODay]], 0, 0, 0, truncate(fractionalDays), "constrain"). + let iso_result = plain_relative_to.iso_date().add_iso_date( + &DateDuration::new(0.0, 0.0, 0.0, frac_days.trunc()), + ArithmeticOverflow::Constrain, + )?; + + // l. Let wholeDaysLater be ? CreateTemporalDate(isoResult.[[Year]], isoResult.[[Month]], isoResult.[[Day]], calendar). + let whole_days_later = TemporalDate::new_unchecked(iso_result, calendar.clone()); + + // 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_relative_to.diff_date(&whole_days_later, TemporalUnit::Year)?; + + // p. Let yearsPassed be timePassed.[[Years]]. + let years_passed = time_passed.date.years(); + + // q. Set years to years + yearsPassed. + result.date.years += years_passed; + + // r. Let yearsDuration be ! CreateTemporalDuration(yearsPassed, 0, 0, 0, 0, 0, 0, 0, 0, 0). + let years_duration = Self::one_year(years_passed); + + // 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) = + plain_relative_to.move_relative_date(&years_duration)?; + + // v. Set fractionalDays to fractionalDays - daysPassed. + frac_days -= days_passed; + + // w. If fractionalDays < 0, let sign be -1; else, let sign be 1. + let sign = if frac_days < 0.0 { -1 } else { 1 }; + + // x. Let oneYear be ! CreateTemporalDuration(sign, 0, 0, 0, 0, 0, 0, 0, 0, 0). + let one_year = Self::one_year(f64::from(sign)); + + // y. Set moveResult to ? MoveRelativeDate(calendar, plainRelativeTo, oneYear, dateAdd). + // z. Let oneYearDays be moveResult.[[Days]]. + let (_, one_year_days) = plain_relative_to.move_relative_date(&one_year)?; + + // aa. Let fractionalYears be years + fractionalDays / abs(oneYearDays). + let frac_years = result.date.years() + (frac_days / one_year_days.abs()); + + // ab. Set years to RoundNumberToIncrement(fractionalYears, increment, roundingMode). + result.date.years = + utils::round_number_to_increment(frac_years, increment, rounding_mode); + + // ac. Set total to fractionalYears. + // ad. Set months and weeks to 0. + result.date.months = 0f64; + result.date.weeks = 0f64; + + frac_years + } + // 9. Else if unit is "month", then + TemporalUnit::Month => { + 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("this must exist."); + + // b. Let yearsMonths be ! CreateTemporalDuration(years, months, 0, 0, 0, 0, 0, 0, 0, 0). + let years_months = Self::from_date_duration(DateDuration::new( + result.date.years(), + result.date.months(), + 0.0, + 0.0, + )); + + // c. If calendar is an Object, then + // i. Let dateAdd be ? GetMethod(calendar, "dateAdd"). + // d. Else, + // i. Let dateAdd be unused. + + // e. Let yearsMonthsLater be ? AddDate(calendar, plainRelativeTo, yearsMonths, undefined, dateAdd). + let years_months_later = + plain_relative_to.add_to_date(&years_months, ArithmeticOverflow::Constrain)?; + + // f. Let yearsMonthsWeeks be ! CreateTemporalDuration(years, months, weeks, 0, 0, 0, 0, 0, 0, 0). + let years_months_weeks = Self::from_date_duration(DateDuration::new( + result.date.years(), + result.date.months(), + result.date.weeks(), + 0.0, + )); + + // g. Let yearsMonthsWeeksLater be ? AddDate(calendar, plainRelativeTo, yearsMonthsWeeks, undefined, dateAdd). + let years_months_weeks_later = plain_relative_to + .add_to_date(&years_months_weeks, ArithmeticOverflow::Constrain)?; + + // h. Let weeksInDays be DaysUntil(yearsMonthsLater, yearsMonthsWeeksLater). + let weeks_in_days = years_months_later.days_until(&years_months_weeks_later); + + // i. Set plainRelativeTo to yearsMonthsLater. + let plain_relative_to = years_months_later; + + // j. Set fractionalDays to fractionalDays + weeksInDays. + frac_days += f64::from(weeks_in_days); + + // k. If fractionalDays < 0, let sign be -1; else, let sign be 1. + let sign = if frac_days < 0.0 { -1f64 } else { 1f64 }; + + // l. Let oneMonth be ! CreateTemporalDuration(0, sign, 0, 0, 0, 0, 0, 0, 0, 0). + let one_month = Self::one_month(sign); + + // 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) = + plain_relative_to.move_relative_date(&one_month)?; + + // p. Repeat, while abs(fractionalDays) ≥ abs(oneMonthDays), + while frac_days.abs() >= one_month_days.abs() { + // i. Set months to months + sign. + result.date.months += sign; + + // ii. Set fractionalDays to fractionalDays - oneMonthDays. + frac_days -= one_month_days; + + // iii. Set moveResult to ? MoveRelativeDate(calendar, plainRelativeTo, oneMonth, dateAdd). + let move_result = plain_relative_to.move_relative_date(&one_month)?; + + // iv. Set plainRelativeTo to moveResult.[[RelativeTo]]. + plain_relative_to = move_result.0; + // v. Set oneMonthDays to moveResult.[[Days]]. + one_month_days = move_result.1; + } + + // q. Let fractionalMonths be months + fractionalDays / abs(oneMonthDays). + let frac_months = result.date.months() + frac_days / one_month_days.abs(); + + // r. Set months to RoundNumberToIncrement(fractionalMonths, increment, roundingMode). + result.date.months = + utils::round_number_to_increment(frac_months, increment, rounding_mode); + + // s. Set total to fractionalMonths. + // t. Set weeks to 0. + result.date.weeks = 0.0; + frac_months + } + // 10. Else if unit is "week", then + TemporalUnit::Week => { + 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"); + + // 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::one_week(sign); + + // d. If calendar is an Object, then + // i. Let dateAdd be ? GetMethod(calendar, "dateAdd"). + // e. Else, + // i. Let dateAdd be unused. + + // 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) = + plain_relative_to.move_relative_date(&one_week)?; + + // i. Repeat, while abs(fractionalDays) ≥ abs(oneWeekDays), + while frac_days.abs() >= one_week_days.abs() { + // i. Set weeks to weeks + sign. + result.date.weeks += sign; + + // ii. Set fractionalDays to fractionalDays - oneWeekDays. + frac_days -= one_week_days; + + // iii. Set moveResult to ? MoveRelativeDate(calendar, plainRelativeTo, oneWeek, dateAdd). + let move_result = plain_relative_to.move_relative_date(&one_week)?; + + // iv. Set plainRelativeTo to moveResult.[[RelativeTo]]. + plain_relative_to = move_result.0; + // v. Set oneWeekDays to moveResult.[[Days]]. + one_week_days = move_result.1; + } + + // j. Let fractionalWeeks be weeks + fractionalDays / abs(oneWeekDays). + let frac_weeks = result.date.weeks() + frac_days / one_week_days.abs(); + + // k. Set weeks to RoundNumberToIncrement(fractionalWeeks, increment, roundingMode). + result.date.weeks = + utils::round_number_to_increment(frac_weeks, increment, rounding_mode); + // l. Set total to fractionalWeeks. + frac_weeks + } + // 11. Else if unit is "day", then + TemporalUnit::Day => { + let frac_days = + frac_days.expect("assert that fractionalDays exists for TemporalUnit::Day"); + + // a. Set days to RoundNumberToIncrement(fractionalDays, increment, roundingMode). + result.date.days = + utils::round_number_to_increment(frac_days, increment, rounding_mode); + // b. Set total to fractionalDays. + frac_days + } + // 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 frac_hours = + (frac_secs / 60f64 + result.time.minutes) / 60f64 + result.time.hours; + // b. Set hours to RoundNumberToIncrement(fractionalHours, increment, roundingMode). + let rounded_hours = + utils::round_number_to_increment(frac_hours, increment, rounding_mode); + // c. Set total to fractionalHours. + // d. Set minutes, seconds, milliseconds, microseconds, and nanoseconds to 0. + result.time = TimeDuration::new(rounded_hours, 0.0, 0.0, 0.0, 0.0, 0.0); + frac_hours + } + // 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 frac_minutes = frac_secs / 60f64 + result.time.minutes; + // b. Set minutes to RoundNumberToIncrement(fractionalMinutes, increment, roundingMode). + let rounded_minutes = + utils::round_number_to_increment(frac_minutes, increment, rounding_mode); + // c. Set total to fractionalMinutes. + // d. Set seconds, milliseconds, microseconds, and nanoseconds to 0. + result.time = + TimeDuration::new(result.time.hours, rounded_minutes, 0.0, 0.0, 0.0, 0.0); + + frac_minutes + } + // 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). + result.time.seconds = + utils::round_number_to_increment(frac_secs, increment, rounding_mode); + // b. Set total to fractionalSeconds. + // c. Set milliseconds, microseconds, and nanoseconds to 0. + result.time.milliseconds = 0.0; + result.time.microseconds = 0.0; + result.time.nanoseconds = 0.0; + + frac_secs + } + // 15. Else if unit is "millisecond", then + TemporalUnit::Millisecond => { + // a. Let fractionalMilliseconds be nanoseconds × 10-6 + microseconds × 10-3 + milliseconds. + let fraction_millis = result.time.nanoseconds.mul_add( + 1_000_000f64, + result + .time + .microseconds + .mul_add(1_000f64, result.time.milliseconds), + ); + + // b. Set milliseconds to RoundNumberToIncrement(fractionalMilliseconds, increment, roundingMode). + result.time.milliseconds = + utils::round_number_to_increment(fraction_millis, increment, rounding_mode); + + // c. Set total to fractionalMilliseconds. + // d. Set microseconds and nanoseconds to 0. + result.time.microseconds = 0.0; + result.time.nanoseconds = 0.0; + fraction_millis + } + // 16. Else if unit is "microsecond", then + TemporalUnit::Microsecond => { + // a. Let fractionalMicroseconds be nanoseconds × 10-3 + microseconds. + let frac_micros = result + .time + .nanoseconds + .mul_add(1_000f64, result.time.microseconds); + + // b. Set microseconds to RoundNumberToIncrement(fractionalMicroseconds, increment, roundingMode). + result.time.microseconds = + utils::round_number_to_increment(frac_micros, increment, rounding_mode); + + // c. Set total to fractionalMicroseconds. + // d. Set nanoseconds to 0. + result.time.nanoseconds = 0.0; + frac_micros + } + // 17. Else, + TemporalUnit::Nanosecond => { + // a. Assert: unit is "nanosecond". + // b. Set total to nanoseconds. + let total = result.time.nanoseconds; + // c. Set nanoseconds to RoundNumberToIncrement(nanoseconds, increment, roundingMode). + result.time.nanoseconds = utils::round_number_to_increment( + result.time.nanoseconds, + increment, + rounding_mode, + ); + + total + } + TemporalUnit::Auto => unreachable!(), + }; + + // 18. Let duration be ? CreateDurationRecord(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds). + // 19. Return the Record { [[DurationRecord]]: duration, [[Total]]: total }. + Ok((result, total)) + } +} diff --git a/boa_temporal/src/error.rs b/boa_temporal/src/error.rs new file mode 100644 index 00000000000..776698149d8 --- /dev/null +++ b/boa_temporal/src/error.rs @@ -0,0 +1,92 @@ +//! An error type for Temporal Errors. + +use core::fmt; + +/// TemporalError's error type. +#[derive(Debug, Default, Clone, Copy)] +pub enum ErrorKind { + /// Error. + #[default] + Generic, + /// TypeError + Type, + /// RangeError + Range, +} + +impl fmt::Display for ErrorKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Generic => "Error", + Self::Type => "TypeError", + Self::Range => "RangeError", + } + .fmt(f) + } +} + +/// The error type for `boa_temporal`. +#[derive(Debug, Clone)] +pub struct TemporalError { + kind: ErrorKind, + msg: Box, +} + +impl TemporalError { + fn new(kind: ErrorKind) -> Self { + Self { + kind, + msg: Box::default(), + } + } + + /// Create a generic error + pub fn general(msg: S) -> Self + where + S: Into>, + { + Self::new(ErrorKind::Generic).with_message(msg) + } + + /// Create a range error. + pub fn range() -> Self { + Self::new(ErrorKind::Range) + } + + /// Create a type error. + pub fn r#type() -> Self { + Self::new(ErrorKind::Type) + } + + /// Add a message to the error. + pub fn with_message(mut self, msg: S) -> Self + where + S: Into>, + { + self.msg = msg.into(); + self + } + + /// Returns this error's kind. + pub fn kind(&self) -> ErrorKind { + self.kind + } + + /// Returns the error message. + pub fn message(&self) -> &str { + &self.msg + } +} + +impl fmt::Display for TemporalError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.kind)?; + + let msg = self.msg.trim(); + if !msg.is_empty() { + write!(f, ": {msg}")?; + } + + Ok(()) + } +} diff --git a/boa_temporal/src/fields.rs b/boa_temporal/src/fields.rs new file mode 100644 index 00000000000..97360f1b09a --- /dev/null +++ b/boa_temporal/src/fields.rs @@ -0,0 +1,756 @@ +//! TemporalFields native Rust representation. + +use std::str::FromStr; + +use crate::{error::TemporalError, TemporalResult}; + +use bitflags::bitflags; +// use rustc_hash::FxHashSet; +use tinystr::{TinyStr16, TinyStr4}; + +bitflags! { + /// FieldMap maps the currently active fields on the `TemporalField` + #[derive(Debug, PartialEq, Eq)] + pub struct FieldMap: u16 { + /// Represents an active `year` field + const YEAR = 0b0000_0000_0000_0001; + /// Represents an active `month` field + const MONTH = 0b0000_0000_0000_0010; + /// Represents an active `monthCode` field + const MONTH_CODE = 0b0000_0000_0000_0100; + /// Represents an active `day` field + const DAY = 0b0000_0000_0000_1000; + /// Represents an active `hour` field + const HOUR = 0b0000_0000_0001_0000; + /// Represents an active `minute` field + const MINUTE = 0b0000_0000_0010_0000; + /// Represents an active `second` field + const SECOND = 0b0000_0000_0100_0000; + /// Represents an active `millisecond` field + const MILLISECOND = 0b0000_0000_1000_0000; + /// Represents an active `microsecond` field + const MICROSECOND = 0b0000_0001_0000_0000; + /// Represents an active `nanosecond` field + const NANOSECOND = 0b0000_0010_0000_0000; + /// Represents an active `offset` field + const OFFSET = 0b0000_0100_0000_0000; + /// Represents an active `era` field + const ERA = 0b0000_1000_0000_0000; + /// Represents an active `eraYear` field + const ERA_YEAR = 0b0001_0000_0000_0000; + /// Represents an active `timeZone` field + const TIME_ZONE = 0b0010_0000_0000_0000; + } +} + +/// The post conversion field value. +#[derive(Debug)] +pub enum FieldValue { + /// Designates the values as an integer. + Integer(i32), + /// Designates the value as a string. + String(String), + /// Designates that the value is undefined. + Undefined, +} + +/// The Conversion type of a field. +#[derive(Debug, Clone, Copy)] +pub enum FieldConversion { + /// Designates the Conversion type is `ToIntegerWithTruncation` + ToIntegerWithTruncation, + /// Designates the Conversion type is `ToPositiveIntegerWithTruncation` + ToPositiveIntegerWithTruncation, + /// Designates the Conversion type is `ToPrimitiveRequireString` + ToPrimativeAndRequireString, + /// Designates the Conversion type is nothing + None, +} + +impl FromStr for FieldConversion { + type Err = TemporalError; + + fn from_str(s: &str) -> Result { + match s { + "year" | "hour" | "minute" | "second" | "millisecond" | "microsecond" + | "nanosecond" => Ok(Self::ToIntegerWithTruncation), + "month" | "day" => Ok(Self::ToPositiveIntegerWithTruncation), + "monthCode" | "offset" | "eraYear" => Ok(Self::ToPrimativeAndRequireString), + _ => Err(TemporalError::range() + .with_message(format!("{s} is not a valid TemporalField Property"))), + } + } +} + +/// The temporal fields are laid out in the Temporal proposal under section 13.46 `PrepareTemporalFields` +/// with conversion and defaults laid out by Table 17 (displayed below). +/// +/// `TemporalFields` is meant to act as a native Rust implementation +/// of the fields. +/// +/// +/// ## Table 17: Temporal field requirements +/// +/// | Property | Conversion | Default | +/// | -------------|-----------------------------------|------------| +/// | "year" | `ToIntegerWithTruncation` | undefined | +/// | "month" | `ToPositiveIntegerWithTruncation` | undefined | +/// | "monthCode" | `ToPrimitiveAndRequireString` | undefined | +/// | "day" | `ToPositiveIntegerWithTruncation` | undefined | +/// | "hour" | `ToIntegerWithTruncation` | +0𝔽 | +/// | "minute" | `ToIntegerWithTruncation` | +0𝔽 | +/// | "second" | `ToIntegerWithTruncation` | +0𝔽 | +/// | "millisecond"| `ToIntegerWithTruncation` | +0𝔽 | +/// | "microsecond"| `ToIntegerWithTruncation` | +0𝔽 | +/// | "nanosecond" | `ToIntegerWithTruncation` | +0𝔽 | +/// | "offset" | `ToPrimitiveAndRequireString` | undefined | +/// | "era" | `ToPrimitiveAndRequireString` | undefined | +/// | "eraYear" | `ToIntegerWithTruncation` | undefined | +/// | "timeZone" | `None` | undefined | +#[derive(Debug)] +pub struct TemporalFields { + bit_map: FieldMap, + year: Option, + month: Option, + month_code: Option, // TODO: Switch to icu compatible value. + day: Option, + hour: i32, + minute: i32, + second: i32, + millisecond: i32, + microsecond: i32, + nanosecond: i32, + offset: Option, // TODO: Switch to tinystr? + era: Option, // TODO: switch to icu compatible value. + era_year: Option, // TODO: switch to icu compatible value. + time_zone: Option, // TODO: figure out the identifier for TimeZone. +} + +impl Default for TemporalFields { + fn default() -> Self { + Self { + bit_map: FieldMap::empty(), + year: None, + month: None, + month_code: None, + day: None, + hour: 0, + minute: 0, + second: 0, + millisecond: 0, + microsecond: 0, + nanosecond: 0, + offset: None, + era: None, + era_year: None, + time_zone: None, + } + } +} + +impl TemporalFields { + pub(crate) const fn year(&self) -> Option { + self.year + } + + pub(crate) const fn month(&self) -> Option { + self.month + } + + pub(crate) const fn day(&self) -> Option { + self.day + } +} + +// TODO: Update the below. +impl TemporalFields { + /// Flags a field as being required. + #[inline] + pub fn require_field(&mut self, field: String) { + match field.as_str() { + "year" => self.bit_map.set(FieldMap::YEAR, true), + "month" => self.bit_map.set(FieldMap::MONTH, true), + "monthCode" => self.bit_map.set(FieldMap::MONTH_CODE, true), + "day" => self.bit_map.set(FieldMap::DAY, true), + "hour" => self.bit_map.set(FieldMap::HOUR, true), + "minute" => self.bit_map.set(FieldMap::MINUTE, true), + "second" => self.bit_map.set(FieldMap::SECOND, true), + "millisecond" => self.bit_map.set(FieldMap::MILLISECOND, true), + "microsecond" => self.bit_map.set(FieldMap::MICROSECOND, true), + "nanosecond" => self.bit_map.set(FieldMap::NANOSECOND, true), + "offset" => self.bit_map.set(FieldMap::OFFSET, true), + "era" => self.bit_map.set(FieldMap::ERA, true), + "eraYear" => self.bit_map.set(FieldMap::ERA_YEAR, true), + "timeZone" => self.bit_map.set(FieldMap::TIME_ZONE, true), + _ => {} + } + } + + #[inline] + /// A generic field setter for `TemporalFields` + /// + /// This method will not run any `JsValue` conversion. `FieldValue` is + /// expected to contain a preconverted value. + pub fn set_field_value( + &mut self, + field: String, // TODO: Switch to an options Enum. + value: FieldValue, + ) -> TemporalResult<()> { + match field.as_str() { + "year" => self.set_year(value)?, + "month" => self.set_month(value)?, + "monthCode" => self.set_month_code(value)?, + "day" => self.set_day(value)?, + "hour" => self.set_hour(value)?, + "minute" => self.set_minute(value)?, + "second" => self.set_second(value)?, + "millisecond" => self.set_milli(value)?, + "microsecond" => self.set_micro(value)?, + "nanosecond" => self.set_nano(value)?, + "offset" => self.set_offset(value)?, + "era" => self.set_era(value)?, + "eraYear" => self.set_era_year(value)?, + "timeZone" => self.set_time_zone(value)?, + _ => unreachable!(), + } + + Ok(()) + } + + #[inline] + fn set_year(&mut self, value: FieldValue) -> TemporalResult<()> { + let FieldValue::Integer(y) = value else { + return Err(TemporalError::r#type().with_message("Year must be an integer.")); + }; + self.year = Some(y); + self.bit_map.set(FieldMap::YEAR, true); + Ok(()) + } + + #[inline] + fn set_month(&mut self, value: FieldValue) -> TemporalResult<()> { + let FieldValue::Integer(mo) = value else { + return Err(TemporalError::r#type().with_message("Month must be an integer.")); + }; + self.year = Some(mo); + self.bit_map.set(FieldMap::MONTH, true); + Ok(()) + } + + #[inline] + fn set_month_code(&mut self, value: FieldValue) -> TemporalResult<()> { + let FieldValue::String(mc) = value else { + return Err(TemporalError::r#type().with_message("monthCode must be string.")); + }; + self.month_code = + Some(TinyStr4::from_bytes(mc.as_bytes()).expect("monthCode must be less than 4 chars")); + self.bit_map.set(FieldMap::MONTH_CODE, true); + Ok(()) + } + + #[inline] + fn set_day(&mut self, value: FieldValue) -> TemporalResult<()> { + let FieldValue::Integer(d) = value else { + return Err(TemporalError::r#type().with_message("day must be an integer.")); + }; + self.day = Some(d); + self.bit_map.set(FieldMap::DAY, true); + Ok(()) + } + + #[inline] + fn set_hour(&mut self, value: FieldValue) -> TemporalResult<()> { + let FieldValue::Integer(h) = value else { + return Err(TemporalError::r#type().with_message("hour must be an integer.")); + }; + self.hour = h; + self.bit_map.set(FieldMap::HOUR, true); + Ok(()) + } + + #[inline] + fn set_minute(&mut self, value: FieldValue) -> TemporalResult<()> { + let FieldValue::Integer(min) = value else { + return Err(TemporalError::r#type().with_message("minute must be an integer.")); + }; + self.minute = min; + self.bit_map.set(FieldMap::MINUTE, true); + Ok(()) + } + + #[inline] + fn set_second(&mut self, value: FieldValue) -> TemporalResult<()> { + let FieldValue::Integer(sec) = value else { + return Err(TemporalError::r#type().with_message("Second must be an integer.")); + }; + self.second = sec; + self.bit_map.set(FieldMap::SECOND, true); + Ok(()) + } + + #[inline] + fn set_milli(&mut self, value: FieldValue) -> TemporalResult<()> { + let FieldValue::Integer(milli) = value else { + return Err(TemporalError::r#type().with_message("Second must be an integer.")); + }; + self.millisecond = milli; + self.bit_map.set(FieldMap::MILLISECOND, true); + Ok(()) + } + + #[inline] + fn set_micro(&mut self, value: FieldValue) -> TemporalResult<()> { + let FieldValue::Integer(micro) = value else { + return Err(TemporalError::r#type().with_message("microsecond must be an integer.")); + }; + self.microsecond = micro; + self.bit_map.set(FieldMap::MICROSECOND, true); + Ok(()) + } + + #[inline] + fn set_nano(&mut self, value: FieldValue) -> TemporalResult<()> { + let FieldValue::Integer(nano) = value else { + return Err(TemporalError::r#type().with_message("nanosecond must be an integer.")); + }; + self.nanosecond = nano; + self.bit_map.set(FieldMap::NANOSECOND, true); + Ok(()) + } + + #[inline] + fn set_offset(&mut self, value: FieldValue) -> TemporalResult<()> { + let FieldValue::String(offset) = value else { + return Err(TemporalError::r#type().with_message("offset must be string.")); + }; + self.offset = Some(offset); + self.bit_map.set(FieldMap::OFFSET, true); + + Ok(()) + } + + #[inline] + fn set_era(&mut self, value: FieldValue) -> TemporalResult<()> { + let FieldValue::String(era) = value else { + return Err(TemporalError::r#type().with_message("era must be string.")); + }; + self.era = + Some(TinyStr16::from_bytes(era.as_bytes()).expect("era should not exceed 16 bytes.")); + self.bit_map.set(FieldMap::ERA, true); + + Ok(()) + } + + #[inline] + fn set_era_year(&mut self, value: FieldValue) -> TemporalResult<()> { + let FieldValue::Integer(era_year) = value else { + return Err(TemporalError::r#type().with_message("eraYear must be an integer.")); + }; + self.era_year = Some(era_year); + self.bit_map.set(FieldMap::ERA_YEAR, true); + Ok(()) + } + + #[inline] + fn set_time_zone(&mut self, value: FieldValue) -> TemporalResult<()> { + let FieldValue::String(tz) = value else { + return Err(TemporalError::r#type().with_message("tz must be string.")); + }; + self.time_zone = Some(tz); + self.bit_map.set(FieldMap::TIME_ZONE, true); + Ok(()) + } +} + +// TODO: optimize into iter. +impl TemporalFields { + /// Returns a vector filled with the key-value pairs marked as active. + pub fn active_kvs(&self) -> Vec<(String, FieldValue)> { + let mut result = Vec::default(); + + for field in self.bit_map.iter() { + match field { + FieldMap::YEAR => result.push(( + "year".to_owned(), + self.year.map_or(FieldValue::Undefined, FieldValue::Integer), + )), + FieldMap::MONTH => result.push(( + "month".to_owned(), + self.month + .map_or(FieldValue::Undefined, FieldValue::Integer), + )), + FieldMap::MONTH_CODE => result.push(( + "monthCode".to_owned(), + self.month_code + .map_or(FieldValue::Undefined, |s| FieldValue::String(s.to_string())), + )), + FieldMap::DAY => result.push(( + "day".to_owned(), + self.day.map_or(FieldValue::Undefined, FieldValue::Integer), + )), + FieldMap::HOUR => result.push(("hour".to_owned(), FieldValue::Integer(self.hour))), + FieldMap::MINUTE => { + result.push(("minute".to_owned(), FieldValue::Integer(self.minute))) + } + FieldMap::SECOND => { + result.push(("second".to_owned(), FieldValue::Integer(self.second))) + } + FieldMap::MILLISECOND => result.push(( + "millisecond".to_owned(), + FieldValue::Integer(self.millisecond), + )), + FieldMap::MICROSECOND => result.push(( + "microsecond".to_owned(), + FieldValue::Integer(self.microsecond), + )), + FieldMap::NANOSECOND => result.push(( + "nanosecond".to_owned(), + FieldValue::Integer(self.nanosecond), + )), + FieldMap::OFFSET => result.push(( + "offset".to_owned(), + self.offset + .clone() + .map_or(FieldValue::Undefined, FieldValue::String), + )), + FieldMap::ERA => result.push(( + "era".to_owned(), + self.era + .map_or(FieldValue::Undefined, |s| FieldValue::String(s.to_string())), + )), + FieldMap::ERA_YEAR => result.push(( + "eraYear".to_owned(), + self.era_year + .map_or(FieldValue::Undefined, FieldValue::Integer), + )), + FieldMap::TIME_ZONE => result.push(( + "timeZone".to_owned(), + self.time_zone + .clone() + .map_or(FieldValue::Undefined, FieldValue::String), + )), + _ => {} + } + } + + result + } + + /// Resolve `TemporalFields` month and monthCode fields. + pub(crate) fn iso_resolve_month(&mut self) -> TemporalResult<()> { + if self.month_code.is_none() { + if self.month.is_some() { + return Ok(()); + } + + return Err(TemporalError::range() + .with_message("month and MonthCode values cannot both be undefined.")); + } + + let unresolved_month_code = self + .month_code + .as_ref() + .expect("monthCode must exist at this point."); + + let month_code_integer = month_code_to_integer(unresolved_month_code)?; + + let new_month = match self.month { + Some(month) if month != month_code_integer => { + return Err( + TemporalError::range().with_message("month and monthCode cannot be resolved.") + ) + } + _ => month_code_integer, + }; + + self.month = Some(new_month); + + Ok(()) + } +} + +fn month_code_to_integer(mc: &TinyStr4) -> TemporalResult { + match mc.as_str() { + "M01" => Ok(1), + "M02" => Ok(2), + "M03" => Ok(3), + "M04" => Ok(4), + "M05" => Ok(5), + "M06" => Ok(6), + "M07" => Ok(7), + "M08" => Ok(8), + "M09" => Ok(9), + "M10" => Ok(10), + "M11" => Ok(11), + "M12" => Ok(12), + "M13" => Ok(13), + _ => Err(TemporalError::range() + .with_message("monthCode is not within the valid values.") + .into()), + } +} + +/* +impl TemporalFields { + // TODO: Shift to JsString or utf16 over String. + /// A method for creating a Native representation for `TemporalFields` from + /// a `JsObject`. + /// + /// 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>, + partial: bool, + dup_behaviour: Option, + context: &mut Context<'_>, + ) -> TemporalResult { + // 1. If duplicateBehaviour is not present, set duplicateBehaviour to throw. + let dup_option = dup_behaviour.unwrap_or_else(|| js_string!("throw")); + + // 2. Let result be OrdinaryObjectCreate(null). + let mut result = Self::default(); + + // 3. Let any be false. + let mut any = false; + // 4. If extraFieldDescriptors is present, then + if let Some(extra_fields) = extended_fields { + for (field_name, required) in extra_fields { + // a. For each Calendar Field Descriptor Record desc of extraFieldDescriptors, do + // i. Assert: fieldNames does not contain desc.[[Property]]. + // ii. Append desc.[[Property]] to fieldNames. + field_names.push(field_name.clone()); + + // iii. If desc.[[Required]] is true and requiredFields is a List, then + if required && !partial { + // 1. Append desc.[[Property]] to requiredFields. + required_fields.push(field_name); + } + } + } + + // 5. Let sortedFieldNames be SortStringListByCodeUnit(fieldNames). + // 6. Let previousProperty be undefined. + let mut dups_map = FxHashSet::default(); + + // 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.to_std_string_escaped().as_str() == "constructor" + || field.to_std_string_escaped().as_str() == "__proto__" + { + // i. Throw a RangeError exception. + return Err(TemporalError::range() + .with_message("constructor or proto is out of field range.") + .into()); + } + + let new_value = dups_map.insert(field); + + // 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(field.clone()), context)?; + // ii. If value is not undefined, then + if !value.is_undefined() { + // 1. Set any to true. + any = true; + + // 2. If property is in the Property column of Table 17 and there is a Conversion value in the same row, then + // a. Let Conversion be the Conversion value of the same row. + // b. If Conversion is ToIntegerWithTruncation, then + // i. Set value to ? ToIntegerWithTruncation(value). + // ii. Set value to 𝔽(value). + // c. Else if Conversion is ToPositiveIntegerWithTruncation, then + // i. Set value to ? ToPositiveIntegerWithTruncation(value). + // ii. Set value to 𝔽(value). + // d. Else, + // i. Assert: Conversion is ToPrimitiveAndRequireString. + // ii. NOTE: Non-primitive values are supported here for consistency with other fields, but such values must coerce to Strings. + // iii. Set value to ? ToPrimitive(value, string). + // iv. If value is not a String, throw a TypeError exception. + // 3. Perform ! CreateDataPropertyOrThrow(result, property, value). + result.set_field_value(field, &value, context)?; + // iii. Else if requiredFields is a List, then + } else if !partial { + // 1. If requiredFields contains property, then + if required_fields.contains(field) { + // a. Throw a TypeError exception. + return Err(TemporalError::typ() + .with_message("A required TemporalField was not provided.") + .into()); + } + + // NOTE: Values set to a default on init. + // 2. If property is in the Property column of Table 17, then + // a. Set value to the corresponding Default value of the same row. + // 3. Perform ! CreateDataPropertyOrThrow(result, property, value). + } + // c. Else if duplicateBehaviour is throw, then + } else if dup_option.to_std_string_escaped() == "throw" { + // i. Throw a RangeError exception. + return Err(TemporalError::range() + .with_message("Cannot have a duplicate field") + .into()); + } + // d. Set previousProperty to property. + } + + // 8. If requiredFields is partial and any is false, then + if partial && !any { + // a. Throw a TypeError exception. + return Err(TemporalError::range() + .with_message("requiredFields cannot be partial when any is false") + .into()); + } + + // 9. Return result. + Ok(result) + } + + /// Convert a `TemporalFields` struct into a `JsObject`. + pub(crate) fn as_object(&self, context: &mut Context<'_>) -> TemporalResult { + let obj = JsObject::with_null_proto(); + + for bit in self.bit_map.iter() { + match bit { + FieldMap::YEAR => { + obj.create_data_property_or_throw( + js_string!("year"), + self.year.map_or(JsValue::undefined(), JsValue::from), + context, + )?; + } + FieldMap::MONTH => { + obj.create_data_property_or_throw( + js_string!("month"), + self.month.map_or(JsValue::undefined(), JsValue::from), + context, + )?; + } + FieldMap::MONTH_CODE => { + obj.create_data_property_or_throw( + js_string!("monthCode"), + self.month_code + .as_ref() + .map_or(JsValue::undefined(), |f| f.clone().into()), + context, + )?; + } + FieldMap::DAY => { + obj.create_data_property( + js_string!("day"), + self.day().map_or(JsValue::undefined(), JsValue::from), + context, + )?; + } + FieldMap::HOUR => { + obj.create_data_property(js_string!("hour"), self.hour, context)?; + } + FieldMap::MINUTE => { + obj.create_data_property(js_string!("minute"), self.minute, context)?; + } + FieldMap::SECOND => { + obj.create_data_property_or_throw(js_string!("second"), self.second, context)?; + } + FieldMap::MILLISECOND => { + obj.create_data_property_or_throw( + js_string!("millisecond"), + self.millisecond, + context, + )?; + } + FieldMap::MICROSECOND => { + obj.create_data_property_or_throw( + js_string!("microsecond"), + self.microsecond, + context, + )?; + } + FieldMap::NANOSECOND => { + obj.create_data_property_or_throw( + js_string!("nanosecond"), + self.nanosecond, + context, + )?; + } + FieldMap::OFFSET => { + obj.create_data_property_or_throw( + js_string!("offset"), + self.offset + .as_ref() + .map_or(JsValue::undefined(), |s| s.clone().into()), + context, + )?; + } + FieldMap::ERA => { + obj.create_data_property_or_throw( + js_string!("era"), + self.era + .as_ref() + .map_or(JsValue::undefined(), |s| s.clone().into()), + context, + )?; + } + FieldMap::ERA_YEAR => { + obj.create_data_property_or_throw( + js_string!("eraYear"), + self.era_year.map_or(JsValue::undefined(), JsValue::from), + context, + )?; + } + FieldMap::TIME_ZONE => { + obj.create_data_property_or_throw( + js_string!("timeZone"), + self.time_zone + .as_ref() + .map_or(JsValue::undefined(), |s| s.clone().into()), + context, + )?; + } + _ => unreachable!(), + } + } + + Ok(obj) + } + + // Note placeholder until overflow is implemented on `ICU4x`'s Date. + /// A function to regulate the current `TemporalFields` according to the overflow value + pub(crate) fn regulate(&mut self, overflow: ArithmeticOverflow) -> TemporalResult<()> { + if let (Some(year), Some(month), Some(day)) = (self.year(), self.month(), self.day()) { + match overflow { + ArithmeticOverflow::Constrain => { + let m = month.clamp(1, 12); + let days_in_month = crate::utils::iso_days_in_month(year, month); + let d = day.clamp(1, days_in_month); + + self.month = Some(m); + self.day = Some(d); + } + ArithmeticOverflow::Reject => { + return Err(TemporalError::range() + .with_message("TemporalFields is out of a valid range.") + ) + } + } + } + Ok(()) + } + + pub(crate) fn regulate_year_month(&mut self, overflow: ArithmeticOverflow) { + match self.month { + Some(month) if overflow == ArithmeticOverflow::Constrain => { + let m = month.clamp(1, 12); + self.month = Some(m); + } + _ => {} + } + } + +} + + + +*/ diff --git a/boa_temporal/src/iso.rs b/boa_temporal/src/iso.rs new file mode 100644 index 00000000000..01538696aee --- /dev/null +++ b/boa_temporal/src/iso.rs @@ -0,0 +1,527 @@ +//! An `IsoDate` that represents the `[[ISOYear]]`, `[[ISOMonth]]`, and `[[ISODay]]` internal slots. + +use crate::{ + duration::DateDuration, error::TemporalError, options::ArithmeticOverflow, utils, + TemporalResult, +}; +use icu_calendar::{Date as IcuDate, Iso}; +use num_bigint::BigInt; +use num_traits::cast::FromPrimitive; + +/// `IsoDateTime` is the Temporal internal representation of +/// a `DateTime` record +#[derive(Debug, Default, Clone, Copy)] +pub struct IsoDateTime { + date: IsoDate, + time: IsoTime, +} + +impl IsoDateTime { + /// Creates a new `IsoDateTime` without any validaiton. + pub(crate) fn new_unchecked(date: IsoDate, time: IsoTime) -> Self { + Self { date, time } + } + + /// Returns whether the `IsoDateTime` is within valid limits. + pub(crate) fn is_within_limits(&self) -> bool { + let Some(ns) = self.to_utc_epoch_nanoseconds(0f64) else { + return false; + }; + + let max = crate::ns_max_instant() + crate::NS_PER_DAY; + let min = crate::ns_min_instant() - crate::NS_PER_DAY; + + min < ns && max > ns + } + + /// Returns the UTC epoch nanoseconds for this `IsoDateTime`. + pub(crate) fn to_utc_epoch_nanoseconds(&self, offset: f64) -> Option { + let day = self.date.to_epoch_days(); + let time = self.time.to_epoch_ms(); + let epoch_ms = utils::epoch_days_to_epoch_ms(day, time); + + let epoch_nanos = epoch_ms.mul_add( + 1_000_000f64, + f64::from(self.time.microsecond).mul_add(1_000f64, f64::from(self.time.nanosecond)), + ); + + BigInt::from_f64(epoch_nanos - offset) + } + + pub(crate) fn iso_date(&self) -> IsoDate { + self.date + } + + pub(crate) fn iso_time(&self) -> IsoTime { + self.time + } +} + +// ==== `IsoDate` section ==== + +// TODO: Figure out `ICU4X` interop / replacement? + +/// `IsoDate` serves as a record for the `[[ISOYear]]`, `[[ISOMonth]]`, +/// and `[[ISODay]]` internal fields. +/// +/// These fields are used for the `Temporal.PlainDate` object, the +/// `Temporal.YearMonth` object, and the `Temporal.MonthDay` object. +#[derive(Debug, Clone, Copy, Default)] +pub struct IsoDate { + year: i32, + month: u8, + day: u8, +} + +impl IsoDate { + /// Creates a new `IsoDate` without determining the validity. + pub(crate) const fn new_unchecked(year: i32, month: u8, day: u8) -> Self { + Self { year, month, day } + } + + pub(crate) fn new( + year: i32, + month: i32, + day: i32, + overflow: ArithmeticOverflow, + ) -> TemporalResult { + match overflow { + ArithmeticOverflow::Constrain => { + let m = month.clamp(1, 12); + let days_in_month = utils::iso_days_in_month(year, month); + let d = day.clamp(1, days_in_month); + Ok(Self::new_unchecked(year, m as u8, d as u8)) + } + ArithmeticOverflow::Reject => { + if !is_valid_date(year, month, day) { + return Err(TemporalError::range().with_message("not a valid ISO date.")); + } + // NOTE: Values have been verified to be in a u8 range. + Ok(Self::new_unchecked(year, month as u8, day as u8)) + } + } + } + + pub(crate) fn new_year_month( + year: i32, + month: i32, + overflow: ArithmeticOverflow, + ) -> TemporalResult { + match overflow { + ArithmeticOverflow::Constrain => { + let m = month.clamp(1, 12) as u8; + Ok(Self::new_unchecked(year, m, 1)) + } + ArithmeticOverflow::Reject => { + if (1..=12).contains(&month) { + return Err(TemporalError::range().with_message("IsoYearMonth is not valid.")); + } + // NOTE: Values have been verified to be in a u8 range. + Ok(Self::new_unchecked(year, month as u8, 1)) + } + } + } + + /// Create a balanced `IsoDate` + /// + /// Equivalent to `BalanceISODate`. + fn balance(year: i32, month: i32, day: i32) -> Self { + let epoch_days = iso_date_to_epoch_days(year, month - 1, day); + let ms = utils::epoch_days_to_epoch_ms(epoch_days, 0f64); + Self::new_unchecked( + utils::epoch_time_to_epoch_year(ms), + utils::epoch_time_to_month_in_year(ms) + 1, + utils::epoch_time_to_date(ms), + ) + } + + /// Returns the year field + pub(crate) const fn year(&self) -> i32 { + self.year + } + + /// Returns the month field + pub(crate) const fn month(&self) -> u8 { + self.month + } + + /// Returns the day field + pub(crate) const fn day(&self) -> u8 { + self.day + } + + /// Functionally the same as Date's abstract operation `MakeDay` + /// + /// Equivalent to `IsoDateToEpochDays` + pub(crate) fn to_epoch_days(&self) -> i32 { + iso_date_to_epoch_days(self.year, self.month.into(), self.day.into()) + } + + /// Returns if the current `IsoDate` is valid. + pub(crate) fn is_valid(&self) -> bool { + is_valid_date(self.year, self.month.into(), self.day.into()) + } + + pub(crate) fn add_iso_date( + &self, + duration: &DateDuration, + overflow: ArithmeticOverflow, + ) -> TemporalResult { + // 1. Assert: year, month, day, years, months, weeks, and days are integers. + // 2. Assert: overflow is either "constrain" or "reject". + // 3. Let intermediate be ! BalanceISOYearMonth(year + years, month + months). + let mut intermediate_year = self.year + duration.years() as i32; + let mut intermediate_month = i32::from(self.month) + duration.months() as i32; + + intermediate_year += (intermediate_month - 1) / 12; + intermediate_month = (intermediate_month - 1) % 12 + 1; + + // 4. Let intermediate be ? RegulateISODate(intermediate.[[Year]], intermediate.[[Month]], day, overflow). + let intermediate = Self::new( + intermediate_year, + intermediate_month, + i32::from(self.day), + overflow, + )?; + + // 5. Set days to days + 7 × weeks. + // 6. Let d be intermediate.[[Day]] + days. + let additional_days = duration.days() as i32 + (duration.weeks() as i32 * 7); + let d = i32::from(intermediate.day) + additional_days; + + // 7. Return BalanceISODate(intermediate.[[Year]], intermediate.[[Month]], d). + Ok(Self::balance( + intermediate.year, + intermediate.month.into(), + d, + )) + } +} + +impl IsoDate { + // TODO: look into using Date across the board...TBD. + /// Creates `[[ISOYear]]`, `[[isoMonth]]`, `[[isoDay]]` fields from `ICU4X`'s `Date` struct. + pub(crate) fn as_icu4x(&self) -> TemporalResult> { + IcuDate::try_new_iso_date(self.year, self.month, self.day) + .map_err(|e| TemporalError::range().with_message(e.to_string())) + } +} + +// ==== `IsoTime` section ==== + +/// An `IsoTime` record that contains `Temporal`'s +/// time slots. +#[derive(Debug, Default, Clone, Copy)] +pub struct IsoTime { + hour: i32, // 0..=23 + minute: i32, // 0..=59 + second: i32, // 0..=59 + millisecond: i32, // 0..=999 + microsecond: i32, // 0..=999 + nanosecond: i32, // 0..=999 +} + +impl IsoTime { + /// Creates a new `IsoTime` without any validation. + pub(crate) fn new_unchecked( + hour: i32, + minute: i32, + second: i32, + millisecond: i32, + microsecond: i32, + nanosecond: i32, + ) -> Self { + Self { + hour, + minute, + second, + millisecond, + microsecond, + nanosecond, + } + } + + /// Creates a new regulated `IsoTime`. + pub fn new( + hour: i32, + minute: i32, + second: i32, + millisecond: i32, + microsecond: i32, + nanosecond: i32, + overflow: ArithmeticOverflow, + ) -> TemporalResult { + match overflow { + ArithmeticOverflow::Constrain => { + let h = hour.clamp(0, 23); + let min = minute.clamp(0, 59); + let sec = second.clamp(0, 59); + let milli = millisecond.clamp(0, 999); + let micro = microsecond.clamp(0, 999); + let nano = nanosecond.clamp(0, 999); + Ok(Self::new_unchecked(h, min, sec, milli, micro, nano)) + } + ArithmeticOverflow::Reject => { + // TODO: Invert structure validation and update fields to u16. + let time = + Self::new_unchecked(hour, minute, second, millisecond, microsecond, nanosecond); + if !time.is_valid() { + return Err(TemporalError::range().with_message("IsoTime is not valid")); + } + Ok(time) + } + } + } + + /// Returns an `IsoTime` set to 12:00:00 + pub(crate) const fn noon() -> Self { + Self { + hour: 12, + minute: 0, + second: 0, + millisecond: 0, + microsecond: 0, + nanosecond: 0, + } + } + + /// Checks if the time is a valid `IsoTime` + pub(crate) fn is_valid(&self) -> bool { + if !(0..=23).contains(&self.hour) { + return false; + } + + let min_sec = 0..=59; + if !min_sec.contains(&self.minute) || !min_sec.contains(&self.second) { + return false; + } + + let sub_second = 0..=999; + sub_second.contains(&self.millisecond) + && sub_second.contains(&self.microsecond) + && sub_second.contains(&self.nanosecond) + } + + /// `IsoTimeToEpochMs` + /// + /// Note: This method is library specific and not in spec + /// + /// Functionally the same as Date's `MakeTime` + pub(crate) fn to_epoch_ms(&self) -> f64 { + f64::from(self.hour).mul_add( + utils::MS_PER_HOUR, + f64::from(self.minute) * utils::MS_PER_MINUTE, + ) + f64::from(self.second).mul_add(1000f64, f64::from(self.millisecond)) + } +} + +// ==== `IsoDate` specific utiltiy functions ==== + +/// Returns the EpochDays based off the given year, month, and day. +#[inline] +fn iso_date_to_epoch_days(year: i32, month: i32, day: i32) -> i32 { + // 1. Let resolvedYear be year + floor(month / 12). + let resolved_year = year + (f64::from(month) / 12_f64).floor() as i32; + // 2. Let resolvedMonth be month modulo 12. + let resolved_month = month % 12; + + // 3. Find a time t such that EpochTimeToEpochYear(t) is resolvedYear, EpochTimeToMonthInYear(t) is resolvedMonth, and EpochTimeToDate(t) is 1. + let year_t = utils::epoch_time_for_year(resolved_year); + let month_t = utils::epoch_time_for_month_given_year(resolved_month.into(), resolved_year); + + // 4. Return EpochTimeToDayNumber(t) + date - 1. + utils::epoch_time_to_day_number(year_t + month_t) + day - 1 +} + +#[inline] +// Determines if the month and day are valid for the given year. +fn is_valid_date(year: i32, month: i32, day: i32) -> bool { + if !(1..=12).contains(&month) { + return false; + } + + let days_in_month = utils::iso_days_in_month(year, month); + (1..=days_in_month).contains(&day) +} + +/* +impl IsoDate { + /// 3.5.6 `RegulateISODate` + pub(crate) fn from_unregulated( + year: i32, + month: i32, + day: i32, + overflow: ArithmeticOverflow, + ) -> TemporalResult { + match overflow { + ArithmeticOverflow::Constrain => { + let m = month.clamp(1, 12); + let days_in_month = utils::iso_days_in_month(year, month); + let d = day.clamp(1, days_in_month); + Ok(Self::new_unchecked(year, m, d)) + } + ArithmeticOverflow::Reject => { + let date = Self::new_unchecked(year, month, day); + if !date.is_valid() { + return Err(TemporalError::range() + .with_message("not a valid ISO date.")); + } + Ok(date) + } + } + } + + /// 12.2.35 `ISODateFromFields ( fields, overflow )` + /// + /// Note: fields.month must be resolved prior to using `from_temporal_fields` + pub(crate) fn from_temporal_fields( + fields: &TemporalFields, + overflow: ArithmeticOverflow, + ) -> TemporalResult { + Self::from_unregulated( + fields.year().expect("Cannot fail per spec"), + fields.month().expect("cannot fail after resolution"), + fields.day().expect("cannot fail per spec"), + overflow, + ) + } + + /// Create a Month-Day record from a `TemporalFields` object. + pub(crate) fn month_day_from_temporal_fields( + fields: &TemporalFields, + overflow: ArithmeticOverflow, + ) -> TemporalResult { + match fields.year() { + Some(year) => Self::from_unregulated( + year, + fields.month().expect("month must exist."), + fields.day().expect("cannot fail per spec"), + overflow, + ), + None => Self::from_unregulated( + 1972, + fields.month().expect("cannot fail per spec"), + fields.day().expect("cannot fail per spec."), + overflow, + ), + } + } + + /// Within `YearMonth` valid limits + pub(crate) const fn within_year_month_limits(&self) -> bool { + if self.year < -271_821 || self.year > 275_760 { + return false; + } + + if self.year == -271_821 && self.month < 4 { + return false; + } + + if self.year == 275_760 && self.month > 9 { + return true; + } + + true + } + + /// 3.5.5 `DifferenceISODate` + pub(crate) fn diff_iso_date( + &self, + o: &Self, + largest_unit: TemporalUnit, + ) -> TemporalResult { + debug_assert!(self.is_valid()); + // TODO: Implement on `ICU4X`. + + Err(TemporalError::range() + .with_message("not yet implemented.")) + } + + /// 3.5.7 `IsValidISODate` + pub(crate) fn is_valid(&self) -> bool { + if self.month < 1 || self.month > 12 { + return false; + } + + let days_in_month = utils::iso_days_in_month(self.year, self.month); + + if self.day < 1 || self.day > days_in_month { + return false; + } + true + } + + /// 13.2 `IsoDateToEpochDays` + pub(crate) fn as_epoch_days(&self) -> i32 { + // 1. Let resolvedYear be year + floor(month / 12). + let resolved_year = self.year + (f64::from(self.month) / 12_f64).floor() as i32; + // 2. Let resolvedMonth be month modulo 12. + let resolved_month = self.month % 12; + + // 3. Find a time t such that EpochTimeToEpochYear(t) is resolvedYear, EpochTimeToMonthInYear(t) is resolvedMonth, and EpochTimeToDate(t) is 1. + let year_t = utils::epoch_time_for_year(resolved_year); + let month_t = utils::epoch_time_for_month_given_year( + resolved_month, + resolved_year, + ); + + // 4. Return EpochTimeToDayNumber(t) + date - 1. + utils::epoch_time_to_day_number(year_t + month_t) + self.day - 1 + } + + // NOTE: Implementing as mut self so balance is applied to self, but TBD. + /// 3.5.8 `BalanceIsoDate` + pub(crate) fn balance(&mut self) { + let epoch_days = self.as_epoch_days(); + let ms = utils::epoch_days_to_epoch_ms(epoch_days, 0); + + // Balance current values + self.year = utils::epoch_time_to_epoch_year(ms); + self.month = utils::epoch_time_to_month_in_year(ms); + self.day = utils::epoch_time_to_date(ms); + } + + // NOTE: Used in AddISODate only, so could possibly be deleted in the future. + /// 9.5.4 `BalanceISOYearMonth ( year, month )` + pub(crate) fn balance_year_month(&mut self) { + self.year += (self.month - 1) / 12; + self.month = ((self.month - 1) % 12) + 1; + } + + /// 3.5.11 `AddISODate ( year, month, day, years, months, weeks, days, overflow )` + pub(crate) fn add_iso_date( + &self, + date_duration: DateDuration, + overflow: ArithmeticOverflow, + ) -> TemporalResult { + // 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_unchecked( + 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(); + + // 4. Let intermediate be ? RegulateISODate(intermediate.[[Year]], intermediate.[[Month]], day, overflow). + let mut new_date = Self::from_unregulated( + intermediate.year(), + intermediate.month(), + self.day, + overflow, + )?; + + // 5. Set days to days + 7 × weeks. + // 6. Let d be intermediate.[[Day]] + days. + 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). + new_date.balance(); + + Ok(new_date) + } +} +*/ diff --git a/boa_temporal/src/lib.rs b/boa_temporal/src/lib.rs new file mode 100644 index 00000000000..494c6007d6d --- /dev/null +++ b/boa_temporal/src/lib.rs @@ -0,0 +1,119 @@ +//! Boa's `boa_temporal` crate is intended to serve as an engine agnostic +//! implementation the ECMAScript's Temporal builtin and algorithm. +#![doc = include_str!("../../ABOUT.md")] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo.svg" +)] +#![cfg_attr(not(test), forbid(clippy::unwrap_used))] +#![warn( + // rustc lint groups https://doc.rust-lang.org/rustc/lints/groups.html + warnings, + future_incompatible, + let_underscore, + nonstandard_style, + rust_2018_compatibility, + rust_2018_idioms, + rust_2021_compatibility, + unused, + + // rustc allowed-by-default lints https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html + missing_docs, + macro_use_extern_crate, + meta_variable_misuse, + missing_abi, + missing_copy_implementations, + missing_debug_implementations, + non_ascii_idents, + noop_method_call, + single_use_lifetimes, + trivial_casts, + trivial_numeric_casts, + unreachable_pub, + unsafe_op_in_unsafe_fn, + unused_crate_dependencies, + unused_import_braces, + unused_lifetimes, + unused_qualifications, + unused_tuple_struct_fields, + variant_size_differences, + + // rustdoc lints https://doc.rust-lang.org/rustdoc/lints.html + rustdoc::broken_intra_doc_links, + rustdoc::private_intra_doc_links, + rustdoc::missing_crate_level_docs, + rustdoc::private_doc_tests, + rustdoc::invalid_codeblock_attributes, + rustdoc::invalid_rust_codeblocks, + rustdoc::bare_urls, + + // clippy allowed by default + clippy::dbg_macro, + + // clippy categories https://doc.rust-lang.org/clippy/ + clippy::all, + clippy::correctness, + clippy::suspicious, + clippy::style, + clippy::complexity, + clippy::perf, + clippy::pedantic, +)] +#![allow( + // Currently throws a false positive regarding dependencies that are only used in benchmarks. + unused_crate_dependencies, + clippy::module_name_repetitions, + clippy::redundant_pub_crate, + clippy::too_many_lines, + clippy::cognitive_complexity, + clippy::missing_errors_doc, + clippy::let_unit_value, + clippy::option_if_let_else, + + // It may be worth to look if we can fix the issues highlighted by these lints. + clippy::cast_possible_truncation, + clippy::cast_sign_loss, + clippy::cast_precision_loss, + clippy::cast_possible_wrap, + + // Add temporarily - Needs addressing + clippy::missing_panics_doc, +)] + +pub mod calendar; +pub mod date; +pub mod datetime; +pub mod duration; +pub mod error; +pub mod fields; +pub mod iso; +pub mod month_day; +pub mod options; +pub mod time; +pub(crate) mod utils; +pub mod year_month; +pub mod zoneddatetime; + +use num_bigint::BigInt; +// TODO: evaluate positives and negatives of using tinystr. +// Re-exporting tinystr as a convenience, as it is currently tied into the API. +pub use tinystr::{TinyAsciiStr, TinyStr16, TinyStr4, TinyStr8}; + +pub use error::TemporalError; + +/// The `Temporal` result type +pub type TemporalResult = Result; + +// Relavant numeric constants +/// Nanoseconds per day constant: 8.64e+13 +pub(crate) const NS_PER_DAY: i64 = 86_400_000_000_000; +/// Milliseconds per day constant: 8.64e+7 +pub(crate) const MS_PER_DAY: i32 = 24 * 60 * 60 * 1000; + +pub(crate) fn ns_max_instant() -> BigInt { + BigInt::from(i128::from(NS_PER_DAY) * 100_000_000i128) +} + +pub(crate) fn ns_min_instant() -> BigInt { + ns_max_instant() * -1 +} diff --git a/boa_temporal/src/month_day.rs b/boa_temporal/src/month_day.rs new file mode 100644 index 00000000000..10409580478 --- /dev/null +++ b/boa_temporal/src/month_day.rs @@ -0,0 +1,42 @@ +//! `TemporalMonthDay` + +use crate::{calendar::CalendarSlot, iso::IsoDate, options::ArithmeticOverflow, TemporalResult}; + +/// The `TemporalMonthDay` struct +#[derive(Debug, Default, Clone)] +pub struct TemporalMonthDay { + iso: IsoDate, + calendar: CalendarSlot, +} + +impl TemporalMonthDay { + #[inline] + /// Creates a new unchecked `TemporalMonthDay` + pub(crate) fn new_unchecked(iso: IsoDate, calendar: CalendarSlot) -> Self { + Self { iso, calendar } + } + + #[inline] + /// Creates a new valid `TemporalMonthDay`. + pub fn new( + month: i32, + day: i32, + calendar: CalendarSlot, + overflow: ArithmeticOverflow, + ) -> TemporalResult { + let iso = IsoDate::new(1972, month, day, overflow)?; + Ok(Self::new_unchecked(iso, calendar)) + } + + #[inline] + /// Returns a reference to this `MonthDay`'s `IsoDate` + pub fn iso_date(&self) -> IsoDate { + self.iso + } + + #[inline] + /// Returns a reference to `MonthDay`'s `CalendarSlot` + pub fn calendar(&self) -> &CalendarSlot { + &self.calendar + } +} diff --git a/boa_temporal/src/options.rs b/boa_temporal/src/options.rs new file mode 100644 index 00000000000..24a9c7555bf --- /dev/null +++ b/boa_temporal/src/options.rs @@ -0,0 +1,355 @@ +//! Temporal Options + +use core::{fmt, str::FromStr}; + +use crate::TemporalError; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum TemporalUnit { + Auto = 0, + Nanosecond, + Microsecond, + Millisecond, + Second, + Minute, + Hour, + Day, + Week, + Month, + Year, +} + +impl TemporalUnit { + pub fn to_maximum_rounding_increment(self) -> Option { + use TemporalUnit::{ + Auto, Day, Hour, Microsecond, Millisecond, Minute, Month, Nanosecond, Second, Week, + Year, + }; + // 1. If unit is "year", "month", "week", or "day", then + // a. Return undefined. + // 2. If unit is "hour", then + // a. Return 24. + // 3. If unit is "minute" or "second", then + // a. Return 60. + // 4. Assert: unit is one of "millisecond", "microsecond", or "nanosecond". + // 5. Return 1000. + match self { + Year | Month | Week | Day => None, + Hour => Some(24), + Minute | Second => Some(60), + Millisecond | Microsecond | Nanosecond => Some(1000), + Auto => unreachable!(), + } + } +} + +#[derive(Debug)] +pub struct ParseTemporalUnitError; + +impl fmt::Display for ParseTemporalUnitError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("provided string was not a valid TemporalUnit") + } +} + +impl FromStr for TemporalUnit { + type Err = ParseTemporalUnitError; + + fn from_str(s: &str) -> Result { + match s { + "auto" => Ok(Self::Auto), + "year" | "years" => Ok(Self::Year), + "month" | "months" => Ok(Self::Month), + "week" | "weeks" => Ok(Self::Week), + "day" | "days" => Ok(Self::Day), + "hour" | "hours" => Ok(Self::Hour), + "minute" | "minutes" => Ok(Self::Minute), + "second" | "seconds" => Ok(Self::Second), + "millisecond" | "milliseconds" => Ok(Self::Millisecond), + "microsecond" | "microseconds" => Ok(Self::Microsecond), + "nanosecond" | "nanoseconds" => Ok(Self::Nanosecond), + _ => Err(ParseTemporalUnitError), + } + } +} + +impl fmt::Display for TemporalUnit { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Auto => "auto", + Self::Year => "constrain", + Self::Month => "month", + Self::Week => "week", + Self::Day => "day", + Self::Hour => "hour", + Self::Minute => "minute", + Self::Second => "second", + Self::Millisecond => "millsecond", + Self::Microsecond => "microsecond", + Self::Nanosecond => "nanosecond", + } + .fmt(f) + } +} + +/// `ArithmeticOverflow` can also be used as an +/// assignment overflow and consists of the "constrain" +/// and "reject" options. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ArithmeticOverflow { + Constrain, + Reject, +} + +#[derive(Debug)] +pub struct ParseArithmeticOverflowError; + +impl fmt::Display for ParseArithmeticOverflowError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("provided string was not a valid overflow value") + } +} + +impl FromStr for ArithmeticOverflow { + type Err = ParseArithmeticOverflowError; + + fn from_str(s: &str) -> Result { + match s { + "constrain" => Ok(Self::Constrain), + "reject" => Ok(Self::Reject), + _ => Err(ParseArithmeticOverflowError), + } + } +} + +impl fmt::Display for ArithmeticOverflow { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Constrain => "constrain", + Self::Reject => "reject", + } + .fmt(f) + } +} + +/// `Duration` overflow options. +pub enum DurationOverflow { + Constrain, + Balance, +} + +#[derive(Debug)] +pub struct ParseDurationOverflowError; + +impl fmt::Display for ParseDurationOverflowError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("provided string was not a valid duration overflow value") + } +} + +impl FromStr for DurationOverflow { + type Err = ParseDurationOverflowError; + + fn from_str(s: &str) -> Result { + match s { + "constrain" => Ok(Self::Constrain), + "balance" => Ok(Self::Balance), + _ => Err(ParseDurationOverflowError), + } + } +} + +impl fmt::Display for DurationOverflow { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Constrain => "constrain", + Self::Balance => "balance", + } + .fmt(f) + } +} + +/// The disambiguation options for an instant. +pub enum InstantDisambiguation { + Compatible, + Earlier, + Later, + Reject, +} + +#[derive(Debug)] +pub struct ParseInstantDisambiguationError; + +impl fmt::Display for ParseInstantDisambiguationError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("provided string was not a valid instant disambiguation value") + } +} + +impl FromStr for InstantDisambiguation { + type Err = ParseInstantDisambiguationError; + + fn from_str(s: &str) -> Result { + match s { + "compatible" => Ok(Self::Compatible), + "earlier" => Ok(Self::Earlier), + "later" => Ok(Self::Later), + "reject" => Ok(Self::Reject), + _ => Err(ParseInstantDisambiguationError), + } + } +} + +impl fmt::Display for InstantDisambiguation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Compatible => "compatible", + Self::Earlier => "earlier", + Self::Later => "later", + Self::Reject => "reject", + } + .fmt(f) + } +} + +/// Offset disambiguation options. +pub enum OffsetDisambiguation { + Use, + Prefer, + Ignore, + Reject, +} + +#[derive(Debug)] +pub struct ParseOffsetDisambiguationError; + +impl fmt::Display for ParseOffsetDisambiguationError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("provided string was not a valid offset disambiguation value") + } +} + +impl FromStr for OffsetDisambiguation { + type Err = ParseOffsetDisambiguationError; + + fn from_str(s: &str) -> Result { + match s { + "use" => Ok(Self::Use), + "prefer" => Ok(Self::Prefer), + "ignore" => Ok(Self::Ignore), + "reject" => Ok(Self::Reject), + _ => Err(ParseOffsetDisambiguationError), + } + } +} + +impl fmt::Display for OffsetDisambiguation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Use => "use", + Self::Prefer => "prefer", + Self::Ignore => "ignore", + Self::Reject => "reject", + } + .fmt(f) + } +} + +// TODO: Figure out what to do with intl's RoundingMode + +#[derive(Debug, Copy, Clone, Default)] +pub enum TemporalRoundingMode { + Ceil, + Floor, + Expand, + Trunc, + HalfCeil, + HalfFloor, + #[default] + HalfExpand, + HalfTrunc, + HalfEven, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TemporalUnsignedRoundingMode { + Infinity, + Zero, + HalfInfinity, + HalfZero, + HalfEven, +} + +impl TemporalRoundingMode { + pub const fn negate(self) -> Self { + use TemporalRoundingMode::{ + Ceil, Expand, Floor, HalfCeil, HalfEven, HalfExpand, HalfFloor, HalfTrunc, Trunc, + }; + + match self { + Ceil => Self::Floor, + Floor => Self::Ceil, + HalfCeil => Self::HalfFloor, + HalfFloor => Self::HalfCeil, + Trunc => Self::Trunc, + Expand => Self::Expand, + HalfTrunc => Self::HalfTrunc, + HalfExpand => Self::HalfExpand, + HalfEven => Self::HalfEven, + } + } + + pub const fn get_unsigned_round_mode(self, is_negative: bool) -> TemporalUnsignedRoundingMode { + use TemporalRoundingMode::{ + Ceil, Expand, Floor, HalfCeil, HalfEven, HalfExpand, HalfFloor, HalfTrunc, Trunc, + }; + + match self { + Ceil if !is_negative => TemporalUnsignedRoundingMode::Infinity, + Ceil => TemporalUnsignedRoundingMode::Zero, + Floor if !is_negative => TemporalUnsignedRoundingMode::Zero, + Floor | Trunc | Expand => TemporalUnsignedRoundingMode::Infinity, + HalfCeil if !is_negative => TemporalUnsignedRoundingMode::HalfInfinity, + HalfCeil | HalfTrunc => TemporalUnsignedRoundingMode::HalfZero, + HalfFloor if !is_negative => TemporalUnsignedRoundingMode::HalfZero, + HalfFloor | HalfExpand => TemporalUnsignedRoundingMode::HalfInfinity, + HalfEven => TemporalUnsignedRoundingMode::HalfEven, + } + } +} + +impl FromStr for TemporalRoundingMode { + type Err = TemporalError; + + fn from_str(s: &str) -> Result { + match s { + "ceil" => Ok(Self::Ceil), + "floor" => Ok(Self::Floor), + "expand" => Ok(Self::Expand), + "trunc" => Ok(Self::Trunc), + "halfCeil" => Ok(Self::HalfCeil), + "halfFloor" => Ok(Self::HalfFloor), + "halfExpand" => Ok(Self::HalfExpand), + "halfTrunc" => Ok(Self::HalfTrunc), + "halfEven" => Ok(Self::HalfEven), + _ => Err(TemporalError::range().with_message("RoundingMode not an accepted value.")), + } + } +} + +impl fmt::Display for TemporalRoundingMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Ceil => "ceil", + Self::Floor => "floor", + Self::Expand => "expand", + Self::Trunc => "trunc", + Self::HalfCeil => "halfCeil", + Self::HalfFloor => "halfFloor", + Self::HalfExpand => "halfExpand", + Self::HalfTrunc => "halfTrunc", + Self::HalfEven => "halfEven", + } + .fmt(f) + } +} diff --git a/boa_temporal/src/time.rs b/boa_temporal/src/time.rs new file mode 100644 index 00000000000..ffa0fcf299b --- /dev/null +++ b/boa_temporal/src/time.rs @@ -0,0 +1,30 @@ +//! Temporal Time Representation. + +use crate::iso::IsoTime; + +#[derive(Debug, Default)] +pub struct TemporalTime { + iso: IsoTime, +} + +// ==== Private API ==== + +impl TemporalTime { + pub(crate) fn new_unchecked( + hour: i32, + minute: i32, + second: i32, + millisecond: i32, + microsecond: i32, + nanosecond: i32, + ) -> Self { + Self { + iso: IsoTime::new_unchecked(hour, minute, second, millisecond, microsecond, nanosecond), + } + } + + /// Checks if the time is a valid `TemporalTime` + pub(crate) fn is_valid(&self) -> bool { + self.iso.is_valid() + } +} diff --git a/boa_temporal/src/utils.rs b/boa_temporal/src/utils.rs new file mode 100644 index 00000000000..88e18a9eef4 --- /dev/null +++ b/boa_temporal/src/utils.rs @@ -0,0 +1,352 @@ +//! Utility equations for Temporal + +use crate::{ + iso::IsoDate, + options::{TemporalRoundingMode, TemporalUnsignedRoundingMode}, + MS_PER_DAY, +}; + +use std::ops::Mul; +use tinystr::TinyStr4; + +// NOTE: Review the below for optimizations and add ALOT of tests. + +fn apply_unsigned_rounding_mode( + x: f64, + r1: f64, + r2: f64, + unsigned_rounding_mode: TemporalUnsignedRoundingMode, +) -> f64 { + // 1. If x is equal to r1, return r1. + if (x - r1).abs() == 0.0 { + return r1; + }; + // 2. Assert: r1 < x < r2. + assert!(r1 < x && x < r2); + // 3. Assert: unsignedRoundingMode is not undefined. + + // 4. If unsignedRoundingMode is zero, return r1. + if unsigned_rounding_mode == TemporalUnsignedRoundingMode::Zero { + return r1; + }; + // 5. If unsignedRoundingMode is infinity, return r2. + if unsigned_rounding_mode == TemporalUnsignedRoundingMode::Infinity { + return r2; + }; + + // 6. Let d1 be x – r1. + let d1 = x - r1; + // 7. Let d2 be r2 – x. + let d2 = r2 - x; + // 8. If d1 < d2, return r1. + if d1 < d2 { + return r1; + } + // 9. If d2 < d1, return r2. + if d2 < d1 { + return r2; + } + // 10. Assert: d1 is equal to d2. + assert!((d1 - d2).abs() == 0.0); + + // 11. If unsignedRoundingMode is half-zero, return r1. + if unsigned_rounding_mode == TemporalUnsignedRoundingMode::HalfZero { + return r1; + }; + // 12. If unsignedRoundingMode is half-infinity, return r2. + if unsigned_rounding_mode == TemporalUnsignedRoundingMode::HalfInfinity { + return r2; + }; + // 13. Assert: unsignedRoundingMode is half-even. + assert!(unsigned_rounding_mode == TemporalUnsignedRoundingMode::HalfEven); + // 14. Let cardinality be (r1 / (r2 – r1)) modulo 2. + let cardinality = (r1 / (r2 - r1)) % 2.0; + // 15. If cardinality is 0, return r1. + if cardinality == 0.0 { + return r1; + } + // 16. Return r2. + r2 +} + +/// 13.28 `RoundNumberToIncrement ( x, increment, roundingMode )` +pub(crate) fn round_number_to_increment( + x: f64, + increment: f64, + rounding_mode: TemporalRoundingMode, +) -> f64 { + // 1. Let quotient be x / increment. + let mut quotient = x / increment; + + // 2. If quotient < 0, then + let is_negative = if quotient < 0_f64 { + // a. Let isNegative be true. + // b. Set quotient to -quotient. + quotient = -quotient; + true + // 3. Else, + } else { + // a. Let isNegative be false. + false + }; + + // 4. Let unsignedRoundingMode be GetUnsignedRoundingMode(roundingMode, isNegative). + let unsigned_rounding_mode = rounding_mode.get_unsigned_round_mode(is_negative); + // 5. Let r1 be the largest integer such that r1 ≤ quotient. + let r1 = quotient.ceil(); + // 6. Let r2 be the smallest integer such that r2 > quotient. + let r2 = quotient.floor(); + // 7. Let rounded be ApplyUnsignedRoundingMode(quotient, r1, r2, unsignedRoundingMode). + let mut rounded = apply_unsigned_rounding_mode(quotient, r1, r2, unsigned_rounding_mode); + // 8. If isNegative is true, set rounded to -rounded. + if is_negative { + rounded = -rounded; + }; + // 9. Return rounded × increment. + rounded * increment +} + +// ==== Begin Date Equations ==== + +pub(crate) const MS_PER_HOUR: f64 = 3_600_000f64; +pub(crate) const MS_PER_MINUTE: f64 = 60_000f64; + +/// `EpochDaysToEpochMS` +/// +/// Functionally the same as Date's abstract operation `MakeDate` +pub(crate) fn epoch_days_to_epoch_ms(day: i32, time: f64) -> f64 { + f64::from(day).mul_add(f64::from(MS_PER_DAY), time).floor() +} + +/// `EpochTimeToDayNumber` +/// +/// This equation is the same ECMAScripts `Date(t)` +pub(crate) fn epoch_time_to_day_number(t: f64) -> i32 { + (t / f64::from(MS_PER_DAY)).floor() as i32 +} + +/// Mathematically determine the days in a year. +pub(crate) fn mathematical_days_in_year(y: i32) -> i32 { + if y % 4 != 0 { + 365 + } else if y % 4 == 0 && y % 100 != 0 { + 366 + } else if y % 100 == 0 && y % 400 != 0 { + 365 + } else { + // Assert that y is divisble by 400 to ensure we are returning the correct result. + assert_eq!(y % 400, 0); + 366 + } +} + +/// Returns the epoch day number for a given year. +pub(crate) fn epoch_day_number_for_year(y: f64) -> f64 { + 365.0f64.mul_add(y - 1970.0, ((y - 1969.0) / 4.0).floor()) - ((y - 1901.0) / 100.0).floor() + + ((y - 1601.0) / 400.0).floor() +} + +pub(crate) fn epoch_time_for_year(y: i32) -> f64 { + f64::from(MS_PER_DAY) * epoch_day_number_for_year(f64::from(y)) +} + +// NOTE: The below returns the epoch years (years since 1970). The spec +// appears to assume the below returns with the epoch applied. +pub(crate) fn epoch_time_to_epoch_year(t: f64) -> i32 { + // roughly calculate the largest possible year given the time t, + // then check and refine the year. + let day_count = epoch_time_to_day_number(t); + let mut year = day_count / 365; + loop { + if epoch_time_for_year(year) <= t { + break; + } + year -= 1; + } + + year + 1970 +} + +/// Returns either 1 (true) or 0 (false) +pub(crate) fn mathematical_in_leap_year(t: f64) -> i32 { + mathematical_days_in_year(epoch_time_to_epoch_year(t)) - 365 +} + +pub(crate) fn epoch_time_to_month_in_year(t: f64) -> u8 { + const DAYS: [i32; 11] = [30, 58, 89, 120, 150, 181, 212, 242, 272, 303, 333]; + const LEAP_DAYS: [i32; 11] = [30, 59, 90, 121, 151, 182, 213, 242, 272, 303, 334]; + + let in_leap_year = mathematical_in_leap_year(t) == 1; + let day = epoch_time_to_day_in_year(t); + + let result = if in_leap_year { + LEAP_DAYS.binary_search(&day) + } else { + DAYS.binary_search(&day) + }; + + match result { + Ok(i) | Err(i) => i as u8, + } +} + +pub(crate) fn epoch_time_for_month_given_year(m: i32, y: i32) -> f64 { + let leap_day = mathematical_days_in_year(y) - 365; + + let days = match m { + 0 => 1, + 1 => 31, + 2 => 59 + leap_day, + 3 => 90 + leap_day, + 4 => 121 + leap_day, + 5 => 151 + leap_day, + 6 => 182 + leap_day, + 7 => 213 + leap_day, + 8 => 243 + leap_day, + 9 => 273 + leap_day, + 10 => 304 + leap_day, + 11 => 334 + leap_day, + _ => unreachable!(), + }; + + (MS_PER_DAY as f64).mul(f64::from(days)) +} + +pub(crate) fn epoch_time_to_date(t: f64) -> u8 { + const OFFSETS: [i16; 12] = [ + 1, -30, -58, -89, -119, -150, -180, -211, -242, -272, -303, -333, + ]; + let day_in_year = epoch_time_to_day_in_year(t); + let in_leap_year = mathematical_in_leap_year(t); + let month = epoch_time_to_month_in_year(t); + + // Cast from i32 to usize should be safe as the return must be 0-11 + let mut date = day_in_year + i32::from(OFFSETS[month as usize]); + + if month >= 2 { + date -= in_leap_year; + } + + // This return of date should be < 31. + date as u8 +} + +pub(crate) fn epoch_time_to_day_in_year(t: f64) -> i32 { + epoch_time_to_day_number(t) + - (epoch_day_number_for_year(f64::from(epoch_time_to_epoch_year(t))) as i32) +} + +pub(crate) fn epoch_time_to_week_day(t: f64) -> i32 { + (epoch_time_to_day_number(t) + 4) % 7 +} + +// ==== End Date Equations ==== +// ==== Begin Calendar Equations ==== + +// NOTE: below was the iso methods in temporal::calendar -> Need to be reassessed. + +/// 12.2.31 `ISODaysInMonth ( year, month )` +pub(crate) fn iso_days_in_month(year: i32, month: i32) -> i32 { + match month { + 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31, + 4 | 6 | 9 | 11 => 30, + 2 => 28 + mathematical_in_leap_year(epoch_time_for_year(year)), + _ => unreachable!("an invalid month value is an implementation error."), + } +} + +/// 12.2.32 `ToISOWeekOfYear ( year, month, day )` +/// +/// Takes an `[[IsoYear]]`, `[[IsoMonth]]`, and `[[IsoDay]]` and returns a (week, year) record. +#[allow(unused)] +pub(crate) fn to_iso_week_of_year(year: i32, month: u8, day: u8) -> (i32, i32) { + // Function constants + // 2. Let wednesday be 3. + // 3. Let thursday be 4. + // 4. Let friday be 5. + // 5. Let saturday be 6. + // 6. Let daysInWeek be 7. + // 7. Let maxWeekNumber be 53. + let day_of_year = to_iso_day_of_year(year, month, day); + let day_of_week = to_iso_day_of_week(year, month, day); + let week = (day_of_week + 7 - day_of_week + 3) / 7; + + if week < 1 { + let first_day_of_year = to_iso_day_of_week(year, 1, 1); + if first_day_of_year == 5 { + return (53, year - 1); + } else if first_day_of_year == 6 + && mathematical_in_leap_year(epoch_time_for_year(year - 1)) == 1 + { + return (52, year - 1); + } + return (52, year - 1); + } else if week == 53 { + let days_in_year = mathematical_days_in_year(year); + let days_later_in_year = days_in_year - day_of_year; + let days_after_thursday = 4 - day_of_week; + if days_later_in_year < days_after_thursday { + return (1, year - 1); + } + } + (week, year) +} + +/// 12.2.33 `ISOMonthCode ( month )` +#[allow(unused)] +fn iso_month_code(month: i32) -> TinyStr4 { + // TODO: optimize + if month < 10 { + TinyStr4::from_bytes(format!("M0{month}").as_bytes()).expect("Cannot be more than 4 bytes") + } else { + TinyStr4::from_bytes(format!("M{month}").as_bytes()).expect("double check these later") + } +} + +/// 12.2.39 `ToISODayOfYear ( year, month, day )` +#[allow(unused)] +fn to_iso_day_of_year(year: i32, month: u8, day: u8) -> i32 { + // TODO: update fn parameter to take IsoDateRecord. + let iso = IsoDate::new_unchecked(year, month - 1, day); + let epoch_days = iso.to_epoch_days(); + epoch_time_to_day_in_year(epoch_days_to_epoch_ms(epoch_days, 0f64)) + 1 +} + +/// 12.2.40 `ToISODayOfWeek ( year, month, day )` +#[allow(unused)] +pub(crate) fn to_iso_day_of_week(year: i32, month: u8, day: u8) -> i32 { + let iso = IsoDate::new_unchecked(year, month - 1, day); + let epoch_days = iso.to_epoch_days(); + let day_of_week = epoch_time_to_week_day(epoch_days_to_epoch_ms(epoch_days, 0f64)); + if day_of_week == 0 { + return 7; + } + day_of_week +} + +// ==== End Calendar Equations ==== + +// ==== Tests ===== + +// TODO(nekevss): Add more to the below. + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn time_to_month() { + let oct_2023 = 1_696_459_917_000_f64; + let mar_1_2020 = 1_583_020_800_000_f64; + let feb_29_2020 = 1_582_934_400_000_f64; + let mar_1_2021 = 1_614_556_800_000_f64; + + assert_eq!(epoch_time_to_month_in_year(oct_2023), 9); + assert_eq!(epoch_time_to_month_in_year(mar_1_2020), 2); + assert_eq!(mathematical_in_leap_year(mar_1_2020), 1); + assert_eq!(epoch_time_to_month_in_year(feb_29_2020), 1); + assert_eq!(mathematical_in_leap_year(feb_29_2020), 1); + assert_eq!(epoch_time_to_month_in_year(mar_1_2021), 2); + assert_eq!(mathematical_in_leap_year(mar_1_2021), 0); + } +} diff --git a/boa_temporal/src/year_month.rs b/boa_temporal/src/year_month.rs new file mode 100644 index 00000000000..2061dcee1ac --- /dev/null +++ b/boa_temporal/src/year_month.rs @@ -0,0 +1,42 @@ +//! TemporalYearMonth + +use crate::{calendar::CalendarSlot, iso::IsoDate, options::ArithmeticOverflow, TemporalResult}; + +/// The `TemporalYearMonth` struct +#[derive(Debug, Default, Clone)] +pub struct TemporalYearMonth { + iso: IsoDate, + calendar: CalendarSlot, +} + +impl TemporalYearMonth { + #[inline] + /// Creates an unvalidated `TemporalYearMonth`. + pub(crate) fn new_unchecked(iso: IsoDate, calendar: CalendarSlot) -> Self { + Self { iso, calendar } + } + + #[inline] + /// Creates a new valid `TemporalYearMonth`. + pub fn new( + year: i32, + month: i32, + calendar: CalendarSlot, + overflow: ArithmeticOverflow, + ) -> TemporalResult { + let iso = IsoDate::new_year_month(year, month, overflow)?; + Ok(Self::new_unchecked(iso, calendar)) + } + + #[inline] + /// Returns a reference to this `YearMonth`'s `IsoDate` + pub fn iso_date(&self) -> IsoDate { + self.iso + } + + #[inline] + /// Returns a reference to `YearMonth`'s `CalendarSlot` + pub fn calendar(&self) -> &CalendarSlot { + &self.calendar + } +} diff --git a/boa_temporal/src/zoneddatetime.rs b/boa_temporal/src/zoneddatetime.rs new file mode 100644 index 00000000000..33d612b0960 --- /dev/null +++ b/boa_temporal/src/zoneddatetime.rs @@ -0,0 +1,6 @@ +//! The ZonedDateTime module. + +// NOTE: Mostly serves as a placeholder currently +// until the rest can be implemented. +/// `TemporalZoneDateTime` +pub struct TemporalZonedDateTime; From 730cf8f8f4e1cf939bd3e7606fcafc7d682f1dac Mon Sep 17 00:00:00 2001 From: nekevss Date: Sat, 11 Nov 2023 22:33:40 -0500 Subject: [PATCH 3/9] Add dyn Any context and some small changes --- boa_engine/Cargo.toml | 2 +- .../src/builtins/temporal/calendar/mod.rs | 54 ++-- .../src/builtins/temporal/calendar/object.rs | 112 +++++-- boa_temporal/Cargo.toml | 4 + boa_temporal/README.md | 8 +- boa_temporal/src/calendar.rs | 221 +++++++++---- boa_temporal/src/calendar/iso.rs | 42 ++- boa_temporal/src/date.rs | 165 ++++++---- boa_temporal/src/duration.rs | 295 ++++++++++-------- 9 files changed, 586 insertions(+), 317 deletions(-) diff --git a/boa_engine/Cargo.toml b/boa_engine/Cargo.toml index 5eff59ba3e3..a9cb58f670d 100644 --- a/boa_engine/Cargo.toml +++ b/boa_engine/Cargo.toml @@ -62,7 +62,7 @@ boa_profiler.workspace = true boa_macros.workspace = true boa_ast.workspace = true boa_parser.workspace = true -boa_temporal.workspace = true +boa_temporal = { workspace = true, features = ["context"] } serde = { workspace = true, features = ["derive", "rc"] } serde_json.workspace = true rand = "0.8.5" diff --git a/boa_engine/src/builtins/temporal/calendar/mod.rs b/boa_engine/src/builtins/temporal/calendar/mod.rs index 2ba39e612dd..a60c22d6ad7 100644 --- a/boa_engine/src/builtins/temporal/calendar/mod.rs +++ b/boa_engine/src/builtins/temporal/calendar/mod.rs @@ -159,7 +159,7 @@ impl Calendar { create_temporal_calendar(slot, None, context) } - fn get_id(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { + fn get_id(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let o = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("this value of Calendar must be an object.") })?; @@ -174,7 +174,7 @@ impl Calendar { CalendarSlot::Protocol(proto) => proto.clone(), }; - Ok(JsString::from(protocol.identifier()?.as_str()).into()) + Ok(JsString::from(protocol.identifier(context)?.as_str()).into()) } /// 15.8.2.1 `Temporal.Calendar.prototype.dateFromFields ( fields [ , options ] )` - Supercedes 12.5.4 @@ -217,7 +217,7 @@ impl Calendar { ]); // 6. If calendar.[[Identifier]] is "iso8601", then - let mut fields = if protocol.identifier()?.as_str() == "iso8601" { + let mut fields = if protocol.identifier(context)?.as_str() == "iso8601" { // a. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « "year", "day" »). let mut required_fields = Vec::from([js_string!("year"), js_string!("day")]); fields::prepare_temporal_fields( @@ -257,7 +257,7 @@ impl Calendar { // a. Perform ? CalendarResolveFields(calendar.[[Identifier]], fields, date). // b. Let result be ? CalendarDateToISO(calendar.[[Identifier]], fields, overflow). - let result = protocol.date_from_fields(&mut fields, overflow)?; + let result = protocol.date_from_fields(&mut fields, overflow, context)?; create_temporal_date(result, None, context).map(Into::into) } @@ -296,7 +296,7 @@ impl Calendar { ]); // 6. Set fields to ? PrepareTemporalFields(fields, « "month", "monthCode", "year" », « "year" »). - let mut fields = if protocol.identifier()?.as_str() == "iso8601" { + let mut fields = if protocol.identifier(context)?.as_str() == "iso8601" { // a. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « "year" »). let mut required_fields = Vec::from([js_string!("year")]); fields::prepare_temporal_fields( @@ -333,7 +333,7 @@ impl Calendar { let overflow = get_option::(&options, utf16!("overflow"), context)? .unwrap_or(ArithmeticOverflow::Constrain); - let result = protocol.year_month_from_fields(&mut fields, overflow)?; + let result = protocol.year_month_from_fields(&mut fields, overflow, context)?; create_temporal_year_month(result, None, context) } @@ -377,7 +377,7 @@ impl Calendar { ]); // 6. If calendar.[[Identifier]] is "iso8601", then - let mut fields = if protocol.identifier()?.as_str() == "iso8601" { + let mut fields = if protocol.identifier(context)?.as_str() == "iso8601" { // a. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « "day" »). let mut required_fields = Vec::from([js_string!("day")]); fields::prepare_temporal_fields( @@ -409,7 +409,7 @@ impl Calendar { let overflow = get_option(&options, utf16!("overflow"), context)? .unwrap_or(ArithmeticOverflow::Constrain); - let result = protocol.month_day_from_fields(&mut fields, overflow)?; + let result = protocol.month_day_from_fields(&mut fields, overflow, context)?; create_temporal_month_day(result, None, context) } @@ -451,7 +451,7 @@ 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)?; - let result = protocol.date_add(&date.inner, &duration, overflow)?; + let result = protocol.date_add(&date.inner, &duration, overflow, context)?; create_temporal_date(result, None, context).map(Into::into) } @@ -493,7 +493,7 @@ impl Calendar { )? .unwrap_or(TemporalUnit::Day); - let result = protocol.date_until(&one.inner, &two.inner, largest_unit)?; + let result = protocol.date_until(&one.inner, &two.inner, largest_unit, context)?; create_temporal_duration(result, None, context).map(Into::into) } @@ -516,7 +516,7 @@ impl Calendar { let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; let result = protocol - .era(&date_like)? + .era(&date_like, context)? .map_or(JsValue::undefined(), |r| JsString::from(r.as_str()).into()); Ok(result) @@ -540,7 +540,7 @@ impl Calendar { let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; let result = protocol - .era_year(&date_like)? + .era_year(&date_like, context)? .map_or(JsValue::undefined(), JsValue::from); Ok(result) @@ -563,7 +563,7 @@ impl Calendar { let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; - let result = protocol.year(&date_like)?; + let result = protocol.year(&date_like, context)?; Ok(result.into()) } @@ -590,7 +590,7 @@ impl Calendar { // 4. If Type(temporalDateLike) is not Object or temporalDateLike does not have an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], or [[InitializedTemporalYearMonth]] internal slot, then // 4.a. Set temporalDateLike to ? ToTemporalDate(temporalDateLike). - let result = protocol.month(&date_like)?; + let result = protocol.month(&date_like, context)?; Ok(result.into()) } @@ -612,7 +612,7 @@ impl Calendar { let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; - let result = protocol.month_code(&date_like)?; + let result = protocol.month_code(&date_like, context)?; Ok(JsString::from(result.as_str()).into()) } @@ -634,7 +634,7 @@ impl Calendar { let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; - let result = protocol.day(&date_like)?; + let result = protocol.day(&date_like, context)?; Ok(result.into()) } @@ -659,7 +659,7 @@ impl Calendar { // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike). let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; - let result = protocol.day_of_week(&CalendarDateLike::Date(date.inner.clone()))?; + let result = protocol.day_of_week(&CalendarDateLike::Date(date.inner.clone()), context)?; Ok(result.into()) } @@ -683,7 +683,7 @@ impl Calendar { // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike). let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; - let result = protocol.day_of_year(&CalendarDateLike::Date(date.inner.clone()))?; + let result = protocol.day_of_year(&CalendarDateLike::Date(date.inner.clone()), context)?; Ok(result.into()) } @@ -706,7 +706,7 @@ impl Calendar { // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike). let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; - let result = protocol.week_of_year(&CalendarDateLike::Date(date.inner.clone()))?; + let result = protocol.week_of_year(&CalendarDateLike::Date(date.inner.clone()), context)?; Ok(result.into()) } @@ -729,7 +729,7 @@ impl Calendar { // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike). let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; - let result = protocol.year_of_week(&CalendarDateLike::Date(date.inner.clone()))?; + let result = protocol.year_of_week(&CalendarDateLike::Date(date.inner.clone()), context)?; Ok(result.into()) } @@ -752,7 +752,7 @@ impl Calendar { // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike). let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; - let result = protocol.days_in_week(&CalendarDateLike::Date(date.inner.clone()))?; + let result = protocol.days_in_week(&CalendarDateLike::Date(date.inner.clone()), context)?; Ok(result.into()) } @@ -774,7 +774,7 @@ impl Calendar { let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; - let result = protocol.days_in_month(&date_like)?; + let result = protocol.days_in_month(&date_like, context)?; Ok(result.into()) } @@ -795,7 +795,7 @@ impl Calendar { }; let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; - let result = protocol.days_in_year(&date_like)?; + let result = protocol.days_in_year(&date_like, context)?; Ok(result.into()) } @@ -821,7 +821,7 @@ impl Calendar { let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; - let result = protocol.months_in_year(&date_like)?; + let result = protocol.months_in_year(&date_like, context)?; Ok(result.into()) } @@ -843,7 +843,7 @@ impl Calendar { let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; - let result = protocol.in_leap_year(&date_like)?; + let result = protocol.in_leap_year(&date_like, context)?; Ok(result.into()) } @@ -916,7 +916,7 @@ impl Calendar { // 7. Let result be fieldNames. // 8. If calendar.[[Identifier]] is not "iso8601", then - if protocol.identifier()?.as_str() != "iso8601" { + if protocol.identifier(context)?.as_str() != "iso8601" { // a. NOTE: Every built-in calendar preserves all input field names in output. // b. Let extraFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], fieldNames). let extended_fields = @@ -1160,7 +1160,7 @@ pub(crate) fn to_temporal_calendar_slot_value( } // Types: Box <- UserCalendar - let protocol = Box::new(CustomRuntimeCalendar::new(calendar_like, context)); + let protocol = Box::new(CustomRuntimeCalendar::new(calendar_like)); // c. Return temporalCalendarLike. return Ok(CalendarSlot::Protocol(protocol)); } diff --git a/boa_engine/src/builtins/temporal/calendar/object.rs b/boa_engine/src/builtins/temporal/calendar/object.rs index 90368afa06d..0d3a7a09523 100644 --- a/boa_engine/src/builtins/temporal/calendar/object.rs +++ b/boa_engine/src/builtins/temporal/calendar/object.rs @@ -1,11 +1,12 @@ //! Boa's implementation of a user-defined Anonymous Calendar. use crate::{ - builtins::temporal::{plain_date, plain_date_time, plain_month_day, plain_year_month}, + builtins::temporal::{plain_date, plain_month_day, plain_year_month}, object::ObjectKind, property::PropertyKey, Context, JsObject, JsValue, }; +use std::any::Any; use boa_macros::utf16; use boa_temporal::{ @@ -28,14 +29,12 @@ use boa_temporal::{ #[derive(Debug, Clone)] pub(crate) struct CustomRuntimeCalendar { calendar: JsObject, - ctx: *mut Context, } impl CustomRuntimeCalendar { - pub(crate) fn new(calendar: &JsObject, context: &mut Context) -> Self { + pub(crate) fn new(calendar: &JsObject) -> Self { Self { calendar: calendar.clone(), - ctx: context, } } } @@ -45,10 +44,13 @@ impl CalendarProtocol for CustomRuntimeCalendar { &self, fields: &mut TemporalFields, overflow: ArithmeticOverflow, + context: &mut dyn Any, ) -> TemporalResult { // Safety: Context lives for the life of the program and execution, so // this should, in theory, be valid. - let context = unsafe { &mut *self.ctx }; + let context = context + .downcast_mut::() + .expect("Context was not provided for a CustomCalendar."); let method = self .calendar @@ -80,6 +82,7 @@ impl CalendarProtocol for CustomRuntimeCalendar { &self, fields: &mut TemporalFields, overflow: ArithmeticOverflow, + context: &mut dyn Any, ) -> TemporalResult { todo!() } @@ -88,6 +91,7 @@ impl CalendarProtocol for CustomRuntimeCalendar { &self, fields: &mut TemporalFields, overflow: ArithmeticOverflow, + context: &mut dyn Any, ) -> TemporalResult { todo!() } @@ -97,6 +101,7 @@ impl CalendarProtocol for CustomRuntimeCalendar { date: &TemporalDate, duration: &Duration, overflow: ArithmeticOverflow, + context: &mut dyn Any, ) -> TemporalResult { todo!() } @@ -106,55 +111,90 @@ impl CalendarProtocol for CustomRuntimeCalendar { one: &TemporalDate, two: &TemporalDate, largest_unit: boa_temporal::options::TemporalUnit, + context: &mut dyn Any, ) -> TemporalResult { todo!() } // TODO: Determine validity of below errors. - fn era(&self, date_like: &CalendarDateLike) -> TemporalResult> { + fn era( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult> { Err(TemporalError::range().with_message("Objects do not implement era")) } - fn era_year(&self, date_like: &CalendarDateLike) -> TemporalResult> { + fn era_year( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult> { Err(TemporalError::range().with_message("Objects do not implement eraYear.")) } - fn year(&self, date_like: &CalendarDateLike) -> TemporalResult { + fn year(&self, date_like: &CalendarDateLike, context: &mut dyn Any) -> TemporalResult { todo!() } - fn month(&self, date_like: &CalendarDateLike) -> TemporalResult { + fn month(&self, date_like: &CalendarDateLike, context: &mut dyn Any) -> TemporalResult { todo!() } - fn month_code(&self, date_like: &CalendarDateLike) -> TemporalResult { + fn month_code( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult { todo!() } - fn day(&self, date_like: &CalendarDateLike) -> TemporalResult { + fn day(&self, date_like: &CalendarDateLike, context: &mut dyn Any) -> TemporalResult { todo!() } - fn day_of_week(&self, date_like: &CalendarDateLike) -> TemporalResult { + fn day_of_week( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult { todo!() } - fn day_of_year(&self, date_like: &CalendarDateLike) -> TemporalResult { + fn day_of_year( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult { todo!() } - fn week_of_year(&self, date_like: &CalendarDateLike) -> TemporalResult { + fn week_of_year( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult { todo!() } - fn year_of_week(&self, date_like: &CalendarDateLike) -> TemporalResult { + fn year_of_week( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult { todo!() } - fn days_in_week(&self, date_like: &CalendarDateLike) -> TemporalResult { + fn days_in_week( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult { // Safety: Context lives for the lifetime of the program's execution, so // this should, in theory, be safe memory to access. - let context = unsafe { &mut *self.ctx }; + let context = context + .downcast_mut::() + .expect("Context was not provided for a CustomCalendar."); let date_like = date_like_to_object(date_like, context)?; @@ -176,10 +216,16 @@ impl CalendarProtocol for CustomRuntimeCalendar { Ok(integral) } - fn days_in_month(&self, date_like: &CalendarDateLike) -> TemporalResult { + fn days_in_month( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult { // Safety: Context lives for the lifetime of the program's execution, so // this should, in theory, be safe memory to access. - let context = unsafe { &mut *self.ctx }; + let context = context + .downcast_mut::() + .expect("Context was not provided for a CustomCalendar."); let date_like = date_like_to_object(date_like, context)?; @@ -200,10 +246,16 @@ impl CalendarProtocol for CustomRuntimeCalendar { Ok(integral) } - fn days_in_year(&self, date_like: &CalendarDateLike) -> TemporalResult { + fn days_in_year( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult { // Safety: Context lives for the lifetime of the program's execution, so // this should, in theory, be safe memory to access. - let context = unsafe { &mut *self.ctx }; + let context = context + .downcast_mut::() + .expect("Context was not provided for a CustomCalendar."); let date_like = date_like_to_object(date_like, context)?; @@ -225,11 +277,19 @@ impl CalendarProtocol for CustomRuntimeCalendar { Ok(integral) } - fn months_in_year(&self, date_like: &CalendarDateLike) -> TemporalResult { + fn months_in_year( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult { todo!() } - fn in_leap_year(&self, date_like: &CalendarDateLike) -> TemporalResult { + fn in_leap_year( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult { todo!() } @@ -254,10 +314,12 @@ impl CalendarProtocol for CustomRuntimeCalendar { todo!() } - fn identifier(&self) -> TemporalResult { + fn identifier(&self, context: &mut dyn Any) -> TemporalResult { // Safety: Context lives for the lifetime of the program's execution, so // this should, in theory, be safe memory to access. - let context = unsafe { &mut *self.ctx }; + let context = context + .downcast_mut::() + .expect("Context was not provided for a CustomCalendar."); let identifier = self .calendar diff --git a/boa_temporal/Cargo.toml b/boa_temporal/Cargo.toml index b93d0f3bf45..c9894970afd 100644 --- a/boa_temporal/Cargo.toml +++ b/boa_temporal/Cargo.toml @@ -19,3 +19,7 @@ bitflags.workspace = true rustc-hash = { workspace = true, features = ["std"] } num-bigint = { workspace = true, features = ["serde"] } num-traits.workspace = true + + +[features] +context = [] diff --git a/boa_temporal/README.md b/boa_temporal/README.md index 4fc79874203..2211542e0f1 100644 --- a/boa_temporal/README.md +++ b/boa_temporal/README.md @@ -1,5 +1,11 @@ +# Temporal in Rust -# Temporal Crate +Provides a standard API for working with dates and time. + +IMPORTANT NOTE: The Temporal Proposal is still in Stage 3. As such, this crate should be viewed +as highly experimental until the proposal has been completely standardized and released. + +## Goal The intended goal of this crate is to provide an engine agnostic implementation of `ECMAScript`'s Temporal algorithms. diff --git a/boa_temporal/src/calendar.rs b/boa_temporal/src/calendar.rs index b2892bce818..8bac53ada46 100644 --- a/boa_temporal/src/calendar.rs +++ b/boa_temporal/src/calendar.rs @@ -7,7 +7,7 @@ //! it is up to implementers to implement an engine specific impl for handling JavaScript //! objects that may return true on `ImplementsCalendarProtocol`. -use std::str::FromStr; +use std::{any::Any, str::FromStr}; use crate::{ date::TemporalDate, @@ -162,18 +162,21 @@ pub trait CalendarProtocol: CalendarProtocolClone { &self, fields: &mut TemporalFields, overflow: ArithmeticOverflow, + context: &mut dyn Any, ) -> TemporalResult; /// Creates a `Temporal.PlainYearMonth` object from the provided fields. fn year_month_from_fields( &self, fields: &mut TemporalFields, overflow: ArithmeticOverflow, + context: &mut dyn Any, ) -> TemporalResult; /// Creates a `Temporal.PlainMonthDay` object from the provided fields. fn month_day_from_fields( &self, fields: &mut TemporalFields, overflow: ArithmeticOverflow, + context: &mut dyn Any, ) -> TemporalResult; /// Returns a `Temporal.PlainDate` based off an added date. fn date_add( @@ -181,6 +184,7 @@ pub trait CalendarProtocol: CalendarProtocolClone { date: &TemporalDate, duration: &Duration, overflow: ArithmeticOverflow, + context: &mut dyn Any, ) -> TemporalResult; /// Returns a `Temporal.Duration` representing the duration between two dates. fn date_until( @@ -188,38 +192,87 @@ pub trait CalendarProtocol: CalendarProtocolClone { one: &TemporalDate, two: &TemporalDate, largest_unit: TemporalUnit, + context: &mut dyn Any, ) -> TemporalResult; /// Returns the era for a given `temporaldatelike`. - fn era(&self, date_like: &CalendarDateLike) -> TemporalResult>; + fn era( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult>; /// Returns the era year for a given `temporaldatelike` - fn era_year(&self, date_like: &CalendarDateLike) -> TemporalResult>; + fn era_year( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult>; /// Returns the `year` for a given `temporaldatelike` - fn year(&self, date_like: &CalendarDateLike) -> TemporalResult; + fn year(&self, date_like: &CalendarDateLike, context: &mut dyn Any) -> TemporalResult; /// Returns the `month` for a given `temporaldatelike` - fn month(&self, date_like: &CalendarDateLike) -> TemporalResult; + fn month(&self, date_like: &CalendarDateLike, context: &mut dyn Any) -> TemporalResult; // 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: &CalendarDateLike) -> TemporalResult; + fn month_code( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult; /// Returns the `day` for a given `temporaldatelike` - fn day(&self, date_like: &CalendarDateLike) -> TemporalResult; + fn day(&self, date_like: &CalendarDateLike, context: &mut dyn Any) -> TemporalResult; /// Returns a value representing the day of the week for a date. - fn day_of_week(&self, date_like: &CalendarDateLike) -> TemporalResult; + fn day_of_week( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult; /// Returns a value representing the day of the year for a given calendar. - fn day_of_year(&self, date_like: &CalendarDateLike) -> TemporalResult; + fn day_of_year( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult; /// Returns a value representing the week of the year for a given calendar. - fn week_of_year(&self, date_like: &CalendarDateLike) -> TemporalResult; + fn week_of_year( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult; /// Returns the year of a given week. - fn year_of_week(&self, date_like: &CalendarDateLike) -> TemporalResult; + fn year_of_week( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult; /// Returns the days in a week for a given calendar. - fn days_in_week(&self, date_like: &CalendarDateLike) -> TemporalResult; + fn days_in_week( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult; /// Returns the days in a month for a given calendar. - fn days_in_month(&self, date_like: &CalendarDateLike) -> TemporalResult; + fn days_in_month( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult; /// Returns the days in a year for a given calendar. - fn days_in_year(&self, date_like: &CalendarDateLike) -> TemporalResult; + fn days_in_year( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult; /// Returns the months in a year for a given calendar. - fn months_in_year(&self, date_like: &CalendarDateLike) -> TemporalResult; + fn months_in_year( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult; /// Returns whether a value is within a leap year according to the designated calendar. - fn in_leap_year(&self, date_like: &CalendarDateLike) -> TemporalResult; + fn in_leap_year( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult; /// Resolve the `TemporalFields` for the implemented Calendar fn resolve_fields( &self, @@ -231,12 +284,16 @@ pub trait CalendarProtocol: CalendarProtocolClone { /// Return the fields to ignore for this Calendar based on provided keys. fn field_keys_to_ignore(&self, additional_keys: Vec) -> Vec; /// Debug name - fn identifier(&self) -> TemporalResult; + fn identifier(&self, context: &mut dyn Any) -> TemporalResult; } impl core::fmt::Debug for dyn CalendarProtocol { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{}", self.identifier().unwrap_or_default().as_str()) + write!( + f, + "{}", + self.identifier(&mut ()).unwrap_or_default().as_str() + ) } } @@ -279,13 +336,14 @@ impl CalendarSlot { date: &TemporalDate, duration: &Duration, overflow: ArithmeticOverflow, + context: &mut dyn Any, ) -> TemporalResult { match self { Self::Identifier(id) => { let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); - protocol.date_add(date, duration, overflow) + protocol.date_add(date, duration, overflow, context) } - Self::Protocol(protocol) => protocol.date_add(date, duration, overflow), + Self::Protocol(protocol) => protocol.date_add(date, duration, overflow, context), } } @@ -297,182 +355,223 @@ impl CalendarSlot { one: &TemporalDate, two: &TemporalDate, largest_unit: TemporalUnit, + context: &mut dyn Any, ) -> TemporalResult { match self { Self::Identifier(id) => { let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); - protocol.date_until(one, two, largest_unit) + protocol.date_until(one, two, largest_unit, context) } - Self::Protocol(protocol) => protocol.date_until(one, two, largest_unit), + Self::Protocol(protocol) => protocol.date_until(one, two, largest_unit, context), } } /// `CalendarYear` /// /// TODO: More docs. - pub fn year(&self, date_like: &CalendarDateLike) -> TemporalResult { + pub fn year(&self, date_like: &CalendarDateLike, context: &mut dyn Any) -> TemporalResult { match self { Self::Identifier(id) => { let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); - protocol.year(date_like) + protocol.year(date_like, &mut ()) } - Self::Protocol(protocol) => protocol.year(date_like), + Self::Protocol(protocol) => protocol.year(date_like, context), } } /// `CalendarMonth` /// /// TODO: More docs. - pub fn month(&self, date_like: &CalendarDateLike) -> TemporalResult { + pub fn month(&self, date_like: &CalendarDateLike, context: &mut dyn Any) -> TemporalResult { match self { Self::Identifier(id) => { let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); - protocol.month(date_like) + protocol.month(date_like, &mut ()) } - Self::Protocol(protocol) => protocol.month(date_like), + Self::Protocol(protocol) => protocol.month(date_like, context), } } /// `CalendarMonthCode` /// /// TODO: More docs. - pub fn month_code(&self, date_like: &CalendarDateLike) -> TemporalResult { + pub fn month_code( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult { match self { Self::Identifier(id) => { let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); - protocol.month_code(date_like) + protocol.month_code(date_like, &mut ()) } - Self::Protocol(protocol) => protocol.month_code(date_like), + Self::Protocol(protocol) => protocol.month_code(date_like, context), } } /// `CalendarDay` /// /// TODO: More docs. - pub fn day(&self, date_like: &CalendarDateLike) -> TemporalResult { + pub fn day(&self, date_like: &CalendarDateLike, context: &mut dyn Any) -> TemporalResult { match self { Self::Identifier(id) => { let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); - protocol.day(date_like) + protocol.day(date_like, &mut ()) } - Self::Protocol(protocol) => protocol.day(date_like), + Self::Protocol(protocol) => protocol.day(date_like, context), } } /// `CalendarDayOfWeek` /// /// TODO: More docs. - pub fn day_of_week(&self, date_like: &CalendarDateLike) -> TemporalResult { + pub fn day_of_week( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult { match self { Self::Identifier(id) => { let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); - protocol.day_of_week(date_like) + protocol.day_of_week(date_like, &mut ()) } - Self::Protocol(protocol) => protocol.day_of_week(date_like), + Self::Protocol(protocol) => protocol.day_of_week(date_like, context), } } /// `CalendarDayOfYear` /// /// TODO: More docs. - pub fn day_of_year(&self, date_like: &CalendarDateLike) -> TemporalResult { + pub fn day_of_year( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult { match self { Self::Identifier(id) => { let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); - protocol.day_of_year(date_like) + protocol.day_of_year(date_like, &mut ()) } - Self::Protocol(protocol) => protocol.day_of_year(date_like), + Self::Protocol(protocol) => protocol.day_of_year(date_like, context), } } /// `CalendarWeekOfYear` /// /// TODO: More docs. - pub fn week_of_year(&self, date_like: &CalendarDateLike) -> TemporalResult { + pub fn week_of_year( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult { match self { Self::Identifier(id) => { let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); - protocol.week_of_year(date_like) + protocol.week_of_year(date_like, &mut ()) } - Self::Protocol(protocol) => protocol.week_of_year(date_like), + Self::Protocol(protocol) => protocol.week_of_year(date_like, context), } } /// `CalendarYearOfWeek` /// /// TODO: More docs. - pub fn year_of_week(&self, date_like: &CalendarDateLike) -> TemporalResult { + pub fn year_of_week( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult { match self { Self::Identifier(id) => { let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); - protocol.year_of_week(date_like) + protocol.year_of_week(date_like, &mut ()) } - Self::Protocol(protocol) => protocol.year_of_week(date_like), + Self::Protocol(protocol) => protocol.year_of_week(date_like, context), } } /// `CalendarDaysInWeek` /// /// TODO: More docs. - pub fn days_in_week(&self, date_like: &CalendarDateLike) -> TemporalResult { + pub fn days_in_week( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult { match self { Self::Identifier(id) => { let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); - protocol.days_in_week(date_like) + protocol.days_in_week(date_like, &mut ()) } - Self::Protocol(protocol) => protocol.days_in_week(date_like), + Self::Protocol(protocol) => protocol.days_in_week(date_like, context), } } /// `CalendarDaysInMonth` /// /// TODO: More docs. - pub fn days_in_month(&self, date_like: &CalendarDateLike) -> TemporalResult { + pub fn days_in_month( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult { match self { Self::Identifier(id) => { let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); - protocol.days_in_month(date_like) + protocol.days_in_month(date_like, &mut ()) } - Self::Protocol(protocol) => protocol.days_in_month(date_like), + Self::Protocol(protocol) => protocol.days_in_month(date_like, context), } } /// `CalendarDaysInYear` /// /// TODO: More docs. - pub fn days_in_year(&self, date_like: &CalendarDateLike) -> TemporalResult { + pub fn days_in_year( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult { match self { Self::Identifier(id) => { let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); - protocol.days_in_year(date_like) + protocol.days_in_year(date_like, &mut ()) } - Self::Protocol(protocol) => protocol.days_in_year(date_like), + Self::Protocol(protocol) => protocol.days_in_year(date_like, context), } } /// `CalendarMonthsInYear` /// /// TODO: More docs. - pub fn months_in_year(&self, date_like: &CalendarDateLike) -> TemporalResult { + pub fn months_in_year( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult { match self { Self::Identifier(id) => { let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); - protocol.months_in_year(date_like) + protocol.months_in_year(date_like, &mut ()) } - Self::Protocol(protocol) => protocol.months_in_year(date_like), + Self::Protocol(protocol) => protocol.months_in_year(date_like, context), } } /// `CalendarInLeapYear` /// /// TODO: More docs. - pub fn in_leap_year(&self, date_like: &CalendarDateLike) -> TemporalResult { + pub fn in_leap_year( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult { match self { Self::Identifier(id) => { let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); - protocol.in_leap_year(date_like) + protocol.in_leap_year(date_like, &mut ()) } - Self::Protocol(protocol) => protocol.in_leap_year(date_like), + Self::Protocol(protocol) => protocol.in_leap_year(date_like, context), } } } diff --git a/boa_temporal/src/calendar/iso.rs b/boa_temporal/src/calendar/iso.rs index cb02415262f..30cfe11a4e6 100644 --- a/boa_temporal/src/calendar/iso.rs +++ b/boa_temporal/src/calendar/iso.rs @@ -11,6 +11,7 @@ use crate::{ year_month::TemporalYearMonth, TemporalResult, }; +use std::any::Any; use tinystr::{tinystr, TinyStr4, TinyStr8}; @@ -31,6 +32,7 @@ impl CalendarProtocol for IsoCalendar { &self, fields: &mut TemporalFields, overflow: ArithmeticOverflow, + _: &mut dyn Any, ) -> TemporalResult { // NOTE: we are in ISO by default here. // a. Perform ? ISOResolveMonth(fields). @@ -54,6 +56,7 @@ impl CalendarProtocol for IsoCalendar { &self, fields: &mut TemporalFields, overflow: ArithmeticOverflow, + _: &mut dyn Any, ) -> TemporalResult { // 9. If calendar.[[Identifier]] is "iso8601", then // a. Perform ? ISOResolveMonth(fields). @@ -77,6 +80,7 @@ impl CalendarProtocol for IsoCalendar { &self, fields: &mut TemporalFields, overflow: ArithmeticOverflow, + _: &mut dyn Any, ) -> TemporalResult { // 8. Perform ? ISOResolveMonth(fields). fields.iso_resolve_month()?; @@ -100,6 +104,7 @@ impl CalendarProtocol for IsoCalendar { _date: &TemporalDate, _duration: &Duration, _overflow: ArithmeticOverflow, + _: &mut dyn Any, ) -> TemporalResult { // TODO: Not stable on `ICU4X`. Implement once completed. Err(TemporalError::range() @@ -118,6 +123,7 @@ impl CalendarProtocol for IsoCalendar { _one: &TemporalDate, _two: &TemporalDate, _largest_unit: TemporalUnit, + _: &mut dyn Any, ) -> TemporalResult { // TODO: Not stable on `ICU4X`. Implement once completed. Err(TemporalError::range() @@ -129,52 +135,56 @@ impl CalendarProtocol for IsoCalendar { } /// `Temporal.Calendar.prototype.era( dateLike )` for iso8601 calendar. - fn era(&self, _: &CalendarDateLike) -> TemporalResult> { + fn era(&self, _: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult> { // Returns undefined on iso8601. Ok(None) } /// `Temporal.Calendar.prototype.eraYear( dateLike )` for iso8601 calendar. - fn era_year(&self, _: &CalendarDateLike) -> TemporalResult> { + fn era_year(&self, _: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult> { // Returns undefined on iso8601. Ok(None) } /// Returns the `year` for the `Iso` calendar. - fn year(&self, date_like: &CalendarDateLike) -> TemporalResult { + fn year(&self, date_like: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult { Ok(date_like.as_iso_date().year()) } /// Returns the `month` for the `Iso` calendar. - fn month(&self, date_like: &CalendarDateLike) -> TemporalResult { + fn month(&self, date_like: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult { Ok(date_like.as_iso_date().month()) } /// Returns the `monthCode` for the `Iso` calendar. - fn month_code(&self, date_like: &CalendarDateLike) -> TemporalResult { + fn month_code( + &self, + date_like: &CalendarDateLike, + _: &mut dyn Any, + ) -> TemporalResult { let date = date_like.as_iso_date().as_icu4x()?; Ok(date.month().code.0) } /// Returns the `day` for the `Iso` calendar. - fn day(&self, date_like: &CalendarDateLike) -> TemporalResult { + fn day(&self, date_like: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult { Ok(date_like.as_iso_date().day()) } /// Returns the `dayOfWeek` for the `Iso` calendar. - fn day_of_week(&self, date_like: &CalendarDateLike) -> TemporalResult { + fn day_of_week(&self, date_like: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult { let date = date_like.as_iso_date().as_icu4x()?; Ok(date.day_of_week() as i32) } /// Returns the `dayOfYear` for the `Iso` calendar. - fn day_of_year(&self, date_like: &CalendarDateLike) -> TemporalResult { + fn day_of_year(&self, date_like: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult { let date = date_like.as_iso_date().as_icu4x()?; 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: &CalendarDateLike) -> TemporalResult { + fn week_of_year(&self, date_like: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult { let date = date_like.as_iso_date().as_icu4x()?; let week_calculator = WeekCalculator::default(); @@ -187,7 +197,7 @@ impl CalendarProtocol for IsoCalendar { } /// Returns the `yearOfWeek` for the `Iso` calendar. - fn year_of_week(&self, date_like: &CalendarDateLike) -> TemporalResult { + fn year_of_week(&self, date_like: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult { let date = date_like.as_iso_date().as_icu4x()?; let week_calculator = WeekCalculator::default(); @@ -206,29 +216,29 @@ impl CalendarProtocol for IsoCalendar { } /// Returns the `daysInWeek` value for the `Iso` calendar. - fn days_in_week(&self, _: &CalendarDateLike) -> TemporalResult { + fn days_in_week(&self, _: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult { Ok(7) } /// Returns the `daysInMonth` value for the `Iso` calendar. - fn days_in_month(&self, date_like: &CalendarDateLike) -> TemporalResult { + fn days_in_month(&self, date_like: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult { let date = date_like.as_iso_date().as_icu4x()?; Ok(i32::from(date.days_in_month())) } /// Returns the `daysInYear` value for the `Iso` calendar. - fn days_in_year(&self, date_like: &CalendarDateLike) -> TemporalResult { + fn days_in_year(&self, date_like: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult { let date = date_like.as_iso_date().as_icu4x()?; Ok(i32::from(date.days_in_year())) } /// Return the amount of months in an ISO Calendar. - fn months_in_year(&self, _: &CalendarDateLike) -> TemporalResult { + fn months_in_year(&self, _: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult { Ok(12) } /// Returns whether provided date is in a leap year according to this calendar. - fn in_leap_year(&self, date_like: &CalendarDateLike) -> TemporalResult { + fn in_leap_year(&self, date_like: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult { // `ICU4X`'s `CalendarArithmetic` is currently private. Ok(utils::mathematical_days_in_year(date_like.as_iso_date().year()) == 366) } @@ -268,7 +278,7 @@ impl CalendarProtocol for IsoCalendar { // NOTE: This is currently not a name that is compliant with // the Temporal proposal. For debugging purposes only. /// Returns the debug name. - fn identifier(&self) -> TemporalResult { + fn identifier(&self, _: &mut dyn Any) -> TemporalResult { Ok("iso8601".to_string()) } } diff --git a/boa_temporal/src/date.rs b/boa_temporal/src/date.rs index 286a1bbc588..0f07040b308 100644 --- a/boa_temporal/src/date.rs +++ b/boa_temporal/src/date.rs @@ -8,6 +8,7 @@ use crate::{ options::{ArithmeticOverflow, TemporalUnit}, TemporalResult, }; +use std::any::Any; /// The `Temporal.PlainDate` equivalent #[derive(Debug, Default, Clone)] @@ -23,6 +24,80 @@ impl TemporalDate { pub(crate) fn new_unchecked(iso: IsoDate, calendar: CalendarSlot) -> Self { Self { iso, calendar } } + + #[inline] + /// `DifferenceDate` + pub(crate) fn diff_date( + &self, + other: &Self, + largest_unit: TemporalUnit, + context: &mut dyn Any, + ) -> TemporalResult { + if self.iso.year() == other.iso.year() + && self.iso.month() == other.iso.month() + && self.iso.day() == other.iso.day() + { + return Ok(Duration::default()); + } + + if largest_unit == TemporalUnit::Day { + let days = self.days_until(other); + return Ok(Duration::from_date_duration(DateDuration::new( + 0f64, + 0f64, + 0f64, + f64::from(days), + ))); + } + + self.calendar() + .date_until(self, other, largest_unit, context) + } + + #[inline] + /// Internal `AddDate` function + pub(crate) fn add_date( + &self, + duration: &Duration, + overflow: ArithmeticOverflow, + context: &mut dyn Any, + ) -> TemporalResult { + // 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.date().years() != 0.0 + || duration.date().months() != 0.0 + || duration.date().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 self.calendar().date_add(&self, duration, overflow, context); + } + + // 3. Let overflow be ? ToTemporalOverflow(options). + // 4. Let days be ? BalanceTimeDuration(duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], "day").[[Days]]. + let (days, _) = duration.balance_time_duration(TemporalUnit::Day)?; + + // 5. Let result be ? AddISODate(plainDate.[[ISOYear]], plainDate.[[ISOMonth]], plainDate.[[ISODay]], 0, 0, 0, days, overflow). + let result = self + .iso + .add_iso_date(&DateDuration::new(0f64, 0f64, 0f64, days), overflow)?; + + Ok(Self::new_unchecked(result, self.calendar().clone())) + } + + #[inline] + /// Returns a new moved date and the days associated with that adjustment + pub(crate) fn move_relative_date( + &self, + duration: &Duration, + context: &mut dyn Any, + ) -> TemporalResult<(TemporalDate, f64)> { + let new_date = self.add_date(duration, ArithmeticOverflow::Constrain, context)?; + let days = f64::from(self.days_until(&new_date)); + Ok((new_date, days)) + } } // ==== Public API ==== @@ -86,72 +161,56 @@ impl TemporalDate { } #[inline] - /// Returns a new moved date and the days associated with that adjustment - pub fn move_relative_date(&self, duration: &Duration) -> TemporalResult<(TemporalDate, f64)> { - let new_date = self.add_to_date(duration, ArithmeticOverflow::Constrain)?; - let days = f64::from(self.days_until(&new_date)); - Ok((new_date, days)) + /// `DaysUntil` + /// + /// Calculates the epoch days between two `TemporalDate`s + pub fn days_until(&self, other: &Self) -> i32 { + other.iso.to_epoch_days() - self.iso.to_epoch_days() } +} +// ==== Context based API ==== + +impl TemporalDate { + #[cfg(feature = "context")] #[inline] - /// `AddDate` pub fn add_to_date( &self, duration: &Duration, overflow: ArithmeticOverflow, + context: &mut dyn Any, ) -> TemporalResult { - // 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.date().years() != 0.0 - || duration.date().months() != 0.0 - || duration.date().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 self.calendar().date_add(&self, duration, overflow); - } - - // 3. Let overflow be ? ToTemporalOverflow(options). - // 4. Let days be ? BalanceTimeDuration(duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], "day").[[Days]]. - let (days, _) = duration.balance_time_duration(TemporalUnit::Day)?; - - // 5. Let result be ? AddISODate(plainDate.[[ISOYear]], plainDate.[[ISOMonth]], plainDate.[[ISODay]], 0, 0, 0, days, overflow). - let result = self - .iso - .add_iso_date(&DateDuration::new(0f64, 0f64, 0f64, days), overflow)?; - - Ok(Self::new_unchecked(result, self.calendar().clone())) + self.add_date(duration, overflow, context) } - /// `DifferenceDate` - pub fn diff_date(&self, other: &Self, largest_unit: TemporalUnit) -> TemporalResult { - if self.iso.year() == other.iso.year() - && self.iso.month() == other.iso.month() - && self.iso.day() == other.iso.day() - { - return Ok(Duration::default()); - } - - if largest_unit == TemporalUnit::Day { - let days = self.days_until(other); - return Ok(Duration::from_date_duration(DateDuration::new( - 0f64, - 0f64, - 0f64, - f64::from(days), - ))); - } + #[cfg(not(feature = "context"))] + #[inline] + pub fn add_to_date( + &self, + duration: &Duration, + overflow: ArithmeticOverflow, + ) -> TemporalResult { + self.add_date(duration, overflow, &mut ()) + } - self.calendar().date_until(self, other, largest_unit) + #[cfg(feature = "context")] + #[inline] + pub fn difference_date( + &self, + duration: &Duration, + overflow: ArithmeticOverflow, + context: &mut dyn Any, + ) -> TemporalResult { + self.add_date(duration, overflow, context) } + #[cfg(not(feature = "context"))] #[inline] - /// `DaysUntil` - /// - /// Calculates the epoch days between two `TemporalDate`s - pub fn days_until(&self, other: &Self) -> i32 { - other.iso.to_epoch_days() - self.iso.to_epoch_days() + pub fn difference_date( + &self, + other: &Self, + largest_unit: TemporalUnit, + ) -> TemporalResult { + self.diff_date(other, largest_unit, &mut ()) } } diff --git a/boa_temporal/src/duration.rs b/boa_temporal/src/duration.rs index c1264c0101d..3d5d1edbc2d 100644 --- a/boa_temporal/src/duration.rs +++ b/boa_temporal/src/duration.rs @@ -10,6 +10,7 @@ use crate::{ zoneddatetime::TemporalZonedDateTime, TemporalError, TemporalResult, NS_PER_DAY, }; +use std::any::Any; // ==== `DateDuration` ==== @@ -243,12 +244,42 @@ pub struct Duration { time: TimeDuration, } +// NOTE(nekevss): Structure of the below is going to be a little convoluted, +// but intended to section everything based on the below +// +// Notation - [section](sub-section(s)). +// +// Sections: +// - Creation (private/public) +// - Getters/Setters +// - Methods (private/public/feature) +// + +// ==== Private Creation methods ==== + impl Duration { /// Creates a new `Duration` from a `DateDuration` and `TimeDuration`. pub(crate) const fn new_unchecked(date: DateDuration, time: TimeDuration) -> Self { Self { date, time } } + /// Utility function to create a year duration. + pub(crate) fn one_year(year_value: f64) -> Self { + Self::from_date_duration(DateDuration::new(year_value, 0f64, 0f64, 0f64)) + } + + /// Utility function to create a month duration. + pub(crate) fn one_month(month_value: f64) -> Self { + Self::from_date_duration(DateDuration::new(0f64, month_value, 0f64, 0f64)) + } + + /// Utility function to create a week duration. + pub(crate) fn one_week(week_value: f64) -> Self { + Self::from_date_duration(DateDuration::new(0f64, 0f64, week_value, 0f64)) + } +} + +impl Duration { /// Creates a new validated `Duration`. #[allow(clippy::too_many_arguments)] pub fn new( @@ -306,28 +337,15 @@ impl Duration { } } - /// Utility function to create a one year duration. - pub(crate) fn one_year(year_value: f64) -> Self { - Self::from_date_duration(DateDuration::new(year_value, 0f64, 0f64, 0f64)) - } - - /// Utility function to create a one month duration. - pub(crate) fn one_month(month_value: f64) -> Self { - Self::from_date_duration(DateDuration::new(0f64, month_value, 0f64, 0f64)) - } - - /// Utility function to create a one week duration. - pub(crate) fn one_week(week_value: f64) -> Self { - Self::from_date_duration(DateDuration::new(0f64, 0f64, week_value, 0f64)) - } - - /// Utility function to return if the Durations values are within their valid ranges. + /// Return if the Durations values are within their valid ranges. #[inline] pub fn is_time_within_range(&self) -> bool { self.time.is_within_range() } } +// ==== Public `Duration` Getters/Setters ==== + impl Duration { /// Returns a reference to the inner `TimeDuration` pub fn time(&self) -> &TimeDuration { @@ -423,12 +441,12 @@ impl Iterator for DurationIter<'_> { 1 => Some(self.duration.date.months()), 2 => Some(self.duration.date.weeks()), 3 => Some(self.duration.date.days()), - 4 => Some(self.duration.time.hours), - 5 => Some(self.duration.time.minutes), - 6 => Some(self.duration.time.seconds), - 7 => Some(self.duration.time.milliseconds), - 8 => Some(self.duration.time.microseconds), - 9 => Some(self.duration.time.nanoseconds), + 4 => Some(self.duration.time.hours()), + 5 => Some(self.duration.time.minutes()), + 6 => Some(self.duration.time.seconds()), + 7 => Some(self.duration.time.milliseconds()), + 8 => Some(self.duration.time.microseconds()), + 9 => Some(self.duration.time.nanoseconds()), _ => None, }; self.index += 1; @@ -436,56 +454,16 @@ impl Iterator for DurationIter<'_> { } } -// ==== Duration method ==== - -impl Duration { - /// Returns the absolute value of `Duration`. - pub fn abs(&self) -> Self { - Self { - date: DateDuration::new( - self.date.years.abs(), - self.date.months.abs(), - self.date.weeks.abs(), - self.date.days.abs(), - ), - time: TimeDuration::new( - self.time.hours.abs(), - self.time.minutes.abs(), - self.time.seconds.abs(), - self.time.milliseconds.abs(), - self.time.microseconds.abs(), - self.time.nanoseconds.abs(), - ), - } - } -} +// ==== Private Duration methods ==== impl Duration { /// Returns the duration time values as a vec - fn time_values(&self) -> Vec { + pub(crate) fn time_values(&self) -> Vec { let mut values = Vec::from([self.date.days]); values.extend(self.time.into_iter()); values } - /// 7.5.10 `DurationSign ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds )` - /// - /// Determines the sign for the current self. - pub fn duration_sign(&self) -> i32 { - // 1. For each value v of « years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds », do - for v in self { - // a. If v < 0, return -1. - if v < 0_f64 { - return -1; - // b. If v > 0, return 1. - } else if v > 0_f64 { - return 1; - } - } - // 2. Return 0. - 0 - } - /// 7.5.11 `IsValidDuration ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds )` /// /// Checks if the current `DurationRecord` is a valid self. @@ -511,30 +489,8 @@ impl Duration { true } - /// 7.5.12 `DefaultTemporalLargestUnit ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds )` - pub fn default_temporal_largest_unit(&self) -> TemporalUnit { - for (index, value) in self.into_iter().enumerate() { - if value != 0.0 { - match index { - 0 => return TemporalUnit::Year, - 1 => return TemporalUnit::Month, - 2 => return TemporalUnit::Week, - 3 => return TemporalUnit::Day, - 4 => return TemporalUnit::Hour, - 5 => return TemporalUnit::Minute, - 6 => return TemporalUnit::Second, - 7 => return TemporalUnit::Millisecond, - 8 => return TemporalUnit::Microsecond, - _ => {} - } - } - } - - TemporalUnit::Nanosecond - } - /// 7.5.17 `TotalDurationNanoseconds ( days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, offsetShift )` - fn total_duration_nanoseconds(&self, offset_shift: f64) -> f64 { + pub(crate) fn total_duration_nanoseconds(&self, offset_shift: f64) -> f64 { let nanoseconds = if self.date.days == 0_f64 { self.time.nanoseconds } else { @@ -551,26 +507,6 @@ impl Duration { .mul_add(1_000_f64, nanoseconds) } - /// Abstract Operation 7.5.17 `BalanceTimeDuration ( days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, largestUnit )` - pub fn balance_time_duration( - &self, - largest_unit: TemporalUnit, - ) -> TemporalResult<(f64, TimeDuration)> { - // 1. Let balanceResult be ? BalancePossiblyInfiniteDuration(days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, largestUnit, relativeTo). - let balance_result = self.balance_possibly_infinite_time_duration(largest_unit)?; - - // 2. If balanceResult is positive overflow or negative overflow, then - let Some(result) = balance_result else { - // a. Throw a RangeError exception. - return Err(TemporalError::range() - .with_message("duration overflowed viable range.") - .into()); - }; - // 3. Else, - // a. Return balanceResult. - Ok(result) - } - /// Abstract Operation 7.5.18 `BalancePossiblyInfiniteDuration ( days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, largestUnit )` pub(crate) fn balance_possibly_infinite_time_duration( &self, @@ -727,13 +663,13 @@ impl Duration { Ok(Some((result_days, result))) } - // TODO: move to DateDuration. /// 7.5.21 UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit, plainRelativeTo )` #[allow(dead_code)] - pub fn unbalance_duration_relative( + pub(crate) fn unbalance_duration_relative( &self, largest_unit: TemporalUnit, plain_relative_to: Option<&TemporalDate>, + context: &mut dyn Any, ) -> TemporalResult { // 1. Let allZero be false. // 2. If years = 0, and months = 0, and weeks = 0, and days = 0, set allZero to true. @@ -798,6 +734,7 @@ impl Duration { &plain_relative_to, &one_year, ArithmeticOverflow::Constrain, + context, )?; // ii. Let untilOptions be OrdinaryObjectCreate(null). @@ -807,6 +744,7 @@ impl Duration { &plain_relative_to, &new_relative_to, TemporalUnit::Month, + context, )?; // v. Let oneYearMonths be untilResult.[[Months]]. @@ -858,7 +796,7 @@ impl Duration { // e. Repeat, while years ≠ 0, while years != 0f64 { // i. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, oneYear, dateAdd). - let move_result = plain_relative_to.move_relative_date(&one_year)?; + let move_result = plain_relative_to.move_relative_date(&one_year, context)?; // ii. Set plainRelativeTo to moveResult.[[RelativeTo]]. plain_relative_to = move_result.0; @@ -872,7 +810,7 @@ impl Duration { // f. Repeat, while months ≠ 0, while months != 0f64 { // i. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, oneMonth, dateAdd). - let move_result = plain_relative_to.move_relative_date(&one_month)?; + let move_result = plain_relative_to.move_relative_date(&one_month, context)?; // ii. Set plainRelativeTo to moveResult.[[RelativeTo]]. plain_relative_to = move_result.0; // iii. Set days to days + moveResult.[[Days]]. @@ -911,7 +849,7 @@ impl Duration { // a. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, oneYear, dateAdd). while years != 0f64 { // a. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, oneYear, dateAdd). - let move_result = plain_relative_to.move_relative_date(&one_year)?; + let move_result = plain_relative_to.move_relative_date(&one_year, context)?; // b. Set plainRelativeTo to moveResult.[[RelativeTo]]. plain_relative_to = move_result.0; @@ -925,7 +863,7 @@ impl Duration { // 18. Repeat, while months ≠ 0, while months != 0f64 { // a. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, oneMonth, dateAdd). - let move_result = plain_relative_to.move_relative_date(&one_month)?; + let move_result = plain_relative_to.move_relative_date(&one_month, context)?; // b. Set plainRelativeTo to moveResult.[[RelativeTo]]. plain_relative_to = move_result.0; // c. Set days to days +moveResult.[[Days]]. @@ -938,7 +876,7 @@ impl Duration { // 19. Repeat, while weeks ≠ 0, while weeks != 0f64 { // a. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, oneWeek, dateAdd). - let move_result = plain_relative_to.move_relative_date(&one_week)?; + let move_result = plain_relative_to.move_relative_date(&one_week, context)?; // b. Set plainRelativeTo to moveResult.[[RelativeTo]]. plain_relative_to = move_result.0; // c. Set days to days + moveResult.[[Days]]. @@ -958,6 +896,7 @@ impl Duration { &self, largest_unit: TemporalUnit, plain_relative_to: Option<&TemporalDate>, + context: &mut dyn Any, ) -> TemporalResult { let mut result = DateDuration::from(self.date); @@ -1010,7 +949,7 @@ impl Duration { // d. Let newRelativeTo be moveResult.[[RelativeTo]]. // e. Let oneYearDays be moveResult.[[Days]]. let (mut new_relative_to, mut one_year_days) = - plain_relative_to.move_relative_date(&one_year)?; + plain_relative_to.move_relative_date(&one_year, context)?; // f. Repeat, while abs(days) ≥ abs(oneYearDays), while result.days().abs() >= one_year_days.abs() { @@ -1023,7 +962,7 @@ impl Duration { // iii. Set relativeTo to newRelativeTo. plain_relative_to = new_relative_to; // iv. Set moveResult to ? MoveRelativeDate(calendar, relativeTo, oneYear, dateAdd). - let move_result = plain_relative_to.move_relative_date(&one_year)?; + let move_result = plain_relative_to.move_relative_date(&one_year, context)?; // v. Set newRelativeTo to moveResult.[[RelativeTo]]. new_relative_to = move_result.0; @@ -1035,7 +974,7 @@ impl Duration { // h. Set newRelativeTo to moveResult.[[RelativeTo]]. // i. Let oneMonthDays be moveResult.[[Days]]. let (mut new_relative_to, mut one_month_days) = - plain_relative_to.move_relative_date(&one_month)?; + plain_relative_to.move_relative_date(&one_month, context)?; // j. Repeat, while abs(days) ≥ abs(oneMonthDays), while result.days().abs() >= one_month_days.abs() { @@ -1047,7 +986,7 @@ impl Duration { // iii. Set relativeTo to newRelativeTo. plain_relative_to = new_relative_to; // iv. Set moveResult to ? MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd). - let move_result = plain_relative_to.move_relative_date(&one_month)?; + let move_result = plain_relative_to.move_relative_date(&one_month, context)?; // v. Set newRelativeTo to moveResult.[[RelativeTo]]. new_relative_to = move_result.0; @@ -1060,6 +999,7 @@ impl Duration { &plain_relative_to, &one_year, ArithmeticOverflow::Constrain, + context, )?; // l. If calendar is an Object, then @@ -1074,6 +1014,7 @@ impl Duration { &plain_relative_to, &new_relative_to, TemporalUnit::Month, + context, )?; // q. Let oneYearMonths be untilResult.[[Months]]. @@ -1094,6 +1035,7 @@ impl Duration { &plain_relative_to, &one_year, ArithmeticOverflow::Constrain, + context, )?; // v. Set untilOptions to OrdinaryObjectCreate(null). @@ -1103,6 +1045,7 @@ impl Duration { &plain_relative_to, &new_relative_to, TemporalUnit::Month, + context, )?; // viii. Set oneYearMonths to untilResult.[[Months]]. @@ -1120,7 +1063,7 @@ impl Duration { // d. Let newRelativeTo be moveResult.[[RelativeTo]]. // e. Let oneMonthDays be moveResult.[[Days]]. let (mut new_relative_to, mut one_month_days) = - plain_relative_to.move_relative_date(&one_month)?; + plain_relative_to.move_relative_date(&one_month, context)?; // f. Repeat, while abs(days) ≥ abs(oneMonthDays), while result.days().abs() >= one_month_days.abs() { @@ -1134,7 +1077,7 @@ impl Duration { plain_relative_to = new_relative_to; // iv. Set moveResult to ? MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd). - let move_result = plain_relative_to.move_relative_date(&one_month)?; + let move_result = plain_relative_to.move_relative_date(&one_month, context)?; // v. Set newRelativeTo to moveResult.[[RelativeTo]]. new_relative_to = move_result.0; @@ -1154,7 +1097,7 @@ impl Duration { // e. Let newRelativeTo be moveResult.[[RelativeTo]]. // f. Let oneWeekDays be moveResult.[[Days]]. let (mut new_relative_to, mut one_week_days) = - plain_relative_to.move_relative_date(&one_week)?; + plain_relative_to.move_relative_date(&one_week, context)?; // g. Repeat, while abs(days) ≥ abs(oneWeekDays), while result.days().abs() >= one_week_days.abs() { @@ -1165,7 +1108,7 @@ impl Duration { // iii. Set relativeTo to newRelativeTo. plain_relative_to = new_relative_to; // iv. Set moveResult to ? MoveRelativeDate(calendar, relativeTo, oneWeek, dateAdd). - let move_result = plain_relative_to.move_relative_date(&one_week)?; + let move_result = plain_relative_to.move_relative_date(&one_week, context)?; // v. Set newRelativeTo to moveResult.[[RelativeTo]]. new_relative_to = move_result.0; @@ -1195,6 +1138,7 @@ impl Duration { Option<&TemporalZonedDateTime>, Option<&TemporalDateTime>, ), + context: &mut dyn Any, ) -> TemporalResult<(Self, f64)> { let mut result = Duration::new_unchecked(unbalance_date_duration, self.time); @@ -1283,7 +1227,7 @@ impl Duration { // e. Let yearsLater be ? AddDate(calendar, plainRelativeTo, yearsDuration, undefined, dateAdd). let years_later = plain_relative_to - .add_to_date(&years_duration, ArithmeticOverflow::Constrain)?; + .add_date(&years_duration, ArithmeticOverflow::Constrain, context)?; // f. Let yearsMonthsWeeks be ! CreateTemporalDuration(years, months, weeks, 0, 0, 0, 0, 0, 0, 0). let years_months_weeks = Self::new_unchecked( @@ -1298,7 +1242,7 @@ impl Duration { // g. Let yearsMonthsWeeksLater be ? AddDate(calendar, plainRelativeTo, yearsMonthsWeeks, undefined, dateAdd). let years_months_weeks_later = plain_relative_to - .add_to_date(&years_months_weeks, ArithmeticOverflow::Constrain)?; + .add_date(&years_months_weeks, ArithmeticOverflow::Constrain, context)?; // h. Let monthsWeeksInDays be DaysUntil(yearsLater, yearsMonthsWeeksLater). let months_weeks_in_days = years_later.days_until(&years_months_weeks_later); @@ -1322,7 +1266,7 @@ impl Duration { // n. Perform ! CreateDataPropertyOrThrow(untilOptions, "largestUnit", "year"). // o. Let timePassed be ? DifferenceDate(calendar, plainRelativeTo, wholeDaysLater, untilOptions). let time_passed = - plain_relative_to.diff_date(&whole_days_later, TemporalUnit::Year)?; + plain_relative_to.diff_date(&whole_days_later, TemporalUnit::Year, context)?; // p. Let yearsPassed be timePassed.[[Years]]. let years_passed = time_passed.date.years(); @@ -1337,7 +1281,7 @@ impl Duration { // t. Set plainRelativeTo to moveResult.[[RelativeTo]]. // u. Let daysPassed be moveResult.[[Days]]. let (plain_relative_to, days_passed) = - plain_relative_to.move_relative_date(&years_duration)?; + plain_relative_to.move_relative_date(&years_duration, context)?; // v. Set fractionalDays to fractionalDays - daysPassed. frac_days -= days_passed; @@ -1350,7 +1294,8 @@ impl Duration { // y. Set moveResult to ? MoveRelativeDate(calendar, plainRelativeTo, oneYear, dateAdd). // z. Let oneYearDays be moveResult.[[Days]]. - let (_, one_year_days) = plain_relative_to.move_relative_date(&one_year)?; + let (_, one_year_days) = + plain_relative_to.move_relative_date(&one_year, context)?; // aa. Let fractionalYears be years + fractionalDays / abs(oneYearDays). let frac_years = result.date.years() + (frac_days / one_year_days.abs()); @@ -1389,7 +1334,7 @@ impl Duration { // e. Let yearsMonthsLater be ? AddDate(calendar, plainRelativeTo, yearsMonths, undefined, dateAdd). let years_months_later = - plain_relative_to.add_to_date(&years_months, ArithmeticOverflow::Constrain)?; + plain_relative_to.add_date(&years_months, ArithmeticOverflow::Constrain, context)?; // f. Let yearsMonthsWeeks be ! CreateTemporalDuration(years, months, weeks, 0, 0, 0, 0, 0, 0, 0). let years_months_weeks = Self::from_date_duration(DateDuration::new( @@ -1401,7 +1346,7 @@ impl Duration { // g. Let yearsMonthsWeeksLater be ? AddDate(calendar, plainRelativeTo, yearsMonthsWeeks, undefined, dateAdd). let years_months_weeks_later = plain_relative_to - .add_to_date(&years_months_weeks, ArithmeticOverflow::Constrain)?; + .add_date(&years_months_weeks, ArithmeticOverflow::Constrain, context)?; // h. Let weeksInDays be DaysUntil(yearsMonthsLater, yearsMonthsWeeksLater). let weeks_in_days = years_months_later.days_until(&years_months_weeks_later); @@ -1422,7 +1367,7 @@ impl Duration { // n. Set plainRelativeTo to moveResult.[[RelativeTo]]. // o. Let oneMonthDays be moveResult.[[Days]]. let (mut plain_relative_to, mut one_month_days) = - plain_relative_to.move_relative_date(&one_month)?; + plain_relative_to.move_relative_date(&one_month, context)?; // p. Repeat, while abs(fractionalDays) ≥ abs(oneMonthDays), while frac_days.abs() >= one_month_days.abs() { @@ -1433,7 +1378,7 @@ impl Duration { frac_days -= one_month_days; // iii. Set moveResult to ? MoveRelativeDate(calendar, plainRelativeTo, oneMonth, dateAdd). - let move_result = plain_relative_to.move_relative_date(&one_month)?; + let move_result = plain_relative_to.move_relative_date(&one_month, context)?; // iv. Set plainRelativeTo to moveResult.[[RelativeTo]]. plain_relative_to = move_result.0; @@ -1475,7 +1420,7 @@ impl Duration { // g. Set plainRelativeTo to moveResult.[[RelativeTo]]. // h. Let oneWeekDays be moveResult.[[Days]]. let (mut plain_relative_to, mut one_week_days) = - plain_relative_to.move_relative_date(&one_week)?; + plain_relative_to.move_relative_date(&one_week, context)?; // i. Repeat, while abs(fractionalDays) ≥ abs(oneWeekDays), while frac_days.abs() >= one_week_days.abs() { @@ -1486,7 +1431,7 @@ impl Duration { frac_days -= one_week_days; // iii. Set moveResult to ? MoveRelativeDate(calendar, plainRelativeTo, oneWeek, dateAdd). - let move_result = plain_relative_to.move_relative_date(&one_week)?; + let move_result = plain_relative_to.move_relative_date(&one_week, context)?; // iv. Set plainRelativeTo to moveResult.[[RelativeTo]]. plain_relative_to = move_result.0; @@ -1620,3 +1565,87 @@ impl Duration { Ok((result, total)) } } + +// ==== Public Duration methods ==== + +impl Duration { + /// Returns the absolute value of `Duration`. + pub fn abs(&self) -> Self { + Self { + date: DateDuration::new( + self.date.years.abs(), + self.date.months.abs(), + self.date.weeks.abs(), + self.date.days.abs(), + ), + time: TimeDuration::new( + self.time.hours.abs(), + self.time.minutes.abs(), + self.time.seconds.abs(), + self.time.milliseconds.abs(), + self.time.microseconds.abs(), + self.time.nanoseconds.abs(), + ), + } + } + + /// 7.5.10 `DurationSign ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds )` + /// + /// Determines the sign for the current self. + pub fn duration_sign(&self) -> i32 { + // 1. For each value v of « years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds », do + for v in self { + // a. If v < 0, return -1. + if v < 0_f64 { + return -1; + // b. If v > 0, return 1. + } else if v > 0_f64 { + return 1; + } + } + // 2. Return 0. + 0 + } + + /// 7.5.12 `DefaultTemporalLargestUnit ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds )` + pub fn default_temporal_largest_unit(&self) -> TemporalUnit { + for (index, value) in self.into_iter().enumerate() { + if value != 0.0 { + match index { + 0 => return TemporalUnit::Year, + 1 => return TemporalUnit::Month, + 2 => return TemporalUnit::Week, + 3 => return TemporalUnit::Day, + 4 => return TemporalUnit::Hour, + 5 => return TemporalUnit::Minute, + 6 => return TemporalUnit::Second, + 7 => return TemporalUnit::Millisecond, + 8 => return TemporalUnit::Microsecond, + _ => {} + } + } + } + + TemporalUnit::Nanosecond + } + + /// Abstract Operation 7.5.17 `BalanceTimeDuration ( days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, largestUnit )` + pub fn balance_time_duration( + &self, + largest_unit: TemporalUnit, + ) -> TemporalResult<(f64, TimeDuration)> { + // 1. Let balanceResult be ? BalancePossiblyInfiniteDuration(days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, largestUnit, relativeTo). + let balance_result = self.balance_possibly_infinite_time_duration(largest_unit)?; + + // 2. If balanceResult is positive overflow or negative overflow, then + let Some(result) = balance_result else { + // a. Throw a RangeError exception. + return Err(TemporalError::range() + .with_message("duration overflowed viable range.") + .into()); + }; + // 3. Else, + // a. Return balanceResult. + Ok(result) + } +} From 64bbfb8e905aed083ee29c4dd053d4ea95b4a6af Mon Sep 17 00:00:00 2001 From: nekevss Date: Sun, 12 Nov 2023 15:41:37 -0500 Subject: [PATCH 4/9] General completion and clean up work of migration --- .../src/builtins/temporal/calendar/mod.rs | 60 +- .../src/builtins/temporal/calendar/object.rs | 617 +++++++++++++++--- .../src/builtins/temporal/plain_date/mod.rs | 2 +- .../builtins/temporal/plain_month_day/mod.rs | 2 +- .../builtins/temporal/plain_year_month/mod.rs | 6 +- boa_temporal/src/calendar.rs | 74 +-- boa_temporal/src/calendar/iso.rs | 35 +- boa_temporal/src/date.rs | 60 +- boa_temporal/src/datetime.rs | 34 +- boa_temporal/src/duration.rs | 150 +++-- boa_temporal/src/error.rs | 8 +- boa_temporal/src/fields.rs | 10 +- boa_temporal/src/iso.rs | 242 +------ boa_temporal/src/month_day.rs | 23 +- boa_temporal/src/options.rs | 68 +- boa_temporal/src/time.rs | 3 +- boa_temporal/src/utils.rs | 4 +- boa_temporal/src/year_month.rs | 29 +- 18 files changed, 921 insertions(+), 506 deletions(-) diff --git a/boa_engine/src/builtins/temporal/calendar/mod.rs b/boa_engine/src/builtins/temporal/calendar/mod.rs index a60c22d6ad7..0a343cb0e85 100644 --- a/boa_engine/src/builtins/temporal/calendar/mod.rs +++ b/boa_engine/src/builtins/temporal/calendar/mod.rs @@ -170,7 +170,7 @@ impl Calendar { })?; let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), CalendarSlot::Protocol(proto) => proto.clone(), }; @@ -195,7 +195,7 @@ impl Calendar { // Retrieve the current CalendarProtocol. let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), CalendarSlot::Protocol(proto) => proto.clone(), }; @@ -221,7 +221,7 @@ impl Calendar { // a. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « "year", "day" »). let mut required_fields = Vec::from([js_string!("year"), js_string!("day")]); fields::prepare_temporal_fields( - &fields_obj, + fields_obj, &mut relevant_field_names, &mut required_fields, None, @@ -235,7 +235,7 @@ impl Calendar { let calendar_relevant_fields = protocol.field_descriptors(CalendarFieldsType::Date); // b. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « », calendarRelevantFieldDescriptors). fields::prepare_temporal_fields( - &fields_obj, + fields_obj, &mut relevant_field_names, &mut Vec::new(), Some(calendar_relevant_fields), @@ -277,7 +277,7 @@ impl Calendar { })?; let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), CalendarSlot::Protocol(proto) => proto.clone(), }; @@ -300,7 +300,7 @@ impl Calendar { // a. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « "year" »). let mut required_fields = Vec::from([js_string!("year")]); fields::prepare_temporal_fields( - &fields_obj, + fields_obj, &mut relevant_field_names, &mut required_fields, None, @@ -315,7 +315,7 @@ impl Calendar { let calendar_relevant_fields = protocol.field_descriptors(CalendarFieldsType::YearMonth); fields::prepare_temporal_fields( - &fields_obj, + fields_obj, &mut relevant_field_names, &mut Vec::new(), Some(calendar_relevant_fields), @@ -355,7 +355,7 @@ impl Calendar { })?; let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), CalendarSlot::Protocol(proto) => proto.clone(), }; @@ -381,7 +381,7 @@ impl Calendar { // a. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « "day" »). let mut required_fields = Vec::from([js_string!("day")]); fields::prepare_temporal_fields( - &fields_obj, + fields_obj, &mut relevant_field_names, &mut required_fields, None, @@ -395,7 +395,7 @@ impl Calendar { let calendar_relevant_fields = protocol.field_descriptors(CalendarFieldsType::MonthDay); // b. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « », calendarRelevantFieldDescriptors). fields::prepare_temporal_fields( - &fields_obj, + fields_obj, &mut relevant_field_names, &mut Vec::new(), Some(calendar_relevant_fields), @@ -428,7 +428,7 @@ impl Calendar { })?; let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), CalendarSlot::Protocol(proto) => proto.clone(), }; @@ -470,7 +470,7 @@ impl Calendar { })?; let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), CalendarSlot::Protocol(proto) => proto.clone(), }; @@ -509,7 +509,7 @@ impl Calendar { })?; let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), CalendarSlot::Protocol(proto) => proto.clone(), }; @@ -533,7 +533,7 @@ impl Calendar { })?; let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), CalendarSlot::Protocol(proto) => proto.clone(), }; @@ -557,7 +557,7 @@ impl Calendar { })?; let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), CalendarSlot::Protocol(proto) => proto.clone(), }; @@ -579,7 +579,7 @@ impl Calendar { })?; let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), CalendarSlot::Protocol(proto) => proto.clone(), }; @@ -606,7 +606,7 @@ impl Calendar { })?; let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), CalendarSlot::Protocol(proto) => proto.clone(), }; @@ -628,7 +628,7 @@ impl Calendar { })?; let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), CalendarSlot::Protocol(proto) => proto.clone(), }; @@ -652,7 +652,7 @@ impl Calendar { })?; let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), CalendarSlot::Protocol(proto) => proto.clone(), }; @@ -676,7 +676,7 @@ impl Calendar { })?; let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), CalendarSlot::Protocol(proto) => proto.clone(), }; @@ -699,7 +699,7 @@ impl Calendar { })?; let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), CalendarSlot::Protocol(proto) => proto.clone(), }; @@ -722,7 +722,7 @@ impl Calendar { })?; let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), CalendarSlot::Protocol(proto) => proto.clone(), }; @@ -745,7 +745,7 @@ impl Calendar { })?; let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), CalendarSlot::Protocol(proto) => proto.clone(), }; @@ -768,7 +768,7 @@ impl Calendar { })?; let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), CalendarSlot::Protocol(proto) => proto.clone(), }; @@ -790,7 +790,7 @@ impl Calendar { })?; let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), CalendarSlot::Protocol(proto) => proto.clone(), }; @@ -815,7 +815,7 @@ impl Calendar { })?; let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), CalendarSlot::Protocol(proto) => proto.clone(), }; @@ -837,7 +837,7 @@ impl Calendar { })?; let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), CalendarSlot::Protocol(proto) => proto.clone(), }; @@ -861,7 +861,7 @@ impl Calendar { })?; let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), CalendarSlot::Protocol(proto) => proto.clone(), }; @@ -951,7 +951,7 @@ impl Calendar { })?; let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(&s)?.to_protocol(), + CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), CalendarSlot::Protocol(proto) => proto.clone(), }; @@ -979,7 +979,7 @@ impl Calendar { let add_keys = additional_fields_copy .__own_property_keys__(context)? .iter() - .map(|k| k.to_string()) + .map(ToString::to_string) .collect::>(); // 7. If calendar.[[Identifier]] is "iso8601", then diff --git a/boa_engine/src/builtins/temporal/calendar/object.rs b/boa_engine/src/builtins/temporal/calendar/object.rs index 0d3a7a09523..60ece60f4eb 100644 --- a/boa_engine/src/builtins/temporal/calendar/object.rs +++ b/boa_engine/src/builtins/temporal/calendar/object.rs @@ -2,9 +2,8 @@ use crate::{ builtins::temporal::{plain_date, plain_month_day, plain_year_month}, - object::ObjectKind, property::PropertyKey, - Context, JsObject, JsValue, + Context, JsObject, JsString, JsValue, }; use std::any::Any; @@ -20,11 +19,12 @@ use boa_temporal::{ year_month::TemporalYearMonth, TemporalResult, TinyStr4, TinyStr8, }; +use num_traits::ToPrimitive; /// A user-defined, custom calendar that is only known at runtime /// and executed at runtime. /// -/// A user-defined calendar implements all methods of the CalendarProtocol, +/// A user-defined calendar implements all of the `CalendarProtocolMethods` /// and therefore satisfies the requirements to be used as a calendar. #[derive(Debug, Clone)] pub(crate) struct CustomRuntimeCalendar { @@ -46,8 +46,6 @@ impl CalendarProtocol for CustomRuntimeCalendar { overflow: ArithmeticOverflow, context: &mut dyn Any, ) -> TemporalResult { - // Safety: Context lives for the life of the program and execution, so - // this should, in theory, be valid. let context = context .downcast_mut::() .expect("Context was not provided for a CustomCalendar."); @@ -57,23 +55,39 @@ impl CalendarProtocol for CustomRuntimeCalendar { .get(utf16!("dateFromFields"), context) .expect("method must exist on a object that implements the CalendarProtocol."); - let fields = JsObject::from_temporal_fields(&fields, context) - .map_err(|_| TemporalError::range().with_message("Need new"))?; + let fields = JsObject::from_temporal_fields(fields, context) + .map_err(|e| TemporalError::general(e.to_string()))?; + + let overflow_obj = JsObject::with_null_proto(); + + overflow_obj + .create_data_property_or_throw( + utf16!("overflow"), + JsString::from(overflow.to_string()), + context, + ) + .map_err(|e| TemporalError::general(e.to_string()))?; let value = method - .call(&self.calendar.clone().into(), &[fields.into()], context) - .map_err(|e| { - TemporalError::range().with_message("Update error to handle error conversions") - })?; - - let obj = value - .as_object() - .map(JsObject::borrow) - .ok_or_else(|| TemporalError::r#type().with_message("could not borrow object"))?; - - let ObjectKind::PlainDate(pd) = obj.kind() else { - return Err(TemporalError::r#type().with_message("Object returned was not a PlainDate")); - }; + .as_callable() + .ok_or_else(|| { + TemporalError::general("dateFromFields must be implemented as a callable method.") + })? + .call( + &self.calendar.clone().into(), + &[fields.into(), overflow_obj.into()], + context, + ) + .map_err(|e| TemporalError::general(e.to_string()))?; + + let obj = value.as_object().map(JsObject::borrow).ok_or_else(|| { + TemporalError::r#type() + .with_message("datefromFields must return a valid PlainDate object.") + })?; + + let pd = obj.as_plain_date().ok_or_else(|| { + TemporalError::r#type().with_message("Object returned was not a PlainDate") + })?; Ok(pd.inner.clone()) } @@ -84,7 +98,52 @@ impl CalendarProtocol for CustomRuntimeCalendar { overflow: ArithmeticOverflow, context: &mut dyn Any, ) -> TemporalResult { - todo!() + let context = context + .downcast_mut::() + .expect("Context was not provided for a CustomCalendar."); + + let method = self + .calendar + .get(utf16!("yearMonthFromFields"), context) + .expect("method must exist on a object that implements the CalendarProtocol."); + + let fields = JsObject::from_temporal_fields(fields, context) + .map_err(|e| TemporalError::general(e.to_string()))?; + + let overflow_obj = JsObject::with_null_proto(); + + overflow_obj + .create_data_property_or_throw( + utf16!("overflow"), + JsString::from(overflow.to_string()), + context, + ) + .map_err(|e| TemporalError::general(e.to_string()))?; + + let value = method + .as_callable() + .ok_or_else(|| { + TemporalError::general( + "yearMonthFromFields must be implemented as a callable method.", + ) + })? + .call( + &self.calendar.clone().into(), + &[fields.into(), overflow_obj.into()], + context, + ) + .map_err(|e| TemporalError::general(e.to_string()))?; + + let obj = value.as_object().map(JsObject::borrow).ok_or_else(|| { + TemporalError::r#type() + .with_message("yearMonthFromFields must return a valid PlainYearMonth object.") + })?; + + let ym = obj.as_plain_year_month().ok_or_else(|| { + TemporalError::r#type().with_message("Object returned was not a PlainDate") + })?; + + Ok(ym.inner.clone()) } fn month_day_from_fields( @@ -93,52 +152,156 @@ impl CalendarProtocol for CustomRuntimeCalendar { overflow: ArithmeticOverflow, context: &mut dyn Any, ) -> TemporalResult { - todo!() + let context = context + .downcast_mut::() + .expect("Context was not provided for a CustomCalendar."); + + let method = self + .calendar + .get(utf16!("yearMonthFromFields"), context) + .expect("method must exist on a object that implements the CalendarProtocol."); + + let fields = JsObject::from_temporal_fields(fields, context) + .map_err(|e| TemporalError::general(e.to_string()))?; + + let overflow_obj = JsObject::with_null_proto(); + + overflow_obj + .create_data_property_or_throw( + utf16!("overflow"), + JsString::from(overflow.to_string()), + context, + ) + .map_err(|e| TemporalError::general(e.to_string()))?; + + let value = method + .as_callable() + .ok_or_else(|| { + TemporalError::general( + "yearMonthFromFields must be implemented as a callable method.", + ) + })? + .call( + &self.calendar.clone().into(), + &[fields.into(), overflow_obj.into()], + context, + ) + .map_err(|e| TemporalError::general(e.to_string()))?; + + let obj = value.as_object().map(JsObject::borrow).ok_or_else(|| { + TemporalError::r#type() + .with_message("yearMonthFromFields must return a valid PlainYearMonth object.") + })?; + + let md = obj.as_plain_month_day().ok_or_else(|| { + TemporalError::r#type().with_message("Object returned was not a PlainDate") + })?; + + Ok(md.inner.clone()) } fn date_add( &self, - date: &TemporalDate, - duration: &Duration, - overflow: ArithmeticOverflow, - context: &mut dyn Any, + _date: &TemporalDate, + _duration: &Duration, + _overflow: ArithmeticOverflow, + _context: &mut dyn Any, ) -> TemporalResult { todo!() } fn date_until( &self, - one: &TemporalDate, - two: &TemporalDate, - largest_unit: boa_temporal::options::TemporalUnit, - context: &mut dyn Any, + _one: &TemporalDate, + _two: &TemporalDate, + _largest_unit: boa_temporal::options::TemporalUnit, + _context: &mut dyn Any, ) -> TemporalResult { todo!() } - // TODO: Determine validity of below errors. - fn era( - &self, - date_like: &CalendarDateLike, - context: &mut dyn Any, - ) -> TemporalResult> { - Err(TemporalError::range().with_message("Objects do not implement era")) + fn era(&self, _: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult> { + // Return undefined as custom calendars do not implement -> Currently. + Ok(None) } - fn era_year( - &self, - date_like: &CalendarDateLike, - context: &mut dyn Any, - ) -> TemporalResult> { - Err(TemporalError::range().with_message("Objects do not implement eraYear.")) + fn era_year(&self, _: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult> { + // Return undefined as custom calendars do not implement -> Currently. + Ok(None) } fn year(&self, date_like: &CalendarDateLike, context: &mut dyn Any) -> TemporalResult { - todo!() + let context = context + .downcast_mut::() + .expect("Context was not provided for a CustomCalendar."); + + let date_like = date_like_to_object(date_like, context)?; + + let method = self + .calendar + .get(PropertyKey::from(utf16!("year")), context) + .expect("method must exist on a object that implements the CalendarProtocol."); + + let val = method + .as_callable() + .expect("is method") + .call(&method, &[date_like], context) + .map_err(|err| TemporalError::general(err.to_string()))?; + + let Some(number) = val.as_number() else { + return Err(TemporalError::r#type().with_message("year must return a number.")); + }; + + if !number.is_finite() || number.fract() != 0.0 { + return Err(TemporalError::r#type().with_message("year return must be integral.")); + } + + if number < 1f64 { + return Err(TemporalError::r#type().with_message("year return must be larger than 1.")); + } + + let result = number + .to_i32() + .ok_or_else(|| TemporalError::range().with_message("year exceeded a valid range."))?; + + Ok(result) } fn month(&self, date_like: &CalendarDateLike, context: &mut dyn Any) -> TemporalResult { - todo!() + let context = context + .downcast_mut::() + .expect("Context was not provided for a CustomCalendar."); + + let date_like = date_like_to_object(date_like, context)?; + + let method = self + .calendar + .get(PropertyKey::from(utf16!("month")), context) + .expect("method must exist on a object that implements the CalendarProtocol."); + + let val = method + .as_callable() + .expect("is method") + .call(&method, &[date_like], context) + .map_err(|err| TemporalError::general(err.to_string()))?; + + let Some(number) = val.as_number() else { + return Err(TemporalError::r#type().with_message("month must return a number.")); + }; + + if !number.is_finite() || number.fract() != 0.0 { + return Err(TemporalError::r#type().with_message("month return must be integral.")); + } + + if number < 1f64 { + return Err(TemporalError::r#type().with_message("month return must be larger than 1.")); + } + + let result = number + .to_u8() + .ok_or_else(|| TemporalError::range().with_message("month exceeded a valid range."))?; + + Ok(result) } fn month_code( @@ -146,35 +309,197 @@ impl CalendarProtocol for CustomRuntimeCalendar { date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult { - todo!() + let context = context + .downcast_mut::() + .expect("Context was not provided for a CustomCalendar."); + + let date_like = date_like_to_object(date_like, context)?; + + let method = self + .calendar + .get(PropertyKey::from(utf16!("monthCode")), context) + .expect("method must exist on a object that implements the CalendarProtocol."); + + let val = method + .as_callable() + .expect("is method") + .call(&method, &[date_like], context) + .map_err(|err| TemporalError::general(err.to_string()))?; + + let JsValue::String(result) = val else { + return Err(TemporalError::r#type().with_message("monthCode return must be a String.")); + }; + + let result = TinyStr4::from_str(&result.to_std_string_escaped()) + .map_err(|_| TemporalError::general("Unexpected monthCode value."))?; + + Ok(result) } fn day(&self, date_like: &CalendarDateLike, context: &mut dyn Any) -> TemporalResult { - todo!() + let context = context + .downcast_mut::() + .expect("Context was not provided for a CustomCalendar."); + + let date_like = date_like_to_object(date_like, context)?; + + let method = self + .calendar + .get(PropertyKey::from(utf16!("day")), context) + .expect("method must exist on a object that implements the CalendarProtocol."); + + let val = method + .as_callable() + .expect("is method") + .call(&method, &[date_like], context) + .map_err(|err| TemporalError::general(err.to_string()))?; + + let Some(number) = val.as_number() else { + return Err(TemporalError::r#type().with_message("day must return a number.")); + }; + + if !number.is_finite() || number.fract() != 0.0 { + return Err(TemporalError::r#type().with_message("day return must be integral.")); + } + + if number < 1f64 { + return Err(TemporalError::r#type().with_message("day return must be larger than 1.")); + } + + let result = number + .to_u8() + .ok_or_else(|| TemporalError::range().with_message("day exceeded a valid range."))?; + + Ok(result) } fn day_of_week( &self, date_like: &CalendarDateLike, context: &mut dyn Any, - ) -> TemporalResult { - todo!() + ) -> TemporalResult { + let context = context + .downcast_mut::() + .expect("Context was not provided for a CustomCalendar."); + + let date_like = date_like_to_object(date_like, context)?; + + let method = self + .calendar + .get(PropertyKey::from(utf16!("dayOfWeek")), context) + .expect("method must exist on a object that implements the CalendarProtocol."); + + let val = method + .as_callable() + .expect("is method") + .call(&method, &[date_like], context) + .map_err(|err| TemporalError::general(err.to_string()))?; + + let Some(number) = val.as_number() else { + return Err(TemporalError::r#type().with_message("DayOfWeek must return a number.")); + }; + + if !number.is_finite() || number.fract() != 0.0 { + return Err(TemporalError::r#type().with_message("DayOfWeek return must be integral.")); + } + + if number < 1f64 { + return Err( + TemporalError::r#type().with_message("DayOfWeek return must be larger than 1.") + ); + } + + let result = number.to_u16().ok_or_else(|| { + TemporalError::range().with_message("DayOfWeek exceeded valid range.") + })?; + + Ok(result) } fn day_of_year( &self, date_like: &CalendarDateLike, context: &mut dyn Any, - ) -> TemporalResult { - todo!() + ) -> TemporalResult { + let context = context + .downcast_mut::() + .expect("Context was not provided for a CustomCalendar."); + + let date_like = date_like_to_object(date_like, context)?; + + let method = self + .calendar + .get(PropertyKey::from(utf16!("dayOfYear")), context) + .expect("method must exist on a object that implements the CalendarProtocol."); + + let val = method + .as_callable() + .expect("is method") + .call(&method, &[date_like], context) + .map_err(|err| TemporalError::general(err.to_string()))?; + + let Some(number) = val.as_number() else { + return Err(TemporalError::r#type().with_message("dayOfYear must return a number.")); + }; + + if !number.is_finite() || number.fract() != 0.0 { + return Err(TemporalError::r#type().with_message("dayOfYear return must be integral.")); + } + + if number < 1f64 { + return Err( + TemporalError::r#type().with_message("dayOfYear return must be larger than 1.") + ); + } + + let result = number.to_u16().ok_or_else(|| { + TemporalError::range().with_message("dayOfYear exceeded valid range.") + })?; + + Ok(result) } fn week_of_year( &self, date_like: &CalendarDateLike, context: &mut dyn Any, - ) -> TemporalResult { - todo!() + ) -> TemporalResult { + let context = context + .downcast_mut::() + .expect("Context was not provided for a CustomCalendar."); + + let date_like = date_like_to_object(date_like, context)?; + + let method = self + .calendar + .get(PropertyKey::from(utf16!("weekOfYear")), context) + .expect("method must exist on a object that implements the CalendarProtocol."); + + let val = method + .as_callable() + .expect("is method") + .call(&method, &[date_like], context) + .map_err(|err| TemporalError::general(err.to_string()))?; + + let Some(number) = val.as_number() else { + return Err(TemporalError::r#type().with_message("weekOfYear must return a number.")); + }; + + if !number.is_finite() || number.fract() != 0.0 { + return Err(TemporalError::r#type().with_message("weekOfYear return must be integral.")); + } + + if number < 1f64 { + return Err( + TemporalError::r#type().with_message("weekOfYear return must be larger than 1.") + ); + } + + let result = number.to_u16().ok_or_else(|| { + TemporalError::range().with_message("weekOfYear exceeded valid range.") + })?; + + Ok(result) } fn year_of_week( @@ -182,16 +507,43 @@ impl CalendarProtocol for CustomRuntimeCalendar { date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult { - todo!() + let context = context + .downcast_mut::() + .expect("Context was not provided for a CustomCalendar."); + + let date_like = date_like_to_object(date_like, context)?; + + let method = self + .calendar + .get(PropertyKey::from(utf16!("yearOfWeek")), context) + .expect("method must exist on a object that implements the CalendarProtocol."); + + let val = method + .as_callable() + .expect("is method") + .call(&method, &[date_like], context) + .map_err(|err| TemporalError::general(err.to_string()))?; + + let Some(number) = val.as_number() else { + return Err(TemporalError::r#type().with_message("yearOfWeek must return a number.")); + }; + + if !number.is_finite() || number.fract() != 0.0 { + return Err(TemporalError::r#type().with_message("yearOfWeek return must be integral.")); + } + + let result = number.to_i32().ok_or_else(|| { + TemporalError::range().with_message("yearOfWeek exceeded valid range.") + })?; + + Ok(result) } fn days_in_week( &self, date_like: &CalendarDateLike, context: &mut dyn Any, - ) -> TemporalResult { - // Safety: Context lives for the lifetime of the program's execution, so - // this should, in theory, be safe memory to access. + ) -> TemporalResult { let context = context .downcast_mut::() .expect("Context was not provided for a CustomCalendar."); @@ -209,20 +561,32 @@ impl CalendarProtocol for CustomRuntimeCalendar { .call(&method, &[date_like], context) .map_err(|err| TemporalError::general(err.to_string()))?; - let JsValue::Integer(integral) = val else { - return Err(TemporalError::range().with_message("Invalid CustomCalendarReturn")); + let Some(number) = val.as_number() else { + return Err(TemporalError::r#type().with_message("daysInWeek must return a number.")); }; - Ok(integral) + if !number.is_finite() || number.fract() != 0.0 { + return Err(TemporalError::r#type().with_message("daysInWeek return must be integral.")); + } + + if number < 1f64 { + return Err( + TemporalError::r#type().with_message("daysInWeek return must be larger than 1.") + ); + } + + let result = number.to_u16().ok_or_else(|| { + TemporalError::range().with_message("daysInWeek exceeded valid range.") + })?; + + Ok(result) } fn days_in_month( &self, date_like: &CalendarDateLike, context: &mut dyn Any, - ) -> TemporalResult { - // Safety: Context lives for the lifetime of the program's execution, so - // this should, in theory, be safe memory to access. + ) -> TemporalResult { let context = context .downcast_mut::() .expect("Context was not provided for a CustomCalendar."); @@ -239,20 +603,34 @@ impl CalendarProtocol for CustomRuntimeCalendar { .call(&method, &[date_like], context) .map_err(|err| TemporalError::general(err.to_string()))?; - let JsValue::Integer(integral) = val else { - return Err(TemporalError::range().with_message("Invalid CustomCalendarReturn")); + let Some(number) = val.as_number() else { + return Err(TemporalError::r#type().with_message("daysInMonth must return a number.")); }; - Ok(integral) + if !number.is_finite() || number.fract() != 0.0 { + return Err( + TemporalError::r#type().with_message("daysInMonth return must be integral.") + ); + } + + if number < 1f64 { + return Err( + TemporalError::r#type().with_message("daysInMonth return must be larger than 1.") + ); + } + + let result = number.to_u16().ok_or_else(|| { + TemporalError::range().with_message("daysInMonth exceeded valid range.") + })?; + + Ok(result) } fn days_in_year( &self, date_like: &CalendarDateLike, context: &mut dyn Any, - ) -> TemporalResult { - // Safety: Context lives for the lifetime of the program's execution, so - // this should, in theory, be safe memory to access. + ) -> TemporalResult { let context = context .downcast_mut::() .expect("Context was not provided for a CustomCalendar."); @@ -270,19 +648,70 @@ impl CalendarProtocol for CustomRuntimeCalendar { .call(&method, &[date_like], context) .map_err(|err| TemporalError::general(err.to_string()))?; - let JsValue::Integer(integral) = val else { - return Err(TemporalError::range().with_message("Invalid CustomCalendarReturn")); + let Some(number) = val.as_number() else { + return Err(TemporalError::r#type().with_message("daysInYear must return a number.")); }; - Ok(integral) + if !number.is_finite() || number.fract() != 0.0 { + return Err(TemporalError::r#type().with_message("daysInYear return must be integral.")); + } + + if number < 1f64 { + return Err( + TemporalError::r#type().with_message("daysInYear return must be larger than 1.") + ); + } + + let result = number.to_u16().ok_or_else(|| { + TemporalError::range().with_message("monthsInYear exceeded valid range.") + })?; + + Ok(result) } fn months_in_year( &self, date_like: &CalendarDateLike, context: &mut dyn Any, - ) -> TemporalResult { - todo!() + ) -> TemporalResult { + let context = context + .downcast_mut::() + .expect("Context was not provided for a CustomCalendar."); + + let date_like = date_like_to_object(date_like, context)?; + + let method = self + .calendar + .get(PropertyKey::from(utf16!("monthsInYear")), context) + .expect("method must exist on a object that implements the CalendarProtocol."); + + let val = method + .as_callable() + .expect("is method") + .call(&method, &[date_like], context) + .map_err(|err| TemporalError::general(err.to_string()))?; + + let Some(number) = val.as_number() else { + return Err(TemporalError::r#type().with_message("monthsInYear must return a number.")); + }; + + if !number.is_finite() || number.fract() != 0.0 { + return Err( + TemporalError::r#type().with_message("monthsInYear return must be integral.") + ); + } + + if number < 1f64 { + return Err( + TemporalError::r#type().with_message("monthsInYear return must be larger than 1.") + ); + } + + let result = number.to_u16().ok_or_else(|| { + TemporalError::range().with_message("monthsInYear exceeded valid range.") + })?; + + Ok(result) } fn in_leap_year( @@ -290,33 +719,54 @@ impl CalendarProtocol for CustomRuntimeCalendar { date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult { - todo!() + let context = context + .downcast_mut::() + .expect("Context was not provided for a CustomCalendar."); + + let date_like = date_like_to_object(date_like, context)?; + + let method = self + .calendar + .get(PropertyKey::from(utf16!("inLeapYear")), context) + .expect("method must exist on a object that implements the CalendarProtocol."); + + let val = method + .as_callable() + .expect("is method") + .call(&method, &[date_like], context) + .map_err(|err| TemporalError::general(err.to_string()))?; + + let JsValue::Boolean(result) = val else { + return Err( + TemporalError::r#type().with_message("inLeapYear must return a valid boolean.") + ); + }; + + Ok(result) } // TODO: Determine fate of fn fields() fn field_descriptors( &self, - r#type: boa_temporal::calendar::CalendarFieldsType, + _: boa_temporal::calendar::CalendarFieldsType, ) -> Vec<(String, bool)> { Vec::default() } - fn field_keys_to_ignore(&self, additional_keys: Vec) -> Vec { + fn field_keys_to_ignore(&self, _: Vec) -> Vec { Vec::default() } fn resolve_fields( &self, - fields: &mut TemporalFields, - r#type: boa_temporal::calendar::CalendarFieldsType, + _: &mut TemporalFields, + _: boa_temporal::calendar::CalendarFieldsType, ) -> TemporalResult<()> { - todo!() + Ok(()) } fn identifier(&self, context: &mut dyn Any) -> TemporalResult { - // Safety: Context lives for the lifetime of the program's execution, so - // this should, in theory, be safe memory to access. let context = context .downcast_mut::() .expect("Context was not provided for a CustomCalendar."); @@ -338,6 +788,7 @@ impl CalendarProtocol for CustomRuntimeCalendar { } } +/// Utility function for converting `Temporal`'s `CalendarDateLike` to it's `Boa` specific `JsObject`. pub(crate) fn date_like_to_object( date_like: &CalendarDateLike, context: &mut Context, @@ -346,7 +797,7 @@ pub(crate) fn date_like_to_object( CalendarDateLike::Date(d) => plain_date::create_temporal_date(d.clone(), None, context) .map_err(|e| TemporalError::general(e.to_string())) .map(Into::into), - CalendarDateLike::DateTime(dt) => { + CalendarDateLike::DateTime(_dt) => { todo!() } CalendarDateLike::MonthDay(md) => { diff --git a/boa_engine/src/builtins/temporal/plain_date/mod.rs b/boa_engine/src/builtins/temporal/plain_date/mod.rs index 531cb5af835..593d43d86bc 100644 --- a/boa_engine/src/builtins/temporal/plain_date/mod.rs +++ b/boa_engine/src/builtins/temporal/plain_date/mod.rs @@ -415,7 +415,7 @@ pub(crate) fn create_temporal_date( }; // 2. If ISODateTimeWithinLimits(isoYear, isoMonth, isoDay, 12, 0, 0, 0, 0, 0) is false, throw a RangeError exception. - if !TemporalDateTime::validate_date(&inner) { + if !TemporalDateTime::validate(&inner) { return Err(JsNativeError::range() .with_message("Date is not within ISO date time limits.") .into()); diff --git a/boa_engine/src/builtins/temporal/plain_month_day/mod.rs b/boa_engine/src/builtins/temporal/plain_month_day/mod.rs index 01fbe38c5f2..48d0b4c5473 100644 --- a/boa_engine/src/builtins/temporal/plain_month_day/mod.rs +++ b/boa_engine/src/builtins/temporal/plain_month_day/mod.rs @@ -73,7 +73,7 @@ pub(crate) fn create_temporal_month_day( ) -> JsResult { // 1. If IsValidISODate(referenceISOYear, isoMonth, isoDay) is false, throw a RangeError exception. // 2. If ISODateTimeWithinLimits(referenceISOYear, isoMonth, isoDay, 12, 0, 0, 0, 0, 0) is false, throw a RangeError exception. - if TemporalDateTime::validate_month_day(&inner) { + if TemporalDateTime::validate(&inner) { return Err(JsNativeError::range() .with_message("PlainMonthDay is not a valid ISO date time.") .into()); diff --git a/boa_engine/src/builtins/temporal/plain_year_month/mod.rs b/boa_engine/src/builtins/temporal/plain_year_month/mod.rs index 0c7beb85217..14b37bc6597 100644 --- a/boa_engine/src/builtins/temporal/plain_year_month/mod.rs +++ b/boa_engine/src/builtins/temporal/plain_year_month/mod.rs @@ -148,10 +148,10 @@ impl BuiltInConstructor for PlainYearMonth { // 2. If referenceISODay is undefined, then let ref_day = if day.is_undefined() { // a. Set referenceISODay to 1𝔽. - 1 + None } else { // 6. Let ref be ? ToIntegerWithTruncation(referenceISODay). - super::to_integer_with_truncation(day, context)? + Some(super::to_integer_with_truncation(day, context)?) }; // 3. Let y be ? ToIntegerWithTruncation(isoYear). @@ -162,7 +162,7 @@ impl BuiltInConstructor for PlainYearMonth { let calendar = to_temporal_calendar_slot_value(args.get_or_undefined(2), context)?; // 7. Return ? CreateTemporalYearMonth(y, m, calendar, ref, NewTarget). - let inner = InnerYearMonth::new(y, m, calendar, ArithmeticOverflow::Reject)?; + let inner = InnerYearMonth::new(y, m, ref_day, calendar, ArithmeticOverflow::Reject)?; create_temporal_year_month(inner, Some(new_target), context) } diff --git a/boa_temporal/src/calendar.rs b/boa_temporal/src/calendar.rs index 8bac53ada46..bc6353cf355 100644 --- a/boa_temporal/src/calendar.rs +++ b/boa_temporal/src/calendar.rs @@ -14,7 +14,7 @@ use crate::{ datetime::TemporalDateTime, duration::Duration, fields::TemporalFields, - iso::IsoDate, + iso::{IsoDate, IsoDateSlots}, month_day::TemporalMonthDay, options::{ArithmeticOverflow, TemporalUnit}, year_month::TemporalYearMonth, @@ -66,8 +66,8 @@ pub enum CalendarFieldsType { // TODO: Optimize to TinyStr or &str. impl From<&[String]> for CalendarFieldsType { fn from(value: &[String]) -> Self { - let year_present = value.contains(&"year".to_string()); - let day_present = value.contains(&"day".to_string()); + let year_present = value.contains(&"year".to_owned()); + let day_present = value.contains(&"day".to_owned()); if year_present && day_present { CalendarFieldsType::Date @@ -79,7 +79,7 @@ impl From<&[String]> for CalendarFieldsType { } } -/// AvailableCalendars lists the currently implemented `CalendarProtocols` +/// `AvailableCalendars` lists the currently implemented `CalendarProtocols` #[derive(Debug, Clone, Copy)] pub enum AvailableCalendars { /// The ISO8601 calendar. @@ -108,7 +108,7 @@ impl AvailableCalendars { } } -/// The `DateLike` objects that can be provided to the CalendarProtocol. +/// The `DateLike` objects that can be provided to the `CalendarProtocol`. #[derive(Debug)] pub enum CalendarDateLike { /// Represents a `Date` datelike @@ -150,11 +150,7 @@ where } } -// TODO: Determine how many methods actually need the context on them while using -// `icu_calendar`. -// -// NOTE (re above's TODO): Most likely context is only going to be needed for `dateFromFields`, -// `yearMonthFromFields`, `monthDayFromFields`, `dateAdd`, and `dateUntil`. +// TODO: Implement `fields` and `mergeFields` /// A trait for implementing a Builtin Calendar's Calendar Protocol in Rust. pub trait CalendarProtocol: CalendarProtocolClone { /// Creates a `Temporal.PlainDate` object from provided fields. @@ -224,19 +220,19 @@ pub trait CalendarProtocol: CalendarProtocolClone { &self, date_like: &CalendarDateLike, context: &mut dyn Any, - ) -> TemporalResult; + ) -> TemporalResult; /// Returns a value representing the day of the year for a given calendar. fn day_of_year( &self, date_like: &CalendarDateLike, context: &mut dyn Any, - ) -> TemporalResult; + ) -> TemporalResult; /// Returns a value representing the week of the year for a given calendar. fn week_of_year( &self, date_like: &CalendarDateLike, context: &mut dyn Any, - ) -> TemporalResult; + ) -> TemporalResult; /// Returns the year of a given week. fn year_of_week( &self, @@ -248,25 +244,25 @@ pub trait CalendarProtocol: CalendarProtocolClone { &self, date_like: &CalendarDateLike, context: &mut dyn Any, - ) -> TemporalResult; + ) -> TemporalResult; /// Returns the days in a month for a given calendar. fn days_in_month( &self, date_like: &CalendarDateLike, context: &mut dyn Any, - ) -> TemporalResult; + ) -> TemporalResult; /// Returns the days in a year for a given calendar. fn days_in_year( &self, date_like: &CalendarDateLike, context: &mut dyn Any, - ) -> TemporalResult; + ) -> TemporalResult; /// Returns the months in a year for a given calendar. fn months_in_year( &self, date_like: &CalendarDateLike, context: &mut dyn Any, - ) -> TemporalResult; + ) -> TemporalResult; /// Returns whether a value is within a leap year according to the designated calendar. fn in_leap_year( &self, @@ -340,7 +336,7 @@ impl CalendarSlot { ) -> TemporalResult { match self { Self::Identifier(id) => { - let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); + let protocol = AvailableCalendars::from_str(id)?.to_protocol(); protocol.date_add(date, duration, overflow, context) } Self::Protocol(protocol) => protocol.date_add(date, duration, overflow, context), @@ -359,7 +355,7 @@ impl CalendarSlot { ) -> TemporalResult { match self { Self::Identifier(id) => { - let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); + let protocol = AvailableCalendars::from_str(id)?.to_protocol(); protocol.date_until(one, two, largest_unit, context) } Self::Protocol(protocol) => protocol.date_until(one, two, largest_unit, context), @@ -372,7 +368,7 @@ impl CalendarSlot { pub fn year(&self, date_like: &CalendarDateLike, context: &mut dyn Any) -> TemporalResult { match self { Self::Identifier(id) => { - let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); + let protocol = AvailableCalendars::from_str(id)?.to_protocol(); protocol.year(date_like, &mut ()) } Self::Protocol(protocol) => protocol.year(date_like, context), @@ -385,7 +381,7 @@ impl CalendarSlot { pub fn month(&self, date_like: &CalendarDateLike, context: &mut dyn Any) -> TemporalResult { match self { Self::Identifier(id) => { - let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); + let protocol = AvailableCalendars::from_str(id)?.to_protocol(); protocol.month(date_like, &mut ()) } Self::Protocol(protocol) => protocol.month(date_like, context), @@ -402,7 +398,7 @@ impl CalendarSlot { ) -> TemporalResult { match self { Self::Identifier(id) => { - let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); + let protocol = AvailableCalendars::from_str(id)?.to_protocol(); protocol.month_code(date_like, &mut ()) } Self::Protocol(protocol) => protocol.month_code(date_like, context), @@ -415,7 +411,7 @@ impl CalendarSlot { pub fn day(&self, date_like: &CalendarDateLike, context: &mut dyn Any) -> TemporalResult { match self { Self::Identifier(id) => { - let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); + let protocol = AvailableCalendars::from_str(id)?.to_protocol(); protocol.day(date_like, &mut ()) } Self::Protocol(protocol) => protocol.day(date_like, context), @@ -429,10 +425,10 @@ impl CalendarSlot { &self, date_like: &CalendarDateLike, context: &mut dyn Any, - ) -> TemporalResult { + ) -> TemporalResult { match self { Self::Identifier(id) => { - let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); + let protocol = AvailableCalendars::from_str(id)?.to_protocol(); protocol.day_of_week(date_like, &mut ()) } Self::Protocol(protocol) => protocol.day_of_week(date_like, context), @@ -446,10 +442,10 @@ impl CalendarSlot { &self, date_like: &CalendarDateLike, context: &mut dyn Any, - ) -> TemporalResult { + ) -> TemporalResult { match self { Self::Identifier(id) => { - let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); + let protocol = AvailableCalendars::from_str(id)?.to_protocol(); protocol.day_of_year(date_like, &mut ()) } Self::Protocol(protocol) => protocol.day_of_year(date_like, context), @@ -463,10 +459,10 @@ impl CalendarSlot { &self, date_like: &CalendarDateLike, context: &mut dyn Any, - ) -> TemporalResult { + ) -> TemporalResult { match self { Self::Identifier(id) => { - let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); + let protocol = AvailableCalendars::from_str(id)?.to_protocol(); protocol.week_of_year(date_like, &mut ()) } Self::Protocol(protocol) => protocol.week_of_year(date_like, context), @@ -483,7 +479,7 @@ impl CalendarSlot { ) -> TemporalResult { match self { Self::Identifier(id) => { - let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); + let protocol = AvailableCalendars::from_str(id)?.to_protocol(); protocol.year_of_week(date_like, &mut ()) } Self::Protocol(protocol) => protocol.year_of_week(date_like, context), @@ -497,10 +493,10 @@ impl CalendarSlot { &self, date_like: &CalendarDateLike, context: &mut dyn Any, - ) -> TemporalResult { + ) -> TemporalResult { match self { Self::Identifier(id) => { - let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); + let protocol = AvailableCalendars::from_str(id)?.to_protocol(); protocol.days_in_week(date_like, &mut ()) } Self::Protocol(protocol) => protocol.days_in_week(date_like, context), @@ -514,10 +510,10 @@ impl CalendarSlot { &self, date_like: &CalendarDateLike, context: &mut dyn Any, - ) -> TemporalResult { + ) -> TemporalResult { match self { Self::Identifier(id) => { - let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); + let protocol = AvailableCalendars::from_str(id)?.to_protocol(); protocol.days_in_month(date_like, &mut ()) } Self::Protocol(protocol) => protocol.days_in_month(date_like, context), @@ -531,10 +527,10 @@ impl CalendarSlot { &self, date_like: &CalendarDateLike, context: &mut dyn Any, - ) -> TemporalResult { + ) -> TemporalResult { match self { Self::Identifier(id) => { - let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); + let protocol = AvailableCalendars::from_str(id)?.to_protocol(); protocol.days_in_year(date_like, &mut ()) } Self::Protocol(protocol) => protocol.days_in_year(date_like, context), @@ -548,10 +544,10 @@ impl CalendarSlot { &self, date_like: &CalendarDateLike, context: &mut dyn Any, - ) -> TemporalResult { + ) -> TemporalResult { match self { Self::Identifier(id) => { - let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); + let protocol = AvailableCalendars::from_str(id)?.to_protocol(); protocol.months_in_year(date_like, &mut ()) } Self::Protocol(protocol) => protocol.months_in_year(date_like, context), @@ -568,7 +564,7 @@ impl CalendarSlot { ) -> TemporalResult { match self { Self::Identifier(id) => { - let protocol = AvailableCalendars::from_str(&id)?.to_protocol(); + let protocol = AvailableCalendars::from_str(id)?.to_protocol(); protocol.in_leap_year(date_like, &mut ()) } Self::Protocol(protocol) => protocol.in_leap_year(date_like, context), diff --git a/boa_temporal/src/calendar/iso.rs b/boa_temporal/src/calendar/iso.rs index 30cfe11a4e6..fb584e89270 100644 --- a/boa_temporal/src/calendar/iso.rs +++ b/boa_temporal/src/calendar/iso.rs @@ -13,7 +13,7 @@ use crate::{ }; use std::any::Any; -use tinystr::{tinystr, TinyStr4, TinyStr8}; +use tinystr::{TinyStr4, TinyStr8}; use super::{CalendarDateLike, CalendarFieldsType, CalendarProtocol, CalendarSlot}; @@ -68,6 +68,7 @@ impl CalendarProtocol for IsoCalendar { TemporalYearMonth::new( fields.year().unwrap_or(0), fields.month().unwrap_or(0), + fields.day(), CalendarSlot::Identifier("iso8601".to_string()), overflow, ) @@ -107,9 +108,7 @@ impl CalendarProtocol for IsoCalendar { _: &mut dyn Any, ) -> TemporalResult { // TODO: Not stable on `ICU4X`. Implement once completed. - Err(TemporalError::range() - .with_message("feature not implemented.") - .into()) + Err(TemporalError::range().with_message("feature not implemented.")) // 9. Let result be ? AddISODate(date.[[ISOYear]], date.[[ISOMonth]], date.[[ISODay]], duration.[[Years]], duration.[[Months]], duration.[[Weeks]], balanceResult.[[Days]], overflow). // 10. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], "iso8601"). @@ -126,9 +125,7 @@ impl CalendarProtocol for IsoCalendar { _: &mut dyn Any, ) -> TemporalResult { // TODO: Not stable on `ICU4X`. Implement once completed. - Err(TemporalError::range() - .with_message("Feature not yet implemented.") - .into()) + Err(TemporalError::range().with_message("Feature not yet implemented.")) // 9. Let result be DifferenceISODate(one.[[ISOYear]], one.[[ISOMonth]], one.[[ISODay]], two.[[ISOYear]], two.[[ISOMonth]], two.[[ISODay]], largestUnit). // 10. Return ! CreateTemporalDuration(result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], 0, 0, 0, 0, 0, 0). @@ -172,19 +169,19 @@ impl CalendarProtocol for IsoCalendar { } /// Returns the `dayOfWeek` for the `Iso` calendar. - fn day_of_week(&self, date_like: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult { + fn day_of_week(&self, date_like: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult { let date = date_like.as_iso_date().as_icu4x()?; - Ok(date.day_of_week() as i32) + Ok(date.day_of_week() as u16) } /// Returns the `dayOfYear` for the `Iso` calendar. - fn day_of_year(&self, date_like: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult { + fn day_of_year(&self, date_like: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult { let date = date_like.as_iso_date().as_icu4x()?; - Ok(i32::from(date.day_of_year_info().day_of_year)) + Ok(date.day_of_year_info().day_of_year) } /// Returns the `weekOfYear` for the `Iso` calendar. - fn week_of_year(&self, date_like: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult { + fn week_of_year(&self, date_like: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult { let date = date_like.as_iso_date().as_icu4x()?; let week_calculator = WeekCalculator::default(); @@ -193,7 +190,7 @@ impl CalendarProtocol for IsoCalendar { .week_of_year(&week_calculator) .map_err(|err| TemporalError::range().with_message(err.to_string()))?; - Ok(i32::from(week_of.week)) + Ok(week_of.week) } /// Returns the `yearOfWeek` for the `Iso` calendar. @@ -216,24 +213,24 @@ impl CalendarProtocol for IsoCalendar { } /// Returns the `daysInWeek` value for the `Iso` calendar. - fn days_in_week(&self, _: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult { + fn days_in_week(&self, _: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult { Ok(7) } /// Returns the `daysInMonth` value for the `Iso` calendar. - fn days_in_month(&self, date_like: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult { + fn days_in_month(&self, date_like: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult { let date = date_like.as_iso_date().as_icu4x()?; - Ok(i32::from(date.days_in_month())) + Ok(u16::from(date.days_in_month())) } /// Returns the `daysInYear` value for the `Iso` calendar. - fn days_in_year(&self, date_like: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult { + fn days_in_year(&self, date_like: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult { let date = date_like.as_iso_date().as_icu4x()?; - Ok(i32::from(date.days_in_year())) + Ok(date.days_in_year()) } /// Return the amount of months in an ISO Calendar. - fn months_in_year(&self, _: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult { + fn months_in_year(&self, _: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult { Ok(12) } diff --git a/boa_temporal/src/date.rs b/boa_temporal/src/date.rs index 0f07040b308..335daa1a8d0 100644 --- a/boa_temporal/src/date.rs +++ b/boa_temporal/src/date.rs @@ -4,7 +4,7 @@ use crate::{ calendar::CalendarSlot, datetime::TemporalDateTime, duration::{DateDuration, Duration}, - iso::IsoDate, + iso::{IsoDate, IsoDateSlots}, options::{ArithmeticOverflow, TemporalUnit}, TemporalResult, }; @@ -20,12 +20,13 @@ pub struct TemporalDate { // ==== Private API ==== impl TemporalDate { - /// Create a new TemporalDate with the date values and calendar slot. + /// Create a new `TemporalDate` with the date values and calendar slot. pub(crate) fn new_unchecked(iso: IsoDate, calendar: CalendarSlot) -> Self { Self { iso, calendar } } #[inline] + #[must_use] /// `DifferenceDate` pub(crate) fn diff_date( &self, @@ -55,6 +56,7 @@ impl TemporalDate { } #[inline] + #[must_use] /// Internal `AddDate` function pub(crate) fn add_date( &self, @@ -72,7 +74,7 @@ impl TemporalDate { // 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 self.calendar().date_add(&self, duration, overflow, context); + return self.calendar().date_add(self, duration, overflow, context); } // 3. Let overflow be ? ToTemporalOverflow(options). @@ -88,6 +90,7 @@ impl TemporalDate { } #[inline] + #[must_use] /// Returns a new moved date and the days associated with that adjustment pub(crate) fn move_relative_date( &self, @@ -115,6 +118,7 @@ impl TemporalDate { Ok(Self::new_unchecked(iso, calendar)) } + #[must_use] /// Creates a `TemporalDate` from a `TemporalDateTime`. pub fn from_datetime(dt: &TemporalDateTime) -> Self { Self { @@ -124,30 +128,35 @@ impl TemporalDate { } #[inline] + #[must_use] /// Returns this `TemporalDate`'s year value. pub const fn year(&self) -> i32 { self.iso.year() } #[inline] + #[must_use] /// Returns this `TemporalDate`'s month value. pub const fn month(&self) -> u8 { self.iso.month() } #[inline] + #[must_use] /// Returns this `TemporalDate`'s day value. pub const fn day(&self) -> u8 { self.iso.day() } #[inline] + #[must_use] /// Returns the `TemporalDate`'s inner `IsoDate` record. pub const fn iso_date(&self) -> IsoDate { self.iso } #[inline] + #[must_use] /// Returns a reference to this `TemporalDate`'s calendar slot. pub fn calendar(&self) -> &CalendarSlot { &self.calendar @@ -155,25 +164,38 @@ impl TemporalDate { /// 3.5.7 `IsValidISODate` /// - /// Checks if the current date is a valid ISODate. + /// Checks if the current date is a valid `ISODate`. + #[must_use] pub fn is_valid(&self) -> bool { self.iso.is_valid() } - #[inline] /// `DaysUntil` /// /// Calculates the epoch days between two `TemporalDate`s + #[inline] + #[must_use] pub fn days_until(&self, other: &Self) -> i32 { other.iso.to_epoch_days() - self.iso.to_epoch_days() } } +impl IsoDateSlots for TemporalDate { + /// Returns the structs `IsoDate` + fn iso_date(&self) -> IsoDate { + self.iso + } +} + // ==== Context based API ==== impl TemporalDate { - #[cfg(feature = "context")] + /// Returns the date after adding the given duration to date. + /// + /// Temporal Equivalent: 3.5.13 `AddDate ( calendar, plainDate, duration [ , options [ , dateAdd ] ] )` #[inline] + #[must_use] + #[cfg(feature = "context")] pub fn add_to_date( &self, duration: &Duration, @@ -183,8 +205,12 @@ impl TemporalDate { self.add_date(duration, overflow, context) } - #[cfg(not(feature = "context"))] + /// Returns the date after adding the given duration to date. + /// + /// Temporal Equivalent: 3.5.13 `AddDate ( calendar, plainDate, duration [ , options [ , dateAdd ] ] )` #[inline] + #[must_use] + #[cfg(not(feature = "context"))] pub fn add_to_date( &self, duration: &Duration, @@ -193,19 +219,27 @@ impl TemporalDate { self.add_date(duration, overflow, &mut ()) } - #[cfg(feature = "context")] + /// Returns a duration representing the difference between the dates one and two. + /// + /// Temporal Equivalent: 3.5.6 `DifferenceDate ( calendar, one, two, options )` #[inline] + #[must_use] + #[cfg(feature = "context")] pub fn difference_date( &self, - duration: &Duration, - overflow: ArithmeticOverflow, + other: &Self, + largest_unit: TemporalUnit, context: &mut dyn Any, - ) -> TemporalResult { - self.add_date(duration, overflow, context) + ) -> TemporalResult { + self.diff_date(other, largest_unit, context) } - #[cfg(not(feature = "context"))] + /// Returns a duration representing the difference between the dates one and two. + /// + /// Temporal Equivalent: 3.5.6 `DifferenceDate ( calendar, one, two, options )` #[inline] + #[must_use] + #[cfg(not(feature = "context"))] pub fn difference_date( &self, other: &Self, diff --git a/boa_temporal/src/datetime.rs b/boa_temporal/src/datetime.rs index c90b6cdfc04..df066c4d3f7 100644 --- a/boa_temporal/src/datetime.rs +++ b/boa_temporal/src/datetime.rs @@ -2,9 +2,7 @@ use crate::{ calendar::CalendarSlot, - date::TemporalDate, - iso::{IsoDate, IsoDateTime, IsoTime}, - month_day::TemporalMonthDay, + iso::{IsoDate, IsoDateSlots, IsoDateTime, IsoTime}, options::ArithmeticOverflow, TemporalResult, }; @@ -16,6 +14,8 @@ pub struct TemporalDateTime { calendar: CalendarSlot, } +// ==== Private DateTime API ==== + impl TemporalDateTime { /// Creates a new unchecked `TemporalDateTime`. pub(crate) fn new_unchecked(date: IsoDate, time: IsoTime, calendar: CalendarSlot) -> Self { @@ -25,6 +25,16 @@ impl TemporalDateTime { } } + #[inline] + /// Utility function for validating `IsoDate`s + fn validate_iso(iso: IsoDate) -> bool { + IsoDateTime::new_unchecked(iso, IsoTime::noon()).is_within_limits() + } +} + +// ==== Public DateTime API ==== + +impl TemporalDateTime { /// Creates a new validated `TemporalDateTime`. #[allow(clippy::too_many_arguments)] pub fn new( @@ -53,21 +63,9 @@ impl TemporalDateTime { } #[inline] - /// Utility function for validating `IsoDate`s - fn validate_iso(iso: IsoDate) -> bool { - IsoDateTime::new_unchecked(iso, IsoTime::noon()).is_within_limits() - } - - #[inline] - /// Validates that the provide `TemporalDate` is within iso limits at noon. - pub fn validate_date(date: &TemporalDate) -> bool { - Self::validate_iso(date.iso_date()) - } - - #[inline] - /// Validates that the provided `TemporalMonthDay` is within limits. - pub fn validate_month_day(month_day: &TemporalMonthDay) -> bool { - Self::validate_iso(month_day.iso_date()) + /// Validates whether ISO date slots are within iso limits at noon. + pub fn validate(target: &T) -> bool { + Self::validate_iso(target.iso_date()) } /// Returns the inner `IsoDate` value. diff --git a/boa_temporal/src/duration.rs b/boa_temporal/src/duration.rs index 3d5d1edbc2d..201314acc23 100644 --- a/boa_temporal/src/duration.rs +++ b/boa_temporal/src/duration.rs @@ -30,6 +30,7 @@ pub struct DateDuration { impl DateDuration { /// Creates a new `DateDuration` with provided values. + #[must_use] pub const fn new(years: f64, months: f64, weeks: f64, days: f64) -> Self { Self { years, @@ -39,7 +40,8 @@ impl DateDuration { } } - /// Returns a `PartialDateDuration` with all fields set to NaN. + /// Returns a `PartialDateDuration` with all fields set to `NaN`. + #[must_use] pub const fn partial() -> Self { Self { years: f64::NAN, @@ -50,21 +52,25 @@ impl DateDuration { } /// Returns the `[[years]]` value. + #[must_use] pub const fn years(&self) -> f64 { self.years } /// Returns the `[[months]]` value. + #[must_use] pub const fn months(&self) -> f64 { self.months } /// Returns the `[[weeks]]` value. + #[must_use] pub const fn weeks(&self) -> f64 { self.weeks } /// Returns the `[[days]]` value. + #[must_use] pub const fn days(&self) -> f64 { self.days } @@ -125,6 +131,7 @@ pub struct TimeDuration { impl TimeDuration { /// Creates a new `TimeDuration`. + #[must_use] pub const fn new( hours: f64, minutes: f64, @@ -143,7 +150,8 @@ impl TimeDuration { } } - /// Creates a partial `TimeDuration` with all values set to NaN. + /// Creates a partial `TimeDuration` with all values set to `NaN`. + #[must_use] pub const fn partial() -> Self { Self { hours: f64::NAN, @@ -157,6 +165,7 @@ impl TimeDuration { /// Utility function for returning if values in a valid range. #[inline] + #[must_use] pub fn is_within_range(&self) -> bool { self.hours.abs() < 24f64 && self.minutes.abs() < 60f64 @@ -167,31 +176,37 @@ impl TimeDuration { } /// Returns the `[[hours]]` value. + #[must_use] pub const fn hours(&self) -> f64 { self.hours } /// Returns the `[[minutes]]` value. + #[must_use] pub const fn minutes(&self) -> f64 { self.minutes } /// Returns the `[[seconds]]` value. + #[must_use] pub const fn seconds(&self) -> f64 { self.seconds } /// Returns the `[[milliseconds]]` value. + #[must_use] pub const fn milliseconds(&self) -> f64 { self.milliseconds } /// Returns the `[[microseconds]]` value. + #[must_use] pub const fn microseconds(&self) -> f64 { self.microseconds } /// Returns the `[[nanoseconds]]` value. + #[must_use] pub const fn nanoseconds(&self) -> f64 { self.nanoseconds } @@ -281,6 +296,7 @@ impl Duration { impl Duration { /// Creates a new validated `Duration`. + #[must_use] #[allow(clippy::too_many_arguments)] pub fn new( years: f64, @@ -311,7 +327,8 @@ impl Duration { Ok(duration) } - /// Creates a partial `Duration` with all fields set to NaN. + /// Creates a partial `Duration` with all fields set to `NaN`. + #[must_use] pub const fn partial() -> Self { Self { date: DateDuration::partial(), @@ -319,7 +336,8 @@ impl Duration { } } - /// Creates a `Duration` from only a DateDuration. + /// Creates a `Duration` from only a `DateDuration`. + #[must_use] pub fn from_date_duration(date: DateDuration) -> Self { Self { date, @@ -330,6 +348,7 @@ impl Duration { /// Creates a `Duration` from a provided a day and a `TimeDuration`. /// /// Note: `TimeDuration` records can store a day value to deal with overflow. + #[must_use] pub const fn from_day_and_time(day: f64, time: TimeDuration) -> Self { Self { date: DateDuration::new(0.0, 0.0, 0.0, day), @@ -339,6 +358,7 @@ impl Duration { /// Return if the Durations values are within their valid ranges. #[inline] + #[must_use] pub fn is_time_within_range(&self) -> bool { self.time.is_within_range() } @@ -348,66 +368,81 @@ impl Duration { impl Duration { /// Returns a reference to the inner `TimeDuration` + #[inline] + #[must_use] pub fn time(&self) -> &TimeDuration { &self.time } /// Returns a reference to the inner `DateDuration` + #[inline] + #[must_use] pub fn date(&self) -> &DateDuration { &self.date } /// Set this `DurationRecord`'s `TimeDuration`. + #[inline] pub fn set_time_duration(&mut self, time: TimeDuration) { self.time = time; } /// Set the value for `years`. + #[inline] pub fn set_years(&mut self, y: f64) { self.date.years = y; } /// Set the value for `months`. + #[inline] pub fn set_months(&mut self, mo: f64) { self.date.months = mo; } /// Set the value for `weeks`. + #[inline] pub fn set_weeks(&mut self, w: f64) { self.date.weeks = w; } /// Set the value for `days`. + #[inline] pub fn set_days(&mut self, d: f64) { self.date.days = d; } /// Set the value for `hours`. + #[inline] pub fn set_hours(&mut self, h: f64) { self.time.hours = h; } /// Set the value for `minutes`. + #[inline] pub fn set_minutes(&mut self, m: f64) { self.time.minutes = m; } /// Set the value for `seconds`. + #[inline] pub fn set_seconds(&mut self, s: f64) { self.time.seconds = s; } /// Set the value for `milliseconds`. + #[inline] pub fn set_milliseconds(&mut self, ms: f64) { self.time.milliseconds = ms; } /// Set the value for `microseconds`. + #[inline] pub fn set_microseconds(&mut self, mis: f64) { self.time.microseconds = mis; } /// Set the value for `nanoseconds`. + #[inline] pub fn set_nanoseconds(&mut self, ns: f64) { self.time.nanoseconds = ns; } @@ -460,7 +495,7 @@ impl Duration { /// Returns the duration time values as a vec pub(crate) fn time_values(&self) -> Vec { let mut values = Vec::from([self.date.days]); - values.extend(self.time.into_iter()); + values.extend(&self.time); values } @@ -538,90 +573,90 @@ impl Duration { // a. Set microseconds to floor(nanoseconds / 1000). result.microseconds = (result.nanoseconds / 1000f64).floor(); // b. Set nanoseconds to nanoseconds modulo 1000. - result.nanoseconds = result.nanoseconds % 1000f64; + result.nanoseconds %= 1000f64; // c. Set milliseconds to floor(microseconds / 1000). result.milliseconds = (result.microseconds / 1000f64).floor(); // d. Set microseconds to microseconds modulo 1000. - result.microseconds = result.microseconds % 1000f64; + result.microseconds %= 1000f64; // e. Set seconds to floor(milliseconds / 1000). result.seconds = (result.milliseconds / 1000f64).floor(); // f. Set milliseconds to milliseconds modulo 1000. - result.milliseconds = result.milliseconds % 1000f64; + result.milliseconds %= 1000f64; // g. Set minutes to floor(seconds / 60). result.minutes = (result.seconds / 60f64).floor(); // h. Set seconds to seconds modulo 60. - result.seconds = result.seconds % 60f64; + result.seconds %= 60f64; // i. Set hours to floor(minutes / 60). result.hours = (result.minutes / 60f64).floor(); // j. Set minutes to minutes modulo 60. - result.minutes = result.minutes % 60f64; + result.minutes %= 60f64; // k. Set days to floor(hours / 24). result_days = (result.hours / 24f64).floor(); // l. Set hours to hours modulo 24. - result.hours = result.hours % 24f64; + result.hours %= 24f64; } // 10. Else if largestUnit is "minute", then TemporalUnit::Minute => { // a. Set microseconds to floor(nanoseconds / 1000). // b. Set nanoseconds to nanoseconds modulo 1000. result.microseconds = (result.nanoseconds / 1000f64).floor(); - result.nanoseconds = result.nanoseconds % 1000f64; + result.nanoseconds %= 1000f64; // c. Set milliseconds to floor(microseconds / 1000). // d. Set microseconds to microseconds modulo 1000. result.milliseconds = (result.microseconds / 1000f64).floor(); - result.microseconds = result.microseconds % 1000f64; + result.microseconds %= 1000f64; // e. Set seconds to floor(milliseconds / 1000). // f. Set milliseconds to milliseconds modulo 1000. result.minutes = (result.seconds / 60f64).floor(); - result.seconds = result.seconds % 60f64; + result.seconds %= 60f64; // g. Set minutes to floor(seconds / 60). // h. Set seconds to seconds modulo 60. result.minutes = (result.seconds / 60f64).floor(); - result.seconds = result.seconds % 60f64; + result.seconds %= 60f64; } // 11. Else if largestUnit is "second", then TemporalUnit::Second => { // a. Set microseconds to floor(nanoseconds / 1000). // b. Set nanoseconds to nanoseconds modulo 1000. result.microseconds = (result.nanoseconds / 1000f64).floor(); - result.nanoseconds = result.nanoseconds % 1000f64; + result.nanoseconds %= 1000f64; // c. Set milliseconds to floor(microseconds / 1000). // d. Set microseconds to microseconds modulo 1000. result.milliseconds = (result.microseconds / 1000f64).floor(); - result.microseconds = result.microseconds % 1000f64; + result.microseconds %= 1000f64; // e. Set seconds to floor(milliseconds / 1000). // f. Set milliseconds to milliseconds modulo 1000. result.minutes = (result.seconds / 60f64).floor(); - result.seconds = result.seconds % 60f64; + result.seconds %= 60f64; } // 12. Else if largestUnit is "millisecond", then TemporalUnit::Millisecond => { // a. Set microseconds to floor(nanoseconds / 1000). // b. Set nanoseconds to nanoseconds modulo 1000. result.microseconds = (result.nanoseconds / 1000f64).floor(); - result.nanoseconds = result.nanoseconds % 1000f64; + result.nanoseconds %= 1000f64; // c. Set milliseconds to floor(microseconds / 1000). // d. Set microseconds to microseconds modulo 1000. result.milliseconds = (result.microseconds / 1000f64).floor(); - result.microseconds = result.microseconds % 1000f64; + result.microseconds %= 1000f64; } // 13. Else if largestUnit is "microsecond", then TemporalUnit::Microsecond => { // a. Set microseconds to floor(nanoseconds / 1000). // b. Set nanoseconds to nanoseconds modulo 1000. result.microseconds = (result.nanoseconds / 1000f64).floor(); - result.nanoseconds = result.nanoseconds % 1000f64; + result.nanoseconds %= 1000f64; } // 14. Else, // a. Assert: largestUnit is "nanosecond". @@ -655,15 +690,15 @@ impl Duration { // `CreateTimeDurationRecord` validates that the record that would be created is a valid duration, so validate here if !self.is_valid() { - return Err(TemporalError::range() - .with_message("TimeDurationRecord is not a valid duration.") - .into()); + return Err( + TemporalError::range().with_message("TimeDurationRecord is not a valid duration.") + ); } Ok(Some((result_days, result))) } - /// 7.5.21 UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit, plainRelativeTo )` + /// 7.5.21 `UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit, plainRelativeTo )` #[allow(dead_code)] pub(crate) fn unbalance_duration_relative( &self, @@ -713,9 +748,7 @@ impl Duration { // b. If calendar is undefined, then let Some(mut plain_relative_to) = plain_relative_to.map(Clone::clone) else { // i. Throw a RangeError exception. - return Err(TemporalError::range() - .with_message("Calendar cannot be undefined.") - .into()); + return Err(TemporalError::range().with_message("Calendar cannot be undefined.")); }; // c. If calendar is an Object, then @@ -781,9 +814,7 @@ impl Duration { // b. If calendar is undefined, then let Some(mut plain_relative_to) = plain_relative_to.map(Clone::clone) else { // i. Throw a RangeError exception. - return Err(TemporalError::range() - .with_message("Calendar cannot be undefined.") - .into()); + return Err(TemporalError::range().with_message("Calendar cannot be undefined.")); }; // c. If calendar is an Object, then @@ -834,9 +865,7 @@ impl Duration { // 14. If calendar is undefined, then let Some(mut plain_relative_to) = plain_relative_to.map(Clone::clone) else { // a. Throw a RangeError exception. - return Err(TemporalError::range() - .with_message("Calendar cannot be undefined.") - .into()); + return Err(TemporalError::range().with_message("Calendar cannot be undefined.")); }; // 15. If calendar is an Object, then @@ -898,7 +927,7 @@ impl Duration { plain_relative_to: Option<&TemporalDate>, context: &mut dyn Any, ) -> TemporalResult { - let mut result = DateDuration::from(self.date); + let mut result = self.date; // 1. Let allZero be false. // 2. If years = 0, and months = 0, and weeks = 0, and days = 0, set allZero to true. @@ -919,9 +948,7 @@ impl Duration { // 4. If plainRelativeTo is undefined, then let Some(mut plain_relative_to) = plain_relative_to.map(Clone::clone) else { // a. Throw a RangeError exception. - return Err(TemporalError::range() - .with_message("relativeTo cannot be undefined.") - .into()); + return Err(TemporalError::range().with_message("relativeTo cannot be undefined.")); }; // 5. Let sign be ! DurationSign(years, months, weeks, days, 0, 0, 0, 0, 0, 0). @@ -1156,8 +1183,7 @@ impl Duration { { // a. Throw a RangeError exception. return Err(TemporalError::range() - .with_message("plainRelativeTo canot be undefined with given TemporalUnit") - .into()); + .with_message("plainRelativeTo canot be undefined with given TemporalUnit")); } // 5. If unit is one of "year", "month", "week", or "day", then TemporalUnit::Year | TemporalUnit::Month | TemporalUnit::Week | TemporalUnit::Day => { @@ -1175,9 +1201,7 @@ impl Duration { result.date.days + nanos / NS_PER_DAY as f64 } else { // implementation of b: i-iii needed. - return Err(TemporalError::range() - .with_message("Not yet implemented.") - .into()); + return Err(TemporalError::range().with_message("Not yet implemented.")); }; // d. Set days, hours, minutes, seconds, milliseconds, microseconds, and nanoseconds to 0. result.date.days = 0f64; @@ -1226,8 +1250,11 @@ impl Duration { // i. Let dateAdd be unused. // e. Let yearsLater be ? AddDate(calendar, plainRelativeTo, yearsDuration, undefined, dateAdd). - let years_later = plain_relative_to - .add_date(&years_duration, ArithmeticOverflow::Constrain, context)?; + let years_later = plain_relative_to.add_date( + &years_duration, + ArithmeticOverflow::Constrain, + context, + )?; // f. Let yearsMonthsWeeks be ! CreateTemporalDuration(years, months, weeks, 0, 0, 0, 0, 0, 0, 0). let years_months_weeks = Self::new_unchecked( @@ -1241,8 +1268,11 @@ impl Duration { ); // g. Let yearsMonthsWeeksLater be ? AddDate(calendar, plainRelativeTo, yearsMonthsWeeks, undefined, dateAdd). - let years_months_weeks_later = plain_relative_to - .add_date(&years_months_weeks, ArithmeticOverflow::Constrain, context)?; + let years_months_weeks_later = plain_relative_to.add_date( + &years_months_weeks, + ArithmeticOverflow::Constrain, + context, + )?; // h. Let monthsWeeksInDays be DaysUntil(yearsLater, yearsMonthsWeeksLater). let months_weeks_in_days = years_later.days_until(&years_months_weeks_later); @@ -1333,8 +1363,11 @@ impl Duration { // i. Let dateAdd be unused. // e. Let yearsMonthsLater be ? AddDate(calendar, plainRelativeTo, yearsMonths, undefined, dateAdd). - let years_months_later = - plain_relative_to.add_date(&years_months, ArithmeticOverflow::Constrain, context)?; + let years_months_later = plain_relative_to.add_date( + &years_months, + ArithmeticOverflow::Constrain, + context, + )?; // f. Let yearsMonthsWeeks be ! CreateTemporalDuration(years, months, weeks, 0, 0, 0, 0, 0, 0, 0). let years_months_weeks = Self::from_date_duration(DateDuration::new( @@ -1345,8 +1378,11 @@ impl Duration { )); // g. Let yearsMonthsWeeksLater be ? AddDate(calendar, plainRelativeTo, yearsMonthsWeeks, undefined, dateAdd). - let years_months_weeks_later = plain_relative_to - .add_date(&years_months_weeks, ArithmeticOverflow::Constrain, context)?; + let years_months_weeks_later = plain_relative_to.add_date( + &years_months_weeks, + ArithmeticOverflow::Constrain, + context, + )?; // h. Let weeksInDays be DaysUntil(yearsMonthsLater, yearsMonthsWeeksLater). let weeks_in_days = years_months_later.days_until(&years_months_weeks_later); @@ -1570,6 +1606,8 @@ impl Duration { impl Duration { /// Returns the absolute value of `Duration`. + #[inline] + #[must_use] pub fn abs(&self) -> Self { Self { date: DateDuration::new( @@ -1592,6 +1630,8 @@ impl Duration { /// 7.5.10 `DurationSign ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds )` /// /// Determines the sign for the current self. + #[inline] + #[must_use] pub fn duration_sign(&self) -> i32 { // 1. For each value v of « years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds », do for v in self { @@ -1608,6 +1648,8 @@ impl Duration { } /// 7.5.12 `DefaultTemporalLargestUnit ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds )` + #[inline] + #[must_use] pub fn default_temporal_largest_unit(&self) -> TemporalUnit { for (index, value) in self.into_iter().enumerate() { if value != 0.0 { @@ -1630,6 +1672,8 @@ impl Duration { } /// Abstract Operation 7.5.17 `BalanceTimeDuration ( days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, largestUnit )` + #[inline] + #[must_use] pub fn balance_time_duration( &self, largest_unit: TemporalUnit, @@ -1640,9 +1684,7 @@ impl Duration { // 2. If balanceResult is positive overflow or negative overflow, then let Some(result) = balance_result else { // a. Throw a RangeError exception. - return Err(TemporalError::range() - .with_message("duration overflowed viable range.") - .into()); + return Err(TemporalError::range().with_message("duration overflowed viable range.")); }; // 3. Else, // a. Return balanceResult. diff --git a/boa_temporal/src/error.rs b/boa_temporal/src/error.rs index 776698149d8..75957a2520c 100644 --- a/boa_temporal/src/error.rs +++ b/boa_temporal/src/error.rs @@ -2,7 +2,7 @@ use core::fmt; -/// TemporalError's error type. +/// `TemporalError`'s error type. #[derive(Debug, Default, Clone, Copy)] pub enum ErrorKind { /// Error. @@ -41,6 +41,7 @@ impl TemporalError { } /// Create a generic error + #[must_use] pub fn general(msg: S) -> Self where S: Into>, @@ -49,16 +50,19 @@ impl TemporalError { } /// Create a range error. + #[must_use] pub fn range() -> Self { Self::new(ErrorKind::Range) } /// Create a type error. + #[must_use] pub fn r#type() -> Self { Self::new(ErrorKind::Type) } /// Add a message to the error. + #[must_use] pub fn with_message(mut self, msg: S) -> Self where S: Into>, @@ -68,11 +72,13 @@ impl TemporalError { } /// Returns this error's kind. + #[must_use] pub fn kind(&self) -> ErrorKind { self.kind } /// Returns the error message. + #[must_use] pub fn message(&self) -> &str { &self.msg } diff --git a/boa_temporal/src/fields.rs b/boa_temporal/src/fields.rs index 97360f1b09a..2ad53a657c7 100644 --- a/boa_temporal/src/fields.rs +++ b/boa_temporal/src/fields.rs @@ -1,4 +1,4 @@ -//! TemporalFields native Rust representation. +//! `TemporalFields` native Rust representation. use std::str::FromStr; @@ -390,10 +390,10 @@ impl TemporalFields { )), FieldMap::HOUR => result.push(("hour".to_owned(), FieldValue::Integer(self.hour))), FieldMap::MINUTE => { - result.push(("minute".to_owned(), FieldValue::Integer(self.minute))) + result.push(("minute".to_owned(), FieldValue::Integer(self.minute))); } FieldMap::SECOND => { - result.push(("second".to_owned(), FieldValue::Integer(self.second))) + result.push(("second".to_owned(), FieldValue::Integer(self.second))); } FieldMap::MILLISECOND => result.push(( "millisecond".to_owned(), @@ -484,9 +484,7 @@ fn month_code_to_integer(mc: &TinyStr4) -> TemporalResult { "M11" => Ok(11), "M12" => Ok(12), "M13" => Ok(13), - _ => Err(TemporalError::range() - .with_message("monthCode is not within the valid values.") - .into()), + _ => Err(TemporalError::range().with_message("monthCode is not within the valid values.")), } } diff --git a/boa_temporal/src/iso.rs b/boa_temporal/src/iso.rs index 01538696aee..559af8f15e8 100644 --- a/boa_temporal/src/iso.rs +++ b/boa_temporal/src/iso.rs @@ -1,4 +1,14 @@ +//! The `ISO` module implements the internal ISO field slots. +//! +//! The three main types of slots are: +//! - `IsoDateTime` +//! - `IsoDate` +//! - `IsoTime` +//! //! An `IsoDate` that represents the `[[ISOYear]]`, `[[ISOMonth]]`, and `[[ISODay]]` internal slots. +//! An `IsoTime` that represents the `[[ISOHour]]`, `[[ISOMinute]]`, `[[ISOsecond]]`, `[[ISOmillisecond]]`, +//! `[[ISOmicrosecond]]`, and `[[ISOnanosecond]]` internal slots. +//! An `IsoDateTime` has the internal slots of both an `IsoDate` and `IsoTime`. use crate::{ duration::DateDuration, error::TemporalError, options::ArithmeticOverflow, utils, @@ -35,7 +45,7 @@ impl IsoDateTime { } /// Returns the UTC epoch nanoseconds for this `IsoDateTime`. - pub(crate) fn to_utc_epoch_nanoseconds(&self, offset: f64) -> Option { + pub(crate) fn to_utc_epoch_nanoseconds(self, offset: f64) -> Option { let day = self.date.to_epoch_days(); let time = self.time.to_epoch_ms(); let epoch_ms = utils::epoch_days_to_epoch_ms(day, time); @@ -61,6 +71,12 @@ impl IsoDateTime { // TODO: Figure out `ICU4X` interop / replacement? +/// A trait for accessing the `IsoDate` across the various Temporal objects +pub trait IsoDateSlots { + /// Returns the target's internal `IsoDate`. + fn iso_date(&self) -> IsoDate; +} + /// `IsoDate` serves as a record for the `[[ISOYear]]`, `[[ISOMonth]]`, /// and `[[ISODay]]` internal fields. /// @@ -102,26 +118,6 @@ impl IsoDate { } } - pub(crate) fn new_year_month( - year: i32, - month: i32, - overflow: ArithmeticOverflow, - ) -> TemporalResult { - match overflow { - ArithmeticOverflow::Constrain => { - let m = month.clamp(1, 12) as u8; - Ok(Self::new_unchecked(year, m, 1)) - } - ArithmeticOverflow::Reject => { - if (1..=12).contains(&month) { - return Err(TemporalError::range().with_message("IsoYearMonth is not valid.")); - } - // NOTE: Values have been verified to be in a u8 range. - Ok(Self::new_unchecked(year, month as u8, 1)) - } - } - } - /// Create a balanced `IsoDate` /// /// Equivalent to `BalanceISODate`. @@ -136,34 +132,35 @@ impl IsoDate { } /// Returns the year field - pub(crate) const fn year(&self) -> i32 { + pub(crate) const fn year(self) -> i32 { self.year } /// Returns the month field - pub(crate) const fn month(&self) -> u8 { + pub(crate) const fn month(self) -> u8 { self.month } /// Returns the day field - pub(crate) const fn day(&self) -> u8 { + pub(crate) const fn day(self) -> u8 { self.day } /// Functionally the same as Date's abstract operation `MakeDay` /// /// Equivalent to `IsoDateToEpochDays` - pub(crate) fn to_epoch_days(&self) -> i32 { + pub(crate) fn to_epoch_days(self) -> i32 { iso_date_to_epoch_days(self.year, self.month.into(), self.day.into()) } /// Returns if the current `IsoDate` is valid. - pub(crate) fn is_valid(&self) -> bool { + pub(crate) fn is_valid(self) -> bool { is_valid_date(self.year, self.month.into(), self.day.into()) } + /// Returns the resulting `IsoDate` from adding a provided `Duration` to this `IsoDate` pub(crate) fn add_iso_date( - &self, + self, duration: &DateDuration, overflow: ArithmeticOverflow, ) -> TemporalResult { @@ -199,9 +196,8 @@ impl IsoDate { } impl IsoDate { - // TODO: look into using Date across the board...TBD. /// Creates `[[ISOYear]]`, `[[isoMonth]]`, `[[isoDay]]` fields from `ICU4X`'s `Date` struct. - pub(crate) fn as_icu4x(&self) -> TemporalResult> { + pub(crate) fn as_icu4x(self) -> TemporalResult> { IcuDate::try_new_iso_date(self.year, self.month, self.day) .map_err(|e| TemporalError::range().with_message(e.to_string())) } @@ -307,7 +303,7 @@ impl IsoTime { /// Note: This method is library specific and not in spec /// /// Functionally the same as Date's `MakeTime` - pub(crate) fn to_epoch_ms(&self) -> f64 { + pub(crate) fn to_epoch_ms(self) -> f64 { f64::from(self.hour).mul_add( utils::MS_PER_HOUR, f64::from(self.minute) * utils::MS_PER_MINUTE, @@ -317,7 +313,7 @@ impl IsoTime { // ==== `IsoDate` specific utiltiy functions ==== -/// Returns the EpochDays based off the given year, month, and day. +/// Returns the Epoch days based off the given year, month, and day. #[inline] fn iso_date_to_epoch_days(year: i32, month: i32, day: i32) -> i32 { // 1. Let resolvedYear be year + floor(month / 12). @@ -327,7 +323,7 @@ fn iso_date_to_epoch_days(year: i32, month: i32, day: i32) -> i32 { // 3. Find a time t such that EpochTimeToEpochYear(t) is resolvedYear, EpochTimeToMonthInYear(t) is resolvedMonth, and EpochTimeToDate(t) is 1. let year_t = utils::epoch_time_for_year(resolved_year); - let month_t = utils::epoch_time_for_month_given_year(resolved_month.into(), resolved_year); + let month_t = utils::epoch_time_for_month_given_year(resolved_month, resolved_year); // 4. Return EpochTimeToDayNumber(t) + date - 1. utils::epoch_time_to_day_number(year_t + month_t) + day - 1 @@ -343,185 +339,3 @@ fn is_valid_date(year: i32, month: i32, day: i32) -> bool { let days_in_month = utils::iso_days_in_month(year, month); (1..=days_in_month).contains(&day) } - -/* -impl IsoDate { - /// 3.5.6 `RegulateISODate` - pub(crate) fn from_unregulated( - year: i32, - month: i32, - day: i32, - overflow: ArithmeticOverflow, - ) -> TemporalResult { - match overflow { - ArithmeticOverflow::Constrain => { - let m = month.clamp(1, 12); - let days_in_month = utils::iso_days_in_month(year, month); - let d = day.clamp(1, days_in_month); - Ok(Self::new_unchecked(year, m, d)) - } - ArithmeticOverflow::Reject => { - let date = Self::new_unchecked(year, month, day); - if !date.is_valid() { - return Err(TemporalError::range() - .with_message("not a valid ISO date.")); - } - Ok(date) - } - } - } - - /// 12.2.35 `ISODateFromFields ( fields, overflow )` - /// - /// Note: fields.month must be resolved prior to using `from_temporal_fields` - pub(crate) fn from_temporal_fields( - fields: &TemporalFields, - overflow: ArithmeticOverflow, - ) -> TemporalResult { - Self::from_unregulated( - fields.year().expect("Cannot fail per spec"), - fields.month().expect("cannot fail after resolution"), - fields.day().expect("cannot fail per spec"), - overflow, - ) - } - - /// Create a Month-Day record from a `TemporalFields` object. - pub(crate) fn month_day_from_temporal_fields( - fields: &TemporalFields, - overflow: ArithmeticOverflow, - ) -> TemporalResult { - match fields.year() { - Some(year) => Self::from_unregulated( - year, - fields.month().expect("month must exist."), - fields.day().expect("cannot fail per spec"), - overflow, - ), - None => Self::from_unregulated( - 1972, - fields.month().expect("cannot fail per spec"), - fields.day().expect("cannot fail per spec."), - overflow, - ), - } - } - - /// Within `YearMonth` valid limits - pub(crate) const fn within_year_month_limits(&self) -> bool { - if self.year < -271_821 || self.year > 275_760 { - return false; - } - - if self.year == -271_821 && self.month < 4 { - return false; - } - - if self.year == 275_760 && self.month > 9 { - return true; - } - - true - } - - /// 3.5.5 `DifferenceISODate` - pub(crate) fn diff_iso_date( - &self, - o: &Self, - largest_unit: TemporalUnit, - ) -> TemporalResult { - debug_assert!(self.is_valid()); - // TODO: Implement on `ICU4X`. - - Err(TemporalError::range() - .with_message("not yet implemented.")) - } - - /// 3.5.7 `IsValidISODate` - pub(crate) fn is_valid(&self) -> bool { - if self.month < 1 || self.month > 12 { - return false; - } - - let days_in_month = utils::iso_days_in_month(self.year, self.month); - - if self.day < 1 || self.day > days_in_month { - return false; - } - true - } - - /// 13.2 `IsoDateToEpochDays` - pub(crate) fn as_epoch_days(&self) -> i32 { - // 1. Let resolvedYear be year + floor(month / 12). - let resolved_year = self.year + (f64::from(self.month) / 12_f64).floor() as i32; - // 2. Let resolvedMonth be month modulo 12. - let resolved_month = self.month % 12; - - // 3. Find a time t such that EpochTimeToEpochYear(t) is resolvedYear, EpochTimeToMonthInYear(t) is resolvedMonth, and EpochTimeToDate(t) is 1. - let year_t = utils::epoch_time_for_year(resolved_year); - let month_t = utils::epoch_time_for_month_given_year( - resolved_month, - resolved_year, - ); - - // 4. Return EpochTimeToDayNumber(t) + date - 1. - utils::epoch_time_to_day_number(year_t + month_t) + self.day - 1 - } - - // NOTE: Implementing as mut self so balance is applied to self, but TBD. - /// 3.5.8 `BalanceIsoDate` - pub(crate) fn balance(&mut self) { - let epoch_days = self.as_epoch_days(); - let ms = utils::epoch_days_to_epoch_ms(epoch_days, 0); - - // Balance current values - self.year = utils::epoch_time_to_epoch_year(ms); - self.month = utils::epoch_time_to_month_in_year(ms); - self.day = utils::epoch_time_to_date(ms); - } - - // NOTE: Used in AddISODate only, so could possibly be deleted in the future. - /// 9.5.4 `BalanceISOYearMonth ( year, month )` - pub(crate) fn balance_year_month(&mut self) { - self.year += (self.month - 1) / 12; - self.month = ((self.month - 1) % 12) + 1; - } - - /// 3.5.11 `AddISODate ( year, month, day, years, months, weeks, days, overflow )` - pub(crate) fn add_iso_date( - &self, - date_duration: DateDuration, - overflow: ArithmeticOverflow, - ) -> TemporalResult { - // 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_unchecked( - 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(); - - // 4. Let intermediate be ? RegulateISODate(intermediate.[[Year]], intermediate.[[Month]], day, overflow). - let mut new_date = Self::from_unregulated( - intermediate.year(), - intermediate.month(), - self.day, - overflow, - )?; - - // 5. Set days to days + 7 × weeks. - // 6. Let d be intermediate.[[Day]] + days. - 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). - new_date.balance(); - - Ok(new_date) - } -} -*/ diff --git a/boa_temporal/src/month_day.rs b/boa_temporal/src/month_day.rs index 10409580478..50a9bcaf21b 100644 --- a/boa_temporal/src/month_day.rs +++ b/boa_temporal/src/month_day.rs @@ -1,6 +1,11 @@ //! `TemporalMonthDay` -use crate::{calendar::CalendarSlot, iso::IsoDate, options::ArithmeticOverflow, TemporalResult}; +use crate::{ + calendar::CalendarSlot, + iso::{IsoDate, IsoDateSlots}, + options::ArithmeticOverflow, + TemporalResult, +}; /// The `TemporalMonthDay` struct #[derive(Debug, Default, Clone)] @@ -17,6 +22,7 @@ impl TemporalMonthDay { } #[inline] + #[must_use] /// Creates a new valid `TemporalMonthDay`. pub fn new( month: i32, @@ -29,14 +35,17 @@ impl TemporalMonthDay { } #[inline] - /// Returns a reference to this `MonthDay`'s `IsoDate` - pub fn iso_date(&self) -> IsoDate { - self.iso - } - - #[inline] + #[must_use] /// Returns a reference to `MonthDay`'s `CalendarSlot` pub fn calendar(&self) -> &CalendarSlot { &self.calendar } } + +impl IsoDateSlots for TemporalMonthDay { + #[inline] + /// Returns this structs `IsoDate`. + fn iso_date(&self) -> IsoDate { + self.iso + } +} diff --git a/boa_temporal/src/options.rs b/boa_temporal/src/options.rs index 24a9c7555bf..6f6ccd3cffb 100644 --- a/boa_temporal/src/options.rs +++ b/boa_temporal/src/options.rs @@ -4,22 +4,38 @@ use core::{fmt, str::FromStr}; use crate::TemporalError; +/// The relevant unit that should be used for the operation that +/// this option is provided as a value. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum TemporalUnit { + /// The `Auto` unit Auto = 0, + /// The `Nanosecond` unit Nanosecond, + /// The `Microsecond` unit Microsecond, + /// The `Millisecond` unit Millisecond, + /// The `Second` unit Second, + /// The `Minute` unit Minute, + /// The `Hour` unit Hour, + /// The `Day` unit Day, + /// The `Week` unit Week, + /// The `Month` unit Month, + /// The `Year` unit Year, } impl TemporalUnit { + #[inline] + #[must_use] + /// Returns the `MaximumRoundingIncrement` for the current `TemporalUnit`. pub fn to_maximum_rounding_increment(self) -> Option { use TemporalUnit::{ Auto, Day, Hour, Microsecond, Millisecond, Minute, Month, Nanosecond, Second, Week, @@ -43,7 +59,8 @@ impl TemporalUnit { } } -#[derive(Debug)] +/// A parsing error for `TemporalUnit` +#[derive(Debug, Clone, Copy)] pub struct ParseTemporalUnitError; impl fmt::Display for ParseTemporalUnitError { @@ -97,11 +114,14 @@ impl fmt::Display for TemporalUnit { /// and "reject" options. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ArithmeticOverflow { + /// Constrain option Constrain, + /// Constrain option Reject, } -#[derive(Debug)] +/// A parsing error for `ArithemeticOverflow` +#[derive(Debug, Clone, Copy)] pub struct ParseArithmeticOverflowError; impl fmt::Display for ParseArithmeticOverflowError { @@ -133,12 +153,16 @@ impl fmt::Display for ArithmeticOverflow { } /// `Duration` overflow options. +#[derive(Debug, Clone, Copy)] pub enum DurationOverflow { + /// Constrain option Constrain, + /// Balance option Balance, } -#[derive(Debug)] +/// A parsing error for `DurationOverflow`. +#[derive(Debug, Clone, Copy)] pub struct ParseDurationOverflowError; impl fmt::Display for ParseDurationOverflowError { @@ -170,14 +194,20 @@ impl fmt::Display for DurationOverflow { } /// The disambiguation options for an instant. +#[derive(Debug, Clone, Copy)] pub enum InstantDisambiguation { + /// Compatible option Compatible, + /// Earlier option Earlier, + /// Later option Later, + /// Reject option Reject, } -#[derive(Debug)] +/// A parsing error on `InstantDisambiguation` options. +#[derive(Debug, Clone, Copy)] pub struct ParseInstantDisambiguationError; impl fmt::Display for ParseInstantDisambiguationError { @@ -213,14 +243,20 @@ impl fmt::Display for InstantDisambiguation { } /// Offset disambiguation options. +#[derive(Debug, Clone, Copy)] pub enum OffsetDisambiguation { + /// Use option Use, + /// Prefer option Prefer, + /// Ignore option Ignore, + /// Reject option Reject, } -#[derive(Debug)] +/// A parsing error for `OffsetDisambiguation` parsing. +#[derive(Debug, Clone, Copy)] pub struct ParseOffsetDisambiguationError; impl fmt::Display for ParseOffsetDisambiguationError { @@ -257,30 +293,49 @@ impl fmt::Display for OffsetDisambiguation { // TODO: Figure out what to do with intl's RoundingMode +/// Declares the specified `RoundingMode` for the operation. #[derive(Debug, Copy, Clone, Default)] pub enum TemporalRoundingMode { + /// Ceil RoundingMode Ceil, + /// Floor RoundingMode Floor, + /// Expand RoundingMode Expand, + /// Truncate RoundingMode Trunc, + /// HalfCeil RoundingMode HalfCeil, + /// HalfFloor RoundingMode HalfFloor, + /// HalfExpand RoundingMode - Default #[default] HalfExpand, + /// HalfTruncate RoundingMode HalfTrunc, + /// HalfEven RoundingMode HalfEven, } +/// The `UnsignedRoundingMode` #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum TemporalUnsignedRoundingMode { + /// `Infinity` `RoundingMode` Infinity, + /// `Zero` `RoundingMode` Zero, + /// `HalfInfinity` `RoundingMode` HalfInfinity, + /// `HalfZero` `RoundingMode` HalfZero, + /// `HalfEven` `RoundingMode` HalfEven, } impl TemporalRoundingMode { + #[inline] + #[must_use] + /// Negates the current `RoundingMode`. pub const fn negate(self) -> Self { use TemporalRoundingMode::{ Ceil, Expand, Floor, HalfCeil, HalfEven, HalfExpand, HalfFloor, HalfTrunc, Trunc, @@ -299,6 +354,9 @@ impl TemporalRoundingMode { } } + #[inline] + #[must_use] + /// Returns the `UnsignedRoundingMode` pub const fn get_unsigned_round_mode(self, is_negative: bool) -> TemporalUnsignedRoundingMode { use TemporalRoundingMode::{ Ceil, Expand, Floor, HalfCeil, HalfEven, HalfExpand, HalfFloor, HalfTrunc, Trunc, diff --git a/boa_temporal/src/time.rs b/boa_temporal/src/time.rs index ffa0fcf299b..e3920f0124d 100644 --- a/boa_temporal/src/time.rs +++ b/boa_temporal/src/time.rs @@ -2,7 +2,8 @@ use crate::iso::IsoTime; -#[derive(Debug, Default)] +/// The Temporal `PlainTime` object. +#[derive(Debug, Default, Clone, Copy)] pub struct TemporalTime { iso: IsoTime, } diff --git a/boa_temporal/src/utils.rs b/boa_temporal/src/utils.rs index 88e18a9eef4..627409f4193 100644 --- a/boa_temporal/src/utils.rs +++ b/boa_temporal/src/utils.rs @@ -120,7 +120,7 @@ pub(crate) fn epoch_days_to_epoch_ms(day: i32, time: f64) -> f64 { /// `EpochTimeToDayNumber` /// -/// This equation is the same ECMAScripts `Date(t)` +/// This equation is the equivalent to `ECMAScript`'s `Date(t)` pub(crate) fn epoch_time_to_day_number(t: f64) -> i32 { (t / f64::from(MS_PER_DAY)).floor() as i32 } @@ -328,7 +328,7 @@ pub(crate) fn to_iso_day_of_week(year: i32, month: u8, day: u8) -> i32 { // ==== Tests ===== -// TODO(nekevss): Add more to the below. +// TODO(nekevss): Add way more to the below. #[cfg(test)] mod tests { diff --git a/boa_temporal/src/year_month.rs b/boa_temporal/src/year_month.rs index 2061dcee1ac..656b98f304a 100644 --- a/boa_temporal/src/year_month.rs +++ b/boa_temporal/src/year_month.rs @@ -1,6 +1,11 @@ -//! TemporalYearMonth +//! `TemporalYearMonth` -use crate::{calendar::CalendarSlot, iso::IsoDate, options::ArithmeticOverflow, TemporalResult}; +use crate::{ + calendar::CalendarSlot, + iso::{IsoDate, IsoDateSlots}, + options::ArithmeticOverflow, + TemporalResult, +}; /// The `TemporalYearMonth` struct #[derive(Debug, Default, Clone)] @@ -17,26 +22,32 @@ impl TemporalYearMonth { } #[inline] + #[must_use] /// Creates a new valid `TemporalYearMonth`. pub fn new( year: i32, month: i32, + reference_day: Option, calendar: CalendarSlot, overflow: ArithmeticOverflow, ) -> TemporalResult { - let iso = IsoDate::new_year_month(year, month, overflow)?; + let day = reference_day.unwrap_or(1); + let iso = IsoDate::new(year, month, day, overflow)?; Ok(Self::new_unchecked(iso, calendar)) } #[inline] - /// Returns a reference to this `YearMonth`'s `IsoDate` - pub fn iso_date(&self) -> IsoDate { - self.iso - } - - #[inline] + #[must_use] /// Returns a reference to `YearMonth`'s `CalendarSlot` pub fn calendar(&self) -> &CalendarSlot { &self.calendar } } + +impl IsoDateSlots for TemporalYearMonth { + #[inline] + /// Returns this `YearMonth`'s `IsoDate` + fn iso_date(&self) -> IsoDate { + self.iso + } +} From 4f576a994ebe66bf365af0cf5266915ce80158c0 Mon Sep 17 00:00:00 2001 From: nekevss Date: Mon, 13 Nov 2023 18:23:05 -0500 Subject: [PATCH 5/9] Finish up clean up and linting of draft --- .../src/builtins/temporal/calendar/mod.rs | 117 +----- .../src/builtins/temporal/calendar/object.rs | 71 +++- .../src/builtins/temporal/duration/mod.rs | 1 - boa_engine/src/builtins/temporal/fields.rs | 8 +- .../src/builtins/temporal/plain_date/iso.rs | 237 ------------- .../builtins/temporal/plain_date_time/iso.rs | 100 ------ boa_temporal/src/calendar.rs | 7 +- boa_temporal/src/date.rs | 9 +- boa_temporal/src/datetime.rs | 12 +- boa_temporal/src/duration.rs | 2 - boa_temporal/src/fields.rs | 334 ++---------------- boa_temporal/src/iso.rs | 4 +- boa_temporal/src/lib.rs | 15 +- boa_temporal/src/month_day.rs | 4 +- boa_temporal/src/utils.rs | 2 +- boa_temporal/src/year_month.rs | 6 +- 16 files changed, 153 insertions(+), 776 deletions(-) delete mode 100644 boa_engine/src/builtins/temporal/plain_date/iso.rs delete mode 100644 boa_engine/src/builtins/temporal/plain_date_time/iso.rs diff --git a/boa_engine/src/builtins/temporal/calendar/mod.rs b/boa_engine/src/builtins/temporal/calendar/mod.rs index 0a343cb0e85..ddd2038e32f 100644 --- a/boa_engine/src/builtins/temporal/calendar/mod.rs +++ b/boa_engine/src/builtins/temporal/calendar/mod.rs @@ -1140,12 +1140,22 @@ pub(crate) fn to_temporal_calendar_slot_value( return Ok(calendar); } } else if calendar_like.is_plain_date_time() { - todo!() + // TODO + return Err(JsNativeError::range() + .with_message("Not yet implemented.") + .into()); } else if calendar_like.is_plain_year_month() { - todo!() + // TODO + return Err(JsNativeError::range() + .with_message("Not yet implemented.") + .into()); } else if calendar_like.is_plain_month_day() { - todo!() + // TODO + return Err(JsNativeError::range() + .with_message("Not yet implemented.") + .into()); } else if calendar_like.is_zoned_date_time() { + // TODO return Err(JsNativeError::range() .with_message("Not yet implemented.") .into()); @@ -1187,6 +1197,7 @@ fn object_implements_calendar_protocol(calendar_like: &JsObject, context: &mut C }) } +/// Utility function for taking a `JsValue` and converting it to a temporal library `CalendarDateLike` enum. fn to_calendar_date_like(date_like: &JsValue, context: &mut Context) -> JsResult { match date_like { JsValue::Object(o) if o.is_plain_date_time() => { @@ -1213,103 +1224,3 @@ fn to_calendar_date_like(date_like: &JsValue, context: &mut Context) -> JsResult } } } - -// ---------------------------- 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 -// function that aligns with a method on `Temporal.Calendar`. These functions appear -// to be a second completely abstract builtin calendar implementation itself, so -// separating them from the other Abstract Operations seems both natural and will -// hopefully make any changes more maintainable. -// -// NOTE: Instead of creating temporal calendar it may be more efficient to retrieve -// the protocol and call the value directly in rust, something to consider. - -/* -/// 12.2.2 `CalendarFields ( calendar, fieldNames )` -/// -/// `CalendarFields` takes the input fields and adds the `extraFieldDescriptors` for -/// that specific calendar. -#[allow(unused)] -pub(crate) fn calendar_fields( - calendar: &JsValue, - field_names: Vec, - context: &mut Context, -) -> JsResult> { - let field_names = Array::create_array_from_list(field_names, context); - // 1. If calendar is a String, then - // a. Set calendar to ! CreateTemporalCalendar(calendar). - // b. Let fieldsArray be ? Call(%Temporal.Calendar.prototype.fields%, calendar, « CreateArrayFromList(fieldNames) »). - // c. Return ! CreateListFromArrayLike(fieldsArray, « String »). - // 2. Let fieldsArray be ? Invoke(calendar, "fields", « CreateArrayFromList(fieldNames) »). - let fields_array = call_method_on_abstract_calendar( - calendar, - &JsString::from("fields"), - &[field_names.into()], - context, - )?; - - // 3. Let iteratorRecord be ? GetIterator(fieldsArray, sync). - let mut iterator_record = fields_array.get_iterator(context, Some(IteratorHint::Sync), None)?; - // 4. Return ? IteratorToListOfType(iteratorRecord, « String »). - super::iterator_to_list_of_types(&mut iterator_record, &[crate::value::Type::String], context) -} - -/// 12.2.3 `CalendarMergeFields ( calendar, fields, additionalFields )` -/// -/// Returns either a normal completion containing an Object, or a throw completion. -#[allow(unused)] -pub(crate) fn calendar_merge_fields( - calendar: &JsValue, - fields: &TemporalFields, - additional_fields: &JsValue, - context: &mut Context, -) -> JsResult { - // 1. If calendar is a String, then - // a. Set calendar to ! CreateTemporalCalendar(calendar). - // b. Return ? Call(%Temporal.Calendar.prototype.mergeFields%, calendar, « fields, additionalFields »). - // 2. Let result be ? Invoke(calendar, "mergeFields", « fields, additionalFields »). - let result = call_method_on_abstract_calendar( - calendar, - &JsString::from("mergeFields"), - &[fields.as_object(context)?.into(), additional_fields.clone()], - context, - )?; - - // 3. If Type(result) is not Object, throw a TypeError exception. - // 4. Return result. - match result { - JsValue::Object(o) => Ok(o), - _ => Err(JsNativeError::typ() - .with_message("mergeFields must return an object") - .into()), - } -} - -/// 12.2.24 `CalendarDateFromFields ( calendar, fields [ , options [ , dateFromFields ] ] )` -#[allow(unused)] -pub(crate) fn calendar_date_from_fields( - calendar: &JsValue, - _fields: &JsObject, - options: Option<&JsValue>, - _date_from_fields: Option<&JsObject>, -) -> JsResult { - let _options = match options { - Some(o) => o.clone(), - _ => JsValue::undefined(), - }; - // 1. If options is not present, set options to undefined. - // 2. If calendar is a String, then - // a. Set calendar to ! CreateTemporalCalendar(calendar). - // b. Return ? Call(%Temporal.Calendar.prototype.dateFromFields%, calendar, « fields, options »). - // 3. If dateFromFields is not present, set dateFromFields to ? GetMethod(calendar, "dateFromFields"). - // 4. Let date be ? Call(calendar, dateFromFields, « fields, options »). - // 5. Perform ? RequireInternalSlot(date, [[InitializedTemporalDate]]). - // 6. Return date. - - Err(JsNativeError::range() - .with_message("not yet implemented.") - .into()) -} -*/ diff --git a/boa_engine/src/builtins/temporal/calendar/object.rs b/boa_engine/src/builtins/temporal/calendar/object.rs index 60ece60f4eb..dedc4978d7f 100644 --- a/boa_engine/src/builtins/temporal/calendar/object.rs +++ b/boa_engine/src/builtins/temporal/calendar/object.rs @@ -207,7 +207,8 @@ impl CalendarProtocol for CustomRuntimeCalendar { _overflow: ArithmeticOverflow, _context: &mut dyn Any, ) -> TemporalResult { - todo!() + // TODO + Err(TemporalError::general("Not yet implemented.")) } fn date_until( @@ -217,7 +218,8 @@ impl CalendarProtocol for CustomRuntimeCalendar { _largest_unit: boa_temporal::options::TemporalUnit, _context: &mut dyn Any, ) -> TemporalResult { - todo!() + // TODO + Err(TemporalError::general("Not yet implemented.")) } fn era(&self, _: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult> { @@ -248,6 +250,12 @@ impl CalendarProtocol for CustomRuntimeCalendar { .call(&method, &[date_like], context) .map_err(|err| TemporalError::general(err.to_string()))?; + // Validate the return value. + // 3. If Type(result) is not Number, throw a TypeError exception. + // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. + // 5. If result < 1𝔽, throw a RangeError exception. + // 6. Return ℝ(result). + let Some(number) = val.as_number() else { return Err(TemporalError::r#type().with_message("year must return a number.")); }; @@ -285,6 +293,12 @@ impl CalendarProtocol for CustomRuntimeCalendar { .call(&method, &[date_like], context) .map_err(|err| TemporalError::general(err.to_string()))?; + // Validate the return value. + // 3. If Type(result) is not Number, throw a TypeError exception. + // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. + // 5. If result < 1𝔽, throw a RangeError exception. + // 6. Return ℝ(result). + let Some(number) = val.as_number() else { return Err(TemporalError::r#type().with_message("month must return a number.")); }; @@ -354,6 +368,12 @@ impl CalendarProtocol for CustomRuntimeCalendar { .call(&method, &[date_like], context) .map_err(|err| TemporalError::general(err.to_string()))?; + // Validate the return value. + // 3. If Type(result) is not Number, throw a TypeError exception. + // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. + // 5. If result < 1𝔽, throw a RangeError exception. + // 6. Return ℝ(result). + let Some(number) = val.as_number() else { return Err(TemporalError::r#type().with_message("day must return a number.")); }; @@ -395,6 +415,12 @@ impl CalendarProtocol for CustomRuntimeCalendar { .call(&method, &[date_like], context) .map_err(|err| TemporalError::general(err.to_string()))?; + // Validate the return value. + // 3. If Type(result) is not Number, throw a TypeError exception. + // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. + // 5. If result < 1𝔽, throw a RangeError exception. + // 6. Return ℝ(result). + let Some(number) = val.as_number() else { return Err(TemporalError::r#type().with_message("DayOfWeek must return a number.")); }; @@ -438,6 +464,12 @@ impl CalendarProtocol for CustomRuntimeCalendar { .call(&method, &[date_like], context) .map_err(|err| TemporalError::general(err.to_string()))?; + // Validate the return value. + // 3. If Type(result) is not Number, throw a TypeError exception. + // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. + // 5. If result < 1𝔽, throw a RangeError exception. + // 6. Return ℝ(result). + let Some(number) = val.as_number() else { return Err(TemporalError::r#type().with_message("dayOfYear must return a number.")); }; @@ -481,6 +513,12 @@ impl CalendarProtocol for CustomRuntimeCalendar { .call(&method, &[date_like], context) .map_err(|err| TemporalError::general(err.to_string()))?; + // Validate the return value. + // 3. If Type(result) is not Number, throw a TypeError exception. + // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. + // 5. If result < 1𝔽, throw a RangeError exception. + // 6. Return ℝ(result). + let Some(number) = val.as_number() else { return Err(TemporalError::r#type().with_message("weekOfYear must return a number.")); }; @@ -524,6 +562,11 @@ impl CalendarProtocol for CustomRuntimeCalendar { .call(&method, &[date_like], context) .map_err(|err| TemporalError::general(err.to_string()))?; + // Validate the return value. + // 3. If Type(result) is not Number, throw a TypeError exception. + // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. + // 5. Return ℝ(result). + let Some(number) = val.as_number() else { return Err(TemporalError::r#type().with_message("yearOfWeek must return a number.")); }; @@ -561,6 +604,12 @@ impl CalendarProtocol for CustomRuntimeCalendar { .call(&method, &[date_like], context) .map_err(|err| TemporalError::general(err.to_string()))?; + // Validate the return value. + // 3. If Type(result) is not Number, throw a TypeError exception. + // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. + // 5. If result < 1𝔽, throw a RangeError exception. + // 6. Return ℝ(result). + let Some(number) = val.as_number() else { return Err(TemporalError::r#type().with_message("daysInWeek must return a number.")); }; @@ -603,6 +652,12 @@ impl CalendarProtocol for CustomRuntimeCalendar { .call(&method, &[date_like], context) .map_err(|err| TemporalError::general(err.to_string()))?; + // Validate the return value. + // 3. If Type(result) is not Number, throw a TypeError exception. + // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. + // 5. If result < 1𝔽, throw a RangeError exception. + // 6. Return ℝ(result). + let Some(number) = val.as_number() else { return Err(TemporalError::r#type().with_message("daysInMonth must return a number.")); }; @@ -648,6 +703,12 @@ impl CalendarProtocol for CustomRuntimeCalendar { .call(&method, &[date_like], context) .map_err(|err| TemporalError::general(err.to_string()))?; + // Validate the return value. + // 3. If Type(result) is not Number, throw a TypeError exception. + // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. + // 5. If result < 1𝔽, throw a RangeError exception. + // 6. Return ℝ(result). + let Some(number) = val.as_number() else { return Err(TemporalError::r#type().with_message("daysInYear must return a number.")); }; @@ -691,6 +752,12 @@ impl CalendarProtocol for CustomRuntimeCalendar { .call(&method, &[date_like], context) .map_err(|err| TemporalError::general(err.to_string()))?; + // Validate the return value. + // 3. If Type(result) is not Number, throw a TypeError exception. + // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. + // 5. If result < 1𝔽, throw a RangeError exception. + // 6. Return ℝ(result). + let Some(number) = val.as_number() else { return Err(TemporalError::r#type().with_message("monthsInYear must return a number.")); }; diff --git a/boa_engine/src/builtins/temporal/duration/mod.rs b/boa_engine/src/builtins/temporal/duration/mod.rs index 343daac8f11..f0d40695159 100644 --- a/boa_engine/src/builtins/temporal/duration/mod.rs +++ b/boa_engine/src/builtins/temporal/duration/mod.rs @@ -946,7 +946,6 @@ pub(crate) fn create_temporal_duration( } /// Equivalent to 7.5.13 `ToTemporalPartialDurationRecord ( temporalDurationLike )` -/// pub(crate) fn to_temporal_partial_duration( duration_like: &JsValue, context: &mut Context, diff --git a/boa_engine/src/builtins/temporal/fields.rs b/boa_engine/src/builtins/temporal/fields.rs index 3dc3c4214a1..c842816c07d 100644 --- a/boa_engine/src/builtins/temporal/fields.rs +++ b/boa_engine/src/builtins/temporal/fields.rs @@ -105,12 +105,14 @@ pub(crate) fn prepare_temporal_fields( // iv. If value is not a String, throw a TypeError exception. FieldValue::String(primitive.to_string(context)?.to_std_string_escaped()) } - _ => unreachable!("todo need to implement conversion handling for tz."), + FieldConversion::None => { + unreachable!("todo need to implement conversion handling for tz.") + } }; // 3. Perform ! CreateDataPropertyOrThrow(result, property, value). result - .set_field_value(field.to_std_string_escaped(), converted_value) + .set_field_value(&field.to_std_string_escaped(), &converted_value) .expect("FieldConversion enforces the appropriate type"); // iii. Else if requiredFields is a List, then } else if !partial { @@ -126,7 +128,7 @@ pub(crate) fn prepare_temporal_fields( // 2. If property is in the Property column of Table 17, then // a. Set value to the corresponding Default value of the same row. // 3. Perform ! CreateDataPropertyOrThrow(result, property, value). - result.require_field(field.to_std_string_escaped()); + result.require_field(&field.to_std_string_escaped()); } // c. Else if duplicateBehaviour is throw, then } else if dup_option.to_std_string_escaped() == "throw" { diff --git a/boa_engine/src/builtins/temporal/plain_date/iso.rs b/boa_engine/src/builtins/temporal/plain_date/iso.rs deleted file mode 100644 index 895188f0ded..00000000000 --- a/boa_engine/src/builtins/temporal/plain_date/iso.rs +++ /dev/null @@ -1,237 +0,0 @@ -//! An `IsoDateRecord` that represents the `[[ISOYear]]`, `[[ISOMonth]]`, and `[[ISODay]]` internal slots. - -use crate::{ - builtins::temporal::{self, DateDuration }, - JsNativeError, JsResult, JsString, -}; - -use boa_temporal::options::ArithmeticOverflow; -use icu_calendar::{Date, Iso}; - -// TODO: Move ISODateRecord to a more generalized location. - -// TODO: Determine whether month/day should be u8 or i32. - -/// `IsoDateRecord` serves as an record for the `[[ISOYear]]`, `[[ISOMonth]]`, -/// and `[[ISODay]]` internal fields. -/// -/// These fields are used for the `Temporal.PlainDate` object, the -/// `Temporal.YearMonth` object, and the `Temporal.MonthDay` object. -#[derive(Debug, Clone, Copy, Default)] -pub(crate) struct IsoDateRecord { - year: i32, - month: i32, - day: i32, -} - -// TODO: determine whether the below is neccessary. -impl IsoDateRecord { - pub(crate) const fn year(&self) -> i32 { - self.year - } - pub(crate) const fn month(&self) -> i32 { - self.month - } - pub(crate) const fn day(&self) -> i32 { - self.day - } -} - -impl IsoDateRecord { - // TODO: look into using Date across the board...TBD. - /// Creates `[[ISOYear]]`, `[[isoMonth]]`, `[[isoDay]]` fields from `ICU4X`'s `Date` struct. - pub(crate) fn from_date_iso(date: Date) -> Self { - Self { - year: date.year().number, - month: date.month().ordinal as i32, - day: i32::from(date.days_in_month()), - } - } -} - -impl IsoDateRecord { - /// 3.5.2 `CreateISODateRecord` - pub(crate) const fn new(year: i32, month: i32, day: i32) -> Self { - Self { year, month, day } - } - - /// 3.5.6 `RegulateISODate` - pub(crate) fn from_unregulated( - year: i32, - month: i32, - day: i32, - overflow: ArithmeticOverflow, - ) -> JsResult { - 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)) - } - ArithmeticOverflow::Reject => { - let date = Self::new(year, month, day); - if !date.is_valid() { - return Err(JsNativeError::range() - .with_message("not a valid ISO date.") - .into()); - } - Ok(date) - } - } - } - - /// 12.2.35 `ISODateFromFields ( fields, overflow )` - /// - /// Note: fields.month must be resolved prior to using `from_temporal_fields` - pub(crate) fn from_temporal_fields( - fields: &TemporalFields, - overflow: ArithmeticOverflow, - ) -> JsResult { - Self::from_unregulated( - fields.year().expect("Cannot fail per spec"), - fields.month().expect("cannot fail after resolution"), - fields.day().expect("cannot fail per spec"), - overflow, - ) - } - - /// Create a Month-Day record from a `TemporalFields` object. - pub(crate) fn month_day_from_temporal_fields( - fields: &TemporalFields, - overflow: ArithmeticOverflow, - ) -> JsResult { - match fields.year() { - Some(year) => Self::from_unregulated( - year, - fields.month().expect("month must exist."), - fields.day().expect("cannot fail per spec"), - overflow, - ), - None => Self::from_unregulated( - 1972, - fields.month().expect("cannot fail per spec"), - fields.day().expect("cannot fail per spec."), - overflow, - ), - } - } - - /// Within `YearMonth` valid limits - pub(crate) const fn within_year_month_limits(&self) -> bool { - if self.year < -271_821 || self.year > 275_760 { - return false; - } - - if self.year == -271_821 && self.month < 4 { - return false; - } - - if self.year == 275_760 && self.month > 9 { - return true; - } - - true - } - - /// 3.5.5 `DifferenceISODate` - pub(crate) fn diff_iso_date( - &self, - o: &Self, - largest_unit: &JsString, - ) -> JsResult { - debug_assert!(self.is_valid()); - // TODO: Implement on `ICU4X`. - - Err(JsNativeError::range() - .with_message("not yet implemented.") - .into()) - } - - /// 3.5.7 `IsValidISODate` - pub(crate) fn is_valid(&self) -> bool { - if self.month < 1 || self.month > 12 { - return false; - } - - let days_in_month = temporal::calendar::utils::iso_days_in_month(self.year, self.month); - - if self.day < 1 || self.day > days_in_month { - return false; - } - true - } - - /// 13.2 `IsoDateToEpochDays` - pub(crate) fn as_epoch_days(&self) -> i32 { - // 1. Let resolvedYear be year + floor(month / 12). - let resolved_year = self.year + (f64::from(self.month) / 12_f64).floor() as i32; - // 2. Let resolvedMonth be month modulo 12. - let resolved_month = self.month % 12; - - // 3. Find a time t such that EpochTimeToEpochYear(t) is resolvedYear, EpochTimeToMonthInYear(t) is resolvedMonth, and EpochTimeToDate(t) is 1. - let year_t = temporal::date_equations::epoch_time_for_year(resolved_year); - let month_t = temporal::date_equations::epoch_time_for_month_given_year( - resolved_month, - resolved_year, - ); - - // 4. Return EpochTimeToDayNumber(t) + date - 1. - temporal::date_equations::epoch_time_to_day_number(year_t + month_t) + self.day - 1 - } - - // NOTE: Implementing as mut self so balance is applied to self, but TBD. - /// 3.5.8 `BalanceIsoDate` - pub(crate) fn balance(&mut self) { - let epoch_days = self.as_epoch_days(); - let ms = temporal::epoch_days_to_epoch_ms(epoch_days, 0); - - // Balance current values - self.year = temporal::date_equations::epoch_time_to_epoch_year(ms); - self.month = temporal::date_equations::epoch_time_to_month_in_year(ms); - self.day = temporal::date_equations::epoch_time_to_date(ms); - } - - // NOTE: Used in AddISODate only, so could possibly be deleted in the future. - /// 9.5.4 `BalanceISOYearMonth ( year, month )` - pub(crate) fn balance_year_month(&mut self) { - self.year += (self.month - 1) / 12; - self.month = ((self.month - 1) % 12) + 1; - } - - /// 3.5.11 `AddISODate ( year, month, day, years, months, weeks, days, overflow )` - pub(crate) fn add_iso_date( - &self, - 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 + 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(); - - // 4. Let intermediate be ? RegulateISODate(intermediate.[[Year]], intermediate.[[Month]], day, overflow). - let mut new_date = Self::from_unregulated( - intermediate.year(), - intermediate.month(), - self.day, - overflow, - )?; - - // 5. Set days to days + 7 × weeks. - // 6. Let d be intermediate.[[Day]] + days. - 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). - new_date.balance(); - - Ok(new_date) - } -} diff --git a/boa_engine/src/builtins/temporal/plain_date_time/iso.rs b/boa_engine/src/builtins/temporal/plain_date_time/iso.rs deleted file mode 100644 index 50b2805a37a..00000000000 --- a/boa_engine/src/builtins/temporal/plain_date_time/iso.rs +++ /dev/null @@ -1,100 +0,0 @@ -use crate::{ - builtins::{ - date::utils, - temporal::{self, plain_date::iso::IsoDateRecord}, - }, - JsBigInt, -}; - -#[derive(Default, Debug, Clone)] -pub(crate) struct IsoDateTimeRecord { - iso_date: IsoDateRecord, - hour: i32, - minute: i32, - second: i32, - millisecond: i32, - microsecond: i32, - nanosecond: i32, -} - -impl IsoDateTimeRecord { - pub(crate) const fn iso_date(&self) -> IsoDateRecord { - self.iso_date - } -} - -// ==== `IsoDateTimeRecord` methods ==== - -impl IsoDateTimeRecord { - pub(crate) const fn with_date(mut self, year: i32, month: i32, day: i32) -> Self { - let iso_date = IsoDateRecord::new(year, month, day); - self.iso_date = iso_date; - self - } - - pub(crate) const fn with_time( - mut self, - hour: i32, - minute: i32, - second: i32, - ms: i32, - mis: i32, - ns: i32, - ) -> Self { - self.hour = hour; - self.minute = minute; - self.second = second; - self.millisecond = ms; - self.microsecond = mis; - self.nanosecond = ns; - self - } - - /// 5.5.1 `ISODateTimeWithinLimits ( year, month, day, hour, minute, second, millisecond, microsecond, nanosecond )` - pub(crate) fn is_valid(&self) -> bool { - self.iso_date.is_valid(); - let ns = self.get_utc_epoch_ns(None).to_f64(); - - if ns <= temporal::ns_min_instant().to_f64() - (temporal::NS_PER_DAY as f64) - || ns >= temporal::ns_max_instant().to_f64() + (temporal::NS_PER_DAY as f64) - { - return false; - } - true - } - - /// 14.8.1 `GetUTCEpochNanoseconds` - pub(crate) fn get_utc_epoch_ns(&self, offset_ns: Option) -> JsBigInt { - let day = utils::make_day( - i64::from(self.iso_date.year()), - i64::from(self.iso_date.month()), - i64::from(self.iso_date.day()), - ) - .unwrap_or_default(); - let time = utils::make_time( - i64::from(self.hour), - i64::from(self.minute), - i64::from(self.second), - i64::from(self.millisecond), - ) - .unwrap_or_default(); - - let ms = utils::make_date(day, time).unwrap_or_default(); - - let epoch_ns = match offset_ns { - Some(offset) if offset != 0 => { - let ns = (ms * 1_000_000_i64) - + (i64::from(self.microsecond) * 1_000_i64) - + i64::from(self.nanosecond); - ns - offset - } - _ => { - (ms * 1_000_000_i64) - + (i64::from(self.microsecond) * 1_000_i64) - + i64::from(self.nanosecond) - } - }; - - JsBigInt::from(epoch_ns) - } -} diff --git a/boa_temporal/src/calendar.rs b/boa_temporal/src/calendar.rs index bc6353cf355..29c4facc74e 100644 --- a/boa_temporal/src/calendar.rs +++ b/boa_temporal/src/calendar.rs @@ -101,6 +101,7 @@ impl FromStr for AvailableCalendars { impl AvailableCalendars { /// Returns the `CalendarProtocol` for the `AvailableCalendar` + #[must_use] pub fn to_protocol(&self) -> Box { match self { Self::Iso => Box::new(IsoCalendar), @@ -123,6 +124,8 @@ pub enum CalendarDateLike { impl CalendarDateLike { /// Retrieves the internal `IsoDate` field. + #[inline] + #[must_use] pub fn as_iso_date(&self) -> IsoDate { match self { CalendarDateLike::Date(d) => d.iso_date(), @@ -150,7 +153,8 @@ where } } -// TODO: Implement `fields` and `mergeFields` +// TODO: Split further into `CalendarProtocol` and `BuiltinCalendar` to better handle +// fields and mergeFields. /// A trait for implementing a Builtin Calendar's Calendar Protocol in Rust. pub trait CalendarProtocol: CalendarProtocolClone { /// Creates a `Temporal.PlainDate` object from provided fields. @@ -323,6 +327,7 @@ impl Default for CalendarSlot { } } +// TODO: Handle `CalendarFields` and `CalendarMergeFields` impl CalendarSlot { /// `CalendarDateAdd` /// diff --git a/boa_temporal/src/date.rs b/boa_temporal/src/date.rs index 335daa1a8d0..0434ee4a023 100644 --- a/boa_temporal/src/date.rs +++ b/boa_temporal/src/date.rs @@ -21,12 +21,13 @@ pub struct TemporalDate { impl TemporalDate { /// Create a new `TemporalDate` with the date values and calendar slot. + #[inline] + #[must_use] pub(crate) fn new_unchecked(iso: IsoDate, calendar: CalendarSlot) -> Self { Self { iso, calendar } } #[inline] - #[must_use] /// `DifferenceDate` pub(crate) fn diff_date( &self, @@ -56,7 +57,6 @@ impl TemporalDate { } #[inline] - #[must_use] /// Internal `AddDate` function pub(crate) fn add_date( &self, @@ -90,7 +90,6 @@ impl TemporalDate { } #[inline] - #[must_use] /// Returns a new moved date and the days associated with that adjustment pub(crate) fn move_relative_date( &self, @@ -194,7 +193,6 @@ impl TemporalDate { /// /// Temporal Equivalent: 3.5.13 `AddDate ( calendar, plainDate, duration [ , options [ , dateAdd ] ] )` #[inline] - #[must_use] #[cfg(feature = "context")] pub fn add_to_date( &self, @@ -209,7 +207,6 @@ impl TemporalDate { /// /// Temporal Equivalent: 3.5.13 `AddDate ( calendar, plainDate, duration [ , options [ , dateAdd ] ] )` #[inline] - #[must_use] #[cfg(not(feature = "context"))] pub fn add_to_date( &self, @@ -223,7 +220,6 @@ impl TemporalDate { /// /// Temporal Equivalent: 3.5.6 `DifferenceDate ( calendar, one, two, options )` #[inline] - #[must_use] #[cfg(feature = "context")] pub fn difference_date( &self, @@ -238,7 +234,6 @@ impl TemporalDate { /// /// Temporal Equivalent: 3.5.6 `DifferenceDate ( calendar, one, two, options )` #[inline] - #[must_use] #[cfg(not(feature = "context"))] pub fn difference_date( &self, diff --git a/boa_temporal/src/datetime.rs b/boa_temporal/src/datetime.rs index df066c4d3f7..0fffd463669 100644 --- a/boa_temporal/src/datetime.rs +++ b/boa_temporal/src/datetime.rs @@ -18,6 +18,8 @@ pub struct TemporalDateTime { impl TemporalDateTime { /// Creates a new unchecked `TemporalDateTime`. + #[inline] + #[must_use] pub(crate) fn new_unchecked(date: IsoDate, time: IsoTime, calendar: CalendarSlot) -> Self { Self { iso: IsoDateTime::new_unchecked(date, time), @@ -26,6 +28,7 @@ impl TemporalDateTime { } #[inline] + #[must_use] /// Utility function for validating `IsoDate`s fn validate_iso(iso: IsoDate) -> bool { IsoDateTime::new_unchecked(iso, IsoTime::noon()).is_within_limits() @@ -36,6 +39,7 @@ impl TemporalDateTime { impl TemporalDateTime { /// Creates a new validated `TemporalDateTime`. + #[inline] #[allow(clippy::too_many_arguments)] pub fn new( year: i32, @@ -62,23 +66,29 @@ impl TemporalDateTime { Ok(Self::new_unchecked(iso_date, iso_time, calendar)) } - #[inline] /// Validates whether ISO date slots are within iso limits at noon. + #[inline] pub fn validate(target: &T) -> bool { Self::validate_iso(target.iso_date()) } /// Returns the inner `IsoDate` value. + #[inline] + #[must_use] pub fn iso_date(&self) -> IsoDate { self.iso.iso_date() } /// Returns the inner `IsoTime` value. + #[inline] + #[must_use] pub fn iso_time(&self) -> IsoTime { self.iso.iso_time() } /// Returns the Calendar value. + #[inline] + #[must_use] pub fn calendar(&self) -> &CalendarSlot { &self.calendar } diff --git a/boa_temporal/src/duration.rs b/boa_temporal/src/duration.rs index 201314acc23..949560bae9a 100644 --- a/boa_temporal/src/duration.rs +++ b/boa_temporal/src/duration.rs @@ -296,7 +296,6 @@ impl Duration { impl Duration { /// Creates a new validated `Duration`. - #[must_use] #[allow(clippy::too_many_arguments)] pub fn new( years: f64, @@ -1673,7 +1672,6 @@ impl Duration { /// Abstract Operation 7.5.17 `BalanceTimeDuration ( days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, largestUnit )` #[inline] - #[must_use] pub fn balance_time_duration( &self, largest_unit: TemporalUnit, diff --git a/boa_temporal/src/fields.rs b/boa_temporal/src/fields.rs index 2ad53a657c7..608b1896e77 100644 --- a/boa_temporal/src/fields.rs +++ b/boa_temporal/src/fields.rs @@ -40,6 +40,7 @@ bitflags! { const ERA_YEAR = 0b0001_0000_0000_0000; /// Represents an active `timeZone` field const TIME_ZONE = 0b0010_0000_0000_0000; + // NOTE(nekevss): Two bits preserved if needed. } } @@ -166,8 +167,8 @@ impl TemporalFields { impl TemporalFields { /// Flags a field as being required. #[inline] - pub fn require_field(&mut self, field: String) { - match field.as_str() { + pub fn require_field(&mut self, field: &str) { + match field { "year" => self.bit_map.set(FieldMap::YEAR, true), "month" => self.bit_map.set(FieldMap::MONTH, true), "monthCode" => self.bit_map.set(FieldMap::MONTH_CODE, true), @@ -191,12 +192,8 @@ impl TemporalFields { /// /// This method will not run any `JsValue` conversion. `FieldValue` is /// expected to contain a preconverted value. - pub fn set_field_value( - &mut self, - field: String, // TODO: Switch to an options Enum. - value: FieldValue, - ) -> TemporalResult<()> { - match field.as_str() { + pub fn set_field_value(&mut self, field: &str, value: &FieldValue) -> TemporalResult<()> { + match field { "year" => self.set_year(value)?, "month" => self.set_month(value)?, "monthCode" => self.set_month_code(value)?, @@ -218,27 +215,27 @@ impl TemporalFields { } #[inline] - fn set_year(&mut self, value: FieldValue) -> TemporalResult<()> { + fn set_year(&mut self, value: &FieldValue) -> TemporalResult<()> { let FieldValue::Integer(y) = value else { return Err(TemporalError::r#type().with_message("Year must be an integer.")); }; - self.year = Some(y); + self.year = Some(*y); self.bit_map.set(FieldMap::YEAR, true); Ok(()) } #[inline] - fn set_month(&mut self, value: FieldValue) -> TemporalResult<()> { + fn set_month(&mut self, value: &FieldValue) -> TemporalResult<()> { let FieldValue::Integer(mo) = value else { return Err(TemporalError::r#type().with_message("Month must be an integer.")); }; - self.year = Some(mo); + self.year = Some(*mo); self.bit_map.set(FieldMap::MONTH, true); Ok(()) } #[inline] - fn set_month_code(&mut self, value: FieldValue) -> TemporalResult<()> { + fn set_month_code(&mut self, value: &FieldValue) -> TemporalResult<()> { let FieldValue::String(mc) = value else { return Err(TemporalError::r#type().with_message("monthCode must be string.")); }; @@ -249,88 +246,88 @@ impl TemporalFields { } #[inline] - fn set_day(&mut self, value: FieldValue) -> TemporalResult<()> { + fn set_day(&mut self, value: &FieldValue) -> TemporalResult<()> { let FieldValue::Integer(d) = value else { return Err(TemporalError::r#type().with_message("day must be an integer.")); }; - self.day = Some(d); + self.day = Some(*d); self.bit_map.set(FieldMap::DAY, true); Ok(()) } #[inline] - fn set_hour(&mut self, value: FieldValue) -> TemporalResult<()> { + fn set_hour(&mut self, value: &FieldValue) -> TemporalResult<()> { let FieldValue::Integer(h) = value else { return Err(TemporalError::r#type().with_message("hour must be an integer.")); }; - self.hour = h; + self.hour = *h; self.bit_map.set(FieldMap::HOUR, true); Ok(()) } #[inline] - fn set_minute(&mut self, value: FieldValue) -> TemporalResult<()> { + fn set_minute(&mut self, value: &FieldValue) -> TemporalResult<()> { let FieldValue::Integer(min) = value else { return Err(TemporalError::r#type().with_message("minute must be an integer.")); }; - self.minute = min; + self.minute = *min; self.bit_map.set(FieldMap::MINUTE, true); Ok(()) } #[inline] - fn set_second(&mut self, value: FieldValue) -> TemporalResult<()> { + fn set_second(&mut self, value: &FieldValue) -> TemporalResult<()> { let FieldValue::Integer(sec) = value else { return Err(TemporalError::r#type().with_message("Second must be an integer.")); }; - self.second = sec; + self.second = *sec; self.bit_map.set(FieldMap::SECOND, true); Ok(()) } #[inline] - fn set_milli(&mut self, value: FieldValue) -> TemporalResult<()> { + fn set_milli(&mut self, value: &FieldValue) -> TemporalResult<()> { let FieldValue::Integer(milli) = value else { return Err(TemporalError::r#type().with_message("Second must be an integer.")); }; - self.millisecond = milli; + self.millisecond = *milli; self.bit_map.set(FieldMap::MILLISECOND, true); Ok(()) } #[inline] - fn set_micro(&mut self, value: FieldValue) -> TemporalResult<()> { + fn set_micro(&mut self, value: &FieldValue) -> TemporalResult<()> { let FieldValue::Integer(micro) = value else { return Err(TemporalError::r#type().with_message("microsecond must be an integer.")); }; - self.microsecond = micro; + self.microsecond = *micro; self.bit_map.set(FieldMap::MICROSECOND, true); Ok(()) } #[inline] - fn set_nano(&mut self, value: FieldValue) -> TemporalResult<()> { + fn set_nano(&mut self, value: &FieldValue) -> TemporalResult<()> { let FieldValue::Integer(nano) = value else { return Err(TemporalError::r#type().with_message("nanosecond must be an integer.")); }; - self.nanosecond = nano; + self.nanosecond = *nano; self.bit_map.set(FieldMap::NANOSECOND, true); Ok(()) } #[inline] - fn set_offset(&mut self, value: FieldValue) -> TemporalResult<()> { + fn set_offset(&mut self, value: &FieldValue) -> TemporalResult<()> { let FieldValue::String(offset) = value else { return Err(TemporalError::r#type().with_message("offset must be string.")); }; - self.offset = Some(offset); + self.offset = Some(offset.clone()); self.bit_map.set(FieldMap::OFFSET, true); Ok(()) } #[inline] - fn set_era(&mut self, value: FieldValue) -> TemporalResult<()> { + fn set_era(&mut self, value: &FieldValue) -> TemporalResult<()> { let FieldValue::String(era) = value else { return Err(TemporalError::r#type().with_message("era must be string.")); }; @@ -342,21 +339,21 @@ impl TemporalFields { } #[inline] - fn set_era_year(&mut self, value: FieldValue) -> TemporalResult<()> { + fn set_era_year(&mut self, value: &FieldValue) -> TemporalResult<()> { let FieldValue::Integer(era_year) = value else { return Err(TemporalError::r#type().with_message("eraYear must be an integer.")); }; - self.era_year = Some(era_year); + self.era_year = Some(*era_year); self.bit_map.set(FieldMap::ERA_YEAR, true); Ok(()) } #[inline] - fn set_time_zone(&mut self, value: FieldValue) -> TemporalResult<()> { + fn set_time_zone(&mut self, value: &FieldValue) -> TemporalResult<()> { let FieldValue::String(tz) = value else { return Err(TemporalError::r#type().with_message("tz must be string.")); }; - self.time_zone = Some(tz); + self.time_zone = Some(tz.clone()); self.bit_map.set(FieldMap::TIME_ZONE, true); Ok(()) } @@ -452,7 +449,7 @@ impl TemporalFields { .as_ref() .expect("monthCode must exist at this point."); - let month_code_integer = month_code_to_integer(unresolved_month_code)?; + let month_code_integer = month_code_to_integer(*unresolved_month_code)?; let new_month = match self.month { Some(month) if month != month_code_integer => { @@ -469,7 +466,7 @@ impl TemporalFields { } } -fn month_code_to_integer(mc: &TinyStr4) -> TemporalResult { +fn month_code_to_integer(mc: TinyStr4) -> TemporalResult { match mc.as_str() { "M01" => Ok(1), "M02" => Ok(2), @@ -487,268 +484,3 @@ fn month_code_to_integer(mc: &TinyStr4) -> TemporalResult { _ => Err(TemporalError::range().with_message("monthCode is not within the valid values.")), } } - -/* -impl TemporalFields { - // TODO: Shift to JsString or utf16 over String. - /// A method for creating a Native representation for `TemporalFields` from - /// a `JsObject`. - /// - /// 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>, - partial: bool, - dup_behaviour: Option, - context: &mut Context<'_>, - ) -> TemporalResult { - // 1. If duplicateBehaviour is not present, set duplicateBehaviour to throw. - let dup_option = dup_behaviour.unwrap_or_else(|| js_string!("throw")); - - // 2. Let result be OrdinaryObjectCreate(null). - let mut result = Self::default(); - - // 3. Let any be false. - let mut any = false; - // 4. If extraFieldDescriptors is present, then - if let Some(extra_fields) = extended_fields { - for (field_name, required) in extra_fields { - // a. For each Calendar Field Descriptor Record desc of extraFieldDescriptors, do - // i. Assert: fieldNames does not contain desc.[[Property]]. - // ii. Append desc.[[Property]] to fieldNames. - field_names.push(field_name.clone()); - - // iii. If desc.[[Required]] is true and requiredFields is a List, then - if required && !partial { - // 1. Append desc.[[Property]] to requiredFields. - required_fields.push(field_name); - } - } - } - - // 5. Let sortedFieldNames be SortStringListByCodeUnit(fieldNames). - // 6. Let previousProperty be undefined. - let mut dups_map = FxHashSet::default(); - - // 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.to_std_string_escaped().as_str() == "constructor" - || field.to_std_string_escaped().as_str() == "__proto__" - { - // i. Throw a RangeError exception. - return Err(TemporalError::range() - .with_message("constructor or proto is out of field range.") - .into()); - } - - let new_value = dups_map.insert(field); - - // 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(field.clone()), context)?; - // ii. If value is not undefined, then - if !value.is_undefined() { - // 1. Set any to true. - any = true; - - // 2. If property is in the Property column of Table 17 and there is a Conversion value in the same row, then - // a. Let Conversion be the Conversion value of the same row. - // b. If Conversion is ToIntegerWithTruncation, then - // i. Set value to ? ToIntegerWithTruncation(value). - // ii. Set value to 𝔽(value). - // c. Else if Conversion is ToPositiveIntegerWithTruncation, then - // i. Set value to ? ToPositiveIntegerWithTruncation(value). - // ii. Set value to 𝔽(value). - // d. Else, - // i. Assert: Conversion is ToPrimitiveAndRequireString. - // ii. NOTE: Non-primitive values are supported here for consistency with other fields, but such values must coerce to Strings. - // iii. Set value to ? ToPrimitive(value, string). - // iv. If value is not a String, throw a TypeError exception. - // 3. Perform ! CreateDataPropertyOrThrow(result, property, value). - result.set_field_value(field, &value, context)?; - // iii. Else if requiredFields is a List, then - } else if !partial { - // 1. If requiredFields contains property, then - if required_fields.contains(field) { - // a. Throw a TypeError exception. - return Err(TemporalError::typ() - .with_message("A required TemporalField was not provided.") - .into()); - } - - // NOTE: Values set to a default on init. - // 2. If property is in the Property column of Table 17, then - // a. Set value to the corresponding Default value of the same row. - // 3. Perform ! CreateDataPropertyOrThrow(result, property, value). - } - // c. Else if duplicateBehaviour is throw, then - } else if dup_option.to_std_string_escaped() == "throw" { - // i. Throw a RangeError exception. - return Err(TemporalError::range() - .with_message("Cannot have a duplicate field") - .into()); - } - // d. Set previousProperty to property. - } - - // 8. If requiredFields is partial and any is false, then - if partial && !any { - // a. Throw a TypeError exception. - return Err(TemporalError::range() - .with_message("requiredFields cannot be partial when any is false") - .into()); - } - - // 9. Return result. - Ok(result) - } - - /// Convert a `TemporalFields` struct into a `JsObject`. - pub(crate) fn as_object(&self, context: &mut Context<'_>) -> TemporalResult { - let obj = JsObject::with_null_proto(); - - for bit in self.bit_map.iter() { - match bit { - FieldMap::YEAR => { - obj.create_data_property_or_throw( - js_string!("year"), - self.year.map_or(JsValue::undefined(), JsValue::from), - context, - )?; - } - FieldMap::MONTH => { - obj.create_data_property_or_throw( - js_string!("month"), - self.month.map_or(JsValue::undefined(), JsValue::from), - context, - )?; - } - FieldMap::MONTH_CODE => { - obj.create_data_property_or_throw( - js_string!("monthCode"), - self.month_code - .as_ref() - .map_or(JsValue::undefined(), |f| f.clone().into()), - context, - )?; - } - FieldMap::DAY => { - obj.create_data_property( - js_string!("day"), - self.day().map_or(JsValue::undefined(), JsValue::from), - context, - )?; - } - FieldMap::HOUR => { - obj.create_data_property(js_string!("hour"), self.hour, context)?; - } - FieldMap::MINUTE => { - obj.create_data_property(js_string!("minute"), self.minute, context)?; - } - FieldMap::SECOND => { - obj.create_data_property_or_throw(js_string!("second"), self.second, context)?; - } - FieldMap::MILLISECOND => { - obj.create_data_property_or_throw( - js_string!("millisecond"), - self.millisecond, - context, - )?; - } - FieldMap::MICROSECOND => { - obj.create_data_property_or_throw( - js_string!("microsecond"), - self.microsecond, - context, - )?; - } - FieldMap::NANOSECOND => { - obj.create_data_property_or_throw( - js_string!("nanosecond"), - self.nanosecond, - context, - )?; - } - FieldMap::OFFSET => { - obj.create_data_property_or_throw( - js_string!("offset"), - self.offset - .as_ref() - .map_or(JsValue::undefined(), |s| s.clone().into()), - context, - )?; - } - FieldMap::ERA => { - obj.create_data_property_or_throw( - js_string!("era"), - self.era - .as_ref() - .map_or(JsValue::undefined(), |s| s.clone().into()), - context, - )?; - } - FieldMap::ERA_YEAR => { - obj.create_data_property_or_throw( - js_string!("eraYear"), - self.era_year.map_or(JsValue::undefined(), JsValue::from), - context, - )?; - } - FieldMap::TIME_ZONE => { - obj.create_data_property_or_throw( - js_string!("timeZone"), - self.time_zone - .as_ref() - .map_or(JsValue::undefined(), |s| s.clone().into()), - context, - )?; - } - _ => unreachable!(), - } - } - - Ok(obj) - } - - // Note placeholder until overflow is implemented on `ICU4x`'s Date. - /// A function to regulate the current `TemporalFields` according to the overflow value - pub(crate) fn regulate(&mut self, overflow: ArithmeticOverflow) -> TemporalResult<()> { - if let (Some(year), Some(month), Some(day)) = (self.year(), self.month(), self.day()) { - match overflow { - ArithmeticOverflow::Constrain => { - let m = month.clamp(1, 12); - let days_in_month = crate::utils::iso_days_in_month(year, month); - let d = day.clamp(1, days_in_month); - - self.month = Some(m); - self.day = Some(d); - } - ArithmeticOverflow::Reject => { - return Err(TemporalError::range() - .with_message("TemporalFields is out of a valid range.") - ) - } - } - } - Ok(()) - } - - pub(crate) fn regulate_year_month(&mut self, overflow: ArithmeticOverflow) { - match self.month { - Some(month) if overflow == ArithmeticOverflow::Constrain => { - let m = month.clamp(1, 12); - self.month = Some(m); - } - _ => {} - } - } - -} - - - -*/ diff --git a/boa_temporal/src/iso.rs b/boa_temporal/src/iso.rs index 559af8f15e8..113730b3400 100644 --- a/boa_temporal/src/iso.rs +++ b/boa_temporal/src/iso.rs @@ -38,8 +38,8 @@ impl IsoDateTime { return false; }; - let max = crate::ns_max_instant() + crate::NS_PER_DAY; - let min = crate::ns_min_instant() - crate::NS_PER_DAY; + let max = BigInt::from(crate::NS_MAX_INSTANT + i128::from(crate::NS_PER_DAY)); + let min = BigInt::from(crate::NS_MIN_INSTANT - i128::from(crate::NS_PER_DAY)); min < ns && max > ns } diff --git a/boa_temporal/src/lib.rs b/boa_temporal/src/lib.rs index 494c6007d6d..0015b1ad999 100644 --- a/boa_temporal/src/lib.rs +++ b/boa_temporal/src/lib.rs @@ -94,7 +94,6 @@ pub(crate) mod utils; pub mod year_month; pub mod zoneddatetime; -use num_bigint::BigInt; // TODO: evaluate positives and negatives of using tinystr. // Re-exporting tinystr as a convenience, as it is currently tied into the API. pub use tinystr::{TinyAsciiStr, TinyStr16, TinyStr4, TinyStr8}; @@ -104,16 +103,12 @@ pub use error::TemporalError; /// The `Temporal` result type pub type TemporalResult = Result; -// Relavant numeric constants +// Relevant numeric constants /// Nanoseconds per day constant: 8.64e+13 pub(crate) const NS_PER_DAY: i64 = 86_400_000_000_000; /// Milliseconds per day constant: 8.64e+7 pub(crate) const MS_PER_DAY: i32 = 24 * 60 * 60 * 1000; - -pub(crate) fn ns_max_instant() -> BigInt { - BigInt::from(i128::from(NS_PER_DAY) * 100_000_000i128) -} - -pub(crate) fn ns_min_instant() -> BigInt { - ns_max_instant() * -1 -} +/// Max Instant nanosecond constant +pub(crate) const NS_MAX_INSTANT: i128 = NS_PER_DAY as i128 * 100_000_000i128; +/// Min Instant nanosecond constant +pub(crate) const NS_MIN_INSTANT: i128 = -NS_MAX_INSTANT; diff --git a/boa_temporal/src/month_day.rs b/boa_temporal/src/month_day.rs index 50a9bcaf21b..69cf7aa0c67 100644 --- a/boa_temporal/src/month_day.rs +++ b/boa_temporal/src/month_day.rs @@ -15,14 +15,14 @@ pub struct TemporalMonthDay { } impl TemporalMonthDay { - #[inline] /// Creates a new unchecked `TemporalMonthDay` + #[inline] + #[must_use] pub(crate) fn new_unchecked(iso: IsoDate, calendar: CalendarSlot) -> Self { Self { iso, calendar } } #[inline] - #[must_use] /// Creates a new valid `TemporalMonthDay`. pub fn new( month: i32, diff --git a/boa_temporal/src/utils.rs b/boa_temporal/src/utils.rs index 627409f4193..4660ba49dd1 100644 --- a/boa_temporal/src/utils.rs +++ b/boa_temporal/src/utils.rs @@ -209,7 +209,7 @@ pub(crate) fn epoch_time_for_month_given_year(m: i32, y: i32) -> f64 { _ => unreachable!(), }; - (MS_PER_DAY as f64).mul(f64::from(days)) + f64::from(MS_PER_DAY).mul(f64::from(days)) } pub(crate) fn epoch_time_to_date(t: f64) -> u8 { diff --git a/boa_temporal/src/year_month.rs b/boa_temporal/src/year_month.rs index 656b98f304a..517bb97feec 100644 --- a/boa_temporal/src/year_month.rs +++ b/boa_temporal/src/year_month.rs @@ -15,15 +15,15 @@ pub struct TemporalYearMonth { } impl TemporalYearMonth { - #[inline] /// Creates an unvalidated `TemporalYearMonth`. + #[inline] + #[must_use] pub(crate) fn new_unchecked(iso: IsoDate, calendar: CalendarSlot) -> Self { Self { iso, calendar } } - #[inline] - #[must_use] /// Creates a new valid `TemporalYearMonth`. + #[inline] pub fn new( year: i32, month: i32, From 08d8e0222f4a43852a502e388b4f01bd82329d59 Mon Sep 17 00:00:00 2001 From: nekevss Date: Mon, 20 Nov 2023 19:55:56 -0500 Subject: [PATCH 6/9] Post-rebase update and a couple changes --- .../src/builtins/temporal/calendar/mod.rs | 23 +++----- .../src/builtins/temporal/calendar/tests.rs | 2 +- boa_temporal/Cargo.toml | 9 ++-- boa_temporal/src/calendar.rs | 2 +- boa_temporal/src/duration.rs | 2 +- boa_temporal/src/fields.rs | 9 ++-- boa_temporal/src/lib.rs | 53 ------------------- boa_temporal/src/time.rs | 3 ++ boa_temporal/src/utils.rs | 4 +- boa_temporal/src/zoneddatetime.rs | 3 +- 10 files changed, 26 insertions(+), 84 deletions(-) diff --git a/boa_engine/src/builtins/temporal/calendar/mod.rs b/boa_engine/src/builtins/temporal/calendar/mod.rs index ddd2038e32f..c9de740c085 100644 --- a/boa_engine/src/builtins/temporal/calendar/mod.rs +++ b/boa_engine/src/builtins/temporal/calendar/mod.rs @@ -1139,23 +1139,12 @@ pub(crate) fn to_temporal_calendar_slot_value( let calendar = date.inner.calendar().clone(); return Ok(calendar); } - } else if calendar_like.is_plain_date_time() { - // TODO - return Err(JsNativeError::range() - .with_message("Not yet implemented.") - .into()); - } else if calendar_like.is_plain_year_month() { - // TODO - return Err(JsNativeError::range() - .with_message("Not yet implemented.") - .into()); - } else if calendar_like.is_plain_month_day() { - // TODO - return Err(JsNativeError::range() - .with_message("Not yet implemented.") - .into()); - } else if calendar_like.is_zoned_date_time() { - // TODO + } else if calendar_like.is_plain_date_time() + || calendar_like.is_plain_year_month() + || calendar_like.is_plain_month_day() + || calendar_like.is_zoned_date_time() + { + // TODO(nekevss): Separate out and reimplement the handling of different branches. return Err(JsNativeError::range() .with_message("Not yet implemented.") .into()); diff --git a/boa_engine/src/builtins/temporal/calendar/tests.rs b/boa_engine/src/builtins/temporal/calendar/tests.rs index 07da841ea3e..7618d924f20 100644 --- a/boa_engine/src/builtins/temporal/calendar/tests.rs +++ b/boa_engine/src/builtins/temporal/calendar/tests.rs @@ -57,5 +57,5 @@ fn run_custom_calendar() { TestAction::assert_eq("cal.daysInMonth(date)", 14), TestAction::assert_eq("cal.daysInWeek(date)", 6), TestAction::assert_eq("cal.daysInYear(date)", 360), - ]) + ]); } diff --git a/boa_temporal/Cargo.toml b/boa_temporal/Cargo.toml index c9894970afd..058e64d3490 100644 --- a/boa_temporal/Cargo.toml +++ b/boa_temporal/Cargo.toml @@ -11,15 +11,16 @@ license.workspace = true repository.workspace = true rust-version.workspace = true - [dependencies] -icu_calendar = { version = "~1.3.2", default-features = false } tinystr = "0.7.4" -bitflags.workspace = true +icu_calendar = { workspace = true, default-features = false } rustc-hash = { workspace = true, features = ["std"] } num-bigint = { workspace = true, features = ["serde"] } +bitflags.workspace = true num-traits.workspace = true - [features] context = [] + +[lints] +workspace = true diff --git a/boa_temporal/src/calendar.rs b/boa_temporal/src/calendar.rs index 29c4facc74e..02286a4a88d 100644 --- a/boa_temporal/src/calendar.rs +++ b/boa_temporal/src/calendar.rs @@ -297,7 +297,7 @@ impl core::fmt::Debug for dyn CalendarProtocol { } } -/// The [[Calendar]] field slot of a Temporal Object. +/// The `[[Calendar]]` field slot of a Temporal Object. #[derive(Debug)] pub enum CalendarSlot { /// The calendar identifier string. diff --git a/boa_temporal/src/duration.rs b/boa_temporal/src/duration.rs index 949560bae9a..fb19fce61d3 100644 --- a/boa_temporal/src/duration.rs +++ b/boa_temporal/src/duration.rs @@ -1173,7 +1173,7 @@ impl Duration { // 2. If zonedRelativeTo is not present, set zonedRelativeTo to undefined. let zoned_relative_to = relative_targets.1; // 3. If precalculatedPlainDateTime is not present, set precalculatedPlainDateTime to undefined. - let _precalc_pdt = relative_targets.2; + let _ = relative_targets.2; let (frac_days, frac_secs) = match unit { // 4. If unit is "year", "month", or "week", and plainRelativeTo is undefined, then diff --git a/boa_temporal/src/fields.rs b/boa_temporal/src/fields.rs index 608b1896e77..98ae9d0bad8 100644 --- a/boa_temporal/src/fields.rs +++ b/boa_temporal/src/fields.rs @@ -46,13 +46,14 @@ bitflags! { /// The post conversion field value. #[derive(Debug)] +#[allow(variant_size_differences)] pub enum FieldValue { /// Designates the values as an integer. Integer(i32), - /// Designates the value as a string. - String(String), /// Designates that the value is undefined. Undefined, + /// Designates the value as a string. + String(String), } /// The Conversion type of a field. @@ -320,7 +321,7 @@ impl TemporalFields { let FieldValue::String(offset) = value else { return Err(TemporalError::r#type().with_message("offset must be string.")); }; - self.offset = Some(offset.clone()); + self.offset = Some(offset.to_string()); self.bit_map.set(FieldMap::OFFSET, true); Ok(()) @@ -353,7 +354,7 @@ impl TemporalFields { let FieldValue::String(tz) = value else { return Err(TemporalError::r#type().with_message("tz must be string.")); }; - self.time_zone = Some(tz.clone()); + self.time_zone = Some(tz.to_string()); self.bit_map.set(FieldMap::TIME_ZONE, true); Ok(()) } diff --git a/boa_temporal/src/lib.rs b/boa_temporal/src/lib.rs index 0015b1ad999..e2da7ee6825 100644 --- a/boa_temporal/src/lib.rs +++ b/boa_temporal/src/lib.rs @@ -6,59 +6,6 @@ html_favicon_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo.svg" )] #![cfg_attr(not(test), forbid(clippy::unwrap_used))] -#![warn( - // rustc lint groups https://doc.rust-lang.org/rustc/lints/groups.html - warnings, - future_incompatible, - let_underscore, - nonstandard_style, - rust_2018_compatibility, - rust_2018_idioms, - rust_2021_compatibility, - unused, - - // rustc allowed-by-default lints https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html - missing_docs, - macro_use_extern_crate, - meta_variable_misuse, - missing_abi, - missing_copy_implementations, - missing_debug_implementations, - non_ascii_idents, - noop_method_call, - single_use_lifetimes, - trivial_casts, - trivial_numeric_casts, - unreachable_pub, - unsafe_op_in_unsafe_fn, - unused_crate_dependencies, - unused_import_braces, - unused_lifetimes, - unused_qualifications, - unused_tuple_struct_fields, - variant_size_differences, - - // rustdoc lints https://doc.rust-lang.org/rustdoc/lints.html - rustdoc::broken_intra_doc_links, - rustdoc::private_intra_doc_links, - rustdoc::missing_crate_level_docs, - rustdoc::private_doc_tests, - rustdoc::invalid_codeblock_attributes, - rustdoc::invalid_rust_codeblocks, - rustdoc::bare_urls, - - // clippy allowed by default - clippy::dbg_macro, - - // clippy categories https://doc.rust-lang.org/clippy/ - clippy::all, - clippy::correctness, - clippy::suspicious, - clippy::style, - clippy::complexity, - clippy::perf, - clippy::pedantic, -)] #![allow( // Currently throws a false positive regarding dependencies that are only used in benchmarks. unused_crate_dependencies, diff --git a/boa_temporal/src/time.rs b/boa_temporal/src/time.rs index e3920f0124d..742c5f2bd68 100644 --- a/boa_temporal/src/time.rs +++ b/boa_temporal/src/time.rs @@ -4,6 +4,7 @@ use crate::iso::IsoTime; /// The Temporal `PlainTime` object. #[derive(Debug, Default, Clone, Copy)] +#[allow(dead_code)] pub struct TemporalTime { iso: IsoTime, } @@ -11,6 +12,7 @@ pub struct TemporalTime { // ==== Private API ==== impl TemporalTime { + #[allow(dead_code)] pub(crate) fn new_unchecked( hour: i32, minute: i32, @@ -25,6 +27,7 @@ impl TemporalTime { } /// Checks if the time is a valid `TemporalTime` + #[allow(dead_code)] pub(crate) fn is_valid(&self) -> bool { self.iso.is_valid() } diff --git a/boa_temporal/src/utils.rs b/boa_temporal/src/utils.rs index 4660ba49dd1..2c0e1dd1621 100644 --- a/boa_temporal/src/utils.rs +++ b/boa_temporal/src/utils.rs @@ -156,7 +156,7 @@ pub(crate) fn epoch_time_to_epoch_year(t: f64) -> i32 { // roughly calculate the largest possible year given the time t, // then check and refine the year. let day_count = epoch_time_to_day_number(t); - let mut year = day_count / 365; + let mut year = (day_count / 365) + 1970; loop { if epoch_time_for_year(year) <= t { break; @@ -164,7 +164,7 @@ pub(crate) fn epoch_time_to_epoch_year(t: f64) -> i32 { year -= 1; } - year + 1970 + year } /// Returns either 1 (true) or 0 (false) diff --git a/boa_temporal/src/zoneddatetime.rs b/boa_temporal/src/zoneddatetime.rs index 33d612b0960..a2aa4843698 100644 --- a/boa_temporal/src/zoneddatetime.rs +++ b/boa_temporal/src/zoneddatetime.rs @@ -1,6 +1,7 @@ -//! The ZonedDateTime module. +//! The `ZonedDateTime` module. // NOTE: Mostly serves as a placeholder currently // until the rest can be implemented. /// `TemporalZoneDateTime` +#[derive(Debug, Clone, Copy)] pub struct TemporalZonedDateTime; From 82aef336487e91e899869e38e810e34864b0681d Mon Sep 17 00:00:00 2001 From: nekevss Date: Sat, 25 Nov 2023 21:34:17 -0500 Subject: [PATCH 7/9] Rename and some cleanup --- .../src/builtins/temporal/calendar/object.rs | 20 ++--- .../src/builtins/temporal/plain_date/mod.rs | 6 +- .../builtins/temporal/plain_date_time/mod.rs | 2 +- .../builtins/temporal/plain_month_day/mod.rs | 4 +- .../builtins/temporal/plain_year_month/mod.rs | 2 +- boa_temporal/src/calendar.rs | 45 +++++----- boa_temporal/src/calendar/iso.rs | 34 ++++---- boa_temporal/src/date.rs | 34 ++++---- boa_temporal/src/datetime.rs | 12 +-- boa_temporal/src/duration.rs | 20 ++--- boa_temporal/src/month_day.rs | 14 ++-- boa_temporal/src/time.rs | 6 +- boa_temporal/src/utils.rs | 82 ++----------------- boa_temporal/src/year_month.rs | 14 ++-- boa_temporal/src/zoneddatetime.rs | 2 +- 15 files changed, 113 insertions(+), 184 deletions(-) diff --git a/boa_engine/src/builtins/temporal/calendar/object.rs b/boa_engine/src/builtins/temporal/calendar/object.rs index dedc4978d7f..f09ef1158fa 100644 --- a/boa_engine/src/builtins/temporal/calendar/object.rs +++ b/boa_engine/src/builtins/temporal/calendar/object.rs @@ -10,13 +10,13 @@ use std::any::Any; use boa_macros::utf16; use boa_temporal::{ calendar::{CalendarDateLike, CalendarProtocol}, - date::TemporalDate, + date::Date, duration::Duration, error::TemporalError, fields::TemporalFields, - month_day::TemporalMonthDay, + month_day::MonthDay, options::ArithmeticOverflow, - year_month::TemporalYearMonth, + year_month::YearMonth, TemporalResult, TinyStr4, TinyStr8, }; use num_traits::ToPrimitive; @@ -45,7 +45,7 @@ impl CalendarProtocol for CustomRuntimeCalendar { fields: &mut TemporalFields, overflow: ArithmeticOverflow, context: &mut dyn Any, - ) -> TemporalResult { + ) -> TemporalResult { let context = context .downcast_mut::() .expect("Context was not provided for a CustomCalendar."); @@ -97,7 +97,7 @@ impl CalendarProtocol for CustomRuntimeCalendar { fields: &mut TemporalFields, overflow: ArithmeticOverflow, context: &mut dyn Any, - ) -> TemporalResult { + ) -> TemporalResult { let context = context .downcast_mut::() .expect("Context was not provided for a CustomCalendar."); @@ -151,7 +151,7 @@ impl CalendarProtocol for CustomRuntimeCalendar { fields: &mut TemporalFields, overflow: ArithmeticOverflow, context: &mut dyn Any, - ) -> TemporalResult { + ) -> TemporalResult { let context = context .downcast_mut::() .expect("Context was not provided for a CustomCalendar."); @@ -202,19 +202,19 @@ impl CalendarProtocol for CustomRuntimeCalendar { fn date_add( &self, - _date: &TemporalDate, + _date: &Date, _duration: &Duration, _overflow: ArithmeticOverflow, _context: &mut dyn Any, - ) -> TemporalResult { + ) -> TemporalResult { // TODO Err(TemporalError::general("Not yet implemented.")) } fn date_until( &self, - _one: &TemporalDate, - _two: &TemporalDate, + _one: &Date, + _two: &Date, _largest_unit: boa_temporal::options::TemporalUnit, _context: &mut dyn Any, ) -> TemporalResult { diff --git a/boa_engine/src/builtins/temporal/plain_date/mod.rs b/boa_engine/src/builtins/temporal/plain_date/mod.rs index 593d43d86bc..517e02f1628 100644 --- a/boa_engine/src/builtins/temporal/plain_date/mod.rs +++ b/boa_engine/src/builtins/temporal/plain_date/mod.rs @@ -20,8 +20,8 @@ use boa_parser::temporal::{IsoCursor, TemporalDateTimeString}; use boa_profiler::Profiler; use boa_temporal::{ calendar::{AvailableCalendars, CalendarSlot}, - date::TemporalDate as InnerDate, - datetime::TemporalDateTime, + date::Date as InnerDate, + datetime::DateTime, options::ArithmeticOverflow, }; @@ -415,7 +415,7 @@ pub(crate) fn create_temporal_date( }; // 2. If ISODateTimeWithinLimits(isoYear, isoMonth, isoDay, 12, 0, 0, 0, 0, 0) is false, throw a RangeError exception. - if !TemporalDateTime::validate(&inner) { + if !DateTime::validate(&inner) { return Err(JsNativeError::range() .with_message("Date is not within ISO date time limits.") .into()); diff --git a/boa_engine/src/builtins/temporal/plain_date_time/mod.rs b/boa_engine/src/builtins/temporal/plain_date_time/mod.rs index 805015c3bde..5d658e24f6b 100644 --- a/boa_engine/src/builtins/temporal/plain_date_time/mod.rs +++ b/boa_engine/src/builtins/temporal/plain_date_time/mod.rs @@ -11,7 +11,7 @@ use crate::{ }; use boa_profiler::Profiler; -use boa_temporal::datetime::TemporalDateTime as InnerDateTime; +use boa_temporal::datetime::DateTime as InnerDateTime; /// The `Temporal.PlainDateTime` object. #[derive(Debug, Clone)] diff --git a/boa_engine/src/builtins/temporal/plain_month_day/mod.rs b/boa_engine/src/builtins/temporal/plain_month_day/mod.rs index 48d0b4c5473..8acd997b0ef 100644 --- a/boa_engine/src/builtins/temporal/plain_month_day/mod.rs +++ b/boa_engine/src/builtins/temporal/plain_month_day/mod.rs @@ -11,7 +11,7 @@ use crate::{ }; use boa_profiler::Profiler; -use boa_temporal::{datetime::TemporalDateTime, month_day::TemporalMonthDay as InnerMonthDay}; +use boa_temporal::{datetime::DateTime, month_day::MonthDay as InnerMonthDay}; /// The `Temporal.PlainMonthDay` object. #[derive(Debug, Clone)] @@ -73,7 +73,7 @@ pub(crate) fn create_temporal_month_day( ) -> JsResult { // 1. If IsValidISODate(referenceISOYear, isoMonth, isoDay) is false, throw a RangeError exception. // 2. If ISODateTimeWithinLimits(referenceISOYear, isoMonth, isoDay, 12, 0, 0, 0, 0, 0) is false, throw a RangeError exception. - if TemporalDateTime::validate(&inner) { + if DateTime::validate(&inner) { return Err(JsNativeError::range() .with_message("PlainMonthDay is not a valid ISO date time.") .into()); diff --git a/boa_engine/src/builtins/temporal/plain_year_month/mod.rs b/boa_engine/src/builtins/temporal/plain_year_month/mod.rs index 14b37bc6597..c3c6479caa3 100644 --- a/boa_engine/src/builtins/temporal/plain_year_month/mod.rs +++ b/boa_engine/src/builtins/temporal/plain_year_month/mod.rs @@ -13,7 +13,7 @@ use crate::{ use boa_profiler::Profiler; use super::calendar::to_temporal_calendar_slot_value; -use boa_temporal::{options::ArithmeticOverflow, year_month::TemporalYearMonth as InnerYearMonth}; +use boa_temporal::{options::ArithmeticOverflow, year_month::YearMonth as InnerYearMonth}; /// The `Temporal.PlainYearMonth` object. #[derive(Debug, Clone)] diff --git a/boa_temporal/src/calendar.rs b/boa_temporal/src/calendar.rs index 02286a4a88d..1348e8d0ce9 100644 --- a/boa_temporal/src/calendar.rs +++ b/boa_temporal/src/calendar.rs @@ -3,21 +3,22 @@ //! The goal of the calendar module of `boa_temporal` is to provide //! Temporal compatible calendar implementations. //! -//! The implementation will only be of calendar's prexisting calendars. That is to say, -//! it is up to implementers to implement an engine specific impl for handling JavaScript -//! objects that may return true on `ImplementsCalendarProtocol`. +//! The implementation will only be of calendar's prexisting calendars. This library +//! does not come with a pre-existing `CustomCalendar` (i.e., an object that implements +//! the calendar protocol), but it does aim to provide the necessary tools and API for +//! implementing one. use std::{any::Any, str::FromStr}; use crate::{ - date::TemporalDate, - datetime::TemporalDateTime, + date::Date, + datetime::DateTime, duration::Duration, fields::TemporalFields, iso::{IsoDate, IsoDateSlots}, - month_day::TemporalMonthDay, + month_day::MonthDay, options::{ArithmeticOverflow, TemporalUnit}, - year_month::TemporalYearMonth, + year_month::YearMonth, TemporalError, TemporalResult, }; @@ -113,13 +114,13 @@ impl AvailableCalendars { #[derive(Debug)] pub enum CalendarDateLike { /// Represents a `Date` datelike - Date(TemporalDate), + Date(Date), /// Represents a `DateTime` datelike - DateTime(TemporalDateTime), + DateTime(DateTime), /// Represents a `YearMonth` datelike - YearMonth(TemporalYearMonth), + YearMonth(YearMonth), /// Represents a `MonthDay` datelike - MonthDay(TemporalMonthDay), + MonthDay(MonthDay), } impl CalendarDateLike { @@ -163,34 +164,34 @@ pub trait CalendarProtocol: CalendarProtocolClone { fields: &mut TemporalFields, overflow: ArithmeticOverflow, context: &mut dyn Any, - ) -> TemporalResult; + ) -> TemporalResult; /// Creates a `Temporal.PlainYearMonth` object from the provided fields. fn year_month_from_fields( &self, fields: &mut TemporalFields, overflow: ArithmeticOverflow, context: &mut dyn Any, - ) -> TemporalResult; + ) -> TemporalResult; /// Creates a `Temporal.PlainMonthDay` object from the provided fields. fn month_day_from_fields( &self, fields: &mut TemporalFields, overflow: ArithmeticOverflow, context: &mut dyn Any, - ) -> TemporalResult; + ) -> TemporalResult; /// Returns a `Temporal.PlainDate` based off an added date. fn date_add( &self, - date: &TemporalDate, + date: &Date, duration: &Duration, overflow: ArithmeticOverflow, context: &mut dyn Any, - ) -> TemporalResult; + ) -> TemporalResult; /// Returns a `Temporal.Duration` representing the duration between two dates. fn date_until( &self, - one: &TemporalDate, - two: &TemporalDate, + one: &Date, + two: &Date, largest_unit: TemporalUnit, context: &mut dyn Any, ) -> TemporalResult; @@ -334,11 +335,11 @@ impl CalendarSlot { /// TODO: More Docs pub fn date_add( &self, - date: &TemporalDate, + date: &Date, duration: &Duration, overflow: ArithmeticOverflow, context: &mut dyn Any, - ) -> TemporalResult { + ) -> TemporalResult { match self { Self::Identifier(id) => { let protocol = AvailableCalendars::from_str(id)?.to_protocol(); @@ -353,8 +354,8 @@ impl CalendarSlot { /// TODO: More Docs pub fn date_until( &self, - one: &TemporalDate, - two: &TemporalDate, + one: &Date, + two: &Date, largest_unit: TemporalUnit, context: &mut dyn Any, ) -> TemporalResult { diff --git a/boa_temporal/src/calendar/iso.rs b/boa_temporal/src/calendar/iso.rs index fb584e89270..7ddb835e3e2 100644 --- a/boa_temporal/src/calendar/iso.rs +++ b/boa_temporal/src/calendar/iso.rs @@ -1,14 +1,14 @@ //! Implementation of the "iso8601" calendar. use crate::{ - date::TemporalDate, + date::Date, duration::Duration, error::TemporalError, fields::TemporalFields, - month_day::TemporalMonthDay, + month_day::MonthDay, options::{ArithmeticOverflow, TemporalUnit}, utils, - year_month::TemporalYearMonth, + year_month::YearMonth, TemporalResult, }; use std::any::Any; @@ -33,14 +33,14 @@ impl CalendarProtocol for IsoCalendar { fields: &mut TemporalFields, overflow: ArithmeticOverflow, _: &mut dyn Any, - ) -> TemporalResult { + ) -> TemporalResult { // NOTE: we are in ISO by default here. // a. Perform ? ISOResolveMonth(fields). // b. Let result be ? ISODateFromFields(fields, overflow). fields.iso_resolve_month()?; - // 9. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], "iso8601"). - TemporalDate::new( + // 9. Return ? CreateDate(result.[[Year]], result.[[Month]], result.[[Day]], "iso8601"). + Date::new( fields.year().unwrap_or(0), fields.month().unwrap_or(0), fields.day().unwrap_or(0), @@ -57,15 +57,15 @@ impl CalendarProtocol for IsoCalendar { fields: &mut TemporalFields, overflow: ArithmeticOverflow, _: &mut dyn Any, - ) -> TemporalResult { + ) -> TemporalResult { // 9. If calendar.[[Identifier]] is "iso8601", then // a. Perform ? ISOResolveMonth(fields). fields.iso_resolve_month()?; // TODO: Do we even need ISOYearMonthFromFields? YearMonth would should pass as a valid date // b. Let result be ? ISOYearMonthFromFields(fields, overflow). - // 10. Return ? CreateTemporalYearMonth(result.[[Year]], result.[[Month]], "iso8601", result.[[ReferenceISODay]]). - TemporalYearMonth::new( + // 10. Return ? CreateYearMonth(result.[[Year]], result.[[Month]], "iso8601", result.[[ReferenceISODay]]). + YearMonth::new( fields.year().unwrap_or(0), fields.month().unwrap_or(0), fields.day(), @@ -82,14 +82,14 @@ impl CalendarProtocol for IsoCalendar { fields: &mut TemporalFields, overflow: ArithmeticOverflow, _: &mut dyn Any, - ) -> TemporalResult { + ) -> TemporalResult { // 8. Perform ? ISOResolveMonth(fields). fields.iso_resolve_month()?; // TODO: double check error mapping is correct for specifcation/test262. // 9. Let result be ? ISOMonthDayFromFields(fields, overflow). - // 10. Return ? CreateTemporalMonthDay(result.[[Month]], result.[[Day]], "iso8601", result.[[ReferenceISOYear]]). - TemporalMonthDay::new( + // 10. Return ? CreateMonthDay(result.[[Month]], result.[[Day]], "iso8601", result.[[ReferenceISOYear]]). + MonthDay::new( fields.month().unwrap_or(0), fields.month().unwrap_or(0), CalendarSlot::Identifier("iso8601".to_string()), @@ -102,16 +102,16 @@ impl CalendarProtocol for IsoCalendar { /// Below implements the basic implementation for an iso8601 calendar's `dateAdd` method. fn date_add( &self, - _date: &TemporalDate, + _date: &Date, _duration: &Duration, _overflow: ArithmeticOverflow, _: &mut dyn Any, - ) -> TemporalResult { + ) -> TemporalResult { // TODO: Not stable on `ICU4X`. Implement once completed. Err(TemporalError::range().with_message("feature not implemented.")) // 9. Let result be ? AddISODate(date.[[ISOYear]], date.[[ISOMonth]], date.[[ISODay]], duration.[[Years]], duration.[[Months]], duration.[[Weeks]], balanceResult.[[Days]], overflow). - // 10. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], "iso8601"). + // 10. Return ? CreateDate(result.[[Year]], result.[[Month]], result.[[Day]], "iso8601"). } /// 12.5.8 `Temporal.Calendar.prototype.dateUntil ( one, two [ , options ] )` @@ -119,8 +119,8 @@ impl CalendarProtocol for IsoCalendar { /// Below implements the basic implementation for an iso8601 calendar's `dateUntil` method. fn date_until( &self, - _one: &TemporalDate, - _two: &TemporalDate, + _one: &Date, + _two: &Date, _largest_unit: TemporalUnit, _: &mut dyn Any, ) -> TemporalResult { diff --git a/boa_temporal/src/date.rs b/boa_temporal/src/date.rs index 0434ee4a023..8413d5ca91c 100644 --- a/boa_temporal/src/date.rs +++ b/boa_temporal/src/date.rs @@ -2,7 +2,7 @@ use crate::{ calendar::CalendarSlot, - datetime::TemporalDateTime, + datetime::DateTime, duration::{DateDuration, Duration}, iso::{IsoDate, IsoDateSlots}, options::{ArithmeticOverflow, TemporalUnit}, @@ -12,15 +12,15 @@ use std::any::Any; /// The `Temporal.PlainDate` equivalent #[derive(Debug, Default, Clone)] -pub struct TemporalDate { +pub struct Date { iso: IsoDate, calendar: CalendarSlot, } // ==== Private API ==== -impl TemporalDate { - /// Create a new `TemporalDate` with the date values and calendar slot. +impl Date { + /// Create a new `Date` with the date values and calendar slot. #[inline] #[must_use] pub(crate) fn new_unchecked(iso: IsoDate, calendar: CalendarSlot) -> Self { @@ -95,7 +95,7 @@ impl TemporalDate { &self, duration: &Duration, context: &mut dyn Any, - ) -> TemporalResult<(TemporalDate, f64)> { + ) -> TemporalResult<(Self, f64)> { let new_date = self.add_date(duration, ArithmeticOverflow::Constrain, context)?; let days = f64::from(self.days_until(&new_date)); Ok((new_date, days)) @@ -104,8 +104,8 @@ impl TemporalDate { // ==== Public API ==== -impl TemporalDate { - /// Creates a new `TemporalDate` while checking for validity. +impl Date { + /// Creates a new `Date` while checking for validity. pub fn new( year: i32, month: i32, @@ -118,8 +118,8 @@ impl TemporalDate { } #[must_use] - /// Creates a `TemporalDate` from a `TemporalDateTime`. - pub fn from_datetime(dt: &TemporalDateTime) -> Self { + /// Creates a `Date` from a `DateTime`. + pub fn from_datetime(dt: &DateTime) -> Self { Self { iso: dt.iso_date(), calendar: dt.calendar().clone(), @@ -128,35 +128,35 @@ impl TemporalDate { #[inline] #[must_use] - /// Returns this `TemporalDate`'s year value. + /// Returns this `Date`'s year value. pub const fn year(&self) -> i32 { self.iso.year() } #[inline] #[must_use] - /// Returns this `TemporalDate`'s month value. + /// Returns this `Date`'s month value. pub const fn month(&self) -> u8 { self.iso.month() } #[inline] #[must_use] - /// Returns this `TemporalDate`'s day value. + /// Returns this `Date`'s day value. pub const fn day(&self) -> u8 { self.iso.day() } #[inline] #[must_use] - /// Returns the `TemporalDate`'s inner `IsoDate` record. + /// Returns the `Date`'s inner `IsoDate` record. pub const fn iso_date(&self) -> IsoDate { self.iso } #[inline] #[must_use] - /// Returns a reference to this `TemporalDate`'s calendar slot. + /// Returns a reference to this `Date`'s calendar slot. pub fn calendar(&self) -> &CalendarSlot { &self.calendar } @@ -171,7 +171,7 @@ impl TemporalDate { /// `DaysUntil` /// - /// Calculates the epoch days between two `TemporalDate`s + /// Calculates the epoch days between two `Date`s #[inline] #[must_use] pub fn days_until(&self, other: &Self) -> i32 { @@ -179,7 +179,7 @@ impl TemporalDate { } } -impl IsoDateSlots for TemporalDate { +impl IsoDateSlots for Date { /// Returns the structs `IsoDate` fn iso_date(&self) -> IsoDate { self.iso @@ -188,7 +188,7 @@ impl IsoDateSlots for TemporalDate { // ==== Context based API ==== -impl TemporalDate { +impl Date { /// Returns the date after adding the given duration to date. /// /// Temporal Equivalent: 3.5.13 `AddDate ( calendar, plainDate, duration [ , options [ , dateAdd ] ] )` diff --git a/boa_temporal/src/datetime.rs b/boa_temporal/src/datetime.rs index 0fffd463669..cca21649297 100644 --- a/boa_temporal/src/datetime.rs +++ b/boa_temporal/src/datetime.rs @@ -7,17 +7,17 @@ use crate::{ TemporalResult, }; -/// The `TemporalDateTime` struct. +/// The `DateTime` struct. #[derive(Debug, Default, Clone)] -pub struct TemporalDateTime { +pub struct DateTime { iso: IsoDateTime, calendar: CalendarSlot, } // ==== Private DateTime API ==== -impl TemporalDateTime { - /// Creates a new unchecked `TemporalDateTime`. +impl DateTime { + /// Creates a new unchecked `DateTime`. #[inline] #[must_use] pub(crate) fn new_unchecked(date: IsoDate, time: IsoTime, calendar: CalendarSlot) -> Self { @@ -37,8 +37,8 @@ impl TemporalDateTime { // ==== Public DateTime API ==== -impl TemporalDateTime { - /// Creates a new validated `TemporalDateTime`. +impl DateTime { + /// Creates a new validated `DateTime`. #[inline] #[allow(clippy::too_many_arguments)] pub fn new( diff --git a/boa_temporal/src/duration.rs b/boa_temporal/src/duration.rs index fb19fce61d3..e04f7cfd7b4 100644 --- a/boa_temporal/src/duration.rs +++ b/boa_temporal/src/duration.rs @@ -3,11 +3,11 @@ //! TODO: Docs use crate::{ - date::TemporalDate, - datetime::TemporalDateTime, + date::Date, + datetime::DateTime, options::{ArithmeticOverflow, TemporalRoundingMode, TemporalUnit}, utils, - zoneddatetime::TemporalZonedDateTime, + zoneddatetime::ZonedDateTime, TemporalError, TemporalResult, NS_PER_DAY, }; use std::any::Any; @@ -702,7 +702,7 @@ impl Duration { pub(crate) fn unbalance_duration_relative( &self, largest_unit: TemporalUnit, - plain_relative_to: Option<&TemporalDate>, + plain_relative_to: Option<&Date>, context: &mut dyn Any, ) -> TemporalResult { // 1. Let allZero be false. @@ -923,7 +923,7 @@ impl Duration { pub fn balance_date_duration_relative( &self, largest_unit: TemporalUnit, - plain_relative_to: Option<&TemporalDate>, + plain_relative_to: Option<&Date>, context: &mut dyn Any, ) -> TemporalResult { let mut result = self.date; @@ -1159,11 +1159,7 @@ impl Duration { increment: f64, unit: TemporalUnit, rounding_mode: TemporalRoundingMode, - relative_targets: ( - Option<&TemporalDate>, - Option<&TemporalZonedDateTime>, - Option<&TemporalDateTime>, - ), + relative_targets: (Option<&Date>, Option<&ZonedDateTime>, Option<&DateTime>), context: &mut dyn Any, ) -> TemporalResult<(Self, f64)> { let mut result = Duration::new_unchecked(unbalance_date_duration, self.time); @@ -1288,8 +1284,8 @@ impl Duration { ArithmeticOverflow::Constrain, )?; - // l. Let wholeDaysLater be ? CreateTemporalDate(isoResult.[[Year]], isoResult.[[Month]], isoResult.[[Day]], calendar). - let whole_days_later = TemporalDate::new_unchecked(iso_result, calendar.clone()); + // l. Let wholeDaysLater be ? CreateDate(isoResult.[[Year]], isoResult.[[Month]], isoResult.[[Day]], calendar). + let whole_days_later = Date::new_unchecked(iso_result, calendar.clone()); // m. Let untilOptions be OrdinaryObjectCreate(null). // n. Perform ! CreateDataPropertyOrThrow(untilOptions, "largestUnit", "year"). diff --git a/boa_temporal/src/month_day.rs b/boa_temporal/src/month_day.rs index 69cf7aa0c67..0d9b7875c34 100644 --- a/boa_temporal/src/month_day.rs +++ b/boa_temporal/src/month_day.rs @@ -1,4 +1,4 @@ -//! `TemporalMonthDay` +//! `MonthDay` use crate::{ calendar::CalendarSlot, @@ -7,15 +7,15 @@ use crate::{ TemporalResult, }; -/// The `TemporalMonthDay` struct +/// The `MonthDay` struct #[derive(Debug, Default, Clone)] -pub struct TemporalMonthDay { +pub struct MonthDay { iso: IsoDate, calendar: CalendarSlot, } -impl TemporalMonthDay { - /// Creates a new unchecked `TemporalMonthDay` +impl MonthDay { + /// Creates a new unchecked `MonthDay` #[inline] #[must_use] pub(crate) fn new_unchecked(iso: IsoDate, calendar: CalendarSlot) -> Self { @@ -23,7 +23,7 @@ impl TemporalMonthDay { } #[inline] - /// Creates a new valid `TemporalMonthDay`. + /// Creates a new valid `MonthDay`. pub fn new( month: i32, day: i32, @@ -42,7 +42,7 @@ impl TemporalMonthDay { } } -impl IsoDateSlots for TemporalMonthDay { +impl IsoDateSlots for MonthDay { #[inline] /// Returns this structs `IsoDate`. fn iso_date(&self) -> IsoDate { diff --git a/boa_temporal/src/time.rs b/boa_temporal/src/time.rs index 742c5f2bd68..f618c3ed0bc 100644 --- a/boa_temporal/src/time.rs +++ b/boa_temporal/src/time.rs @@ -5,13 +5,13 @@ use crate::iso::IsoTime; /// The Temporal `PlainTime` object. #[derive(Debug, Default, Clone, Copy)] #[allow(dead_code)] -pub struct TemporalTime { +pub struct Time { iso: IsoTime, } // ==== Private API ==== -impl TemporalTime { +impl Time { #[allow(dead_code)] pub(crate) fn new_unchecked( hour: i32, @@ -26,7 +26,7 @@ impl TemporalTime { } } - /// Checks if the time is a valid `TemporalTime` + /// Returns true if a valid `Time`. #[allow(dead_code)] pub(crate) fn is_valid(&self) -> bool { self.iso.is_valid() diff --git a/boa_temporal/src/utils.rs b/boa_temporal/src/utils.rs index 2c0e1dd1621..09ed3b71c22 100644 --- a/boa_temporal/src/utils.rs +++ b/boa_temporal/src/utils.rs @@ -1,13 +1,11 @@ //! Utility equations for Temporal use crate::{ - iso::IsoDate, options::{TemporalRoundingMode, TemporalUnsignedRoundingMode}, MS_PER_DAY, }; use std::ops::Mul; -use tinystr::TinyStr4; // NOTE: Review the below for optimizations and add ALOT of tests. @@ -150,8 +148,6 @@ pub(crate) fn epoch_time_for_year(y: i32) -> f64 { f64::from(MS_PER_DAY) * epoch_day_number_for_year(f64::from(y)) } -// NOTE: The below returns the epoch years (years since 1970). The spec -// appears to assume the below returns with the epoch applied. pub(crate) fn epoch_time_to_epoch_year(t: f64) -> i32 { // roughly calculate the largest possible year given the time t, // then check and refine the year. @@ -236,11 +232,10 @@ pub(crate) fn epoch_time_to_day_in_year(t: f64) -> i32 { - (epoch_day_number_for_year(f64::from(epoch_time_to_epoch_year(t))) as i32) } -pub(crate) fn epoch_time_to_week_day(t: f64) -> i32 { - (epoch_time_to_day_number(t) + 4) % 7 -} +// EpochTimeTOWeekDay -> REMOVED // ==== End Date Equations ==== + // ==== Begin Calendar Equations ==== // NOTE: below was the iso methods in temporal::calendar -> Need to be reassessed. @@ -255,74 +250,11 @@ pub(crate) fn iso_days_in_month(year: i32, month: i32) -> i32 { } } -/// 12.2.32 `ToISOWeekOfYear ( year, month, day )` -/// -/// Takes an `[[IsoYear]]`, `[[IsoMonth]]`, and `[[IsoDay]]` and returns a (week, year) record. -#[allow(unused)] -pub(crate) fn to_iso_week_of_year(year: i32, month: u8, day: u8) -> (i32, i32) { - // Function constants - // 2. Let wednesday be 3. - // 3. Let thursday be 4. - // 4. Let friday be 5. - // 5. Let saturday be 6. - // 6. Let daysInWeek be 7. - // 7. Let maxWeekNumber be 53. - let day_of_year = to_iso_day_of_year(year, month, day); - let day_of_week = to_iso_day_of_week(year, month, day); - let week = (day_of_week + 7 - day_of_week + 3) / 7; - - if week < 1 { - let first_day_of_year = to_iso_day_of_week(year, 1, 1); - if first_day_of_year == 5 { - return (53, year - 1); - } else if first_day_of_year == 6 - && mathematical_in_leap_year(epoch_time_for_year(year - 1)) == 1 - { - return (52, year - 1); - } - return (52, year - 1); - } else if week == 53 { - let days_in_year = mathematical_days_in_year(year); - let days_later_in_year = days_in_year - day_of_year; - let days_after_thursday = 4 - day_of_week; - if days_later_in_year < days_after_thursday { - return (1, year - 1); - } - } - (week, year) -} - -/// 12.2.33 `ISOMonthCode ( month )` -#[allow(unused)] -fn iso_month_code(month: i32) -> TinyStr4 { - // TODO: optimize - if month < 10 { - TinyStr4::from_bytes(format!("M0{month}").as_bytes()).expect("Cannot be more than 4 bytes") - } else { - TinyStr4::from_bytes(format!("M{month}").as_bytes()).expect("double check these later") - } -} - -/// 12.2.39 `ToISODayOfYear ( year, month, day )` -#[allow(unused)] -fn to_iso_day_of_year(year: i32, month: u8, day: u8) -> i32 { - // TODO: update fn parameter to take IsoDateRecord. - let iso = IsoDate::new_unchecked(year, month - 1, day); - let epoch_days = iso.to_epoch_days(); - epoch_time_to_day_in_year(epoch_days_to_epoch_ms(epoch_days, 0f64)) + 1 -} - -/// 12.2.40 `ToISODayOfWeek ( year, month, day )` -#[allow(unused)] -pub(crate) fn to_iso_day_of_week(year: i32, month: u8, day: u8) -> i32 { - let iso = IsoDate::new_unchecked(year, month - 1, day); - let epoch_days = iso.to_epoch_days(); - let day_of_week = epoch_time_to_week_day(epoch_days_to_epoch_ms(epoch_days, 0f64)); - if day_of_week == 0 { - return 7; - } - day_of_week -} +// The below calendar abstract equations/utilities were removed for being unused. +// 12.2.32 `ToISOWeekOfYear ( year, month, day )` +// 12.2.33 `ISOMonthCode ( month )` +// 12.2.39 `ToISODayOfYear ( year, month, day )` +// 12.2.40 `ToISODayOfWeek ( year, month, day )` // ==== End Calendar Equations ==== diff --git a/boa_temporal/src/year_month.rs b/boa_temporal/src/year_month.rs index 517bb97feec..c7283cb7b03 100644 --- a/boa_temporal/src/year_month.rs +++ b/boa_temporal/src/year_month.rs @@ -1,4 +1,4 @@ -//! `TemporalYearMonth` +//! `YearMonth` use crate::{ calendar::CalendarSlot, @@ -7,22 +7,22 @@ use crate::{ TemporalResult, }; -/// The `TemporalYearMonth` struct +/// The `YearMonth` struct #[derive(Debug, Default, Clone)] -pub struct TemporalYearMonth { +pub struct YearMonth { iso: IsoDate, calendar: CalendarSlot, } -impl TemporalYearMonth { - /// Creates an unvalidated `TemporalYearMonth`. +impl YearMonth { + /// Creates an unvalidated `YearMonth`. #[inline] #[must_use] pub(crate) fn new_unchecked(iso: IsoDate, calendar: CalendarSlot) -> Self { Self { iso, calendar } } - /// Creates a new valid `TemporalYearMonth`. + /// Creates a new valid `YearMonth`. #[inline] pub fn new( year: i32, @@ -44,7 +44,7 @@ impl TemporalYearMonth { } } -impl IsoDateSlots for TemporalYearMonth { +impl IsoDateSlots for YearMonth { #[inline] /// Returns this `YearMonth`'s `IsoDate` fn iso_date(&self) -> IsoDate { diff --git a/boa_temporal/src/zoneddatetime.rs b/boa_temporal/src/zoneddatetime.rs index a2aa4843698..6fd3f2c94a4 100644 --- a/boa_temporal/src/zoneddatetime.rs +++ b/boa_temporal/src/zoneddatetime.rs @@ -4,4 +4,4 @@ // until the rest can be implemented. /// `TemporalZoneDateTime` #[derive(Debug, Clone, Copy)] -pub struct TemporalZonedDateTime; +pub struct ZonedDateTime; From 928ea22b21d1da31196c6e3b68c5e8172a5874bb Mon Sep 17 00:00:00 2001 From: nekevss Date: Tue, 28 Nov 2023 17:27:11 -0500 Subject: [PATCH 8/9] Remove migrated record file --- .../src/builtins/temporal/duration/record.rs | 1975 ----------------- 1 file changed, 1975 deletions(-) delete mode 100644 boa_engine/src/builtins/temporal/duration/record.rs diff --git a/boa_engine/src/builtins/temporal/duration/record.rs b/boa_engine/src/builtins/temporal/duration/record.rs deleted file mode 100644 index 8b2f2f7d0d1..00000000000 --- a/boa_engine/src/builtins/temporal/duration/record.rs +++ /dev/null @@ -1,1975 +0,0 @@ -//! The `DurationRecord` implements the internal representation of a Temporal Duration. - -use crate::{ - builtins::{ - options::RoundingMode, - 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, plain_date, to_integer_if_integral, PlainDate, PlainDateTime, ZonedDateTime, -}; - -// ==== `DateDuration` ==== - -/// `DateDuration` represents the [date duration record][spec] of the `DurationRecord.` -/// -/// These fields are laid out in the [Temporal Proposal][field spec] as 64-bit floating point numbers. -/// -/// [spec]: https://tc39.es/proposal-temporal/#sec-temporal-date-duration-records -/// [field spec]: https://tc39.es/proposal-temporal/#sec-properties-of-temporal-duration-instances -#[derive(Debug, Default, Clone, Copy)] -pub(crate) struct DateDuration { - years: f64, - months: f64, - weeks: f64, - days: f64, -} - -impl DateDuration { - pub(crate) const fn new(years: f64, months: f64, weeks: f64, days: f64) -> Self { - Self { - years, - months, - weeks, - days, - } - } - - pub(crate) const fn partial() -> Self { - Self { - years: f64::NAN, - months: f64::NAN, - weeks: f64::NAN, - 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 { - type Item = f64; - type IntoIter = DateIter<'a>; - - fn into_iter(self) -> Self::IntoIter { - DateIter { - date: self, - index: 0, - } - } -} - -pub(crate) struct DateIter<'a> { - date: &'a DateDuration, - index: usize, -} - -impl Iterator for DateIter<'_> { - type Item = f64; - - fn next(&mut self) -> Option { - let result = match self.index { - 0 => Some(self.date.years), - 1 => Some(self.date.months), - 2 => Some(self.date.weeks), - 3 => Some(self.date.days), - _ => None, - }; - self.index += 1; - result - } -} - -// ==== `TimeDuration` ==== - -/// `TimeDuration` represents the [Time Duration record][spec] of the `DurationRecord.` -/// -/// These fields are laid out in the [Temporal Proposal][field spec] as 64-bit floating point numbers. -/// -/// [spec]: https://tc39.es/proposal-temporal/#sec-temporal-time-duration-records -/// [field spec]: https://tc39.es/proposal-temporal/#sec-properties-of-temporal-duration-instances -#[derive(Debug, Default, Clone, Copy)] -pub(crate) struct TimeDuration { - hours: f64, - minutes: f64, - seconds: f64, - milliseconds: f64, - microseconds: f64, - nanoseconds: f64, -} - -impl TimeDuration { - pub(crate) const fn new( - hours: f64, - minutes: f64, - seconds: f64, - milliseconds: f64, - microseconds: f64, - nanoseconds: f64, - ) -> Self { - Self { - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, - } - } - - pub(crate) const fn partial() -> Self { - Self { - hours: f64::NAN, - minutes: f64::NAN, - seconds: f64::NAN, - milliseconds: f64::NAN, - microseconds: f64::NAN, - nanoseconds: f64::NAN, - } - } - - /// Utility function for returning if values in a valid range. - #[inline] - pub(crate) fn is_within_range(&self) -> bool { - self.hours.abs() < 24f64 - && self.minutes.abs() < 60f64 - && self.seconds.abs() < 60f64 - && self.milliseconds.abs() < 1000f64 - && self.milliseconds.abs() < 1000f64 - && self.milliseconds.abs() < 1000f64 - } -} - -impl<'a> IntoIterator for &'a TimeDuration { - type Item = f64; - type IntoIter = TimeIter<'a>; - - fn into_iter(self) -> Self::IntoIter { - TimeIter { - time: self, - index: 0, - } - } -} - -pub(crate) struct TimeIter<'a> { - time: &'a TimeDuration, - index: usize, -} - -impl Iterator for TimeIter<'_> { - type Item = f64; - - fn next(&mut self) -> Option { - let result = match self.index { - 0 => Some(self.time.hours), - 1 => Some(self.time.minutes), - 2 => Some(self.time.seconds), - 3 => Some(self.time.milliseconds), - 4 => Some(self.time.microseconds), - 5 => Some(self.time.nanoseconds), - _ => None, - }; - self.index += 1; - result - } -} - -// ==== `DurationRecord` ==== - -/// The `DurationRecord` is a native Rust implementation of the `Duration` builtin -/// object internal fields and is primarily defined by Abtract Operation 7.5.1-5. -#[derive(Debug, Clone, Copy, Default)] -pub(crate) struct DurationRecord { - date: DateDuration, - time: TimeDuration, -} - -impl DurationRecord { - pub(crate) const fn new(date: DateDuration, time: TimeDuration) -> Self { - Self { date, time } - } - - pub(crate) const fn partial() -> Self { - Self { - date: DateDuration::partial(), - time: TimeDuration::partial(), - } - } - - pub(crate) fn from_date_duration(date: DateDuration) -> Self { - Self { - date, - time: TimeDuration::default(), - } - } - - pub(crate) const fn from_day_and_time(day: f64, time: TimeDuration) -> Self { - Self { - date: DateDuration::new(0.0, 0.0, 0.0, day), - time, - } - } - - /// Utility function to create a one year duration. - pub(crate) fn one_year(year_value: f64) -> Self { - Self::from_date_duration(DateDuration::new(year_value, 0f64, 0f64, 0f64)) - } - - /// Utility function to create a one month duration. - pub(crate) fn one_month(month_value: f64) -> Self { - Self::from_date_duration(DateDuration::new(0f64, month_value, 0f64, 0f64)) - } - - /// Utility function to create a one week duration. - pub(crate) fn one_week(week_value: f64) -> Self { - Self::from_date_duration(DateDuration::new(0f64, 0f64, week_value, 0f64)) - } - - /// Utility function to return if the Durations values are within their valid ranges. - #[inline] - pub(crate) fn is_time_within_range(&self) -> bool { - self.time.is_within_range() - } - - /// Equivalent to 7.5.13 `ToTemporalPartialDurationRecord ( temporalDurationLike )` - /// - /// Takes an unknown `JsObject` and attempts to create a partial duration - pub(crate) fn from_partial_js_object( - duration_like: &JsValue, - context: &mut Context, - ) -> JsResult { - // 1. If Type(temporalDurationLike) is not Object, then - let JsValue::Object(unknown_object) = duration_like else { - // a. Throw a TypeError exception. - return Err(JsNativeError::typ() - .with_message("temporalDurationLike must be an object.") - .into()); - }; - - // 2. Let result be a new partial Duration Record with each field set to undefined. - let mut result = Self::partial(); - - // 3. NOTE: The following steps read properties and perform independent validation in alphabetical order. - // 4. Let days be ? Get(temporalDurationLike, "days"). - let days = unknown_object.get(utf16!("days"), context)?; - if !days.is_undefined() { - // 5. If days is not undefined, set result.[[Days]] to ? ToIntegerIfIntegral(days). - result.set_days(f64::from(to_integer_if_integral(&days, context)?)); - } - - // 6. Let hours be ? Get(temporalDurationLike, "hours"). - let hours = unknown_object.get(utf16!("hours"), context)?; - // 7. If hours is not undefined, set result.[[Hours]] to ? ToIntegerIfIntegral(hours). - if !hours.is_undefined() { - result.set_days(f64::from(to_integer_if_integral(&hours, context)?)); - } - - // 8. Let microseconds be ? Get(temporalDurationLike, "microseconds"). - let microseconds = unknown_object.get(utf16!("microseconds"), context)?; - // 9. If microseconds is not undefined, set result.[[Microseconds]] to ? ToIntegerIfIntegral(microseconds). - if !microseconds.is_undefined() { - result.set_days(f64::from(to_integer_if_integral(µseconds, context)?)); - } - - // 10. Let milliseconds be ? Get(temporalDurationLike, "milliseconds"). - let milliseconds = unknown_object.get(utf16!("milliseconds"), context)?; - // 11. If milliseconds is not undefined, set result.[[Milliseconds]] to ? ToIntegerIfIntegral(milliseconds). - if !milliseconds.is_undefined() { - result.set_days(f64::from(to_integer_if_integral(&milliseconds, context)?)); - } - - // 12. Let minutes be ? Get(temporalDurationLike, "minutes"). - let minutes = unknown_object.get(utf16!("minutes"), context)?; - // 13. If minutes is not undefined, set result.[[Minutes]] to ? ToIntegerIfIntegral(minutes). - if !minutes.is_undefined() { - result.set_days(f64::from(to_integer_if_integral(&minutes, context)?)); - } - - // 14. Let months be ? Get(temporalDurationLike, "months"). - let months = unknown_object.get(utf16!("months"), context)?; - // 15. If months is not undefined, set result.[[Months]] to ? ToIntegerIfIntegral(months). - if !months.is_undefined() { - result.set_days(f64::from(to_integer_if_integral(&months, context)?)); - } - - // 16. Let nanoseconds be ? Get(temporalDurationLike, "nanoseconds"). - let nanoseconds = unknown_object.get(utf16!("nanoseconds"), context)?; - // 17. If nanoseconds is not undefined, set result.[[Nanoseconds]] to ? ToIntegerIfIntegral(nanoseconds). - if !nanoseconds.is_undefined() { - result.set_days(f64::from(to_integer_if_integral(&nanoseconds, context)?)); - } - - // 18. Let seconds be ? Get(temporalDurationLike, "seconds"). - let seconds = unknown_object.get(utf16!("seconds"), context)?; - // 19. If seconds is not undefined, set result.[[Seconds]] to ? ToIntegerIfIntegral(seconds). - if !seconds.is_undefined() { - result.set_days(f64::from(to_integer_if_integral(&seconds, context)?)); - } - - // 20. Let weeks be ? Get(temporalDurationLike, "weeks"). - let weeks = unknown_object.get(utf16!("weeks"), context)?; - // 21. If weeks is not undefined, set result.[[Weeks]] to ? ToIntegerIfIntegral(weeks). - if !weeks.is_undefined() { - result.set_days(f64::from(to_integer_if_integral(&weeks, context)?)); - } - - // 22. Let years be ? Get(temporalDurationLike, "years"). - let years = unknown_object.get(utf16!("years"), context)?; - // 23. If years is not undefined, set result.[[Years]] to ? ToIntegerIfIntegral(years). - if !years.is_undefined() { - result.set_days(f64::from(to_integer_if_integral(&years, context)?)); - } - - // 24. If years is undefined, and months is undefined, and weeks is undefined, and days is undefined, and hours is undefined, and minutes is undefined, and seconds is undefined, and milliseconds is undefined, and microseconds is undefined, and nanoseconds is undefined, throw a TypeError exception. - if result.into_iter().all(f64::is_nan) { - return Err(JsNativeError::typ() - .with_message("no valid Duration fields on temporalDurationLike.") - .into()); - } - - // 25. Return result. - Ok(result) - } -} - -// -- `DurationRecord` bubble/balance methods -- - -impl DurationRecord { - /// Balance/bubble the current unit from one step down. - fn balance_hours(&mut self) { - // 1. Set hours to floor(minutes / 60). - self.set_hours((self.minutes() / 60_f64).floor()); - // 2. Set minutes to minutes modulo 60. - self.set_minutes(self.minutes() % 60_f64); - } - - /// Balance/bubble the current unit from one step down. - fn balance_minutes(&mut self) { - // 1. Set minutes to floor(seconds / 60). - self.set_minutes((self.seconds() / 60_f64).floor()); - // 2. Set seconds to seconds modulo 60. - self.set_seconds(self.seconds() % 60_f64); - } - - /// Balance/bubble the current unit from one step down. - fn balance_seconds(&mut self) { - // 1. Set seconds to floor(milliseconds / 1000). - self.set_seconds((self.milliseconds() / 1_000_f64).floor()); - // 2. Set milliseconds to milliseconds modulo 1000. - self.set_milliseconds(self.milliseconds() % 1_000_f64); - } - - /// Balance/bubble the current unit from one step down. - fn balance_milliseconds(&mut self) { - // c. Set milliseconds to floor(microseconds / 1000). - self.set_milliseconds((self.microseconds() / 1_000_f64).floor()); - // d. Set microseconds to microseconds modulo 1000. - self.set_microseconds(self.microseconds() % 1_000_f64); - } - - /// Balance/bubble the current unit from one step down. - fn balance_microseconds(&mut self) { - // a. Set microseconds to floor(nanoseconds / 1000). - self.set_microseconds((self.nanoseconds() / 1_000_f64).floor()); - // b. Set nanoseconds to nanoseconds modulo 1000. - self.set_nanoseconds(self.nanoseconds() % 1_000_f64); - } -} - -// ==== `DurationRecord` getter/setter methods ==== - -impl DurationRecord { - /// Return this `DurationRecord`'s `DateDuration` - pub(crate) const fn date(&self) -> DateDuration { - self.date - } - - /// Return this `DurationRecord`'s `TimeDuration` - pub(crate) const fn time(&self) -> TimeDuration { - self.time - } - - /// Set this `DurationRecord`'s `TimeDuration`. - pub(crate) fn set_time_duration(&mut self, time: TimeDuration) { - self.time = time; - } - - /// Set the value for `years`. - pub(crate) fn set_years(&mut self, y: f64) { - self.date.years = y; - } - - /// Return the value for `years`. - pub(crate) const fn years(&self) -> f64 { - self.date.years - } - - /// Set the value for `months`. - pub(crate) fn set_months(&mut self, mo: f64) { - self.date.months = mo; - } - - /// Return the value for `months`. - pub(crate) const fn months(&self) -> f64 { - self.date.months - } - - /// Set the value for `weeks`. - pub(crate) fn set_weeks(&mut self, w: f64) { - self.date.weeks = w; - } - - /// Return the value for `weeks`. - pub(crate) const fn weeks(&self) -> f64 { - self.date.weeks - } - - /// Set the value for `days`. - pub(crate) fn set_days(&mut self, d: f64) { - self.date.days = d; - } - - /// Return the value for `days`. - pub(crate) const fn days(&self) -> f64 { - self.date.days - } - - /// Set the value for `hours`. - pub(crate) fn set_hours(&mut self, h: f64) { - self.time.hours = h; - } - - /// Return the value for `hours`. - pub(crate) const fn hours(&self) -> f64 { - self.time.hours - } - - /// Set the value for `minutes`. - pub(crate) fn set_minutes(&mut self, m: f64) { - self.time.minutes = m; - } - - /// Return the value for `minutes`. - pub(crate) const fn minutes(&self) -> f64 { - self.time.minutes - } - - /// Set the value for `seconds`. - pub(crate) fn set_seconds(&mut self, s: f64) { - self.time.seconds = s; - } - - /// Return the value for `seconds`. - pub(crate) const fn seconds(&self) -> f64 { - self.time.seconds - } - - /// Set the value for `milliseconds`. - pub(crate) fn set_milliseconds(&mut self, ms: f64) { - self.time.milliseconds = ms; - } - - /// Return the value for `milliseconds`. - pub(crate) const fn milliseconds(&self) -> f64 { - self.time.milliseconds - } - - /// Set the value for `microseconds`. - pub(crate) fn set_microseconds(&mut self, mis: f64) { - self.time.microseconds = mis; - } - - /// Return the value for `microseconds`. - pub(crate) const fn microseconds(&self) -> f64 { - self.time.microseconds - } - - /// Set the value for `nanoseconds`. - pub(crate) fn set_nanoseconds(&mut self, ns: f64) { - self.time.nanoseconds = ns; - } - - /// Return the value for `nanoseconds`. - pub(crate) const fn nanoseconds(&self) -> f64 { - self.time.nanoseconds - } -} - -impl<'a> IntoIterator for &'a DurationRecord { - type Item = f64; - type IntoIter = DurationIter<'a>; - - fn into_iter(self) -> Self::IntoIter { - DurationIter { - duration: self, - index: 0, - } - } -} - -pub(crate) struct DurationIter<'a> { - duration: &'a DurationRecord, - index: usize, -} - -impl Iterator for DurationIter<'_> { - type Item = f64; - - fn next(&mut self) -> Option { - let result = match self.index { - 0 => Some(self.duration.years()), - 1 => Some(self.duration.months()), - 2 => Some(self.duration.weeks()), - 3 => Some(self.duration.days()), - 4 => Some(self.duration.hours()), - 5 => Some(self.duration.minutes()), - 6 => Some(self.duration.seconds()), - 7 => Some(self.duration.milliseconds()), - 8 => Some(self.duration.microseconds()), - 9 => Some(self.duration.nanoseconds()), - _ => None, - }; - self.index += 1; - result - } -} - -// ==== DurationRecord method ==== - -impl DurationRecord { - pub(crate) fn abs(&self) -> Self { - Self { - date: DateDuration::new( - self.years().abs(), - self.months().abs(), - self.weeks().abs(), - self.days().abs(), - ), - time: TimeDuration::new( - self.hours().abs(), - self.minutes().abs(), - self.seconds().abs(), - self.milliseconds().abs(), - self.microseconds().abs(), - self.nanoseconds().abs(), - ), - } - } -} - -// ==== Abstract Operations implemented on `DurationRecord` ==== - -impl DurationRecord { - // TODO: look into making this destructive / Into. - // Trace current callers and check whether the value - // can be fed a native `DurationRecord` instead. - /// Creates a `Duration` object from the current `DurationRecord`. - pub(crate) fn as_object(&self, context: &mut Context) -> JsResult { - super::create_temporal_duration(*self, None, context) - } - - /// Returns the duration time values as a vec - fn time_values(&self) -> Vec { - self.time.into_iter().collect() - } - - // Note(nekevss): This currently assumes that an overflow has been stored into the years - // column as the duration is nonviable and storing it in years allows for invalidating - // the duration the fastest. - /// Determines if the `DurationRecord` has overflowed. - #[inline] - fn is_overfowed(&self) -> bool { - self.years().is_infinite() - } - - #[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() - } - - /// 7.5.10 `DurationSign ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds )` - /// - /// Determines the sign for the current self. - pub(crate) fn duration_sign(&self) -> i32 { - // 1. For each value v of « years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds », do - for v in self { - // a. If v < 0, return -1. - if v < 0_f64 { - return -1; - // b. If v > 0, return 1. - } else if v > 0_f64 { - return 1; - } - } - // 2. Return 0. - 0 - } - - /// 7.5.11 `IsValidDuration ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds )` - /// - /// Checks if the current `DurationRecord` is a valid self. - pub(crate) fn is_valid_duration(&self) -> bool { - // 1. Let sign be ! DurationSign(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds). - let sign = self.duration_sign(); - // 2. For each value v of « years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds », do - for v in self { - // a. If 𝔽(v) is not finite, return false. - if !v.is_finite() { - return false; - } - // b. If v < 0 and sign > 0, return false. - if v < 0_f64 && sign > 0 { - return false; - } - // c. If v > 0 and sign < 0, return false. - if v > 0_f64 && sign < 0 { - return false; - } - } - // 3. Return true. - true - } - - /// 7.5.12 `DefaultTemporalLargestUnit ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds )` - pub(crate) fn default_temporal_largest_unit(&self) -> TemporalUnit { - for (index, value) in self.into_iter().enumerate() { - if value != 0.0 { - match index { - 0 => return TemporalUnit::Year, - 1 => return TemporalUnit::Month, - 2 => return TemporalUnit::Week, - 3 => return TemporalUnit::Day, - 4 => return TemporalUnit::Hour, - 5 => return TemporalUnit::Minute, - 6 => return TemporalUnit::Second, - 7 => return TemporalUnit::Millisecond, - 8 => return TemporalUnit::Microsecond, - _ => {} - } - } - } - - TemporalUnit::Nanosecond - } - - // TODO: implement on `DurationRecord` - /// 7.5.17 `TotalDurationNanoseconds ( days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, offsetShift )` - fn total_duration_nanoseconds(&self, offset_shift: f64) -> f64 { - let nanoseconds = if self.days() == 0_f64 { - self.nanoseconds() - } else { - self.nanoseconds() - offset_shift - }; - - self.days() - .mul_add(24_f64, self.hours()) - .mul_add(60_f64, self.minutes()) - .mul_add(60_f64, self.seconds()) - .mul_add(1_000_f64, self.milliseconds()) - .mul_add(1_000_f64, self.microseconds()) - .mul_add(1_000_f64, nanoseconds) - } -<<<<<<< HEAD - - /// Abstract Operation 7.5.18 `BalanceTimeDuration ( days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, largestUnit [ , relativeTo ] )` - pub(crate) fn balance_time_duration( - &mut self, - largest_unit: TemporalUnit, - relative_to: Option<&JsValue>, - ) -> JsResult<()> { - // 1. Let balanceResult be ? BalancePossiblyInfiniteDuration(days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, largestUnit, relativeTo). - self.balance_possibly_infinite_duration(largest_unit, relative_to)?; - // 2. If balanceResult is positive overflow or negative overflow, then - if self.is_overfowed() { - // a. Throw a RangeError exception. - return Err(JsNativeError::range() - .with_message("duration overflowed viable range.") - .into()); - } - // 3. Else, - // a. Return balanceResult. - Ok(()) - } - - /// Abstract Operation 7.5.19 `BalancePossiblyInfiniteDuration ( days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, largestUnit [ , relativeTo ] )` - pub(crate) fn balance_possibly_infinite_duration( - &mut self, - largest_unit: TemporalUnit, - relative_to: Option<&JsValue>, - ) -> JsResult<()> { - // 1. If relativeTo is not present, set relativeTo to undefined. - let relative_to = if let Some(value) = relative_to { - value.clone() - } else { - JsValue::undefined() - }; - - // 2. 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 here.") - .is_zoned_date_time() - { - // TODO - // a. Let endNs be ? AddZonedDateTime(relativeTo.[[Nanoseconds]], relativeTo.[[TimeZone]], relativeTo.[[Calendar]], 0, 0, 0, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds). - // b. Set nanoseconds to ℝ(endNs - relativeTo.[[Nanoseconds]]). - self.set_nanoseconds(0_f64); - // 3. Else, - } else { - // a. Set nanoseconds to ! TotalDurationNanoseconds(days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, 0). - self.set_nanoseconds(self.total_duration_nanoseconds(0.0)); - } - - match largest_unit { - // 4. If largestUnit is one of "year", "month", "week", or "day", then - TemporalUnit::Year | TemporalUnit::Month | TemporalUnit::Week | TemporalUnit::Day => { - // a. Let result be ? NanosecondsToDays(nanoseconds, relativeTo). - let _result = temporal::zoned_date_time::nanoseconds_to_days( - self.nanoseconds(), - &relative_to, - ); - // b. Set days to result.[[Days]]. - // c. Set nanoseconds to result.[[Nanoseconds]]. - return Err(JsNativeError::error() - .with_message("not yet implemented.") - .into()); - } - // 5. Else, - // a. Set days to 0. - _ => self.set_days(0_f64), - } - - // 6. Set hours, minutes, seconds, milliseconds, and microseconds to 0. - let new_time = TimeDuration::new(0_f64, 0_f64, 0_f64, 0_f64, 0_f64, self.nanoseconds()); - self.time = new_time; - - // 7. If nanoseconds < 0, let sign be -1; else, let sign be 1. - let sign = if self.nanoseconds() < 0_f64 { - -1_f64 - } else { - 1_f64 - }; - // 8. Set nanoseconds to abs(nanoseconds). - self.set_nanoseconds(self.nanoseconds().abs()); - - match largest_unit { - // 9. If largestUnit is "year", "month", "week", "day", or "hour", then - TemporalUnit::Year - | TemporalUnit::Month - | TemporalUnit::Week - | TemporalUnit::Day - | TemporalUnit::Hour => { - // a. Set microseconds to floor(nanoseconds / 1000). - // b. Set nanoseconds to nanoseconds modulo 1000. - self.balance_microseconds(); - - // c. Set milliseconds to floor(microseconds / 1000). - // d. Set microseconds to microseconds modulo 1000. - self.balance_milliseconds(); - - // e. Set seconds to floor(milliseconds / 1000). - // f. Set milliseconds to milliseconds modulo 1000. - self.balance_minutes(); - - // g. Set minutes to floor(seconds / 60). - // h. Set seconds to seconds modulo 60. - self.balance_minutes(); - - // i. Set hours to floor(minutes / 60). - // j. Set minutes to minutes modulo 60. - self.balance_hours(); - } - // 10. Else if largestUnit is "minute", then - TemporalUnit::Minute => { - // a. Set microseconds to floor(nanoseconds / 1000). - // b. Set nanoseconds to nanoseconds modulo 1000. - self.balance_microseconds(); - - // c. Set milliseconds to floor(microseconds / 1000). - // d. Set microseconds to microseconds modulo 1000. - self.balance_milliseconds(); - - // e. Set seconds to floor(milliseconds / 1000). - // f. Set milliseconds to milliseconds modulo 1000. - self.balance_seconds(); - - // g. Set minutes to floor(seconds / 60). - // h. Set seconds to seconds modulo 60. - self.balance_minutes(); - } - // 11. Else if largestUnit is "second", then - TemporalUnit::Second => { - // a. Set microseconds to floor(nanoseconds / 1000). - // b. Set nanoseconds to nanoseconds modulo 1000. - self.balance_microseconds(); - - // c. Set milliseconds to floor(microseconds / 1000). - // d. Set microseconds to microseconds modulo 1000. - self.balance_milliseconds(); - - // e. Set seconds to floor(milliseconds / 1000). - // f. Set milliseconds to milliseconds modulo 1000. - self.balance_seconds(); - } - // 12. Else if largestUnit is "millisecond", then - TemporalUnit::Millisecond => { - // a. Set microseconds to floor(nanoseconds / 1000). - // b. Set nanoseconds to nanoseconds modulo 1000. - self.balance_microseconds(); - - // c. Set milliseconds to floor(microseconds / 1000). - // d. Set microseconds to microseconds modulo 1000. - self.balance_milliseconds(); - } - // 13. Else if largestUnit is "microsecond", then - TemporalUnit::Microsecond => { - // a. Set microseconds to floor(nanoseconds / 1000). - // b. Set nanoseconds to nanoseconds modulo 1000. - self.balance_microseconds(); - } - // 14. Else, - // a. Assert: largestUnit is "nanosecond". - _ => assert!(largest_unit == TemporalUnit::Nanosecond), - } - - // 15. For each value v of « days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds », do - for value in self.time_values() { - // a. If 𝔽(v) is not finite, then - if !value.is_finite() { - // i. If sign = 1, then - if sign as i32 == 1 { - // 1. Return positive overflow. - self.set_years(f64::INFINITY); - return Ok(()); - } - // ii. Else if sign = -1, then - // 1. Return negative overflow. - self.set_years(f64::NEG_INFINITY); - return Ok(()); - } - } - - // NOTE (nekevss): diviate from spec here as the current implementation with `DurationRecord` means that we create the record and than mutate values. - // 16. Return ? CreateTimeDurationRecord(days, hours × sign, minutes × sign, seconds × sign, milliseconds × sign, microseconds × sign, nanoseconds × sign). - self.set_hours(self.hours() * sign); - self.set_minutes(self.minutes() * sign); - self.set_seconds(self.seconds() * sign); - self.set_milliseconds(self.milliseconds() * sign); - self.set_microseconds(self.microseconds() * sign); - self.set_nanoseconds(self.nanoseconds() * sign); - - // `CreateTimeDurationRecord` validates that the record that would be created is a valid duration, so validate here - if !self.is_valid_duration() { - return Err(JsNativeError::range() - .with_message("TimeDurationRecord was not a valid duration.") - .into()); - } - - Ok(()) - } - - /// 7.5.21 `UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit, plainRelativeTo )` - #[allow(dead_code)] - pub(crate) fn unbalance_duration_relative( - &self, - largest_unit: TemporalUnit, - plain_relative_to: Option<&PlainDate>, - context: &mut Context, - ) -> JsResult { - // 1. Let allZero be false. - // 2. If years = 0, and months = 0, and weeks = 0, and days = 0, set allZero to true. - let all_zero = self.years() == 0_f64 - && self.months() == 0_f64 - && self.weeks() == 0_f64 - && self.days() == 0_f64; - - // 3. If largestUnit is "year" or allZero is true, then - if largest_unit == TemporalUnit::Year || all_zero { - // a. Return ! CreateDateDurationRecord(years, months, weeks, days). - return Ok(self.date()); - }; - - // 4. Let sign be ! DurationSign(years, months, weeks, days, 0, 0, 0, 0, 0, 0). - // 5. Assert: sign ≠ 0. - let sign = f64::from(self.duration_sign()); - - // 6. Let oneYear be ! CreateTemporalDuration(sign, 0, 0, 0, 0, 0, 0, 0, 0, 0). - let one_year = Self::one_year(sign); - // 7. Let oneMonth be ! CreateTemporalDuration(0, sign, 0, 0, 0, 0, 0, 0, 0, 0). - let one_month = Self::one_month(sign); - - // 9. If plainRelativeTo is not undefined, then - // a. Let calendar be plainRelativeTo.[[Calendar]]. - // 10. Else, - // a. Let calendar be undefined. - - // 11. If largestUnit is "month", then - if largest_unit == TemporalUnit::Month { - // a. If years = 0, return ! CreateDateDurationRecord(0, months, weeks, days). - if self.years() == 0f64 { - return Ok(DateDuration::new( - 0f64, - self.months(), - self.weeks(), - self.days(), - )); - } - - // b. If calendar is undefined, then - let (mut plain_relative_to, calendar) = - if let Some(plain_relative_to) = plain_relative_to { - ( - PlainDate::new(plain_relative_to.inner, plain_relative_to.calendar.clone()), - plain_relative_to.calendar.clone(), - ) - } else { - // i. Throw a RangeError exception. - return Err(JsNativeError::range() - .with_message("Calendar cannot be undefined.") - .into()); - }; - - // c. If calendar is an Object, then - // i. Let dateAdd be ? GetMethod(calendar, "dateAdd"). - // ii. Let dateUntil be ? GetMethod(calendar, "dateUntil"). - // d. Else, - // i. Let dateAdd be unused. - // ii. Let dateUntil be unused. - - let mut years = self.years(); - let mut months = self.months(); - // e. Repeat, while years ≠ 0, - while years != 0f64 { - // i. Let newRelativeTo be ? CalendarDateAdd(calendar, plainRelativeTo, oneYear, undefined, dateAdd). - let new_relative_to = calendar::calendar_date_add( - &calendar, - &plain_relative_to, - &one_year, - &JsValue::undefined(), - context, - )?; - - // ii. Let untilOptions be OrdinaryObjectCreate(null). - let until_options = JsObject::with_null_proto(); - // iii. Perform ! CreateDataPropertyOrThrow(untilOptions, "largestUnit", "month"). - until_options.create_data_property_or_throw( - utf16!("largestUnit"), - js_string!("month"), - context, - )?; - - // iv. Let untilResult be ? CalendarDateUntil(calendar, plainRelativeTo, newRelativeTo, untilOptions, dateUntil). - let until_result = calendar::calendar_date_until( - &calendar, - &plain_relative_to, - &new_relative_to, - &until_options.into(), - context, - )?; - - // v. Let oneYearMonths be untilResult.[[Months]]. - let one_year_months = until_result.months(); - - // vi. Set plainRelativeTo to newRelativeTo. - plain_relative_to = new_relative_to; - - // vii. Set years to years - sign. - years -= sign; - // viii. Set months to months + oneYearMonths. - months += one_year_months; - } - // f. Return ? CreateDateDurationRecord(0, months, weeks, days). - return Ok(DateDuration::new(years, months, self.weeks(), self.days())); - - // 12. If largestUnit is "week", then - } else if largest_unit == TemporalUnit::Week { - // a. If years = 0 and months = 0, return ! CreateDateDurationRecord(0, 0, weeks, days). - if self.years() == 0f64 && self.months() == 0f64 { - return Ok(DateDuration::new(0f64, 0f64, self.weeks(), self.days())); - } - - // b. If calendar is undefined, then - let (mut plain_relative_to, calendar) = - if let Some(plain_relative_to) = plain_relative_to { - ( - PlainDate::new(plain_relative_to.inner, plain_relative_to.calendar.clone()), - plain_relative_to.calendar.clone(), - ) - } else { - // i. Throw a RangeError exception. - return Err(JsNativeError::range() - .with_message("Calendar cannot be undefined.") - .into()); - }; - - // c. If calendar is an Object, then - // i. Let dateAdd be ? GetMethod(calendar, "dateAdd"). - // d. Else, - // i. Let dateAdd be unused. - - let mut years = self.years(); - let mut days = self.days(); - // e. Repeat, while years ≠ 0, - while years != 0f64 { - // i. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, oneYear, dateAdd). - let move_result = - super::move_relative_date(&calendar, &plain_relative_to, &one_year, context)?; - - // ii. Set plainRelativeTo to moveResult.[[RelativeTo]]. - plain_relative_to = move_result.0; - // iii. Set days to days + moveResult.[[Days]]. - days += move_result.1; - // iv. Set years to years - sign. - years -= sign; - } - - let mut months = self.months(); - // f. Repeat, while months ≠ 0, - while months != 0f64 { - // i. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, oneMonth, dateAdd). - let move_result = - super::move_relative_date(&calendar, &plain_relative_to, &one_month, context)?; - // ii. Set plainRelativeTo to moveResult.[[RelativeTo]]. - plain_relative_to = move_result.0; - // iii. Set days to days + moveResult.[[Days]]. - days += move_result.1; - // iv. Set months to months - sign. - months -= sign; - } - // g. Return ? CreateDateDurationRecord(0, 0, weeks, days). - return Ok(DateDuration::new(0f64, 0f64, self.weeks(), days)); - } - - // 13. If years = 0, and months = 0, and weeks = 0, return ! CreateDateDurationRecord(0, 0, 0, days). - if self.years() == 0f64 && self.months() == 0f64 && self.weeks() == 0f64 { - return Ok(DateDuration::new(0f64, 0f64, 0f64, self.days())); - } - - // NOTE: Move 8 down to past 13 as we only use one_week after making it past 13. - // 8. Let oneWeek be ! CreateTemporalDuration(0, 0, sign, 0, 0, 0, 0, 0, 0, 0). - let one_week = Self::one_week(sign); - - // 14. If calendar is undefined, then - let (mut plain_relative_to, calendar) = if let Some(plain_relative_to) = plain_relative_to { - ( - PlainDate::new(plain_relative_to.inner, plain_relative_to.calendar.clone()), - plain_relative_to.calendar.clone(), - ) - } else { - // a. Throw a RangeError exception. - return Err(JsNativeError::range() - .with_message("Calendar cannot be undefined.") - .into()); - }; - - // 15. If calendar is an Object, then - // a. Let dateAdd be ? GetMethod(calendar, "dateAdd"). - // 16. Else, - // a. Let dateAdd be unused. - - let mut years = self.years(); - let mut days = self.days(); - // a. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, oneYear, dateAdd). - while years != 0f64 { - // a. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, oneYear, dateAdd). - let move_result = - super::move_relative_date(&calendar, &plain_relative_to, &one_year, context)?; - - // b. Set plainRelativeTo to moveResult.[[RelativeTo]]. - plain_relative_to = move_result.0; - // c. Set days to days + moveResult.[[Days]]. - days += move_result.1; - // d. Set years to years - sign. - years -= sign; - } - - let mut months = self.months(); - // 18. Repeat, while months ≠ 0, - while months != 0f64 { - // a. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, oneMonth, dateAdd). - let move_result = - super::move_relative_date(&calendar, &plain_relative_to, &one_month, context)?; - // b. Set plainRelativeTo to moveResult.[[RelativeTo]]. - plain_relative_to = move_result.0; - // c. Set days to days +moveResult.[[Days]]. - days += move_result.1; - // d. Set months to months - sign. - months -= sign; - } - - let mut weeks = self.weeks(); - // 19. Repeat, while weeks ≠ 0, - while weeks != 0f64 { - // a. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, oneWeek, dateAdd). - let move_result = - super::move_relative_date(&calendar, &plain_relative_to, &one_week, context)?; - // b. Set plainRelativeTo to moveResult.[[RelativeTo]]. - plain_relative_to = move_result.0; - // c. Set days to days + moveResult.[[Days]]. - days += move_result.1; - // d. Set weeks to weeks - sign. - weeks -= sign; - } - - // 20. Return ? CreateDateDurationRecord(0, 0, 0, days). - Ok(DateDuration::new(0f64, 0f64, 0f64, days)) - } - - /// `BalanceDateDurationRelative` - #[allow(unused)] - pub(crate) fn balance_date_duration_relative( - &mut self, - largest_unit: TemporalUnit, - relative_to: &JsValue, - context: &mut Context, - ) -> JsResult<()> { - // 1. Let allZero be false. - // 2. If years = 0, and months = 0, and weeks = 0, and days = 0, set allZero to true. - let all_zero = self.years() == 0.0 - && self.months() == 0.0 - && self.weeks() == 0.0 - && self.days() == 0.0; - - // 3. If largestUnit is not one of "year", "month", or "week", or allZero is true, then - match largest_unit { - TemporalUnit::Year | TemporalUnit::Month | TemporalUnit::Week if !all_zero => {} - _ => { - // a. Return ! CreateDateDurationRecord(years, months, weeks, days). - return Ok(()); - } - } - - // 4. If relativeTo is undefined, then - if relative_to.is_undefined() { - // a. Throw a RangeError exception. - return Err(JsNativeError::range() - .with_message("relativeTo cannot be undefined.") - .into()); - } - - // 5. Let sign be ! DurationSign(years, months, weeks, days, 0, 0, 0, 0, 0, 0). - // 6. Assert: sign ≠ 0. - 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::one_year(sign); - // 8. Let oneMonth be ! CreateTemporalDuration(0, sign, 0, 0, 0, 0, 0, 0, 0, 0). - let one_month = Self::one_month(sign); - // 9. Let oneWeek be ! CreateTemporalDuration(0, 0, sign, 0, 0, 0, 0, 0, 0, 0). - let one_week = Self::one_week(sign); - - // 10. Set relativeTo to ? ToTemporalDate(relativeTo). - let mut relative_to = to_temporal_date(relative_to, None, context)?; - - // 11. Let calendar be relativeTo.[[Calendar]]. - let calendar = &relative_to.calendar.clone(); - - match largest_unit { - // 12. If largestUnit is "year", then - TemporalUnit::Year => { - // a. If calendar is an Object, then - // i. Let dateAdd be ? GetMethod(calendar, "dateAdd"). - // 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, context)?; - - // d. Let newRelativeTo be moveResult.[[RelativeTo]]. - let mut new_relative = move_result.0; - // e. Let oneYearDays be moveResult.[[Days]]. - let mut one_year_days = move_result.1; - - // f. Repeat, while abs(days) ≥ abs(oneYearDays), - while self.days().abs() >= one_year_days.abs() { - // i. Set days to days - oneYearDays. - self.set_days(self.days() - one_year_days); - - // ii. Set years to years + 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, context)?; - - // v. Set newRelativeTo to moveResult.[[RelativeTo]]. - new_relative = move_result.0; - // vi. Set oneYearDays to moveResult.[[Days]]. - one_year_days = move_result.1; - } - - // g. Set moveResult to ? MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd). - // h. Set newRelativeTo to moveResult.[[RelativeTo]]. - // i. Let oneMonthDays be moveResult.[[Days]]. - 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() + sign); - // iii. Set relativeTo to newRelativeTo. - - 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, 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; - } - - // 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), - 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 => { - // a. If calendar is an Object, then - // 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), - 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 => { - // a. Assert: largestUnit is "week". - // b. If calendar is an Object, then - // 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), - 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). - 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 [ , plainRelativeTo [, zonedRelativeTo [, precalculatedDateTime]]] )` - pub(crate) fn round_duration( - &self, - unbalance_date_duration: DateDuration, - increment: f64, - unit: TemporalUnit, - rounding_mode: RoundingMode, - relative_targets: ( - Option<&PlainDate>, - Option<&ZonedDateTime>, - Option<&PlainDateTime>, - ), - context: &mut Context, - ) -> JsResult<(Self, f64)> { - let mut result = DurationRecord::new(unbalance_date_duration, self.time()); - - // 1. If plainRelativeTo is not present, set plainRelativeTo to undefined. - let plain_relative_to = relative_targets.0; - // 2. If zonedRelativeTo is not present, set zonedRelativeTo to undefined. - let zoned_relative_to = relative_targets.1; - // 3. If precalculatedPlainDateTime is not present, set precalculatedPlainDateTime to undefined. - let _ = relative_targets.2; - - 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("plainRelativeTo canot be undefined with given TemporalUnit") - .into()); - } - // 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(hours, minutes, seconds, milliseconds, microseconds, nanoseconds). - let nanos = - Self::from_day_and_time(0.0, result.time()).total_duration_nanoseconds(0.0); - - // 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() { - result.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. Set days, hours, minutes, seconds, milliseconds, microseconds, and nanoseconds to 0. - result.set_days(0.0); - result.set_time_duration(TimeDuration::default()); - // e. Assert: fractionalSeconds is not used below. - (Some(frac_days), None) - } - // 6. Else, - _ => { - // a. Let fractionalSeconds be nanoseconds × 10-9 + microseconds × 10-6 + milliseconds × 10-3 + seconds. - let frac_secs = result.nanoseconds().mul_add( - 1_000_000_000f64, - result.microseconds().mul_add( - 1_000_000f64, - result.milliseconds().mul_add(1_000f64, result.seconds()), - ), - ); - - // b. Assert: fractionalDays is not used below. - (None, Some(frac_secs)) - } - }; - - // 7. let total be unset. - // We begin matching against unit and return the remainder value. - let total = match unit { - // 8. If unit is "year", then - TemporalUnit::Year => { - let mut frac_days = - frac_days.expect("assert that fractionalDays exists for TemporalUnit == year"); - - 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(result.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"). - // d. Else, - // i. Let dateAdd be unused. - - // e. Let yearsLater be ? AddDate(calendar, plainRelativeTo, yearsDuration, undefined, dateAdd). - let years_later = plain_date::add_date( - calendar, - plain_relative_to, - &years_duration, - &JsValue::undefined(), - context, - )?; - - // f. Let yearsMonthsWeeks be ! CreateTemporalDuration(years, months, weeks, 0, 0, 0, 0, 0, 0, 0). - let years_months_weeks = Self::new( - DateDuration::new(result.years(), result.months(), result.weeks(), 0.0), - TimeDuration::default(), - ); - - // 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, - &JsValue::undefined(), - context, - )?; - - // h. Let monthsWeeksInDays be DaysUntil(yearsLater, yearsMonthsWeeksLater). - let months_weeks_in_days = - super::days_until(&years_later, &years_months_weeks_later); - - // i. Set plainRelativeTo to yearsLater. - let plain_relative_to = years_later; - - // j. Set fractionalDays to fractionalDays + monthsWeeksInDays. - frac_days += f64::from(months_weeks_in_days); - - // 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 wholeDaysLater be ? CreateTemporalDate(isoResult.[[Year]], isoResult.[[Month]], isoResult.[[Day]], calendar). - let whole_days_later = PlainDate::new(iso_result, calendar.clone()); - - // 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, - TemporalUnit::Year, - context, - )?; - - // p. Let yearsPassed be timePassed.[[Years]]. - let years_passed = time_passed.years(); - - // q. Set years to years + yearsPassed. - result.set_years(result.years() + years_passed); - - // r. Let yearsDuration be ! CreateTemporalDuration(yearsPassed, 0, 0, 0, 0, 0, 0, 0, 0, 0). - let years_duration = Self::one_year(years_passed); - - // 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, - context, - )?; - - // v. Set fractionalDays to fractionalDays - daysPassed. - frac_days -= days_passed; - - // w. If fractionalDays < 0, let sign be -1; else, let sign be 1. - let sign = if frac_days < 0.0 { -1 } else { 1 }; - - // x. Let oneYear be ! CreateTemporalDuration(sign, 0, 0, 0, 0, 0, 0, 0, 0, 0). - let one_year = Self::one_year(f64::from(sign)); - - // 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)?; - - // aa. Let fractionalYears be years + fractionalDays / abs(oneYearDays). - let frac_years = result.years() + (frac_days / one_year_days.abs()); - - // ab. Set years to RoundNumberToIncrement(fractionalYears, increment, roundingMode). - result.set_years(round_number_to_increment( - frac_years, - increment, - rounding_mode, - )); - - // ac. Set total to fractionalYears. - // ad. Set months and weeks to 0. - result.set_months(0.0); - result.set_weeks(0.0); - - frac_years - } - // 9. Else if unit is "month", then - TemporalUnit::Month => { - 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("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::from_date_duration(DateDuration::new( - result.years(), - result.months(), - 0.0, - 0.0, - )); - - // c. If calendar is an Object, then - // i. Let dateAdd be ? GetMethod(calendar, "dateAdd"). - // d. Else, - // i. Let dateAdd be unused. - - // 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, - )?; - - // f. Let yearsMonthsWeeks be ! CreateTemporalDuration(years, months, weeks, 0, 0, 0, 0, 0, 0, 0). - let years_months_weeks = Self::from_date_duration(DateDuration::new( - result.years(), - result.months(), - result.weeks(), - 0.0, - )); - - // 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, - &JsValue::undefined(), - context, - )?; - - // h. Let weeksInDays be DaysUntil(yearsMonthsLater, yearsMonthsWeeksLater). - let weeks_in_days = - super::days_until(&years_months_later, &years_months_weeks_later); - - // i. Set plainRelativeTo to yearsMonthsLater. - let plain_relative_to = years_months_later; - - // j. Set fractionalDays to fractionalDays + weeksInDays. - frac_days += f64::from(weeks_in_days); - - // k. If fractionalDays < 0, let sign be -1; else, let sign be 1. - let sign = if frac_days < 0.0 { -1f64 } else { 1f64 }; - - // l. Let oneMonth be ! CreateTemporalDuration(0, sign, 0, 0, 0, 0, 0, 0, 0, 0). - let one_month = Self::one_month(sign); - - // 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)?; - - // p. Repeat, while abs(fractionalDays) ≥ abs(oneMonthDays), - while frac_days.abs() >= one_month_days.abs() { - // i. Set months to months + sign. - result.set_months(result.months() + sign); - - // 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, - &plain_relative_to, - &one_month, - context, - )?; - // iv. Set plainRelativeTo to moveResult.[[RelativeTo]]. - plain_relative_to = move_result.0; - // v. Set oneMonthDays to moveResult.[[Days]]. - one_month_days = move_result.1; - } - - // q. Let fractionalMonths be months + fractionalDays / abs(oneMonthDays). - let frac_months = result.months() + frac_days / one_month_days.abs(); - - // r. Set months to RoundNumberToIncrement(fractionalMonths, increment, roundingMode). - result.set_months(round_number_to_increment( - frac_months, - increment, - rounding_mode, - )); - - // s. Set total to fractionalMonths. - // t. Set weeks to 0. - result.set_weeks(0.0); - frac_months - } - // 10. Else if unit is "week", then - TemporalUnit::Week => { - 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::one_week(sign); - - // d. If calendar is an Object, then - // i. Let dateAdd be ? GetMethod(calendar, "dateAdd"). - // e. Else, - // i. Let dateAdd be unused. - - // 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)?; - - // i. Repeat, while abs(fractionalDays) ≥ abs(oneWeekDays), - while frac_days.abs() >= one_week_days.abs() { - // i. Set weeks to weeks + sign. - result.set_weeks(result.weeks() + sign); - - // 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, - &plain_relative_to, - &one_week, - context, - )?; - - // iv. Set plainRelativeTo to moveResult.[[RelativeTo]]. - plain_relative_to = move_result.0; - // v. Set oneWeekDays to moveResult.[[Days]]. - one_week_days = move_result.1; - } - - // j. Let fractionalWeeks be weeks + fractionalDays / abs(oneWeekDays). - let frac_weeks = result.weeks() + frac_days / one_week_days.abs(); - - // k. Set weeks to RoundNumberToIncrement(fractionalWeeks, increment, roundingMode). - result.set_weeks(round_number_to_increment( - frac_weeks, - increment, - rounding_mode, - )); - // l. Set total to fractionalWeeks. - frac_weeks - } - // 11. Else if unit is "day", then - TemporalUnit::Day => { - let frac_days = - frac_days.expect("assert that fractionalDays exists for TemporalUnit::Day"); - - // a. Set days to RoundNumberToIncrement(fractionalDays, increment, roundingMode). - result.set_days(round_number_to_increment( - frac_days, - increment, - rounding_mode, - )); - // b. Set total to fractionalDays. - frac_days - } - // 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 frac_hours = (frac_secs / 60f64 + result.minutes()) / 60f64 + result.hours(); - // b. Set hours to RoundNumberToIncrement(fractionalHours, increment, roundingMode). - 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. - result.set_time_duration(TimeDuration::new(rounded_hours, 0.0, 0.0, 0.0, 0.0, 0.0)); - frac_hours - } - // 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 frac_minutes = frac_secs / 60f64 + result.minutes(); - // b. Set minutes to RoundNumberToIncrement(fractionalMinutes, increment, roundingMode). - 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. - result.set_time_duration(TimeDuration::new( - result.hours(), - rounded_minutes, - 0.0, - 0.0, - 0.0, - 0.0, - )); - - frac_minutes - } - // 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). - result.set_seconds(round_number_to_increment( - frac_secs, - increment, - rounding_mode, - )); - // b. Set total to fractionalSeconds. - // c. Set milliseconds, microseconds, and nanoseconds to 0. - result.set_milliseconds(0.0); - result.set_microseconds(0.0); - result.set_nanoseconds(0.0); - - frac_secs - } - // 15. Else if unit is "millisecond", then - TemporalUnit::Millisecond => { - // a. Let fractionalMilliseconds be nanoseconds × 10-6 + microseconds × 10-3 + milliseconds. - let fraction_millis = result.nanoseconds().mul_add( - 1_000_000f64, - result - .microseconds() - .mul_add(1_000f64, result.milliseconds()), - ); - - // b. Set milliseconds to RoundNumberToIncrement(fractionalMilliseconds, increment, roundingMode). - result.set_milliseconds(round_number_to_increment( - fraction_millis, - increment, - rounding_mode, - )); - - // c. Set total to fractionalMilliseconds. - // d. Set microseconds and nanoseconds to 0. - result.set_microseconds(0.0); - result.set_nanoseconds(0.0); - fraction_millis - } - // 16. Else if unit is "microsecond", then - TemporalUnit::Microsecond => { - // a. Let fractionalMicroseconds be nanoseconds × 10-3 + microseconds. - let frac_micros = result - .nanoseconds() - .mul_add(1_000f64, result.microseconds()); - - // b. Set microseconds to RoundNumberToIncrement(fractionalMicroseconds, increment, roundingMode). - result.set_microseconds(round_number_to_increment( - frac_micros, - increment, - rounding_mode, - )); - - // c. Set total to fractionalMicroseconds. - // d. Set nanoseconds to 0. - result.set_nanoseconds(0.0); - frac_micros - } - // 17. Else, - TemporalUnit::Nanosecond => { - // a. Assert: unit is "nanosecond". - // b. Set total to nanoseconds. - let total = result.nanoseconds(); - // c. Set nanoseconds to RoundNumberToIncrement(nanoseconds, increment, roundingMode). - result.set_nanoseconds(round_number_to_increment( - result.nanoseconds(), - increment, - rounding_mode, - )); - - total - } - TemporalUnit::Auto => unreachable!(), - }; - - // 18. Let duration be ? CreateDurationRecord(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds). - // 19. Return the Record { [[DurationRecord]]: duration, [[Total]]: total }. - Ok((result, total)) - } - - /// 7.5.27 `AdjustRoundedDurationDays ( years, months, weeks, days, hours, minutes, seconds, milliseconds, - /// microseconds, nanoseconds, increment, unit, roundingMode, relativeTo )` - #[allow(unused)] - pub(crate) fn adjust_rounded_duration_days( - &mut self, - increment: f64, - unit: TemporalUnit, - rounding_mode: RoundingMode, - relative_to: Option<&JsValue>, - context: &mut Context, - ) -> JsResult<()> { - // 1. If Type(relativeTo) is not Object; or relativeTo does not have an [[InitializedTemporalZonedDateTime]] - // internal slot; or unit is one of "year", "month", "week", or "day"; or unit is "nanosecond" and increment is 1, then - let relative_to = match relative_to { - Some(rt) - if rt.is_object() - && rt.as_object().expect("must be object").is_zoned_date_time() => - { - let obj = rt.as_object().expect("This must be an object."); - let obj = obj.borrow(); - // a. Return ! CreateDurationRecord(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds). - obj.as_zoned_date_time() - .expect("Object must be a ZonedDateTime.") - .clone() - } - _ => return Ok(()), - }; - - match unit { - // a. Return ! CreateDurationRecord(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds). - TemporalUnit::Year | TemporalUnit::Month | TemporalUnit::Week | TemporalUnit::Day => { - return Ok(()) - } - // a. Return ! CreateDurationRecord(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds). - TemporalUnit::Nanosecond if (increment - 1_f64).abs() < f64::EPSILON => return Ok(()), - _ => {} - } - - // 2. Let timeRemainderNs be ! TotalDurationNanoseconds(0, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, 0). - let time_remainder_ns = - Self::from_day_and_time(0.0, self.time()).total_duration_nanoseconds(0.0); - - // 3. If timeRemainderNs = 0, let direction be 0. - let _direction = if time_remainder_ns == 0_f64 { - 0 - // 4. Else if timeRemainderNs < 0, let direction be -1. - } else if time_remainder_ns < 0_f64 { - -1 - // 5. Else, let direction be 1. - } else { - 1 - }; - - // TODO: 6.5.5 AddZonedDateTime - // 6. Let dayStart be ? AddZonedDateTime(relativeTo.[[Nanoseconds]], relativeTo.[[TimeZone]], relativeTo.[[Calendar]], years, months, weeks, days, 0, 0, 0, 0, 0, 0). - // 7. Let dayEnd be ? AddZonedDateTime(dayStart, relativeTo.[[TimeZone]], relativeTo.[[Calendar]], 0, 0, 0, direction, 0, 0, 0, 0, 0, 0). - // 8. Let dayLengthNs be ℝ(dayEnd - dayStart). - // 9. If (timeRemainderNs - dayLengthNs) × direction < 0, then - // a. Return ! CreateDurationRecord(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds). - // 10. Set timeRemainderNs to ℝ(RoundTemporalInstant(ℤ(timeRemainderNs - dayLengthNs), increment, unit, roundingMode)). - // 11. Let adjustedDateDuration be ? AddDuration(years, months, weeks, days, 0, 0, 0, 0, 0, 0, 0, 0, 0, direction, 0, 0, 0, 0, 0, 0, relativeTo). - // 12. Let adjustedTimeDuration be ? BalanceDuration(0, 0, 0, 0, 0, 0, timeRemainderNs, "hour"). - // 13. Return ! CreateDurationRecord(adjustedDateDuration.[[Years]], adjustedDateDuration.[[Months]], adjustedDateDuration.[[Weeks]], - // adjustedDateDuration.[[Days]], adjustedTimeDuration.[[Hours]], adjustedTimeDuration.[[Minutes]], adjustedTimeDuration.[[Seconds]], - // adjustedTimeDuration.[[Milliseconds]], adjustedTimeDuration.[[Microseconds]], adjustedTimeDuration.[[Nanoseconds]]). - Err(JsNativeError::range() - .with_message("not yet implemented.") - .into()) - } -======= ->>>>>>> b5192f7fe7 (Begin Temporal crate migration) -} From 1b2ae988ce177a3baa12d43d7a34671088feec19 Mon Sep 17 00:00:00 2001 From: nekevss Date: Sun, 3 Dec 2023 20:22:50 -0500 Subject: [PATCH 9/9] Apply Review --- boa_engine/Cargo.toml | 2 +- .../src/builtins/temporal/calendar/object.rs | 12 +- boa_temporal/Cargo.toml | 3 - boa_temporal/src/calendar.rs | 8 +- boa_temporal/src/calendar/iso.rs | 10 +- boa_temporal/src/date.rs | 127 +++++++----------- boa_temporal/src/duration.rs | 15 ++- boa_temporal/src/fields.rs | 4 +- boa_temporal/src/lib.rs | 2 +- 9 files changed, 83 insertions(+), 100 deletions(-) diff --git a/boa_engine/Cargo.toml b/boa_engine/Cargo.toml index a9cb58f670d..a93f8ee755c 100644 --- a/boa_engine/Cargo.toml +++ b/boa_engine/Cargo.toml @@ -62,7 +62,7 @@ boa_profiler.workspace = true boa_macros.workspace = true boa_ast.workspace = true boa_parser.workspace = true -boa_temporal = { workspace = true, features = ["context"] } +boa_temporal = { workspace = true } serde = { workspace = true, features = ["derive", "rc"] } serde_json.workspace = true rand = "0.8.5" diff --git a/boa_engine/src/builtins/temporal/calendar/object.rs b/boa_engine/src/builtins/temporal/calendar/object.rs index f09ef1158fa..291a3d59257 100644 --- a/boa_engine/src/builtins/temporal/calendar/object.rs +++ b/boa_engine/src/builtins/temporal/calendar/object.rs @@ -17,7 +17,7 @@ use boa_temporal::{ month_day::MonthDay, options::ArithmeticOverflow, year_month::YearMonth, - TemporalResult, TinyStr4, TinyStr8, + TemporalResult, TinyAsciiStr, }; use num_traits::ToPrimitive; @@ -222,7 +222,11 @@ impl CalendarProtocol for CustomRuntimeCalendar { Err(TemporalError::general("Not yet implemented.")) } - fn era(&self, _: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult> { + fn era( + &self, + _: &CalendarDateLike, + _: &mut dyn Any, + ) -> TemporalResult>> { // Return undefined as custom calendars do not implement -> Currently. Ok(None) } @@ -322,7 +326,7 @@ impl CalendarProtocol for CustomRuntimeCalendar { &self, date_like: &CalendarDateLike, context: &mut dyn Any, - ) -> TemporalResult { + ) -> TemporalResult> { let context = context .downcast_mut::() .expect("Context was not provided for a CustomCalendar."); @@ -344,7 +348,7 @@ impl CalendarProtocol for CustomRuntimeCalendar { return Err(TemporalError::r#type().with_message("monthCode return must be a String.")); }; - let result = TinyStr4::from_str(&result.to_std_string_escaped()) + let result = TinyAsciiStr::<4>::from_str(&result.to_std_string_escaped()) .map_err(|_| TemporalError::general("Unexpected monthCode value."))?; Ok(result) diff --git a/boa_temporal/Cargo.toml b/boa_temporal/Cargo.toml index 058e64d3490..2c3a1286209 100644 --- a/boa_temporal/Cargo.toml +++ b/boa_temporal/Cargo.toml @@ -19,8 +19,5 @@ num-bigint = { workspace = true, features = ["serde"] } bitflags.workspace = true num-traits.workspace = true -[features] -context = [] - [lints] workspace = true diff --git a/boa_temporal/src/calendar.rs b/boa_temporal/src/calendar.rs index 1348e8d0ce9..5d9e112f830 100644 --- a/boa_temporal/src/calendar.rs +++ b/boa_temporal/src/calendar.rs @@ -22,7 +22,7 @@ use crate::{ TemporalError, TemporalResult, }; -use tinystr::{TinyStr4, TinyStr8}; +use tinystr::TinyAsciiStr; use self::iso::IsoCalendar; @@ -200,7 +200,7 @@ pub trait CalendarProtocol: CalendarProtocolClone { &self, date_like: &CalendarDateLike, context: &mut dyn Any, - ) -> TemporalResult>; + ) -> TemporalResult>>; /// Returns the era year for a given `temporaldatelike` fn era_year( &self, @@ -217,7 +217,7 @@ pub trait CalendarProtocol: CalendarProtocolClone { &self, date_like: &CalendarDateLike, context: &mut dyn Any, - ) -> TemporalResult; + ) -> TemporalResult>; /// Returns the `day` for a given `temporaldatelike` fn day(&self, date_like: &CalendarDateLike, context: &mut dyn Any) -> TemporalResult; /// Returns a value representing the day of the week for a date. @@ -401,7 +401,7 @@ impl CalendarSlot { &self, date_like: &CalendarDateLike, context: &mut dyn Any, - ) -> TemporalResult { + ) -> TemporalResult> { match self { Self::Identifier(id) => { let protocol = AvailableCalendars::from_str(id)?.to_protocol(); diff --git a/boa_temporal/src/calendar/iso.rs b/boa_temporal/src/calendar/iso.rs index 7ddb835e3e2..1ac7283fb22 100644 --- a/boa_temporal/src/calendar/iso.rs +++ b/boa_temporal/src/calendar/iso.rs @@ -13,7 +13,7 @@ use crate::{ }; use std::any::Any; -use tinystr::{TinyStr4, TinyStr8}; +use tinystr::TinyAsciiStr; use super::{CalendarDateLike, CalendarFieldsType, CalendarProtocol, CalendarSlot}; @@ -132,7 +132,11 @@ impl CalendarProtocol for IsoCalendar { } /// `Temporal.Calendar.prototype.era( dateLike )` for iso8601 calendar. - fn era(&self, _: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult> { + fn era( + &self, + _: &CalendarDateLike, + _: &mut dyn Any, + ) -> TemporalResult>> { // Returns undefined on iso8601. Ok(None) } @@ -158,7 +162,7 @@ impl CalendarProtocol for IsoCalendar { &self, date_like: &CalendarDateLike, _: &mut dyn Any, - ) -> TemporalResult { + ) -> TemporalResult> { let date = date_like.as_iso_date().as_icu4x()?; Ok(date.month().code.0) } diff --git a/boa_temporal/src/date.rs b/boa_temporal/src/date.rs index 8413d5ca91c..054940790c4 100644 --- a/boa_temporal/src/date.rs +++ b/boa_temporal/src/date.rs @@ -27,68 +27,6 @@ impl Date { Self { iso, calendar } } - #[inline] - /// `DifferenceDate` - pub(crate) fn diff_date( - &self, - other: &Self, - largest_unit: TemporalUnit, - context: &mut dyn Any, - ) -> TemporalResult { - if self.iso.year() == other.iso.year() - && self.iso.month() == other.iso.month() - && self.iso.day() == other.iso.day() - { - return Ok(Duration::default()); - } - - if largest_unit == TemporalUnit::Day { - let days = self.days_until(other); - return Ok(Duration::from_date_duration(DateDuration::new( - 0f64, - 0f64, - 0f64, - f64::from(days), - ))); - } - - self.calendar() - .date_until(self, other, largest_unit, context) - } - - #[inline] - /// Internal `AddDate` function - pub(crate) fn add_date( - &self, - duration: &Duration, - overflow: ArithmeticOverflow, - context: &mut dyn Any, - ) -> TemporalResult { - // 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.date().years() != 0.0 - || duration.date().months() != 0.0 - || duration.date().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 self.calendar().date_add(self, duration, overflow, context); - } - - // 3. Let overflow be ? ToTemporalOverflow(options). - // 4. Let days be ? BalanceTimeDuration(duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], "day").[[Days]]. - let (days, _) = duration.balance_time_duration(TemporalUnit::Day)?; - - // 5. Let result be ? AddISODate(plainDate.[[ISOYear]], plainDate.[[ISOMonth]], plainDate.[[ISODay]], 0, 0, 0, days, overflow). - let result = self - .iso - .add_iso_date(&DateDuration::new(0f64, 0f64, 0f64, days), overflow)?; - - Ok(Self::new_unchecked(result, self.calendar().clone())) - } - #[inline] /// Returns a new moved date and the days associated with that adjustment pub(crate) fn move_relative_date( @@ -96,7 +34,8 @@ impl Date { duration: &Duration, context: &mut dyn Any, ) -> TemporalResult<(Self, f64)> { - let new_date = self.add_date(duration, ArithmeticOverflow::Constrain, context)?; + let new_date = + self.contextual_add_date(duration, ArithmeticOverflow::Constrain, context)?; let days = f64::from(self.days_until(&new_date)); Ok((new_date, days)) } @@ -189,57 +128,93 @@ impl IsoDateSlots for Date { // ==== Context based API ==== impl Date { - /// Returns the date after adding the given duration to date. + /// Returns the date after adding the given duration to date with a provided context. /// /// Temporal Equivalent: 3.5.13 `AddDate ( calendar, plainDate, duration [ , options [ , dateAdd ] ] )` #[inline] - #[cfg(feature = "context")] - pub fn add_to_date( + pub fn contextual_add_date( &self, duration: &Duration, overflow: ArithmeticOverflow, context: &mut dyn Any, ) -> TemporalResult { - self.add_date(duration, overflow, context) + // 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.date().years() != 0.0 + || duration.date().months() != 0.0 + || duration.date().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 self.calendar().date_add(self, duration, overflow, context); + } + + // 3. Let overflow be ? ToTemporalOverflow(options). + // 4. Let days be ? BalanceTimeDuration(duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], "day").[[Days]]. + let (days, _) = duration.balance_time_duration(TemporalUnit::Day)?; + + // 5. Let result be ? AddISODate(plainDate.[[ISOYear]], plainDate.[[ISOMonth]], plainDate.[[ISODay]], 0, 0, 0, days, overflow). + let result = self + .iso + .add_iso_date(&DateDuration::new(0f64, 0f64, 0f64, days), overflow)?; + + Ok(Self::new_unchecked(result, self.calendar().clone())) } /// Returns the date after adding the given duration to date. /// /// Temporal Equivalent: 3.5.13 `AddDate ( calendar, plainDate, duration [ , options [ , dateAdd ] ] )` #[inline] - #[cfg(not(feature = "context"))] - pub fn add_to_date( + pub fn add_date( &self, duration: &Duration, overflow: ArithmeticOverflow, ) -> TemporalResult { - self.add_date(duration, overflow, &mut ()) + self.contextual_add_date(duration, overflow, &mut ()) } - /// Returns a duration representing the difference between the dates one and two. + /// Returns a duration representing the difference between the dates one and two with a provided context. /// /// Temporal Equivalent: 3.5.6 `DifferenceDate ( calendar, one, two, options )` #[inline] - #[cfg(feature = "context")] - pub fn difference_date( + pub fn contextual_difference_date( &self, other: &Self, largest_unit: TemporalUnit, context: &mut dyn Any, ) -> TemporalResult { - self.diff_date(other, largest_unit, context) + if self.iso.year() == other.iso.year() + && self.iso.month() == other.iso.month() + && self.iso.day() == other.iso.day() + { + return Ok(Duration::default()); + } + + if largest_unit == TemporalUnit::Day { + let days = self.days_until(other); + return Ok(Duration::from_date_duration(DateDuration::new( + 0f64, + 0f64, + 0f64, + f64::from(days), + ))); + } + + self.calendar() + .date_until(self, other, largest_unit, context) } /// Returns a duration representing the difference between the dates one and two. /// /// Temporal Equivalent: 3.5.6 `DifferenceDate ( calendar, one, two, options )` #[inline] - #[cfg(not(feature = "context"))] pub fn difference_date( &self, other: &Self, largest_unit: TemporalUnit, ) -> TemporalResult { - self.diff_date(other, largest_unit, &mut ()) + self.contextual_difference_date(other, largest_unit, &mut ()) } } diff --git a/boa_temporal/src/duration.rs b/boa_temporal/src/duration.rs index e04f7cfd7b4..fa2c9f4962b 100644 --- a/boa_temporal/src/duration.rs +++ b/boa_temporal/src/duration.rs @@ -1245,7 +1245,7 @@ impl Duration { // i. Let dateAdd be unused. // e. Let yearsLater be ? AddDate(calendar, plainRelativeTo, yearsDuration, undefined, dateAdd). - let years_later = plain_relative_to.add_date( + let years_later = plain_relative_to.contextual_add_date( &years_duration, ArithmeticOverflow::Constrain, context, @@ -1263,7 +1263,7 @@ impl Duration { ); // g. Let yearsMonthsWeeksLater be ? AddDate(calendar, plainRelativeTo, yearsMonthsWeeks, undefined, dateAdd). - let years_months_weeks_later = plain_relative_to.add_date( + let years_months_weeks_later = plain_relative_to.contextual_add_date( &years_months_weeks, ArithmeticOverflow::Constrain, context, @@ -1290,8 +1290,11 @@ impl Duration { // 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_relative_to.diff_date(&whole_days_later, TemporalUnit::Year, context)?; + let time_passed = plain_relative_to.contextual_difference_date( + &whole_days_later, + TemporalUnit::Year, + context, + )?; // p. Let yearsPassed be timePassed.[[Years]]. let years_passed = time_passed.date.years(); @@ -1358,7 +1361,7 @@ impl Duration { // i. Let dateAdd be unused. // e. Let yearsMonthsLater be ? AddDate(calendar, plainRelativeTo, yearsMonths, undefined, dateAdd). - let years_months_later = plain_relative_to.add_date( + let years_months_later = plain_relative_to.contextual_add_date( &years_months, ArithmeticOverflow::Constrain, context, @@ -1373,7 +1376,7 @@ impl Duration { )); // g. Let yearsMonthsWeeksLater be ? AddDate(calendar, plainRelativeTo, yearsMonthsWeeks, undefined, dateAdd). - let years_months_weeks_later = plain_relative_to.add_date( + let years_months_weeks_later = plain_relative_to.contextual_add_date( &years_months_weeks, ArithmeticOverflow::Constrain, context, diff --git a/boa_temporal/src/fields.rs b/boa_temporal/src/fields.rs index 98ae9d0bad8..1c6d2a94118 100644 --- a/boa_temporal/src/fields.rs +++ b/boa_temporal/src/fields.rs @@ -6,7 +6,7 @@ use crate::{error::TemporalError, TemporalResult}; use bitflags::bitflags; // use rustc_hash::FxHashSet; -use tinystr::{TinyStr16, TinyStr4}; +use tinystr::{TinyAsciiStr, TinyStr16, TinyStr4}; bitflags! { /// FieldMap maps the currently active fields on the `TemporalField` @@ -467,7 +467,7 @@ impl TemporalFields { } } -fn month_code_to_integer(mc: TinyStr4) -> TemporalResult { +fn month_code_to_integer(mc: TinyAsciiStr<4>) -> TemporalResult { match mc.as_str() { "M01" => Ok(1), "M02" => Ok(2), diff --git a/boa_temporal/src/lib.rs b/boa_temporal/src/lib.rs index e2da7ee6825..fb5149aac07 100644 --- a/boa_temporal/src/lib.rs +++ b/boa_temporal/src/lib.rs @@ -43,7 +43,7 @@ pub mod zoneddatetime; // TODO: evaluate positives and negatives of using tinystr. // Re-exporting tinystr as a convenience, as it is currently tied into the API. -pub use tinystr::{TinyAsciiStr, TinyStr16, TinyStr4, TinyStr8}; +pub use tinystr::TinyAsciiStr; pub use error::TemporalError;