diff --git a/boa_ast/src/temporal/mod.rs b/boa_ast/src/temporal/mod.rs index 25e5c976569..a5bc0172de3 100644 --- a/boa_ast/src/temporal/mod.rs +++ b/boa_ast/src/temporal/mod.rs @@ -2,7 +2,7 @@ /// An ISO Date Node consisting of non-validated date fields and calendar value. #[derive(Default, Debug)] -pub struct ISODate { +pub struct IsoDate { /// Date Year pub year: i32, /// Date Month @@ -13,9 +13,9 @@ pub struct ISODate { pub calendar: Option, } -/// The `ISOTime` node consists of non-validated time fields. +/// The `IsoTime` node consists of non-validated time fields. #[derive(Default, Debug, Clone, Copy)] -pub struct ISOTime { +pub struct IsoTime { /// An hour value between 0-23 pub hour: u8, /// A minute value between 0-59 @@ -30,7 +30,7 @@ pub struct ISOTime { pub nanosecond: u16, } -impl ISOTime { +impl IsoTime { #[must_use] #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] /// A utility initialization function to create `ISOTime` from the `TimeSpec` components. @@ -52,13 +52,13 @@ impl ISOTime { } } -/// The `ISODateTime` node output by the ISO parser +/// The `IsoDateTime` node output by the ISO parser #[derive(Default, Debug)] -pub struct ISODateTime { +pub struct IsoDateTime { /// The `ISODate` record - pub date: ISODate, + pub date: IsoDate, /// The `ISOTime` record - pub time: ISOTime, + pub time: IsoTime, /// The `TimeZone` value for this `ISODateTime` pub tz: Option, } @@ -87,9 +87,9 @@ pub struct UTCOffset { pub fraction: f64, } -/// An `ISODuration` Node output by the ISO parser. +/// An `IsoDuration` Node output by the ISO parser. #[derive(Debug, Default, Clone, Copy)] -pub struct ISODuration { +pub struct IsoDuration { /// Years value. pub years: i32, /// Months value. diff --git a/boa_engine/src/bigint.rs b/boa_engine/src/bigint.rs index 6b08bc7518d..f5e29728320 100644 --- a/boa_engine/src/bigint.rs +++ b/boa_engine/src/bigint.rs @@ -227,8 +227,8 @@ impl JsBigInt { /// Utility function for performing `+` operation on more than two values. #[inline] - #[must_use] - pub fn add_n(values: &[Self]) -> Self { + #[cfg(feature = "experimental")] + pub(crate) fn add_n(values: &[Self]) -> Self { let mut result = Self::zero(); for big_int in values { result = Self::add(&result, big_int); diff --git a/boa_engine/src/builtins/temporal/calendar/mod.rs b/boa_engine/src/builtins/temporal/calendar/mod.rs index 337823a51db..c95a3605a3a 100644 --- a/boa_engine/src/builtins/temporal/calendar/mod.rs +++ b/boa_engine/src/builtins/temporal/calendar/mod.rs @@ -616,11 +616,18 @@ impl Calendar { &options, utf16!("largestUnit"), TemporalUnitGroup::Date, + false, Some(TemporalUnit::Day), None, context, )?; + let Some(largest_unit) = largest_unit else { + return Err(JsNativeError::range() + .with_message("largestUnit cannot be undefined in this context.") + .into()); + }; + this_calendar.date_until(&one, &two, largest_unit, context) } diff --git a/boa_engine/src/builtins/temporal/calendar/utils.rs b/boa_engine/src/builtins/temporal/calendar/utils.rs index 58a10441893..2e7b6f0eba0 100644 --- a/boa_engine/src/builtins/temporal/calendar/utils.rs +++ b/boa_engine/src/builtins/temporal/calendar/utils.rs @@ -1,6 +1,6 @@ //! Calendar utility calculations -// TODO: determine if which are needed. +// TODO: determine if any of the below are needed. use crate::builtins::temporal::{self, date_equations, plain_date::iso::IsoDateRecord}; use crate::JsString; diff --git a/boa_engine/src/builtins/temporal/date_equations.rs b/boa_engine/src/builtins/temporal/date_equations.rs index aa5e1df808b..222e0139757 100644 --- a/boa_engine/src/builtins/temporal/date_equations.rs +++ b/boa_engine/src/builtins/temporal/date_equations.rs @@ -5,7 +5,7 @@ use std::ops::Mul; pub(crate) fn epoch_time_to_day_number(t: f64) -> i32 { - (t / super::NS_PER_DAY as f64).floor() as i32 + (t / f64::from(super::MS_PER_DAY)).floor() as i32 } pub(crate) fn mathematical_days_in_year(y: i32) -> i32 { @@ -22,15 +22,17 @@ pub(crate) fn mathematical_days_in_year(y: i32) -> i32 { } } -pub(crate) const fn epoch_day_number_for_year(y: i32) -> i32 { - 365 * (y - 1970) + ((y - 1970) / 4) - ((y - 1901) / 100) + ((y - 1601) / 400) +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() } -// TODO: potentially inaccurate -> Need to further test this and epoch_day_number_for_year pub(crate) fn epoch_time_for_year(y: i32) -> f64 { - super::NS_PER_DAY as f64 * f64::from(epoch_day_number_for_year(y)) + 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. @@ -43,7 +45,7 @@ pub(crate) fn epoch_time_to_epoch_year(t: f64) -> i32 { year -= 1; } - year + year + 1970 } /// Returns either 1 (true) or 0 (false) @@ -52,39 +54,18 @@ pub(crate) fn mathematical_in_leap_year(t: f64) -> i32 { } pub(crate) fn epoch_time_to_month_in_year(t: f64) -> i32 { - let days = epoch_time_to_day_in_year(t); - let in_leap_year = mathematical_in_leap_year(t) == 1; - - match days { - 0..=30 => 0, - 31..=59 if in_leap_year => 1, - 31..=58 => 1, - 60..=90 if in_leap_year => 2, - 59..=89 => 2, - 91..=121 if in_leap_year => 3, - 90..=120 => 3, - 122..=151 if in_leap_year => 4, - 121..=150 => 4, - 152..=182 if in_leap_year => 5, - 151..=181 => 5, - 183..=213 if in_leap_year => 6, - 182..=212 => 6, - 214..=243 if in_leap_year => 7, - 213..=242 => 7, - 244..=273 if in_leap_year => 8, - 243..=272 => 8, - 274..=304 if in_leap_year => 9, - 273..=303 => 9, - 305..=334 if in_leap_year => 10, - 304..=333 => 10, - 335..=366 if in_leap_year => 11, - 334..=365 => 11, - _ => unreachable!(), + const DAYS: [i32; 11] = [30, 58, 89, 120, 150, 181, 212, 242, 272, 303, 333]; + let day = epoch_time_to_day_in_year(t); + + let result = 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 = i32::from(mathematical_days_in_year(y) == 366); + let leap_day = mathematical_days_in_year(y) - 365; let days = match m { 0 => 1, @@ -128,7 +109,8 @@ pub(crate) fn epoch_time_to_date(t: f64) -> i32 { } pub(crate) fn epoch_time_to_day_in_year(t: f64) -> i32 { - epoch_time_to_day_number(t) - epoch_day_number_for_year(epoch_time_to_epoch_year(t)) + 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 { diff --git a/boa_engine/src/builtins/temporal/duration/mod.rs b/boa_engine/src/builtins/temporal/duration/mod.rs index 3598ceef359..a63f9bc2bdf 100644 --- a/boa_engine/src/builtins/temporal/duration/mod.rs +++ b/boa_engine/src/builtins/temporal/duration/mod.rs @@ -618,11 +618,12 @@ impl Duration { // (ToRelativeTemporalObject reads "relativeTo", ToTemporalRoundingIncrement reads "roundingIncrement" and ToTemporalRoundingMode reads "roundingMode"). // 9. Let largestUnit be ? GetTemporalUnit(roundTo, "largestUnit", datetime, undefined, « "auto" »). - let mut largest_unit = get_temporal_unit( + let largest_unit = get_temporal_unit( &round_to, utf16!("largestUnit"), TemporalUnitGroup::DateTime, - Some(TemporalUnit::Undefined), + false, + None, Some([TemporalUnit::Auto].into()), context, )?; @@ -639,22 +640,25 @@ impl Duration { .unwrap_or(RoundingMode::HalfExpand); // 13. Let smallestUnit be ? GetTemporalUnit(roundTo, "smallestUnit", datetime, undefined). - let mut smallest_unit = get_temporal_unit( + let smallest_unit = get_temporal_unit( &round_to, utf16!("smallestUnit"), TemporalUnitGroup::DateTime, - Some(TemporalUnit::Undefined), + false, + None, None, context, )?; // 14. If smallestUnit is undefined, then - if smallest_unit.is_undefined() { + let smallest_unit = if let Some(unit) = smallest_unit { + unit + } else { // a. Set smallestUnitPresent to false. smallest_unit_present = false; // b. Set smallestUnit to "nanosecond". - smallest_unit = TemporalUnit::Nanosecond; - } + TemporalUnit::Nanosecond + }; // 15. Let defaultLargestUnit be ! DefaultTemporalLargestUnit(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]]). let mut default_largest_unit = duration.inner.default_temporal_largest_unit(); @@ -663,15 +667,16 @@ impl Duration { default_largest_unit = core::cmp::max(default_largest_unit, smallest_unit); // 17. If largestUnit is undefined, then - if largest_unit.is_undefined() { - // a. Set largestUnitPresent to false. - largest_unit_present = false; - // b. Set largestUnit to defaultLargestUnit. - largest_unit = default_largest_unit; - } else if largest_unit.is_auto() { - // a. Set largestUnit to defaultLargestUnit. - largest_unit = default_largest_unit; - } + let largest_unit = match largest_unit { + Some(u) if u == TemporalUnit::Auto => default_largest_unit, + Some(u) => u, + None => { + // a. Set largestUnitPresent to false. + largest_unit_present = false; + // b. Set largestUnit to defaultLargestUnit. + default_largest_unit + } + }; // 19. If smallestUnitPresent is false and largestUnitPresent is false, then if !smallest_unit_present && !largest_unit_present { @@ -693,7 +698,7 @@ impl Duration { // 22. If maximum is not undefined, perform ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false). if let Some(max) = maximum { - validate_temporal_rounding_increment(rounding_increment, max, false)?; + validate_temporal_rounding_increment(rounding_increment, f64::from(max), false)?; } let mut unbalance_duration = DurationRecord::from_date_duration(duration.inner.date()); @@ -794,11 +799,18 @@ impl Duration { &total_of, utf16!("unit"), TemporalUnitGroup::DateTime, + true, None, None, context, )?; + let Some(unit) = unit else { + return Err(JsNativeError::range() + .with_message("TemporalUnit cannot be undefined in this context.") + .into()); + }; + let mut unbalance_duration = DurationRecord::from_date_duration(duration.inner.date()); // 9. Let unbalanceResult be ? UnbalanceDurationRelative(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], unit, relativeTo). @@ -894,7 +906,7 @@ impl Duration { // b. Let whole be roundResult.[[Nanoseconds]]. TemporalUnit::Nanosecond => round_record.nanoseconds(), // a. Assert: unit is "nanosecond". - _=> unreachable!("Unit must be a valid temporal unit. Any other value would be an implementation error."), + TemporalUnit::Auto=> unreachable!("Unit must be a valid temporal unit. Any other value would be an implementation error."), }; // 28. Return 𝔽(whole + roundRecord.[[Remainder]]). diff --git a/boa_engine/src/builtins/temporal/duration/record.rs b/boa_engine/src/builtins/temporal/duration/record.rs index e22f2b3a8e6..d05f07aecb1 100644 --- a/boa_engine/src/builtins/temporal/duration/record.rs +++ b/boa_engine/src/builtins/temporal/duration/record.rs @@ -1,17 +1,23 @@ -use boa_macros::utf16; - use crate::{ builtins::{ options::RoundingMode, temporal::{self, create_temporal_date, options::TemporalUnit, to_temporal_date}, }, - js_string, Context, JsNativeError, JsObject, JsResult, JsValue, + js_string, + string::utf16, + Context, JsNativeError, JsObject, JsResult, JsValue, }; use super::super::{calendar, to_integer_if_integral, zoned_date_time}; // ==== `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, @@ -75,6 +81,12 @@ impl Iterator for DateIter<'_> { // ==== `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, @@ -153,7 +165,8 @@ impl Iterator for TimeIter<'_> { // ==== `DurationRecord` ==== -/// The `DurationRecord` is defined by Abtract Operation 7.5.1 `DurationRecords` +/// 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, @@ -298,7 +311,7 @@ impl DurationRecord { self.set_minutes(self.minutes() % 60_f64); } - // Balance/bubble the current unit from one step down. + /// 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()); @@ -306,7 +319,7 @@ impl DurationRecord { self.set_seconds(self.seconds() % 60_f64); } - // Balance/bubble the current unit from one step down. + /// 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()); @@ -314,7 +327,7 @@ impl DurationRecord { self.set_milliseconds(self.milliseconds() % 1_000_f64); } - // Balance/bubble the current unit from one step down. + /// 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()); @@ -322,7 +335,7 @@ impl DurationRecord { self.set_microseconds(self.microseconds() % 1_000_f64); } - // Balance/bubble the current unit from one step down. + /// 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()); @@ -528,10 +541,10 @@ impl DurationRecord { self.time.into_iter().collect() } - /// Determines if the `DurationRecord` has overflowed. // 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() @@ -700,11 +713,8 @@ impl DurationRecord { } // 6. Set hours, minutes, seconds, milliseconds, and microseconds to 0. - self.set_hours(0_f64); - self.set_minutes(0_f64); - self.set_seconds(0_f64); - self.set_milliseconds(0_f64); - self.set_microseconds(0_f64); + 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 { @@ -1727,7 +1737,7 @@ impl DurationRecord { // d. Set remainder to remainder - nanoseconds. remainder - self.nanoseconds() } - _ => unreachable!(), + TemporalUnit::Auto => unreachable!(), }; // 19. Assert: days is an integer. diff --git a/boa_engine/src/builtins/temporal/instant/mod.rs b/boa_engine/src/builtins/temporal/instant/mod.rs index ae183a9a981..dd3c7efcb02 100644 --- a/boa_engine/src/builtins/temporal/instant/mod.rs +++ b/boa_engine/src/builtins/temporal/instant/mod.rs @@ -1,5 +1,5 @@ +//! Boa's implementation of ECMAScript's `Temporal.Instant` builtin object. #![allow(dead_code)] -//! Boa's implementation of ECMAScript's `Temporal.Instant` object. use crate::{ builtins::{ @@ -22,7 +22,7 @@ use crate::{ }; use boa_profiler::Profiler; -use super::{duration, ns_max_instant, ns_min_instant, MICRO_PER_DAY, MILLI_PER_DAY, NS_PER_DAY}; +use super::{duration, 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; @@ -374,11 +374,18 @@ impl Instant { &round_to, utf16!("smallestUnit"), TemporalUnitGroup::Time, + true, None, None, context, )?; + let Some(smallest_unit) = smallest_unit else { + return Err(JsNativeError::range() + .with_message("smallestUnit cannot be undefined.") + .into()); + }; + let maximum = match smallest_unit { // 10. If smallestUnit is "hour"), then // a. Let maximum be HoursPerDay. @@ -391,10 +398,10 @@ impl Instant { TemporalUnit::Second => 86400, // 13. Else if smallestUnit is "millisecond"), then // a. Let maximum be ℝ(msPerDay). - TemporalUnit::Millisecond => MILLI_PER_DAY, + TemporalUnit::Millisecond => i64::from(MS_PER_DAY), // 14. Else if smallestUnit is "microsecond"), then - // a. Let maximum be 103 × ℝ(msPerDay). - TemporalUnit::Microsecond => MICRO_PER_DAY, + // a. Let maximum be 10^3 × ℝ(msPerDay). + TemporalUnit::Microsecond => MIS_PER_DAY, // 15. Else, // a. Assert: smallestUnit is "nanosecond". // b. Let maximum be nsPerDay. diff --git a/boa_engine/src/builtins/temporal/mod.rs b/boa_engine/src/builtins/temporal/mod.rs index e1093d477d0..e73d80b431c 100644 --- a/boa_engine/src/builtins/temporal/mod.rs +++ b/boa_engine/src/builtins/temporal/mod.rs @@ -53,9 +53,9 @@ use boa_profiler::Profiler; /// Nanoseconds per day constant: 8.64e+13 pub(crate) const NS_PER_DAY: i64 = 86_400_000_000_000; /// Microseconds per day constant: 8.64e+10 -pub(crate) const MICRO_PER_DAY: i64 = 8_640_000_000; +pub(crate) const MIS_PER_DAY: i64 = 8_640_000_000; /// Milliseconds per day constant: 8.64e+7 -pub(crate) const MILLI_PER_DAY: i64 = 24 * 60 * 60 * 1000; +pub(crate) const MS_PER_DAY: i32 = 24 * 60 * 60 * 1000; pub(crate) fn ns_max_instant() -> JsBigInt { JsBigInt::from(i128::from(NS_PER_DAY) * 100_000_000_i128) @@ -190,7 +190,9 @@ fn to_zero_padded_decimal_string(n: u64, min_length: usize) -> String { format!("{n:0min_length$}") } -// TODO: 13.1 `IteratorToListOfType` +/// Abstract Operation 13.1 [`IteratorToListOfType`][proposal] +/// +/// [proposal]: https://tc39.es/proposal-temporal/#sec-iteratortolistoftype pub(crate) fn iterator_to_list_of_types( iterator: &mut IteratorRecord, element_types: &[Type], @@ -227,123 +229,19 @@ pub(crate) fn iterator_to_list_of_types( /// 13.2 `ISODateToEpochDays ( year, month, date )` // Note: implemented on IsoDateRecord. -// TODO: 13.3 `EpochDaysToEpochMs` +// Abstract Operation 13.3 `EpochDaysToEpochMs` pub(crate) fn epoch_days_to_epoch_ms(day: i32, time: i32) -> f64 { - f64::from(day).mul_add(MILLI_PER_DAY as f64, f64::from(time)) -} - -// TODO: 13.4 Date Equations -> See ./date_equations.rs - -/* -/// Abstract Operation 13.5 `GetOptionsObject ( options )` -#[inline] -pub(crate) fn get_options_object(options: &JsValue) -> JsResult { - // 1. If options is undefined, then - if options.is_undefined() { - // a. Return OrdinaryObjectCreate(null). - return Ok(JsObject::with_null_proto()); - // 2. If Type(options) is Object, then - } else if options.is_object() { - // a. Return options. - return Ok(options - .as_object() - .expect("options is confirmed as an object.") - .clone()); - } - // 3. Throw a TypeError exception. - Err(JsNativeError::typ() - .with_message("options value was not an object.") - .into()) + f64::from(day).mul_add(f64::from(MS_PER_DAY), f64::from(time)) } -/// ---- `CopyOptions ( options )` REMOVED - -#[inline] -pub(crate) fn copy_options(options: &JsValue, context: &mut Context<'_>) -> JsResult { - // 1. Let optionsCopy be OrdinaryObjectCreate(null). - let options_copy = JsObject::with_null_proto(); - // 2. Perform ? CopyDataProperties(optionsCopy, ? GetOptionsObject(options), « »). - let option_object = get_options_object(options)?; - let excluded_keys: Vec = Vec::new(); - options_copy.copy_data_properties(&option_object.into(), excluded_keys, context)?; - // 3. Return optionsCopy. - Ok(options_copy) -} - -#[derive(Debug, Clone, Copy)] -pub(crate) enum OptionType { - String, - Bool, - Number, -} - -/// 13.6 `GetOption ( options, property, type, values, default )` -#[inline] -pub(crate) fn get_option( - options: &JsObject, - property: PropertyKey, - r#type: OptionType, - values: Option<&[JsString]>, - default: Option<&JsValue>, - context: &mut Context<'_>, -) -> JsResult { - // 1. Let value be ? Get(options, property). - let initial_value = options.get(property, context)?; - - // 2. If value is undefined, then - if initial_value.is_undefined() { - match default { - // a. If default is required, throw a RangeError exception. - None => { - return Err(JsNativeError::range() - .with_message("options object is required.") - .into()) - } - // b. Return default. - Some(option_value) => return Ok(option_value.clone()), - } - } - - let value: JsValue = match r#type { - // 3. If type is "boolean", then - OptionType::Bool => { - // a. Set value to ToBoolean(value). - initial_value.to_boolean().into() - } - // 4. Else if type is "number", then - OptionType::Number => { - // a. Set value to ? ToNumber(value). - let value = initial_value.to_number(context)?; - // b. If value is NaN, throw a RangeError exception. - if value.is_nan() { - return Err(JsNativeError::range() - .with_message("option value is NaN") - .into()); - }; - - value.into() - } - // 5. Else, - // a. Assert: type is "string". - OptionType::String => { - // b. Set value to ? ToString(value). - initial_value.to_string(context)?.into() - } - }; +// 13.4 Date Equations +// implemented in temporal/date_equations.rs - // 6. If values is not undefined and values does not contain an element equal to value, throw a RangeError exception. - // NOTE: per spec, values is only provided/defined in string cases, so the below should be correct. - if let (Some(vals), Some(value_as_string)) = (values, value.as_string()) { - if !vals.contains(value_as_string) { - return Err(JsNativeError::range() - .with_message("Option value is not in the provided options.") - .into()); - } - } +// Abstract Operation 13.5 `GetOptionsObject ( options )` +// Implemented in builtin/options.rs - // 7. Return value. - Ok(value) -} -*/ +// 13.6 `GetOption ( options, property, type, values, default )` +// Implemented in builtin/options.rs /// 13.7 `ToTemporalOverflow (options)` // Now implemented in temporal/options.rs @@ -355,6 +253,7 @@ pub(crate) fn get_option( // Now implemented in builtin/options.rs // 13.16 `ToTemporalRoundingIncrement ( normalizedOptions )` +// Now implemented in temporal/options.rs /// 13.17 `ValidateTemporalRoundingIncrement ( increment, dividend, inclusive )` #[inline] @@ -392,160 +291,18 @@ pub(crate) fn validate_temporal_rounding_increment( Ok(()) } -/* -/// Abstract operation 13.20 `GetTemporalUnit ( normalizedOptions, key, unitGroup, default [ , extraValues ] )` -#[inline] -pub(crate) fn get_temporal_unit( - normalized_options: &JsObject, - key: PropertyKey, - unit_group: &JsString, // JsString or temporal - default: Option<&JsValue>, // Must be required (none), undefined, or JsString. - extra_values: Option>, // Vec - context: &mut Context<'_>, -) -> JsResult> { - // 1. Let singularNames be a new empty List. - let temporal_units = TemporalUnits::default(); - // 2. For each row of Table 13, except the header row, in table order, do - // a. Let unit be the value in the Singular column of the row. // b. If the Category column of the row is date and unitGroup is date or datetime, append unit to singularNames. - // c. Else if the Category column of the row is time and unitGroup is time or datetime, append unit to singularNames. - let mut singular_names = if unit_group.as_slice() == utf16!("date") { - temporal_units.date_singulars() - } else if unit_group.as_slice() == utf16!("time") { - temporal_units.time_singulars() - } else { - temporal_units.datetime_singulars() - }; - // 3. If extraValues is present, then - // a. Set singularNames to the list-concatenation of singularNames and extraValues. - if let Some(values) = extra_values { - singular_names.extend(values); - } - // 4. If default is required, then - // a. Let defaultValue be undefined. - // 5. Else, - // a. Let defaultValue be default. - // b. If defaultValue is not undefined and singularNames does not contain defaultValue, then - // i. Append defaultValue to singularNames. - let default_value = if let Some(value) = default { - // NOTE: singular name must be either undefined or a JsString, any other value is an implementation error. - if !value.is_undefined() { - if let Some(value_string) = value.as_string() { - if singular_names.contains(value_string) { - singular_names.push(value_string.clone()); - } - } - } - Some(value) - } else { - None - }; - - // 6. Let allowedValues be a copy of singularNames. - // 7. For each element singularName of singularNames, do - // a. If singularName is listed in the Singular column of Table 13, then - // i. Let pluralName be the value in the Plural column of the corresponding row. - // ii. Append pluralName to allowedValues. - // 8. NOTE: For each singular Temporal unit name that is contained within allowedValues, the - // corresponding plural name is also contained within it. - temporal_units.append_plural_units(&mut singular_names); - - // 9. Let value be ? GetOption(normalizedOptions, key, "string", allowedValues, defaultValue). - let value = get_option( - normalized_options, - key, - OptionType::String, - Some(&singular_names), - default_value, - context, - )?; - - // 10. If value is undefined and default is required, throw a RangeError exception. - if value.is_undefined() && default.is_none() { - return Err(JsNativeError::range() - .with_message("option cannot be undefined when required.") - .into()); - } - - // 11. If value is listed in the Plural column of Table 13, then - // a. Set value to the value in the Singular column of the corresponding row. - // 12. Return value. - match value { - JsValue::String(lookup_value) => Ok(Some(temporal_units.plural_lookup(&lookup_value))), - JsValue::Undefined => Ok(None), - // TODO: verify that this is correct to specification, i.e. is it possible for default value to exist and value to be undefined? - _ => unreachable!("The value returned from getTemporalUnit must be a string or undefined"), - } -} -*/ - /// 13.21 `ToRelativeTemporalObject ( options )` pub(crate) fn to_relative_temporal_object( - options: &JsObject, - context: &mut Context<'_>, + _options: &JsObject, + _context: &mut Context<'_>, ) -> JsResult { - // 1. Assert: Type(options) is Object. - // 2. Let value be ? Get(options, "relativeTo"). - let value = options.get(js_string!("relativeTo"), context)?; - // 3. If value is undefined, then - if value.is_undefined() { - // a. Return value. - return Ok(value); - } - // 4. Let offsetBehaviour be option. - // 5. Let matchBehaviour be match exactly. - // 6. If Type(value) is Object, then - // a. If value has either an [[InitializedTemporalDate]] or [[InitializedTemporalZonedDateTime]] internal slot, then - // i. Return value. - // b. If value has an [[InitializedTemporalDateTime]] internal slot, then - // i. Return ! CreateTemporalDate(value.[[ISOYear]], value.[[ISOMonth]], value.[[ISODay]], value.[[Calendar]]). - // c. Let calendar be ? GetTemporalCalendarSlotValueWithISODefault(value). - // d. Let fieldNames be ? CalendarFields(calendar, « "day", "hour", "microsecond", "millisecond", "minute", "month", "monthCode", "nanosecond", "second", "year" »). - // e. Append "timeZone" to fieldNames. - // f. Append "offset" to fieldNames. - // g. Let fields be ? PrepareTemporalFields(value, fieldNames, «»). - // h. Let dateOptions be OrdinaryObjectCreate(null). - // i. Perform ! CreateDataPropertyOrThrow(dateOptions, "overflow", "constrain"). - // j. Let result be ? InterpretTemporalDateTimeFields(calendar, fields, dateOptions). - // k. Let offsetString be ! Get(fields, "offset"). - // l. Let timeZone be ! Get(fields, "timeZone"). - // m. If timeZone is not undefined, then - // i. Set timeZone to ? ToTemporalTimeZoneSlotValue(timeZone). - // n. If offsetString is undefined, then - // i. Set offsetBehaviour to wall. - // 7. Else, - // a. Let string be ? ToString(value). - // b. Let result be ? ParseTemporalRelativeToString(string). - // c. Let offsetString be result.[[TimeZone]].[[OffsetString]]. - // d. Let timeZoneName be result.[[TimeZone]].[[Name]]. - // e. If timeZoneName is undefined, then - // i. Let timeZone be undefined. - // f. Else, - // i. Let timeZone be ? ToTemporalTimeZoneSlotValue(timeZoneName). - // ii. If result.[[TimeZone]].[[Z]] is true, then - // 1. Set offsetBehaviour to exact. - // iii. Else if offsetString is undefined, then - // 1. Set offsetBehaviour to wall. - // iv. Set matchBehaviour to match minutes. - // g. Let calendar be result.[[Calendar]]. - // h. If calendar is undefined, set calendar to "iso8601". - // i. If IsBuiltinCalendar(calendar) is false, throw a RangeError exception. - // j. Set calendar to the ASCII-lowercase of calendar. - // 8. If timeZone is undefined, then - // a. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], calendar). - // 9. If offsetBehaviour is option, then - // a. If IsTimeZoneOffsetString(offsetString) is false, throw a RangeError exception. - // b. Let offsetNs be ParseTimeZoneOffsetString(offsetString). - // 10. Else, - // a. Let offsetNs be 0. - // 11. Let epochNanoseconds be ? InterpretISODateTimeOffset(result.[[Year]], result.[[Month]], result.[[Day]], result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]], offsetBehaviour, offsetNs, timeZone, "compatible", "reject", matchBehaviour). - // 12. Return ! CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar). Err(JsNativeError::range() .with_message("not yet implemented.") .into()) } // 13.22 `LargerOfTwoTemporalUnits ( u1, u2 )` -// core::cmp::max +// use core::cmp::max // 13.23 `MaximumTemporalDurationRoundingIncrement ( unit )` // Implemented on TemporalUnit in temporal/options.rs @@ -729,7 +486,7 @@ pub(crate) fn to_integer_if_integral(arg: &JsValue, context: &mut Context<'_>) - // 13.46 `PrepareTemporalFields ( fields, fieldNames, requiredFields [ , duplicateBehaviour ] )` // See fields.rs -// IMPLEMENTATION NOTE: op -> true == until | false == since +// NOTE: op -> true == until | false == since /// 13.47 `GetDifferenceSettings ( operation, options, unitGroup, disallowedUnits, fallbackSmallestUnit, smallestLargestDefaultUnit )` #[inline] pub(crate) fn get_diff_settings( @@ -743,15 +500,22 @@ pub(crate) fn get_diff_settings( ) -> JsResult<(TemporalUnit, TemporalUnit, RoundingMode, f64)> { // 1. NOTE: The following steps read options and perform independent validation in alphabetical order (ToTemporalRoundingIncrement reads "roundingIncrement" and ToTemporalRoundingMode reads "roundingMode"). // 2. Let largestUnit be ? GetTemporalUnit(options, "largestUnit", unitGroup, "auto"). - let mut largest_unit = get_temporal_unit( + let largest_unit = get_temporal_unit( options, utf16!("largestUnit"), unit_group, + false, Some(TemporalUnit::Auto), None, context, )?; + let Some(mut largest_unit) = largest_unit else { + return Err(JsNativeError::range() + .with_message("largestUnit cannot be undefined in this context.") + .into()); + }; + // 3. If disallowedUnits contains largestUnit, throw a RangeError exception. if disallowed_units.contains(&largest_unit) { return Err(JsNativeError::range() @@ -777,11 +541,18 @@ pub(crate) fn get_diff_settings( options, utf16!("smallestUnit"), unit_group, + false, Some(fallback_smallest_unit), None, context, )?; + let Some(smallest_unit) = smallest_unit else { + return Err(JsNativeError::range() + .with_message("smallestUnit cannot be undefined in this context.") + .into()); + }; + // 8. If disallowedUnits contains smallestUnit, throw a RangeError exception. if disallowed_units.contains(&smallest_unit) { return Err(JsNativeError::range() @@ -809,7 +580,7 @@ pub(crate) fn get_diff_settings( // 13. If maximum is not undefined, perform ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false). if let Some(max) = maximum { - validate_temporal_rounding_increment(rounding_increment, max, false)?; + validate_temporal_rounding_increment(rounding_increment, f64::from(max), false)?; } // 14. Return the Record { [[SmallestUnit]]: smallestUnit, [[LargestUnit]]: largestUnit, [[RoundingMode]]: roundingMode, [[RoundingIncrement]]: roundingIncrement, }. @@ -821,6 +592,7 @@ pub(crate) fn get_diff_settings( )) } +// NOTE: used for MergeFields methods. Potentially can be omitted in favor of `TemporalFields`. /// 14.6 `CopyDataProperties ( target, source, excludedKeys [ , excludedValues ] )` pub(crate) fn copy_data_properties( target: &JsObject, diff --git a/boa_engine/src/builtins/temporal/options.rs b/boa_engine/src/builtins/temporal/options.rs index c39e1836fe7..2b144d008db 100644 --- a/boa_engine/src/builtins/temporal/options.rs +++ b/boa_engine/src/builtins/temporal/options.rs @@ -40,8 +40,8 @@ pub(crate) fn get_temporal_rounding_increment( // 3. Let integerIncrement be truncate(ℝ(increment)). let integer_increment = increment.trunc(); - // 4. If integerIncrement < 1 or integerIncrement > 109, throw a RangeError exception. - if (1.0..=109.0).contains(&integer_increment) { + // 4. If integerIncrement < 1 or integerIncrement > 10^9, throw a RangeError exception. + if (1.0..=1_000_000_000.0).contains(&integer_increment) { return Err(JsNativeError::range() .with_message("rounding increment was out of range.") .into()); @@ -57,26 +57,26 @@ pub(crate) fn get_temporal_unit( options: &JsObject, key: &[u16], unit_group: TemporalUnitGroup, - required: Option, + required: bool, + default: Option, extra_values: Option>, context: &mut Context<'_>, -) -> JsResult { +) -> JsResult> { let extra = extra_values.unwrap_or_default(); let mut unit_values = unit_group.group(); unit_values.extend(extra); - let (required, default) = if let Some(unit) = required { - (false, unit) - } else { - // Note: using auto here should be fine as the default value will not be used. - (true, TemporalUnit::Auto) - }; + let unit = get_option::(options, key, required, context)?.map_or(default, Some); - let unit = get_option::(options, key, required, context)?.unwrap_or(default); - - if !unit_values.contains(&unit) { + if let Some(u) = &unit { + if !unit_values.contains(u) { + return Err(JsNativeError::range() + .with_message("TemporalUnit was not part of the valid UnitGroup.") + .into()); + } + } else if unit.is_none() && required { return Err(JsNativeError::range() - .with_message("TemporalUnit was not part of the valid UnitGroup.") + .with_message("TemporalUnit cannot be undefined when required.") .into()); } @@ -131,24 +131,24 @@ fn datetime_units() -> impl Iterator { } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub(crate) enum TemporalUnit { - Year, - Month, - Week, - Day, - Hour, - Minute, - Second, - Millisecond, - Microsecond, + Auto = 0, Nanosecond, - Auto, - Undefined, + Microsecond, + Millisecond, + Second, + Minute, + Hour, + Day, + Week, + Month, + Year, } impl TemporalUnit { - pub(crate) fn to_maximum_rounding_increment(self) -> Option { + pub(crate) fn to_maximum_rounding_increment(self) -> Option { use TemporalUnit::{ - Day, Hour, Microsecond, Millisecond, Minute, Month, Nanosecond, Second, Week, Year, + Auto, Day, Hour, Microsecond, Millisecond, Minute, Month, Nanosecond, Second, Week, + Year, }; // 1. If unit is "year", "month", "week", or "day", then // a. Return undefined. @@ -160,22 +160,12 @@ impl TemporalUnit { // 5. Return 1000. match self { Year | Month | Week | Day => None, - Hour => Some(24.0), - Minute | Second => Some(60.0), - Millisecond | Microsecond | Nanosecond => Some(1000.0), - _ => unreachable!(), + Hour => Some(24), + Minute | Second => Some(60), + Millisecond | Microsecond | Nanosecond => Some(1000), + Auto => unreachable!(), } } - - /// Returns if value of unit is "Undefined". - pub(crate) fn is_undefined(self) -> bool { - self == Self::Undefined - } - - /// Returns if value of enum is "auto" - pub(crate) fn is_auto(self) -> bool { - self == Self::Auto - } } #[derive(Debug)] @@ -203,7 +193,6 @@ impl FromStr for TemporalUnit { "millisecond" | "milliseconds" => Ok(Self::Millisecond), "microsecond" | "microseconds" => Ok(Self::Microsecond), "nanosecond" | "nanoseconds" => Ok(Self::Nanosecond), - // Note: undefined is an implementation value. It would be an error to parse undefined. _ => Err(ParseTemporalUnitError), } } @@ -214,6 +203,7 @@ 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", @@ -224,8 +214,6 @@ impl fmt::Display for TemporalUnit { Self::Millisecond => "millsecond", Self::Microsecond => "microsecond", Self::Nanosecond => "nanosecond", - Self::Auto => "auto", - Self::Undefined => "undefined", } .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 05a6b57bc1d..c85dd1f7cd8 100644 --- a/boa_engine/src/builtins/temporal/plain_date/iso.rs +++ b/boa_engine/src/builtins/temporal/plain_date/iso.rs @@ -1,3 +1,5 @@ +//! An `IsoDateRecord` that represents the `[[ISOYear]]`, `[[ISOMonth]]`, and `[[ISODay]]` internal slots. + use crate::{ builtins::temporal::{self, TemporalFields}, JsNativeError, JsResult, JsString, @@ -7,11 +9,13 @@ use icu_calendar::{Date, Iso}; // TODO: Move ISODateRecord to a more generalized location. -// TODO: shift month and day to u8's to better align with `ICU4x`. +// TODO: Determine whether month/day should be u8 or i32. -/// `IsoDateRecord` serves as an inner Record for the `Temporal.PlainDate` -/// object, the `Temporal.YearMonth` object, and the `Temporal.MonthDay` -/// object. +/// `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, @@ -92,6 +96,7 @@ impl IsoDateRecord { ) } + /// Create a Month-Day record from a `TemporalFields` object. pub(crate) fn month_day_from_temporal_fields( fields: &TemporalFields, overflow: &JsString, diff --git a/boa_engine/src/builtins/temporal/plain_date/mod.rs b/boa_engine/src/builtins/temporal/plain_date/mod.rs index 88748716074..c7f20828244 100644 --- a/boa_engine/src/builtins/temporal/plain_date/mod.rs +++ b/boa_engine/src/builtins/temporal/plain_date/mod.rs @@ -1,3 +1,4 @@ +//! Boa's implementation of the ECMAScript `Temporal.PlainDate` builtin object. #![allow(dead_code, unused_variables)] use crate::{ 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 d12f55cd074..e3e5bf938f1 100644 --- a/boa_engine/src/builtins/temporal/plain_date_time/mod.rs +++ b/boa_engine/src/builtins/temporal/plain_date_time/mod.rs @@ -1,4 +1,6 @@ +//! Boa's implementation of the ECMAScript `Temporal.PlainDateTime` builtin object. #![allow(dead_code, unused_variables)] + use crate::{ builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, 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 ac26a522f40..a970fc26f23 100644 --- a/boa_engine/src/builtins/temporal/plain_month_day/mod.rs +++ b/boa_engine/src/builtins/temporal/plain_month_day/mod.rs @@ -1,3 +1,4 @@ +//! Boa's implementation of the ECMAScript `Temporal.PlainMonthDay` builtin object. #![allow(dead_code, unused_variables)] use crate::{ builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, diff --git a/boa_engine/src/builtins/temporal/plain_time/mod.rs b/boa_engine/src/builtins/temporal/plain_time/mod.rs index 88fd5eeb176..245fbbd0084 100644 --- a/boa_engine/src/builtins/temporal/plain_time/mod.rs +++ b/boa_engine/src/builtins/temporal/plain_time/mod.rs @@ -1,4 +1,6 @@ +//! Boa's implementation of the ECMAScript `Temporal.PlainTime` builtin object. #![allow(dead_code, unused_variables)] + use crate::{ builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, diff --git a/boa_engine/src/builtins/temporal/tests.rs b/boa_engine/src/builtins/temporal/tests.rs index 7400eb98f80..154946d7c69 100644 --- a/boa_engine/src/builtins/temporal/tests.rs +++ b/boa_engine/src/builtins/temporal/tests.rs @@ -1,5 +1,8 @@ +use super::date_equations::epoch_time_to_month_in_year; use crate::{js_string, run_test_actions, JsValue, TestAction}; +// Temporal Object tests. + #[test] fn temporal_object() { // Temporal Builtin tests. @@ -29,3 +32,15 @@ fn now_object() { TestAction::assert_eq("Temporal.Now.prototype", JsValue::undefined()), ]); } + +// Date Equations + +#[test] +fn time_to_month() { + let milliseconds = [1696459917000_f64]; + + for test_epochs in milliseconds { + println!("{}", epoch_time_to_month_in_year(test_epochs)); + assert_eq!(epoch_time_to_month_in_year(test_epochs), 9); + } +} diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs index 8651af7dd77..4d050cc7437 100644 --- a/boa_engine/src/object/mod.rs +++ b/boa_engine/src/object/mod.rs @@ -462,7 +462,7 @@ pub enum ObjectKind { /// The `Temporal.TimeZone` object kind. #[cfg(feature = "experimental")] - TimeZone(Box), + TimeZone(TimeZone), /// The `Temporal.Duration` object kind. #[cfg(feature = "experimental")] @@ -474,7 +474,7 @@ pub enum ObjectKind { /// The `Temporal.Calendar` object kind. #[cfg(feature = "experimental")] - Calendar(Box), + Calendar(Calendar), } unsafe impl Trace for ObjectKind { @@ -1037,7 +1037,7 @@ impl ObjectData { #[must_use] pub fn time_zone(time_zone: TimeZone) -> Self { Self { - kind: ObjectKind::TimeZone(Box::new(time_zone)), + kind: ObjectKind::TimeZone(time_zone), internal_methods: &ORDINARY_INTERNAL_METHODS, } } @@ -1067,7 +1067,7 @@ impl ObjectData { #[must_use] pub fn calendar(calendar: Calendar) -> Self { Self { - kind: ObjectKind::Calendar(Box::new(calendar)), + kind: ObjectKind::Calendar(calendar), internal_methods: &ORDINARY_INTERNAL_METHODS, } } @@ -2075,7 +2075,7 @@ impl Object { #[inline] #[must_use] #[cfg(feature = "experimental")] - pub fn as_time_zone(&self) -> Option<&TimeZone> { + pub const fn as_time_zone(&self) -> Option<&TimeZone> { match self.kind { ObjectKind::TimeZone(ref tz) => Some(tz), _ => None, @@ -2282,7 +2282,7 @@ impl Object { #[inline] #[must_use] #[cfg(feature = "experimental")] - pub fn as_calendar(&self) -> Option<&Calendar> { + pub const fn as_calendar(&self) -> Option<&Calendar> { match &self.kind { ObjectKind::Calendar(calendar) => Some(calendar), _ => None, diff --git a/boa_parser/src/temporal/mod.rs b/boa_parser/src/temporal/mod.rs index fdcf7243240..c9fc55d448e 100644 --- a/boa_parser/src/temporal/mod.rs +++ b/boa_parser/src/temporal/mod.rs @@ -1,4 +1,4 @@ -//! Implementation of ISO8601 grammar lexing/parsing +//! Implementation of Iso8601 grammar lexing/parsing use crate::error::ParseResult; @@ -9,7 +9,7 @@ mod grammar; mod time; mod time_zone; -use boa_ast::temporal::{ISODate, ISODateTime, ISODuration, ISOTime, TimeZone}; +use boa_ast::temporal::{IsoDate, IsoDateTime, IsoDuration, IsoTime, TimeZone}; use date_time::DateRecord; use time::TimeSpec; @@ -20,9 +20,9 @@ mod tests; // TODO: optimize where possible. -/// An `ISOParseRecord` is an intermediary record returned by ISO parsing functions. +/// An `IsoParseRecord` is an intermediary record returned by ISO parsing functions. /// -/// `ISOParseRecord` is converted into the ISO AST Nodes. +/// `IsoParseRecord` is converted into the ISO AST Nodes. #[derive(Default, Debug)] pub(crate) struct IsoParseRecord { /// Parsed Date Record @@ -47,22 +47,22 @@ impl TemporalDateTimeString { /// # Errors /// /// The parse will error if the provided target is not valid - /// ISO8601 grammar. - pub fn parse(zoned: bool, cursor: &mut IsoCursor) -> ParseResult { + /// Iso8601 grammar. + pub fn parse(zoned: bool, cursor: &mut IsoCursor) -> ParseResult { let parse_record = date_time::parse_annotated_date_time(zoned, false, false, cursor)?; - let date = ISODate { + let date = IsoDate { year: parse_record.date.year, month: parse_record.date.month, day: parse_record.date.day, calendar: parse_record.calendar, }; - let time = parse_record.time.map_or_else(ISOTime::default, |time| { - ISOTime::from_components(time.hour, time.minute, time.second, time.fraction) + let time = parse_record.time.map_or_else(IsoTime::default, |time| { + IsoTime::from_components(time.hour, time.minute, time.second, time.fraction) }); - Ok(ISODateTime { + Ok(IsoDateTime { date, time, tz: parse_record.tz, @@ -82,7 +82,7 @@ impl TemporalTimeZoneString { /// # Errors /// /// The parse will error if the provided target is not valid - /// ISO8601 grammar. + /// Iso8601 grammar. pub fn parse(cursor: &mut IsoCursor) -> ParseResult { time_zone::parse_time_zone(cursor) } @@ -100,8 +100,8 @@ impl TemporalYearMonthString { /// # Errors /// /// The parse will error if the provided target is not valid - /// ISO8601 grammar. - pub fn parse(cursor: &mut IsoCursor) -> ParseResult { + /// Iso8601 grammar. + pub fn parse(cursor: &mut IsoCursor) -> ParseResult { if date_time::peek_year_month(cursor)? { let ym = date_time::parse_year_month(cursor)?; @@ -112,7 +112,7 @@ impl TemporalYearMonthString { None }; - return Ok(ISODate { + return Ok(IsoDate { year: ym.0, month: ym.1, day: 0, @@ -122,7 +122,7 @@ impl TemporalYearMonthString { let parse_record = date_time::parse_annotated_date_time(false, false, false, cursor)?; - Ok(ISODate { + Ok(IsoDate { year: parse_record.date.year, month: parse_record.date.month, day: parse_record.date.day, @@ -143,8 +143,8 @@ impl TemporalMonthDayString { /// # Errors /// /// The parse will error if the provided target is not valid - /// ISO8601 grammar. - pub fn parse(cursor: &mut IsoCursor) -> ParseResult { + /// Iso8601 grammar. + pub fn parse(cursor: &mut IsoCursor) -> ParseResult { if date_time::peek_month_day(cursor)? { let md = date_time::parse_month_day(cursor)?; @@ -155,7 +155,7 @@ impl TemporalMonthDayString { None }; - return Ok(ISODate { + return Ok(IsoDate { year: 0, month: md.0, day: md.1, @@ -165,7 +165,7 @@ impl TemporalMonthDayString { let parse_record = date_time::parse_annotated_date_time(false, false, false, cursor)?; - Ok(ISODate { + Ok(IsoDate { year: parse_record.date.year, month: parse_record.date.month, day: parse_record.date.day, @@ -186,22 +186,22 @@ impl TemporalInstantString { /// # Errors /// /// The parse will error if the provided target is not valid - /// ISO8601 grammar. - pub fn parse(cursor: &mut IsoCursor) -> ParseResult { + /// Iso8601 grammar. + pub fn parse(cursor: &mut IsoCursor) -> ParseResult { let parse_record = date_time::parse_annotated_date_time(false, true, true, cursor)?; - let date = ISODate { + let date = IsoDate { year: parse_record.date.year, month: parse_record.date.month, day: parse_record.date.day, calendar: parse_record.calendar, }; - let time = parse_record.time.map_or_else(ISOTime::default, |time| { - ISOTime::from_components(time.hour, time.minute, time.second, time.fraction) + let time = parse_record.time.map_or_else(IsoTime::default, |time| { + IsoTime::from_components(time.hour, time.minute, time.second, time.fraction) }); - Ok(ISODateTime { + Ok(IsoDateTime { date, time, tz: parse_record.tz, @@ -223,8 +223,8 @@ impl TemporalDurationString { /// # Errors /// /// The parse will error if the provided target is not valid - /// ISO8601 grammar. - pub fn parse(cursor: &mut IsoCursor) -> ParseResult { + /// Iso8601 grammar. + pub fn parse(cursor: &mut IsoCursor) -> ParseResult { let parse_record = duration::parse_duration(cursor)?; let minutes = if parse_record.time.fhours > 0.0 { @@ -252,7 +252,7 @@ impl TemporalDurationString { let sign = if parse_record.sign { 1 } else { -1 }; - Ok(ISODuration { + Ok(IsoDuration { years: parse_record.date.years * sign, months: parse_record.date.months * sign, weeks: parse_record.date.weeks * sign, @@ -267,9 +267,9 @@ impl TemporalDurationString { } } -// ==== Mini cursor implementation for ISO8601 targets ==== +// ==== Mini cursor implementation for Iso8601 targets ==== -/// `IsoCursor` is a small cursor implementation for parsing ISO8601 grammar. +/// `IsoCursor` is a small cursor implementation for parsing Iso8601 grammar. #[derive(Debug)] pub struct IsoCursor { pos: u32,