diff --git a/src/components/duration.rs b/src/components/duration.rs index 2d9661d0..bc984700 100644 --- a/src/components/duration.rs +++ b/src/components/duration.rs @@ -8,6 +8,8 @@ use crate::{ }; use std::str::FromStr; +use self::normalized::{NormalizedDurationRecord, NormalizedTimeDuration}; + use super::{calendar::CalendarProtocol, tz::TzProtocol}; mod date; @@ -803,14 +805,15 @@ impl Duration { } // TODO (nekevss): Refactor relative_to's into a RelativeTo struct? - // TODO (nekevss): Update to `Duration` normalization. + // TODO (nekevss): Impl `Duration::round` -> Potentially rename to `round_internal` /// Abstract Operation 7.5.26 `RoundDuration ( years, months, weeks, days, hours, minutes, /// seconds, milliseconds, microseconds, nanoseconds, increment, unit, /// roundingMode [ , plainRelativeTo [, zonedRelativeTo [, precalculatedDateTime]]] )` #[allow(clippy::type_complexity)] - pub fn round_duration( + #[allow(dead_code)] + pub(crate) fn round_duration( &self, - increment: f64, + increment: u64, unit: TemporalUnit, rounding_mode: TemporalRoundingMode, relative_targets: ( @@ -819,19 +822,22 @@ impl Duration { Option<&DateTime>, ), context: &mut C::Context, - ) -> TemporalResult<(Self, f64)> { + ) -> TemporalResult<(NormalizedDurationRecord, f64)> { match unit { TemporalUnit::Year | TemporalUnit::Month | TemporalUnit::Week | TemporalUnit::Day => { let round_result = self.date().round( - Some(self.time), + Some(self.time.to_normalized()), increment, unit, rounding_mode, relative_targets, context, )?; - let result = Self::from_date_duration(round_result.0); - Ok((result, round_result.1)) + let norm_record = NormalizedDurationRecord::new( + round_result.0, + NormalizedTimeDuration::default(), + )?; + Ok((norm_record, round_result.1)) } TemporalUnit::Hour | TemporalUnit::Minute @@ -840,11 +846,8 @@ impl Duration { | 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)) + let norm = NormalizedDurationRecord::new(DateDuration::default(), round_result.0)?; + Ok((norm, round_result.1 as f64)) } TemporalUnit::Auto => { Err(TemporalError::range().with_message("Invalid TemporalUnit for Duration.round")) diff --git a/src/components/duration/date.rs b/src/components/duration/date.rs index 233ab766..4feb3b35 100644 --- a/src/components/duration/date.rs +++ b/src/components/duration/date.rs @@ -9,6 +9,8 @@ use crate::{ utils, TemporalError, TemporalResult, NS_PER_DAY, }; +use super::normalized::NormalizedTimeDuration; + /// `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. @@ -109,6 +111,13 @@ impl DateDuration { } } + /// Returns the sign for the current `DateDuration`. + #[inline] + #[must_use] + pub fn sign(&self) -> i32 { + super::duration_sign(&self.iter().collect()) + } + /// Returns the `[[years]]` value. #[must_use] pub const fn years(&self) -> f64 { @@ -148,8 +157,8 @@ impl DateDuration { #[allow(clippy::type_complexity, clippy::let_and_return)] pub fn round( &self, - additional_time: Option, - increment: f64, + normalized_time: Option, + increment: u64, unit: TemporalUnit, rounding_mode: TemporalRoundingMode, relative_targets: ( @@ -177,23 +186,19 @@ impl DateDuration { } // 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 + // a. If zonedRelativeTo is not undefined, then + if let Some(_zoned_relative) = zoned_relative_to { + // TODO: + // i. Let intermediate be ? MoveRelativeZonedDateTime(zonedRelativeTo, calendarRec, timeZoneRec, years, months, weeks, days, precalculatedPlainDateTime). + // ii. Let result be ? NormalizedTimeDurationToDays(norm, intermediate, timeZoneRec). + // iii. Let fractionalDays be days + result.[[Days]] + DivideNormalizedTimeDuration(result.[[Remainder]], result.[[DayLength]]). + return Err(TemporalError::general("Not yet implemented.")); + // b. Else, } else { - // implementation of b: i-iii needed. - return Err(TemporalError::range().with_message("Not yet implemented.")); + // i. Let fractionalDays be days + DivideNormalizedTimeDuration(norm, nsPerDay). + self.days + normalized_time.unwrap_or_default().divide(NS_PER_DAY) as f64 } + // c. Set days to 0. } _ => { return Err(TemporalError::range() @@ -300,11 +305,11 @@ impl DateDuration { // ab. Set years to RoundNumberToIncrement(fractionalYears, increment, roundingMode). let rounded_years = - utils::round_number_to_increment(frac_years, increment, rounding_mode); + utils::round_number_to_increment(frac_years as i64, increment, rounding_mode); // ac. Set total to fractionalYears. // ad. Set months and weeks to 0. - let result = Self::new(rounded_years, 0f64, 0f64, 0f64)?; + let result = Self::new(rounded_years as f64, 0f64, 0f64, 0f64)?; Ok((result, frac_years)) } // 9. Else if unit is "month", then @@ -391,11 +396,11 @@ impl DateDuration { // r. Set months to RoundNumberToIncrement(fractionalMonths, increment, roundingMode). let rounded_months = - utils::round_number_to_increment(frac_months, increment, rounding_mode); + utils::round_number_to_increment(frac_months as i64, increment, rounding_mode); // s. Set total to fractionalMonths. // t. Set weeks to 0. - let result = Self::new(self.years, rounded_months, 0f64, 0f64)?; + let result = Self::new(self.years, rounded_months as f64, 0f64, 0f64)?; Ok((result, frac_months)) } // 10. Else if unit is "week", then @@ -443,18 +448,23 @@ impl DateDuration { // k. Set weeks to RoundNumberToIncrement(fractionalWeeks, increment, roundingMode). let rounded_weeks = - utils::round_number_to_increment(frac_weeks, increment, rounding_mode); + utils::round_number_to_increment(frac_weeks as i64, increment, rounding_mode); // l. Set total to fractionalWeeks. - let result = Self::new(self.years, self.months, rounded_weeks, 0f64)?; + let result = Self::new(self.years, self.months, rounded_weeks as f64, 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); + let rounded_days = utils::round_number_to_increment( + fractional_days as i64, + increment, + rounding_mode, + ); + // b. Set total to fractionalDays. - let result = Self::new(self.years, self.months, self.weeks, rounded_days)?; + // c. Set norm to ZeroTimeDuration(). + let result = Self::new(self.years, self.months, self.weeks, rounded_days as f64)?; Ok((result, fractional_days)) } _ => unreachable!("All other TemporalUnits were returned early as invalid."), diff --git a/src/components/duration/normalized.rs b/src/components/duration/normalized.rs index 27c281cd..3238d724 100644 --- a/src/components/duration/normalized.rs +++ b/src/components/duration/normalized.rs @@ -1,17 +1,20 @@ //! This module implements the normalized `Duration` records. -use crate::{TemporalError, TemporalResult, NS_PER_DAY}; +use std::ops::Add; -use super::TimeDuration; +use crate::{options::TemporalRoundingMode, utils, TemporalError, TemporalResult, NS_PER_DAY}; + +use super::{DateDuration, TimeDuration}; const MAX_TIME_DURATION: f64 = 2e53 * 10e9 - 1.0; +/// A Normalized `TimeDuration` that represents the current `TimeDuration` in nanoseconds. #[derive(Debug, Clone, Copy, Default, PartialEq, PartialOrd)] -pub(crate) struct NormalizedTimeDuration(pub(crate) f64); +pub struct NormalizedTimeDuration(pub(super) f64); impl NormalizedTimeDuration { /// Equivalent: 7.5.20 NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds, nanoseconds ) - pub(crate) fn from_time_duration(time: &TimeDuration) -> Self { + pub(super) fn from_time_duration(time: &TimeDuration) -> Self { let minutes = time.minutes + time.hours * 60.0; let seconds = time.seconds + minutes * 60.0; let milliseconds = time.milliseconds + seconds * 1000.0; @@ -22,10 +25,10 @@ impl NormalizedTimeDuration { Self(nanoseconds) } - /// Equivalent: 7.5.22 AddNormalizedTimeDuration ( one, two ) + /// Equivalent: 7.5.23 Add24HourDaysToNormalizedTimeDuration ( d, days ) #[allow(unused)] - pub(crate) fn add(&self, other: &Self) -> TemporalResult { - let result = self.0 + other.0; + pub(super) 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.")); @@ -33,27 +36,74 @@ impl NormalizedTimeDuration { Ok(Self(result)) } - /// Equivalent: 7.5.23 Add24HourDaysToNormalizedTimeDuration ( d, days ) - #[allow(unused)] - pub(crate) fn add_days(&self, days: f64) -> TemporalResult { - let result = self.0 + days * NS_PER_DAY as f64; + // TODO: Implement as `ops::Div` + /// `Divide the NormalizedTimeDuraiton` by a divisor. + pub(super) fn divide(&self, divisor: i64) -> i64 { + // TODO: Validate. + self.0 as i64 / divisor + } + + /// Equivalent: 7.5.31 NormalizedTimeDurationSign ( d ) + #[inline] + #[must_use] + pub(super) fn sign(&self) -> i32 { + if self.0 < 0.0 { + return -1; + } else if self.0 > 0.0 { + return 1; + } + 0 + } + + /// Return the seconds value of the `NormalizedTimeDuration`. + pub(super) fn seconds(&self) -> i64 { + (self.0 / 10e9).trunc() as i64 + } + + /// Returns the subsecond components of the `NormalizedTimeDuration`. + pub(super) fn subseconds(&self) -> i32 { + // SAFETY: Remainder is 10e9 which is in range of i32 + (self.0 % 10e9f64) as i32 + } + + /// Round the current `NormalizedTimeDuration`. + pub(super) fn round(&self, increment: u64, mode: TemporalRoundingMode) -> TemporalResult { + let rounded = utils::round_number_to_increment(self.0 as i64, increment, mode); + if rounded.abs() > MAX_TIME_DURATION as i64 { + return Err(TemporalError::range() + .with_message("normalizedTimeDuration exceeds maxTimeDuration.")); + } + Ok(Self(rounded as f64)) + } +} + +// NOTE(nekevss): As this `Add` impl is fallible. Maybe it would be best implemented as a method. +/// Equivalent: 7.5.22 AddNormalizedTimeDuration ( one, two ) +impl Add for NormalizedTimeDuration { + type Output = TemporalResult; + + fn add(self, rhs: Self) -> Self::Output { + let result = self.0 + rhs.0; if result.abs() > MAX_TIME_DURATION { return Err(TemporalError::range() .with_message("normalizedTimeDuration exceeds maxTimeDuration.")); } Ok(Self(result)) } +} - // NOTE: DivideNormalizedTimeDuration probably requires `__float128` support as `NormalizedTimeDuration` is not `safe integer`. - // Tracking issue: https://github.com/rust-lang/rfcs/pull/3453 +/// A normalized `DurationRecord` that contains a `DateDuration` and `NormalizedTimeDuration`. +pub struct NormalizedDurationRecord(pub(super) (DateDuration, NormalizedTimeDuration)); - /// Equivalent: 7.5.31 NormalizedTimeDurationSign ( d ) - pub(crate) fn sign(&self) -> f64 { - if self.0 < 0.0 { - return -1.0; - } else if self.0 > 0.0 { - return 1.0; +impl NormalizedDurationRecord { + /// Creates a new `NormalizedDurationRecord`. + /// + /// Equivalent: `CreateNormalizedDurationRecord` & `CombineDateAndNormalizedTimeDuration`. + pub(super) fn new(date: DateDuration, norm: NormalizedTimeDuration) -> TemporalResult { + if date.sign() != 0 && norm.sign() != 0 && date.sign() != norm.sign() { + return Err(TemporalError::range() + .with_message("DateDuration and NormalizedTimeDuration must agree.")); } - 0.0 + Ok(Self((date, norm))) } } diff --git a/src/components/duration/time.rs b/src/components/duration/time.rs index 746e4fe2..5941fce8 100644 --- a/src/components/duration/time.rs +++ b/src/components/duration/time.rs @@ -2,11 +2,15 @@ use crate::{ options::{TemporalRoundingMode, TemporalUnit}, - utils, TemporalError, TemporalResult, + TemporalError, TemporalResult, }; use super::{is_valid_duration, normalized::NormalizedTimeDuration}; +const NANOSECONDS_PER_SECOND: u64 = 1_000_000_000; +const NANOSECONDS_PER_MINUTE: u64 = NANOSECONDS_PER_SECOND * 60; +const NANOSECONDS_PER_HOUR: u64 = NANOSECONDS_PER_MINUTE * 60; + /// `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. @@ -44,17 +48,6 @@ impl TimeDuration { 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) - } } // ==== TimeDuration's public API ==== @@ -155,7 +148,7 @@ impl TimeDuration { let mut microseconds = 0f64; // 2. Let sign be NormalizedTimeDurationSign(norm). - let sign = norm.sign(); + let sign = f64::from(norm.sign()); // 3. Let nanoseconds be NormalizedTimeDurationAbs(norm).[[TotalNanoseconds]]. let mut nanoseconds = norm.0.abs(); @@ -413,11 +406,11 @@ impl TimeDuration { #[inline] pub fn round( &self, - increment: f64, + increment: u64, unit: TemporalUnit, - rounding_mode: TemporalRoundingMode, - ) -> TemporalResult<(Self, f64)> { - let fraction_seconds = match unit { + mode: TemporalRoundingMode, + ) -> TemporalResult<(NormalizedTimeDuration, i64)> { + let norm = match unit { TemporalUnit::Year | TemporalUnit::Month | TemporalUnit::Week @@ -426,117 +419,64 @@ impl TimeDuration { 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()), - ), - ), + _ => self.to_normalized(), }; 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)) + // a. Let divisor be 3.6 × 10**12. + // b. Set total to DivideNormalizedTimeDuration(norm, divisor). + let total = norm.divide(NANOSECONDS_PER_HOUR as i64); + // c. Set norm to ? RoundNormalizedTimeDurationToIncrement(norm, divisor × increment, roundingMode). + let norm = norm.round(NANOSECONDS_PER_HOUR * increment, mode)?; + Ok((norm, total)) } // 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)) + // a. Let divisor be 6 × 10**10. + // b. Set total to DivideNormalizedTimeDuration(norm, divisor). + let total = norm.divide(NANOSECONDS_PER_HOUR as i64); + // c. Set norm to ? RoundNormalizedTimeDurationToIncrement(norm, divisor × increment, roundingMode). + let norm = norm.round(NANOSECONDS_PER_HOUR * increment, mode)?; + Ok((norm, total)) } // 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)) + // a. Let divisor be 10**9. + // b. Set total to DivideNormalizedTimeDuration(norm, divisor). + let total = norm.divide(NANOSECONDS_PER_HOUR as i64); + // c. Set norm to ? RoundNormalizedTimeDurationToIncrement(norm, divisor × increment, roundingMode). + let norm = norm.round(NANOSECONDS_PER_HOUR * increment, mode)?; + Ok((norm, total)) } // 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)) + // a. Let divisor be 10**6. + // b. Set total to DivideNormalizedTimeDuration(norm, divisor). + let total = norm.divide(NANOSECONDS_PER_HOUR as i64); + // c. Set norm to ? RoundNormalizedTimeDurationToIncrement(norm, divisor × increment, roundingMode). + let norm = norm.round(NANOSECONDS_PER_HOUR * increment, mode)?; + Ok((norm, total)) } // 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)) + // a. Let divisor be 10**3. + // b. Set total to DivideNormalizedTimeDuration(norm, divisor). + let total = norm.divide(NANOSECONDS_PER_HOUR as i64); + // c. Set norm to ? RoundNormalizedTimeDurationToIncrement(norm, divisor × increment, roundingMode). + let norm = norm.round(NANOSECONDS_PER_HOUR * increment, mode)?; + Ok((norm, total)) } // 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)) + // b. Set total to NormalizedTimeDurationSeconds(norm) × 10**9 + NormalizedTimeDurationSubseconds(norm). + let total = + norm.seconds() * (NANOSECONDS_PER_SECOND as i64) + i64::from(norm.subseconds()); + // c. Set norm to ? RoundNormalizedTimeDurationToIncrement(norm, increment, roundingMode). + let norm = norm.round(increment, mode)?; + Ok((norm, total)) } _ => unreachable!("All other units early return error."), } diff --git a/src/components/instant.rs b/src/components/instant.rs index 1d2ec849..76d710a6 100644 --- a/src/components/instant.rs +++ b/src/components/instant.rs @@ -61,7 +61,7 @@ impl Instant { let secs = (diff / NANOSECONDS_PER_SECOND).trunc(); // Handle the settings provided to `diff_instant` - let rounding_increment = rounding_increment.unwrap_or(1.0); + let increment = utils::to_rounding_increment(rounding_increment)?; let rounding_mode = if op { rounding_mode .unwrap_or(TemporalRoundingMode::Trunc) @@ -86,28 +86,27 @@ impl Instant { } let (round_result, _) = TimeDuration::new(0f64, 0f64, secs, millis, micros, nanos)?.round( - rounding_increment, + increment, smallest_unit, rounding_mode, )?; - let (_, result) = - TimeDuration::from_normalized(round_result.to_normalized(), largest_unit)?; + let (_, result) = TimeDuration::from_normalized(round_result, largest_unit)?; Ok(result) } /// Rounds a current `Instant` given the resolved options, returning a `BigInt` result. pub(crate) fn round_instant( &self, - increment: f64, + increment: u64, unit: TemporalUnit, rounding_mode: TemporalRoundingMode, ) -> TemporalResult { - let increment_nanos = match unit { - TemporalUnit::Hour => increment * NANOSECONDS_PER_HOUR, - TemporalUnit::Minute => increment * NANOSECONDS_PER_MINUTE, - TemporalUnit::Second => increment * NANOSECONDS_PER_SECOND, - TemporalUnit::Millisecond => increment * 1_000_000f64, - TemporalUnit::Microsecond => increment * 1_000f64, + let increment = match unit { + TemporalUnit::Hour => increment * (NANOSECONDS_PER_HOUR as u64), + TemporalUnit::Minute => increment * (NANOSECONDS_PER_MINUTE as u64), + TemporalUnit::Second => increment * (NANOSECONDS_PER_SECOND as u64), + TemporalUnit::Millisecond => increment * 1_000_000, + TemporalUnit::Microsecond => increment * 1_000, TemporalUnit::Nanosecond => increment, _ => { return Err(TemporalError::range() @@ -116,12 +115,12 @@ impl Instant { }; let rounded = utils::round_number_to_increment_as_if_positive( - self.to_f64(), - increment_nanos, + self.to_f64() as u64, // TODO: Update in numeric refactor. + increment, rounding_mode, ); - BigInt::from_f64(rounded) + BigInt::from_u64(rounded) .ok_or_else(|| TemporalError::range().with_message("Invalid rounded Instant value.")) } diff --git a/src/iso.rs b/src/iso.rs index 96bec11c..49ce55ff 100644 --- a/src/iso.rs +++ b/src/iso.rs @@ -409,7 +409,7 @@ impl IsoTime { /// Rounds the current `IsoTime` according to the provided settings. pub(crate) fn round( &self, - increment: f64, + increment: u64, unit: TemporalUnit, mode: TemporalRoundingMode, day_length_ns: Option, @@ -470,16 +470,18 @@ impl IsoTime { }; let ns_per_unit = if unit == TemporalUnit::Day { - day_length_ns.unwrap_or(NS_PER_DAY) as f64 + day_length_ns.unwrap_or(NS_PER_DAY) } else { - unit.as_nanoseconds().expect("Only valid time values are ") + unit.as_nanoseconds().expect("Only valid time values are ") as i64 }; // TODO: Verify validity of cast or handle better. // 9. Let result be RoundNumberToIncrement(quantity, increment, roundingMode). - let result = - utils::round_number_to_increment(quantity as f64, ns_per_unit * increment, mode) - / ns_per_unit; + let result = (utils::round_number_to_increment( + quantity as i64, + (ns_per_unit as u64) * increment, + mode, + ) / ns_per_unit) as f64; let result = match unit { // 10. If unit is "day", then diff --git a/src/utils.rs b/src/utils.rs index cc370f42..6d194a83 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,5 +1,7 @@ //! Utility date and time equations for Temporal +use std::{cmp::Ordering, ops::Neg}; + use crate::{ options::{TemporalRoundingMode, TemporalUnsignedRoundingMode}, TemporalError, TemporalResult, MS_PER_DAY, @@ -8,7 +10,7 @@ use crate::{ // NOTE: Review the below for optimizations and add ALOT of tests. /// Converts and validates an `Option` rounding increment value into a valid increment result. -pub(crate) fn to_rounding_increment(increment: Option) -> TemporalResult { +pub(crate) fn to_rounding_increment(increment: Option) -> TemporalResult { let inc = increment.unwrap_or(1.0); if !inc.is_finite() { @@ -23,81 +25,80 @@ pub(crate) fn to_rounding_increment(increment: Option) -> TemporalResult f64 { +) -> u64 { // 1. If x is equal to r1, return r1. - if (x - r1).abs() == 0.0 { - return r1; + if x % increment == 0 { + return ceil; }; + // 2. Assert: r1 < x < r2. - assert!(r1 < x && x < r2); // 3. Assert: unsignedRoundingMode is not undefined. // 4. If unsignedRoundingMode is zero, return r1. if unsigned_rounding_mode == TemporalUnsignedRoundingMode::Zero { - return r1; + return floor; }; // 5. If unsignedRoundingMode is infinity, return r2. if unsigned_rounding_mode == TemporalUnsignedRoundingMode::Infinity { - return r2; + return ceil; }; // 6. Let d1 be x – r1. - let d1 = x - r1; // 7. Let d2 be r2 – x. - let d2 = r2 - x; // 8. If d1 < d2, return r1. - if d1 < d2 { - return r1; - } // 9. If d2 < d1, return r2. - if d2 < d1 { - return r2; - } - // 10. Assert: d1 is equal to d2. - assert!((d1 - d2).abs() == 0.0); - - // 11. If unsignedRoundingMode is half-zero, return r1. - if unsigned_rounding_mode == TemporalUnsignedRoundingMode::HalfZero { - return r1; - }; - // 12. If unsignedRoundingMode is half-infinity, return r2. - if unsigned_rounding_mode == TemporalUnsignedRoundingMode::HalfInfinity { - return r2; - }; - // 13. Assert: unsignedRoundingMode is half-even. - assert!(unsigned_rounding_mode == TemporalUnsignedRoundingMode::HalfEven); - // 14. Let cardinality be (r1 / (r2 – r1)) modulo 2. - let cardinality = (r1 / (r2 - r1)) % 2.0; - // 15. If cardinality is 0, return r1. - if cardinality == 0.0 { - return r1; + let remainder = x % increment; + let half = increment / 2; + match remainder.cmp(&half) { + Ordering::Less => floor, + Ordering::Greater => ceil, + Ordering::Equal => { + // 10. Assert: d1 is equal to d2. + // 11. If unsignedRoundingMode is half-zero, return r1. + if unsigned_rounding_mode == TemporalUnsignedRoundingMode::HalfZero { + return floor; + }; + // 12. If unsignedRoundingMode is half-infinity, return r2. + if unsigned_rounding_mode == TemporalUnsignedRoundingMode::HalfInfinity { + return ceil; + }; + // 13. Assert: unsignedRoundingMode is half-even. + assert!(unsigned_rounding_mode == TemporalUnsignedRoundingMode::HalfEven); + // 14. Let cardinality be (r1 / (r2 – r1)) modulo 2. + let cardinality = (floor / (ceil - floor)) % 2; + // 15. If cardinality is 0, return r1. + if cardinality == 0 { + return floor; + } + // 16. Return r2. + ceil + } } - // 16. Return r2. - r2 } +// TODO: Use `div_ceil` and `div_floor` once stable. +// Tracking issue: https://github.com/rust-lang/rust/issues/88581 /// 13.28 `RoundNumberToIncrement ( x, increment, roundingMode )` pub(crate) fn round_number_to_increment( - x: f64, - increment: f64, + x: i64, + increment: u64, rounding_mode: TemporalRoundingMode, -) -> f64 { +) -> i64 { // 1. Let quotient be x / increment. - let mut quotient = x / increment; - // 2. If quotient < 0, then - let is_negative = if quotient < 0_f64 { + let is_negative = if x / (increment as i64) < 0 { // a. Let isNegative be true. // b. Set quotient to -quotient. - quotient = -quotient; true // 3. Else, } else { @@ -107,38 +108,44 @@ pub(crate) fn round_number_to_increment( // 4. Let unsignedRoundingMode be GetUnsignedRoundingMode(roundingMode, isNegative). let unsigned_rounding_mode = rounding_mode.get_unsigned_round_mode(is_negative); + + let x = x.unsigned_abs(); // 5. Let r1 be the largest integer such that r1 ≤ quotient. - let r1 = quotient.floor(); + let floor = x / increment; // 6. Let r2 be the smallest integer such that r2 > quotient. - let r2 = quotient.ceil(); + let ceil = x.div_ceil(increment); + // 7. Let rounded be ApplyUnsignedRoundingMode(quotient, r1, r2, unsignedRoundingMode). - let mut rounded = apply_unsigned_rounding_mode(quotient, r1, r2, unsigned_rounding_mode); + let rounded = apply_unsigned_rounding_mode(x, increment, floor, ceil, unsigned_rounding_mode); + // 8. If isNegative is true, set rounded to -rounded. - if is_negative { - rounded = -rounded; + let rounded = if is_negative { + (rounded as i64).neg() + } else { + rounded as i64 }; + // 9. Return rounded × increment. - rounded * increment + rounded * (increment as i64) } /// Rounds provided number assuming that the increment is greater than 0. pub(crate) fn round_number_to_increment_as_if_positive( - nanos: f64, - increment_nanos: f64, + nanos: u64, + increment: u64, rounding_mode: TemporalRoundingMode, -) -> f64 { +) -> u64 { // 1. Let quotient be x / increment. - let quotient = nanos / increment_nanos; // 2. Let unsignedRoundingMode be GetUnsignedRoundingMode(roundingMode, false). let unsigned_rounding_mode = rounding_mode.get_unsigned_round_mode(false); // 3. Let r1 be the largest integer such that r1 ≤ quotient. - let r1 = quotient.floor(); + let r1 = nanos / increment; // 4. Let r2 be the smallest integer such that r2 > quotient. - let r2 = quotient.ceil(); + let r2 = nanos / increment + 1; // 5. Let rounded be ApplyUnsignedRoundingMode(quotient, r1, r2, unsignedRoundingMode). - let rounded = apply_unsigned_rounding_mode(quotient, r1, r2, unsigned_rounding_mode); + let rounded = apply_unsigned_rounding_mode(nanos, increment, r1, r2, unsigned_rounding_mode); // 6. Return rounded × increment. - rounded * increment_nanos + rounded * increment } pub(crate) fn validate_temporal_rounding_increment(