diff --git a/src/components/calendar.rs b/src/components/calendar.rs index 5edea849..d4817bcb 100644 --- a/src/components/calendar.rs +++ b/src/components/calendar.rs @@ -408,7 +408,7 @@ impl Calendar { duration.years(), duration.months(), duration.weeks(), - duration.days() + balance_days, + duration.days().checked_add(&balance_days)?, ), overflow, )?; diff --git a/src/components/date.rs b/src/components/date.rs index bb89b949..7755a02a 100644 --- a/src/components/date.rs +++ b/src/components/date.rs @@ -14,7 +14,8 @@ use crate::{ TemporalUnit, }, parsers::parse_date_time, - TemporalError, TemporalFields, TemporalResult, TemporalUnwrap, + primitive::FiniteF64, + Sign, TemporalError, TemporalFields, TemporalResult, TemporalUnwrap, }; use std::str::FromStr; @@ -79,13 +80,20 @@ impl Date { // 4. Let overflow be ? ToTemporalOverflow(options). // 5. Let norm be NormalizeTimeDuration(duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]]). // 6. Let days be duration.[[Days]] + BalanceTimeDuration(norm, "day").[[Days]]. - let days = duration.days() - + TimeDuration::from_normalized(duration.time().to_normalized(), TemporalUnit::Day)?.0; + let days = duration.days().checked_add( + &TimeDuration::from_normalized(duration.time().to_normalized(), TemporalUnit::Day)?.0, + )?; // 7. Let result be ? AddISODate(plainDate.[[ISOYear]], plainDate.[[ISOMonth]], plainDate.[[ISODay]], 0, 0, 0, days, overflow). - let result = self - .iso - .add_date_duration(&DateDuration::new(0f64, 0f64, 0f64, days)?, overflow)?; + let result = self.iso.add_date_duration( + &DateDuration::new( + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + days, + )?, + overflow, + )?; Ok(Self::new_unchecked(result, self.calendar().clone())) } @@ -109,10 +117,10 @@ impl Date { if largest_unit == TemporalUnit::Day { let days = self.days_until(other); return Ok(Duration::from(DateDuration::new( - 0f64, - 0f64, - 0f64, - f64::from(days), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::from(days), )?)); } @@ -179,14 +187,11 @@ impl Date { duration.date() }; - let sign = f64::from(sign as i8); // 13. Return ! CreateTemporalDuration(sign × duration.[[Years]], sign × duration.[[Months]], sign × duration.[[Weeks]], sign × duration.[[Days]], 0, 0, 0, 0, 0, 0). - Ok(Duration::from(DateDuration::new( - date_duration.years * sign, - date_duration.months * sign, - date_duration.weeks * sign, - date_duration.days * sign, - )?)) + match sign { + Sign::Positive | Sign::Zero => Ok(Duration::from(date_duration)), + Sign::Negative => Ok(Duration::from(date_duration.negated())), + } } } diff --git a/src/components/datetime.rs b/src/components/datetime.rs index 640b2d11..671ebcf8 100644 --- a/src/components/datetime.rs +++ b/src/components/datetime.rs @@ -11,6 +11,7 @@ use crate::{ temporal_assert, Sign, TemporalError, TemporalResult, TemporalUnwrap, }; +use num_traits::AsPrimitive; use std::{cmp::Ordering, str::FromStr}; use tinystr::TinyAsciiStr; @@ -167,7 +168,7 @@ impl DateTime { // a. Let normWithDays be ? Add24HourDaysToNormalizedTimeDuration(diff.[[NormalizedTime]], diff.[[Days]]). let norm_with_days = diff .normalized_time_duration() - .add_days(diff.date().days as i64)?; + .add_days(diff.date().days.as_())?; // b. Let timeResult be ! BalanceTimeDuration(normWithDays, largestUnit). let (days, time_duration) = TimeDuration::from_normalized(norm_with_days, options.largest_unit)?; @@ -530,6 +531,7 @@ mod tests { components::{calendar::Calendar, duration::DateDuration, Duration}, iso::{IsoDate, IsoTime}, options::{DifferenceSettings, RoundingIncrement, TemporalRoundingMode, TemporalUnit}, + primitive::FiniteF64, }; use super::DateTime; @@ -565,7 +567,15 @@ mod tests { let result = pdt .add( - &Duration::from(DateDuration::new(0.0, 1.0, 0.0, 0.0).unwrap()), + &Duration::from( + DateDuration::new( + FiniteF64::default(), + FiniteF64(1.0), + FiniteF64::default(), + FiniteF64::default(), + ) + .unwrap(), + ), None, ) .unwrap(); @@ -582,7 +592,15 @@ mod tests { let result = pdt .subtract( - &Duration::from(DateDuration::new(0.0, 1.0, 0.0, 0.0).unwrap()), + &Duration::from( + DateDuration::new( + FiniteF64::default(), + FiniteF64(1.0), + FiniteF64::default(), + FiniteF64::default(), + ) + .unwrap(), + ), None, ) .unwrap(); @@ -597,7 +615,7 @@ mod tests { let dt = DateTime::new(2019, 10, 29, 10, 46, 38, 271, 986, 102, Calendar::default()).unwrap(); - let result = dt.subtract(&Duration::hour(12.0), None).unwrap(); + let result = dt.subtract(&Duration::hour(FiniteF64(12.0)), None).unwrap(); assert_eq!( result.iso.date, @@ -619,7 +637,7 @@ mod tests { } ); - let result = dt.add(&Duration::hour(-12.0), None).unwrap(); + let result = dt.add(&Duration::hour(FiniteF64(-12.0)), None).unwrap(); assert_eq!( result.iso.date, diff --git a/src/components/duration.rs b/src/components/duration.rs index 4952c050..a7d9562b 100644 --- a/src/components/duration.rs +++ b/src/components/duration.rs @@ -4,9 +4,11 @@ use crate::{ components::{DateTime, Time}, iso::{IsoDateTime, IsoTime}, options::{RelativeTo, ResolvedRoundingOptions, RoundingOptions, TemporalUnit}, + primitive::FiniteF64, temporal_assert, Sign, TemporalError, TemporalResult, }; use ixdtf::parsers::{records::TimeDurationRecord, IsoDurationParser}; +use num_traits::AsPrimitive; use std::str::FromStr; use self::normalized::NormalizedTimeDuration; @@ -23,6 +25,31 @@ pub use date::DateDuration; #[doc(inline)] pub use time::TimeDuration; +/// A `PartialDuration` is a Duration that may have fields not set. +#[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)] +pub struct PartialDuration { + /// A potentially existent `years` field. + pub years: Option, + /// A potentially existent `months` field. + pub months: Option, + /// A potentially existent `weeks` field. + pub weeks: Option, + /// A potentially existent `days` field. + pub days: Option, + /// A potentially existent `hours` field. + pub hours: Option, + /// A potentially existent `minutes` field. + pub minutes: Option, + /// A potentially existent `seconds` field. + pub seconds: Option, + /// A potentially existent `milliseconds` field. + pub milliseconds: Option, + /// A potentially existent `microseconds` field. + pub microseconds: Option, + /// A potentially existent `nanoseconds` field. + pub nanoseconds: Option, +} + /// The native Rust implementation of `Temporal.Duration`. /// /// `Duration` is made up of a `DateDuration` and `TimeDuration` as primarily @@ -47,10 +74,17 @@ pub struct Duration { #[cfg(test)] impl Duration { - pub(crate) fn hour(value: f64) -> Self { + pub(crate) fn hour(value: FiniteF64) -> Self { Self::new_unchecked( DateDuration::default(), - TimeDuration::new_unchecked(value, 0.0, 0.0, 0.0, 0.0, 0.0), + TimeDuration::new_unchecked( + value, + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + ), ) } } @@ -67,7 +101,7 @@ impl Duration { /// Returns the a `Vec` of the fields values. #[inline] #[must_use] - pub(crate) fn fields(&self) -> Vec { + pub(crate) fn fields(&self) -> Vec { Vec::from(&[ self.years(), self.months(), @@ -108,16 +142,16 @@ impl Duration { /// Creates a new validated `Duration`. #[allow(clippy::too_many_arguments)] pub fn new( - years: f64, - months: f64, - weeks: f64, - days: f64, - hours: f64, - minutes: f64, - seconds: f64, - milliseconds: f64, - microseconds: f64, - nanoseconds: f64, + years: FiniteF64, + months: FiniteF64, + weeks: FiniteF64, + days: FiniteF64, + hours: FiniteF64, + minutes: FiniteF64, + seconds: FiniteF64, + milliseconds: FiniteF64, + microseconds: FiniteF64, + nanoseconds: FiniteF64, ) -> TemporalResult { let duration = Self::new_unchecked( DateDuration::new_unchecked(years, months, weeks, days), @@ -147,47 +181,40 @@ impl Duration { Ok(duration) } - /// Creates a partial `Duration` with all fields set to `NaN`. - #[must_use] - pub const fn partial() -> Self { - Self { - date: DateDuration::partial(), - time: TimeDuration::partial(), - } - } - /// Creates a `Duration` from a provided a day and a `TimeDuration`. /// /// Note: `TimeDuration` records can store a day value to deal with overflow. #[must_use] - pub fn from_day_and_time(day: f64, time: &TimeDuration) -> Self { + pub fn from_day_and_time(day: FiniteF64, time: &TimeDuration) -> Self { Self { - date: DateDuration::new_unchecked(0.0, 0.0, 0.0, day), + date: DateDuration::new_unchecked( + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + day, + ), time: *time, } } - /// Creates a new valid `Duration` from a partial `Duration`. - pub fn from_partial(partial: &Duration) -> TemporalResult { - let duration = Self { - date: DateDuration::from_partial(partial.date()), - time: TimeDuration::from_partial(partial.time()), - }; - if !is_valid_duration( - duration.years(), - duration.months(), - duration.weeks(), - duration.days(), - duration.hours(), - duration.minutes(), - duration.seconds(), - duration.milliseconds(), - duration.microseconds(), - duration.nanoseconds(), - ) { - return Err(TemporalError::range().with_message("Duration was not valid.")); + /// Creates a `Duration` from a provided `PartialDuration`. + pub fn from_partial_duration(partial: PartialDuration) -> TemporalResult { + if partial == PartialDuration::default() { + return Err(TemporalError::r#type() + .with_message("PartialDuration cannot have all empty fields.")); } - Ok(duration) + Self::new( + partial.years.unwrap_or_default(), + partial.months.unwrap_or_default(), + partial.weeks.unwrap_or_default(), + partial.days.unwrap_or_default(), + partial.hours.unwrap_or_default(), + partial.minutes.unwrap_or_default(), + partial.seconds.unwrap_or_default(), + partial.milliseconds.unwrap_or_default(), + partial.microseconds.unwrap_or_default(), + partial.nanoseconds.unwrap_or_default(), + ) } /// Return if the Durations values are within their valid ranges. @@ -221,133 +248,73 @@ impl Duration { self.time = time; } - /// Set the value for `years`. - #[inline] - pub fn set_years(&mut self, y: f64) { - self.date.years = y; - } - /// Returns the `years` field of duration. #[inline] #[must_use] - pub const fn years(&self) -> f64 { + pub const fn years(&self) -> FiniteF64 { 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 { + pub const fn months(&self) -> FiniteF64 { 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 { + pub const fn weeks(&self) -> FiniteF64 { 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 { + pub const fn days(&self) -> FiniteF64 { 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 { + pub const fn hours(&self) -> FiniteF64 { 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 { + pub const fn minutes(&self) -> FiniteF64 { 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 { + pub const fn seconds(&self) -> FiniteF64 { 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 { + pub const fn milliseconds(&self) -> FiniteF64 { 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 { + pub const fn microseconds(&self) -> FiniteF64 { 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 { + pub const fn nanoseconds(&self) -> FiniteF64 { self.time.nanoseconds } } @@ -417,7 +384,8 @@ impl Duration { // 29. Let normResult be ? AddNormalizedTimeDuration(norm1, norm2). // 30. Set normResult to ? Add24HourDaysToNormalizedTimeDuration(normResult, d1 + d2). - let result = (norm_one + norm_two)?.add_days((self.days() + other.days()) as i64)?; + let result = + (norm_one + norm_two)?.add_days((self.days().checked_add(&other.days())?).as_())?; // 31. Let result be ? BalanceTimeDuration(normResult, largestUnit). let (result_days, result_time) = TimeDuration::from_normalized(result, largest_unit)?; @@ -549,7 +517,7 @@ impl Duration { self.years(), self.months(), self.weeks(), - self.days() + f64::from(balanced_days), + self.days().checked_add(&FiniteF64::from(balanced_days))?, )?; // c. Let targetDate be ? AddDate(calendarRec, plainRelativeTo, dateDuration). @@ -593,7 +561,7 @@ impl Duration { // roundRecord.[[NormalizedDuration]].[[Days]]). let norm_with_days = round_record .normalized_time_duration() - .add_days(round_record.date().days as i64)?; + .add_days(round_record.date().days.as_())?; // e. Let balanceResult be ? BalanceTimeDuration(normWithDays, largestUnit). let (balanced_days, balanced_time) = TimeDuration::from_normalized(norm_with_days, resolved_options.largest_unit)?; @@ -610,21 +578,22 @@ impl Duration { // TODO: Update, optimize, and fix the below. is_valid_duration should probably be generic over a T. +// NOTE: Can FiniteF64 optimize the duration_validation /// Utility function to check whether the `Duration` fields are valid. #[inline] #[must_use] #[allow(clippy::too_many_arguments)] pub(crate) fn is_valid_duration( - years: f64, - months: f64, - weeks: f64, - days: f64, - hours: f64, - minutes: f64, - seconds: f64, - milliseconds: f64, - microseconds: f64, - nanoseconds: f64, + years: FiniteF64, + months: FiniteF64, + weeks: FiniteF64, + days: FiniteF64, + hours: FiniteF64, + minutes: FiniteF64, + seconds: FiniteF64, + milliseconds: FiniteF64, + microseconds: FiniteF64, + nanoseconds: FiniteF64, ) -> bool { // 1. Let sign be ! DurationSign(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds). let set = vec![ @@ -642,10 +611,8 @@ pub(crate) fn is_valid_duration( 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 { + // FiniteF64 must always be finite. // 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 == Sign::Positive { return false; @@ -676,14 +643,16 @@ pub(crate) fn is_valid_duration( // in C++ with an implementation of std::remquo() with sufficient bits in the quotient. // String manipulation will also give an exact result, since the multiplication is by a power of 10. // Seconds part - let normalized_seconds = days.mul_add( + let normalized_seconds = days.0.mul_add( 86_400.0, - hours.mul_add(3600.0, minutes.mul_add(60.0, seconds)), + hours.0.mul_add(3600.0, minutes.0.mul_add(60.0, seconds.0)), ); // Subseconds part - let normalized_subseconds_parts = milliseconds.mul_add( + let normalized_subseconds_parts = milliseconds.0.mul_add( 10e-3, - microseconds.mul_add(10e-6, nanoseconds.mul_add(10e-9, 0.0)), + microseconds + .0 + .mul_add(10e-6, nanoseconds.0.mul_add(10e-9, 0.0)), ); let normalized_seconds = normalized_seconds + normalized_subseconds_parts; @@ -701,7 +670,7 @@ pub(crate) fn is_valid_duration( /// Equivalent: 7.5.10 `DurationSign ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds )` #[inline] #[must_use] -fn duration_sign(set: &Vec) -> Sign { +fn duration_sign(set: &Vec) -> Sign { // 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. @@ -825,16 +794,16 @@ impl FromStr for Duration { let sign = f64::from(parse_record.sign as i8); Self::new( - f64::from(years) * sign, - f64::from(months) * sign, - f64::from(weeks) * sign, - f64::from(days) * sign, - hours * sign, - minutes * sign, - seconds * sign, - millis * sign, - micros * sign, - nanos * sign, + FiniteF64::from(years).copysign(sign), + FiniteF64::from(months).copysign(sign), + FiniteF64::from(weeks).copysign(sign), + FiniteF64::from(days).copysign(sign), + FiniteF64::try_from(hours)?.copysign(sign), + FiniteF64::try_from(minutes)?.copysign(sign), + FiniteF64::try_from(seconds)?.copysign(sign), + FiniteF64::try_from(millis)?.copysign(sign), + FiniteF64::try_from(micros)?.copysign(sign), + FiniteF64::try_from(nanos)?.copysign(sign), ) } } diff --git a/src/components/duration/date.rs b/src/components/duration/date.rs index 110e81fd..88d8647e 100644 --- a/src/components/duration/date.rs +++ b/src/components/duration/date.rs @@ -1,10 +1,6 @@ //! Implementation of a `DateDuration` -use crate::{ - components::{Date, Duration}, - options::{ArithmeticOverflow, TemporalUnit}, - Sign, TemporalError, TemporalResult, TemporalUnwrap, -}; +use crate::{primitive::FiniteF64, Sign, TemporalError, TemporalResult}; /// `DateDuration` represents the [date duration record][spec] of the `Duration.` /// @@ -16,20 +12,25 @@ use crate::{ #[derive(Debug, Default, Clone, Copy)] pub struct DateDuration { /// `DateDuration`'s internal year value. - pub years: f64, + pub years: FiniteF64, /// `DateDuration`'s internal month value. - pub months: f64, + pub months: FiniteF64, /// `DateDuration`'s internal week value. - pub weeks: f64, + pub weeks: FiniteF64, /// `DateDuration`'s internal day value. - pub days: f64, + pub days: FiniteF64, } 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 { + pub(crate) const fn new_unchecked( + years: FiniteF64, + months: FiniteF64, + weeks: FiniteF64, + days: FiniteF64, + ) -> Self { Self { years, months, @@ -38,148 +39,10 @@ impl DateDuration { } } - /// 7.5.38 BalanceDateDurationRelative ( years, months, weeks, days, largestUnit, smallestUnit, plainRelativeTo, calendarRec ) - pub fn balance_relative( - &self, - largest_unit: TemporalUnit, - smallest_unit: TemporalUnit, - plain_relative_to: Option<&Date>, - ) -> TemporalResult { - // TODO: Confirm 1 or 5 based off response to issue. - // 1. Assert: If plainRelativeTo is not undefined, calendarRec is not undefined. - let plain_relative = plain_relative_to.temporal_unwrap()?; - - // 2. Let allZero be false. - // 3. If years = 0, and months = 0, and weeks = 0, and days = 0, set allZero to true. - let all_zero = - self.years == 0.0 && self.months == 0.0 && self.weeks == 0.0 && self.days == 0.0; - - // 4. If largestUnit is not one of "year", "month", or "week", or allZero is true, then - match largest_unit { - TemporalUnit::Year | TemporalUnit::Month | TemporalUnit::Week if !all_zero => {} - _ => { - // a. Return ! CreateDateDurationRecord(years, months, weeks, days). - return Ok(*self); - } - } - - // NOTE: See Step 1. - // 5. If plainRelativeTo is undefined, then - // a. Throw a RangeError exception. - // 6. Assert: CalendarMethodsRecordHasLookedUp(calendarRec, DATE-ADD) is true. - // 7. Assert: CalendarMethodsRecordHasLookedUp(calendarRec, DATE-UNTIL) is true. - // 8. Let untilOptions be OrdinaryObjectCreate(null). - // 9. Perform ! CreateDataPropertyOrThrow(untilOptions, "largestUnit", largestUnit). - - match largest_unit { - // 10. If largestUnit is "year", then - TemporalUnit::Year => { - // a. If smallestUnit is "week", then - if smallest_unit == TemporalUnit::Week { - // i. Assert: days = 0. - // ii. Let yearsMonthsDuration be ! CreateTemporalDuration(years, months, 0, 0, 0, 0, 0, 0, 0, 0). - let years_months = - Duration::from(Self::new_unchecked(self.years, self.months, 0.0, 0.0)); - - // iii. Let later be ? AddDate(calendarRec, plainRelativeTo, yearsMonthsDuration). - let later = plain_relative.calendar().date_add( - plain_relative, - &years_months, - ArithmeticOverflow::Constrain, - )?; - - // iv. Let untilResult be ? CalendarDateUntil(calendarRec, plainRelativeTo, later, untilOptions). - let until = plain_relative.calendar().date_until( - plain_relative, - &later, - largest_unit, - )?; - - // v. Return ? CreateDateDurationRecord(untilResult.[[Years]], untilResult.[[Months]], weeks, 0). - return Self::new(until.years(), until.months(), self.weeks, 0.0); - } - - // b. Let yearsMonthsWeeksDaysDuration be ! CreateTemporalDuration(years, months, weeks, days, 0, 0, 0, 0, 0, 0). - let years_months_weeks = Duration::from(*self); - - // c. Let later be ? AddDate(calendarRec, plainRelativeTo, yearsMonthsWeeksDaysDuration). - let later = plain_relative.calendar().date_add( - plain_relative, - &years_months_weeks, - ArithmeticOverflow::Constrain, - )?; - // d. Let untilResult be ? CalendarDateUntil(calendarRec, plainRelativeTo, later, untilOptions). - let until = - plain_relative - .calendar() - .date_until(plain_relative, &later, largest_unit)?; - // e. Return ! CreateDateDurationRecord(untilResult.[[Years]], untilResult.[[Months]], untilResult.[[Weeks]], untilResult.[[Days]]). - Self::new(until.years(), until.months(), until.weeks(), until.days()) - } - // 11. If largestUnit is "month", then - TemporalUnit::Month => { - // a. Assert: years = 0. - // b. If smallestUnit is "week", then - if smallest_unit == TemporalUnit::Week { - // i. Assert: days = 0. - // ii. Return ! CreateDateDurationRecord(0, months, weeks, 0). - return Self::new(0.0, self.months, self.weeks, 0.0); - } - - // c. Let monthsWeeksDaysDuration be ! CreateTemporalDuration(0, months, weeks, days, 0, 0, 0, 0, 0, 0). - let months_weeks_days = - Duration::from(Self::new_unchecked(0.0, self.months, self.weeks, self.days)); - - // d. Let later be ? AddDate(calendarRec, plainRelativeTo, monthsWeeksDaysDuration). - let later = plain_relative.calendar().date_add( - plain_relative, - &months_weeks_days, - ArithmeticOverflow::Constrain, - )?; - - // e. Let untilResult be ? CalendarDateUntil(calendarRec, plainRelativeTo, later, untilOptions). - let until = - plain_relative - .calendar() - .date_until(plain_relative, &later, largest_unit)?; - - // f. Return ! CreateDateDurationRecord(0, untilResult.[[Months]], untilResult.[[Weeks]], untilResult.[[Days]]). - Self::new(0.0, until.months(), until.weeks(), until.days()) - } - // 12. Assert: largestUnit is "week". - TemporalUnit::Week => { - // 13. Assert: years = 0. - // 14. Assert: months = 0. - // 15. Let weeksDaysDuration be ! CreateTemporalDuration(0, 0, weeks, days, 0, 0, 0, 0, 0, 0). - let weeks_days = - Duration::from(Self::new_unchecked(0.0, 0.0, self.weeks, self.days)); - - // 16. Let later be ? AddDate(calendarRec, plainRelativeTo, weeksDaysDuration). - let later = plain_relative.calendar().date_add( - plain_relative, - &weeks_days, - ArithmeticOverflow::Constrain, - )?; - - // 17. Let untilResult be ? CalendarDateUntil(calendarRec, plainRelativeTo, later, untilOptions). - let until = - plain_relative - .calendar() - .date_until(plain_relative, &later, largest_unit)?; - - // 18. Return ! CreateDateDurationRecord(0, 0, untilResult.[[Weeks]], untilResult.[[Days]]). - Self::new(0.0, 0.0, until.weeks(), until.days()) - } - _ => Err(TemporalError::general( - "largestUnit in BalanceDateDurationRelative exceeded possible values.", - )), - } - } - /// Returns the iterator for `DateDuration` #[inline] #[must_use] - pub(crate) fn fields(&self) -> Vec { + pub(crate) fn fields(&self) -> Vec { Vec::from(&[self.years, self.months, self.weeks, self.days]) } } @@ -187,61 +50,39 @@ impl DateDuration { impl DateDuration { /// Creates a new `DateDuration` with provided values. #[inline] - pub fn new(years: f64, months: f64, weeks: f64, days: f64) -> TemporalResult { + pub fn new( + years: FiniteF64, + months: FiniteF64, + weeks: FiniteF64, + days: FiniteF64, + ) -> TemporalResult { let result = Self::new_unchecked(years, months, weeks, days); - if !super::is_valid_duration(years, months, weeks, days, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) { + if !super::is_valid_duration( + years, + months, + weeks, + days, + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + ) { 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, - } - } - - /// Creates a `DateDuration` from a provided partial `DateDuration`. - #[must_use] - pub fn from_partial(partial: &DateDuration) -> Self { - Self { - years: if partial.years.is_nan() { - 0.0 - } else { - partial.years - }, - months: if partial.months.is_nan() { - 0.0 - } else { - partial.months - }, - weeks: if partial.weeks.is_nan() { - 0.0 - } else { - partial.weeks - }, - days: if partial.days.is_nan() { - 0.0 - } else { - partial.days - }, - } - } - /// Returns a negated `DateDuration`. #[inline] #[must_use] pub fn negated(&self) -> Self { Self { - years: self.years * -1.0, - months: self.months * -1.0, - weeks: self.weeks * -1.0, - days: self.days * -1.0, + years: self.years.negate(), + months: self.months.negate(), + weeks: self.weeks.negate(), + days: self.days.negate(), } } diff --git a/src/components/duration/normalized.rs b/src/components/duration/normalized.rs index 6e141046..ff54e04a 100644 --- a/src/components/duration/normalized.rs +++ b/src/components/duration/normalized.rs @@ -2,12 +2,13 @@ use std::{num::NonZeroU128, ops::Add}; -use num_traits::Euclid; +use num_traits::{AsPrimitive, Euclid}; use crate::{ components::{tz::TimeZone, Date, DateTime}, iso::IsoDate, options::{ArithmeticOverflow, ResolvedRoundingOptions, TemporalRoundingMode, TemporalUnit}, + primitive::FiniteF64, rounding::{IncrementRounder, Round}, TemporalError, TemporalResult, TemporalUnwrap, NS_PER_DAY, }; @@ -38,12 +39,12 @@ impl NormalizedTimeDuration { /// Equivalent: 7.5.20 NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds, nanoseconds ) pub(crate) fn from_time_duration(time: &TimeDuration) -> Self { // TODO: Determine if there is a loss in precision from casting. If so, times by 1,000 (calculate in picoseconds) than truncate? - let mut nanoseconds: i128 = (time.hours * NANOSECONDS_PER_HOUR) as i128; - nanoseconds += (time.minutes * NANOSECONDS_PER_MINUTE) as i128; - nanoseconds += (time.seconds * 1_000_000_000.0) as i128; - nanoseconds += (time.milliseconds * 1_000_000.0) as i128; - nanoseconds += (time.microseconds * 1_000.0) as i128; - nanoseconds += time.nanoseconds as i128; + let mut nanoseconds: i128 = (time.hours.0 * NANOSECONDS_PER_HOUR) as i128; + nanoseconds += (time.minutes.0 * NANOSECONDS_PER_MINUTE) as i128; + nanoseconds += (time.seconds.0 * 1_000_000_000.0) as i128; + nanoseconds += (time.milliseconds.0 * 1_000_000.0) as i128; + nanoseconds += (time.microseconds.0 * 1_000.0) as i128; + nanoseconds += time.nanoseconds.0 as i128; // NOTE(nekevss): Is it worth returning a `RangeError` below. debug_assert!(nanoseconds.abs() <= MAX_TIME_DURATION); Self(nanoseconds) @@ -230,7 +231,7 @@ impl NormalizedDurationRecord { TemporalUnit::Year => { // a. Let years be RoundNumberToIncrement(duration.[[Years]], increment, "trunc"). let years = IncrementRounder::from_potentially_negative_parts( - self.date().years, + self.date().years.0, options.increment.as_extended_increment(), )? .round(TemporalRoundingMode::Trunc); @@ -244,15 +245,25 @@ impl NormalizedDurationRecord { ( r1, r2, - DateDuration::new(r1 as f64, 0.0, 0.0, 0.0)?, - DateDuration::new(r2 as f64, 0.0, 0.0, 0.0)?, + DateDuration::new( + FiniteF64::try_from(r1)?, + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + )?, + DateDuration::new( + FiniteF64::try_from(r2)?, + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + )?, ) } // 2. Else if unit is "month", then TemporalUnit::Month => { // a. Let months be RoundNumberToIncrement(duration.[[Months]], increment, "trunc"). let months = IncrementRounder::from_potentially_negative_parts( - self.date().months, + self.date().months.0, options.increment.as_extended_increment(), )? .round(TemporalRoundingMode::Trunc); @@ -266,8 +277,18 @@ impl NormalizedDurationRecord { ( r1, r2, - DateDuration::new(self.date().years, r1 as f64, 0.0, 0.0)?, - DateDuration::new(self.date().years, r2 as f64, 0.0, 0.0)?, + DateDuration::new( + self.date().years, + FiniteF64::try_from(r1)?, + FiniteF64::default(), + FiniteF64::default(), + )?, + DateDuration::new( + self.date().years, + FiniteF64::try_from(r2)?, + FiniteF64::default(), + FiniteF64::default(), + )?, ) } // 3. Else if unit is "week", then @@ -276,17 +297,17 @@ impl NormalizedDurationRecord { // a. Let isoResult1 be BalanceISODate(dateTime.[[Year]] + duration.[[Years]], // dateTime.[[Month]] + duration.[[Months]], dateTime.[[Day]]). let iso_one = IsoDate::balance( - dt.iso_year() + self.date().years as i32, - i32::from(dt.iso_month()) + self.date().months as i32, + dt.iso_year() + self.date().years.as_date_value()?, + i32::from(dt.iso_month()) + self.date().months.as_date_value()?, i32::from(dt.iso_day()), ); // b. Let isoResult2 be BalanceISODate(dateTime.[[Year]] + duration.[[Years]], dateTime.[[Month]] + // duration.[[Months]], dateTime.[[Day]] + duration.[[Days]]). let iso_two = IsoDate::balance( - dt.iso_year() + self.date().years as i32, - i32::from(dt.iso_month()) + self.date().months as i32, - i32::from(dt.iso_day()) + self.date().days as i32, + dt.iso_year() + self.date().years.as_date_value()?, + i32::from(dt.iso_month()) + self.date().months.as_date_value()?, + i32::from(dt.iso_day()) + self.date().days.as_date_value()?, ); // c. Let weeksStart be ! CreateTemporalDate(isoResult1.[[Year]], isoResult1.[[Month]], isoResult1.[[Day]], @@ -317,7 +338,7 @@ impl NormalizedDurationRecord { // h. Let weeks be RoundNumberToIncrement(duration.[[Weeks]] + untilResult.[[Weeks]], increment, "trunc"). let weeks = IncrementRounder::from_potentially_negative_parts( - self.date().weeks + until_result.weeks(), + self.date().weeks.checked_add(&until_result.weeks())?.0, options.increment.as_extended_increment(), )? .round(TemporalRoundingMode::Trunc); @@ -332,8 +353,18 @@ impl NormalizedDurationRecord { ( r1, r2, - DateDuration::new(self.date().years, self.date().months, r1 as f64, 0.0)?, - DateDuration::new(self.date().years, self.date().months, r2 as f64, 0.0)?, + DateDuration::new( + self.date().years, + self.date().months, + FiniteF64::try_from(r1)?, + FiniteF64::default(), + )?, + DateDuration::new( + self.date().years, + self.date().months, + FiniteF64::try_from(r2)?, + FiniteF64::default(), + )?, ) } TemporalUnit::Day => { @@ -341,7 +372,7 @@ impl NormalizedDurationRecord { // a. Assert: unit is "day". // b. Let days be RoundNumberToIncrement(duration.[[Days]], increment, "trunc"). let days = IncrementRounder::from_potentially_negative_parts( - self.date().days, + self.date().days.0, options.increment.as_extended_increment(), )? .round(TemporalRoundingMode::Trunc); @@ -359,13 +390,13 @@ impl NormalizedDurationRecord { self.date().years, self.date().months, self.date().weeks, - r1 as f64, + FiniteF64::try_from(r1)?, )?, DateDuration::new( self.date().years, self.date().months, self.date().weeks, - r2 as f64, + FiniteF64::try_from(r2)?, )?, ) } @@ -502,7 +533,7 @@ impl NormalizedDurationRecord { // 2. Let norm be ! Add24HourDaysToNormalizedTimeDuration(duration.[[NormalizedTime]], duration.[[Days]]). let norm = self .normalized_time_duration() - .add_days(self.date().days as i64)?; + .add_days(self.date().days.as_())?; // 3. Let unitLength be the value in the "Length in Nanoseconds" column of the row of Table 22 whose "Singular" column contains smallestUnit. let unit_length = options.smallest_unit.as_nanoseconds().temporal_unwrap()?; @@ -555,7 +586,7 @@ impl NormalizedDurationRecord { self.date().years, self.date().months, self.date().weeks, - days as f64, + FiniteF64::try_from(days)?, )?, remainder, )?; @@ -610,10 +641,13 @@ impl NormalizedDurationRecord { // 1. Let years be duration.[[Years]] + sign. // 2. Let endDuration be ? CreateNormalizedDurationRecord(years, 0, 0, 0, ZeroTimeDuration()). DateDuration::new( - duration.date().years + f64::from(sign.as_sign_multiplier()), - 0.0, - 0.0, - 0.0, + duration + .date() + .years + .checked_add(&FiniteF64::from(sign.as_sign_multiplier()))?, + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), )? } // ii. Else if unit is "month", then @@ -622,9 +656,12 @@ impl NormalizedDurationRecord { // 2. Let endDuration be ? CreateNormalizedDurationRecord(duration.[[Years]], months, 0, 0, ZeroTimeDuration()). DateDuration::new( duration.date().years, - duration.date().months + f64::from(sign.as_sign_multiplier()), - 0.0, - 0.0, + duration + .date() + .months + .checked_add(&FiniteF64::from(sign.as_sign_multiplier()))?, + FiniteF64::default(), + FiniteF64::default(), )? } // iii. Else if unit is "week", then @@ -634,8 +671,11 @@ impl NormalizedDurationRecord { DateDuration::new( duration.date().years, duration.date().months, - duration.date().weeks + f64::from(sign.as_sign_multiplier()), - 0.0, + duration + .date() + .weeks + .checked_add(&FiniteF64::from(sign.as_sign_multiplier()))?, + FiniteF64::default(), )? } // iv. Else, @@ -647,7 +687,10 @@ impl NormalizedDurationRecord { duration.date().years, duration.date().months, duration.date().weeks, - duration.date().days + f64::from(sign.as_sign_multiplier()), + duration + .date() + .days + .checked_add(&FiniteF64::from(sign.as_sign_multiplier()))?, )? } _ => unreachable!(), diff --git a/src/components/duration/tests.rs b/src/components/duration/tests.rs index 0edf2164..6b3bdea5 100644 --- a/src/components/duration/tests.rs +++ b/src/components/duration/tests.rs @@ -15,15 +15,26 @@ fn get_round_result( .unwrap() .fields() .iter() - .map(|f| *f as i32) + .map(|f| f.as_date_value().unwrap()) .collect::>() } // roundingmode-floor.js #[test] fn basic_positive_floor_rounding_v2() { - let test_duration = - Duration::new(5.0, 6.0, 7.0, 8.0, 40.0, 30.0, 20.0, 123.0, 987.0, 500.0).unwrap(); + let test_duration = Duration::new( + FiniteF64(5.0), + FiniteF64(6.0), + FiniteF64(7.0), + FiniteF64(8.0), + FiniteF64(40.0), + FiniteF64(30.0), + FiniteF64(20.0), + FiniteF64(123.0), + FiniteF64(987.0), + FiniteF64(500.0), + ) + .unwrap(); let forward_date = Date::new( 2020, 4, @@ -89,8 +100,19 @@ fn basic_positive_floor_rounding_v2() { #[test] fn basic_negative_floor_rounding_v2() { // Test setup - let test_duration = - Duration::new(5.0, 6.0, 7.0, 8.0, 40.0, 30.0, 20.0, 123.0, 987.0, 500.0).unwrap(); + let test_duration = Duration::new( + FiniteF64(5.0), + FiniteF64(6.0), + FiniteF64(7.0), + FiniteF64(8.0), + FiniteF64(40.0), + FiniteF64(30.0), + FiniteF64(20.0), + FiniteF64(123.0), + FiniteF64(987.0), + FiniteF64(500.0), + ) + .unwrap(); let backward_date = Date::new( 2020, 12, @@ -156,8 +178,19 @@ fn basic_negative_floor_rounding_v2() { // roundingmode-ceil.js #[test] fn basic_positive_ceil_rounding() { - let test_duration = - Duration::new(5.0, 6.0, 7.0, 8.0, 40.0, 30.0, 20.0, 123.0, 987.0, 500.0).unwrap(); + let test_duration = Duration::new( + FiniteF64(5.0), + FiniteF64(6.0), + FiniteF64(7.0), + FiniteF64(8.0), + FiniteF64(40.0), + FiniteF64(30.0), + FiniteF64(20.0), + FiniteF64(123.0), + FiniteF64(987.0), + FiniteF64(500.0), + ) + .unwrap(); let forward_date = Date::new( 2020, 4, @@ -222,8 +255,19 @@ fn basic_positive_ceil_rounding() { #[test] fn basic_negative_ceil_rounding() { - let test_duration = - Duration::new(5.0, 6.0, 7.0, 8.0, 40.0, 30.0, 20.0, 123.0, 987.0, 500.0).unwrap(); + let test_duration = Duration::new( + FiniteF64(5.0), + FiniteF64(6.0), + FiniteF64(7.0), + FiniteF64(8.0), + FiniteF64(40.0), + FiniteF64(30.0), + FiniteF64(20.0), + FiniteF64(123.0), + FiniteF64(987.0), + FiniteF64(500.0), + ) + .unwrap(); let backward_date = Date::new( 2020, 12, @@ -288,8 +332,19 @@ fn basic_negative_ceil_rounding() { // roundingmode-expand.js #[test] fn basic_positive_expand_rounding() { - let test_duration = - Duration::new(5.0, 6.0, 7.0, 8.0, 40.0, 30.0, 20.0, 123.0, 987.0, 500.0).unwrap(); + let test_duration = Duration::new( + FiniteF64(5.0), + FiniteF64(6.0), + FiniteF64(7.0), + FiniteF64(8.0), + FiniteF64(40.0), + FiniteF64(30.0), + FiniteF64(20.0), + FiniteF64(123.0), + FiniteF64(987.0), + FiniteF64(500.0), + ) + .unwrap(); let forward_date = Date::new( 2020, 4, @@ -354,8 +409,19 @@ fn basic_positive_expand_rounding() { #[test] fn basic_negative_expand_rounding() { - let test_duration = - Duration::new(5.0, 6.0, 7.0, 8.0, 40.0, 30.0, 20.0, 123.0, 987.0, 500.0).unwrap(); + let test_duration = Duration::new( + FiniteF64(5.0), + FiniteF64(6.0), + FiniteF64(7.0), + FiniteF64(8.0), + FiniteF64(40.0), + FiniteF64(30.0), + FiniteF64(20.0), + FiniteF64(123.0), + FiniteF64(987.0), + FiniteF64(500.0), + ) + .unwrap(); let backward_date = Date::new( 2020, @@ -422,7 +488,15 @@ fn basic_negative_expand_rounding() { // test262/test/built-ins/Temporal/Duration/prototype/round/roundingincrement-non-integer.js #[test] fn rounding_increment_non_integer() { - let test_duration = Duration::from(DateDuration::new(0.0, 0.0, 0.0, 1.0).unwrap()); + let test_duration = Duration::from( + DateDuration::new( + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64(1.0), + ) + .unwrap(), + ); let binding = Date::new( 2000, 1, @@ -450,7 +524,18 @@ fn rounding_increment_non_integer() { assert_eq!( result.fields(), - &[0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + &[ + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64(2.0), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default() + ] ); let _ = options @@ -459,19 +544,66 @@ fn rounding_increment_non_integer() { let result = test_duration.round(options, &relative_to).unwrap(); assert_eq!( result.fields(), - &[0.0, 0.0, 0.0, 1e9, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + &[ + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64(1e9), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default() + ] ); } #[test] fn basic_add_duration() { - let base = Duration::new(0.0, 0.0, 0.0, 1.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0).unwrap(); - let other = Duration::new(0.0, 0.0, 0.0, 2.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0).unwrap(); + let base = Duration::new( + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64(1.0), + FiniteF64::default(), + FiniteF64(5.0), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + ) + .unwrap(); + let other = Duration::new( + FiniteF64(0.0), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64(2.0), + FiniteF64::default(), + FiniteF64(5.0), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + ) + .unwrap(); let result = base.add(&other).unwrap(); assert_eq!(result.days(), 3.0); assert_eq!(result.minutes(), 10.0); - let other = Duration::new(0.0, 0.0, 0.0, -3.0, 0.0, -15.0, 0.0, 0.0, 0.0, 0.0).unwrap(); + let other = Duration::new( + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64(-3.0), + FiniteF64::default(), + FiniteF64(-15.0), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + ) + .unwrap(); let result = base.add(&other).unwrap(); assert_eq!(result.days(), -2.0); assert_eq!(result.minutes(), -10.0); @@ -479,14 +611,64 @@ fn basic_add_duration() { #[test] fn basic_subtract_duration() { - let base = Duration::new(0.0, 0.0, 0.0, 3.0, 0.0, 15.0, 0.0, 0.0, 0.0, 0.0).unwrap(); - let other = Duration::new(0.0, 0.0, 0.0, 1.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0).unwrap(); + let base = Duration::new( + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64(3.0), + FiniteF64::default(), + FiniteF64(15.0), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + ) + .unwrap(); + let other = Duration::new( + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64(1.0), + FiniteF64::default(), + FiniteF64(5.0), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + ) + .unwrap(); let result = base.subtract(&other).unwrap(); assert_eq!(result.days(), 2.0); assert_eq!(result.minutes(), 10.0); - let other = Duration::new(0.0, 0.0, 0.0, -3.0, 0.0, -15.0, 0.0, 0.0, 0.0, 0.0).unwrap(); + let other = Duration::new( + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64(-3.0), + FiniteF64::default(), + FiniteF64(-15.0), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + ) + .unwrap(); let result = base.subtract(&other).unwrap(); assert_eq!(result.days(), 6.0); assert_eq!(result.minutes(), 30.0); } + +#[test] +fn partial_duration_empty() { + let err = Duration::from_partial_duration(PartialDuration::default()); + assert!(err.is_err()) +} + +#[test] +fn partial_duration_values() { + let mut partial = PartialDuration::default(); + let _ = partial.years.insert(FiniteF64(20.0)); + let result = Duration::from_partial_duration(partial).unwrap(); + assert_eq!(result.years(), 20.0); +} diff --git a/src/components/duration/time.rs b/src/components/duration/time.rs index 67aa1d7a..b8d2eace 100644 --- a/src/components/duration/time.rs +++ b/src/components/duration/time.rs @@ -4,6 +4,7 @@ use std::num::NonZeroU128; use crate::{ options::{ResolvedRoundingOptions, TemporalUnit}, + primitive::FiniteF64, rounding::{IncrementRounder, Round}, temporal_assert, TemporalError, TemporalResult, TemporalUnwrap, }; @@ -14,7 +15,7 @@ use super::{ DateDuration, }; -use num_traits::{Euclid, FromPrimitive, MulAdd}; +use num_traits::{Euclid, FromPrimitive}; /// `TimeDuration` represents the [Time Duration record][spec] of the `Duration.` /// @@ -26,17 +27,17 @@ use num_traits::{Euclid, FromPrimitive, MulAdd}; #[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)] pub struct TimeDuration { /// `TimeDuration`'s internal hour value. - pub hours: f64, + pub hours: FiniteF64, /// `TimeDuration`'s internal minute value. - pub minutes: f64, + pub minutes: FiniteF64, /// `TimeDuration`'s internal second value. - pub seconds: f64, + pub seconds: FiniteF64, /// `TimeDuration`'s internal millisecond value. - pub milliseconds: f64, + pub milliseconds: FiniteF64, /// `TimeDuration`'s internal microsecond value. - pub microseconds: f64, + pub microseconds: FiniteF64, /// `TimeDuration`'s internal nanosecond value. - pub nanoseconds: f64, + pub nanoseconds: FiniteF64, } // ==== TimeDuration Private API ==== @@ -44,12 +45,12 @@ 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, + hours: FiniteF64, + minutes: FiniteF64, + seconds: FiniteF64, + milliseconds: FiniteF64, + microseconds: FiniteF64, + nanoseconds: FiniteF64, ) -> Self { Self { hours, @@ -71,7 +72,7 @@ impl TimeDuration { pub(crate) fn from_normalized( norm: NormalizedTimeDuration, largest_unit: TemporalUnit, - ) -> TemporalResult<(f64, Self)> { + ) -> TemporalResult<(FiniteF64, Self)> { // 1. Let days, hours, minutes, seconds, milliseconds, and microseconds be 0. let mut days = 0; let mut hours = 0; @@ -196,21 +197,21 @@ impl TimeDuration { // NOTE: days may have the potentially to exceed i64 // 12. Return ! CreateTimeDurationRecord(days × sign, hours × sign, minutes × sign, seconds × sign, milliseconds × sign, microseconds × sign, nanoseconds × sign). - let days = (days as i64).mul_add(sign.into(), 0); + let days = FiniteF64::try_from(days as f64)?.copysign(sign.into()); let result = Self::new_unchecked( - (hours as i32).mul_add(sign, 0).into(), - (minutes as i32).mul_add(sign, 0).into(), - (seconds as i32).mul_add(sign, 0).into(), - (milliseconds as i32).mul_add(sign, 0).into(), - (microseconds as i32).mul_add(sign, 0).into(), - (nanoseconds as i32).mul_add(sign, 0).into(), + FiniteF64::try_from(hours)?.copysign(f64::from(sign)), + FiniteF64::try_from(minutes)?.copysign(f64::from(sign)), + FiniteF64::try_from(seconds)?.copysign(f64::from(sign)), + FiniteF64::try_from(milliseconds)?.copysign(f64::from(sign)), + FiniteF64::try_from(microseconds)?.copysign(f64::from(sign)), + FiniteF64::try_from(nanoseconds)?.copysign(f64::from(sign)), ); if !is_valid_duration( - 0.0, - 0.0, - 0.0, - days as f64, + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + days, result.hours, result.minutes, result.seconds, @@ -222,7 +223,7 @@ impl TimeDuration { } // TODO: Remove cast below. - Ok((days as f64, result)) + Ok((days, result)) } /// Returns this `TimeDuration` as a `NormalizedTimeDuration`. @@ -234,7 +235,7 @@ impl TimeDuration { /// Returns the value of `TimeDuration`'s fields. #[inline] #[must_use] - pub(crate) fn fields(&self) -> Vec { + pub(crate) fn fields(&self) -> Vec { Vec::from(&[ self.hours, self.minutes, @@ -251,12 +252,12 @@ impl TimeDuration { impl TimeDuration { /// Creates a new validated `TimeDuration`. pub fn new( - hours: f64, - minutes: f64, - seconds: f64, - milliseconds: f64, - microseconds: f64, - nanoseconds: f64, + hours: FiniteF64, + minutes: FiniteF64, + seconds: FiniteF64, + milliseconds: FiniteF64, + microseconds: FiniteF64, + nanoseconds: FiniteF64, ) -> TemporalResult { let result = Self::new_unchecked( hours, @@ -267,10 +268,10 @@ impl TimeDuration { nanoseconds, ); if !is_valid_duration( - 0.0, - 0.0, - 0.0, - 0.0, + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), hours, minutes, seconds, @@ -285,55 +286,6 @@ impl 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, - } - } - - /// Creates a `TimeDuration` from a provided partial `TimeDuration`. - #[must_use] - pub fn from_partial(partial: &TimeDuration) -> Self { - Self { - hours: if partial.hours.is_nan() { - 0.0 - } else { - partial.hours - }, - minutes: if partial.minutes.is_nan() { - 0.0 - } else { - partial.minutes - }, - seconds: if partial.seconds.is_nan() { - 0.0 - } else { - partial.seconds - }, - milliseconds: if partial.milliseconds.is_nan() { - 0.0 - } else { - partial.milliseconds - }, - microseconds: if partial.microseconds.is_nan() { - 0.0 - } else { - partial.microseconds - }, - nanoseconds: if partial.nanoseconds.is_nan() { - 0.0 - } else { - partial.nanoseconds - }, - } - } /// Returns a new `TimeDuration` representing the absolute value of the current. #[inline] #[must_use] @@ -353,12 +305,12 @@ impl TimeDuration { #[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, + hours: self.hours.negate(), + minutes: self.minutes.negate(), + seconds: self.seconds.negate(), + milliseconds: self.milliseconds.negate(), + microseconds: self.microseconds.negate(), + nanoseconds: self.nanoseconds.negate(), } } @@ -380,7 +332,7 @@ impl TimeDuration { impl TimeDuration { // TODO: Maybe move to `NormalizedTimeDuration` pub(crate) fn round( - days: f64, + days: FiniteF64, norm: &NormalizedTimeDuration, options: ResolvedRoundingOptions, ) -> TemporalResult<(NormalizedDurationRecord, Option)> { @@ -389,21 +341,19 @@ impl TimeDuration { // 2. If unit is "day", then TemporalUnit::Day => { // a. Let fractionalDays be days + DivideNormalizedTimeDuration(norm, nsPerDay). - let fractional_days = days + norm.as_fractional_days(); + let fractional_days = days.checked_add(&FiniteF64(norm.as_fractional_days()))?; // b. Set days to RoundNumberToIncrement(fractionalDays, increment, roundingMode). let days = IncrementRounder::from_potentially_negative_parts( - fractional_days, + fractional_days.0, options.increment.as_extended_increment(), )? .round(options.rounding_mode); // c. Let total be fractionalDays. // d. Set norm to ZeroTimeDuration(). ( - f64::from_i128(days).ok_or( - TemporalError::range().with_message("days exceeded a valid range."), - )?, + FiniteF64::try_from(days)?, NormalizedTimeDuration::default(), - i128::from_f64(fractional_days), + i128::from_f64(fractional_days.0), ) } // 3. Else, @@ -433,7 +383,15 @@ impl TimeDuration { // 4. Return the Record { [[NormalizedDuration]]: ? CreateNormalizedDurationRecord(0, 0, 0, days, norm), [[Total]]: total }. Ok(( - NormalizedDurationRecord::new(DateDuration::new(0.0, 0.0, 0.0, days)?, norm)?, + NormalizedDurationRecord::new( + DateDuration::new( + FiniteF64::default(), + FiniteF64::default(), + FiniteF64::default(), + days, + )?, + norm, + )?, total, )) } diff --git a/src/components/instant.rs b/src/components/instant.rs index 5da3e06f..f6cfa802 100644 --- a/src/components/instant.rs +++ b/src/components/instant.rs @@ -10,6 +10,7 @@ use crate::{ RoundingOptions, TemporalUnit, }, parsers::parse_instant, + primitive::FiniteF64, rounding::{IncrementRounder, Round}, Sign, TemporalError, TemporalResult, TemporalUnwrap, }; @@ -32,17 +33,18 @@ pub struct Instant { // ==== Private API ==== impl Instant { + // TODO: Update to `i128`? /// Adds a `TimeDuration` to the current `Instant`. /// /// Temporal-Proposal equivalent: `AddDurationToOrSubtractDurationFrom`. pub(crate) fn add_to_instant(&self, duration: &TimeDuration) -> TemporalResult { let result = self.epoch_nanoseconds() - + duration.nanoseconds - + (duration.microseconds * 1000f64) - + (duration.milliseconds * 1_000_000f64) - + (duration.seconds * NANOSECONDS_PER_SECOND) - + (duration.minutes * NANOSECONDS_PER_MINUTE) - + (duration.hours * NANOSECONDS_PER_HOUR); + + duration.nanoseconds.0 + + (duration.microseconds.0 * 1000f64) + + (duration.milliseconds.0 * 1_000_000f64) + + (duration.seconds.0 * NANOSECONDS_PER_SECOND) + + (duration.minutes.0 * NANOSECONDS_PER_MINUTE) + + (duration.hours.0 * NANOSECONDS_PER_HOUR); let nanos = i128::from_f64(result).ok_or_else(|| { TemporalError::range().with_message("Duration added to instant exceeded valid range.") })?; @@ -78,7 +80,7 @@ impl Instant { other.epoch_nanos, self.epoch_nanos, )?; - let (round_record, _) = TimeDuration::round(0.0, &diff, resolved_options)?; + let (round_record, _) = TimeDuration::round(FiniteF64::default(), &diff, resolved_options)?; // 6. Let norm be diffRecord.[[NormalizedTimeDuration]]. // 7. Let result be ! BalanceTimeDuration(norm, settings.[[LargestUnit]]). @@ -319,6 +321,7 @@ mod tests { use crate::{ components::{duration::TimeDuration, Instant}, options::{DifferenceSettings, TemporalRoundingMode, TemporalUnit}, + primitive::FiniteF64, NS_MAX_INSTANT, NS_MIN_INSTANT, }; use num_traits::ToPrimitive; @@ -358,12 +361,12 @@ mod tests { assert_eq!( td, TimeDuration { - hours: expected.0, - minutes: expected.1, - seconds: expected.2, - milliseconds: expected.3, - microseconds: expected.4, - nanoseconds: expected.5, + hours: FiniteF64(expected.0), + minutes: FiniteF64(expected.1), + seconds: FiniteF64(expected.2), + milliseconds: FiniteF64(expected.3), + microseconds: FiniteF64(expected.4), + nanoseconds: FiniteF64(expected.5), } ) }; @@ -437,12 +440,12 @@ mod tests { assert_eq!( td, TimeDuration { - hours: expected.0, - minutes: expected.1, - seconds: expected.2, - milliseconds: expected.3, - microseconds: expected.4, - nanoseconds: expected.5, + hours: FiniteF64(expected.0), + minutes: FiniteF64(expected.1), + seconds: FiniteF64(expected.2), + milliseconds: FiniteF64(expected.3), + microseconds: FiniteF64(expected.4), + nanoseconds: FiniteF64(expected.5), } ) }; diff --git a/src/components/time.rs b/src/components/time.rs index ad2138c6..e0b9862c 100644 --- a/src/components/time.rs +++ b/src/components/time.rs @@ -8,7 +8,8 @@ use crate::{ RoundingIncrement, TemporalRoundingMode, TemporalUnit, }, parsers::parse_time, - TemporalError, TemporalResult, + primitive::FiniteF64, + Sign, TemporalError, TemporalResult, }; use super::{duration::normalized::NormalizedTimeDuration, DateTime}; @@ -60,19 +61,29 @@ impl Time { /// Adds a `TimeDuration` to the current `Time`. /// /// Spec Equivalent: `AddDurationToOrSubtractDurationFromPlainTime`. - pub(crate) fn add_to_time(&self, duration: &TimeDuration) -> Self { + pub(crate) fn add_to_time(&self, duration: &TimeDuration) -> TemporalResult { let (_, result) = IsoTime::balance( - f64::from(self.hour()) + duration.hours, - f64::from(self.minute()) + duration.minutes, - f64::from(self.second()) + duration.seconds, - f64::from(self.millisecond()) + duration.milliseconds, - f64::from(self.microsecond()) + duration.microseconds, - f64::from(self.nanosecond()) + duration.nanoseconds, + FiniteF64::from(self.hour()).checked_add(&duration.hours)?.0, + FiniteF64::from(self.minute()) + .checked_add(&duration.minutes)? + .0, + FiniteF64::from(self.second()) + .checked_add(&duration.seconds)? + .0, + FiniteF64::from(self.millisecond()) + .checked_add(&duration.milliseconds)? + .0, + FiniteF64::from(self.microsecond()) + .checked_add(&duration.microseconds)? + .0, + FiniteF64::from(self.nanosecond()) + .checked_add(&duration.nanoseconds)? + .0, ); // NOTE (nekevss): IsoTime::balance should never return an invalid `IsoTime` - Self::new_unchecked(result) + Ok(Self::new_unchecked(result)) } // TODO: Migrate to @@ -105,7 +116,8 @@ impl Time { || resolved.increment != RoundingIncrement::ONE { // a. Let roundRecord be ! RoundDuration(0, 0, 0, 0, norm, settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[RoundingMode]]). - let (round_record, _) = TimeDuration::round(0.0, &normalized_time, resolved)?; + let (round_record, _) = + TimeDuration::round(FiniteF64::default(), &normalized_time, resolved)?; // b. Set norm to roundRecord.[[NormalizedDuration]].[[NormalizedTime]]. normalized_time = round_record.normalized_time_duration() }; @@ -113,20 +125,11 @@ impl Time { // 7. Let result be BalanceTimeDuration(norm, settings.[[LargestUnit]]). let result = TimeDuration::from_normalized(normalized_time, resolved.largest_unit)?.1; - let sign = f64::from(sign as i8); // 8. Return ! CreateTemporalDuration(0, 0, 0, 0, sign × result.[[Hours]], sign × result.[[Minutes]], sign × result.[[Seconds]], sign × result.[[Milliseconds]], sign × result.[[Microseconds]], sign × result.[[Nanoseconds]]). - Duration::new( - 0.0, - 0.0, - 0.0, - 0.0, - sign * result.hours, - sign * result.minutes, - sign * result.seconds, - sign * result.milliseconds, - sign * result.microseconds, - sign * result.nanoseconds, - ) + match sign { + Sign::Positive | Sign::Zero => Ok(Duration::from(result)), + Sign::Negative => Ok(Duration::from(result.negated())), + } } } @@ -203,13 +206,12 @@ impl Time { return Err(TemporalError::range() .with_message("DateDuration values cannot be added to `Time`.")); } - Ok(self.add_time_duration(duration.time())) + self.add_time_duration(duration.time()) } /// Adds a `TimeDuration` to the current `Time`. #[inline] - #[must_use] - pub fn add_time_duration(&self, duration: &TimeDuration) -> Self { + pub fn add_time_duration(&self, duration: &TimeDuration) -> TemporalResult { self.add_to_time(duration) } @@ -219,13 +221,12 @@ impl Time { return Err(TemporalError::range() .with_message("DateDuration values cannot be added to `Time` component.")); } - Ok(self.add_time_duration(duration.time())) + self.subtract_time_duration(duration.time()) } /// Adds a `TimeDuration` to the current `Time`. #[inline] - #[must_use] - pub fn subtract_time_duration(&self, duration: &TimeDuration) -> Self { + pub fn subtract_time_duration(&self, duration: &TimeDuration) -> TemporalResult { self.add_to_time(&duration.negated()) } diff --git a/src/iso.rs b/src/iso.rs index 0119b91b..c7592fe8 100644 --- a/src/iso.rs +++ b/src/iso.rs @@ -25,12 +25,13 @@ use crate::{ }, error::TemporalError, options::{ArithmeticOverflow, RoundingIncrement, TemporalRoundingMode, TemporalUnit}, + primitive::FiniteF64, rounding::{IncrementRounder, Round}, temporal_assert, utils, TemporalResult, TemporalUnwrap, NS_PER_DAY, }; use icu_calendar::{Date as IcuDate, Iso}; use num_bigint::BigInt; -use num_traits::{cast::FromPrimitive, ToPrimitive}; +use num_traits::{cast::FromPrimitive, AsPrimitive, ToPrimitive}; /// `IsoDateTime` is the record of the `IsoDate` and `IsoTime` internal slots. #[non_exhaustive] @@ -152,18 +153,15 @@ impl IsoDateTime { let date = Date::new_unchecked(self.date, calendar); // 5. Let dateDuration be ? CreateTemporalDuration(years, months, weeks, days + timeResult.[[Days]], 0, 0, 0, 0, 0, 0). - let duration = Duration::new( + let date_duration = DateDuration::new( date_duration.years, date_duration.months, date_duration.weeks, - date_duration.days + f64::from(t_result.0), - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, + date_duration + .days + .checked_add(&FiniteF64::from(t_result.0))?, )?; + let duration = Duration::from(date_duration); // 6. Let addedDate be ? AddDate(calendarRec, datePart, dateDuration, options). let added_date = date.add_date(&duration, overflow)?; @@ -238,9 +236,9 @@ impl IsoDateTime { date_diff.days() } else { // a. Set timeDuration to ? Add24HourDaysToNormalizedTimeDuration(timeDuration, dateDifference.[[Days]]). - time_duration = time_duration.add_days(date_diff.days() as i64)?; + time_duration = time_duration.add_days(date_diff.days().as_())?; // b. Set days to 0. - 0.0 + FiniteF64::default() }; // 17. Return ? CreateNormalizedDurationRecord(dateDifference.[[Years]], dateDifference.[[Months]], dateDifference.[[Weeks]], days, timeDuration). @@ -355,8 +353,8 @@ impl IsoDate { // 2. Assert: overflow is either "constrain" or "reject". // 3. Let intermediate be ! BalanceISOYearMonth(year + years, month + months). let intermediate = balance_iso_year_month( - self.year + duration.years as i32, - i32::from(self.month) + duration.months as i32, + self.year + duration.years.as_date_value()?, + i32::from(self.month) + duration.months.as_date_value()?, ); // 4. Let intermediate be ? RegulateISODate(intermediate.[[Year]], intermediate.[[Month]], day, overflow). @@ -368,7 +366,8 @@ impl IsoDate { )?; // 5. Set days to days + 7 × weeks. - let additional_days = duration.days as i32 + (duration.weeks as i32 * 7); + let additional_days = + duration.days.as_date_value()? + (duration.weeks.as_date_value()? * 7); // 6. Let d be intermediate.[[Day]] + days. let d = i32::from(intermediate.day) + additional_days; @@ -468,7 +467,12 @@ impl IsoDate { }; // 17. Return ! CreateDateDurationRecord(years, months, weeks, days). - DateDuration::new(years as f64, months as f64, weeks as f64, days as f64) + DateDuration::new( + FiniteF64::from(years), + FiniteF64::from(months), + FiniteF64::from(weeks), + FiniteF64::from(days), + ) } } @@ -638,14 +642,21 @@ impl IsoTime { /// Difference this `IsoTime` against another and returning a `TimeDuration`. pub(crate) fn diff(&self, other: &Self) -> TimeDuration { - let h = f64::from(other.hour) - f64::from(self.hour); - let m = f64::from(other.minute) - f64::from(self.minute); - let s = f64::from(other.second) - f64::from(self.second); - let ms = f64::from(other.millisecond) - f64::from(self.millisecond); - let mis = f64::from(other.microsecond) - f64::from(self.microsecond); - let ns = f64::from(other.nanosecond) - f64::from(self.nanosecond); - - TimeDuration::new_unchecked(h, m, s, ms, mis, ns) + let h = i32::from(other.hour) - i32::from(self.hour); + let m = i32::from(other.minute) - i32::from(self.minute); + let s = i32::from(other.second) - i32::from(self.second); + let ms = i32::from(other.millisecond) - i32::from(self.millisecond); + let mis = i32::from(other.microsecond) - i32::from(self.microsecond); + let ns = i32::from(other.nanosecond) - i32::from(self.nanosecond); + + TimeDuration::new_unchecked( + FiniteF64::from(h), + FiniteF64::from(m), + FiniteF64::from(s), + FiniteF64::from(ms), + FiniteF64::from(mis), + FiniteF64::from(ns), + ) } // NOTE (nekevss): Specification seemed to be off / not entirely working, so the below was adapted from the diff --git a/src/lib.rs b/src/lib.rs index 53133f54..5eadf517 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,6 +44,7 @@ pub mod fields; pub mod iso; pub mod options; pub mod parsers; +pub mod primitive; #[doc(hidden)] pub(crate) mod rounding; diff --git a/src/primitive.rs b/src/primitive.rs new file mode 100644 index 00000000..c5f1c1bb --- /dev/null +++ b/src/primitive.rs @@ -0,0 +1,138 @@ +//! Implementation of the FiniteF64 primitive + +use crate::{TemporalError, TemporalResult}; +use num_traits::{AsPrimitive, FromPrimitive}; + +#[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)] +pub struct FiniteF64(pub(crate) f64); + +impl FiniteF64 { + #[inline] + pub fn as_inner(&self) -> f64 { + self.0 + } + + #[inline] + pub fn is_zero(&self) -> bool { + self.0 == 0.0 + } + + #[inline] + pub fn negate(&self) -> Self { + if !self.is_zero() { + Self(self.0 * -1.0) + } else { + *self + } + } + + #[inline] + pub fn abs(&self) -> Self { + Self(self.0.abs()) + } + + #[inline] + pub fn checked_add(&self, other: &Self) -> TemporalResult { + let result = Self(self.0 + other.0); + if !result.0.is_finite() { + return Err(TemporalError::range().with_message("number value is not a finite value.")); + } + Ok(result) + } + + #[inline] + pub fn checked_mul_add(&self, a: FiniteF64, b: FiniteF64) -> TemporalResult { + let result = Self(self.0.mul_add(a.0, b.0)); + if !result.0.is_finite() { + return Err(TemporalError::range().with_message("number value is not a finite value.")); + } + Ok(result) + } + + pub fn copysign(&self, other: f64) -> Self { + Self(self.0.copysign(other)) + } + + pub(crate) fn as_date_value(&self) -> TemporalResult { + if !(f64::from(i32::MIN)..=f64::from(i32::MAX)).contains(&self.0) { + return Err(TemporalError::range().with_message("number exceeds a valid date value.")); + } + Ok(self.0 as i32) + } +} + +impl AsPrimitive for FiniteF64 { + fn as_(self) -> i64 { + self.0 as i64 + } +} + +impl AsPrimitive for FiniteF64 { + fn as_(self) -> i128 { + self.0 as i128 + } +} + +impl TryFrom for FiniteF64 { + type Error = TemporalError; + fn try_from(value: f64) -> Result { + if !value.is_finite() { + return Err(TemporalError::range().with_message("number value is not a finite value.")); + } + Ok(Self(value)) + } +} + +impl TryFrom for FiniteF64 { + type Error = TemporalError; + fn try_from(value: i128) -> Result { + let result = f64::from_i128(value) + .ok_or(TemporalError::range().with_message("days exceeded a valid range."))?; + if !result.is_finite() { + return Err(TemporalError::range().with_message("number value is not a finite value.")); + } + Ok(Self(result)) + } +} + +impl From for FiniteF64 { + fn from(value: i8) -> Self { + Self(f64::from(value)) + } +} + +impl From for FiniteF64 { + fn from(value: i32) -> Self { + Self(f64::from(value)) + } +} + +impl From for FiniteF64 { + fn from(value: u8) -> Self { + Self(f64::from(value)) + } +} + +impl From for FiniteF64 { + fn from(value: u16) -> Self { + Self(f64::from(value)) + } +} + +impl From for FiniteF64 { + fn from(value: u32) -> Self { + Self(f64::from(value)) + } +} + +impl PartialEq for FiniteF64 { + fn eq(&self, other: &f64) -> bool { + self.0 == *other + } +} + +impl PartialOrd for FiniteF64 { + fn partial_cmp(&self, other: &f64) -> Option { + self.0.partial_cmp(other) + } +}