From d71334e0f53b6d37350bb35026a53c822f1d98df Mon Sep 17 00:00:00 2001 From: Kevin <46825870+nekevss@users.noreply.github.com> Date: Tue, 16 Jan 2024 13:17:07 -0500 Subject: [PATCH] Implement `DifferenceInstant` and related refactor (#3568) * Work on building out instant methods * Needed to rebase --- core/temporal/src/components/date.rs | 16 +- core/temporal/src/components/duration.rs | 1161 +++-------------- core/temporal/src/components/duration/date.rs | 461 +++++++ core/temporal/src/components/duration/time.rs | 545 ++++++++ core/temporal/src/components/instant.rs | 58 +- core/temporal/src/components/mod.rs | 2 +- core/temporal/src/options.rs | 13 + 7 files changed, 1285 insertions(+), 971 deletions(-) create mode 100644 core/temporal/src/components/duration/date.rs create mode 100644 core/temporal/src/components/duration/time.rs diff --git a/core/temporal/src/components/date.rs b/core/temporal/src/components/date.rs index 13c800ba98..b94ff6131d 100644 --- a/core/temporal/src/components/date.rs +++ b/core/temporal/src/components/date.rs @@ -13,6 +13,8 @@ use crate::{ }; use std::{any::Any, str::FromStr}; +use super::duration::TimeDuration; + /// The native Rust implementation of `Temporal.PlainDate`. #[derive(Debug, Default, Clone)] pub struct Date { @@ -156,12 +158,20 @@ impl Date { // 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)?; + let (days, _) = TimeDuration::new_unchecked( + duration.hours(), + duration.minutes(), + duration.seconds(), + duration.milliseconds(), + duration.microseconds(), + duration.nanoseconds(), + ) + .balance(duration.days(), 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)?; + .add_iso_date(&DateDuration::new(0f64, 0f64, 0f64, days)?, overflow)?; Ok(Self::new_unchecked(result, self.calendar().clone())) } @@ -202,7 +212,7 @@ impl Date { 0f64, 0f64, f64::from(days), - ))); + )?)); } self.calendar() diff --git a/core/temporal/src/components/duration.rs b/core/temporal/src/components/duration.rs index cc3d93a1f9..0f0255a1b7 100644 --- a/core/temporal/src/components/duration.rs +++ b/core/temporal/src/components/duration.rs @@ -4,250 +4,19 @@ use crate::{ components::{Date, DateTime, ZonedDateTime}, options::{ArithmeticOverflow, TemporalRoundingMode, TemporalUnit}, parser::{duration::parse_duration, Cursor}, - utils, TemporalError, TemporalResult, NS_PER_DAY, + TemporalError, TemporalResult, }; use std::{any::Any, str::FromStr}; use super::{calendar::CalendarProtocol, tz::TzProtocol}; -// ==== `DateDuration` ==== +mod date; +mod time; -/// `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. - #[must_use] - 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`. - #[must_use] - pub const fn partial() -> Self { - Self { - years: f64::NAN, - months: f64::NAN, - weeks: f64::NAN, - days: f64::NAN, - } - } - - /// 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 - } -} - -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`. - #[must_use] - 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`. - #[must_use] - 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] - #[must_use] - 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. - #[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 - } -} - -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` ==== +#[doc(inline)] +pub use date::DateDuration; +#[doc(inline)] +pub use time::TimeDuration; /// The native Rust implementation of `Temporal.Duration`. /// @@ -280,20 +49,22 @@ impl Duration { /// 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)) + Self::from_date_duration(DateDuration::new_unchecked(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)) + Self::from_date_duration(DateDuration::new_unchecked(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)) + Self::from_date_duration(DateDuration::new_unchecked(0f64, 0f64, week_value, 0f64)) } } +// ==== Public Duration API ==== + impl Duration { /// Creates a new validated `Duration`. #[allow(clippy::too_many_arguments)] @@ -310,8 +81,8 @@ impl Duration { nanoseconds: f64, ) -> TemporalResult { let duration = Self::new_unchecked( - DateDuration::new(years, months, weeks, days), - TimeDuration::new( + DateDuration::new_unchecked(years, months, weeks, days), + TimeDuration::new_unchecked( hours, minutes, seconds, @@ -320,7 +91,7 @@ impl Duration { nanoseconds, ), ); - if !duration.is_valid() { + if !is_valid_duration(&duration.into_iter().collect()) { return Err(TemporalError::range().with_message("Duration was not valid.")); } Ok(duration) @@ -348,9 +119,9 @@ impl Duration { /// /// 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 { + pub fn from_day_and_time(day: f64, time: TimeDuration) -> Self { Self { - date: DateDuration::new(0.0, 0.0, 0.0, day), + date: DateDuration::new_unchecked(0.0, 0.0, 0.0, day), time, } } @@ -361,13 +132,6 @@ impl Duration { pub fn is_time_within_range(&self) -> bool { self.time.is_within_range() } - - /// Return an iterator over the `Duration`'s values. - #[inline] - #[must_use] - pub fn iter(&self) -> DurationIter<'_> { - <&Self as IntoIterator>::into_iter(self) - } } // ==== Public `Duration` Getters/Setters ==== @@ -399,59 +163,135 @@ impl Duration { self.date.years = y; } + /// Returns the `years` field of duration. + #[inline] + #[must_use] + pub const fn years(&self) -> f64 { + self.date.years + } + /// Set the value for `months`. #[inline] pub fn set_months(&mut self, mo: f64) { self.date.months = mo; } + /// Returns the `months` field of duration. + #[inline] + #[must_use] + pub const fn months(&self) -> f64 { + self.date.months + } + /// Set the value for `weeks`. #[inline] pub fn set_weeks(&mut self, w: f64) { self.date.weeks = w; } + /// Returns the `weeks` field of duration. + #[inline] + #[must_use] + pub const fn weeks(&self) -> f64 { + self.date.weeks + } + /// Set the value for `days`. #[inline] pub fn set_days(&mut self, d: f64) { self.date.days = d; } + /// Returns the `weeks` field of duration. + #[inline] + #[must_use] + pub const fn days(&self) -> f64 { + self.date.days + } + /// Set the value for `hours`. #[inline] pub fn set_hours(&mut self, h: f64) { self.time.hours = h; } + /// Returns the `hours` field of duration. + #[inline] + #[must_use] + pub const fn hours(&self) -> f64 { + self.time.hours + } + /// Set the value for `minutes`. #[inline] pub fn set_minutes(&mut self, m: f64) { self.time.minutes = m; } + /// Returns the `hours` field of duration. + #[inline] + #[must_use] + pub const fn minutes(&self) -> f64 { + self.time.minutes + } + /// Set the value for `seconds`. #[inline] pub fn set_seconds(&mut self, s: f64) { self.time.seconds = s; } + /// Returns the `seconds` field of duration. + #[inline] + #[must_use] + pub const fn seconds(&self) -> f64 { + self.time.seconds + } + /// Set the value for `milliseconds`. #[inline] pub fn set_milliseconds(&mut self, ms: f64) { self.time.milliseconds = ms; } + /// Returns the `hours` field of duration. + #[inline] + #[must_use] + pub const fn milliseconds(&self) -> f64 { + self.time.milliseconds + } + /// Set the value for `microseconds`. #[inline] pub fn set_microseconds(&mut self, mis: f64) { self.time.microseconds = mis; } + /// Returns the `microseconds` field of duration. + #[inline] + #[must_use] + pub const fn microseconds(&self) -> f64 { + self.time.microseconds + } + /// Set the value for `nanoseconds`. #[inline] pub fn set_nanoseconds(&mut self, ns: f64) { self.time.nanoseconds = ns; } + + /// Returns the `nanoseconds` field of duration. + #[inline] + #[must_use] + pub const fn nanoseconds(&self) -> f64 { + self.time.nanoseconds + } + + /// Returns `Duration`'s iterator + #[must_use] + pub fn iter(&self) -> DurationIter<'_> { + <&Self as IntoIterator>::into_iter(self) + } } impl<'a> IntoIterator for &'a Duration { @@ -498,212 +338,6 @@ impl Iterator for DurationIter<'_> { // ==== Private Duration methods ==== 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); - values - } - - /// 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.17 `TotalDurationNanoseconds ( days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, offsetShift )` - pub(crate) 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.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 %= 1000f64; - - // c. Set milliseconds to floor(microseconds / 1000). - result.milliseconds = (result.microseconds / 1000f64).floor(); - // d. Set microseconds to microseconds modulo 1000. - 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 %= 1000f64; - - // g. Set minutes to floor(seconds / 60). - result.minutes = (result.seconds / 60f64).floor(); - // h. Set seconds to seconds modulo 60. - 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 %= 60f64; - - // k. Set days to floor(hours / 24). - result_days = (result.hours / 24f64).floor(); - // l. Set hours to hours modulo 24. - 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 %= 1000f64; - - // c. Set milliseconds to floor(microseconds / 1000). - // d. Set microseconds to microseconds modulo 1000. - result.milliseconds = (result.microseconds / 1000f64).floor(); - 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 %= 60f64; - - // g. Set minutes to floor(seconds / 60). - // h. Set seconds to seconds modulo 60. - result.minutes = (result.seconds / 60f64).floor(); - 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 %= 1000f64; - - // c. Set milliseconds to floor(microseconds / 1000). - // d. Set microseconds to microseconds modulo 1000. - result.milliseconds = (result.microseconds / 1000f64).floor(); - 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 %= 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 %= 1000f64; - - // c. Set milliseconds to floor(microseconds / 1000). - // d. Set microseconds to microseconds modulo 1000. - result.milliseconds = (result.microseconds / 1000f64).floor(); - 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 %= 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.") - ); - } - - Ok(Some((result_days, result))) - } - /// 7.5.21 `UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit, plainRelativeTo )` #[allow(dead_code)] pub(crate) fn unbalance_duration_relative( @@ -743,12 +377,7 @@ impl Duration { 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, - )); + return DateDuration::new(0f64, self.date.months, self.date.weeks, self.date.days); } // b. If calendar is undefined, then @@ -798,23 +427,13 @@ impl Duration { months += one_year_months; } // f. Return ? CreateDateDurationRecord(0, months, weeks, days). - return Ok(DateDuration::new( - years, - months, - self.date.weeks, - self.date.days, - )); + return 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, - )); + return DateDuration::new(0f64, 0f64, self.date.weeks, self.date.days); } // b. If calendar is undefined, then @@ -856,12 +475,12 @@ impl Duration { months -= sign; } // g. Return ? CreateDateDurationRecord(0, 0, weeks, days). - return Ok(DateDuration::new(0f64, 0f64, self.date.weeks(), days)); + return 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)); + return 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. @@ -921,7 +540,7 @@ impl Duration { } // 20. Return ? CreateDateDurationRecord(0, 0, 0, days). - Ok(DateDuration::new(0f64, 0f64, 0f64, days)) + DateDuration::new(0f64, 0f64, 0f64, days) } // TODO: Move to DateDuration @@ -1163,7 +782,6 @@ impl Duration { #[allow(clippy::type_complexity)] pub fn round_duration( &self, - unbalance_date_duration: DateDuration, increment: f64, unit: TemporalUnit, rounding_mode: TemporalRoundingMode, @@ -1174,441 +792,39 @@ impl Duration { ), context: &mut dyn Any, ) -> 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 _ = 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")); - } - // 5. If unit is one of "year", "month", "week", or "day", then + match unit { 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.")); - }; - // 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.contextual_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( - 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.contextual_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); - - // 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 ? 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"). - // o. Let timePassed be ? DifferenceDate(calendar, plainRelativeTo, wholeDaysLater, untilOptions). - 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(); - - // 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, 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) = - 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()); - - // 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.contextual_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( - 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.contextual_add_date( - &years_months_weeks, - ArithmeticOverflow::Constrain, + let round_result = self.date().round( + Some(self.time), + increment, + unit, + rounding_mode, + relative_targets, context, )?; - - // 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, context)?; - - // 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, 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.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, context)?; - - // 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, 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.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 + let result = Self::from_date_duration(round_result.0); + Ok((result, round_result.1)) + } + TemporalUnit::Hour + | TemporalUnit::Minute + | TemporalUnit::Second + | TemporalUnit::Millisecond + | TemporalUnit::Microsecond + | TemporalUnit::Nanosecond => { + let round_result = self.time().round(increment, unit, rounding_mode)?; + let result = Self { + date: self.date, + time: round_result.0, + }; + Ok((result, round_result.1)) } - // 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 + TemporalUnit::Auto => { + Err(TemporalError::range().with_message("Invalid TemporalUnit for Duration.round")) } - // 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)) } } @@ -1620,20 +836,8 @@ impl Duration { #[must_use] 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(), - ), + date: self.date().abs(), + time: self.time().abs(), } } @@ -1643,18 +847,7 @@ impl Duration { #[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 { - // 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 + duration_sign(&self.into_iter().collect()) } /// 7.5.12 `DefaultTemporalLargestUnit ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds )` @@ -1662,43 +855,83 @@ impl Duration { #[must_use] 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, - _ => {} - } + if value == 0f64 { + continue; + } + + 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 )` + /// Calls `TimeDuration`'s balance method on the current `Duration`. #[inline] - 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)?; + pub fn balance_time_duration(&self, unit: TemporalUnit) -> TemporalResult<(f64, TimeDuration)> { + TimeDuration::new_unchecked( + self.hours(), + self.minutes(), + self.seconds(), + self.milliseconds(), + self.microseconds(), + self.nanoseconds(), + ) + .balance(self.days(), 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.")); - }; - // 3. Else, - // a. Return balanceResult. - Ok(result) +/// Utility function to check whether the `Duration` fields are valid. +#[inline] +#[must_use] +pub(crate) fn is_valid_duration(set: &Vec) -> bool { + // 1. Let sign be ! DurationSign(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds). + let sign = duration_sign(set); + // 2. For each value v of « years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds », do + for v in set { + // 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 < 0f64 && sign > 0 { + return false; + } + // c. If v > 0 and sign < 0, return false. + if *v > 0f64 && sign < 0 { + return false; + } } + // 3. Return true. + true +} + +/// Utility function for determining the sign for the current set of `Duration` fields. +#[inline] +#[must_use] +fn duration_sign(set: &Vec) -> i32 { + // 1. For each value v of « years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds », do + for v in set { + // a. If v < 0, return -1. + if *v < 0f64 { + return -1; + // b. If v > 0, return 1. + } else if *v > 0f64 { + return 1; + } + } + // 2. Return 0. + 0 } // ==== FromStr trait impl ==== @@ -1740,7 +973,7 @@ impl FromStr for Duration { f64::from(parse_record.date.months) * sign, f64::from(parse_record.date.weeks) * sign, f64::from(parse_record.date.days) * sign, - ), + )?, time: TimeDuration::new( f64::from(parse_record.time.hours) * sign, minutes.floor() * sign, @@ -1748,7 +981,7 @@ impl FromStr for Duration { milliseconds.floor() * sign, micro.floor() * sign, nano.floor() * sign, - ), + )?, }) } } diff --git a/core/temporal/src/components/duration/date.rs b/core/temporal/src/components/duration/date.rs new file mode 100644 index 0000000000..8d16968437 --- /dev/null +++ b/core/temporal/src/components/duration/date.rs @@ -0,0 +1,461 @@ +//! Implementation of a `DateDuration` + +use crate::{ + components::{ + calendar::CalendarProtocol, duration::TimeDuration, tz::TzProtocol, Date, DateTime, + Duration, ZonedDateTime, + }, + options::{ArithmeticOverflow, TemporalRoundingMode, TemporalUnit}, + utils, TemporalError, TemporalResult, NS_PER_DAY, +}; + +use std::any::Any; + +/// `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 { + pub(crate) years: f64, + pub(crate) months: f64, + pub(crate) weeks: f64, + pub(crate) days: f64, +} + +impl DateDuration { + /// Creates a new, non-validated `DateDuration`. + #[inline] + #[must_use] + pub(crate) const fn new_unchecked(years: f64, months: f64, weeks: f64, days: f64) -> Self { + Self { + years, + months, + weeks, + days, + } + } +} + +impl DateDuration { + /// Creates a new `DateDuration` with provided values. + pub fn new(years: f64, months: f64, weeks: f64, days: f64) -> TemporalResult { + let result = Self::new_unchecked(years, months, weeks, days); + if !super::is_valid_duration(&result.into_iter().collect()) { + return Err(TemporalError::range().with_message("Invalid DateDuration.")); + } + Ok(result) + } + + /// Returns a `PartialDateDuration` with all fields set to `NaN`. + #[must_use] + pub const fn partial() -> Self { + Self { + years: f64::NAN, + months: f64::NAN, + weeks: f64::NAN, + days: f64::NAN, + } + } + + /// Returns a new `DateDuration` representing the absolute value of the current. + #[inline] + #[must_use] + pub fn abs(&self) -> Self { + Self { + years: self.years.abs(), + months: self.months.abs(), + weeks: self.weeks.abs(), + days: self.days.abs(), + } + } + + /// 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 + } + + /// Returns the iterator for `DateDuration` + #[must_use] + pub fn iter(&self) -> DateIter<'_> { + <&Self as IntoIterator>::into_iter(self) + } +} + +// ==== DateDuration Operations ==== + +impl DateDuration { + /// Rounds the current `DateDuration` returning a tuple of the rounded `DateDuration` and + /// the `total` value of the smallest unit prior to rounding. + #[allow(clippy::type_complexity, clippy::let_and_return)] + pub fn round( + &self, + additional_time: Option, + increment: f64, + unit: TemporalUnit, + rounding_mode: TemporalRoundingMode, + relative_targets: ( + Option<&Date>, + Option<&ZonedDateTime>, + Option<&DateTime>, + ), + context: &mut dyn Any, + ) -> TemporalResult<(Self, f64)> { + // 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 mut fractional_days = 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")); + } + // 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 nanoseconds = additional_time.unwrap_or_default().as_nanos(); + + // 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. + // d. Set days, hours, minutes, seconds, milliseconds, microseconds, and nanoseconds to 0. + // e. Assert: fractionalSeconds is not used below. + if zoned_relative_to.is_none() { + self.days + nanoseconds / NS_PER_DAY as f64 + } else { + // implementation of b: i-iii needed. + return Err(TemporalError::range().with_message("Not yet implemented.")); + } + } + _ => { + return Err(TemporalError::range() + .with_message("Invalid TemporalUnit provided to DateDuration.round")) + } + }; + + // 7. let total be unset. + // We begin matching against unit and return the remainder value. + match unit { + // 8. If unit is "year", then + 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_unchecked(self.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.contextual_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 = Duration::new_unchecked( + Self::new_unchecked(self.years, self.months, self.weeks, 0.0), + TimeDuration::default(), + ); + + // g. Let yearsMonthsWeeksLater be ? AddDate(calendar, plainRelativeTo, yearsMonthsWeeks, undefined, dateAdd). + let years_months_weeks_later = plain_relative_to.contextual_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); + + // i. Set plainRelativeTo to yearsLater. + let plain_relative_to = years_later; + + // j. Set fractionalDays to fractionalDays + monthsWeeksInDays. + fractional_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_unchecked(0.0, 0.0, 0.0, fractional_days.trunc()), + ArithmeticOverflow::Constrain, + )?; + + // 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"). + // o. Let timePassed be ? DifferenceDate(calendar, plainRelativeTo, wholeDaysLater, untilOptions). + 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(); + + // q. Set years to years + yearsPassed. + let years = self.years() + years_passed; + + // r. Let yearsDuration be ! CreateTemporalDuration(yearsPassed, 0, 0, 0, 0, 0, 0, 0, 0, 0). + let years_duration = Duration::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, context)?; + + // v. Set fractionalDays to fractionalDays - daysPassed. + fractional_days -= days_passed; + + // w. If fractionalDays < 0, let sign be -1; else, let sign be 1. + let sign = if fractional_days < 0.0 { -1 } else { 1 }; + + // x. Let oneYear be ! CreateTemporalDuration(sign, 0, 0, 0, 0, 0, 0, 0, 0, 0). + let one_year = Duration::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, context)?; + + // aa. Let fractionalYears be years + fractionalDays / abs(oneYearDays). + let frac_years = years + (fractional_days / one_year_days.abs()); + + // ab. Set years to RoundNumberToIncrement(fractionalYears, increment, roundingMode). + let rounded_years = + utils::round_number_to_increment(frac_years, increment, rounding_mode); + + // ac. Set total to fractionalYears. + // ad. Set months and weeks to 0. + let result = Self::new(rounded_years, 0f64, 0f64, 0f64)?; + Ok((result, frac_years)) + } + // 9. Else if unit is "month", then + 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 = Duration::from_date_duration(DateDuration::new_unchecked( + self.years(), + self.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.contextual_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 = Duration::from_date_duration(DateDuration::new_unchecked( + self.years(), + self.months(), + self.weeks(), + 0.0, + )); + + // g. Let yearsMonthsWeeksLater be ? AddDate(calendar, plainRelativeTo, yearsMonthsWeeks, undefined, dateAdd). + let years_months_weeks_later = plain_relative_to.contextual_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); + + // i. Set plainRelativeTo to yearsMonthsLater. + let plain_relative_to = years_months_later; + + // j. Set fractionalDays to fractionalDays + weeksInDays. + fractional_days += f64::from(weeks_in_days); + + // k. If fractionalDays < 0, let sign be -1; else, let sign be 1. + let sign = if fractional_days < 0.0 { -1f64 } else { 1f64 }; + + // l. Let oneMonth be ! CreateTemporalDuration(0, sign, 0, 0, 0, 0, 0, 0, 0, 0). + let one_month = Duration::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, context)?; + + let mut months = self.months; + // p. Repeat, while abs(fractionalDays) ≥ abs(oneMonthDays), + while fractional_days.abs() >= one_month_days.abs() { + // i. Set months to months + sign. + months += sign; + + // ii. Set fractionalDays to fractionalDays - oneMonthDays. + fractional_days -= one_month_days; + + // iii. Set moveResult to ? MoveRelativeDate(calendar, plainRelativeTo, oneMonth, dateAdd). + let move_result = plain_relative_to.move_relative_date(&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 = months + fractional_days / one_month_days.abs(); + + // r. Set months to RoundNumberToIncrement(fractionalMonths, increment, roundingMode). + let rounded_months = + utils::round_number_to_increment(frac_months, increment, rounding_mode); + + // s. Set total to fractionalMonths. + // t. Set weeks to 0. + let result = Self::new(self.years, rounded_months, 0f64, 0f64)?; + Ok((result, frac_months)) + } + // 10. Else if unit is "week", then + TemporalUnit::Week => { + // 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 fractional_days < 0.0 { -1f64 } else { 1f64 }; + + // c. Let oneWeek be ! CreateTemporalDuration(0, 0, sign, 0, 0, 0, 0, 0, 0, 0). + let one_week = Duration::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, context)?; + + let mut weeks = self.weeks; + // i. Repeat, while abs(fractionalDays) ≥ abs(oneWeekDays), + while fractional_days.abs() >= one_week_days.abs() { + // i. Set weeks to weeks + sign. + weeks += sign; + + // ii. Set fractionalDays to fractionalDays - oneWeekDays. + fractional_days -= one_week_days; + + // iii. Set moveResult to ? MoveRelativeDate(calendar, plainRelativeTo, oneWeek, dateAdd). + let move_result = plain_relative_to.move_relative_date(&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 = weeks + fractional_days / one_week_days.abs(); + + // k. Set weeks to RoundNumberToIncrement(fractionalWeeks, increment, roundingMode). + let rounded_weeks = + utils::round_number_to_increment(frac_weeks, increment, rounding_mode); + // l. Set total to fractionalWeeks. + let result = Self::new(self.years, self.months, rounded_weeks, 0f64)?; + Ok((result, frac_weeks)) + } + // 11. Else if unit is "day", then + TemporalUnit::Day => { + // a. Set days to RoundNumberToIncrement(fractionalDays, increment, roundingMode). + let rounded_days = + utils::round_number_to_increment(fractional_days, increment, rounding_mode); + // b. Set total to fractionalDays. + let result = Self::new(self.years, self.months, self.weeks, rounded_days)?; + Ok((result, fractional_days)) + } + _ => unreachable!("All other TemporalUnits were returned early as invalid."), + } + } +} + +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 + } +} diff --git a/core/temporal/src/components/duration/time.rs b/core/temporal/src/components/duration/time.rs new file mode 100644 index 0000000000..242a29ce01 --- /dev/null +++ b/core/temporal/src/components/duration/time.rs @@ -0,0 +1,545 @@ +//! An implementation of `TimeDuration` and it's methods. + +use crate::{ + options::{TemporalRoundingMode, TemporalUnit}, + utils, TemporalError, TemporalResult, +}; + +use super::is_valid_duration; + +/// `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 { + pub(crate) hours: f64, + pub(crate) minutes: f64, + pub(crate) seconds: f64, + pub(crate) milliseconds: f64, + pub(crate) microseconds: f64, + pub(crate) nanoseconds: f64, +} +// ==== TimeDuration Private API ==== + +impl TimeDuration { + /// Creates a new `TimeDuration`. + #[must_use] + pub(crate) const fn new_unchecked( + hours: f64, + minutes: f64, + seconds: f64, + milliseconds: f64, + microseconds: f64, + nanoseconds: f64, + ) -> Self { + Self { + hours, + minutes, + seconds, + milliseconds, + microseconds, + nanoseconds, + } + } + + /// Returns the current `TimeDuration` as nanoseconds. + #[inline] + pub(crate) fn as_nanos(&self) -> 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, self.nanoseconds) + } + + /// Abstract Operation 7.5.18 `BalancePossiblyInfiniteDuration ( days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, largestUnit )` + /// + /// This function will balance the current `TimeDuration`. It returns the balanced `day` and `TimeDuration` value. + #[allow(clippy::too_many_arguments)] + pub(crate) fn balance_possibly_infinite_time_duration( + days: f64, + hours: f64, + minutes: f64, + seconds: f64, + milliseconds: f64, + microseconds: f64, + nanoseconds: f64, + largest_unit: TemporalUnit, + ) -> TemporalResult<(f64, Option)> { + // 1. Set hours to hours + days × 24. + let hours = hours + (days * 24f64); + + // 2. Set nanoseconds to TotalDurationNanoseconds(hours, minutes, seconds, milliseconds, microseconds, nanoseconds). + let mut nanoseconds = Self::new_unchecked( + hours, + minutes, + seconds, + milliseconds, + microseconds, + nanoseconds, + ) + .as_nanos(); + + // 3. Set days, hours, minutes, seconds, milliseconds, and microseconds to 0. + let mut days = 0f64; + let mut hours = 0f64; + let mut minutes = 0f64; + let mut seconds = 0f64; + let mut milliseconds = 0f64; + let mut microseconds = 0f64; + + // 4. If nanoseconds < 0, let sign be -1; else, let sign be 1. + let sign = if nanoseconds < 0f64 { -1 } else { 1 }; + // 5. Set nanoseconds to abs(nanoseconds). + nanoseconds = 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). + microseconds = (nanoseconds / 1000f64).floor(); + // b. Set nanoseconds to nanoseconds modulo 1000. + nanoseconds %= 1000f64; + + // c. Set milliseconds to floor(microseconds / 1000). + milliseconds = (microseconds / 1000f64).floor(); + // d. Set microseconds to microseconds modulo 1000. + microseconds %= 1000f64; + + // e. Set seconds to floor(milliseconds / 1000). + seconds = (milliseconds / 1000f64).floor(); + // f. Set milliseconds to milliseconds modulo 1000. + milliseconds %= 1000f64; + + // g. Set minutes to floor(seconds / 60). + minutes = (seconds / 60f64).floor(); + // h. Set seconds to seconds modulo 60. + seconds %= 60f64; + + // i. Set hours to floor(minutes / 60). + hours = (minutes / 60f64).floor(); + // j. Set minutes to minutes modulo 60. + minutes %= 60f64; + + // k. Set days to floor(hours / 24). + days = (hours / 24f64).floor(); + // l. Set hours to hours modulo 24. + 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. + microseconds = (nanoseconds / 1000f64).floor(); + nanoseconds %= 1000f64; + + // c. Set milliseconds to floor(microseconds / 1000). + // d. Set microseconds to microseconds modulo 1000. + milliseconds = (microseconds / 1000f64).floor(); + microseconds %= 1000f64; + + // e. Set seconds to floor(milliseconds / 1000). + // f. Set milliseconds to milliseconds modulo 1000. + seconds = (milliseconds / 1000f64).floor(); + milliseconds %= 1000f64; + + // g. Set minutes to floor(seconds / 60). + // h. Set seconds to seconds modulo 60. + minutes = (seconds / 60f64).floor(); + 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. + microseconds = (nanoseconds / 1000f64).floor(); + nanoseconds %= 1000f64; + + // c. Set milliseconds to floor(microseconds / 1000). + // d. Set microseconds to microseconds modulo 1000. + milliseconds = (microseconds / 1000f64).floor(); + microseconds %= 1000f64; + + // e. Set seconds to floor(milliseconds / 1000). + // f. Set milliseconds to milliseconds modulo 1000. + seconds = (milliseconds / 1000f64).floor(); + milliseconds %= 1000f64; + } + // 12. Else if largestUnit is "millisecond", then + TemporalUnit::Millisecond => { + // a. Set microseconds to floor(nanoseconds / 1000). + // b. Set nanoseconds to nanoseconds modulo 1000. + microseconds = (nanoseconds / 1000f64).floor(); + nanoseconds %= 1000f64; + + // c. Set milliseconds to floor(microseconds / 1000). + // d. Set microseconds to microseconds modulo 1000. + milliseconds = (microseconds / 1000f64).floor(); + 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. + microseconds = (nanoseconds / 1000f64).floor(); + nanoseconds %= 1000f64; + } + // 14. Else, + // a. Assert: largestUnit is "nanosecond". + _ => debug_assert!(largest_unit == TemporalUnit::Nanosecond), + } + + let result_values = Vec::from(&[ + days, + hours, + minutes, + seconds, + milliseconds, + microseconds, + nanoseconds, + ]); + // 15. For each value v of « days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds », do + for value in result_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((f64::INFINITY, None)); + } + // ii. Else if sign = -1, then + // 1. Return negative overflow. + return Ok((f64::NEG_INFINITY, None)); + } + } + + let sign = f64::from(sign); + + // 16. Return ? CreateTimeDurationRecord(days, hours × sign, minutes × sign, seconds × sign, milliseconds × sign, microseconds × sign, nanoseconds × sign). + let result = Self::new( + hours * sign, + minutes * sign, + seconds * sign, + milliseconds * sign, + microseconds * sign, + nanoseconds * sign, + )?; + + Ok((days, Some(result))) + } +} + +// ==== TimeDuration's public API ==== + +impl TimeDuration { + /// Creates a new validated `TimeDuration`. + pub fn new( + hours: f64, + minutes: f64, + seconds: f64, + milliseconds: f64, + microseconds: f64, + nanoseconds: f64, + ) -> TemporalResult { + let result = Self::new_unchecked( + hours, + minutes, + seconds, + milliseconds, + microseconds, + nanoseconds, + ); + if !is_valid_duration(&result.into_iter().collect()) { + return Err( + TemporalError::range().with_message("Attempted to create an invalid TimeDuration.") + ); + } + Ok(result) + } + + /// Creates a partial `TimeDuration` with all values set to `NaN`. + #[must_use] + pub const fn partial() -> Self { + Self { + hours: f64::NAN, + minutes: f64::NAN, + seconds: f64::NAN, + milliseconds: f64::NAN, + microseconds: f64::NAN, + nanoseconds: f64::NAN, + } + } + + /// Returns a new `TimeDuration` representing the absolute value of the current. + #[inline] + #[must_use] + pub fn abs(&self) -> Self { + Self { + hours: self.hours.abs(), + minutes: self.minutes.abs(), + seconds: self.seconds.abs(), + milliseconds: self.milliseconds.abs(), + microseconds: self.microseconds.abs(), + nanoseconds: self.nanoseconds.abs(), + } + } + + /// Balances a `TimeDuration` given a day value and the largest unit. `balance` will return + /// the balanced `day` and `TimeDuration`. + /// + /// # Errors: + /// - Will error if provided duration is invalid + pub fn balance(&self, days: f64, largest_unit: TemporalUnit) -> TemporalResult<(f64, Self)> { + let result = Self::balance_possibly_infinite_time_duration( + days, + self.hours, + self.minutes, + self.seconds, + self.milliseconds, + self.microseconds, + self.nanoseconds, + largest_unit, + )?; + let Some(time_duration) = result.1 else { + return Err(TemporalError::range().with_message("Invalid balance TimeDuration.")); + }; + Ok((result.0, time_duration)) + } + + /// 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 + && self.seconds.abs() < 60f64 + && self.milliseconds.abs() < 1000f64 + && self.milliseconds.abs() < 1000f64 + && self.milliseconds.abs() < 1000f64 + } + + /// 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 + } + + /// Returns the `TimeDuration`'s iterator. + #[must_use] + pub fn iter(&self) -> TimeIter<'_> { + <&Self as IntoIterator>::into_iter(self) + } +} + +// ==== TimeDuration method impls ==== + +impl TimeDuration { + /// Rounds the current `TimeDuration` given a rounding increment, unit and rounding mode. `round` will return a tuple of the rounded `TimeDuration` and + /// the `total` value of the smallest unit prior to rounding. + #[inline] + pub fn round( + &self, + increment: f64, + unit: TemporalUnit, + rounding_mode: TemporalRoundingMode, + ) -> TemporalResult<(Self, f64)> { + let fraction_seconds = match unit { + TemporalUnit::Year + | TemporalUnit::Month + | TemporalUnit::Week + | TemporalUnit::Day + | TemporalUnit::Auto => { + return Err(TemporalError::r#type() + .with_message("Invalid unit provided to for TimeDuration to round.")) + } + _ => self.nanoseconds().mul_add( + 1_000_000_000f64, + self.microseconds().mul_add( + 1_000_000f64, + self.milliseconds().mul_add(1000f64, self.seconds()), + ), + ), + }; + + match unit { + // 12. Else if unit is "hour", then + TemporalUnit::Hour => { + // a. Let fractionalHours be (fractionalSeconds / 60 + minutes) / 60 + hours. + let frac_hours = (fraction_seconds / 60f64 + self.minutes) / 60f64 + self.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. + let result = Self::new(rounded_hours, 0f64, 0f64, 0f64, 0f64, 0f64)?; + Ok((result, frac_hours)) + } + // 13. Else if unit is "minute", then + TemporalUnit::Minute => { + // a. Let fractionalMinutes be fractionalSeconds / 60 + minutes. + let frac_minutes = fraction_seconds / 60f64 + self.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. + let result = Self::new(self.hours, rounded_minutes, 0f64, 0f64, 0f64, 0f64)?; + + Ok((result, frac_minutes)) + } + // 14. Else if unit is "second", then + TemporalUnit::Second => { + // a. Set seconds to RoundNumberToIncrement(fractionalSeconds, increment, roundingMode). + let rounded_seconds = + utils::round_number_to_increment(fraction_seconds, increment, rounding_mode); + // b. Set total to fractionalSeconds. + // c. Set milliseconds, microseconds, and nanoseconds to 0. + let result = + Self::new(self.hours, self.minutes, rounded_seconds, 0f64, 0f64, 0f64)?; + + Ok((result, fraction_seconds)) + } + // 15. Else if unit is "millisecond", then + TemporalUnit::Millisecond => { + // a. Let fractionalMilliseconds be nanoseconds × 10-6 + microseconds × 10-3 + milliseconds. + let fraction_millis = self.nanoseconds.mul_add( + 1_000_000f64, + self.microseconds.mul_add(1_000f64, self.milliseconds), + ); + + // b. Set milliseconds to RoundNumberToIncrement(fractionalMilliseconds, increment, roundingMode). + let rounded_millis = + utils::round_number_to_increment(fraction_millis, increment, rounding_mode); + + // c. Set total to fractionalMilliseconds. + // d. Set microseconds and nanoseconds to 0. + let result = Self::new( + self.hours, + self.minutes, + self.seconds, + rounded_millis, + 0f64, + 0f64, + )?; + Ok((result, fraction_millis)) + } + // 16. Else if unit is "microsecond", then + TemporalUnit::Microsecond => { + // a. Let fractionalMicroseconds be nanoseconds × 10-3 + microseconds. + let frac_micros = self.nanoseconds.mul_add(1_000f64, self.microseconds); + + // b. Set microseconds to RoundNumberToIncrement(fractionalMicroseconds, increment, roundingMode). + let rounded_micros = + utils::round_number_to_increment(frac_micros, increment, rounding_mode); + + // c. Set total to fractionalMicroseconds. + // d. Set nanoseconds to 0. + let result = Self::new( + self.hours, + self.minutes, + self.seconds, + self.milliseconds, + rounded_micros, + 0f64, + )?; + Ok((result, frac_micros)) + } + // 17. Else, + TemporalUnit::Nanosecond => { + // a. Assert: unit is "nanosecond". + // b. Set total to nanoseconds. + let total = self.nanoseconds; + // c. Set nanoseconds to RoundNumberToIncrement(nanoseconds, increment, roundingMode). + let rounded_nanos = + utils::round_number_to_increment(self.nanoseconds, increment, rounding_mode); + + let result = Self::new( + self.hours, + self.minutes, + self.seconds, + self.milliseconds, + self.microseconds, + rounded_nanos, + )?; + + Ok((result, total)) + } + _ => unreachable!("All other units early return error."), + } + } +} + +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, Clone)] +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 + } +} diff --git a/core/temporal/src/components/instant.rs b/core/temporal/src/components/instant.rs index 22fd69b3ad..d00a70cd0c 100644 --- a/core/temporal/src/components/instant.rs +++ b/core/temporal/src/components/instant.rs @@ -1,16 +1,63 @@ //! An implementation of the Temporal Instant. -use crate::{TemporalError, TemporalResult}; +use crate::{ + components::duration::TimeDuration, + options::{DifferenceSettings, TemporalUnit}, + TemporalError, TemporalResult, +}; use num_bigint::BigInt; use num_traits::ToPrimitive; /// The native Rust implementation of `Temporal.Instant` -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Instant { pub(crate) nanos: BigInt, } +// ==== Private API ==== + +impl Instant { + // TODO: Add test for `diff_instant`. + // NOTE(nekevss): As the below is internal, op will be left as a boolean + // with a `since` op being true and `until` being false. + /// Internal operation to handle `since` and `until` difference ops. + #[allow(unused)] + pub(crate) fn diff_instant( + &self, + op: bool, + other: &Self, + settings: DifferenceSettings, // TODO: Determine DifferenceSettings fate -> is there a better way to approach this. + ) -> TemporalResult { + let diff = self + .nanos + .to_f64() + .expect("valid instant is representable by f64.") + - other + .nanos + .to_f64() + .expect("Valid instant nanos is representable by f64."); + let nanos = diff.rem_euclid(1000f64); + let micros = (diff / 1000f64).trunc().rem_euclid(1000f64); + let millis = (diff / 1_000_000f64).trunc().rem_euclid(1000f64); + let secs = (diff / 1_000_000_000f64).trunc(); + + if settings.smallest_unit == TemporalUnit::Nanosecond { + let (_, result) = TimeDuration::new_unchecked(0f64, 0f64, secs, millis, micros, nanos) + .balance(0f64, settings.largest_unit)?; + return Ok(result); + } + + let (round_result, _) = TimeDuration::new(0f64, 0f64, secs, millis, micros, nanos)?.round( + settings.rounding_increment, + settings.smallest_unit, + settings.rounding_mode, + )?; + let (_, result) = round_result.balance(0f64, settings.largest_unit)?; + Ok(result) + } +} + // ==== Public API ==== impl Instant { @@ -60,6 +107,8 @@ impl Instant { } } +// ==== Utility Functions ==== + /// Utility for determining if the nanos are within a valid range. #[inline] #[must_use] @@ -67,6 +116,8 @@ pub(crate) fn is_valid_epoch_nanos(nanos: &BigInt) -> bool { nanos <= &BigInt::from(crate::NS_MAX_INSTANT) && nanos >= &BigInt::from(crate::NS_MIN_INSTANT) } +// ==== Instant Tests ==== + #[cfg(test)] mod tests { use crate::{components::Instant, NS_MAX_INSTANT, NS_MIN_INSTANT}; @@ -76,7 +127,8 @@ mod tests { #[test] #[allow(clippy::float_cmp)] fn max_and_minimum_instant_bounds() { - // This test is primarily to assert that the `expect` in the epoch methods is valid. + // This test is primarily to assert that the `expect` in the epoch methods is + // valid, i.e., a valid instant is within the range of an f64. let max = BigInt::from(NS_MAX_INSTANT); let min = BigInt::from(NS_MIN_INSTANT); let max_instant = Instant::new(max.clone()).unwrap(); diff --git a/core/temporal/src/components/mod.rs b/core/temporal/src/components/mod.rs index c3175e33f1..3d0d811f3a 100644 --- a/core/temporal/src/components/mod.rs +++ b/core/temporal/src/components/mod.rs @@ -17,11 +17,11 @@ // TODO: Expand upon above introduction. pub mod calendar; +pub mod duration; pub mod tz; mod date; mod datetime; -pub(crate) mod duration; mod instant; mod month_day; mod time; diff --git a/core/temporal/src/options.rs b/core/temporal/src/options.rs index 09027a3575..0f80123743 100644 --- a/core/temporal/src/options.rs +++ b/core/temporal/src/options.rs @@ -7,6 +7,19 @@ use core::{fmt, str::FromStr}; use crate::TemporalError; +// NOTE: Currently the `DifferenceSetting` is the record returned from 13.47 `GetDifferenceSetting`. +// This should be reassessed once Instant is added to the builtin `Temporal.Instant`. +/// The settings for a difference Op +#[derive(Debug, Clone, Copy)] +pub struct DifferenceSettings { + pub(crate) rounding_mode: TemporalRoundingMode, + pub(crate) rounding_increment: f64, + pub(crate) largest_unit: TemporalUnit, + pub(crate) smallest_unit: TemporalUnit, +} + +// ==== Options enums and methods ==== + /// 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)]