diff --git a/src/components/calendar.rs b/src/components/calendar.rs index 9543cb80..c5c5520b 100644 --- a/src/components/calendar.rs +++ b/src/components/calendar.rs @@ -11,7 +11,10 @@ use std::str::FromStr; use crate::{ - components::{Date, DateTime, Duration, MonthDay, YearMonth}, + components::{ + duration::{DateDuration, TimeDuration}, + Date, DateTime, Duration, MonthDay, YearMonth, + }, iso::{IsoDate, IsoDateSlots}, options::{ArithmeticOverflow, TemporalUnit}, TemporalError, TemporalFields, TemporalResult, @@ -490,7 +493,28 @@ impl CalendarSlot { ) -> TemporalResult> { match self { CalendarSlot::Builtin(_) => { - Err(TemporalError::range().with_message("Not yet implemented.")) + // 8. Let norm be NormalizeTimeDuration(duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]]). + // 9. Let balanceResult be BalanceTimeDuration(norm, "day"). + let (balance_days, _) = + TimeDuration::from_normalized(duration.time().as_norm(), TemporalUnit::Day)?; + // 10. Let result be ? AddISODate(date.[[ISOYear]], date.[[ISOMonth]], date.[[ISODay]], duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]] + balanceResult.[[Days]], overflow). + let result = date.iso().add_iso_date( + &DateDuration::new_unchecked( + duration.days(), + duration.months(), + duration.weeks(), + duration.days() + balance_days, + ), + overflow, + )?; + // 11. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], "iso8601"). + Date::new( + result.year, + result.month.into(), + result.day.into(), + date.calendar().clone(), + ArithmeticOverflow::Reject, + ) } CalendarSlot::Protocol(protocol) => { protocol.date_add(date, duration, overflow, context) diff --git a/src/components/date.rs b/src/components/date.rs index e11fe12b..abad227f 100644 --- a/src/components/date.rs +++ b/src/components/date.rs @@ -358,15 +358,8 @@ 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, _) = TimeDuration::new_unchecked( - duration.hours(), - duration.minutes(), - duration.seconds(), - duration.milliseconds(), - duration.microseconds(), - duration.nanoseconds(), - ) - .balance(TemporalUnit::Day)?; + let (days, _) = + TimeDuration::from_normalized(duration.time().as_norm(), TemporalUnit::Day)?; // 5. Let result be ? AddISODate(plainDate.[[ISOYear]], plainDate.[[ISOMonth]], plainDate.[[ISODay]], 0, 0, 0, days, overflow). let result = self diff --git a/src/components/duration.rs b/src/components/duration.rs index 2ad972b7..3c3d7ed8 100644 --- a/src/components/duration.rs +++ b/src/components/duration.rs @@ -11,8 +11,8 @@ use std::str::FromStr; use super::{calendar::CalendarProtocol, tz::TzProtocol}; mod date; +pub(crate) mod normalized; mod time; -mod normalized; #[doc(inline)] pub use date::DateDuration; @@ -924,7 +924,7 @@ impl Duration { /// Calls `TimeDuration`'s balance method on the current `Duration`. #[inline] pub fn balance_time_duration(&self, unit: TemporalUnit) -> TemporalResult<(f64, TimeDuration)> { - self.time().balance(unit) + TimeDuration::from_normalized(self.time().as_norm(), unit) } } diff --git a/src/components/duration/normalized.rs b/src/components/duration/normalized.rs index fa89125c..27c281cd 100644 --- a/src/components/duration/normalized.rs +++ b/src/components/duration/normalized.rs @@ -9,7 +9,6 @@ const MAX_TIME_DURATION: f64 = 2e53 * 10e9 - 1.0; #[derive(Debug, Clone, Copy, Default, PartialEq, PartialOrd)] pub(crate) struct NormalizedTimeDuration(pub(crate) f64); - impl NormalizedTimeDuration { /// Equivalent: 7.5.20 NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds, nanoseconds ) pub(crate) fn from_time_duration(time: &TimeDuration) -> Self { @@ -28,7 +27,8 @@ impl NormalizedTimeDuration { pub(crate) fn add(&self, other: &Self) -> TemporalResult { let result = self.0 + other.0; if result.abs() > MAX_TIME_DURATION { - return Err(TemporalError::range().with_message("normalizedTimeDuration exceeds maxTimeDuration.")) + return Err(TemporalError::range() + .with_message("normalizedTimeDuration exceeds maxTimeDuration.")); } Ok(Self(result)) } @@ -38,7 +38,8 @@ impl NormalizedTimeDuration { pub(crate) fn add_days(&self, days: f64) -> TemporalResult { let result = self.0 + days * NS_PER_DAY as f64; if result.abs() > MAX_TIME_DURATION { - return Err(TemporalError::range().with_message("normalizedTimeDuration exceeds maxTimeDuration.")) + return Err(TemporalError::range() + .with_message("normalizedTimeDuration exceeds maxTimeDuration.")); } Ok(Self(result)) } @@ -49,11 +50,10 @@ impl NormalizedTimeDuration { /// Equivalent: 7.5.31 NormalizedTimeDurationSign ( d ) pub(crate) fn sign(&self) -> f64 { if self.0 < 0.0 { - return -1.0 + return -1.0; } else if self.0 > 0.0 { - return 1.0 + return 1.0; } 0.0 } } - diff --git a/src/components/duration/time.rs b/src/components/duration/time.rs index fa88abda..04b65f60 100644 --- a/src/components/duration/time.rs +++ b/src/components/duration/time.rs @@ -135,42 +135,17 @@ impl TimeDuration { } } - /// 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(), - } - } - - /// Returns a negated `TimeDuration`. - #[inline] - #[must_use] - pub fn negated(&self) -> Self { - Self { - hours: self.hours * -1f64, - minutes: self.minutes * -1f64, - seconds: self.seconds * -1f64, - milliseconds: self.milliseconds * -1f64, - microseconds: self.microseconds * -1f64, - nanoseconds: self.nanoseconds * -1f64, - } - } - - /// Balances a `TimeDuration` given a day value and the largest unit. `balance` will return - /// the balanced `day` and `TimeDuration`. + /// Balances and creates `TimeDuration` from a `NormalizedTimeDuration`. This method will return + /// a tuple (f64, TimeDuration) where f64 is the overflow day value from balancing. + /// + /// Equivalent: `BalanceTimeDuration` /// /// # Errors: /// - Will error if provided duration is invalid - pub fn balance(&self, largest_unit: TemporalUnit) -> TemporalResult<(f64, Self)> { - let norm = NormalizedTimeDuration::from_time_duration(&self); - + pub(crate) fn from_normalized( + norm: NormalizedTimeDuration, + largest_unit: TemporalUnit, + ) -> TemporalResult<(f64, Self)> { // 1. Let days, hours, minutes, seconds, milliseconds, and microseconds be 0. let mut days = 0f64; let mut hours = 0f64; @@ -186,10 +161,7 @@ impl TimeDuration { match largest_unit { // 4. If largestUnit is "year", "month", "week", or "day", then - TemporalUnit::Year - | TemporalUnit::Month - | TemporalUnit::Week - | TemporalUnit::Day => { + TemporalUnit::Year | TemporalUnit::Month | TemporalUnit::Week | TemporalUnit::Day => { // a. Set microseconds to floor(nanoseconds / 1000). microseconds = (nanoseconds / 1000f64).floor(); // b. Set nanoseconds to nanoseconds modulo 1000. @@ -309,14 +281,34 @@ impl TimeDuration { // a. Assert: largestUnit is "nanosecond". _ => debug_assert!(largest_unit == TemporalUnit::Nanosecond), } - // 11. NOTE: When largestUnit is "millisecond", "microsecond", or "nanosecond", milliseconds, microseconds, or nanoseconds may be an unsafe integer. In this case, - // care must be taken when implementing the calculation using floating point arithmetic. It can be implemented in C++ using std::fma(). String manipulation will also + + // NOTE(nekevss): `mul_add` is essentially the Rust's implementation of `std::fma()`, so that's handy, but + // this should be tested much further. + // 11. NOTE: When largestUnit is "millisecond", "microsecond", or "nanosecond", milliseconds, microseconds, or + // nanoseconds may be an unsafe integer. In this case, care must be taken when implementing the calculation + // using floating point arithmetic. It can be implemented in C++ using std::fma(). String manipulation will also // give an exact result, since the multiplication is by a power of 10. + // 12. Return ! CreateTimeDurationRecord(days × sign, hours × sign, minutes × sign, seconds × sign, milliseconds × sign, microseconds × sign, nanoseconds × sign). let days = days.mul_add(sign, 0.0); - let result = Self::new_unchecked(hours.mul_add(sign, 0.0), minutes.mul_add(sign, 0.0), seconds.mul_add(sign, 0.0), milliseconds.mul_add(sign, 0.0), microseconds.mul_add(sign, 0.0), nanoseconds.mul_add(sign, 0.0)); + let result = Self::new_unchecked( + hours.mul_add(sign, 0.0), + minutes.mul_add(sign, 0.0), + seconds.mul_add(sign, 0.0), + milliseconds.mul_add(sign, 0.0), + microseconds.mul_add(sign, 0.0), + nanoseconds.mul_add(sign, 0.0), + ); - let td = Vec::from(&[days, result.hours, result.minutes, result.seconds, result.milliseconds, result.microseconds, result.nanoseconds]); + let td = Vec::from(&[ + days, + result.hours, + result.minutes, + result.seconds, + result.milliseconds, + result.microseconds, + result.nanoseconds, + ]); if !is_valid_duration(&td) { return Err(TemporalError::range().with_message("Invalid balance TimeDuration.")); } @@ -324,6 +316,34 @@ impl TimeDuration { Ok((days, result)) } + /// 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(), + } + } + + /// Returns a negated `TimeDuration`. + #[inline] + #[must_use] + pub fn negated(&self) -> Self { + Self { + hours: self.hours * -1f64, + minutes: self.minutes * -1f64, + seconds: self.seconds * -1f64, + milliseconds: self.milliseconds * -1f64, + microseconds: self.microseconds * -1f64, + nanoseconds: self.nanoseconds * -1f64, + } + } + /// Utility function for returning if values in a valid range. #[inline] #[must_use] @@ -377,6 +397,11 @@ impl TimeDuration { pub fn iter(&self) -> TimeIter<'_> { <&Self as IntoIterator>::into_iter(self) } + + /// Returns this `TimeDuration` as a `NormalizedTimeDuration`. + pub(crate) fn as_norm(&self) -> NormalizedTimeDuration { + NormalizedTimeDuration::from_time_duration(self) + } } // ==== TimeDuration method impls ==== diff --git a/src/components/instant.rs b/src/components/instant.rs index fc1bc46d..2b72d594 100644 --- a/src/components/instant.rs +++ b/src/components/instant.rs @@ -77,8 +77,10 @@ impl Instant { // Steps 11-13 of 13.47 GetDifferenceSettings if smallest_unit == TemporalUnit::Nanosecond { - let (_, result) = TimeDuration::new_unchecked(0f64, 0f64, secs, millis, micros, nanos) - .balance(largest_unit)?; + let (_, result) = TimeDuration::from_normalized( + TimeDuration::new_unchecked(0f64, 0f64, secs, millis, micros, nanos).as_norm(), + largest_unit, + )?; return Ok(result); } @@ -87,7 +89,7 @@ impl Instant { smallest_unit, rounding_mode, )?; - let (_, result) = round_result.balance(largest_unit)?; + let (_, result) = TimeDuration::from_normalized(round_result.as_norm(), largest_unit)?; Ok(result) }