From c999bb366c9cceff7fadae23c5c6f2cacd7d6e2f Mon Sep 17 00:00:00 2001 From: Kevin Ness <46825870+nekevss@users.noreply.github.com> Date: Sat, 6 Jul 2024 12:58:35 -0400 Subject: [PATCH] Update duration rounding to new algorithms (#65) ~~Currently a WIP.~~ This closes #19 outside of anything related to ZonedDateTime, which I'd prefer to consider separately due to the reliance on time zones. This PR updates duration rounding to the new duration rounding algorithms. As it's a bit of a larger PR, I thought I would create a draft in case anyone wants to take a look early. Overall, this draft is fairly close to complete, but there are still at least a couple bugs around the actually rounding that I'm working through. --- README.md | 2 +- src/components/date.rs | 184 +++---- src/components/datetime.rs | 102 +++- src/components/duration.rs | 284 ++++------- src/components/duration/date.rs | 454 +---------------- src/components/duration/normalized.rs | 668 +++++++++++++++++++++++++- src/components/duration/tests.rs | 585 ++++++++-------------- src/components/duration/time.rs | 171 +++---- src/components/instant.rs | 85 ++-- src/components/time.rs | 127 +++-- src/iso.rs | 125 ++++- src/lib.rs | 30 ++ src/options.rs | 167 ++++++- src/options/increment.rs | 6 +- src/rounding.rs | 61 +-- 15 files changed, 1584 insertions(+), 1467 deletions(-) diff --git a/README.md b/README.md index 8a40ed7f..90fb92f4 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ `Temporal` is a calendar and timezone aware date/time library that is currently being designed and proposed as a new builtin to the `ECMAScript` specification. -This crate is an implementation of `Temporal` in Rust. While initially developed for the `Boa`, the crate has been externalized +This crate is an implementation of `Temporal` in Rust. While initially developed for `Boa`, the crate has been externalized as we intended to make an engine agnostic and general usage implementation of `Temporal` and its algorithms. ## Temporal Proposal diff --git a/src/components/date.rs b/src/components/date.rs index d46b2d7a..291f9abb 100644 --- a/src/components/date.rs +++ b/src/components/date.rs @@ -8,16 +8,20 @@ use crate::{ duration::DateDuration, DateTime, Duration, }, - iso::{IsoDate, IsoDateSlots, IsoDateTime}, + iso::{IsoDate, IsoDateSlots, IsoDateTime, IsoTime}, options::{ - ArithmeticOverflow, RelativeTo, RoundingIncrement, TemporalRoundingMode, TemporalUnit, + ArithmeticOverflow, DifferenceOperation, DifferenceSettings, ResolvedRoundingOptions, + TemporalUnit, }, parsers::parse_date_time, TemporalError, TemporalFields, TemporalResult, TemporalUnwrap, }; use std::str::FromStr; -use super::{duration::TimeDuration, MonthDay, Time, YearMonth}; +use super::{ + duration::{normalized::NormalizedDurationRecord, TimeDuration}, + MonthDay, Time, YearMonth, +}; /// The native Rust implementation of `Temporal.PlainDate`. #[non_exhaustive] @@ -37,14 +41,6 @@ impl Date { Self { iso, calendar } } - #[inline] - /// Returns a new moved date and the days associated with that adjustment - pub(crate) fn move_relative_date(&self, duration: &Duration) -> TemporalResult<(Self, f64)> { - let new_date = self.add_date(duration, None)?; - let days = f64::from(self.days_until(&new_date)); - Ok((new_date, days)) - } - /// Returns the date after adding the given duration to date. /// /// Temporal Equivalent: 3.5.13 `AddDate ( calendar, plainDate, duration [ , options [ , dateAdd ] ] )` @@ -112,20 +108,15 @@ impl Date { } /// Equivalent: DifferenceTemporalPlainDate - #[allow(clippy::too_many_arguments)] pub(crate) fn diff_date( &self, - op: bool, + op: DifferenceOperation, other: &Self, - rounding_mode: Option, - rounding_increment: Option, - largest_unit: Option, - smallest_unit: Option, + settings: DifferenceSettings, ) -> TemporalResult { // 1. If operation is SINCE, let sign be -1. Otherwise, let sign be 1. // 2. Set other to ? ToTemporalDate(other). - // TODO(improvement): Implement `PartialEq` for `TemporalCalendar` // 3. If ? CalendarEquals(temporalDate.[[Calendar]], other.[[Calendar]]) is false, throw a RangeError exception. if self.calendar().identifier()? != other.calendar().identifier()? { return Err(TemporalError::range() @@ -134,20 +125,12 @@ impl Date { // 4. Let resolvedOptions be ? SnapshotOwnProperties(? GetOptionsObject(options), null). // 5. Let settings be ? GetDifferenceSettings(operation, resolvedOptions, DATE, « », "day", "day"). - let rounding_increment = rounding_increment.unwrap_or_default(); - let (sign, rounding_mode) = if op { - ( - -1.0, - rounding_mode - .unwrap_or(TemporalRoundingMode::Trunc) - .negate(), - ) - } else { - (1.0, rounding_mode.unwrap_or(TemporalRoundingMode::Trunc)) - }; - let smallest_unit = smallest_unit.unwrap_or(TemporalUnit::Day); - // Use the defaultlargestunit which is max smallestlargestdefault and smallestunit - let largest_unit = largest_unit.unwrap_or(smallest_unit.max(TemporalUnit::Day)); + let (sign, resolved) = ResolvedRoundingOptions::from_diff_settings( + settings, + op, + TemporalUnit::Day, + TemporalUnit::Day, + )?; // 6. If temporalDate.[[ISOYear]] = other.[[ISOYear]], and temporalDate.[[ISOMonth]] = other.[[ISOMonth]], // and temporalDate.[[ISODay]] = other.[[ISODay]], then @@ -159,62 +142,39 @@ impl Date { // 7. Let calendarRec be ? CreateCalendarMethodsRecord(temporalDate.[[Calendar]], « DATE-ADD, DATE-UNTIL »). // 8. Perform ! CreateDataPropertyOrThrow(resolvedOptions, "largestUnit", settings.[[LargestUnit]]). // 9. Let result be ? DifferenceDate(calendarRec, temporalDate, other, resolvedOptions). - let result = self.internal_diff_date(other, largest_unit)?; - - // 10. If settings.[[SmallestUnit]] is "day" and settings.[[RoundingIncrement]] = 1, - // let roundingGranularityIsNoop be true; else let roundingGranularityIsNoop be false. - let is_noop = - smallest_unit == TemporalUnit::Day && rounding_increment == RoundingIncrement::ONE; - - // 12. Return ! CreateTemporalDuration(sign × result.[[Years]], sign × result.[[Months]], sign × result.[[Weeks]], sign × result.[[Days]], 0, 0, 0, 0, 0, 0). - if is_noop { - return Duration::new( - result.years() * sign, - result.months() * sign, - result.weeks() * sign, - result.days() * sign, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, + let result = self.internal_diff_date(other, resolved.largest_unit)?; + + // 10. Let duration be ! CreateNormalizedDurationRecord(result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], ZeroTimeDuration()). + let duration = NormalizedDurationRecord::from_date_duration(*result.date())?; + // 11. If settings.[[SmallestUnit]] is "day" and settings.[[RoundingIncrement]] = 1, let roundingGranularityIsNoop be true; else let roundingGranularityIsNoop be false. + let rounding_granularity_is_noop = + resolved.smallest_unit == TemporalUnit::Day && resolved.increment.get() == 1; + // 12. If roundingGranularityIsNoop is false, then + let date_duration = if !rounding_granularity_is_noop { + // a. Let destEpochNs be GetUTCEpochNanoseconds(other.[[ISOYear]], other.[[ISOMonth]], other.[[ISODay]], 0, 0, 0, 0, 0, 0). + let dest_epoch_ns = other.iso.as_nanoseconds().temporal_unwrap()?; + // b. Let dateTime be ISO Date-Time Record { [[Year]]: temporalDate.[[ISOYear]], [[Month]]: temporalDate.[[ISOMonth]], [[Day]]: temporalDate.[[ISODay]], [[Hour]]: 0, [[Minute]]: 0, [[Second]]: 0, [[Millisecond]]: 0, [[Microsecond]]: 0, [[Nanosecond]]: 0 }. + let dt = DateTime::new_unchecked( + IsoDateTime::new_unchecked(self.iso, IsoTime::default()), + self.calendar.clone(), ); - } + // c. Set duration to ? RoundRelativeDuration(duration, destEpochNs, dateTime, calendarRec, unset, settings.[[LargestUnit]], settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[RoundingMode]]). + *duration + .round_relative_duration(dest_epoch_ns, &dt, None, resolved)? + .0 + .date() + } else { + duration.date() + }; - // 11. If roundingGranularityIsNoop is false, then - // a. Let roundRecord be ? RoundDuration(result.[[Years]], result.[[Months]], result.[[Weeks]], - // result.[[Days]], ZeroTimeDuration(), settings.[[RoundingIncrement]], settings.[[SmallestUnit]], - // settings.[[RoundingMode]], temporalDate, calendarRec). - // TODO: Look into simplifying round_internal's parameters. - let round_record = result.round_internal( - rounding_increment, - smallest_unit, - rounding_mode, - &RelativeTo { - zdt: None, - date: Some(self), - }, - None, - )?; - // b. Let roundResult be roundRecord.[[NormalizedDuration]]. - let round_result = round_record.0 .0 .0; - // c. Set result to ? BalanceDateDurationRelative(roundResult.[[Years]], roundResult.[[Months]], roundResult.[[Weeks]], - // roundResult.[[Days]], settings.[[LargestUnit]], settings.[[SmallestUnit]], temporalDate, calendarRec). - let result = round_result.balance_relative(largest_unit, smallest_unit, Some(self))?; - - Duration::new( - result.years * sign, - result.months * sign, - result.weeks * sign, - result.days * sign, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - ) + 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_date_duration(&DateDuration::new( + date_duration.years * sign, + date_duration.months * sign, + date_duration.weeks * sign, + date_duration.days * sign, + )?)) } } @@ -303,40 +263,12 @@ impl Date { self.add_date(&duration.negated(), overflow) } - pub fn until( - &self, - other: &Self, - rounding_mode: Option, - rounding_increment: Option, - smallest_unit: Option, - largest_unit: Option, - ) -> TemporalResult { - self.diff_date( - false, - other, - rounding_mode, - rounding_increment, - smallest_unit, - largest_unit, - ) + pub fn until(&self, other: &Self, settings: DifferenceSettings) -> TemporalResult { + self.diff_date(DifferenceOperation::Until, other, settings) } - pub fn since( - &self, - other: &Self, - rounding_mode: Option, - rounding_increment: Option, - smallest_unit: Option, - largest_unit: Option, - ) -> TemporalResult { - self.diff_date( - true, - other, - rounding_mode, - rounding_increment, - smallest_unit, - largest_unit, - ) + pub fn since(&self, other: &Self, settings: DifferenceSettings) -> TemporalResult { + self.diff_date(DifferenceOperation::Since, other, settings) } } @@ -582,11 +514,15 @@ mod tests { fn simple_date_until() { let earlier = Date::from_str("1969-07-24").unwrap(); let later = Date::from_str("1969-10-05").unwrap(); - let result = earlier.until(&later, None, None, None, None).unwrap(); + let result = earlier + .until(&later, DifferenceSettings::default()) + .unwrap(); assert_eq!(result.days(), 73.0,); let later = Date::from_str("1996-03-03").unwrap(); - let result = earlier.until(&later, None, None, None, None).unwrap(); + let result = earlier + .until(&later, DifferenceSettings::default()) + .unwrap(); assert_eq!(result.days(), 9719.0,); } @@ -594,11 +530,15 @@ mod tests { fn simple_date_since() { let earlier = Date::from_str("1969-07-24").unwrap(); let later = Date::from_str("1969-10-05").unwrap(); - let result = later.since(&earlier, None, None, None, None).unwrap(); + let result = later + .since(&earlier, DifferenceSettings::default()) + .unwrap(); assert_eq!(result.days(), 73.0,); let later = Date::from_str("1996-03-03").unwrap(); - let result = later.since(&earlier, None, None, None, None).unwrap(); + let result = later + .since(&earlier, DifferenceSettings::default()) + .unwrap(); assert_eq!(result.days(), 9719.0,); } diff --git a/src/components/datetime.rs b/src/components/datetime.rs index f1bdd6a2..8007f801 100644 --- a/src/components/datetime.rs +++ b/src/components/datetime.rs @@ -1,19 +1,19 @@ //! This module implements `DateTime` any directly related algorithms. use crate::{ - components::Instant, + components::{calendar::Calendar, duration::TimeDuration, Instant}, iso::{IsoDate, IsoDateSlots, IsoDateTime, IsoTime}, - options::ArithmeticOverflow, + options::{ArithmeticOverflow, ResolvedRoundingOptions, TemporalUnit}, parsers::parse_date_time, TemporalError, TemporalResult, TemporalUnwrap, }; -use std::str::FromStr; +use std::{cmp::Ordering, str::FromStr}; use tinystr::TinyAsciiStr; use super::{ - calendar::{Calendar, CalendarDateLike, GetTemporalCalendar}, - duration::normalized::NormalizedTimeDuration, + calendar::{CalendarDateLike, GetTemporalCalendar}, + duration::normalized::{NormalizedTimeDuration, RelativeRoundResult}, Duration, }; @@ -21,7 +21,7 @@ use super::{ #[non_exhaustive] #[derive(Debug, Default, Clone)] pub struct DateTime { - iso: IsoDateTime, + pub(crate) iso: IsoDateTime, calendar: Calendar, } @@ -75,12 +75,82 @@ impl DateTime { .add_date_duration(self.calendar().clone(), duration.date(), norm, overflow)?; // 7. Assert: IsValidISODate(result.[[Year]], result.[[Month]], result.[[Day]]) is true. - // 8. Assert: IsValidTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]]) is true. + // 8. Assert: IsValidTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], + // result.[[Microsecond]], result.[[Nanosecond]]) is true. assert!(result.is_within_limits()); - // 9. Return ? CreateTemporalDateTime(result.[[Year]], result.[[Month]], result.[[Day]], result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]], dateTime.[[Calendar]]). + // 9. Return ? CreateTemporalDateTime(result.[[Year]], result.[[Month]], result.[[Day]], result.[[Hour]], + // result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], + // result.[[Nanosecond]], dateTime.[[Calendar]]). Ok(Self::new_unchecked(result, self.calendar.clone())) } + + // TODO: Figure out whether to handle resolvedOptions + // 5.5.12 DifferencePlainDateTimeWithRounding ( y1, mon1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, d2, h2, min2, s2, ms2, + // mus2, ns2, calendarRec, largestUnit, roundingIncrement, smallestUnit, roundingMode, resolvedOptions ) + pub(crate) fn diff_dt_with_rounding( + &self, + other: &Self, + options: ResolvedRoundingOptions, + ) -> TemporalResult { + // 1. Assert: IsValidISODate(y1, mon1, d1) is true. + // 2. Assert: IsValidISODate(y2, mon2, d2) is true. + // 3. If CompareISODateTime(y1, mon1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, d2, h2, min2, s2, ms2, mus2, ns2) = 0, then + if matches!(self.iso.cmp(&other.iso), Ordering::Equal) { + // a. Let durationRecord be CreateDurationRecord(0, 0, 0, 0, 0, 0, 0, 0, 0, 0). + // b. Return the Record { [[DurationRecord]]: durationRecord, [[Total]]: 0 }. + return Ok((Duration::default(), Some(0))); + } + + // 4. Let diff be ? DifferenceISODateTime(y1, mon1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, d2, h2, min2, s2, ms2, mus2, ns2, calendarRec, largestUnit, resolvedOptions). + let diff = self + .iso + .diff(&other.iso, &self.calendar, options.largest_unit)?; + + // 5. If smallestUnit is "nanosecond" and roundingIncrement = 1, then + if options.smallest_unit == TemporalUnit::Nanosecond && options.increment.get() == 1 { + // a. Let normWithDays be ? Add24HourDaysToNormalizedTimeDuration(diff.[[NormalizedTime]], diff.[[Days]]). + let norm_with_days = diff + .normalized_time_duration() + .add_days(diff.date().days as i64)?; + // b. Let timeResult be ! BalanceTimeDuration(normWithDays, largestUnit). + let (days, time_duration) = + TimeDuration::from_normalized(norm_with_days, options.largest_unit)?; + + // c. Let total be NormalizedTimeDurationSeconds(normWithDays) × 10**9 + NormalizedTimeDurationSubseconds(normWithDays). + let total = + norm_with_days.seconds() * 1_000_000_000 + i64::from(norm_with_days.subseconds()); + + // d. Let durationRecord be CreateDurationRecord(diff.[[Years]], diff.[[Months]], diff.[[Weeks]], timeResult.[[Days]], + // timeResult.[[Hours]], timeResult.[[Minutes]], timeResult.[[Seconds]], timeResult.[[Milliseconds]], + // timeResult.[[Microseconds]], timeResult.[[Nanoseconds]]). + let duration = Duration::new( + diff.date().years, + diff.date().months, + diff.date().weeks, + days, + time_duration.hours, + time_duration.minutes, + time_duration.seconds, + time_duration.milliseconds, + time_duration.microseconds, + time_duration.nanoseconds, + )?; + + // e. Return the Record { [[DurationRecord]]: durationRecord, [[Total]]: total }. + return Ok((duration, Some(i128::from(total)))); + } + + // 6. Let dateTime be ISO Date-TimeRecord { [[Year]]: y1, [[Month]]: mon1, + // [[Day]]: d1, [[Hour]]: h1, [[Minute]]: min1, [[Second]]: s1, [[Millisecond]]: + // ms1, [[Microsecond]]: mus1, [[Nanosecond]]: ns1 }. + // 7. Let destEpochNs be GetUTCEpochNanoseconds(y2, mon2, d2, h2, min2, s2, ms2, mus2, ns2). + let dest_epoch_ns = other.iso.as_nanoseconds(0.0).temporal_unwrap()?; + + // 8. Return ? RoundRelativeDuration(diff, destEpochNs, dateTime, calendarRec, unset, largestUnit, + // roundingIncrement, smallestUnit, roundingMode). + diff.round_relative_duration(dest_epoch_ns, self, None, options) + } } // ==== Public DateTime API ==== @@ -347,7 +417,7 @@ mod tests { use std::str::FromStr; use crate::{ - components::{calendar::Calendar, Duration}, + components::{calendar::Calendar, duration::DateDuration, Duration}, iso::{IsoDate, IsoTime}, }; @@ -382,7 +452,12 @@ mod tests { let pdt = DateTime::new(2020, 1, 31, 12, 34, 56, 987, 654, 321, Calendar::default()).unwrap(); - let result = pdt.add(&Duration::one_month(1.0), None).unwrap(); + let result = pdt + .add( + &Duration::from_date_duration(&DateDuration::new(0.0, 1.0, 0.0, 0.0).unwrap()), + None, + ) + .unwrap(); assert_eq!(result.month(), Ok(2)); assert_eq!(result.day(), Ok(29)); @@ -394,7 +469,12 @@ mod tests { let pdt = DateTime::new(2000, 3, 31, 12, 34, 56, 987, 654, 321, Calendar::default()).unwrap(); - let result = pdt.subtract(&Duration::one_month(1.0), None).unwrap(); + let result = pdt + .subtract( + &Duration::from_date_duration(&DateDuration::new(0.0, 1.0, 0.0, 0.0).unwrap()), + None, + ) + .unwrap(); assert_eq!(result.month(), Ok(2)); assert_eq!(result.day(), Ok(29)); diff --git a/src/components/duration.rs b/src/components/duration.rs index 97e6f4a4..4502da5e 100644 --- a/src/components/duration.rs +++ b/src/components/duration.rs @@ -1,14 +1,15 @@ //! This module implements `Duration` along with it's methods and components. use crate::{ - components::DateTime, - options::{RelativeTo, RoundingIncrement, TemporalRoundingMode, TemporalUnit}, - TemporalError, TemporalResult, + components::{DateTime, Time}, + iso::{IsoDateTime, IsoTime}, + options::{RelativeTo, ResolvedRoundingOptions, RoundingOptions, TemporalUnit}, + Sign, TemporalError, TemporalResult, }; use ixdtf::parsers::{records::TimeDurationRecord, IsoDurationParser}; use std::str::FromStr; -use self::normalized::{NormalizedDurationRecord, NormalizedTimeDuration}; +use self::normalized::NormalizedTimeDuration; mod date; pub(crate) mod normalized; @@ -63,24 +64,6 @@ impl Duration { Self { date, time } } - /// Utility function to create a year duration. - #[inline] - pub(crate) fn one_year(year_value: f64) -> Self { - Self::from_date_duration(&DateDuration::new_unchecked(year_value, 1f64, 0f64, 0f64)) - } - - /// Utility function to create a month duration. - #[inline] - pub(crate) fn one_month(month_value: f64) -> Self { - Self::from_date_duration(&DateDuration::new_unchecked(0f64, month_value, 0f64, 0f64)) - } - - /// Utility function to create a week duration. - #[inline] - pub(crate) fn one_week(week_value: f64) -> Self { - Self::from_date_duration(&DateDuration::new_unchecked(0f64, 0f64, week_value, 0f64)) - } - /// Returns the a `Vec` of the fields values. #[inline] #[must_use] @@ -356,65 +339,13 @@ impl Duration { } } -// ==== Private Duration methods ==== - -impl Duration { - // TODO (nekevss): Build out `RelativeTo` handling - /// 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(crate) fn round_internal( - &self, - increment: RoundingIncrement, - unit: TemporalUnit, - rounding_mode: TemporalRoundingMode, - relative_to: &RelativeTo, - precalculated_dt: Option, - ) -> TemporalResult<(NormalizedDurationRecord, f64)> { - match unit { - TemporalUnit::Year | TemporalUnit::Month | TemporalUnit::Week | TemporalUnit::Day => { - let round_result = self.date().round( - Some(self.time.to_normalized()), - increment, - unit, - rounding_mode, - relative_to, - precalculated_dt, - )?; - let norm_record = NormalizedDurationRecord::new( - round_result.0, - NormalizedTimeDuration::default(), - )?; - Ok((norm_record, round_result.1)) - } - TemporalUnit::Hour - | TemporalUnit::Minute - | TemporalUnit::Second - | TemporalUnit::Millisecond - | TemporalUnit::Microsecond - | TemporalUnit::Nanosecond => { - let round_result = self.time().round(increment, unit, rounding_mode)?; - let norm = NormalizedDurationRecord::new(*self.date(), round_result.0)?; - Ok((norm, round_result.1 as f64)) - } - TemporalUnit::Auto => { - Err(TemporalError::range().with_message("Invalid TemporalUnit for Duration.round")) - } - } - // 18. Let duration be ? CreateDurationRecord(years, months, weeks, days, hours, - // minutes, seconds, milliseconds, microseconds, nanoseconds). - // 19. Return the Record { [[DurationRecord]]: duration, [[Total]]: total }. - } -} - // ==== Public Duration methods ==== impl Duration { /// Determines the sign for the current self. #[inline] #[must_use] - pub fn sign(&self) -> i32 { + pub fn sign(&self) -> Sign { duration_sign(&self.fields()) } @@ -424,7 +355,7 @@ impl Duration { #[inline] #[must_use] pub fn is_zero(&self) -> bool { - self.sign() == 0 + self.sign() == Sign::Zero } /// Returns a negated `Duration` @@ -447,68 +378,35 @@ impl Duration { } } - /// Rounds the current `Duration`. #[inline] pub fn round( &self, - increment: Option, - smallest_unit: Option, - largest_unit: Option, - rounding_mode: Option, + options: RoundingOptions, relative_to: &RelativeTo, ) -> TemporalResult { // NOTE: Steps 1-14 seem to be implementation specific steps. - - // 22. If smallestUnitPresent is false and largestUnitPresent is false, then - if largest_unit.is_none() && smallest_unit.is_none() { - // a. Throw a RangeError exception. - return Err(TemporalError::range() - .with_message("smallestUnit and largestUnit cannot both be None.")); - } - // 14. Let roundingIncrement be ? ToTemporalRoundingIncrement(roundTo). - let increment = increment.unwrap_or_default(); // 15. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand"). - let mode = rounding_mode.unwrap_or_default(); - // 16. Let smallestUnit be ? GetTemporalUnit(roundTo, "smallestUnit", DATETIME, undefined). // 17. If smallestUnit is undefined, then // a. Set smallestUnitPresent to false. // b. Set smallestUnit to "nanosecond". - let smallest_unit = smallest_unit.unwrap_or(TemporalUnit::Nanosecond); - // 18. Let existingLargestUnit be ! DefaultTemporalLargestUnit(duration.[[Years]], // duration.[[Months]], duration.[[Weeks]], duration.[[Days]], duration.[[Hours]], // duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], // duration.[[Microseconds]]). - let existing_largest_unit = self.default_largest_unit(); - // 19. Let defaultLargestUnit be LargerOfTwoTemporalUnits(existingLargestUnit, smallestUnit). - let default_largest = existing_largest_unit.max(smallest_unit); - // 20. If largestUnit is undefined, then // a. Set largestUnitPresent to false. // b. Set largestUnit to defaultLargestUnit. // 21. Else if largestUnit is "auto", then // a. Set largestUnit to defaultLargestUnit. - let largest_unit = match largest_unit { - Some(TemporalUnit::Auto) | None => default_largest, - Some(unit) => unit, - }; - // 23. If LargerOfTwoTemporalUnits(largestUnit, smallestUnit) is not largestUnit, throw a RangeError exception. - if largest_unit.max(smallest_unit) != largest_unit { - return Err(TemporalError::range().with_message( - "largestUnit when rounding Duration was not the largest provided unit", - )); - } - // 24. Let maximum be MaximumTemporalDurationRoundingIncrement(smallestUnit). - let maximum = smallest_unit.to_maximum_rounding_increment(); // 25. If maximum is not undefined, perform ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false). - if let Some(max) = maximum { - increment.validate(max.into(), false)?; - } + let existing_largest_unit = self.default_largest_unit(); + let resolved_options = + ResolvedRoundingOptions::from_options(options, existing_largest_unit)?; // 26. Let hoursToDaysConversionMayOccur be false. // 27. If duration.[[Days]] ≠ 0 and zonedRelativeTo is not undefined, set hoursToDaysConversionMayOccur to true. @@ -518,18 +416,18 @@ impl Duration { // 29. If smallestUnit is "nanosecond" and roundingIncrement = 1, let roundingGranularityIsNoop // be true; else let roundingGranularityIsNoop be false. - let is_noop = - smallest_unit == TemporalUnit::Nanosecond && increment == RoundingIncrement::ONE; // 30. If duration.[[Years]] = 0 and duration.[[Months]] = 0 and duration.[[Weeks]] = 0, // let calendarUnitsPresent be false; else let calendarUnitsPresent be true. let calendar_units_present = !(self.years() == 0.0 && self.months() == 0.0 && self.weeks() == 0.0); + let is_noop = resolved_options.is_noop(); + // 31. If roundingGranularityIsNoop is true, and largestUnit is existingLargestUnit, and calendarUnitsPresent is false, // and hoursToDaysConversionMayOccur is false, and abs(duration.[[Minutes]]) < 60, and abs(duration.[[Seconds]]) < 60, // and abs(duration.[[Milliseconds]]) < 1000, and abs(duration.[[Microseconds]]) < 1000, and abs(duration.[[Nanoseconds]]) < 1000, then if is_noop - && largest_unit == existing_largest_unit + && resolved_options.largest_unit == existing_largest_unit && !calendar_units_present && !hours_to_days_may_occur && self.minutes().abs() < 60.0 @@ -554,13 +452,13 @@ impl Duration { // or calendarUnitsPresent is true, or duration.[[Days]] ≠ 0, let plainDateTimeOrRelativeToWillBeUsed be true; // else let plainDateTimeOrRelativeToWillBeUsed be false. let pdtr_will_be_used = !is_noop - || largest_unit.is_calendar_unit() - || largest_unit == TemporalUnit::Day + || resolved_options.largest_unit.is_calendar_unit() + || resolved_options.largest_unit == TemporalUnit::Day || calendar_units_present || self.days() == 0.0; // 34. If zonedRelativeTo is not undefined and plainDateTimeOrRelativeToWillBeUsed is true, then - let precalculated = if relative_to.zdt.is_some() && pdtr_will_be_used { + let _precalculated: Option = if relative_to.zdt.is_some() && pdtr_will_be_used { return Err(TemporalError::general("Not yet implemented.")); // a. NOTE: The above conditions mean that the corresponding Temporal.PlainDateTime or // Temporal.PlainDate for zonedRelativeTo will be used in one of the operations below. @@ -573,74 +471,82 @@ impl Duration { }; // 35. Let calendarRec be ? CreateCalendarMethodsRecordFromRelativeTo(plainRelativeTo, zonedRelativeTo, « DATE-ADD, DATE-UNTIL »). - // TODO: relativeTo will need to be removed soon. - let relative_to_date = relative_to.date; - - // 36. Let unbalanceResult be ? UnbalanceDateDurationRelative(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], largestUnit, plainRelativeTo, calendarRec). - let unbalanced = self - .date() - .unbalance_relative(largest_unit, relative_to_date)?; - - // NOTE: Step 37 handled in round duration - // 37. Let norm be NormalizeTimeDuration(duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], - // duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]]). - // 38. Let roundRecord be ? RoundDuration(unbalanceResult.[[Years]], unbalanceResult.[[Months]], - // unbalanceResult.[[Weeks]], unbalanceResult.[[Days]], norm, roundingIncrement, smallestUnit, - // roundingMode, plainRelativeTo, calendarRec, zonedRelativeTo, timeZoneRec, precalculatedPlainDateTime). - let (round_result, _) = Self::new_unchecked(unbalanced, *self.time()).round_internal( - increment, - smallest_unit, - mode, - relative_to, - precalculated, - )?; - - // 39. Let roundResult be roundRecord.[[NormalizedDuration]]. - // 40. If zonedRelativeTo is not undefined, then - let balance_result = if relative_to.zdt.is_some() { + // 36. Let norm be NormalizeTimeDuration(duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], + // duration.[[Microseconds]], duration.[[Nanoseconds]]). + let norm = NormalizedTimeDuration::from_time_duration(self.time()); + // 37. Let emptyOptions be OrdinaryObjectCreate(null). + + // 38. If zonedRelativeTo is not undefined, then + let round_result = if let Some(_zdt) = relative_to.zdt { + // a. Let relativeEpochNs be zonedRelativeTo.[[Nanoseconds]]. + // b. Let relativeInstant be ! CreateTemporalInstant(relativeEpochNs). + // c. Let targetEpochNs be ? AddZonedDateTime(relativeInstant, timeZoneRec, calendarRec, duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], norm, precalculatedPlainDateTime). + // d. Let roundRecord be ? DifferenceZonedDateTimeWithRounding(relativeEpochNs, targetEpochNs, calendarRec, timeZoneRec, precalculatedPlainDateTime, emptyOptions, largestUnit, roundingIncrement, smallestUnit, roundingMode). + // e. Let roundResult be roundRecord.[[DurationRecord]]. return Err(TemporalError::general("Not yet implemented.")); - // a. Set roundResult to ? AdjustRoundedDurationDays(roundResult.[[Years]], roundResult.[[Months]], - // roundResult.[[Weeks]], roundResult.[[Days]], roundResult.[[NormalizedTime]], roundingIncrement, - // smallestUnit, roundingMode, zonedRelativeTo, calendarRec, timeZoneRec, precalculatedPlainDateTime). - // b. Let balanceResult be ? BalanceTimeDurationRelative(roundResult.[[Days]], - // roundResult.[[NormalizedTime]], largestUnit, zonedRelativeTo, timeZoneRec, precalculatedPlainDateTime). - // 41. Else, + // 39. Else if plainRelativeTo is not undefined, then + } else if let Some(plain_date) = relative_to.date { + // a. Let targetTime be AddTime(0, 0, 0, 0, 0, 0, norm). + let (balanced_days, time) = Time::default().add_normalized_time_duration(norm); + // b. Let dateDuration be ? CreateTemporalDuration(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], + // duration.[[Days]] + targetTime.[[Days]], 0, 0, 0, 0, 0, 0). + let date_duration = DateDuration::new( + self.years(), + self.months(), + self.weeks(), + self.days() + f64::from(balanced_days), + )?; + + // c. Let targetDate be ? AddDate(calendarRec, plainRelativeTo, dateDuration). + let target_date = + plain_date.add_date(&Duration::from_date_duration(&date_duration), None)?; + + let plain_dt = DateTime::new_unchecked( + IsoDateTime::new(plain_date.iso, IsoTime::default())?, + plain_date.calendar().clone(), + ); + + let target_dt = DateTime::new_unchecked( + IsoDateTime::new(target_date.iso, time.iso)?, + target_date.calendar().clone(), + ); + + // d. Let roundRecord be ? DifferencePlainDateTimeWithRounding(plainRelativeTo.[[ISOYear]], plainRelativeTo.[[ISOMonth]], + // plainRelativeTo.[[ISODay]], 0, 0, 0, 0, 0, 0, targetDate.[[ISOYear]], targetDate.[[ISOMonth]], targetDate.[[ISODay]], + // targetTime.[[Hours]], targetTime.[[Minutes]], targetTime.[[Seconds]], targetTime.[[Milliseconds]], + // targetTime.[[Microseconds]], targetTime.[[Nanoseconds]], calendarRec, largestUnit, roundingIncrement, + // smallestUnit, roundingMode, emptyOptions). + let round_record = plain_dt.diff_dt_with_rounding(&target_dt, resolved_options)?; + // e. Let roundResult be roundRecord.[[DurationRecord]]. + round_record.0 + // 40. Else, } else { - // NOTE: DateDuration::round will always return a NormalizedTime::default as per spec. - // a. Let normWithDays be ? Add24HourDaysToNormalizedTimeDuration(roundResult.[[NormalizedTime]], roundResult.[[Days]]). - let norm_with_days = round_result.0 .1.add_days(round_result.0 .0.days as i64)?; - // b. Let balanceResult be BalanceTimeDuration(normWithDays, largestUnit). - TimeDuration::from_normalized(norm_with_days, largest_unit)? + // a. If calendarUnitsPresent is true, or IsCalendarUnit(largestUnit) is true, throw a RangeError exception. + if calendar_units_present || resolved_options.largest_unit.is_calendar_unit() { + return Err(TemporalError::range() + .with_message("Calendar units cannot be present without a relative point.")); + } + // b. Assert: IsCalendarUnit(smallestUnit) is false. + debug_assert!(!resolved_options.smallest_unit.is_calendar_unit()); + + // c. Let roundRecord be ? RoundTimeDuration(duration.[[Days]], norm, roundingIncrement, smallestUnit, roundingMode). + let (round_record, _) = TimeDuration::round(self.days(), &norm, resolved_options)?; + // d. Let normWithDays be ? Add24HourDaysToNormalizedTimeDuration(roundRecord.[[NormalizedDuration]].[[NormalizedTime]], + // roundRecord.[[NormalizedDuration]].[[Days]]). + let norm_with_days = round_record + .normalized_time_duration() + .add_days(round_record.date().days as i64)?; + // e. Let balanceResult be ? BalanceTimeDuration(normWithDays, largestUnit). + let (balanced_days, balanced_time) = + TimeDuration::from_normalized(norm_with_days, resolved_options.largest_unit)?; + // f. Let roundResult be CreateDurationRecord(0, 0, 0, balanceResult.[[Days]], balanceResult.[[Hours]], + // balanceResult.[[Minutes]], balanceResult.[[Seconds]], balanceResult.[[Milliseconds]], + // balanceResult.[[Microseconds]], balanceResult.[[Nanoseconds]]). + Duration::from_day_and_time(balanced_days, &balanced_time) }; - // 42. Let result be ? BalanceDateDurationRelative(roundResult.[[Years]], - // roundResult.[[Months]], roundResult.[[Weeks]], balanceResult.[[Days]], - // largestUnit, smallestUnit, plainRelativeTo, calendarRec). - let intermediate = DateDuration::new_unchecked( - round_result.0 .0.years, - round_result.0 .0.months, - round_result.0 .0.weeks, - balance_result.0, - ); - let result = - intermediate.balance_relative(largest_unit, smallest_unit, relative_to_date)?; - - // 43. Return ! CreateTemporalDuration(result.[[Years]], result.[[Months]], - // result.[[Weeks]], result.[[Days]], balanceResult.[[Hours]], balanceResult.[[Minutes]], - // balanceResult.[[Seconds]], balanceResult.[[Milliseconds]], balanceResult.[[Microseconds]], - // balanceResult.[[Nanoseconds]]). - Self::new( - result.years, - result.months, - result.weeks, - result.days, - balance_result.1.hours, - balance_result.1.minutes, - balance_result.1.seconds, - balance_result.1.milliseconds, - balance_result.1.microseconds, - balance_result.1.nanoseconds, - ) + // 41. Return ? CreateTemporalDuration(roundResult.[[Years]], roundResult.[[Months]], roundResult.[[Weeks]], roundResult.[[Days]], roundResult.[[Hours]], roundResult.[[Minutes]], roundResult.[[Seconds]], roundResult.[[Milliseconds]], roundResult.[[Microseconds]], roundResult.[[Nanoseconds]]). + Ok(round_result) } } @@ -659,11 +565,11 @@ pub(crate) fn is_valid_duration(set: &Vec) -> bool { return false; } // b. If v < 0 and sign > 0, return false. - if *v < 0f64 && sign > 0 { + if *v < 0f64 && sign == Sign::Positive { return false; } // c. If v > 0 and sign < 0, return false. - if *v > 0f64 && sign < 0 { + if *v > 0f64 && sign == Sign::Negative { return false; } } @@ -676,19 +582,19 @@ pub(crate) fn is_valid_duration(set: &Vec) -> bool { /// Equivalent: 7.5.10 `DurationSign ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds )` #[inline] #[must_use] -fn duration_sign(set: &Vec) -> i32 { +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. if *v < 0f64 { - return -1; + return Sign::Negative; // b. If v > 0, return 1. } else if *v > 0f64 { - return 1; + return Sign::Positive; } } // 2. Return 0. - 0 + Sign::Zero } impl From for Duration { diff --git a/src/components/duration/date.rs b/src/components/duration/date.rs index 26ec6082..dd6156d8 100644 --- a/src/components/duration/date.rs +++ b/src/components/duration/date.rs @@ -1,16 +1,11 @@ //! Implementation of a `DateDuration` use crate::{ - components::{duration::TimeDuration, Date, DateTime, Duration}, - options::{ - ArithmeticOverflow, RelativeTo, RoundingIncrement, TemporalRoundingMode, TemporalUnit, - }, - rounding::{IncrementRounder, Round}, - TemporalError, TemporalResult, TemporalUnwrap, + components::{Date, Duration}, + options::{ArithmeticOverflow, TemporalUnit}, + Sign, TemporalError, TemporalResult, TemporalUnwrap, }; -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. @@ -43,134 +38,6 @@ impl DateDuration { } } - /// Returns the `TemporalUnit` corresponding to the largest non-zero field. - #[inline] - pub(crate) fn default_largest_unit(&self) -> TemporalUnit { - self.fields() - .iter() - .enumerate() - .find(|x| x.1 != &0.0) - .map(|x| TemporalUnit::from(10 - x.0)) - .unwrap_or(TemporalUnit::Nanosecond) - } - - /// 7.5.38 `UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit, plainRelativeTo )` - #[inline] - pub(crate) fn unbalance_relative( - &self, - largest_unit: TemporalUnit, - plain_relative_to: Option<&Date>, - ) -> TemporalResult { - // 1. Assert: If plainRelativeTo is not undefined, calendarRec is not undefined. - let plain_relative = plain_relative_to.temporal_unwrap()?; - - // 2. Let defaultLargestUnit be DefaultTemporalLargestUnit(years, months, weeks, days, 0, 0, 0, 0, 0). - let default_largest = self.default_largest_unit(); - - // 3. Let effectiveLargestUnit be LargerOfTwoTemporalUnits(largestUnit, "day"). - let effective_largest = largest_unit.max(TemporalUnit::Day); - - // 4. If effectiveLargestUnit is LargerOfTwoTemporalUnits(defaultLargestUnit, effectiveLargestUnit), then - if default_largest <= effective_largest { - // a. Return ! CreateDateDurationRecord(years, months, weeks, days). - return Ok(*self); - } - - // NOTE: Should the assertion in 5 be an early error. - // 5. Assert: effectiveLargestUnit is not "year". - // 6. If calendarRec is undefined, then - // a. Throw a RangeError exception. - // 7. Assert: CalendarMethodsRecordHasLookedUp(calendarRec, DATE-ADD) is true. - - match effective_largest { - // 8. If effectiveLargestUnit is "month", then - TemporalUnit::Month => { - // a. Assert: CalendarMethodsRecordHasLookedUp(calendarRec, DATE-UNTIL) is true. - // b. Let yearsDuration be ! CreateTemporalDuration(years, 0, 0, 0, 0, 0, 0, 0, 0, 0). - let years = - Duration::from_date_duration(&Self::new_unchecked(self.years, 0.0, 0.0, 0.0)); - - // c. Let later be ? CalendarDateAdd(calendarRec, plainRelativeTo, yearsDuration). - let later = plain_relative.calendar().date_add( - plain_relative, - &years, - ArithmeticOverflow::Constrain, - )?; - - // d. Let untilOptions be OrdinaryObjectCreate(null). - // e. Perform ! CreateDataPropertyOrThrow(untilOptions, "largestUnit", "month"). - // f. Let untilResult be ? CalendarDateUntil(calendarRec, plainRelativeTo, later, untilOptions). - let until = - plain_relative - .calendar() - .date_until(plain_relative, &later, largest_unit)?; - - // g. Let yearsInMonths be untilResult.[[Months]]. - // h. Return ? CreateDateDurationRecord(0, months + yearsInMonths, weeks, days). - Self::new(0.0, self.months + until.months(), self.weeks, self.days) - } - // 9. If effectiveLargestUnit is "week", then - TemporalUnit::Week => { - // a. Let yearsMonthsDuration be ! CreateTemporalDuration(years, months, 0, 0, 0, 0, 0, 0, 0, 0). - let years_months = Duration::from_date_duration(&Self::new_unchecked( - self.years, - self.months, - 0.0, - 0.0, - )); - - // b. Let later be ? CalendarDateAdd(calendarRec, plainRelativeTo, yearsMonthsDuration). - let later = plain_relative.calendar().date_add( - plain_relative, - &years_months, - ArithmeticOverflow::Constrain, - )?; - - // c. Let yearsMonthsInDays be DaysUntil(plainRelativeTo, later). - let years_months_in_days = - plain_relative.iso.to_epoch_days() - later.iso.to_epoch_days(); - // d. Return ? CreateDateDurationRecord(0, 0, weeks, days + yearsMonthsInDays). - Self::new( - 0.0, - 0.0, - self.weeks, - self.days + f64::from(years_months_in_days), - ) - } - TemporalUnit::Day => { - // 10. NOTE: largestUnit can be any time unit as well as "day". - // 11. Let yearsMonthsWeeksDuration be ! CreateTemporalDuration(years, months, weeks, 0, 0, 0, 0, 0, 0, 0). - let years_months_weeks = Duration::from_date_duration(&Self::new_unchecked( - self.years, - self.months, - self.weeks, - 0.0, - )); - - // 12. Let later be ? CalendarDateAdd(calendarRec, plainRelativeTo, yearsMonthsWeeksDuration). - let later = plain_relative.calendar().date_add( - plain_relative, - &years_months_weeks, - ArithmeticOverflow::Constrain, - )?; - - // 13. Let yearsMonthsWeeksInDays be DaysUntil(plainRelativeTo, later). - let years_months_weeks_in_days = - plain_relative.iso.to_epoch_days() - later.iso.to_epoch_days(); - // 14. Return ? CreateDateDurationRecord(0, 0, 0, days + yearsMonthsWeeksInDays). - Self::new( - 0.0, - 0.0, - self.weeks, - self.days + f64::from(years_months_weeks_in_days), - ) - } - _ => Err(TemporalError::general( - "Invalid TemporalUnit provided to UnbalanceDateDurationRelative", - )), - } - } - /// 7.5.38 BalanceDateDurationRelative ( years, months, weeks, days, largestUnit, smallestUnit, plainRelativeTo, calendarRec ) pub fn balance_relative( &self, @@ -402,320 +269,7 @@ impl DateDuration { /// Returns the sign for the current `DateDuration`. #[inline] #[must_use] - pub fn sign(&self) -> i32 { + pub fn sign(&self) -> Sign { super::duration_sign(&self.fields()) } } - -// ==== DateDuration Operations ==== - -impl DateDuration { - /// Rounds the current `DateDuration` returning a tuple of the rounded `DateDuration` and - /// the `total` value of the smallest unit prior to rounding. - #[allow( - clippy::type_complexity, - clippy::let_and_return, - clippy::too_many_arguments - )] - pub fn round( - &self, - normalized_time: Option, - increment: RoundingIncrement, - unit: TemporalUnit, - rounding_mode: TemporalRoundingMode, - relative_to: &RelativeTo, - _precalculated_dt: Option, - ) -> TemporalResult<(Self, f64)> { - // 1. If plainRelativeTo is not present, set plainRelativeTo to undefined. - let plain_relative_to = relative_to.date; - // 2. If zonedRelativeTo is not present, set zonedRelativeTo to undefined. - let zoned_relative_to = relative_to.zdt; - // 3. If precalculatedPlainDateTime is not present, set precalculatedPlainDateTime to undefined. - - let mut fractional_days = match unit { - // 4. If unit is "year", "month", or "week", and plainRelativeTo is undefined, then - TemporalUnit::Year | TemporalUnit::Month | TemporalUnit::Week - if plain_relative_to.is_none() => - { - // a. Throw a RangeError exception. - return Err(TemporalError::range() - .with_message("plainRelativeTo canot be undefined with given TemporalUnit")); - } - // 5. If unit is one of "year", "month", "week", or "day", then - TemporalUnit::Year | TemporalUnit::Month | TemporalUnit::Week | TemporalUnit::Day => { - // a. 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 { - // TODO: fix the below cast - // i. Let fractionalDays be days + DivideNormalizedTimeDuration(norm, nsPerDay). - self.days + normalized_time.unwrap_or_default().as_fractional_days() - } - // c. Set days to 0. - } - _ => { - return Err(TemporalError::range() - .with_message("Invalid TemporalUnit provided to DateDuration.round")) - } - }; - // 7. let total be unset. - // We begin matching against unit and return the remainder value. - match unit { - // 8. If unit is "year", then - TemporalUnit::Year => { - let plain_relative_to = plain_relative_to.expect("this must exist."); - // a. Let calendar be plainRelativeTo.[[Calendar]]. - let calendar = plain_relative_to.calendar(); - - // b. Let yearsDuration be ! CreateTemporalDuration(years, 0, 0, 0, 0, 0, 0, 0, 0, 0). - let years = DateDuration::new_unchecked(self.years, 0.0, 0.0, 0.0); - let years_duration = Duration::new_unchecked(years, TimeDuration::default()); - - // c. If calendar is an Object, then - // i. Let dateAdd be ? GetMethod(calendar, "dateAdd"). - // d. Else, - // i. Let dateAdd be unused. - - // e. Let yearsLater be ? AddDate(calendar, plainRelativeTo, yearsDuration, undefined, dateAdd). - let years_later = plain_relative_to.add_date(&years_duration, None)?; - - // f. Let yearsMonthsWeeks be ! CreateTemporalDuration(years, months, weeks, 0, 0, 0, 0, 0, 0, 0). - let years_months_weeks = Duration::new_unchecked( - Self::new_unchecked(self.years, self.months, self.weeks, 0.0), - TimeDuration::default(), - ); - - // g. Let yearsMonthsWeeksLater be ? AddDate(calendar, plainRelativeTo, yearsMonthsWeeks, undefined, dateAdd). - let years_months_weeks_later = - plain_relative_to.add_date(&years_months_weeks, None)?; - - // h. Let monthsWeeksInDays be DaysUntil(yearsLater, yearsMonthsWeeksLater). - let months_weeks_in_days = years_later.days_until(&years_months_weeks_later); - - // i. Set plainRelativeTo to yearsLater. - let plain_relative_to = years_later; - - // j. Set fractionalDays to fractionalDays + monthsWeeksInDays. - fractional_days += f64::from(months_weeks_in_days); - - // k. Let isoResult be ! AddISODate(plainRelativeTo.[[ISOYear]]. plainRelativeTo.[[ISOMonth]], plainRelativeTo.[[ISODay]], 0, 0, 0, truncate(fractionalDays), "constrain"). - let iso_result = plain_relative_to.iso.add_date_duration( - &DateDuration::new_unchecked(0.0, 0.0, 0.0, fractional_days.trunc()), - ArithmeticOverflow::Constrain, - )?; - - // l. Let wholeDaysLater be ? CreateDate(isoResult.[[Year]], isoResult.[[Month]], isoResult.[[Day]], calendar). - let whole_days_later = Date::new_unchecked(iso_result, calendar.clone()); - - // m. Let untilOptions be OrdinaryObjectCreate(null). - // n. Perform ! CreateDataPropertyOrThrow(untilOptions, "largestUnit", "year"). - // o. Let timePassed be ? DifferenceDate(calendar, plainRelativeTo, wholeDaysLater, untilOptions). - let time_passed = - plain_relative_to.internal_diff_date(&whole_days_later, TemporalUnit::Year)?; - - // p. Let yearsPassed be timePassed.[[Years]]. - let years_passed = time_passed.date.years; - - // q. Set years to years + yearsPassed. - let years = self.years + years_passed; - - // r. Let yearsDuration be ! CreateTemporalDuration(yearsPassed, 0, 0, 0, 0, 0, 0, 0, 0, 0). - let years_duration = Duration::one_year(years_passed); - - // s. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, yearsDuration, dateAdd). - // t. Set plainRelativeTo to moveResult.[[RelativeTo]]. - // u. Let daysPassed be moveResult.[[Days]]. - let (plain_relative_to, days_passed) = - plain_relative_to.move_relative_date(&years_duration)?; - - // v. Set fractionalDays to fractionalDays - daysPassed. - fractional_days -= days_passed; - - // w. If fractionalDays < 0, let sign be -1; else, let sign be 1. - let sign = if fractional_days < 0.0 { -1 } else { 1 }; - - // x. Let oneYear be ! CreateTemporalDuration(sign, 0, 0, 0, 0, 0, 0, 0, 0, 0). - let one_year = Duration::one_year(f64::from(sign)); - - // y. Set moveResult to ? MoveRelativeDate(calendar, plainRelativeTo, oneYear, dateAdd). - // z. Let oneYearDays be moveResult.[[Days]]. - let (_, one_year_days) = plain_relative_to.move_relative_date(&one_year)?; - - if one_year_days == 0.0 { - return Err(TemporalError::range().with_message("oneYearDays exceeds ranges.")); - } - // aa. Let fractionalYears be years + fractionalDays / abs(oneYearDays). - let frac_years = years + (fractional_days / one_year_days.abs()); - - // ab. Set years to RoundNumberToIncrement(fractionalYears, increment, roundingMode). - let rounded_years = IncrementRounder::::from_potentially_negative_parts( - frac_years, - increment.as_extended_increment(), - )? - .round(rounding_mode); - - // ac. Set total to fractionalYears. - // ad. Set months and weeks to 0. - let result = Self::new(rounded_years as f64, 0f64, 0f64, 0f64)?; - Ok((result, frac_years)) - } - // 9. Else if unit is "month", then - TemporalUnit::Month => { - // a. Let calendar be plainRelativeTo.[[Calendar]]. - let plain_relative_to = plain_relative_to.expect("this must exist."); - - // b. Let yearsMonths be ! CreateTemporalDuration(years, months, 0, 0, 0, 0, 0, 0, 0, 0). - let years_months = Duration::from_date_duration(&DateDuration::new_unchecked( - self.years, - self.months, - 0.0, - 0.0, - )); - - // c. If calendar is an Object, then - // i. Let dateAdd be ? GetMethod(calendar, "dateAdd"). - // d. Else, - // i. Let dateAdd be unused. - - // e. Let yearsMonthsLater be ? AddDate(calendar, plainRelativeTo, yearsMonths, undefined, dateAdd). - let years_months_later = plain_relative_to.add_date(&years_months, None)?; - - // f. Let yearsMonthsWeeks be ! CreateTemporalDuration(years, months, weeks, 0, 0, 0, 0, 0, 0, 0). - let years_months_weeks = Duration::from_date_duration( - &DateDuration::new_unchecked(self.years, self.months, self.weeks, 0.0), - ); - - // g. Let yearsMonthsWeeksLater be ? AddDate(calendar, plainRelativeTo, yearsMonthsWeeks, undefined, dateAdd). - let years_months_weeks_later = - plain_relative_to.add_date(&years_months_weeks, None)?; - - // h. Let weeksInDays be DaysUntil(yearsMonthsLater, yearsMonthsWeeksLater). - let weeks_in_days = years_months_later.days_until(&years_months_weeks_later); - - // i. Set plainRelativeTo to yearsMonthsLater. - let plain_relative_to = years_months_later; - - // j. Set fractionalDays to fractionalDays + weeksInDays. - fractional_days += f64::from(weeks_in_days); - - // k. If fractionalDays < 0, let sign be -1; else, let sign be 1. - let sign = if fractional_days < 0.0 { -1f64 } else { 1f64 }; - - // l. Let oneMonth be ! CreateTemporalDuration(0, sign, 0, 0, 0, 0, 0, 0, 0, 0). - let one_month = Duration::one_month(sign); - - // m. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, oneMonth, dateAdd). - // n. Set plainRelativeTo to moveResult.[[RelativeTo]]. - // o. Let oneMonthDays be moveResult.[[Days]]. - let (mut plain_relative_to, mut one_month_days) = - plain_relative_to.move_relative_date(&one_month)?; - - let mut months = self.months; - // p. Repeat, while abs(fractionalDays) ≥ abs(oneMonthDays), - while fractional_days.abs() >= one_month_days.abs() { - // i. Set months to months + sign. - months += sign; - - // ii. Set fractionalDays to fractionalDays - oneMonthDays. - fractional_days -= one_month_days; - - // iii. Set moveResult to ? MoveRelativeDate(calendar, plainRelativeTo, oneMonth, dateAdd). - let move_result = plain_relative_to.move_relative_date(&one_month)?; - - // iv. Set plainRelativeTo to moveResult.[[RelativeTo]]. - plain_relative_to = move_result.0; - // v. Set oneMonthDays to moveResult.[[Days]]. - one_month_days = move_result.1; - } - - // q. Let fractionalMonths be months + fractionalDays / abs(oneMonthDays). - let frac_months = months + fractional_days / one_month_days.abs(); - - // r. Set months to RoundNumberToIncrement(fractionalMonths, increment, roundingMode). - let rounded_months = IncrementRounder::::from_potentially_negative_parts( - frac_months, - increment.as_extended_increment(), - )? - .round(rounding_mode); - - // s. Set total to fractionalMonths. - // t. Set weeks to 0. - let result = Self::new(self.years, rounded_months as f64, 0f64, 0f64)?; - Ok((result, frac_months)) - } - // 10. Else if unit is "week", then - TemporalUnit::Week => { - // a. Let calendar be plainRelativeTo.[[Calendar]]. - let plain_relative_to = plain_relative_to.expect("date must exist given Week"); - - // b. If fractionalDays < 0, let sign be -1; else, let sign be 1. - let sign = if fractional_days < 0.0 { -1f64 } else { 1f64 }; - - // c. Let oneWeek be ! CreateTemporalDuration(0, 0, sign, 0, 0, 0, 0, 0, 0, 0). - let one_week = Duration::one_week(sign); - - // d. If calendar is an Object, then - // i. Let dateAdd be ? GetMethod(calendar, "dateAdd"). - // e. Else, - // i. Let dateAdd be unused. - - // f. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, oneWeek, dateAdd). - // g. Set plainRelativeTo to moveResult.[[RelativeTo]]. - // h. Let oneWeekDays be moveResult.[[Days]]. - let (mut plain_relative_to, mut one_week_days) = - plain_relative_to.move_relative_date(&one_week)?; - - let mut weeks = self.weeks; - // i. Repeat, while abs(fractionalDays) ≥ abs(oneWeekDays), - while fractional_days.abs() >= one_week_days.abs() { - // i. Set weeks to weeks + sign. - weeks += sign; - - // ii. Set fractionalDays to fractionalDays - oneWeekDays. - fractional_days -= one_week_days; - - // iii. Set moveResult to ? MoveRelativeDate(calendar, plainRelativeTo, oneWeek, dateAdd). - let move_result = plain_relative_to.move_relative_date(&one_week)?; - - // iv. Set plainRelativeTo to moveResult.[[RelativeTo]]. - plain_relative_to = move_result.0; - // v. Set oneWeekDays to moveResult.[[Days]]. - one_week_days = move_result.1; - } - - // j. Let fractionalWeeks be weeks + fractionalDays / abs(oneWeekDays). - let frac_weeks = weeks + fractional_days / one_week_days.abs(); - - // k. Set weeks to RoundNumberToIncrement(fractionalWeeks, increment, roundingMode). - let rounded_weeks = IncrementRounder::::from_potentially_negative_parts( - frac_weeks, - increment.as_extended_increment(), - )? - .round(rounding_mode); - // l. Set total to fractionalWeeks. - 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 = IncrementRounder::::from_potentially_negative_parts( - fractional_days, - increment.as_extended_increment(), - )? - .round(rounding_mode); - - // b. Set total to fractionalDays. - // 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 934a38f4..3935b0ad 100644 --- a/src/components/duration/normalized.rs +++ b/src/components/duration/normalized.rs @@ -1,16 +1,18 @@ //! This module implements the normalized `Duration` records. -use std::{num::NonZeroU64, ops::Add}; +use std::{num::NonZeroU128, ops::Add}; use num_traits::Euclid; use crate::{ - options::TemporalRoundingMode, + components::{tz::TimeZone, Date, DateTime}, + iso::IsoDate, + options::{ArithmeticOverflow, ResolvedRoundingOptions, TemporalRoundingMode, TemporalUnit}, rounding::{IncrementRounder, Round}, - TemporalError, TemporalResult, NS_PER_DAY, + TemporalError, TemporalResult, TemporalUnwrap, NS_PER_DAY, }; -use super::{DateDuration, TimeDuration}; +use super::{DateDuration, Duration, Sign, TimeDuration}; const MAX_TIME_DURATION: i128 = 9_007_199_254_740_991_999_999_999; @@ -20,7 +22,14 @@ const NS_PER_DAY_128BIT: i128 = NS_PER_DAY as i128; const NANOSECONDS_PER_MINUTE: f64 = 60.0 * 1e9; const NANOSECONDS_PER_HOUR: f64 = 60.0 * 60.0 * 1e9; -// TODO: This should be moved to i128 +// ==== NormalizedTimeDuration ==== +// +// A time duration represented in pure nanoseconds. +// +// Invariants: +// +// nanoseconds.abs() <= MAX_TIME_DURATION + /// A Normalized `TimeDuration` that represents the current `TimeDuration` in nanoseconds. #[derive(Debug, Clone, Copy, Default, PartialEq, PartialOrd)] pub struct NormalizedTimeDuration(pub(crate) i128); @@ -31,8 +40,8 @@ impl NormalizedTimeDuration { // 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 * 1e9) as i128; - nanoseconds += (time.milliseconds * 1e6) 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; // NOTE(nekevss): Is it worth returning a `RangeError` below. @@ -43,7 +52,7 @@ impl NormalizedTimeDuration { // NOTE: `days: f64` should be an integer -> `i64`. /// Equivalent: 7.5.23 Add24HourDaysToNormalizedTimeDuration ( d, days ) #[allow(unused)] - pub(super) fn add_days(&self, days: i64) -> TemporalResult { + pub(crate) fn add_days(&self, days: i64) -> TemporalResult { let result = self.0 + i128::from(days) * i128::from(NS_PER_DAY); if result.abs() > MAX_TIME_DURATION { return Err(TemporalError::range() @@ -52,17 +61,22 @@ impl NormalizedTimeDuration { Ok(Self(result)) } - // TODO: Implement as `ops::Div` + // TODO: Potentially, update divisor to u64? /// `Divide the NormalizedTimeDuraiton` by a divisor. pub(super) fn divide(&self, divisor: i64) -> i128 { // TODO: Validate. self.0 / i128::from(divisor) } - // TODO: Use in algorithm update or remove. - #[allow(unused)] + // NOTE(nekevss): non-euclid is required here for negative rounding. + /// Returns the div_rem of this NormalizedTimeDuration. + pub(super) fn div_rem(&self, divisor: u64) -> (i128, i128) { + (self.0 / i128::from(divisor), self.0 % i128::from(divisor)) + } + + // Returns the fractionalDays value represented by this `NormalizedTimeDuration` pub(super) fn as_fractional_days(&self) -> f64 { - // TODO: Verify Max norm is within a castable f64 range. + // TODO: Unit test to verify MaxNormalized is within a castable f64 range. let (days, remainder) = self.0.div_rem_euclid(&NS_PER_DAY_128BIT); days as f64 + (remainder as f64 / NS_PER_DAY as f64) } @@ -71,26 +85,38 @@ impl NormalizedTimeDuration { /// Equivalent: 7.5.31 NormalizedTimeDurationSign ( d ) #[inline] #[must_use] - pub(super) fn sign(&self) -> i32 { - self.0.cmp(&0) as i32 + pub(crate) fn sign(&self) -> Sign { + Sign::from(self.0.cmp(&0) as i8) } + // NOTE(nekevss): non-euclid is required here for negative rounding. /// Return the seconds value of the `NormalizedTimeDuration`. pub(crate) fn seconds(&self) -> i64 { // SAFETY: See validate_second_cast test. - (self.0.div_euclid(1_000_000_000)) as i64 + (self.0 / 1_000_000_000) as i64 } + // NOTE(nekevss): non-euclid is required here for negative rounding. /// Returns the subsecond components of the `NormalizedTimeDuration`. pub(crate) fn subseconds(&self) -> i32 { // SAFETY: Remainder is 10e9 which is in range of i32 - (self.0.rem_euclid(1_000_000_000)) as i32 + (self.0 % 1_000_000_000) as i32 + } + + pub(crate) fn checked_sub(&self, other: &Self) -> TemporalResult { + let result = self.0 - other.0; + if result.abs() > MAX_TIME_DURATION { + return Err(TemporalError::range().with_message( + "SubtractNormalizedTimeDuration exceeded a valid TimeDuration range.", + )); + } + Ok(Self(result)) } /// Round the current `NormalizedTimeDuration`. pub(super) fn round( &self, - increment: NonZeroU64, + increment: NonZeroU128, mode: TemporalRoundingMode, ) -> TemporalResult { let rounded = IncrementRounder::::from_potentially_negative_parts(self.0, increment)? @@ -118,20 +144,618 @@ impl Add for NormalizedTimeDuration { } } -/// A normalized `DurationRecord` that contains a `DateDuration` and `NormalizedTimeDuration`. +// ==== NormalizedDurationRecord ==== +// +// A record consisting of a DateDuration and NormalizedTimeDuration +// + +/// A NormalizedDurationRecord is a duration record that contains +/// a `DateDuration` and `NormalizedTimeDuration`. #[derive(Debug, Clone, Copy)] -pub struct NormalizedDurationRecord(pub(crate) (DateDuration, NormalizedTimeDuration)); +pub struct NormalizedDurationRecord { + date: DateDuration, + norm: NormalizedTimeDuration, +} 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() { + pub(crate) fn new(date: DateDuration, norm: NormalizedTimeDuration) -> TemporalResult { + if date.sign() != Sign::Zero && norm.sign() != Sign::Zero && date.sign() != norm.sign() { return Err(TemporalError::range() .with_message("DateDuration and NormalizedTimeDuration must agree.")); } - Ok(Self((date, norm))) + Ok(Self { date, norm }) + } + + pub(crate) fn from_date_duration(date: DateDuration) -> TemporalResult { + Self::new(date, NormalizedTimeDuration::default()) + } + + pub(crate) fn date(&self) -> DateDuration { + self.date + } + + pub(crate) fn normalized_time_duration(&self) -> NormalizedTimeDuration { + self.norm + } + + pub(crate) fn sign(&self) -> TemporalResult { + Ok(self.date.sign()) + } +} + +// ==== Nudge Duration Rounding Functions ==== + +// Below implements the nudge rounding functionality for Duration. +// +// Generally, this rounding is implemented on a NormalizedDurationRecord, +// which is the reason the functionality lives below. + +#[derive(Debug)] +struct NudgeRecord { + normalized: NormalizedDurationRecord, + total: Option, // TODO: adjust + nudge_epoch_ns: i128, + expanded: bool, +} + +pub(crate) type RelativeRoundResult = (Duration, Option); + +impl NormalizedDurationRecord { + // TODO: Add assertion into impl. + // TODO: Add unit tests specifically for nudge_calendar_unit if possible. + fn nudge_calendar_unit( + &self, + sign: Sign, + dest_epoch_ns: i128, + dt: &DateTime, + tz: Option, // ??? + options: ResolvedRoundingOptions, + ) -> TemporalResult { + // NOTE: r2 may never be used...need to test. + let (r1, r2, start_duration, end_duration) = match options.smallest_unit { + // 1. If unit is "year", then + TemporalUnit::Year => { + // a. Let years be RoundNumberToIncrement(duration.[[Years]], increment, "trunc"). + let years = IncrementRounder::from_potentially_negative_parts( + self.date().years, + options.increment.as_extended_increment(), + )? + .round(TemporalRoundingMode::Trunc); + // b. Let r1 be years. + let r1 = years; + // c. Let r2 be years + increment × sign. + let r2 = years + + i128::from(options.increment.get()) * i128::from(sign.as_sign_multiplier()); + // d. Let startDuration be ? CreateNormalizedDurationRecord(r1, 0, 0, 0, ZeroTimeDuration()). + // e. Let endDuration be ? CreateNormalizedDurationRecord(r2, 0, 0, 0, ZeroTimeDuration()). + ( + r1, + r2, + DateDuration::new(r1 as f64, 0.0, 0.0, 0.0)?, + DateDuration::new(r2 as f64, 0.0, 0.0, 0.0)?, + ) + } + // 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, + options.increment.as_extended_increment(), + )? + .round(TemporalRoundingMode::Trunc); + // b. Let r1 be months. + let r1 = months; + // c. Let r2 be months + increment × sign. + let r2 = months + + i128::from(options.increment.get()) * i128::from(sign.as_sign_multiplier()); + // d. Let startDuration be ? CreateNormalizedDurationRecord(duration.[[Years]], r1, 0, 0, ZeroTimeDuration()). + // e. Let endDuration be ? CreateNormalizedDurationRecord(duration.[[Years]], r2, 0, 0, ZeroTimeDuration()). + ( + 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)?, + ) + } + // 3. Else if unit is "week", then + TemporalUnit::Week => { + // TODO: Reconcile potential overflow on years as i32. `ValidateDuration` requires years, months, weeks to be abs(x) <= 2^32 + // 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, + 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, + ); + + // c. Let weeksStart be ! CreateTemporalDate(isoResult1.[[Year]], isoResult1.[[Month]], isoResult1.[[Day]], + // calendarRec.[[Receiver]]). + let weeks_start = Date::new( + iso_one.year, + iso_one.month.into(), + iso_one.day.into(), + dt.calendar().clone(), + ArithmeticOverflow::Reject, + )?; + + // d. Let weeksEnd be ! CreateTemporalDate(isoResult2.[[Year]], isoResult2.[[Month]], isoResult2.[[Day]], + // calendarRec.[[Receiver]]). + let weeks_end = Date::new( + iso_two.year, + iso_two.month.into(), + iso_two.day.into(), + dt.calendar().clone(), + ArithmeticOverflow::Reject, + )?; + + // e. Let untilOptions be OrdinaryObjectCreate(null). + // f. Perform ! CreateDataPropertyOrThrow(untilOptions, "largestUnit", "week"). + // g. Let untilResult be ? DifferenceDate(calendarRec, weeksStart, weeksEnd, untilOptions). + let until_result = + weeks_start.internal_diff_date(&weeks_end, TemporalUnit::Week)?; + + // h. Let weeks be RoundNumberToIncrement(duration.[[Weeks]] + untilResult.[[Weeks]], increment, "trunc"). + let weeks = IncrementRounder::from_potentially_negative_parts( + self.date().weeks + until_result.weeks(), + options.increment.as_extended_increment(), + )? + .round(TemporalRoundingMode::Trunc); + + // i. Let r1 be weeks. + let r1 = weeks; + // j. Let r2 be weeks + increment × sign. + let r2 = weeks + + i128::from(options.increment.get()) * i128::from(sign.as_sign_multiplier()); + // k. Let startDuration be ? CreateNormalizedDurationRecord(duration.[[Years]], duration.[[Months]], r1, 0, ZeroTimeDuration()). + // l. Let endDuration be ? CreateNormalizedDurationRecord(duration.[[Years]], duration.[[Months]], r2, 0, ZeroTimeDuration()). + ( + 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)?, + ) + } + TemporalUnit::Day => { + // 4. Else, + // a. Assert: unit is "day". + // b. Let days be RoundNumberToIncrement(duration.[[Days]], increment, "trunc"). + let days = IncrementRounder::from_potentially_negative_parts( + self.date().days, + options.increment.as_extended_increment(), + )? + .round(TemporalRoundingMode::Trunc); + // c. Let r1 be days. + let r1 = days; + // d. Let r2 be days + increment × sign. + let r2 = days + + i128::from(options.increment.get()) * i128::from(sign.as_sign_multiplier()); + // e. Let startDuration be ? CreateNormalizedDurationRecord(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], r1, ZeroTimeDuration()). + // f. Let endDuration be ? CreateNormalizedDurationRecord(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], r2, ZeroTimeDuration()). + ( + r1, + r2, + DateDuration::new( + self.date().years, + self.date().months, + self.date().weeks, + r1 as f64, + )?, + DateDuration::new( + self.date().years, + self.date().months, + self.date().weeks, + r2 as f64, + )?, + ) + } + _ => unreachable!(), // TODO: potentially reject with range error? + }; + + // 5. Let start be ? AddDateTime(dateTime.[[Year]], dateTime.[[Month]], dateTime.[[Day]], dateTime.[[Hour]], dateTime.[[Minute]], + // dateTime.[[Second]], dateTime.[[Millisecond]], dateTime.[[Microsecond]], dateTime.[[Nanosecond]], calendarRec, + // startDuration.[[Years]], startDuration.[[Months]], startDuration.[[Weeks]], startDuration.[[Days]], startDuration.[[NormalizedTime]], undefined). + let start = dt.iso.add_date_duration( + dt.calendar().clone(), + &start_duration, + NormalizedTimeDuration::default(), + None, + )?; + + // 6. Let end be ? AddDateTime(dateTime.[[Year]], dateTime.[[Month]], dateTime.[[Day]], dateTime.[[Hour]], + // dateTime.[[Minute]], dateTime.[[Second]], dateTime.[[Millisecond]], dateTime.[[Microsecond]], + // dateTime.[[Nanosecond]], calendarRec, endDuration.[[Years]], endDuration.[[Months]], endDuration.[[Weeks]], + // endDuration.[[Days]], endDuration.[[NormalizedTime]], undefined). + let end = dt.iso.add_date_duration( + dt.calendar().clone(), + &end_duration, + NormalizedTimeDuration::default(), + None, + )?; + + // 7. If timeZoneRec is unset, then + let (start_epoch_ns, end_epoch_ns) = if tz.is_none() { + // TODO: Test valid range of EpochNanoseconds in order to add `expect` over `unwrap_or` + // a. Let startEpochNs be GetUTCEpochNanoseconds(start.[[Year]], start.[[Month]], start.[[Day]], start.[[Hour]], start.[[Minute]], start.[[Second]], start.[[Millisecond]], start.[[Microsecond]], start.[[Nanosecond]]). + // b. Let endEpochNs be GetUTCEpochNanoseconds(end.[[Year]], end.[[Month]], end.[[Day]], end.[[Hour]], end.[[Minute]], end.[[Second]], end.[[Millisecond]], end.[[Microsecond]], end.[[Nanosecond]]). + ( + start.as_nanoseconds(0.0).unwrap_or(0), + end.as_nanoseconds(0.0).unwrap_or(0), + ) + // 8. Else, + } else { + // a. Let startDateTime be ! CreateTemporalDateTime(start.[[Year]], start.[[Month]], start.[[Day]], + // start.[[Hour]], start.[[Minute]], start.[[Second]], start.[[Millisecond]], start.[[Microsecond]], + // start.[[Nanosecond]], calendarRec.[[Receiver]]). + // b. Let startInstant be ? GetInstantFor(timeZoneRec, startDateTime, "compatible"). + // c. Let startEpochNs be startInstant.[[Nanoseconds]]. + // d. Let endDateTime be ! CreateTemporalDateTime(end.[[Year]], end.[[Month]], end.[[Day]], end.[[Hour]], end.[[Minute]], end.[[Second]], end.[[Millisecond]], end.[[Microsecond]], end.[[Nanosecond]], calendarRec.[[Receiver]]). + // e. Let endInstant be ? GetInstantFor(timeZoneRec, endDateTime, "compatible"). + // f. Let endEpochNs be endInstant.[[Nanoseconds]]. + return Err(TemporalError::general( + "TimeZone handling not yet implemented.", + )); + }; + + // 9. If endEpochNs = startEpochNs, throw a RangeError exception. + if end_epoch_ns == start_epoch_ns { + return Err( + TemporalError::range().with_message("endEpochNs cannot be equal to startEpochNs") + ); + } + + // TODO: Add early RangeError steps that are currently missing + + // NOTE: Below is removed in place of using `IncrementRounder` + // 10. If sign < 0, let isNegative be negative; else let isNegative be positive. + // 11. Let unsignedRoundingMode be GetUnsignedRoundingMode(roundingMode, isNegative). + + // NOTE(nekevss): Step 12..13 could be problematic...need tests + // and verify, or completely change the approach involved. + // TODO(nekevss): Validate that the `f64` casts here are valid in all scenarios + // 12. Let progress be (destEpochNs - startEpochNs) / (endEpochNs - startEpochNs). + // 13. Let total be r1 + progress × increment × sign. + let progress = + (dest_epoch_ns - start_epoch_ns) as f64 / (end_epoch_ns - start_epoch_ns) as f64; + let total = r1 as f64 + + progress * options.increment.get() as f64 * f64::from(sign.as_sign_multiplier()); + + // TODO: Test and verify that `IncrementRounder` handles the below case. + // NOTE(nekevss): Below will not return the calculated r1 or r2, so it is imporant to not use + // the result beyond determining rounding direction. + // 14. NOTE: The above two steps cannot be implemented directly using floating-point arithmetic. + // This division can be implemented as if constructing Normalized Time Duration Records for the denominator + // and numerator of total and performing one division operation with a floating-point result. + // 15. Let roundedUnit be ApplyUnsignedRoundingMode(total, r1, r2, unsignedRoundingMode). + let rounded_unit = IncrementRounder::from_potentially_negative_parts( + total, + options.increment.as_extended_increment(), + )? + .round(options.rounding_mode); + + // 16. If roundedUnit - total < 0, let roundedSign be -1; else let roundedSign be 1. + // 19. Return Duration Nudge Result Record { [[Duration]]: resultDuration, [[Total]]: total, [[NudgedEpochNs]]: nudgedEpochNs, [[DidExpandCalendarUnit]]: didExpandCalendarUnit }. + // 17. If roundedSign = sign, then + if rounded_unit == r2 { + // a. Let didExpandCalendarUnit be true. + // b. Let resultDuration be endDuration. + // c. Let nudgedEpochNs be endEpochNs. + Ok(NudgeRecord { + normalized: NormalizedDurationRecord::new( + end_duration, + NormalizedTimeDuration::default(), + )?, + total: Some(total as i128), + nudge_epoch_ns: end_epoch_ns, + expanded: true, + }) + // 18. Else, + } else { + // a. Let didExpandCalendarUnit be false. + // b. Let resultDuration be startDuration. + // c. Let nudgedEpochNs be startEpochNs. + Ok(NudgeRecord { + normalized: NormalizedDurationRecord::new( + start_duration, + NormalizedTimeDuration::default(), + )?, + total: Some(total as i128), + nudge_epoch_ns: start_epoch_ns, + expanded: false, + }) + } + } + + #[inline] + fn nudge_to_zoned_time(&self) -> TemporalResult { + // TODO: Implement + Err(TemporalError::general("Not yet implemented.")) + } + + #[inline] + fn nudge_to_day_or_time( + &self, + dest_epoch_ns: i128, + options: ResolvedRoundingOptions, + ) -> TemporalResult { + // 1. Assert: The value in the "Category" column of the row of Table 22 whose "Singular" column contains smallestUnit, is time. + // 2. Let norm be ! Add24HourDaysToNormalizedTimeDuration(duration.[[NormalizedTime]], duration.[[Days]]). + let norm = self + .normalized_time_duration() + .add_days(self.date().days as i64)?; + + // 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()?; + // 4. Let total be DivideNormalizedTimeDuration(norm, unitLength). + let total = norm.divide(unit_length as i64); + + // 5. Let roundedNorm be ? RoundNormalizedTimeDurationToIncrement(norm, unitLength × increment, roundingMode). + let rounded_norm = norm.round( + unsafe { + NonZeroU128::new_unchecked(unit_length.into()) + .checked_mul(options.increment.as_extended_increment()) + .temporal_unwrap()? + }, + options.rounding_mode, + )?; + + // 6. Let diffNorm be ! SubtractNormalizedTimeDuration(roundedNorm, norm). + let diff_norm = rounded_norm.checked_sub(&norm)?; + + // 7. Let wholeDays be truncate(DivideNormalizedTimeDuration(norm, nsPerDay)). + let whole_days = norm.divide(NS_PER_DAY as i64); + + // 8. Let roundedFractionalDays be DivideNormalizedTimeDuration(roundedNorm, nsPerDay). + let (rounded_whole_days, rounded_remainder) = rounded_norm.div_rem(NS_PER_DAY); + + // 9. Let roundedWholeDays be truncate(roundedFractionalDays). + // 10. Let dayDelta be roundedWholeDays - wholeDays. + let delta = rounded_whole_days - whole_days; + // 11. If dayDelta < 0, let dayDeltaSign be -1; else if dayDelta > 0, let dayDeltaSign be 1; else let dayDeltaSign be 0. + // 12. If dayDeltaSign = NormalizedTimeDurationSign(norm), let didExpandDays be true; else let didExpandDays be false. + let did_expand_days = delta.signum() as i8 == norm.sign() as i8; + + // 13. Let nudgedEpochNs be AddNormalizedTimeDurationToEpochNanoseconds(diffNorm, destEpochNs). + let nudged_ns = diff_norm.0 + dest_epoch_ns; + + // 14. Let days be 0. + let mut days = 0; + // 15. Let remainder be roundedNorm. + let mut remainder = rounded_norm; + // 16. If LargerOfTwoTemporalUnits(largestUnit, "day") is largestUnit, then + if options.largest_unit.max(TemporalUnit::Day) == options.largest_unit { + // a. Set days to roundedWholeDays. + days = rounded_whole_days; + // b. Set remainder to remainder(roundedFractionalDays, 1) × nsPerDay. + remainder = NormalizedTimeDuration(rounded_remainder); + } + // 17. Let resultDuration be ? CreateNormalizedDurationRecord(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], days, remainder). + let result_duration = NormalizedDurationRecord::new( + DateDuration::new( + self.date().years, + self.date().months, + self.date().weeks, + days as f64, + )?, + remainder, + )?; + // 18. Return Duration Nudge Result Record { [[Duration]]: resultDuration, [[Total]]: total, + // [[NudgedEpochNs]]: nudgedEpochNs, [[DidExpandCalendarUnit]]: didExpandDays }. + Ok(NudgeRecord { + normalized: result_duration, + total: Some(total), + nudge_epoch_ns: nudged_ns, + expanded: did_expand_days, + }) + } + + // 7.5.43 BubbleRelativeDuration ( sign, duration, nudgedEpochNs, dateTime, calendarRec, timeZoneRec, largestUnit, smallestUnit ) + #[inline] + #[allow(clippy::too_many_arguments)] + fn bubble_relative_duration( + &self, + sign: Sign, + nudge_epoch_ns: i128, + date_time: &DateTime, + tz: Option, + largest_unit: TemporalUnit, + smallest_unit: TemporalUnit, + ) -> TemporalResult { + // Assert: The value in the "Category" column of the row of Table 22 whose "Singular" column contains largestUnit, is date. + // 2. Assert: The value in the "Category" column of the row of Table 22 whose "Singular" column contains smallestUnit, is date. + let mut duration = *self; + // 3. If smallestUnit is "year", return duration. + if smallest_unit == TemporalUnit::Year { + return Ok(duration); + } + + // NOTE: Invert ops as Temporal Proposal table is inverted (i.e. Year = 0 ... Nanosecond = 9) + // 4. Let largestUnitIndex be the ordinal index of the row of Table 22 whose "Singular" column contains largestUnit. + // 5. Let smallestUnitIndex be the ordinal index of the row of Table 22 whose "Singular" column contains smallestUnit. + // 6. Let unitIndex be smallestUnitIndex - 1. + let mut unit = smallest_unit + 1; + // 7. Let done be false. + // 8. Repeat, while unitIndex ≤ largestUnitIndex and done is false, + while unit != TemporalUnit::Auto && unit <= largest_unit { + // a. Let unit be the value in the "Singular" column of Table 22 in the row whose ordinal index is unitIndex. + // b. If unit is not "week", or largestUnit is "week", then + if unit == TemporalUnit::Week || largest_unit != TemporalUnit::Week { + unit = unit + 1; + continue; + } + + let end_duration = match unit { + // i. If unit is "year", then + TemporalUnit::Year => { + // 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, + )? + } + // ii. Else if unit is "month", then + TemporalUnit::Month => { + // 1. Let months be duration.[[Months]] + sign. + // 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, + )? + } + // iii. Else if unit is "week", then + TemporalUnit::Week => { + // 1. Let weeks be duration.[[Weeks]] + sign. + // 2. Let endDuration be ? CreateNormalizedDurationRecord(duration.[[Years]], duration.[[Months]], weeks, 0, ZeroTimeDuration()). + DateDuration::new( + duration.date().years, + duration.date().months, + duration.date().weeks + f64::from(sign.as_sign_multiplier()), + 0.0, + )? + } + // iv. Else, + TemporalUnit::Day => { + // 1. Assert: unit is "day". + // 2. Let days be duration.[[Days]] + sign. + // 3. Let endDuration be ? CreateNormalizedDurationRecord(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], days, ZeroTimeDuration()). + DateDuration::new( + duration.date().years, + duration.date().months, + duration.date().weeks, + duration.date().days + f64::from(sign.as_sign_multiplier()), + )? + } + _ => unreachable!(), + }; + + // v. Let end be ? AddDateTime(dateTime.[[Year]], dateTime.[[Month]], dateTime.[[Day]], dateTime.[[Hour]], dateTime.[[Minute]], + // dateTime.[[Second]], dateTime.[[Millisecond]], dateTime.[[Microsecond]], dateTime.[[Nanosecond]], calendarRec, + // endDuration.[[Years]], endDuration.[[Months]], endDuration.[[Weeks]], endDuration.[[Days]], endDuration.[[NormalizedTime]], undefined). + let end = date_time.iso.add_date_duration( + date_time.calendar().clone(), + &end_duration, + NormalizedTimeDuration::default(), + None, + )?; + + // vi. If timeZoneRec is unset, then + let end_epoch_ns = if let Some(ref _tz) = tz { + // 1. Let endDateTime be ! CreateTemporalDateTime(end.[[Year]], end.[[Month]], end.[[Day]], + // end.[[Hour]], end.[[Minute]], end.[[Second]], end.[[Millisecond]], end.[[Microsecond]], + // end.[[Nanosecond]], calendarRec.[[Receiver]]). + // 2. Let endInstant be ? GetInstantFor(timeZoneRec, endDateTime, "compatible"). + // 3. Let endEpochNs be endInstant.[[Nanoseconds]]. + return Err(TemporalError::general("Not yet implemented.")); + // vii. Else, + } else { + // 1. Let endEpochNs be GetUTCEpochNanoseconds(end.[[Year]], end.[[Month]], end.[[Day]], end.[[Hour]], + // end.[[Minute]], end.[[Second]], end.[[Millisecond]], end.[[Microsecond]], end.[[Nanosecond]]). + end.as_nanoseconds(0.0).temporal_unwrap()? + }; + // viii. Let beyondEnd be nudgedEpochNs - endEpochNs. + let beyond_end = nudge_epoch_ns - end_epoch_ns; + // ix. If beyondEnd < 0, let beyondEndSign be -1; else if beyondEnd > 0, let beyondEndSign be 1; else let beyondEndSign be 0. + // x. If beyondEndSign ≠ -sign, then + if beyond_end.signum() != -i128::from(sign.as_sign_multiplier()) { + // 1. Set duration to endDuration. + duration = NormalizedDurationRecord::from_date_duration(end_duration)?; + // xi. Else, + } else { + // 1. Set done to true. + break; + } + // c. Set unitIndex to unitIndex - 1. + unit = unit + 1; + } + + Ok(duration) + } + + // 7.5.44 RoundRelativeDuration ( duration, destEpochNs, dateTime, calendarRec, timeZoneRec, largestUnit, increment, smallestUnit, roundingMode ) + #[inline] + pub(crate) fn round_relative_duration( + &self, + dest_epoch_ns: i128, + dt: &DateTime, + tz: Option, + options: ResolvedRoundingOptions, + ) -> TemporalResult { + // 1. Let irregularLengthUnit be false. + // 2. If IsCalendarUnit(smallestUnit) is true, set irregularLengthUnit to true. + // 3. If timeZoneRec is not unset and smallestUnit is "day", set irregularLengthUnit to true. + let irregular_unit = options.smallest_unit.is_calendar_unit() + || (tz.is_some() && options.smallest_unit == TemporalUnit::Day); + + // 4. If DurationSign(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], NormalizedTimeDurationSign(duration.[[NormalizedTime]]), 0, 0, 0, 0, 0) < 0, let sign be -1; else let sign be 1. + let sign = self.sign()?; + + // 5. If irregularLengthUnit is true, then + let nudge_result = if irregular_unit { + // a. Let nudgeResult be ? NudgeToCalendarUnit(sign, duration, destEpochNs, dateTime, calendarRec, timeZoneRec, increment, smallestUnit, roundingMode). + self.nudge_calendar_unit(sign, dest_epoch_ns, dt, tz.clone(), options)? + // 6. Else if timeZoneRec is not unset, then + } else if let Some(ref _tz) = tz { + // a. Let nudgeResult be ? NudgeToZonedTime(sign, duration, dateTime, calendarRec, timeZoneRec, increment, smallestUnit, roundingMode). + self.nudge_to_zoned_time()? + // 7. Else, + } else { + // a. Let nudgeResult be ? NudgeToDayOrTime(duration, destEpochNs, largestUnit, increment, smallestUnit, roundingMode). + self.nudge_to_day_or_time(dest_epoch_ns, options)? + }; + + // 8. Set duration to nudgeResult.[[Duration]]. + let mut duration = nudge_result.normalized; + + // 9. If nudgeResult.[[DidExpandCalendarUnit]] is true and smallestUnit is not "week", then + if nudge_result.expanded && options.smallest_unit != TemporalUnit::Week { + // a. Let startUnit be LargerOfTwoTemporalUnits(smallestUnit, "day"). + let start_unit = options.smallest_unit.max(TemporalUnit::Day); + // b. Set duration to ? BubbleRelativeDuration(sign, duration, nudgeResult.[[NudgedEpochNs]], dateTime, calendarRec, timeZoneRec, largestUnit, startUnit). + duration = duration.bubble_relative_duration( + sign, + nudge_result.nudge_epoch_ns, + dt, + tz, + options.largest_unit, + start_unit, + )? + }; + + // 10. If IsCalendarUnit(largestUnit) is true or largestUnit is "day", then + let largest_unit = if options.largest_unit.is_calendar_unit() + || options.largest_unit == TemporalUnit::Day + { + // a. Set largestUnit to "hour". + TemporalUnit::Hour + } else { + options.largest_unit + }; + + // 11. Let balanceResult be ? BalanceTimeDuration(duration.[[NormalizedTime]], largestUnit). + let balance_result = + TimeDuration::from_normalized(duration.normalized_time_duration(), largest_unit)?; + + // TODO: Need to validate the below. + // 12. Return the Record { [[Duration]]: CreateDurationRecord(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], balanceResult.[[Hours]], balanceResult.[[Minutes]], balanceResult.[[Seconds]], balanceResult.[[Milliseconds]], balanceResult.[[Microseconds]], balanceResult.[[Nanoseconds]]), [[Total]]: nudgeResult.[[Total]] }. + Ok(( + Duration::new_unchecked(duration.date(), balance_result.1), + nudge_result.total, + )) } } diff --git a/src/components/duration/tests.rs b/src/components/duration/tests.rs index ad1e1b1d..ebcae0a5 100644 --- a/src/components/duration/tests.rs +++ b/src/components/duration/tests.rs @@ -1,18 +1,17 @@ use crate::{ components::{calendar::Calendar, Date}, - options::ArithmeticOverflow, + options::{ArithmeticOverflow, RoundingIncrement, TemporalRoundingMode}, }; use super::*; fn get_round_result( test_duration: &Duration, - relative_to: &RelativeTo<'_>, - unit: TemporalUnit, - mode: TemporalRoundingMode, + relative_to: &RelativeTo, + options: RoundingOptions, ) -> Vec { test_duration - .round(None, Some(unit), None, Some(mode), relative_to) + .round(options, relative_to) .unwrap() .fields() .iter() @@ -22,7 +21,7 @@ fn get_round_result( // roundingmode-floor.js #[test] -fn basic_positive_floor_rounding() { +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 forward_date = Date::new( @@ -39,89 +38,56 @@ fn basic_positive_floor_rounding() { zdt: None, }; - let result = get_round_result( - &test_duration, - &relative_forward, - TemporalUnit::Year, - TemporalRoundingMode::Floor, - ); + let mut options = RoundingOptions { + largest_unit: None, + smallest_unit: None, + increment: None, + rounding_mode: Some(TemporalRoundingMode::Floor), + }; + + let _ = options.smallest_unit.insert(TemporalUnit::Year); + let result = get_round_result(&test_duration, &relative_forward, options); assert_eq!(&result, &[5, 0, 0, 0, 0, 0, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration, - &relative_forward, - TemporalUnit::Month, - TemporalRoundingMode::Floor, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Month); + let result = get_round_result(&test_duration, &relative_forward, options); assert_eq!(&result, &[5, 7, 0, 0, 0, 0, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration, - &relative_forward, - TemporalUnit::Week, - TemporalRoundingMode::Floor, - ); - assert_eq!(&result, &[5, 6, 8, 0, 0, 0, 0, 0, 0, 0],); + let _ = options.smallest_unit.insert(TemporalUnit::Week); + let result = get_round_result(&test_duration, &relative_forward, options); + assert_eq!(&result, &[5, 7, 3, 0, 0, 0, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration, - &relative_forward, - TemporalUnit::Day, - TemporalRoundingMode::Floor, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Day); + let result = get_round_result(&test_duration, &relative_forward, options); assert_eq!(&result, &[5, 7, 0, 27, 0, 0, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration, - &relative_forward, - TemporalUnit::Hour, - TemporalRoundingMode::Floor, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Hour); + let result = get_round_result(&test_duration, &relative_forward, options); assert_eq!(&result, &[5, 7, 0, 27, 16, 0, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration, - &relative_forward, - TemporalUnit::Minute, - TemporalRoundingMode::Floor, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Minute); + let result = get_round_result(&test_duration, &relative_forward, options); assert_eq!(&result, &[5, 7, 0, 27, 16, 30, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration, - &relative_forward, - TemporalUnit::Second, - TemporalRoundingMode::Floor, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Second); + let result = get_round_result(&test_duration, &relative_forward, options); assert_eq!(&result, &[5, 7, 0, 27, 16, 30, 20, 0, 0, 0],); - let result = get_round_result( - &test_duration, - &relative_forward, - TemporalUnit::Millisecond, - TemporalRoundingMode::Floor, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Millisecond); + let result = get_round_result(&test_duration, &relative_forward, options); assert_eq!(&result, &[5, 7, 0, 27, 16, 30, 20, 123, 0, 0],); - let result = get_round_result( - &test_duration, - &relative_forward, - TemporalUnit::Microsecond, - TemporalRoundingMode::Floor, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Microsecond); + let result = get_round_result(&test_duration, &relative_forward, options); assert_eq!(&result, &[5, 7, 0, 27, 16, 30, 20, 123, 987, 0],); - let result = get_round_result( - &test_duration, - &relative_forward, - TemporalUnit::Nanosecond, - TemporalRoundingMode::Floor, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Nanosecond); + let result = get_round_result(&test_duration, &relative_forward, options); assert_eq!(&result, &[5, 7, 0, 27, 16, 30, 20, 123, 987, 500],); } #[test] -fn basic_negative_floor_rounding() { +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(); @@ -139,84 +105,51 @@ fn basic_negative_floor_rounding() { zdt: None, }; - let result = get_round_result( - &test_duration.negated(), - &relative_backward, - TemporalUnit::Year, - TemporalRoundingMode::Floor, - ); + let mut options = RoundingOptions { + largest_unit: None, + smallest_unit: None, + increment: None, + rounding_mode: Some(TemporalRoundingMode::Floor), + }; + + let _ = options.smallest_unit.insert(TemporalUnit::Year); + let result = get_round_result(&test_duration.negated(), &relative_backward, options); assert_eq!(&result, &[-6, 0, 0, 0, 0, 0, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration.negated(), - &relative_backward, - TemporalUnit::Month, - TemporalRoundingMode::Floor, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Month); + let result = get_round_result(&test_duration.negated(), &relative_backward, options); assert_eq!(&result, &[-5, -8, 0, 0, 0, 0, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration.negated(), - &relative_backward, - TemporalUnit::Week, - TemporalRoundingMode::Floor, - ); - assert_eq!(&result, &[-5, -6, -9, 0, 0, 0, 0, 0, 0, 0],); + let _ = options.smallest_unit.insert(TemporalUnit::Week); + let result = get_round_result(&test_duration.negated(), &relative_backward, options); + assert_eq!(&result, &[-5, -7, -4, 0, 0, 0, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration.negated(), - &relative_backward, - TemporalUnit::Day, - TemporalRoundingMode::Floor, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Day); + let result = get_round_result(&test_duration.negated(), &relative_backward, options); assert_eq!(&result, &[-5, -7, 0, -28, 0, 0, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration.negated(), - &relative_backward, - TemporalUnit::Hour, - TemporalRoundingMode::Floor, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Hour); + let result = get_round_result(&test_duration.negated(), &relative_backward, options); assert_eq!(&result, &[-5, -7, 0, -27, -17, 0, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration.negated(), - &relative_backward, - TemporalUnit::Minute, - TemporalRoundingMode::Floor, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Minute); + let result = get_round_result(&test_duration.negated(), &relative_backward, options); assert_eq!(&result, &[-5, -7, 0, -27, -16, -31, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration.negated(), - &relative_backward, - TemporalUnit::Second, - TemporalRoundingMode::Floor, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Second); + let result = get_round_result(&test_duration.negated(), &relative_backward, options); assert_eq!(&result, &[-5, -7, 0, -27, -16, -30, -21, 0, 0, 0],); - let result = get_round_result( - &test_duration.negated(), - &relative_backward, - TemporalUnit::Millisecond, - TemporalRoundingMode::Floor, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Millisecond); + let result = get_round_result(&test_duration.negated(), &relative_backward, options); assert_eq!(&result, &[-5, -7, 0, -27, -16, -30, -20, -124, 0, 0],); - let result = get_round_result( - &test_duration.negated(), - &relative_backward, - TemporalUnit::Microsecond, - TemporalRoundingMode::Floor, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Microsecond); + let result = get_round_result(&test_duration.negated(), &relative_backward, options); assert_eq!(&result, &[-5, -7, 0, -27, -16, -30, -20, -123, -988, 0],); - let result = get_round_result( - &test_duration.negated(), - &relative_backward, - TemporalUnit::Nanosecond, - TemporalRoundingMode::Floor, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Nanosecond); + let result = get_round_result(&test_duration.negated(), &relative_backward, options); assert_eq!(&result, &[-5, -7, 0, -27, -16, -30, -20, -123, -987, -500],); } @@ -239,84 +172,51 @@ fn basic_positive_ceil_rounding() { zdt: None, }; - let result = get_round_result( - &test_duration, - &relative_forward, - TemporalUnit::Year, - TemporalRoundingMode::Ceil, - ); + let mut options = RoundingOptions { + largest_unit: None, + smallest_unit: None, + increment: None, + rounding_mode: Some(TemporalRoundingMode::Ceil), + }; + + let _ = options.smallest_unit.insert(TemporalUnit::Year); + let result = get_round_result(&test_duration, &relative_forward, options); assert_eq!(&result, &[6, 0, 0, 0, 0, 0, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration, - &relative_forward, - TemporalUnit::Month, - TemporalRoundingMode::Ceil, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Month); + let result = get_round_result(&test_duration, &relative_forward, options); assert_eq!(&result, &[5, 8, 0, 0, 0, 0, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration, - &relative_forward, - TemporalUnit::Week, - TemporalRoundingMode::Ceil, - ); - assert_eq!(&result, &[5, 6, 9, 0, 0, 0, 0, 0, 0, 0],); + let _ = options.smallest_unit.insert(TemporalUnit::Week); + let result = get_round_result(&test_duration, &relative_forward, options); + assert_eq!(&result, &[5, 7, 4, 0, 0, 0, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration, - &relative_forward, - TemporalUnit::Day, - TemporalRoundingMode::Ceil, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Day); + let result = get_round_result(&test_duration, &relative_forward, options); assert_eq!(&result, &[5, 7, 0, 28, 0, 0, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration, - &relative_forward, - TemporalUnit::Hour, - TemporalRoundingMode::Ceil, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Hour); + let result = get_round_result(&test_duration, &relative_forward, options); assert_eq!(&result, &[5, 7, 0, 27, 17, 0, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration, - &relative_forward, - TemporalUnit::Minute, - TemporalRoundingMode::Ceil, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Minute); + let result = get_round_result(&test_duration, &relative_forward, options); assert_eq!(&result, &[5, 7, 0, 27, 16, 31, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration, - &relative_forward, - TemporalUnit::Second, - TemporalRoundingMode::Ceil, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Second); + let result = get_round_result(&test_duration, &relative_forward, options); assert_eq!(&result, &[5, 7, 0, 27, 16, 30, 21, 0, 0, 0],); - let result = get_round_result( - &test_duration, - &relative_forward, - TemporalUnit::Millisecond, - TemporalRoundingMode::Ceil, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Millisecond); + let result = get_round_result(&test_duration, &relative_forward, options); assert_eq!(&result, &[5, 7, 0, 27, 16, 30, 20, 124, 0, 0],); - let result = get_round_result( - &test_duration, - &relative_forward, - TemporalUnit::Microsecond, - TemporalRoundingMode::Ceil, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Microsecond); + let result = get_round_result(&test_duration, &relative_forward, options); assert_eq!(&result, &[5, 7, 0, 27, 16, 30, 20, 123, 988, 0],); - let result = get_round_result( - &test_duration, - &relative_forward, - TemporalUnit::Nanosecond, - TemporalRoundingMode::Ceil, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Nanosecond); + let result = get_round_result(&test_duration, &relative_forward, options); assert_eq!(&result, &[5, 7, 0, 27, 16, 30, 20, 123, 987, 500],); } @@ -337,84 +237,51 @@ fn basic_negative_ceil_rounding() { zdt: None, }; - let result = get_round_result( - &test_duration.negated(), - &relative_backward, - TemporalUnit::Year, - TemporalRoundingMode::Ceil, - ); + let mut options = RoundingOptions { + largest_unit: None, + smallest_unit: None, + increment: None, + rounding_mode: Some(TemporalRoundingMode::Ceil), + }; + + let _ = options.smallest_unit.insert(TemporalUnit::Year); + let result = get_round_result(&test_duration.negated(), &relative_backward, options); assert_eq!(&result, &[-5, 0, 0, 0, 0, 0, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration.negated(), - &relative_backward, - TemporalUnit::Month, - TemporalRoundingMode::Ceil, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Month); + let result = get_round_result(&test_duration.negated(), &relative_backward, options); assert_eq!(&result, &[-5, -7, 0, 0, 0, 0, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration.negated(), - &relative_backward, - TemporalUnit::Week, - TemporalRoundingMode::Ceil, - ); - assert_eq!(&result, &[-5, -6, -8, 0, 0, 0, 0, 0, 0, 0],); + let _ = options.smallest_unit.insert(TemporalUnit::Week); + let result = get_round_result(&test_duration.negated(), &relative_backward, options); + assert_eq!(&result, &[-5, -7, -3, 0, 0, 0, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration.negated(), - &relative_backward, - TemporalUnit::Day, - TemporalRoundingMode::Ceil, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Day); + let result = get_round_result(&test_duration.negated(), &relative_backward, options); assert_eq!(&result, &[-5, -7, 0, -27, 0, 0, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration.negated(), - &relative_backward, - TemporalUnit::Hour, - TemporalRoundingMode::Ceil, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Hour); + let result = get_round_result(&test_duration.negated(), &relative_backward, options); assert_eq!(&result, &[-5, -7, 0, -27, -16, 0, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration.negated(), - &relative_backward, - TemporalUnit::Minute, - TemporalRoundingMode::Ceil, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Minute); + let result = get_round_result(&test_duration.negated(), &relative_backward, options); assert_eq!(&result, &[-5, -7, 0, -27, -16, -30, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration.negated(), - &relative_backward, - TemporalUnit::Second, - TemporalRoundingMode::Ceil, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Second); + let result = get_round_result(&test_duration.negated(), &relative_backward, options); assert_eq!(&result, &[-5, -7, 0, -27, -16, -30, -20, 0, 0, 0],); - let result = get_round_result( - &test_duration.negated(), - &relative_backward, - TemporalUnit::Millisecond, - TemporalRoundingMode::Ceil, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Millisecond); + let result = get_round_result(&test_duration.negated(), &relative_backward, options); assert_eq!(&result, &[-5, -7, 0, -27, -16, -30, -20, -123, 0, 0],); - let result = get_round_result( - &test_duration.negated(), - &relative_backward, - TemporalUnit::Microsecond, - TemporalRoundingMode::Ceil, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Microsecond); + let result = get_round_result(&test_duration.negated(), &relative_backward, options); assert_eq!(&result, &[-5, -7, 0, -27, -16, -30, -20, -123, -987, 0],); - let result = get_round_result( - &test_duration.negated(), - &relative_backward, - TemporalUnit::Nanosecond, - TemporalRoundingMode::Ceil, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Nanosecond); + let result = get_round_result(&test_duration.negated(), &relative_backward, options); assert_eq!(&result, &[-5, -7, 0, -27, -16, -30, -20, -123, -987, -500],); } @@ -437,84 +304,51 @@ fn basic_positive_expand_rounding() { zdt: None, }; - let result = get_round_result( - &test_duration, - &relative_forward, - TemporalUnit::Year, - TemporalRoundingMode::Ceil, - ); + let mut options = RoundingOptions { + largest_unit: None, + smallest_unit: None, + increment: None, + rounding_mode: Some(TemporalRoundingMode::Expand), + }; + + let _ = options.smallest_unit.insert(TemporalUnit::Year); + let result = get_round_result(&test_duration, &relative_forward, options); assert_eq!(&result, &[6, 0, 0, 0, 0, 0, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration, - &relative_forward, - TemporalUnit::Month, - TemporalRoundingMode::Expand, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Month); + let result = get_round_result(&test_duration, &relative_forward, options); assert_eq!(&result, &[5, 8, 0, 0, 0, 0, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration, - &relative_forward, - TemporalUnit::Week, - TemporalRoundingMode::Expand, - ); - assert_eq!(&result, &[5, 6, 9, 0, 0, 0, 0, 0, 0, 0],); + let _ = options.smallest_unit.insert(TemporalUnit::Week); + let result = get_round_result(&test_duration, &relative_forward, options); + assert_eq!(&result, &[5, 7, 4, 0, 0, 0, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration, - &relative_forward, - TemporalUnit::Day, - TemporalRoundingMode::Expand, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Day); + let result = get_round_result(&test_duration, &relative_forward, options); assert_eq!(&result, &[5, 7, 0, 28, 0, 0, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration, - &relative_forward, - TemporalUnit::Hour, - TemporalRoundingMode::Expand, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Hour); + let result = get_round_result(&test_duration, &relative_forward, options); assert_eq!(&result, &[5, 7, 0, 27, 17, 0, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration, - &relative_forward, - TemporalUnit::Minute, - TemporalRoundingMode::Expand, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Minute); + let result = get_round_result(&test_duration, &relative_forward, options); assert_eq!(&result, &[5, 7, 0, 27, 16, 31, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration, - &relative_forward, - TemporalUnit::Second, - TemporalRoundingMode::Expand, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Second); + let result = get_round_result(&test_duration, &relative_forward, options); assert_eq!(&result, &[5, 7, 0, 27, 16, 30, 21, 0, 0, 0],); - let result = get_round_result( - &test_duration, - &relative_forward, - TemporalUnit::Millisecond, - TemporalRoundingMode::Expand, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Millisecond); + let result = get_round_result(&test_duration, &relative_forward, options); assert_eq!(&result, &[5, 7, 0, 27, 16, 30, 20, 124, 0, 0],); - let result = get_round_result( - &test_duration, - &relative_forward, - TemporalUnit::Microsecond, - TemporalRoundingMode::Expand, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Microsecond); + let result = get_round_result(&test_duration, &relative_forward, options); assert_eq!(&result, &[5, 7, 0, 27, 16, 30, 20, 123, 988, 0],); - let result = get_round_result( - &test_duration, - &relative_forward, - TemporalUnit::Nanosecond, - TemporalRoundingMode::Expand, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Nanosecond); + let result = get_round_result(&test_duration, &relative_forward, options); assert_eq!(&result, &[5, 7, 0, 27, 16, 30, 20, 123, 987, 500],); } @@ -537,84 +371,51 @@ fn basic_negative_expand_rounding() { zdt: None, }; - let result = get_round_result( - &test_duration.negated(), - &relative_backward, - TemporalUnit::Year, - TemporalRoundingMode::Expand, - ); + let mut options = RoundingOptions { + largest_unit: None, + smallest_unit: None, + increment: None, + rounding_mode: Some(TemporalRoundingMode::Expand), + }; + + let _ = options.smallest_unit.insert(TemporalUnit::Year); + let result = get_round_result(&test_duration.negated(), &relative_backward, options); assert_eq!(&result, &[-6, 0, 0, 0, 0, 0, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration.negated(), - &relative_backward, - TemporalUnit::Month, - TemporalRoundingMode::Expand, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Month); + let result = get_round_result(&test_duration.negated(), &relative_backward, options); assert_eq!(&result, &[-5, -8, 0, 0, 0, 0, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration.negated(), - &relative_backward, - TemporalUnit::Week, - TemporalRoundingMode::Expand, - ); - assert_eq!(&result, &[-5, -6, -9, 0, 0, 0, 0, 0, 0, 0],); + let _ = options.smallest_unit.insert(TemporalUnit::Week); + let result = get_round_result(&test_duration.negated(), &relative_backward, options); + assert_eq!(&result, &[-5, -7, -4, 0, 0, 0, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration.negated(), - &relative_backward, - TemporalUnit::Day, - TemporalRoundingMode::Expand, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Day); + let result = get_round_result(&test_duration.negated(), &relative_backward, options); assert_eq!(&result, &[-5, -7, 0, -28, 0, 0, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration.negated(), - &relative_backward, - TemporalUnit::Hour, - TemporalRoundingMode::Expand, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Hour); + let result = get_round_result(&test_duration.negated(), &relative_backward, options); assert_eq!(&result, &[-5, -7, 0, -27, -17, 0, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration.negated(), - &relative_backward, - TemporalUnit::Minute, - TemporalRoundingMode::Expand, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Minute); + let result = get_round_result(&test_duration.negated(), &relative_backward, options); assert_eq!(&result, &[-5, -7, 0, -27, -16, -31, 0, 0, 0, 0],); - let result = get_round_result( - &test_duration.negated(), - &relative_backward, - TemporalUnit::Second, - TemporalRoundingMode::Expand, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Second); + let result = get_round_result(&test_duration.negated(), &relative_backward, options); assert_eq!(&result, &[-5, -7, 0, -27, -16, -30, -21, 0, 0, 0],); - let result = get_round_result( - &test_duration.negated(), - &relative_backward, - TemporalUnit::Millisecond, - TemporalRoundingMode::Expand, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Millisecond); + let result = get_round_result(&test_duration.negated(), &relative_backward, options); assert_eq!(&result, &[-5, -7, 0, -27, -16, -30, -20, -124, 0, 0],); - let result = get_round_result( - &test_duration.negated(), - &relative_backward, - TemporalUnit::Microsecond, - TemporalRoundingMode::Expand, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Microsecond); + let result = get_round_result(&test_duration.negated(), &relative_backward, options); assert_eq!(&result, &[-5, -7, 0, -27, -16, -30, -20, -123, -988, 0],); - let result = get_round_result( - &test_duration.negated(), - &relative_backward, - TemporalUnit::Nanosecond, - TemporalRoundingMode::Expand, - ); + let _ = options.smallest_unit.insert(TemporalUnit::Nanosecond); + let result = get_round_result(&test_duration.negated(), &relative_backward, options); assert_eq!(&result, &[-5, -7, 0, -27, -16, -30, -20, -123, -987, -500],); } @@ -636,31 +437,29 @@ fn rounding_increment_non_integer() { zdt: None, }; + let mut options = RoundingOptions { + largest_unit: None, + smallest_unit: Some(TemporalUnit::Day), + increment: None, + rounding_mode: Some(TemporalRoundingMode::Expand), + }; + + let _ = options + .increment + .insert(RoundingIncrement::try_from(2.5).unwrap()); + let result = test_duration.round(options, &relative_to).unwrap(); + assert_eq!( - test_duration - .round( - Some(RoundingIncrement::try_from(2.5).unwrap()), - Some(TemporalUnit::Day), - None, - Some(TemporalRoundingMode::Expand), - &relative_to, - ) - .unwrap() - .fields(), + result.fields(), &[0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] ); + let _ = options + .increment + .insert(RoundingIncrement::try_from(1e9 + 0.5).unwrap()); + let result = test_duration.round(options, &relative_to).unwrap(); assert_eq!( - test_duration - .round( - Some(RoundingIncrement::try_from(1e9 + 0.5).unwrap()), - Some(TemporalUnit::Day), - None, - Some(TemporalRoundingMode::Expand), - &relative_to, - ) - .unwrap() - .fields(), + result.fields(), &[0.0, 0.0, 0.0, 1e9, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] ); } diff --git a/src/components/duration/time.rs b/src/components/duration/time.rs index 40cbf2c4..09ed43d8 100644 --- a/src/components/duration/time.rs +++ b/src/components/duration/time.rs @@ -1,19 +1,20 @@ //! An implementation of `TimeDuration` and it's methods. -use std::num::NonZeroU64; +use std::num::NonZeroU128; use crate::{ - options::{RoundingIncrement, TemporalRoundingMode, TemporalUnit}, + options::{ResolvedRoundingOptions, TemporalUnit}, + rounding::{IncrementRounder, Round}, TemporalError, TemporalResult, TemporalUnwrap, }; -use super::{is_valid_duration, normalized::NormalizedTimeDuration}; - -use num_traits::{Euclid, MulAdd}; +use super::{ + is_valid_duration, + normalized::{NormalizedDurationRecord, NormalizedTimeDuration}, + DateDuration, +}; -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; +use num_traits::{Euclid, FromPrimitive, MulAdd}; /// `TimeDuration` represents the [Time Duration record][spec] of the `Duration.` /// @@ -80,7 +81,7 @@ impl TimeDuration { let mut microseconds = 0; // 2. Let sign be NormalizedTimeDurationSign(norm). - let sign = norm.sign(); + let sign = i32::from(norm.sign() as i8); // 3. Let nanoseconds be NormalizedTimeDurationAbs(norm).[[TotalNanoseconds]]. let mut nanoseconds = norm.0.abs(); @@ -365,105 +366,63 @@ impl TimeDuration { // ==== TimeDuration method impls ==== impl TimeDuration { - // TODO: Update round to accomodate `Normalization`. - /// Rounds the current `TimeDuration` given a rounding increment, unit and rounding mode. `round` will return a tuple of the rounded `TimeDuration` and - /// the `total` value of the smallest unit prior to rounding. - #[inline] - pub fn round( - &self, - increment: RoundingIncrement, - unit: TemporalUnit, - mode: TemporalRoundingMode, - ) -> TemporalResult<(NormalizedTimeDuration, i64)> { - let norm = match unit { - TemporalUnit::Year - | TemporalUnit::Month - | TemporalUnit::Week - | TemporalUnit::Day - | TemporalUnit::Auto => { - return Err(TemporalError::r#type() - .with_message("Invalid unit provided to for TimeDuration to round.")) + // TODO: Maybe move to `NormalizedTimeDuration` + pub(crate) fn round( + days: f64, + norm: &NormalizedTimeDuration, + options: ResolvedRoundingOptions, + ) -> TemporalResult<(NormalizedDurationRecord, Option)> { + // 1. Assert: IsCalendarUnit(unit) is false. + let (days, norm, total) = match options.smallest_unit { + // 2. If unit is "day", then + TemporalUnit::Day => { + // a. Let fractionalDays be days + DivideNormalizedTimeDuration(norm, nsPerDay). + let fractional_days = days + norm.as_fractional_days(); + // b. Set days to RoundNumberToIncrement(fractionalDays, increment, roundingMode). + let days = IncrementRounder::from_potentially_negative_parts( + fractional_days, + 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."), + )?, + NormalizedTimeDuration::default(), + i128::from_f64(fractional_days), + ) + } + // 3. Else, + TemporalUnit::Hour + | TemporalUnit::Minute + | TemporalUnit::Second + | TemporalUnit::Millisecond + | TemporalUnit::Microsecond + | TemporalUnit::Nanosecond => { + // a. Assert: The value in the "Category" column of the row of Table 22 whose "Singular" column contains unit, is time. + // b. Let divisor be the value in the "Length in Nanoseconds" column of the row of Table 22 whose "Singular" column contains unit. + let divisor = options.smallest_unit.as_nanoseconds().temporal_unwrap()?; + // c. Let total be DivideNormalizedTimeDuration(norm, divisor). + let total = norm.divide(divisor as i64); + let non_zero_divisor = unsafe { NonZeroU128::new_unchecked(divisor.into()) }; + // d. Set norm to ? RoundNormalizedTimeDurationToIncrement(norm, divisor × increment, roundingMode). + let norm = norm.round( + non_zero_divisor + .checked_mul(options.increment.as_extended_increment()) + .temporal_unwrap()?, + options.rounding_mode, + )?; + (days, norm, Some(total)) } - _ => self.to_normalized(), + _ => return Err(TemporalError::assert()), }; - match unit { - // 12. Else if unit is "hour", then - TemporalUnit::Hour => { - // 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 increment_mul_divisor = increment - .as_extended_increment() - .checked_mul(unsafe { NonZeroU64::new_unchecked(NANOSECONDS_PER_HOUR) }) - .temporal_unwrap()?; - let norm = norm.round(increment_mul_divisor, mode)?; - Ok((norm, total as i64)) - } - // 13. Else if unit is "minute", then - TemporalUnit::Minute => { - // a. Let divisor be 6 × 10**10. - // b. Set total to DivideNormalizedTimeDuration(norm, divisor). - let total = norm.divide(NANOSECONDS_PER_MINUTE as i64); - // c. Set norm to ? RoundNormalizedTimeDurationToIncrement(norm, divisor × increment, roundingMode). - let increment_mul_divisor = increment - .as_extended_increment() - .checked_mul(unsafe { NonZeroU64::new_unchecked(NANOSECONDS_PER_MINUTE) }) - .temporal_unwrap()?; - let norm = norm.round(increment_mul_divisor, mode)?; - Ok((norm, total as i64)) - } - // 14. Else if unit is "second", then - TemporalUnit::Second => { - // a. Let divisor be 10**9. - // b. Set total to DivideNormalizedTimeDuration(norm, divisor). - let total = norm.divide(NANOSECONDS_PER_SECOND as i64); - // c. Set norm to ? RoundNormalizedTimeDurationToIncrement(norm, divisor × increment, roundingMode). - let increment_mul_divisor = increment - .as_extended_increment() - .checked_mul(unsafe { NonZeroU64::new_unchecked(NANOSECONDS_PER_SECOND) }) - .temporal_unwrap()?; - let norm = norm.round(increment_mul_divisor, mode)?; - Ok((norm, total as i64)) - } - // 15. Else if unit is "millisecond", then - TemporalUnit::Millisecond => { - // a. Let divisor be 10**6. - // b. Set total to DivideNormalizedTimeDuration(norm, divisor). - let total = norm.divide(1_000_000); - // c. Set norm to ? RoundNormalizedTimeDurationToIncrement(norm, divisor × increment, roundingMode). - let increment_mul_divisor = increment - .as_extended_increment() - .checked_mul(unsafe { NonZeroU64::new_unchecked(1_000_000) }) - .temporal_unwrap()?; - let norm = norm.round(increment_mul_divisor, mode)?; - Ok((norm, total as i64)) - } - // 16. Else if unit is "microsecond", then - TemporalUnit::Microsecond => { - // a. Let divisor be 10**3. - // b. Set total to DivideNormalizedTimeDuration(norm, divisor). - let total = norm.divide(1_000); - // c. Set norm to ? RoundNormalizedTimeDurationToIncrement(norm, divisor × increment, roundingMode). - let increment_mul_divisor = increment - .as_extended_increment() - .checked_mul(unsafe { NonZeroU64::new_unchecked(1_000) }) - .temporal_unwrap()?; - let norm = norm.round(increment_mul_divisor, mode)?; - Ok((norm, total as i64)) - } - // 17. Else, - TemporalUnit::Nanosecond => { - // a. Assert: unit is "nanosecond". - // 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.as_extended_increment(), mode)?; - Ok((norm, total)) - } - _ => unreachable!("All other units early return error."), - } + // 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)?, + total, + )) } } diff --git a/src/components/instant.rs b/src/components/instant.rs index 842545b1..267c5b9a 100644 --- a/src/components/instant.rs +++ b/src/components/instant.rs @@ -1,10 +1,13 @@ //! An implementation of the Temporal Instant. -use std::num::NonZeroU64; +use std::num::NonZeroU128; use crate::{ components::{duration::TimeDuration, Duration}, - options::{RoundingIncrement, TemporalRoundingMode, TemporalUnit}, + options::{ + DifferenceOperation, DifferenceSettings, ResolvedRoundingOptions, RoundingIncrement, + TemporalRoundingMode, TemporalUnit, + }, rounding::{IncrementRounder, Round}, TemporalError, TemporalResult, TemporalUnwrap, MS_PER_DAY, NS_PER_DAY, }; @@ -50,12 +53,9 @@ impl Instant { #[allow(unused)] pub(crate) fn diff_instant( &self, - op: bool, + op: DifferenceOperation, other: &Self, - rounding_mode: Option, - rounding_increment: Option, - largest_unit: Option, - smallest_unit: Option, + options: DifferenceSettings, ) -> TemporalResult { // diff the instant and determine its component values. let diff = self.to_f64() - other.to_f64(); @@ -65,36 +65,33 @@ impl Instant { let secs = (diff / NANOSECONDS_PER_SECOND).trunc(); // Handle the settings provided to `diff_instant` - let increment = rounding_increment.unwrap_or_default(); - let rounding_mode = if op { - rounding_mode - .unwrap_or(TemporalRoundingMode::Trunc) - .negate() - } else { - rounding_mode.unwrap_or(TemporalRoundingMode::Trunc) - }; - let smallest_unit = smallest_unit.unwrap_or(TemporalUnit::Nanosecond); - // Use the defaultlargestunit which is max smallestlargestdefault and smallestunit - let largest_unit = largest_unit.unwrap_or(smallest_unit.max(TemporalUnit::Second)); + let (sign, resolved) = ResolvedRoundingOptions::from_diff_settings( + options, + op, + TemporalUnit::Second, + TemporalUnit::Nanosecond, + )?; // TODO: validate roundingincrement // Steps 11-13 of 13.47 GetDifferenceSettings - if smallest_unit == TemporalUnit::Nanosecond { + if resolved.smallest_unit == TemporalUnit::Nanosecond { let (_, result) = TimeDuration::from_normalized( TimeDuration::new_unchecked(0f64, 0f64, secs, millis, micros, nanos) .to_normalized(), - largest_unit, + resolved.largest_unit, )?; return Ok(result); } - let (round_result, _) = TimeDuration::new(0f64, 0f64, secs, millis, micros, nanos)?.round( - increment, - smallest_unit, - rounding_mode, + let normalized_time_duration = + TimeDuration::new(0f64, 0f64, secs, millis, micros, nanos)?.to_normalized(); + + let (round_result, _) = TimeDuration::round(0.0, &normalized_time_duration, resolved)?; + let (_, result) = TimeDuration::from_normalized( + round_result.normalized_time_duration(), + resolved.largest_unit, )?; - let (_, result) = TimeDuration::from_normalized(round_result, largest_unit)?; Ok(result) } @@ -108,16 +105,16 @@ impl Instant { let increment = increment.as_extended_increment(); let increment = match unit { TemporalUnit::Hour => increment - .checked_mul(NonZeroU64::new(NANOSECONDS_PER_HOUR as u64).temporal_unwrap()?), + .checked_mul(NonZeroU128::new(NANOSECONDS_PER_HOUR as u128).temporal_unwrap()?), TemporalUnit::Minute => increment - .checked_mul(NonZeroU64::new(NANOSECONDS_PER_MINUTE as u64).temporal_unwrap()?), + .checked_mul(NonZeroU128::new(NANOSECONDS_PER_MINUTE as u128).temporal_unwrap()?), TemporalUnit::Second => increment - .checked_mul(NonZeroU64::new(NANOSECONDS_PER_SECOND as u64).temporal_unwrap()?), + .checked_mul(NonZeroU128::new(NANOSECONDS_PER_SECOND as u128).temporal_unwrap()?), TemporalUnit::Millisecond => { - increment.checked_mul(NonZeroU64::new(1_000_000).temporal_unwrap()?) + increment.checked_mul(NonZeroU128::new(1_000_000).temporal_unwrap()?) } TemporalUnit::Microsecond => { - increment.checked_mul(NonZeroU64::new(1_000).temporal_unwrap()?) + increment.checked_mul(NonZeroU128::new(1_000).temporal_unwrap()?) } TemporalUnit::Nanosecond => Some(increment), _ => { @@ -202,19 +199,9 @@ impl Instant { pub fn since( &self, other: &Self, - rounding_mode: Option, - rounding_increment: Option, - largest_unit: Option, - smallest_unit: Option, + settings: DifferenceSettings, ) -> TemporalResult { - self.diff_instant( - true, - other, - rounding_mode, - rounding_increment, - largest_unit, - smallest_unit, - ) + self.diff_instant(DifferenceOperation::Since, other, settings) } /// Returns a `TimeDuration` representing the duration until provided `Instant` @@ -222,19 +209,9 @@ impl Instant { pub fn until( &self, other: &Self, - rounding_mode: Option, - rounding_increment: Option, - largest_unit: Option, - smallest_unit: Option, + settings: DifferenceSettings, ) -> TemporalResult { - self.diff_instant( - false, - other, - rounding_mode, - rounding_increment, - largest_unit, - smallest_unit, - ) + self.diff_instant(DifferenceOperation::Until, other, settings) } /// Returns an `Instant` by rounding the current `Instant` according to the provided settings. diff --git a/src/components/time.rs b/src/components/time.rs index b588bf10..e4be9104 100644 --- a/src/components/time.rs +++ b/src/components/time.rs @@ -3,10 +3,15 @@ use crate::{ components::{duration::TimeDuration, Duration}, iso::IsoTime, - options::{ArithmeticOverflow, RoundingIncrement, TemporalRoundingMode, TemporalUnit}, + options::{ + ArithmeticOverflow, DifferenceOperation, DifferenceSettings, ResolvedRoundingOptions, + RoundingIncrement, TemporalRoundingMode, TemporalUnit, + }, TemporalError, TemporalResult, }; +use super::duration::normalized::NormalizedTimeDuration; + /// The native Rust implementation of `Temporal.PlainTime`. #[non_exhaustive] #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] @@ -30,9 +35,28 @@ impl Time { self.iso.is_valid() } + /// Specification equivalent to `AddTime` + pub(crate) fn add_normalized_time_duration(&self, norm: NormalizedTimeDuration) -> (i32, Self) { + // 1. Set second to second + NormalizedTimeDurationSeconds(norm). + let second = i64::from(self.second()) + norm.seconds(); + // 2. Set nanosecond to nanosecond + NormalizedTimeDurationSubseconds(norm). + let nanosecond = i32::from(self.nanosecond()) + norm.subseconds(); + // 3. Return BalanceTime(hour, minute, second, millisecond, microsecond, nanosecond). + let (day, balance_result) = IsoTime::balance( + f64::from(self.hour()), + f64::from(self.minute()), + second as f64, + f64::from(self.millisecond()), + f64::from(self.microsecond()), + f64::from(nanosecond), + ); + + (day, Self::new_unchecked(balance_result)) + } + /// Adds a `TimeDuration` to the current `Time`. /// - /// Spec Equivalent: `AddDurationToOrSubtractDurationFromPlainTime` AND `AddTime`. + /// Spec Equivalent: `AddDurationToOrSubtractDurationFromPlainTime`. pub(crate) fn add_to_time(&self, duration: &TimeDuration) -> Self { let (_, result) = IsoTime::balance( f64::from(self.hour()) + duration.hours, @@ -48,56 +72,45 @@ impl Time { Self::new_unchecked(result) } + // TODO: Migrate to /// Performs a desired difference op between two `Time`'s, returning the resulting `Duration`. pub(crate) fn diff_time( &self, - op: bool, + op: DifferenceOperation, other: &Time, - rounding_mode: Option, - rounding_increment: Option, - largest_unit: Option, - smallest_unit: Option, + settings: DifferenceSettings, ) -> TemporalResult { // 1. If operation is SINCE, let sign be -1. Otherwise, let sign be 1. // 2. Set other to ? ToTemporalTime(other). // 3. Let resolvedOptions be ? SnapshotOwnProperties(? GetOptionsObject(options), null). // 4. Let settings be ? GetDifferenceSettings(operation, resolvedOptions, TIME, « », "nanosecond", "hour"). - let rounding_increment = rounding_increment.unwrap_or_default(); - let (sign, rounding_mode) = if op { - ( - -1.0, - rounding_mode - .unwrap_or(TemporalRoundingMode::Trunc) - .negate(), - ) - } else { - (1.0, rounding_mode.unwrap_or(TemporalRoundingMode::Trunc)) - }; - - let smallest_unit = smallest_unit.unwrap_or(TemporalUnit::Nanosecond); - // Use the defaultlargestunit which is max smallestlargestdefault and smallestunit - let largest_unit = largest_unit.unwrap_or(smallest_unit.max(TemporalUnit::Hour)); + let (sign, resolved) = ResolvedRoundingOptions::from_diff_settings( + settings, + op, + TemporalUnit::Hour, + TemporalUnit::Nanosecond, + )?; // 5. Let norm be ! DifferenceTime(temporalTime.[[ISOHour]], temporalTime.[[ISOMinute]], // temporalTime.[[ISOSecond]], temporalTime.[[ISOMillisecond]], temporalTime.[[ISOMicrosecond]], // temporalTime.[[ISONanosecond]], other.[[ISOHour]], other.[[ISOMinute]], other.[[ISOSecond]], // other.[[ISOMillisecond]], other.[[ISOMicrosecond]], other.[[ISONanosecond]]). - let time = self.iso.diff(&other.iso); + let mut normalized_time = self.iso.diff(&other.iso).to_normalized(); // 6. If settings.[[SmallestUnit]] is not "nanosecond" or settings.[[RoundingIncrement]] ≠ 1, then - let norm = if smallest_unit != TemporalUnit::Nanosecond - || rounding_increment != RoundingIncrement::ONE + if resolved.smallest_unit != TemporalUnit::Nanosecond + || resolved.increment != RoundingIncrement::ONE { // a. Let roundRecord be ! RoundDuration(0, 0, 0, 0, norm, settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[RoundingMode]]). - let round_record = time.round(rounding_increment, smallest_unit, rounding_mode)?; + let (round_record, _) = TimeDuration::round(0.0, &normalized_time, resolved)?; // b. Set norm to roundRecord.[[NormalizedDuration]].[[NormalizedTime]]. - round_record.0 - } else { - time.to_normalized() + normalized_time = round_record.normalized_time_duration() }; // 7. Let result be BalanceTimeDuration(norm, settings.[[LargestUnit]]). - let result = TimeDuration::from_normalized(norm, largest_unit)?.1; + 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, @@ -217,44 +230,16 @@ impl Time { /// Returns the `Duration` until the provided `Time` from the current `Time`. /// /// NOTE: `until` assumes the provided other time will occur in the future relative to the current. - pub fn until( - &self, - other: &Self, - rounding_mode: Option, - rounding_increment: Option, - largest_unit: Option, - smallest_unit: Option, - ) -> TemporalResult { - self.diff_time( - false, - other, - rounding_mode, - rounding_increment, - largest_unit, - smallest_unit, - ) + pub fn until(&self, other: &Self, settings: DifferenceSettings) -> TemporalResult { + self.diff_time(DifferenceOperation::Until, other, settings) } #[inline] /// Returns the `Duration` since the provided `Time` from the current `Time`. /// /// NOTE: `since` assumes the provided other time is in the past relative to the current. - pub fn since( - &self, - other: &Self, - rounding_mode: Option, - rounding_increment: Option, - largest_unit: Option, - smallest_unit: Option, - ) -> TemporalResult { - self.diff_time( - true, - other, - rounding_mode, - rounding_increment, - largest_unit, - smallest_unit, - ) + pub fn since(&self, other: &Self, settings: DifferenceSettings) -> TemporalResult { + self.diff_time(DifferenceOperation::Since, other, settings) } // TODO (nekevss): optimize and test rounding_increment type (f64 vs. u64). @@ -290,7 +275,7 @@ mod tests { use crate::{ components::Duration, iso::IsoTime, - options::{ArithmeticOverflow, TemporalUnit}, + options::{ArithmeticOverflow, DifferenceSettings, TemporalUnit}, }; use super::Time; @@ -400,17 +385,17 @@ mod tests { let two = Time::new(14, 23, 30, 123, 456, 789, ArithmeticOverflow::Constrain).unwrap(); let three = Time::new(13, 30, 30, 123, 456, 789, ArithmeticOverflow::Constrain).unwrap(); - let result = one.since(&two, None, None, None, None).unwrap(); + let result = one.since(&two, DifferenceSettings::default()).unwrap(); assert_eq!(result.hours(), 1.0); - let result = two.since(&one, None, None, None, None).unwrap(); + let result = two.since(&one, DifferenceSettings::default()).unwrap(); assert_eq!(result.hours(), -1.0); - let result = one.since(&three, None, None, None, None).unwrap(); + let result = one.since(&three, DifferenceSettings::default()).unwrap(); assert_eq!(result.hours(), 1.0); assert_eq!(result.minutes(), 53.0); - let result = three.since(&one, None, None, None, None).unwrap(); + let result = three.since(&one, DifferenceSettings::default()).unwrap(); assert_eq!(result.hours(), -1.0); assert_eq!(result.minutes(), -53.0); } @@ -421,17 +406,17 @@ mod tests { let two = Time::new(16, 23, 30, 123, 456, 789, ArithmeticOverflow::Constrain).unwrap(); let three = Time::new(17, 0, 30, 123, 456, 789, ArithmeticOverflow::Constrain).unwrap(); - let result = one.until(&two, None, None, None, None).unwrap(); + let result = one.until(&two, DifferenceSettings::default()).unwrap(); assert_eq!(result.hours(), 1.0); - let result = two.until(&one, None, None, None, None).unwrap(); + let result = two.until(&one, DifferenceSettings::default()).unwrap(); assert_eq!(result.hours(), -1.0); - let result = one.until(&three, None, None, None, None).unwrap(); + let result = one.until(&three, DifferenceSettings::default()).unwrap(); assert_eq!(result.hours(), 1.0); assert_eq!(result.minutes(), 37.0); - let result = three.until(&one, None, None, None, None).unwrap(); + let result = three.until(&one, DifferenceSettings::default()).unwrap(); assert_eq!(result.hours(), -1.0); assert_eq!(result.minutes(), -37.0); } diff --git a/src/iso.rs b/src/iso.rs index 327f6a8e..96e509c6 100644 --- a/src/iso.rs +++ b/src/iso.rs @@ -12,12 +12,15 @@ //! //! An `IsoDateTime` has the internal slots of both an `IsoDate` and `IsoTime`. -use std::num::NonZeroU64; +use std::num::NonZeroU128; use crate::{ components::{ calendar::Calendar, - duration::{normalized::NormalizedTimeDuration, DateDuration, TimeDuration}, + duration::{ + normalized::{NormalizedDurationRecord, NormalizedTimeDuration}, + DateDuration, TimeDuration, + }, Date, Duration, }, error::TemporalError, @@ -31,7 +34,7 @@ use num_traits::{cast::FromPrimitive, ToPrimitive}; /// `IsoDateTime` is the record of the `IsoDate` and `IsoTime` internal slots. #[non_exhaustive] -#[derive(Debug, Default, Clone, Copy)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct IsoDateTime { pub(crate) date: IsoDate, pub(crate) time: IsoTime, @@ -125,6 +128,12 @@ impl IsoDateTime { iso_dt_within_valid_limits(self.date, &self.time) } + // TODO: Move offset away from f64? + /// Returns this `IsoDateTime` in nanoseconds + pub(crate) fn as_nanoseconds(&self, offset: f64) -> Option { + utc_epoch_nanos(self.date, &self.time, offset).and_then(|z| z.to_i128()) + } + /// Specification equivalent to 5.5.9 `AddDateTime`. pub(crate) fn add_date_duration( &self, @@ -164,6 +173,86 @@ impl IsoDateTime { // [[Microsecond]]: timeResult.[[Microsecond]], [[Nanosecond]]: timeResult.[[Nanosecond]] }. Ok(Self::new_unchecked(added_date.iso, t_result.1)) } + + // TODO: Determine whether to provide an options object...seems duplicative. + /// 5.5.11 DifferenceISODateTime ( y1, mon1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, d2, h2, min2, s2, ms2, mus2, ns2, calendarRec, largestUnit, options ) + pub(crate) fn diff( + &self, + other: &Self, + calendar: &Calendar, + largest_unit: TemporalUnit, + ) -> TemporalResult { + // 1. Assert: ISODateTimeWithinLimits(y1, mon1, d1, h1, min1, s1, ms1, mus1, ns1) is true. + // 2. Assert: ISODateTimeWithinLimits(y2, mon2, d2, h2, min2, s2, ms2, mus2, ns2) is true. + // 3. Assert: If y1 ≠ y2, and mon1 ≠ mon2, and d1 ≠ d2, and LargerOfTwoTemporalUnits(largestUnit, "day") + // is not "day", CalendarMethodsRecordHasLookedUp(calendarRec, date-until) is true. + + // 4. Let timeDuration be DifferenceTime(h1, min1, s1, ms1, mus1, ns1, h2, min2, s2, ms2, mus2, ns2). + let mut time_duration = + NormalizedTimeDuration::from_time_duration(&self.time.diff(&other.time)); + + // 5. Let timeSign be NormalizedTimeDurationSign(timeDuration). + let time_sign = time_duration.sign() as i8; + + // 6. Let dateSign be CompareISODate(y2, mon2, d2, y1, mon1, d1). + let date_sign = other.date.cmp(&self.date) as i32; + // 7. Let adjustedDate be CreateISODateRecord(y2, mon2, d2). + let mut adjusted_date = other.date; + + // 8. If timeSign = -dateSign, then + if i32::from(time_sign) == -date_sign { + // a. Set adjustedDate to BalanceISODate(adjustedDate.[[Year]], adjustedDate.[[Month]], adjustedDate.[[Day]] + timeSign). + adjusted_date = IsoDate::balance( + adjusted_date.year, + i32::from(adjusted_date.month), + i32::from(adjusted_date.day) + i32::from(time_sign), + ); + // b. Set timeDuration to ? Add24HourDaysToNormalizedTimeDuration(timeDuration, -timeSign). + time_duration = time_duration.add_days(-i64::from(time_sign))?; + } + + // 9. Let date1 be ! CreateTemporalDate(y1, mon1, d1, calendarRec.[[Receiver]]). + let date_one = Date::new_unchecked(self.date, calendar.clone()); + // 10. Let date2 be ! CreateTemporalDate(adjustedDate.[[Year]], adjustedDate.[[Month]], + // adjustedDate.[[Day]], calendarRec.[[Receiver]]). + let date_two = Date::new( + adjusted_date.year, + adjusted_date.month.into(), + adjusted_date.day.into(), + calendar.clone(), + ArithmeticOverflow::Reject, + )?; + + // 11. Let dateLargestUnit be LargerOfTwoTemporalUnits("day", largestUnit). + // 12. Let untilOptions be ! SnapshotOwnProperties(options, null). + let date_largest_unit = largest_unit.max(TemporalUnit::Day); + + // 13. Perform ! CreateDataPropertyOrThrow(untilOptions, "largestUnit", dateLargestUnit). + // 14. Let dateDifference be ? DifferenceDate(calendarRec, date1, date2, untilOptions). + let date_diff = date_one.internal_diff_date(&date_two, date_largest_unit)?; + + // 16. If largestUnit is not dateLargestUnit, then + let days = if largest_unit == date_largest_unit { + // 15. Let days be dateDifference.[[Days]]. + date_diff.days() + } else { + // a. Set timeDuration to ? Add24HourDaysToNormalizedTimeDuration(timeDuration, dateDifference.[[Days]]). + time_duration = time_duration.add_days(date_diff.days() as i64)?; + // b. Set days to 0. + 0.0 + }; + + // 17. Return ? CreateNormalizedDurationRecord(dateDifference.[[Years]], dateDifference.[[Months]], dateDifference.[[Weeks]], days, timeDuration). + NormalizedDurationRecord::new( + DateDuration::new_unchecked( + date_diff.years(), + date_diff.months(), + date_diff.weeks(), + days, + ), + time_duration, + ) + } } // ==== `IsoDate` section ==== @@ -228,7 +317,7 @@ impl IsoDate { /// Create a balanced `IsoDate` /// /// Equivalent to `BalanceISODate`. - fn balance(year: i32, month: i32, day: i32) -> Self { + pub(crate) fn balance(year: i32, month: i32, day: i32) -> Self { let epoch_days = iso_date_to_epoch_days(year, month - 1, day); let ms = utils::epoch_days_to_epoch_ms(epoch_days, 0f64); Self::new_unchecked( @@ -238,6 +327,11 @@ impl IsoDate { ) } + /// Returns this `IsoDate` in nanoseconds. + pub(crate) fn as_nanoseconds(&self) -> Option { + utc_epoch_nanos(*self, &IsoTime::default(), 0.0).and_then(|z| z.to_i128()) + } + /// Functionally the same as Date's abstract operation `MakeDay` /// /// Equivalent to `IsoDateToEpochDays` @@ -301,8 +395,9 @@ impl IsoDate { // 5. Let years be 0. let mut years = 0; + let mut months = 0; // 6. If largestUnit is "year", then - if largest_unit == TemporalUnit::Year { + if largest_unit == TemporalUnit::Year || largest_unit == TemporalUnit::Month { // others.year - self.year is adopted from temporal-proposal/polyfill as it saves iterations. // a. Let candidateYears be sign. let mut candidate_years: i32 = other.year - self.year; @@ -320,12 +415,9 @@ impl IsoDate { // ii. Set candidateYears to candidateYears + sign. candidate_years += i32::from(sign); } - } - // 7. Let months be 0. - let mut months = 0; - // 8. If largestUnit is "year" or largestUnit is "month", then - if largest_unit == TemporalUnit::Year || largest_unit == TemporalUnit::Month { + // 7. Let months be 0. + // 8. If largestUnit is "year" or largestUnit is "month", then // a. Let candidateMonths be sign. let mut candidate_months: i32 = sign.into(); // b. Let intermediate be BalanceISOYearMonth(y1 + years, m1 + candidateMonths). @@ -369,10 +461,11 @@ impl IsoDate { ); let (weeks, days) = if largest_unit == TemporalUnit::Week { - (days / 7, days.rem_euclid(7)) + (days / 7, days % 7) } else { (0, days) }; + // 17. Return ! CreateDateDurationRecord(years, months, weeks, days). DateDuration::new(years as f64, months as f64, weeks as f64, days as f64) } @@ -621,10 +714,10 @@ impl IsoTime { }; let ns_per_unit = if unit == TemporalUnit::Day { - unsafe { NonZeroU64::new_unchecked(day_length_ns.unwrap_or(NS_PER_DAY)) } + unsafe { NonZeroU128::new_unchecked(day_length_ns.unwrap_or(NS_PER_DAY).into()) } } else { let nanos = unit.as_nanoseconds().temporal_unwrap()?; - unsafe { NonZeroU64::new_unchecked(nanos) } + unsafe { NonZeroU128::new_unchecked(nanos.into()) } }; let increment = ns_per_unit @@ -636,7 +729,7 @@ impl IsoTime { let result = IncrementRounder::::from_potentially_negative_parts(quantity.into(), increment)? .round(mode) - / i128::from(ns_per_unit.get()); + / i128::from_u128(ns_per_unit.get()).temporal_unwrap()?; let result = match unit { // 10. If unit is "day", then @@ -779,10 +872,12 @@ fn utc_epoch_nanos(date: IsoDate, time: &IsoTime, offset: f64) -> Option // ==== `IsoDate` specific utiltiy functions ==== /// Returns the Epoch days based off the given year, month, and day. +/// +/// NOTE: Month should be in a range of 0-11 #[inline] fn iso_date_to_epoch_days(year: i32, month: i32, day: i32) -> i32 { // 1. Let resolvedYear be year + floor(month / 12). - let resolved_year = year + (month / 12); + let resolved_year = year + month.div_euclid(12); // 2. Let resolvedMonth be month modulo 12. let resolved_month = month.rem_euclid(12); diff --git a/src/lib.rs b/src/lib.rs index 91c9b5c5..194ab92b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,6 +50,8 @@ pub(crate) mod rounding; #[doc(hidden)] pub(crate) mod utils; +use std::cmp::Ordering; + // TODO: evaluate positives and negatives of using tinystr. // Re-exporting tinystr as a convenience, as it is currently tied into the API. pub use tinystr::TinyAsciiStr; @@ -80,6 +82,34 @@ impl TemporalUnwrap for Option { } } +#[repr(i8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum Sign { + Positive = 1, + Zero = 0, + Negative = -1, +} + +impl From for Sign { + fn from(value: i8) -> Self { + match value.cmp(&0) { + Ordering::Greater => Self::Positive, + Ordering::Equal => Self::Zero, + Ordering::Less => Self::Negative, + } + } +} + +impl Sign { + /// Coerces the current `Sign` to be either negative or positive. + pub(crate) fn as_sign_multiplier(&self) -> i8 { + if matches!(self, Self::Zero) { + return 1; + } + *self as i8 + } +} + // Relevant numeric constants /// Nanoseconds per day constant: 8.64e+13 pub const NS_PER_DAY: u64 = MS_PER_DAY as u64 * 1_000_000; diff --git a/src/options.rs b/src/options.rs index c8b186ff..1cdb3144 100644 --- a/src/options.rs +++ b/src/options.rs @@ -4,15 +4,169 @@ //! operation may be completed. use core::{fmt, str::FromStr}; +use std::ops::Add; use crate::{ components::{Date, ZonedDateTime}, - TemporalError, + Sign, TemporalError, TemporalResult, NS_PER_DAY, }; mod increment; pub use increment::RoundingIncrement; +// ==== RoundingOptions / DifferenceSettings ==== + +pub(crate) enum DifferenceOperation { + Until, + Since, +} + +#[non_exhaustive] +#[derive(Debug, Default, Clone, Copy)] +pub struct DifferenceSettings { + pub largest_unit: Option, + pub smallest_unit: Option, + pub rounding_mode: Option, + pub increment: Option, +} + +#[non_exhaustive] +#[derive(Debug, Clone, Copy)] +pub struct RoundingOptions { + pub largest_unit: Option, + pub smallest_unit: Option, + pub rounding_mode: Option, + pub increment: Option, +} + +// Note: Specification does not clearly state a default, but +// having both largest and smallest unit None would auto throw. + +impl Default for RoundingOptions { + fn default() -> Self { + Self { + largest_unit: Some(TemporalUnit::Auto), + smallest_unit: None, + rounding_mode: None, + increment: None, + } + } +} + +/// Internal options object that represents the resolved rounding options. +#[derive(Debug, Clone, Copy)] +pub(crate) struct ResolvedRoundingOptions { + pub(crate) largest_unit: TemporalUnit, + pub(crate) smallest_unit: TemporalUnit, + pub(crate) increment: RoundingIncrement, + pub(crate) rounding_mode: TemporalRoundingMode, +} + +impl ResolvedRoundingOptions { + pub(crate) fn from_diff_settings( + options: DifferenceSettings, + operation: DifferenceOperation, + fallback_largest: TemporalUnit, + fallback_smallest: TemporalUnit, + ) -> TemporalResult<(Sign, Self)> { + // 4. Let resolvedOptions be ? SnapshotOwnProperties(? GetOptionsObject(options), null). + // 5. Let settings be ? GetDifferenceSettings(operation, resolvedOptions, DATE, « », "day", "day"). + let increment = options.increment.unwrap_or_default(); + let (sign, rounding_mode) = match operation { + DifferenceOperation::Since => { + let mode = options + .rounding_mode + .unwrap_or(TemporalRoundingMode::Trunc) + .negate(); + (Sign::Negative, mode) + } + DifferenceOperation::Until => ( + Sign::Positive, + options.rounding_mode.unwrap_or(TemporalRoundingMode::Trunc), + ), + }; + let smallest_unit = options.smallest_unit.unwrap_or(fallback_smallest); + // Use the defaultlargestunit which is max smallestlargestdefault and smallestunit + let largest_unit = options + .largest_unit + .unwrap_or(smallest_unit.max(fallback_largest)); + + let resolved = ResolvedRoundingOptions { + largest_unit, + smallest_unit, + increment, + rounding_mode, + }; + + Ok((sign, resolved)) + } + + pub(crate) fn from_options( + options: RoundingOptions, + existing_largest: TemporalUnit, + ) -> TemporalResult { + // 22. If smallestUnitPresent is false and largestUnitPresent is false, then + if options.largest_unit.is_none() && options.smallest_unit.is_none() { + // a. Throw a RangeError exception. + return Err(TemporalError::range() + .with_message("smallestUnit and largestUnit cannot both be None.")); + } + + // 14. Let roundingIncrement be ? ToTemporalRoundingIncrement(roundTo). + let increment = options.increment.unwrap_or_default(); + // 15. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand"). + let rounding_mode = options.rounding_mode.unwrap_or_default(); + // 16. Let smallestUnit be ? GetTemporalUnit(roundTo, "smallestUnit", DATETIME, undefined). + // 17. If smallestUnit is undefined, then + // a. Set smallestUnitPresent to false. + // b. Set smallestUnit to "nanosecond". + // 18. Let existingLargestUnit be ! DefaultTemporalLargestUnit(duration.[[Years]], + // duration.[[Months]], duration.[[Weeks]], duration.[[Days]], duration.[[Hours]], + // duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], + // duration.[[Microseconds]]). + // 19. Let defaultLargestUnit be LargerOfTwoTemporalUnits(existingLargestUnit, smallestUnit). + // 20. If largestUnit is undefined, then + // a. Set largestUnitPresent to false. + // b. Set largestUnit to defaultLargestUnit. + // 21. Else if largestUnit is "auto", then + // a. Set largestUnit to defaultLargestUnit. + // 23. If LargerOfTwoTemporalUnits(largestUnit, smallestUnit) is not largestUnit, throw a RangeError exception. + // 24. Let maximum be MaximumTemporalDurationRoundingIncrement(smallestUnit). + // 25. If maximum is not undefined, perform ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false). + let smallest_unit = options.smallest_unit.unwrap_or(TemporalUnit::Nanosecond); + + let default_largest = existing_largest.max(smallest_unit); + + let largest_unit = match options.largest_unit { + Some(TemporalUnit::Auto) | None => default_largest, + Some(unit) => unit, + }; + + if largest_unit.max(smallest_unit) != largest_unit { + return Err(TemporalError::range().with_message( + "largestUnit when rounding Duration was not the largest provided unit", + )); + } + + let maximum = smallest_unit.to_maximum_rounding_increment(); + // 25. If maximum is not undefined, perform ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false). + if let Some(max) = maximum { + increment.validate(max.into(), false)?; + } + + Ok(Self { + largest_unit, + smallest_unit, + increment, + rounding_mode, + }) + } + + pub(crate) fn is_noop(&self) -> bool { + self.smallest_unit == TemporalUnit::Nanosecond && self.increment == RoundingIncrement::ONE + } +} + // ==== RelativeTo Object ==== pub struct RelativeTo<'a> { @@ -87,7 +241,8 @@ impl TemporalUnit { Year, }; match self { - Year | Month | Week | Day | Auto => None, + Year | Month | Week | Auto => None, + Day => Some(NS_PER_DAY), Hour => Some(3_600_000_000_000), Minute => Some(60_000_000_000), Second => Some(1_000_000_000), @@ -122,6 +277,14 @@ impl From for TemporalUnit { } } +impl Add for TemporalUnit { + type Output = TemporalUnit; + + fn add(self, rhs: usize) -> Self::Output { + TemporalUnit::from(self as usize + rhs) + } +} + /// A parsing error for `TemporalUnit` #[derive(Debug, Clone, Copy)] pub struct ParseTemporalUnitError; diff --git a/src/options/increment.rs b/src/options/increment.rs index bde3abd3..004dc0d6 100644 --- a/src/options/increment.rs +++ b/src/options/increment.rs @@ -1,4 +1,4 @@ -use std::num::{NonZeroU32, NonZeroU64}; +use std::num::{NonZeroU128, NonZeroU32}; use crate::{TemporalError, TemporalResult}; @@ -107,7 +107,7 @@ impl RoundingIncrement { Ok(()) } - pub(crate) fn as_extended_increment(&self) -> NonZeroU64 { - NonZeroU64::from(self.0) + pub(crate) fn as_extended_increment(&self) -> NonZeroU128 { + NonZeroU128::from(self.0) } } diff --git a/src/rounding.rs b/src/rounding.rs index 3c14a90d..081ac4e4 100644 --- a/src/rounding.rs +++ b/src/rounding.rs @@ -7,7 +7,7 @@ use crate::{ use std::{ cmp::Ordering, - num::NonZeroU64, + num::NonZeroU128, ops::{Div, Neg}, }; @@ -44,7 +44,7 @@ impl IncrementRounder { // ==== PUBLIC ==== pub(crate) fn from_potentially_negative_parts( number: T, - increment: NonZeroU64, + increment: NonZeroU128, ) -> TemporalResult { let increment = ::from(increment.get()).temporal_unwrap()?; Ok(Self { @@ -54,7 +54,7 @@ impl IncrementRounder { }) } - pub(crate) fn from_positive_parts(number: T, increment: NonZeroU64) -> TemporalResult { + pub(crate) fn from_positive_parts(number: T, increment: NonZeroU128) -> TemporalResult { let increment = ::from(increment.get()).temporal_unwrap()?; debug_assert!(number >= T::ZERO); @@ -195,29 +195,32 @@ fn apply_unsigned_rounding_mode( #[cfg(test)] mod tests { - use std::num::NonZeroU64; + use std::num::NonZeroU128; use super::{IncrementRounder, Round, TemporalRoundingMode}; #[test] fn basic_f64_rounding() { - let result = IncrementRounder::::from_positive_parts(2.5, NonZeroU64::new(1).unwrap()) - .unwrap() - .round_as_positive(TemporalRoundingMode::Floor); + let result = + IncrementRounder::::from_positive_parts(2.5, NonZeroU128::new(1).unwrap()) + .unwrap() + .round_as_positive(TemporalRoundingMode::Floor); assert_eq!(result, 2); - let result = IncrementRounder::::from_positive_parts(2.5, NonZeroU64::new(1).unwrap()) - .unwrap() - .round_as_positive(TemporalRoundingMode::Ceil); + let result = + IncrementRounder::::from_positive_parts(2.5, NonZeroU128::new(1).unwrap()) + .unwrap() + .round_as_positive(TemporalRoundingMode::Ceil); assert_eq!(result, 3); - let result = IncrementRounder::::from_positive_parts(7.5, NonZeroU64::new(3).unwrap()) - .unwrap() - .round_as_positive(TemporalRoundingMode::HalfEven); + let result = + IncrementRounder::::from_positive_parts(7.5, NonZeroU128::new(3).unwrap()) + .unwrap() + .round_as_positive(TemporalRoundingMode::HalfEven); assert_eq!(result, 6); let result = - IncrementRounder::::from_positive_parts(10.5, NonZeroU64::new(3).unwrap()) + IncrementRounder::::from_positive_parts(10.5, NonZeroU128::new(3).unwrap()) .unwrap() .round_as_positive(TemporalRoundingMode::HalfEven); assert_eq!(result, 12); @@ -225,34 +228,36 @@ mod tests { #[test] fn basic_i128_rounding() { - let result = IncrementRounder::::from_positive_parts(5, NonZeroU64::new(2).unwrap()) + let result = IncrementRounder::::from_positive_parts(5, NonZeroU128::new(2).unwrap()) .unwrap() .round_as_positive(TemporalRoundingMode::Floor); assert_eq!(result, 4); - let result = IncrementRounder::::from_positive_parts(5, NonZeroU64::new(2).unwrap()) + let result = IncrementRounder::::from_positive_parts(5, NonZeroU128::new(2).unwrap()) .unwrap() .round_as_positive(TemporalRoundingMode::Ceil); assert_eq!(result, 6); - let result = IncrementRounder::::from_positive_parts(15, NonZeroU64::new(7).unwrap()) - .unwrap() - .round_as_positive(TemporalRoundingMode::HalfEven); + let result = + IncrementRounder::::from_positive_parts(15, NonZeroU128::new(7).unwrap()) + .unwrap() + .round_as_positive(TemporalRoundingMode::HalfEven); assert_eq!(result, 14); let result = - IncrementRounder::::from_positive_parts(27, NonZeroU64::new(13).unwrap()) + IncrementRounder::::from_positive_parts(27, NonZeroU128::new(13).unwrap()) .unwrap() .round_as_positive(TemporalRoundingMode::HalfEven); assert_eq!(result, 26); - let result = IncrementRounder::::from_positive_parts(20, NonZeroU64::new(7).unwrap()) - .unwrap() - .round_as_positive(TemporalRoundingMode::HalfEven); + let result = + IncrementRounder::::from_positive_parts(20, NonZeroU128::new(7).unwrap()) + .unwrap() + .round_as_positive(TemporalRoundingMode::HalfEven); assert_eq!(result, 21); let result = - IncrementRounder::::from_positive_parts(37, NonZeroU64::new(13).unwrap()) + IncrementRounder::::from_positive_parts(37, NonZeroU128::new(13).unwrap()) .unwrap() .round_as_positive(TemporalRoundingMode::HalfEven); assert_eq!(result, 39); @@ -262,7 +267,7 @@ mod tests { fn neg_i128_rounding() { let result = IncrementRounder::::from_potentially_negative_parts( -9, - NonZeroU64::new(2).unwrap(), + NonZeroU128::new(2).unwrap(), ) .unwrap() .round(TemporalRoundingMode::Ceil); @@ -270,7 +275,7 @@ mod tests { let result = IncrementRounder::::from_potentially_negative_parts( -9, - NonZeroU64::new(2).unwrap(), + NonZeroU128::new(2).unwrap(), ) .unwrap() .round(TemporalRoundingMode::Floor); @@ -281,7 +286,7 @@ mod tests { fn neg_f64_rounding() { let result = IncrementRounder::::from_potentially_negative_parts( -8.5, - NonZeroU64::new(1).unwrap(), + NonZeroU128::new(1).unwrap(), ) .unwrap() .round(TemporalRoundingMode::Ceil); @@ -289,7 +294,7 @@ mod tests { let result = IncrementRounder::::from_potentially_negative_parts( -8.5, - NonZeroU64::new(1).unwrap(), + NonZeroU128::new(1).unwrap(), ) .unwrap() .round(TemporalRoundingMode::Floor);