From a42618f63cb127190164ef161111535de20efd0e Mon Sep 17 00:00:00 2001 From: Kevin Ness Date: Mon, 11 Aug 2025 19:06:50 -0500 Subject: [PATCH 1/6] Update duration to specification plus general changes based off PR 366 --- src/builtins/compiled/duration/tests.rs | 6 +- src/builtins/core/date.rs | 69 +-- src/builtins/core/datetime.rs | 76 ++-- src/builtins/core/duration.rs | 538 +++++++++++++++-------- src/builtins/core/duration/date.rs | 45 +- src/builtins/core/duration/normalized.rs | 219 ++++----- src/builtins/core/duration/time.rs | 322 -------------- src/builtins/core/instant.rs | 177 ++++---- src/builtins/core/mod.rs | 2 +- src/builtins/core/time.rs | 65 +-- src/builtins/core/year_month.rs | 2 +- src/builtins/core/zoneddatetime.rs | 25 +- src/error.rs | 2 + src/iso.rs | 56 +-- src/lib.rs | 4 +- temporal_capi/src/duration.rs | 48 -- temporal_capi/src/instant.rs | 24 +- temporal_capi/src/plain_time.rs | 20 +- tools/tzif-inspect/src/main.rs | 10 +- 19 files changed, 725 insertions(+), 985 deletions(-) delete mode 100644 src/builtins/core/duration/time.rs diff --git a/src/builtins/compiled/duration/tests.rs b/src/builtins/compiled/duration/tests.rs index 4fcf9cae3..12eadb33e 100644 --- a/src/builtins/compiled/duration/tests.rs +++ b/src/builtins/compiled/duration/tests.rs @@ -3,7 +3,7 @@ use crate::{ OffsetDisambiguation, RelativeTo, RoundingIncrement, RoundingMode, RoundingOptions, Unit, }, partial::PartialDuration, - Calendar, DateDuration, PlainDate, TimeDuration, TimeZone, ZonedDateTime, + Calendar, DateDuration, PlainDate, TimeZone, ZonedDateTime, }; use core::{num::NonZeroU32, str::FromStr}; @@ -373,7 +373,7 @@ fn basic_negative_expand_rounding() { #[test] fn rounding_to_fractional_day_tests() { - let twenty_five_hours = Duration::from(TimeDuration::new(25, 0, 0, 0, 0, 0).unwrap()); + let twenty_five_hours = Duration::from_hours(25); let options = RoundingOptions { largest_unit: Some(Unit::Day), smallest_unit: None, @@ -485,7 +485,7 @@ fn basic_subtract_duration() { // days-24-hours-relative-to-zoned-date-time.js #[test] fn round_relative_to_zoned_datetime() { - let duration = Duration::from(TimeDuration::new(25, 0, 0, 0, 0, 0).unwrap()); + let duration = Duration::from_hours(25); let zdt = ZonedDateTime::try_new( 1_000_000_000_000_000_000, Calendar::default(), diff --git a/src/builtins/core/date.rs b/src/builtins/core/date.rs index 72fb8ceeb..18dec32f5 100644 --- a/src/builtins/core/date.rs +++ b/src/builtins/core/date.rs @@ -23,10 +23,7 @@ use core::{cmp::Ordering, str::FromStr}; use icu_calendar::AnyCalendarKind; use writeable::Writeable; -use super::{ - duration::{normalized::NormalizedDurationRecord, TimeDuration}, - PlainMonthDay, PlainYearMonth, -}; +use super::{duration::normalized::NormalizedDurationRecord, PlainMonthDay, PlainYearMonth}; use tinystr::TinyAsciiStr; // TODO (potentially): Bump era up to TinyAsciiStr<18> to accomodate @@ -185,53 +182,31 @@ impl PlainDate { Self { iso, calendar } } + // Updated: 2025-08-03 /// Returns the date after adding the given duration to date. /// - /// Temporal Equivalent: 3.5.13 `AddDate ( calendar, plainDate, duration [ , options [ , dateAdd ] ] )` + /// 3.5.14 `AddDurationToDate` + /// + /// More information: + /// + /// - [AO specification](https://tc39.es/proposal-temporal/#sec-temporal-adddurationtodate) #[inline] - pub(crate) fn add_date( + pub(crate) fn add_duration_to_date( &self, duration: &Duration, overflow: Option, ) -> TemporalResult { - // 2. If options is not present, set options to undefined. + // 3. If operation is subtract, set duration to CreateNegatedTemporalDuration(duration). + // 4. Let dateDuration be ToDateDurationRecordWithoutTime(duration). + // TODO: Look into why this is fallible, and make some adjustments + let date_duration = duration.to_date_duration_record_without_time()?; + // 5. Let resolvedOptions be ? GetOptionsObject(options). + // 6. Let overflow be ? GetTemporalOverflowOption(resolvedOptions). let overflow = overflow.unwrap_or(ArithmeticOverflow::Constrain); - // 3. If duration.[[Years]] ≠ 0, or duration.[[Months]] ≠ 0, or duration.[[Weeks]] ≠ 0, then - if duration.date().years != 0 || duration.date().months != 0 || duration.date().weeks != 0 { - // a. If dateAdd is not present, then - // i. Set dateAdd to unused. - // ii. If calendar is an Object, set dateAdd to ? GetMethod(calendar, "dateAdd"). - // b. Return ? CalendarDateAdd(calendar, plainDate, duration, options, dateAdd). - return self - .calendar() - .date_add(&self.iso, duration.date(), overflow); - } - - // 4. Let overflow be ? ToTemporalOverflow(options). - // 5. Let norm be NormalizeTimeDuration(duration.[[Hours]], - // duration.[[Minutes]], duration.[[Seconds]], - // duration.[[Milliseconds]], duration.[[Microseconds]], - // duration.[[Nanoseconds]]). - // 6. Let days be duration.[[Days]] + BalanceTimeDuration(norm, - // "day").[[Days]]. - let days = duration - .days() - .checked_add( - TimeDuration::from_normalized(duration.time().to_normalized(), Unit::Day)?.0, - ) - .ok_or(TemporalError::range())?; - - // 7. Let result be ? AddISODate(plainDate.[[ISOYear]], plainDate.[[ISOMonth]], plainDate.[[ISODay]], 0, 0, 0, days, overflow). - let result = self - .iso - .add_date_duration(&DateDuration::new(0, 0, 0, days)?, overflow)?; - - Self::try_new( - result.year, - result.month, - result.day, - self.calendar().clone(), - ) + // 7. Let result be ? CalendarDateAdd(calendar, temporalDate.[[ISODate]], dateDuration, overflow). + // 8. Return ! CreateTemporalDate(result, calendar). + self.calendar() + .date_add(&self.iso, &date_duration, overflow) } /// Returns a duration representing the difference between the dates one and two. @@ -298,7 +273,7 @@ impl PlainDate { 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 mut duration = NormalizedDurationRecord::from_date_duration(*result.date())?; + let mut 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 == Unit::Day && resolved.increment.get() == 1; @@ -319,7 +294,7 @@ impl PlainDate { resolved, )? } - let result = Duration::from_normalized(duration, Unit::Day)?; + let result = Duration::from_internal(duration, Unit::Day)?; // 13. Return ! CreateTemporalDuration(sign × duration.[[Years]], sign × duration.[[Months]], sign × duration.[[Weeks]], sign × duration.[[Days]], 0, 0, 0, 0, 0, 0). match op { DifferenceOperation::Until => Ok(result), @@ -520,7 +495,7 @@ impl PlainDate { duration: &Duration, overflow: Option, ) -> TemporalResult { - self.add_date(duration, overflow) + self.add_duration_to_date(duration, overflow) } #[inline] @@ -530,7 +505,7 @@ impl PlainDate { duration: &Duration, overflow: Option, ) -> TemporalResult { - self.add_date(&duration.negated(), overflow) + self.add_duration_to_date(&duration.negated(), overflow) } #[inline] diff --git a/src/builtins/core/datetime.rs b/src/builtins/core/datetime.rs index 3c2779973..50f304a25 100644 --- a/src/builtins/core/datetime.rs +++ b/src/builtins/core/datetime.rs @@ -1,8 +1,8 @@ //! This module implements `DateTime` any directly related algorithms. use super::{ - duration::normalized::{NormalizedDurationRecord, NormalizedTimeDuration}, - Duration, PartialTime, PlainDate, PlainTime, ZonedDateTime, + duration::normalized::NormalizedDurationRecord, Duration, PartialTime, PlainDate, PlainTime, + ZonedDateTime, }; use crate::parsed_intermediates::ParsedDateTime; use crate::{ @@ -19,7 +19,7 @@ use crate::{ parsers::IxdtfStringBuilder, primitive::FiniteF64, provider::{NeverProvider, TimeZoneProvider}, - temporal_assert, MonthCode, TemporalError, TemporalResult, TimeZone, + MonthCode, TemporalError, TemporalResult, TimeZone, }; use alloc::string::String; use core::{cmp::Ordering, str::FromStr}; @@ -225,33 +225,26 @@ impl PlainDateTime { overflow: Option, ) -> TemporalResult { // SKIP: 1, 2, 3, 4 - // 1. If operation is subtract, let sign be -1. Otherwise, let sign be 1. - // 2. Let duration be ? ToTemporalDurationRecord(temporalDurationLike). - // 3. Set options to ? GetOptionsObject(options). - // 4. Let calendarRec be ? CreateCalendarMethodsRecord(dateTime.[[Calendar]], « date-add »). - - // 5. Let norm be NormalizeTimeDuration(sign × duration.[[Hours]], sign × duration.[[Minutes]], sign × duration.[[Seconds]], sign × duration.[[Milliseconds]], sign × duration.[[Microseconds]], sign × duration.[[Nanoseconds]]). - let norm = NormalizedTimeDuration::from_time_duration(duration.time()); - - // TODO: validate Constrain is default with all the recent changes. - // 6. Let result be ? AddDateTime(dateTime.[[ISOYear]], dateTime.[[ISOMonth]], dateTime.[[ISODay]], dateTime.[[ISOHour]], dateTime.[[ISOMinute]], dateTime.[[ISOSecond]], dateTime.[[ISOMillisecond]], dateTime.[[ISOMicrosecond]], dateTime.[[ISONanosecond]], calendarRec, sign × duration.[[Years]], sign × duration.[[Months]], sign × duration.[[Weeks]], sign × duration.[[Days]], norm, options). - let result = - self.iso - .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. - temporal_assert!( - result.is_within_limits(), - "Assertion failed: the below datetime is not within valid limits:\n{:?}", - result - ); - - // 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())) + // 5. Let internalDuration be ToInternalDurationRecordWith24HourDays(duration). + let internal_duration = + NormalizedDurationRecord::from_duration_with_24_hour_days(duration)?; + // 6. Let timeResult be AddTime(dateTime.[[ISODateTime]].[[Time]], internalDuration.[[Time]]). + let (days, time_result) = self + .iso + .time + .add(internal_duration.normalized_time_duration()); + // 7. Let dateDuration be ? AdjustDateDurationRecord(internalDuration.[[Date]], timeResult.[[Days]]). + let date_duration = internal_duration.date().adjust(days, None, None)?; + // 8. Let addedDate be ? CalendarDateAdd(dateTime.[[Calendar]], dateTime.[[ISODateTime]].[[ISODate]], dateDuration, overflow). + let added_date = self.calendar().date_add( + &self.iso.date, + &date_duration, + overflow.unwrap_or(ArithmeticOverflow::Constrain), + )?; + // 9. Let result be CombineISODateAndTimeRecord(addedDate, timeResult). + let result = IsoDateTime::new(added_date.iso, time_result)?; + // 10. Return ? CreateTemporalDateTime(result, dateTime.[[Calendar]]). + Ok(Self::new_unchecked(result, self.calendar().clone())) } /// Difference two `DateTime`s together. @@ -284,7 +277,7 @@ impl PlainDateTime { // Step 10-11. let norm_record = self.diff_dt_with_rounding(other, options)?; - let result = Duration::from_normalized(norm_record, options.largest_unit)?; + let result = Duration::from_internal(norm_record, options.largest_unit)?; // Step 12 match op { @@ -1248,13 +1241,13 @@ mod tests { PlainDateTime::try_new(2019, 10, 29, 10, 46, 38, 271, 986, 102, Calendar::default()) .unwrap(); - let result = dt.subtract(&Duration::hour(12), None).unwrap(); + let result = dt.subtract(&Duration::from_hours(12), None).unwrap(); assert_datetime( result, (2019, 10, tinystr!(4, "M10"), 28, 22, 46, 38, 271, 986, 102), ); - let result = dt.add(&Duration::hour(-12), None).unwrap(); + let result = dt.add(&Duration::from_hours(-12), None).unwrap(); assert_datetime( result, (2019, 10, tinystr!(4, "M10"), 28, 22, 46, 38, 271, 986, 102), @@ -1543,4 +1536,21 @@ mod tests { "pads 4 decimal places to 9" ); } + + #[test] + fn datetime_add() { + use crate::{Duration, PlainDateTime}; + use core::str::FromStr; + + let dt = PlainDateTime::from_str("2024-01-15T12:00:00").unwrap(); + + let duration = Duration::from_str("P1M2DT3H4M").unwrap(); + + // Add duration + let later = dt.add(&duration, None).unwrap(); + assert_eq!(later.month(), 2); + assert_eq!(later.day(), 17); + assert_eq!(later.hour(), 15); + assert_eq!(later.minute(), 4); + } } diff --git a/src/builtins/core/duration.rs b/src/builtins/core/duration.rs index a7dcff393..b782ddd84 100644 --- a/src/builtins/core/duration.rs +++ b/src/builtins/core/duration.rs @@ -20,20 +20,18 @@ use ixdtf::{ encoding::Utf8, parsers::IsoDurationParser, records::Fraction, records::TimeDurationRecord, }; use normalized::NormalizedDurationRecord; +use num_traits::Euclid; use self::normalized::NormalizedTimeDuration; mod date; pub(crate) mod normalized; -mod time; #[cfg(test)] mod tests; #[doc(inline)] pub use date::DateDuration; -#[doc(inline)] -pub use time::TimeDuration; /// A `PartialDuration` is a Duration that may have fields not set. #[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] @@ -236,7 +234,7 @@ impl PartialDuration { /// /// // 2023: Jan 31 + 1 month = Feb 28 (no Feb 31st exists) /// assert_eq!(feb_2023.day(), 28); -/// // 2024: Jan 31 + 1 month = Feb 29 (leap year) +/// // 2024: Jan 31 + 1 month = Feb 29 (leap year) /// assert_eq!(feb_2024.day(), 29); /// ``` /// @@ -291,10 +289,37 @@ impl PartialDuration { /// /// [mdn-duration]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration #[non_exhaustive] -#[derive(Debug, Clone, Copy, Default)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] pub struct Duration { - date: DateDuration, - time: TimeDuration, + pub(crate) sign: Sign, + pub(crate) years: u32, + pub(crate) months: u32, + pub(crate) weeks: u32, + pub(crate) days: u64, + pub(crate) hours: u64, + pub(crate) minutes: u64, + pub(crate) seconds: u64, + pub(crate) milliseconds: u64, + pub(crate) microseconds: u128, + pub(crate) nanoseconds: u128, +} + +impl Default for Duration { + fn default() -> Self { + Self { + sign: Sign::Zero, + years: 0, + months: 0, + weeks: 0, + days: 0, + hours: 0, + minutes: 0, + seconds: 0, + milliseconds: 0, + microseconds: 0, + nanoseconds: 0, + } + } } impl core::fmt::Display for Duration { @@ -322,11 +347,12 @@ impl core::fmt::Display for Duration { #[cfg(test)] impl Duration { - pub(crate) fn hour(value: i64) -> Self { - Self::new_unchecked( - DateDuration::default(), - TimeDuration::new_unchecked(value, 0, 0, 0, 0, 0), - ) + pub(crate) fn from_hours(value: i64) -> Self { + Self { + sign: Sign::from(value.signum() as i8), + hours: value.saturating_abs() as u64, + ..Default::default() + } } } @@ -334,59 +360,184 @@ impl Duration { impl Duration { /// Creates a new `Duration` from a `DateDuration` and `TimeDuration`. - #[inline] - pub(crate) const fn new_unchecked(date: DateDuration, time: TimeDuration) -> Self { - Self { date, time } - } - - pub(crate) fn try_new_from_durations( - date: DateDuration, - time: TimeDuration, - ) -> TemporalResult { - if !is_valid_duration( - date.years, - date.months, - date.weeks, - date.days, - time.hours, - time.minutes, - time.seconds, - time.milliseconds, - time.microseconds, - time.nanoseconds, - ) { - return Err(TemporalError::range().with_message("Duration was not valid.")); + #[allow(clippy::too_many_arguments)] + pub(crate) const fn new_unchecked( + sign: Sign, + years: u32, + months: u32, + weeks: u32, + days: u64, + hours: u64, + minutes: u64, + seconds: u64, + milliseconds: u64, + microseconds: u128, + nanoseconds: u128, + ) -> Self { + Self { + sign, + years, + months, + weeks, + days, + hours, + minutes, + seconds, + milliseconds, + microseconds, + nanoseconds, } - Ok(Self::new_unchecked(date, time)) } #[inline] - pub(crate) fn from_normalized( + pub(crate) fn from_internal( duration_record: NormalizedDurationRecord, largest_unit: Unit, ) -> TemporalResult { - let (overflow_day, time) = TimeDuration::from_normalized( - duration_record.normalized_time_duration(), - largest_unit, - )?; - Self::new( + // 1. Let days, hours, minutes, seconds, milliseconds, and microseconds be 0. + let mut days = 0; + let mut hours = 0; + let mut minutes = 0; + let mut seconds = 0; + let mut milliseconds = 0; + let mut microseconds = 0; + + // 2. Let sign be TimeDurationSign(internalDuration.[[Time]]). + let sign = duration_record + .normalized_time_duration() + .sign() + .as_sign_multiplier(); + + // 3. Let nanoseconds be abs(internalDuration.[[Time]]). + let mut nanoseconds = duration_record.normalized_time_duration().0.abs(); + match largest_unit { + // 4. If largestUnit is "year", "month", "week", or "day", then + Unit::Year | Unit::Month | Unit::Week | Unit::Day => { + // a. Set microseconds to floor(nanoseconds / 1000). + // b. Set nanoseconds to nanoseconds modulo 1000. + (microseconds, nanoseconds) = nanoseconds.div_rem_euclid(&1_000); + + // c. Set milliseconds to floor(microseconds / 1000). + // d. Set microseconds to microseconds modulo 1000. + (milliseconds, microseconds) = microseconds.div_rem_euclid(&1_000); + + // e. Set seconds to floor(milliseconds / 1000). + // f. Set milliseconds to milliseconds modulo 1000. + (seconds, milliseconds) = milliseconds.div_rem_euclid(&1_000); + + // g. Set minutes to floor(seconds / 60). + // h. Set seconds to seconds modulo 60. + (minutes, seconds) = seconds.div_rem_euclid(&60); + + // i. Set hours to floor(minutes / 60). + // j. Set minutes to minutes modulo 60. + (hours, minutes) = minutes.div_rem_euclid(&60); + + // k. Set days to floor(hours / 24). + // l. Set hours to hours modulo 24. + (days, hours) = hours.div_rem_euclid(&24); + } + // 5. Else if largestUnit is "hour", then + Unit::Hour => { + // a. Set microseconds to floor(nanoseconds / 1000). + // b. Set nanoseconds to nanoseconds modulo 1000. + (microseconds, nanoseconds) = nanoseconds.div_rem_euclid(&1_000); + + // c. Set milliseconds to floor(microseconds / 1000). + // d. Set microseconds to microseconds modulo 1000. + (milliseconds, microseconds) = microseconds.div_rem_euclid(&1_000); + + // e. Set seconds to floor(milliseconds / 1000). + // f. Set milliseconds to milliseconds modulo 1000. + (seconds, milliseconds) = milliseconds.div_rem_euclid(&1_000); + + // g. Set minutes to floor(seconds / 60). + // h. Set seconds to seconds modulo 60. + (minutes, seconds) = seconds.div_rem_euclid(&60); + + // i. Set hours to floor(minutes / 60). + // j. Set minutes to minutes modulo 60. + (hours, minutes) = minutes.div_rem_euclid(&60); + } + // 6. Else if largestUnit is "minute", then + Unit::Minute => { + // a. Set microseconds to floor(nanoseconds / 1000). + // b. Set nanoseconds to nanoseconds modulo 1000. + (microseconds, nanoseconds) = nanoseconds.div_rem_euclid(&1_000); + + // c. Set milliseconds to floor(microseconds / 1000). + // d. Set microseconds to microseconds modulo 1000. + (milliseconds, microseconds) = microseconds.div_rem_euclid(&1_000); + + // e. Set seconds to floor(milliseconds / 1000). + // f. Set milliseconds to milliseconds modulo 1000. + (seconds, milliseconds) = milliseconds.div_rem_euclid(&1_000); + + // g. Set minutes to floor(seconds / 60). + // h. Set seconds to seconds modulo 60. + (minutes, seconds) = seconds.div_rem_euclid(&60); + } + // 7. Else if largestUnit is "second", then + Unit::Second => { + // a. Set microseconds to floor(nanoseconds / 1000). + // b. Set nanoseconds to nanoseconds modulo 1000. + (microseconds, nanoseconds) = nanoseconds.div_rem_euclid(&1_000); + + // c. Set milliseconds to floor(microseconds / 1000). + // d. Set microseconds to microseconds modulo 1000. + (milliseconds, microseconds) = microseconds.div_rem_euclid(&1_000); + + // e. Set seconds to floor(milliseconds / 1000). + // f. Set milliseconds to milliseconds modulo 1000. + (seconds, milliseconds) = milliseconds.div_rem_euclid(&1_000); + } + // 8. Else if largestUnit is "millisecond", then + Unit::Millisecond => { + // a. Set microseconds to floor(nanoseconds / 1000). + // b. Set nanoseconds to nanoseconds modulo 1000. + (microseconds, nanoseconds) = nanoseconds.div_rem_euclid(&1_000); + + // c. Set milliseconds to floor(microseconds / 1000). + // d. Set microseconds to microseconds modulo 1000. + (milliseconds, microseconds) = microseconds.div_rem_euclid(&1_000); + } + // 9. Else if largestUnit is "microsecond", then + Unit::Microsecond => { + // a. Set microseconds to floor(nanoseconds / 1000). + // b. Set nanoseconds to nanoseconds modulo 1000. + (microseconds, nanoseconds) = nanoseconds.div_rem_euclid(&1_000); + } + // 10. Else, + // a. Assert: largestUnit is "nanosecond". + _ => temporal_assert!(largest_unit == Unit::Nanosecond), + } + // 11. NOTE: When largestUnit is millisecond, microsecond, or nanosecond, milliseconds, microseconds, or + // nanoseconds may be an unsafe integer. In this case, care must be taken when implementing the calculation using + // floating point arithmetic. It can be implemented in C++ using std::fma(). String manipulation will also give an + // exact result, since the multiplication is by a power of 10. + // 12. Return ? CreateTemporalDuration(internalDuration.[[Date]].[[Years]], internalDuration.[[Date]].[[Months]], + // internalDuration.[[Date]].[[Weeks]], internalDuration.[[Date]].[[Days]] + days × sign, hours × sign, minutes × sign, + // seconds × sign, milliseconds × sign, microseconds × sign, nanoseconds × sign). + Duration::new( duration_record.date().years, duration_record.date().months, duration_record.date().weeks, - duration_record - .date() - .days - .checked_add(overflow_day) - .ok_or(TemporalError::range())?, - time.hours, - time.minutes, - time.seconds, - time.milliseconds, - time.microseconds, - time.nanoseconds, + duration_record.date().days + days as i64 * sign as i64, + hours as i64 * sign as i64, + minutes as i64 * sign as i64, + seconds as i64 * sign as i64, + milliseconds as i64 * sign as i64, + microseconds * sign as i128, + nanoseconds * sign as i128, ) } + /// Returns this `Duration` as a `NormalizedTimeDuration`. + #[inline] + pub(crate) fn to_normalized(self) -> NormalizedTimeDuration { + NormalizedTimeDuration::from_duration(&self) + } + /// Returns the a `Vec` of the fields values. #[inline] #[must_use] @@ -405,13 +556,6 @@ impl Duration { ] } - /// Returns whether `Duration`'s `DateDuration` is empty and is therefore a `TimeDuration`. - #[inline] - #[must_use] - pub(crate) fn is_time_duration(&self) -> bool { - self.date().fields().iter().all(|x| x == &0) - } - /// Returns the `Unit` corresponding to the largest non-zero field. #[inline] pub(crate) fn default_largest_unit(&self) -> Unit { @@ -423,6 +567,24 @@ impl Duration { .unwrap_or(Unit::Nanosecond) } + // 7.5.5 ToInternalDurationRecord ( duration ) + pub(crate) fn to_internal_duration_record(self) -> NormalizedDurationRecord { + // 1. Let dateDuration be ! CreateDateDurationRecord(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]]). + let date_duration = + DateDuration::new_unchecked(self.years(), self.months(), self.weeks(), self.days()); + // 2. Let timeDuration be TimeDurationFromComponents(duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]]). + let time_duration = NormalizedTimeDuration::from_components( + self.hours(), + self.minutes(), + self.seconds(), + self.milliseconds(), + self.microseconds(), + self.nanoseconds(), + ); + // 3. Return CombineDateAndTimeDuration(dateDuration, timeDuration). + NormalizedDurationRecord::combine(date_duration, time_duration) + } + /// Equivalent of [`7.5.7 ToDateDurationRecordWithoutTime ( duration )`][spec] /// /// [spec]: @@ -456,17 +618,6 @@ impl Duration { microseconds: i128, nanoseconds: i128, ) -> TemporalResult { - let duration = Self::new_unchecked( - DateDuration::new_unchecked(years, months, weeks, days), - TimeDuration::new_unchecked( - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, - ), - ); if !is_valid_duration( years, months, @@ -481,17 +632,48 @@ impl Duration { ) { return Err(TemporalError::range().with_message("Duration was not valid.")); } - Ok(duration) - } - - /// Creates a `Duration` from a provided a day and a `TimeDuration`. - /// - /// Note: `TimeDuration` records can store a day value to deal with overflow. - pub(crate) fn try_from_day_and_time(day: i64, time: &TimeDuration) -> TemporalResult { - Self::try_new_from_durations(DateDuration::new_unchecked(0, 0, 0, day), *time) + let sign = duration_sign(&[ + years, + months, + weeks, + days, + hours, + minutes, + seconds, + milliseconds, + microseconds as i64, + nanoseconds as i64, + ]); + Ok(Duration::new_unchecked( + sign, + years.saturating_abs() as u32, + months.saturating_abs() as u32, + weeks.saturating_abs() as u32, + days.unsigned_abs(), + hours.unsigned_abs(), + minutes.unsigned_abs(), + seconds.unsigned_abs(), + milliseconds.unsigned_abs(), + microseconds.unsigned_abs(), + nanoseconds.unsigned_abs(), + )) } /// Creates a `Duration` from a provided `PartialDuration`. + /// + /// ## Examples + /// + /// ```rust + /// use temporal_rs::{partial::PartialDuration, Duration}; + /// + /// let duration = Duration::from_partial_duration(PartialDuration { + /// seconds: Some(4), + /// ..Default::default() + /// }).unwrap(); + /// + /// assert_eq!(duration.seconds(), 4); + /// assert_eq!(duration.to_string(), "PT4S"); + /// ``` pub fn from_partial_duration(partial: PartialDuration) -> TemporalResult { if partial == PartialDuration::default() { return Err(TemporalError::r#type() @@ -631,7 +813,12 @@ impl Duration { #[inline] #[must_use] pub fn is_time_within_range(&self) -> bool { - self.time.is_within_range() + self.hours < 24 + && self.minutes < 60 + && self.seconds < 60 + && self.milliseconds < 1000 + && self.microseconds < 1000 + && self.nanoseconds < 1000 } #[inline] @@ -641,7 +828,7 @@ impl Duration { relative_to: Option, provider: &impl TimeZoneProvider, ) -> TemporalResult { - if self.date == other.date && self.time == other.time { + if self == other { return Ok(Ordering::Equal); } // 8. Let largestUnit1 be DefaultTemporalLargestUnit(one). @@ -649,7 +836,9 @@ impl Duration { let largest_unit_1 = self.default_largest_unit(); let largest_unit_2 = other.default_largest_unit(); // 10. Let duration1 be ToInternalDurationRecord(one). + let duration_one = self.to_internal_duration_record(); // 11. Let duration2 be ToInternalDurationRecord(two). + let duration_two = other.to_internal_duration_record(); // 12. If zonedRelativeTo is not undefined, and either UnitCategory(largestUnit1) or UnitCategory(largestUnit2) is date, then if let Some(RelativeTo::ZonedDateTime(zdt)) = relative_to.as_ref() { if largest_unit_1.is_date_unit() || largest_unit_2.is_date_unit() { @@ -657,8 +846,10 @@ impl Duration { // b. Let calendar be zonedRelativeTo.[[Calendar]]. // c. Let after1 be ? AddZonedDateTime(zonedRelativeTo.[[EpochNanoseconds]], timeZone, calendar, duration1, constrain). // d. Let after2 be ? AddZonedDateTime(zonedRelativeTo.[[EpochNanoseconds]], timeZone, calendar, duration2, constrain). - let after1 = zdt.add_as_instant(self, ArithmeticOverflow::Constrain, provider)?; - let after2 = zdt.add_as_instant(other, ArithmeticOverflow::Constrain, provider)?; + let after1 = + zdt.add_zoned_date_time(duration_one, ArithmeticOverflow::Constrain, provider)?; + let after2 = + zdt.add_zoned_date_time(duration_two, ArithmeticOverflow::Constrain, provider)?; // e. If after1 > after2, return 1𝔽. // f. If after1 < after2, return -1𝔽. // g. Return +0𝔽. @@ -674,16 +865,16 @@ impl Duration { let Some(RelativeTo::PlainDate(pdt)) = relative_to.as_ref() else { return Err(TemporalError::range()); }; - let days1 = self.date.days(pdt)?; - let days2 = other.date.days(pdt)?; + let days1 = self.date().days(pdt)?; + let days2 = other.date().days(pdt)?; (days1, days2) } else { - (self.date.days, other.date.days) + (self.date().days, other.date().days) }; // 15. Let timeDuration1 be ? Add24HourDaysToTimeDuration(duration1.[[Time]], days1). - let time_duration_1 = self.time.to_normalized().add_days(days1)?; + let time_duration_1 = self.to_normalized().add_days(days1)?; // 16. Let timeDuration2 be ? Add24HourDaysToTimeDuration(duration2.[[Time]], days2). - let time_duration_2 = other.time.to_normalized().add_days(days2)?; + let time_duration_2 = other.to_normalized().add_days(days2)?; // 17. Return 𝔽(CompareTimeDuration(timeDuration1, timeDuration2)). Ok(time_duration_1.cmp(&time_duration_2)) } @@ -692,88 +883,81 @@ impl Duration { // ==== Public `Duration` Getters/Setters ==== impl Duration { - /// Returns a reference to the inner `TimeDuration` - #[inline] - #[must_use] - pub fn time(&self) -> &TimeDuration { - &self.time - } - /// Returns a reference to the inner `DateDuration` #[inline] #[must_use] - pub fn date(&self) -> &DateDuration { - &self.date + pub fn date(&self) -> DateDuration { + DateDuration::from(self) } /// Returns the `years` field of duration. #[inline] #[must_use] pub const fn years(&self) -> i64 { - self.date.years + self.years as i64 * self.sign.as_sign_multiplier() as i64 } /// Returns the `months` field of duration. #[inline] #[must_use] pub const fn months(&self) -> i64 { - self.date.months + self.months as i64 * self.sign.as_sign_multiplier() as i64 } /// Returns the `weeks` field of duration. #[inline] #[must_use] pub const fn weeks(&self) -> i64 { - self.date.weeks + self.weeks as i64 * self.sign.as_sign_multiplier() as i64 } /// Returns the `days` field of duration. #[inline] #[must_use] pub const fn days(&self) -> i64 { - self.date.days + self.days as i64 * self.sign.as_sign_multiplier() as i64 } /// Returns the `hours` field of duration. #[inline] #[must_use] pub const fn hours(&self) -> i64 { - self.time.hours + self.hours as i64 * self.sign.as_sign_multiplier() as i64 } /// Returns the `hours` field of duration. #[inline] #[must_use] pub const fn minutes(&self) -> i64 { - self.time.minutes + self.minutes as i64 * self.sign.as_sign_multiplier() as i64 } /// Returns the `seconds` field of duration. #[inline] #[must_use] pub const fn seconds(&self) -> i64 { - self.time.seconds + self.seconds as i64 * self.sign.as_sign_multiplier() as i64 } /// Returns the `hours` field of duration. #[inline] #[must_use] pub const fn milliseconds(&self) -> i64 { - self.time.milliseconds + self.milliseconds as i64 * self.sign.as_sign_multiplier() as i64 } /// Returns the `microseconds` field of duration. #[inline] #[must_use] pub const fn microseconds(&self) -> i128 { - self.time.microseconds + self.microseconds as i128 * self.sign.as_sign_multiplier() as i128 } /// Returns the `nanoseconds` field of duration. #[inline] #[must_use] pub const fn nanoseconds(&self) -> i128 { - self.time.nanoseconds + self.nanoseconds as i128 * self.sign.as_sign_multiplier() as i128 } } @@ -784,7 +968,7 @@ impl Duration { #[inline] #[must_use] pub fn sign(&self) -> Sign { - duration_sign(&self.fields_signum()) + self.sign } /// Returns whether the current `Duration` is zero. @@ -801,8 +985,8 @@ impl Duration { #[must_use] pub fn negated(&self) -> Self { Self { - date: self.date().negated(), - time: self.time().negated(), + sign: self.sign.negate(), + ..*self } } @@ -811,52 +995,43 @@ impl Duration { #[must_use] pub fn abs(&self) -> Self { Self { - date: self.date().abs(), - time: self.time().abs(), + sign: if self.sign == Sign::Zero { + Sign::Zero + } else { + Sign::Positive + }, + ..*self } } /// Returns the result of adding a `Duration` to the current `Duration` #[inline] pub fn add(&self, other: &Self) -> TemporalResult { - // NOTE: Implemented from AddDurations - // Steps 1-22 are functionally useless in this context. - - // 23. Let largestUnit1 be DefaultTemporalLargestUnit(y1, mon1, w1, d1, h1, min1, s1, ms1, mus1). - let largest_one = self.default_largest_unit(); - // 24. Let largestUnit2 be DefaultTemporalLargestUnit(y2, mon2, w2, d2, h2, min2, s2, ms2, mus2). - let largest_two = other.default_largest_unit(); - // 25. Let largestUnit be LargerOfTwoUnits(largestUnit1, largestUnit2). - let largest_unit = largest_one.max(largest_two); - // 26. Let norm1 be NormalizeTimeDuration(h1, min1, s1, ms1, mus1, ns1). - let norm_one = NormalizedTimeDuration::from_time_duration(self.time()); - // 27. Let norm2 be NormalizeTimeDuration(h2, min2, s2, ms2, mus2, ns2). - let norm_two = NormalizedTimeDuration::from_time_duration(other.time()); - - // 28. If IsCalendarUnit(largestUnit), throw a RangeError exception. + // 1. Set other to ? ToTemporalDuration(other). + // 2. If operation is subtract, set other to CreateNegatedTemporalDuration(other). + // 3. Let largestUnit1 be DefaultTemporalLargestUnit(duration). + let largest_unit_one = self.default_largest_unit(); + // 4. Let largestUnit2 be DefaultTemporalLargestUnit(other). + let largest_unit_two = other.default_largest_unit(); + // 5. Let largestUnit be LargerOfTwoTemporalUnits(largestUnit1, largestUnit2). + let largest_unit = largest_unit_one.max(largest_unit_two); + // 6. If IsCalendarUnit(largestUnit) is true, throw a RangeError exception. if largest_unit.is_calendar_unit() { return Err(TemporalError::range().with_message( "Largest unit cannot be a calendar unit when adding two durations.", )); } - // NOTE: for lines 488-489 - // - // Maximum amount of days in a valid duration: 104_249_991_374 * 2 < i64::MAX - // 29. Let normResult be ? AddNormalizedTimeDuration(norm1, norm2). - // 30. Set normResult to ? Add24HourDaysToNormalizedTimeDuration(normResult, d1 + d2). - let result = (norm_one + norm_two)?.add_days( - self.days() - .checked_add(other.days()) - .ok_or(TemporalError::range())?, - )?; - - // 31. Let result be ? BalanceTimeDuration(normResult, largestUnit). - let (result_days, result_time) = TimeDuration::from_normalized(result, largest_unit)?; - - // 32. Return ! CreateTemporalDuration(0, 0, 0, result.[[Days]], result.[[Hours]], result.[[Minutes]], - // result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]). - Duration::try_from_day_and_time(result_days, &result_time) + // 7. Let d1 be ToInternalDurationRecordWith24HourDays(duration). + let d1 = NormalizedDurationRecord::from_duration_with_24_hour_days(self)?; + // 8. Let d2 be ToInternalDurationRecordWith24HourDays(other). + let d2 = NormalizedDurationRecord::from_duration_with_24_hour_days(other)?; + // 9. Let timeResult be ? AddTimeDuration(d1.[[Time]], d2.[[Time]]). + let time_result = (d1.normalized_time_duration() + d2.normalized_time_duration())?; + // 10. Let result be CombineDateAndTimeDuration(ZeroDateDuration(), timeResult). + let result = NormalizedDurationRecord::combine(DateDuration::default(), time_result); + // 11. Return ? TemporalDurationFromInternal(result, largestUnit). + Duration::from_internal(result, largest_unit) } /// Returns the result of subtracting a `Duration` from the current `Duration` @@ -973,6 +1148,7 @@ impl Duration { // 26. If zonedRelativeTo is not undefined, then Some(RelativeTo::ZonedDateTime(zoned_relative_to)) => { // a. Let internalDuration be ToInternalDurationRecord(duration). + let internal_duration = self.to_internal_duration_record(); // b. Let timeZone be zonedRelativeTo.[[TimeZone]]. let time_zone = zoned_relative_to.timezone().clone(); @@ -984,8 +1160,8 @@ impl Duration { // let relative_epoch_ns = zoned_relative_to.epoch_nanoseconds(); // e. Let targetEpochNs be ? AddZonedDateTime(relativeEpochNs, timeZone, calendar, internalDuration, constrain). - let target_epoch_ns = zoned_relative_to.add_as_instant( - self, + let target_epoch_ns = zoned_relative_to.add_zoned_date_time( + internal_duration, ArithmeticOverflow::Constrain, provider, )?; @@ -1004,7 +1180,7 @@ impl Duration { } // h. Return ? TemporalDurationFromInternal(internalDuration, largestUnit). - return Duration::from_normalized(internal, largest_unit); + return Duration::from_internal(internal, largest_unit); } // 27. If plainRelativeTo is not undefined, then @@ -1049,7 +1225,7 @@ impl Duration { )?; // i. Return ? TemporalDurationFromInternal(internalDuration, largestUnit). - return Duration::from_normalized(internal_duration, resolved_options.largest_unit); + return Duration::from_internal(internal_duration, resolved_options.largest_unit); } None => {} } @@ -1097,7 +1273,7 @@ impl Duration { }; // 33. Return ? TemporalDurationFromInternal(internalDuration, largestUnit). - Duration::from_normalized(internal_duration, resolved_options.largest_unit) + Duration::from_internal(internal_duration, resolved_options.largest_unit) } /// Returns the total of the `Duration` @@ -1112,12 +1288,16 @@ impl Duration { // 11. If zonedRelativeTo is not undefined, then Some(RelativeTo::ZonedDateTime(zoned_datetime)) => { // a. Let internalDuration be ToInternalDurationRecord(duration). + let internal_duration = self.to_internal_duration_record(); // b. Let timeZone be zonedRelativeTo.[[TimeZone]]. // c. Let calendar be zonedRelativeTo.[[Calendar]]. // d. Let relativeEpochNs be zonedRelativeTo.[[EpochNanoseconds]]. // e. Let targetEpochNs be ? AddZonedDateTime(relativeEpochNs, timeZone, calendar, internalDuration, constrain). - let target_epcoh_ns = - zoned_datetime.add_as_instant(self, ArithmeticOverflow::Constrain, provider)?; + let target_epcoh_ns = zoned_datetime.add_zoned_date_time( + internal_duration, + ArithmeticOverflow::Constrain, + provider, + )?; // f. Let total be ? DifferenceZonedDateTimeWithTotal(relativeEpochNs, targetEpochNs, timeZone, calendar, unit). let total = zoned_datetime.diff_with_total( &ZonedDateTime::new_unchecked( @@ -1135,7 +1315,7 @@ impl Duration { // a. Let internalDuration be ToInternalDurationRecordWith24HourDays(duration). // b. Let targetTime be AddTime(MidnightTimeRecord(), internalDuration.[[Time]]). let (balanced_days, time) = - PlainTime::default().add_normalized_time_duration(self.time.to_normalized()); + PlainTime::default().add_normalized_time_duration(self.to_normalized()); // c. Let calendar be plainRelativeTo.[[Calendar]]. // d. Let dateDuration be ! AdjustDateDurationRecord(internalDuration.[[Date]], targetTime.[[Days]]). let date_duration = DateDuration::new( @@ -1200,23 +1380,24 @@ impl Duration { let rounding_options = ResolvedRoundingOptions::from_to_string_options(&resolved_options); - // 11. Let largestUnit be DefaultTemporalLargestUnit(duration). + // 12. Let largestUnit be DefaultTemporalLargestUnit(duration). let largest = self.default_largest_unit(); - // 12. Let internalDuration be ToInternalDurationRecord(duration). - let norm = NormalizedDurationRecord::new( - self.date, - NormalizedTimeDuration::from_time_duration(&self.time), - )?; - // 13. Let timeDuration be ? RoundTimeDuration(internalDuration.[[Time]], precision.[[Increment]], precision.[[Unit]], roundingMode). - let time = norm.normalized_time_duration().round(rounding_options)?; - // 14. Set internalDuration to CombineDateAndTimeDuration(internalDuration.[[Date]], timeDuration). - let norm = NormalizedDurationRecord::new(norm.date(), time)?; - // 15. Let roundedLargestUnit be LargerOfTwoUnits(largestUnit, second). - let rounded_largest = largest.max(Unit::Second); - // 16. Let roundedDuration be ? TemporalDurationFromInternal(internalDuration, roundedLargestUnit). - let rounded = Self::from_normalized(norm, rounded_largest)?; - - // 17. Return TemporalDurationToString(roundedDuration, precision.[[Precision]]). + // 13. Let internalDuration be ToInternalDurationRecord(duration). + let internal_duration = self.to_internal_duration_record(); + // 14. Let timeDuration be ? RoundTimeDuration(internalDuration.[[Time]], precision.[[Increment]], precision.[[Unit]], roundingMode). + let time_duration = internal_duration + .normalized_time_duration() + .round(rounding_options)?; + // 15. Set internalDuration to CombineDateAndTimeDuration(internalDuration.[[Date]], timeDuration). + let internal_duration = + NormalizedDurationRecord::combine(internal_duration.date(), time_duration); + // 16. Let roundedLargestUnit be LargerOfTwoTemporalUnits(largestUnit, second). + let rounded_largest_unit = largest.max(Unit::Second); + + // 17. Let roundedDuration be ? TemporalDurationFromInternal(internalDuration, roundedLargestUnit). + let rounded = Self::from_internal(internal_duration, rounded_largest_unit)?; + + // 18. Return TemporalDurationToString(roundedDuration, precision.[[Precision]]). Ok(duration_to_formattable(&rounded, resolved_options.precision)?.to_string()) } } @@ -1242,14 +1423,14 @@ pub fn duration_to_formattable( let hours = duration.hours().abs(); let minutes = duration.minutes().abs(); - let time = NormalizedTimeDuration::from_time_duration(&TimeDuration::new_unchecked( + let time = NormalizedTimeDuration::from_components( 0, 0, duration.seconds(), duration.milliseconds(), duration.microseconds(), duration.nanoseconds(), - )); + ); let seconds = time.seconds().unsigned_abs(); let subseconds = time.subseconds().unsigned_abs(); @@ -1377,7 +1558,7 @@ pub(crate) fn is_valid_duration( /// Equivalent: 7.5.10 `DurationSign ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds )` #[inline] #[must_use] -fn duration_sign(set: &[i64]) -> Sign { +pub(crate) fn duration_sign(set: &[i64]) -> 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. @@ -1392,20 +1573,15 @@ fn duration_sign(set: &[i64]) -> Sign { Sign::Zero } -impl From for Duration { - fn from(value: TimeDuration) -> Self { - Self { - time: value, - date: DateDuration::default(), - } - } -} - impl From for Duration { fn from(value: DateDuration) -> Self { Self { - date: value, - time: TimeDuration::default(), + sign: value.sign(), + years: value.years.unsigned_abs() as u32, + months: value.months.unsigned_abs() as u32, + weeks: value.weeks.unsigned_abs() as u32, + days: value.days.unsigned_abs(), + ..Default::default() } } } diff --git a/src/builtins/core/duration/date.rs b/src/builtins/core/duration/date.rs index b50271ac5..b07a69ac1 100644 --- a/src/builtins/core/duration/date.rs +++ b/src/builtins/core/duration/date.rs @@ -38,12 +38,37 @@ impl DateDuration { days, } } +} - /// Returns the iterator for `DateDuration` +impl From for DateDuration { + /// Converts a `Duration` into a `DateDuration`. + /// + /// This conversion is lossy, as `Duration` can represent time durations + /// that are not strictly date durations. #[inline] - #[must_use] - pub(crate) fn fields(&self) -> [i64; 4] { - [self.years, self.months, self.weeks, self.days] + fn from(duration: Duration) -> Self { + Self::new_unchecked( + duration.years(), + duration.months(), + duration.weeks(), + duration.days(), + ) + } +} + +impl From<&Duration> for DateDuration { + /// Converts a `Duration` into a `DateDuration`. + /// + /// This conversion is lossy, as `Duration` can represent time durations + /// that are not strictly date durations. + #[inline] + fn from(duration: &Duration) -> Self { + Self::new_unchecked( + duration.years(), + duration.months(), + duration.weeks(), + duration.days(), + ) } } @@ -94,7 +119,7 @@ impl DateDuration { #[inline] #[must_use] pub fn sign(&self) -> Sign { - duration_sign(self.fields().as_slice()) + duration_sign(&[self.years, self.months, self.weeks, self.days]) } /// DateDurationDays @@ -106,12 +131,10 @@ impl DateDuration { return Ok(self.days); } // 3. Let later be ? CalendarDateAdd(plainRelativeTo.[[Calendar]], plainRelativeTo.[[ISODate]], yearsMonthsWeeksDuration, constrain). - let later = relative_to.add( - &Duration { - date: *self, - time: Default::default(), - }, - Some(ArithmeticOverflow::Constrain), + let later = relative_to.calendar().date_add( + &relative_to.iso, + &ymw_duration, + ArithmeticOverflow::Constrain, )?; // 4. Let epochDays1 be ISODateToEpochDays(plainRelativeTo.[[ISODate]].[[Year]], plainRelativeTo.[[ISODate]].[[Month]] - 1, plainRelativeTo.[[ISODate]].[[Day]]). let epoch_days_1 = iso_date_to_epoch_days( diff --git a/src/builtins/core/duration/normalized.rs b/src/builtins/core/duration/normalized.rs index ced92b23b..44cdbe4b6 100644 --- a/src/builtins/core/duration/normalized.rs +++ b/src/builtins/core/duration/normalized.rs @@ -17,7 +17,7 @@ use crate::{ Calendar, TemporalError, TemporalResult, TemporalUnwrap, NS_PER_DAY, NS_PER_DAY_NONZERO, }; -use super::{DateDuration, Duration, Sign, TimeDuration}; +use super::{DateDuration, Duration, Sign}; const MAX_TIME_DURATION: i128 = 9_007_199_254_740_991_999_999_999; @@ -40,15 +40,40 @@ const NANOSECONDS_PER_HOUR: i128 = 60 * NANOSECONDS_PER_MINUTE; pub(crate) struct NormalizedTimeDuration(pub(crate) i128); impl NormalizedTimeDuration { + /// Creates a `NormalizedTimeDuration` from signed integer components. + /// This method preserves the sign of each component during the calculation. + pub(crate) fn from_components( + hours: i64, + minutes: i64, + seconds: i64, + milliseconds: i64, + microseconds: i128, + nanoseconds: i128, + ) -> Self { + let mut total_nanoseconds: i128 = 0; + + total_nanoseconds += i128::from(hours) * NANOSECONDS_PER_HOUR; + total_nanoseconds += i128::from(minutes) * NANOSECONDS_PER_MINUTE; + total_nanoseconds += i128::from(seconds) * 1_000_000_000; + total_nanoseconds += i128::from(milliseconds) * 1_000_000; + total_nanoseconds += microseconds * 1_000; + total_nanoseconds += nanoseconds; + + debug_assert!(total_nanoseconds.abs() <= MAX_TIME_DURATION); + Self(total_nanoseconds) + } + /// Equivalent: 7.5.20 NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds, nanoseconds ) - pub(crate) fn from_time_duration(time: &TimeDuration) -> Self { + pub(crate) fn from_duration(duration: &Duration) -> Self { // Note: Calculations must be done after casting to `i128` in order to preserve precision - let mut nanoseconds: i128 = time.hours as i128 * NANOSECONDS_PER_HOUR; - nanoseconds += time.minutes as i128 * NANOSECONDS_PER_MINUTE; - nanoseconds += time.seconds as i128 * 1_000_000_000; - nanoseconds += time.milliseconds as i128 * 1_000_000; - nanoseconds += time.microseconds * 1_000; - nanoseconds += time.nanoseconds; + let sign_multiplier = duration.sign().as_sign_multiplier() as i128; + let mut nanoseconds: i128 = + i128::from(duration.hours) * NANOSECONDS_PER_HOUR * sign_multiplier; + nanoseconds += i128::from(duration.minutes) * NANOSECONDS_PER_MINUTE * sign_multiplier; + nanoseconds += i128::from(duration.seconds) * 1_000_000_000 * sign_multiplier; + nanoseconds += i128::from(duration.milliseconds) * 1_000_000 * sign_multiplier; + nanoseconds += duration.microseconds as i128 * 1_000 * sign_multiplier; + nanoseconds += duration.nanoseconds as i128 * sign_multiplier; // NOTE(nekevss): Is it worth returning a `RangeError` below. debug_assert!(nanoseconds.abs() <= MAX_TIME_DURATION); Self(nanoseconds) @@ -70,7 +95,7 @@ impl NormalizedTimeDuration { let result = self.0 + i128::from(days) * i128::from(NS_PER_DAY); if result.abs() > MAX_TIME_DURATION { return Err(TemporalError::range() - .with_message("normalizedTimeDuration exceeds maxTimeDuration.")); + .with_message("SubtractNormalizedTimeDuration exceeded a valid Duration range.")); } Ok(Self(result)) } @@ -82,12 +107,6 @@ impl NormalizedTimeDuration { self.0 / i128::from(divisor) } - // 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)) - } - /// Equivalent: 7.5.31 NormalizedTimeDurationSign ( d ) #[inline] #[must_use] @@ -242,6 +261,14 @@ pub struct NormalizedDurationRecord { } impl NormalizedDurationRecord { + pub(crate) fn combine(date: DateDuration, norm: NormalizedTimeDuration) -> Self { + // 1. Let dateSign be DateDurationSign(dateDuration). + // 2. Let timeSign be TimeDurationSign(timeDuration). + // 3. Assert: If dateSign ≠ 0 and timeSign ≠ 0, dateSign = timeSign. + // 4. Return Internal Duration Record { [[Date]]: dateDuration, [[Time]]: timeDuration }. + Self { date, norm } + } + /// Creates a new `NormalizedDurationRecord`. /// /// Equivalent: `CreateNormalizedDurationRecord` & `CombineDateAndNormalizedTimeDuration`. @@ -260,7 +287,7 @@ impl NormalizedDurationRecord { pub(crate) fn from_duration_with_24_hour_days(duration: &Duration) -> TemporalResult { // 1. Let timeDuration be TimeDurationFromComponents(duration.[[Hours]], duration.[[Minutes]], // duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]]). - let normalized_time = NormalizedTimeDuration::from_time_duration(&duration.time); + let normalized_time = NormalizedTimeDuration::from_duration(duration); // 2. Set timeDuration to ! Add24HourDaysToTimeDuration(timeDuration, duration.[[Days]]). let normalized_time = normalized_time.add_days(duration.days())?; // 3. Let dateDuration be ! CreateDateDurationRecord(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], 0). @@ -280,16 +307,18 @@ impl NormalizedDurationRecord { // 1. Let internalDuration be ToInternalDurationRecordWith24HourDays(duration). let internal_duration = self; + // NOTE: days SHOULD be in range of an i64. + // MAX_TIME_DURATION / NS_PER_DAY <= i64::MAX // 2. Let days be truncate(internalDuration.[[Time]] / nsPerDay). - let days = internal_duration.normalized_time_duration().0 / i128::from(NS_PER_DAY); + let days = (internal_duration.normalized_time_duration().0 / i128::from(NS_PER_DAY)) as i64; // 3. Return ! CreateDateDurationRecord(internalDuration.[[Date]].[[Years]], internalDuration.[[Date]].[[Months]], internalDuration.[[Date]].[[Weeks]], days). - Ok(DateDuration::new_unchecked( + DateDuration::new( internal_duration.date().years, internal_duration.date().months, internal_duration.date().weeks, - days.try_into().ok().temporal_unwrap()?, - )) + days, + ) } pub(crate) fn from_date_duration(date: DateDuration) -> TemporalResult { @@ -527,29 +556,20 @@ impl NormalizedDurationRecord { _ => 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, - )?; - - // NOTE: 7-8 are inversed - // 8. Else, + // 7. Let start be ? CalendarDateAdd(calendar, isoDateTime.[[ISODate]], startDuration, constrain). + let start = + dt.calendar() + .date_add(&dt.iso.date, &start_duration, ArithmeticOverflow::Constrain)?; + // 8. Let end be ? CalendarDateAdd(calendar, isoDateTime.[[ISODate]], endDuration, constrain). + let end = + dt.calendar() + .date_add(&dt.iso.date, &end_duration, ArithmeticOverflow::Constrain)?; + // 9. Let startDateTime be CombineISODateAndTimeRecord(start, isoDateTime.[[Time]]). + let start = IsoDateTime::new_unchecked(start.iso, dt.iso.time); + // 10. Let endDateTime be CombineISODateAndTimeRecord(end, isoDateTime.[[Time]]). + let end = IsoDateTime::new_unchecked(end.iso, dt.iso.time); + + // 12. Else, let (start_epoch_ns, end_epoch_ns) = if let Some((tz, provider)) = tz { // a. Let startEpochNs be ? GetEpochNanosecondsFor(timeZone, startDateTime, compatible). // b. Let endEpochNs be ? GetEpochNanosecondsFor(timeZone, endDateTime, compatible). @@ -558,39 +578,31 @@ impl NormalizedDurationRecord { let end_epoch_ns = tz.get_epoch_nanoseconds_for(end, Disambiguation::Compatible, provider)?; (start_epoch_ns, end_epoch_ns) - // 7. If timeZoneRec is unset, then + // 11. If timeZoneRec is unset, then } else { // 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(), end.as_nanoseconds()) }; - // 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). + // TODO: look into handling asserts + // 13. If sign is 1, then + // a. Assert: startEpochNs ≤ destEpochNs ≤ endEpochNs. + // 14. Else, + // a. Assert: endEpochNs ≤ destEpochNs ≤ startEpochNs. + // 15. Assert: startEpochNs ≠ endEpochNs. + // TODO: Don't use f64 below ... // 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. + // 16. Let progress be (destEpochNs - startEpochNs) / (endEpochNs - startEpochNs). + // 17. Let total be r1 + progress × increment × sign. let progress = (dest_epoch_ns - start_epoch_ns.0) as f64 / (end_epoch_ns.0 - start_epoch_ns.0) 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. @@ -728,70 +740,61 @@ impl NormalizedDurationRecord { 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_())?; - - // 3. Let unitLength be the value in the "Length in Nanoseconds" column of the row of Table 22 whose "Singular" column contains smallestUnit. + // 1. Let timeDuration be ! Add24HourDaysToTimeDuration(duration.[[Time]], duration.[[Date]].[[Days]]). + let time_duration = self.normalized_time_duration().add_days(self.date().days)?; + // 2. Let unitLength be the value in the "Length in Nanoseconds" column of the row of Table 21 whose "Value" 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.get() as i64); - - // 5. Let roundedNorm be ? RoundNormalizedTimeDurationToIncrement(norm, unitLength × increment, roundingMode). - let rounded_norm = norm.round_inner( + // 3. Let roundedTime be ? RoundTimeDurationToIncrement(timeDuration, unitLength × increment, roundingMode). + let rounded_time = time_duration.round_inner( unit_length .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)?; + // 4. Let diffTime be ! AddTimeDuration(roundedTime, -timeDuration). + let diff_time = rounded_time.checked_sub(&time_duration)?; - // 7. Let wholeDays be truncate(DivideNormalizedTimeDuration(norm, nsPerDay)). - let whole_days = norm.divide(NS_PER_DAY as i64); + // 5. Let wholeDays be truncate(TotalTimeDuration(timeDuration, day)). + let whole_days = time_duration.divide(NS_PER_DAY as i64) 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. + // 6. Let roundedWholeDays be truncate(TotalTimeDuration(roundedTime, day)). + let rounded_whole_days = rounded_time.divide(NS_PER_DAY as i64) as i64; + // 7. 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; + // 8. If dayDelta < 0, let dayDeltaSign be -1; else if dayDelta > 0, let dayDeltaSign be 1; else let dayDeltaSign be 0. + // 9. If dayDeltaSign = TimeDurationSign(timeDuration), let didExpandDays be true; else let didExpandDays be false. + let did_expand_days = delta.signum() as i8 == time_duration.sign() as i8; + // 10. Let nudgedEpochNs be AddTimeDurationToEpochNanoseconds(diffTime, destEpochNs). + let nudged_ns = diff_time.0 + dest_epoch_ns; - // 14. Let days be 0. + // 11. Let days be 0. let mut days = 0; - // 15. Let remainder be roundedNorm. - let mut remainder = rounded_norm; - // 16. If LargerOfTwoUnits(largestUnit, "day") is largestUnit, then - if options.largest_unit.max(Unit::Day) == options.largest_unit { + // 12. Let remainder be roundedTime. + let mut remainder = rounded_time; + // 13. If TemporalUnitCategory(largestUnit) is date, then + if options.largest_unit.is_date_unit() { // a. Set days to roundedWholeDays. days = rounded_whole_days; - // b. Set remainder to remainder(roundedFractionalDays, 1) × nsPerDay. - remainder = NormalizedTimeDuration(rounded_remainder); + // b. Set remainder to ! AddTimeDuration(roundedTime, TimeDurationFromComponents(-roundedWholeDays * HoursPerDay, 0, 0, 0, 0, 0)). + remainder = rounded_time.add(NormalizedTimeDuration::from_components( + -rounded_whole_days * 24, + 0, + 0, + 0, + 0, + 0, + ))?; } - // 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 i64, - )?, - remainder, - )?; - // 18. Return Duration Nudge Result Record { [[Duration]]: resultDuration, [[Total]]: total, - // [[NudgedEpochNs]]: nudgedEpochNs, [[DidExpandCalendarUnit]]: didExpandDays }. + + // 14. Let dateDuration be ! AdjustDateDurationRecord(duration.[[Date]], days). + let date_duration = self.date().adjust(days, None, None)?; + // 15. Let resultDuration be CombineDateAndTimeDuration(dateDuration, remainder). + let result_duration = Self::combine(date_duration, remainder); + // 16. Return Duration Nudge Result Record { [[Duration]]: resultDuration, [[NudgedEpochNs]]: nudgedEpochNs, [[DidExpandCalendarUnit]]: didExpandDays }. Ok(NudgeRecord { normalized: result_duration, - total: Some(FiniteF64::try_from(total)?), + total: None, nudge_epoch_ns: nudged_ns, expanded: did_expand_days, }) diff --git a/src/builtins/core/duration/time.rs b/src/builtins/core/duration/time.rs deleted file mode 100644 index aa2790652..000000000 --- a/src/builtins/core/duration/time.rs +++ /dev/null @@ -1,322 +0,0 @@ -//! An implementation of `TimeDuration` and it's methods. - -use crate::{options::Unit, temporal_assert, Sign, TemporalError, TemporalResult}; - -use super::{duration_sign, is_valid_duration, normalized::NormalizedTimeDuration}; - -use num_traits::Euclid; - -/// `TimeDuration` represents the [Time Duration record][spec] of the `Duration.` -/// -/// These fields are laid out in the [Temporal Proposal][field spec] as 64-bit floating point numbers. -/// -/// [spec]: https://tc39.es/proposal-temporal/#sec-temporal-time-duration-records -/// [field spec]: https://tc39.es/proposal-temporal/#sec-properties-of-temporal-duration-instances -#[non_exhaustive] -#[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)] -pub struct TimeDuration { - /// `TimeDuration`'s internal hour value. - pub hours: i64, - /// `TimeDuration`'s internal minute value. - pub minutes: i64, - /// `TimeDuration`'s internal second value. - pub seconds: i64, - /// `TimeDuration`'s internal millisecond value. - pub milliseconds: i64, - /// `TimeDuration`'s internal microsecond value. - pub microseconds: i128, - /// `TimeDuration`'s internal nanosecond value. - pub nanoseconds: i128, -} -// ==== TimeDuration Private API ==== - -impl TimeDuration { - /// Creates a new `TimeDuration`. - #[must_use] - pub(crate) const fn new_unchecked( - hours: i64, - minutes: i64, - seconds: i64, - milliseconds: i64, - microseconds: i128, - nanoseconds: i128, - ) -> Self { - Self { - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, - } - } - - /// Balances and creates `TimeDuration` from a `NormalizedTimeDuration`. This method will return - /// a tuple (f64, TimeDuration) where f64 is the overflow day value from balancing. - /// - /// Equivalent: `BalanceTimeDuration` - /// - /// # Errors: - /// - Will error if provided duration is invalid - pub(crate) fn from_normalized( - norm: NormalizedTimeDuration, - largest_unit: Unit, - ) -> TemporalResult<(i64, Self)> { - // 1. Let days, hours, minutes, seconds, milliseconds, and microseconds be 0. - let mut days = 0; - let mut hours = 0; - let mut minutes = 0; - let mut seconds = 0; - let mut milliseconds = 0; - let mut microseconds = 0; - - // 2. Let sign be NormalizedTimeDurationSign(norm). - let sign = i64::from(norm.sign() as i8); - // 3. Let nanoseconds be NormalizedTimeDurationAbs(norm).[[TotalNanoseconds]]. - let mut nanoseconds = norm.0.abs(); - - match largest_unit { - // 4. If largestUnit is "year", "month", "week", or "day", then - Unit::Year | Unit::Month | Unit::Week | Unit::Day => { - // a. Set microseconds to floor(nanoseconds / 1000). - // b. Set nanoseconds to nanoseconds modulo 1000. - (microseconds, nanoseconds) = nanoseconds.div_rem_euclid(&1_000); - - // c. Set milliseconds to floor(microseconds / 1000). - // d. Set microseconds to microseconds modulo 1000. - (milliseconds, microseconds) = microseconds.div_rem_euclid(&1_000); - - // e. Set seconds to floor(milliseconds / 1000). - // f. Set milliseconds to milliseconds modulo 1000. - (seconds, milliseconds) = milliseconds.div_rem_euclid(&1_000); - - // g. Set minutes to floor(seconds / 60). - // h. Set seconds to seconds modulo 60. - (minutes, seconds) = seconds.div_rem_euclid(&60); - - // i. Set hours to floor(minutes / 60). - // j. Set minutes to minutes modulo 60. - (hours, minutes) = minutes.div_rem_euclid(&60); - - // k. Set days to floor(hours / 24). - // l. Set hours to hours modulo 24. - (days, hours) = hours.div_rem_euclid(&24); - } - // 5. Else if largestUnit is "hour", then - Unit::Hour => { - // a. Set microseconds to floor(nanoseconds / 1000). - // b. Set nanoseconds to nanoseconds modulo 1000. - (microseconds, nanoseconds) = nanoseconds.div_rem_euclid(&1_000); - - // c. Set milliseconds to floor(microseconds / 1000). - // d. Set microseconds to microseconds modulo 1000. - (milliseconds, microseconds) = microseconds.div_rem_euclid(&1_000); - - // e. Set seconds to floor(milliseconds / 1000). - // f. Set milliseconds to milliseconds modulo 1000. - (seconds, milliseconds) = milliseconds.div_rem_euclid(&1_000); - - // g. Set minutes to floor(seconds / 60). - // h. Set seconds to seconds modulo 60. - (minutes, seconds) = seconds.div_rem_euclid(&60); - - // i. Set hours to floor(minutes / 60). - // j. Set minutes to minutes modulo 60. - (hours, minutes) = minutes.div_rem_euclid(&60); - } - // 6. Else if largestUnit is "minute", then - Unit::Minute => { - // a. Set microseconds to floor(nanoseconds / 1000). - // b. Set nanoseconds to nanoseconds modulo 1000. - (microseconds, nanoseconds) = nanoseconds.div_rem_euclid(&1_000); - - // c. Set milliseconds to floor(microseconds / 1000). - // d. Set microseconds to microseconds modulo 1000. - (milliseconds, microseconds) = microseconds.div_rem_euclid(&1_000); - - // e. Set seconds to floor(milliseconds / 1000). - // f. Set milliseconds to milliseconds modulo 1000. - (seconds, milliseconds) = milliseconds.div_rem_euclid(&1_000); - - // g. Set minutes to floor(seconds / 60). - // h. Set seconds to seconds modulo 60. - (minutes, seconds) = seconds.div_rem_euclid(&60); - } - // 7. Else if largestUnit is "second", then - Unit::Second => { - // a. Set microseconds to floor(nanoseconds / 1000). - // b. Set nanoseconds to nanoseconds modulo 1000. - (microseconds, nanoseconds) = nanoseconds.div_rem_euclid(&1_000); - - // c. Set milliseconds to floor(microseconds / 1000). - // d. Set microseconds to microseconds modulo 1000. - (milliseconds, microseconds) = microseconds.div_rem_euclid(&1_000); - - // e. Set seconds to floor(milliseconds / 1000). - // f. Set milliseconds to milliseconds modulo 1000. - (seconds, milliseconds) = milliseconds.div_rem_euclid(&1_000); - } - // 8. Else if largestUnit is "millisecond", then - Unit::Millisecond => { - // a. Set microseconds to floor(nanoseconds / 1000). - // b. Set nanoseconds to nanoseconds modulo 1000. - (microseconds, nanoseconds) = nanoseconds.div_rem_euclid(&1_000); - - // c. Set milliseconds to floor(microseconds / 1000). - // d. Set microseconds to microseconds modulo 1000. - (milliseconds, microseconds) = microseconds.div_rem_euclid(&1_000); - } - // 9. Else if largestUnit is "microsecond", then - Unit::Microsecond => { - // a. Set microseconds to floor(nanoseconds / 1000). - // b. Set nanoseconds to nanoseconds modulo 1000. - (microseconds, nanoseconds) = nanoseconds.div_rem_euclid(&1_000); - } - // 10. Else, - // a. Assert: largestUnit is "nanosecond". - _ => temporal_assert!(largest_unit == Unit::Nanosecond), - } - - // NOTE(nekevss): `mul_add` is essentially the Rust's implementation of `std::fma()`, so that's handy, but - // this should be tested much further. - // 11. NOTE: When largestUnit is "millisecond", "microsecond", or "nanosecond", milliseconds, microseconds, or - // nanoseconds may be an unsafe integer. In this case, care must be taken when implementing the calculation - // using floating point arithmetic. It can be implemented in C++ using std::fma(). String manipulation will also - // give an exact result, since the multiplication is by a power of 10. - - // NOTE: days may have the potentially to exceed i64 - // 12. Return ! CreateTimeDurationRecord(days × sign, hours × sign, minutes × sign, seconds × sign, milliseconds × sign, microseconds × sign, nanoseconds × sign). - let days = i64::try_from(days).map_err(|_| TemporalError::range())? * sign; - let result = Self::new_unchecked( - hours as i64 * sign, - minutes as i64 * sign, - seconds as i64 * sign, - milliseconds as i64 * sign, - microseconds * sign as i128, - nanoseconds * sign as i128, - ); - - if !is_valid_duration( - 0, - 0, - 0, - days, - result.hours, - result.minutes, - result.seconds, - result.milliseconds, - result.microseconds, - result.nanoseconds, - ) { - return Err(TemporalError::range().with_message("Invalid balance TimeDuration.")); - } - - // TODO: Remove cast below. - Ok((days, result)) - } - - /// Returns this `TimeDuration` as a `NormalizedTimeDuration`. - #[inline] - pub(crate) fn to_normalized(self) -> NormalizedTimeDuration { - NormalizedTimeDuration::from_time_duration(&self) - } - - /// Returns the value of `TimeDuration`'s fields. - #[inline] - #[must_use] - pub(crate) fn fields(&self) -> [i64; 6] { - [ - self.hours, - self.minutes, - self.seconds, - self.milliseconds, - self.microseconds.signum() as i64, - self.nanoseconds.signum() as i64, - ] - } -} - -// ==== TimeDuration's public API ==== - -impl TimeDuration { - /// Creates a new validated `TimeDuration`. - pub fn new( - hours: i64, - minutes: i64, - seconds: i64, - milliseconds: i64, - microseconds: i128, - nanoseconds: i128, - ) -> TemporalResult { - let result = Self::new_unchecked( - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, - ); - if !is_valid_duration( - 0, - 0, - 0, - 0, - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, - ) { - return Err( - TemporalError::range().with_message("Attempted to create an invalid TimeDuration.") - ); - } - Ok(result) - } - - /// Returns a new `TimeDuration` representing the absolute value of the current. - #[inline] - #[must_use] - pub fn abs(&self) -> Self { - Self { - hours: self.hours.abs(), - minutes: self.minutes.abs(), - seconds: self.seconds.abs(), - milliseconds: self.milliseconds.abs(), - microseconds: self.microseconds.abs(), - nanoseconds: self.nanoseconds.abs(), - } - } - - /// Returns a negated `TimeDuration`. - #[inline] - #[must_use] - pub fn negated(&self) -> Self { - Self { - hours: self.hours.saturating_neg(), - minutes: self.minutes.saturating_neg(), - seconds: self.seconds.saturating_neg(), - milliseconds: self.milliseconds.saturating_neg(), - microseconds: self.microseconds.saturating_neg(), - nanoseconds: self.nanoseconds.saturating_neg(), - } - } - - /// Utility function for returning if values in a valid range. - #[inline] - #[must_use] - pub fn is_within_range(&self) -> bool { - self.hours.abs() < 24 - && self.minutes.abs() < 60 - && self.seconds.abs() < 60 - && self.milliseconds.abs() < 1000 - && self.milliseconds.abs() < 1000 - && self.milliseconds.abs() < 1000 - } - - #[inline] - pub fn sign(&self) -> Sign { - duration_sign(&self.fields()) - } -} diff --git a/src/builtins/core/instant.rs b/src/builtins/core/instant.rs index 7f2187a13..ccc37d8e0 100644 --- a/src/builtins/core/instant.rs +++ b/src/builtins/core/instant.rs @@ -4,9 +4,8 @@ use alloc::string::String; use core::{num::NonZeroU128, str::FromStr}; use crate::{ - builtins::core::{ - duration::TimeDuration, zoneddatetime::nanoseconds_to_formattable_offset_minutes, Duration, - }, + builtins::core::{zoneddatetime::nanoseconds_to_formattable_offset_minutes, Duration}, + error::ErrorMessage, iso::IsoDateTime, options::{ DifferenceOperation, DifferenceSettings, DisplayOffset, ResolvedRoundingOptions, @@ -162,10 +161,9 @@ impl Instant { /// Adds a `TimeDuration` to the current `Instant`. /// /// Temporal-Proposal equivalent: `AddInstant`. - pub(crate) fn add_to_instant(&self, duration: &TimeDuration) -> TemporalResult { + pub(crate) fn add_to_instant(&self, duration: &NormalizedTimeDuration) -> TemporalResult { // 1. Let result be AddTimeDurationToEpochNanoseconds(timeDuration, epochNanoseconds). - let norm = NormalizedTimeDuration::from_time_duration(duration); - let result = self.epoch_nanoseconds().0 + norm.0; + let result = self.epoch_nanoseconds().0 + duration.0; let ns = EpochNanoseconds::from(result); // 2. If IsValidEpochNanoseconds(result) is false, throw a RangeError exception. ns.check_validity()?; @@ -173,6 +171,23 @@ impl Instant { Ok(Self::from(ns)) } + /// 8.5.10 AddDurationToInstant ( operation, instant, temporalDurationLike ) + pub(crate) fn add_duration_to_instant(&self, duration: &Duration) -> TemporalResult { + // 3. Let largestUnit be DefaultTemporalLargestUnit(duration). + let largest_unit = duration.default_largest_unit(); + // 4. If TemporalUnitCategory(largestUnit) is date, throw a RangeError exception. + if largest_unit.is_date_unit() { + // TODO: Add enum + return Err(TemporalError::range().with_enum(ErrorMessage::LargestUnitCannotBeDateUnit)); + } + // 5. Let internalDuration be ToInternalDurationRecordWith24HourDays(duration). + let internal_duration = + NormalizedDurationRecord::from_duration_with_24_hour_days(duration)?; + // 6. Let ns be ? AddInstant(instant.[[EpochNanoseconds]], internalDuration.[[Time]]). + // 7. Return ! CreateTemporalInstant(ns). + self.add_to_instant(&internal_duration.normalized_time_duration()) + } + /// `temporal_rs` equivalent of `DifferenceInstant` pub(crate) fn diff_instant_internal( &self, @@ -212,7 +227,7 @@ impl Instant { // settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[RoundingMode]]). let internal_record = self.diff_instant_internal(other, resolved_options)?; - let result = Duration::from_normalized(internal_record, resolved_options.largest_unit)?; + let result = Duration::from_internal(internal_record, resolved_options.largest_unit)?; // 6. Let norm be diffRecord.[[NormalizedTimeDuration]]. // 7. Let result be ! BalanceTimeDuration(norm, settings.[[LargestUnit]]). @@ -333,44 +348,24 @@ impl Instant { /// Adds a `Duration` to the current `Instant`, returning an error if the `Duration` /// contains a `DateDuration`. #[inline] - pub fn add(&self, duration: Duration) -> TemporalResult { - if !duration.is_time_duration() { - return Err(TemporalError::range() - .with_message("DateDuration values cannot be added to instant.")); - } - self.add_time_duration(duration.time()) - } - - /// Adds a `TimeDuration` to `Instant`. - #[inline] - pub fn add_time_duration(&self, duration: &TimeDuration) -> TemporalResult { - self.add_to_instant(duration) + pub fn add(&self, duration: &Duration) -> TemporalResult { + self.add_duration_to_instant(duration) } /// Subtract a `Duration` to the current `Instant`, returning an error if the `Duration` /// contains a `DateDuration`. #[inline] - pub fn subtract(&self, duration: Duration) -> TemporalResult { - if !duration.is_time_duration() { - return Err(TemporalError::range() - .with_message("DateDuration values cannot be added to instant.")); - } - self.subtract_time_duration(duration.time()) - } - - /// Subtracts a `TimeDuration` to `Instant`. - #[inline] - pub fn subtract_time_duration(&self, duration: &TimeDuration) -> TemporalResult { - self.add_to_instant(&duration.negated()) + pub fn subtract(&self, duration: &Duration) -> TemporalResult { + self.add_duration_to_instant(&duration.negated()) } - /// Returns a `TimeDuration` representing the duration since provided `Instant` + /// Returns a `Duration` representing the duration since provided `Instant` #[inline] pub fn since(&self, other: &Self, settings: DifferenceSettings) -> TemporalResult { self.diff_instant(DifferenceOperation::Since, other, settings) } - /// Returns a `TimeDuration` representing the duration until provided `Instant` + /// Returns a `Duration` representing the duration until provided `Instant` #[inline] pub fn until(&self, other: &Self, settings: DifferenceSettings) -> TemporalResult { self.diff_instant(DifferenceOperation::Until, other, settings) @@ -464,10 +459,10 @@ mod tests { use core::str::FromStr; use crate::{ - builtins::core::{duration::TimeDuration, Instant}, + builtins::{core::Instant, duration::duration_sign}, options::{DifferenceSettings, RoundingMode, Unit}, unix_time::EpochNanoseconds, - NS_MAX_INSTANT, NS_MIN_INSTANT, + Duration, NS_MAX_INSTANT, NS_MIN_INSTANT, }; #[test] @@ -568,20 +563,28 @@ mod tests { } }; - let assert_time_duration = - |td: &TimeDuration, expected: (i64, i64, i64, i64, i128, i128)| { - assert_eq!( - td, - &TimeDuration { - hours: expected.0, - minutes: expected.1, - seconds: expected.2, - milliseconds: expected.3, - microseconds: expected.4, - nanoseconds: expected.5, - } - ) - }; + let assert_duration = |td: Duration, expected: (i64, i64, i64, i64, i128, i128)| { + assert_eq!( + td, + Duration { + sign: duration_sign(&[ + expected.0, + expected.1, + expected.2, + expected.3, + expected.4 as i64, + expected.5 as i64 + ]), + hours: expected.0.unsigned_abs(), + minutes: expected.1.unsigned_abs(), + seconds: expected.2.unsigned_abs(), + milliseconds: expected.3.unsigned_abs(), + microseconds: expected.4.unsigned_abs(), + nanoseconds: expected.5.unsigned_abs(), + ..Default::default() + } + ) + }; let earlier = Instant::try_new( 217_178_610_123_456_789, /* 1976-11-18T15:23:30.123456789Z */ @@ -595,40 +598,40 @@ mod tests { let positive_result = earlier .until(&later, init_diff_setting(Unit::Hour)) .unwrap(); - assert_time_duration(positive_result.time(), (376436, 0, 0, 0, 0, 0)); + assert_duration(positive_result, (376436, 0, 0, 0, 0, 0)); let negative_result = later .until(&earlier, init_diff_setting(Unit::Hour)) .unwrap(); - assert_time_duration(negative_result.time(), (-376435, 0, 0, 0, 0, 0)); + assert_duration(negative_result, (-376435, 0, 0, 0, 0, 0)); let positive_result = earlier .until(&later, init_diff_setting(Unit::Minute)) .unwrap(); - assert_time_duration(positive_result.time(), (376435, 24, 0, 0, 0, 0)); + assert_duration(positive_result, (376435, 24, 0, 0, 0, 0)); let negative_result = later .until(&earlier, init_diff_setting(Unit::Minute)) .unwrap(); - assert_time_duration(negative_result.time(), (-376435, -23, 0, 0, 0, 0)); + assert_duration(negative_result, (-376435, -23, 0, 0, 0, 0)); // ... Skip to lower units ... let positive_result = earlier .until(&later, init_diff_setting(Unit::Microsecond)) .unwrap(); - assert_time_duration(positive_result.time(), (376435, 23, 8, 148, 530, 0)); + assert_duration(positive_result, (376435, 23, 8, 148, 530, 0)); let negative_result = later .until(&earlier, init_diff_setting(Unit::Microsecond)) .unwrap(); - assert_time_duration(negative_result.time(), (-376435, -23, -8, -148, -529, 0)); + assert_duration(negative_result, (-376435, -23, -8, -148, -529, 0)); let positive_result = earlier .until(&later, init_diff_setting(Unit::Nanosecond)) .unwrap(); - assert_time_duration(positive_result.time(), (376435, 23, 8, 148, 529, 500)); + assert_duration(positive_result, (376435, 23, 8, 148, 529, 500)); let negative_result = later .until(&earlier, init_diff_setting(Unit::Nanosecond)) .unwrap(); - assert_time_duration(negative_result.time(), (-376435, -23, -8, -148, -529, -500)); + assert_duration(negative_result, (-376435, -23, -8, -148, -529, -500)); } #[test] @@ -642,20 +645,28 @@ mod tests { } }; - let assert_time_duration = - |td: &TimeDuration, expected: (i64, i64, i64, i64, i128, i128)| { - assert_eq!( - td, - &TimeDuration { - hours: expected.0, - minutes: expected.1, - seconds: expected.2, - milliseconds: expected.3, - microseconds: expected.4, - nanoseconds: expected.5, - } - ) - }; + let assert_duration = |td: Duration, expected: (i64, i64, i64, i64, i128, i128)| { + assert_eq!( + td, + Duration { + sign: duration_sign(&[ + expected.0, + expected.1, + expected.2, + expected.3, + expected.4 as i64, + expected.5 as i64 + ]), + hours: expected.0.unsigned_abs(), + minutes: expected.1.unsigned_abs(), + seconds: expected.2.unsigned_abs(), + milliseconds: expected.3.unsigned_abs(), + microseconds: expected.4.unsigned_abs(), + nanoseconds: expected.5.unsigned_abs(), + ..Default::default() + } + ) + }; let earlier = Instant::try_new( 217_178_610_123_456_789, /* 1976-11-18T15:23:30.123456789Z */ @@ -669,40 +680,40 @@ mod tests { let positive_result = later .since(&earlier, init_diff_setting(Unit::Hour)) .unwrap(); - assert_time_duration(positive_result.time(), (376436, 0, 0, 0, 0, 0)); + assert_duration(positive_result, (376436, 0, 0, 0, 0, 0)); let negative_result = earlier .since(&later, init_diff_setting(Unit::Hour)) .unwrap(); - assert_time_duration(negative_result.time(), (-376435, 0, 0, 0, 0, 0)); + assert_duration(negative_result, (-376435, 0, 0, 0, 0, 0)); let positive_result = later .since(&earlier, init_diff_setting(Unit::Minute)) .unwrap(); - assert_time_duration(positive_result.time(), (376435, 24, 0, 0, 0, 0)); + assert_duration(positive_result, (376435, 24, 0, 0, 0, 0)); let negative_result = earlier .since(&later, init_diff_setting(Unit::Minute)) .unwrap(); - assert_time_duration(negative_result.time(), (-376435, -23, 0, 0, 0, 0)); + assert_duration(negative_result, (-376435, -23, 0, 0, 0, 0)); // ... Skip to lower units ... let positive_result = later .since(&earlier, init_diff_setting(Unit::Microsecond)) .unwrap(); - assert_time_duration(positive_result.time(), (376435, 23, 8, 148, 530, 0)); + assert_duration(positive_result, (376435, 23, 8, 148, 530, 0)); let negative_result = earlier .since(&later, init_diff_setting(Unit::Microsecond)) .unwrap(); - assert_time_duration(negative_result.time(), (-376435, -23, -8, -148, -529, 0)); + assert_duration(negative_result, (-376435, -23, -8, -148, -529, 0)); let positive_result = later .since(&earlier, init_diff_setting(Unit::Nanosecond)) .unwrap(); - assert_time_duration(positive_result.time(), (376435, 23, 8, 148, 529, 500)); + assert_duration(positive_result, (376435, 23, 8, 148, 529, 500)); let negative_result = earlier .since(&later, init_diff_setting(Unit::Nanosecond)) .unwrap(); - assert_time_duration(negative_result.time(), (-376435, -23, -8, -148, -529, -500)); + assert_duration(negative_result, (-376435, -23, -8, -148, -529, -500)); } // /test/built-ins/Temporal/Instant/prototype/add/cross-epoch.js @@ -718,7 +729,7 @@ mod tests { let instant = Instant::from_str("1969-12-25T12:23:45.678901234Z").unwrap(); let one = instant .subtract( - Duration::from_partial_duration(PartialDuration { + &Duration::from_partial_duration(PartialDuration { hours: Some(240.into()), nanoseconds: Some(800.into()), ..Default::default() @@ -728,7 +739,7 @@ mod tests { .unwrap(); let two = instant .add( - Duration::from_partial_duration(PartialDuration { + &Duration::from_partial_duration(PartialDuration { hours: Some(240.into()), nanoseconds: Some(800.into()), ..Default::default() @@ -738,7 +749,7 @@ mod tests { .unwrap(); let three = two .subtract( - Duration::from_partial_duration(PartialDuration { + &Duration::from_partial_duration(PartialDuration { hours: Some(480.into()), nanoseconds: Some(1600.into()), ..Default::default() @@ -748,7 +759,7 @@ mod tests { .unwrap(); let four = one .add( - Duration::from_partial_duration(PartialDuration { + &Duration::from_partial_duration(PartialDuration { hours: Some(480.into()), nanoseconds: Some(1600.into()), ..Default::default() diff --git a/src/builtins/core/mod.rs b/src/builtins/core/mod.rs index 2fbf2a5c7..8bd283141 100644 --- a/src/builtins/core/mod.rs +++ b/src/builtins/core/mod.rs @@ -28,7 +28,7 @@ pub use date::{PartialDate, PlainDate}; #[doc(inline)] pub use datetime::{DateTimeFields, PartialDateTime, PlainDateTime}; #[doc(inline)] -pub use duration::{DateDuration, Duration, PartialDuration, TimeDuration}; +pub use duration::{DateDuration, Duration, PartialDuration}; #[doc(inline)] pub use instant::Instant; #[doc(inline)] diff --git a/src/builtins/core/time.rs b/src/builtins/core/time.rs index 10ff6c528..fd44169da 100644 --- a/src/builtins/core/time.rs +++ b/src/builtins/core/time.rs @@ -1,14 +1,14 @@ //! This module implements `Time` and any directly related algorithms. use crate::{ - builtins::core::{duration::TimeDuration, Duration}, + builtins::{core::Duration, duration::normalized::NormalizedDurationRecord}, iso::IsoTime, options::{ ArithmeticOverflow, DifferenceOperation, DifferenceSettings, ResolvedRoundingOptions, RoundingIncrement, RoundingMode, ToStringRoundingOptions, Unit, UnitGroup, }, parsers::{parse_time, IxdtfStringBuilder}, - TemporalError, TemporalResult, + DateDuration, TemporalError, TemporalResult, }; use alloc::string::String; use core::str::FromStr; @@ -234,17 +234,17 @@ impl PlainTime { (day, Self::new_unchecked(balance_result)) } - /// Adds a `TimeDuration` to the current `Time`. + /// Adds a `Duration` to the current `Time`. /// /// Spec Equivalent: `AddDurationToOrSubtractDurationFromPlainTime`. - pub(crate) fn add_to_time(&self, duration: &TimeDuration) -> TemporalResult { + pub(crate) fn add_to_time(&self, duration: &Duration) -> TemporalResult { let (_, result) = IsoTime::balance( - i64::from(self.hour()).saturating_add(duration.hours), - i64::from(self.minute()).saturating_add(duration.minutes), - i64::from(self.second()).saturating_add(duration.seconds), - i64::from(self.millisecond()).saturating_add(duration.milliseconds), - i128::from(self.microsecond()).saturating_add(duration.microseconds), - i128::from(self.nanosecond()).saturating_add(duration.nanoseconds), + i64::from(self.hour()).saturating_add(duration.hours()), + i64::from(self.minute()).saturating_add(duration.minutes()), + i64::from(self.second()).saturating_add(duration.seconds()), + i64::from(self.millisecond()).saturating_add(duration.milliseconds()), + i128::from(self.microsecond()).saturating_add(duration.microseconds()), + i128::from(self.nanosecond()).saturating_add(duration.nanoseconds()), ); // NOTE (nekevss): IsoTime::balance should never return an invalid `IsoTime` @@ -271,29 +271,20 @@ impl PlainTime { Unit::Hour, Unit::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 mut normalized_time = self.iso.diff(&other.iso).to_normalized(); - - // 6. If settings.[[SmallestUnit]] is not "nanosecond" or settings.[[RoundingIncrement]] ≠ 1, then - if resolved.smallest_unit != Unit::Nanosecond - || resolved.increment != RoundingIncrement::ONE - { - // a. Let roundRecord be ! RoundDuration(0, 0, 0, 0, norm, settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[RoundingMode]]). - // b. Set norm to roundRecord.[[NormalizedDuration]].[[NormalizedTime]]. - normalized_time = normalized_time.round(resolved)?; - }; - - // 7. Let result be BalanceTimeDuration(norm, settings.[[LargestUnit]]). - let result = TimeDuration::from_normalized(normalized_time, resolved.largest_unit)?.1; - + // 4. Let timeDuration be DifferenceTime(temporalTime.[[Time]], other.[[Time]]). + let mut normalized_time = self.iso.diff(&other.iso); + // 5. Set timeDuration to ! RoundTimeDuration(timeDuration, settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[RoundingMode]]). + normalized_time = normalized_time.round(resolved)?; + // 6. Let duration be CombineDateAndTimeDuration(ZeroDateDuration(), timeDuration). + let duration = NormalizedDurationRecord::combine(DateDuration::default(), normalized_time); + // 7. Let result be ! TemporalDurationFromInternal(duration, settings.[[LargestUnit]]). + let result = Duration::from_internal(duration, resolved.largest_unit)?; + // 8. If operation is since, set result to CreateNegatedTemporalDuration(result). + // 9. Return result. // 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]]). match op { - DifferenceOperation::Until => Ok(Duration::from(result)), - DifferenceOperation::Since => Ok(Duration::from(result.negated())), + DifferenceOperation::Until => Ok(result), + DifferenceOperation::Since => Ok(result.negated()), } } } @@ -505,23 +496,11 @@ impl PlainTime { /// Add a `Duration` to the current `Time`. pub fn add(&self, duration: &Duration) -> TemporalResult { - self.add_time_duration(duration.time()) - } - - /// Adds a `TimeDuration` to the current `Time`. - #[inline] - pub fn add_time_duration(&self, duration: &TimeDuration) -> TemporalResult { self.add_to_time(duration) } /// Subtract a `Duration` to the current `Time`. pub fn subtract(&self, duration: &Duration) -> TemporalResult { - self.subtract_time_duration(duration.time()) - } - - /// Adds a `TimeDuration` to the current `Time`. - #[inline] - pub fn subtract_time_duration(&self, duration: &TimeDuration) -> TemporalResult { self.add_to_time(&duration.negated()) } diff --git a/src/builtins/core/year_month.rs b/src/builtins/core/year_month.rs index 252950f12..be6a349dd 100644 --- a/src/builtins/core/year_month.rs +++ b/src/builtins/core/year_month.rs @@ -339,7 +339,7 @@ impl PlainYearMonth { } // 17. Let result be ! TemporalDurationFromInternal(duration, day). - let result = Duration::from_normalized(duration, Unit::Day)?; + let result = Duration::from_internal(duration, Unit::Day)?; // 18. If operation is since, set result to CreateNegatedTemporalDuration(result). // 19. Return result. diff --git a/src/builtins/core/zoneddatetime.rs b/src/builtins/core/zoneddatetime.rs index dd3041356..f693b21e8 100644 --- a/src/builtins/core/zoneddatetime.rs +++ b/src/builtins/core/zoneddatetime.rs @@ -267,23 +267,25 @@ impl ZonedDateTime { } } - pub(crate) fn add_as_instant( + pub(crate) fn add_zoned_date_time( &self, - duration: &Duration, + duration: NormalizedDurationRecord, overflow: ArithmeticOverflow, provider: &impl TimeZoneProvider, ) -> TemporalResult { // 1. If DateDurationSign(duration.[[Date]]) = 0, then if duration.date().sign() == Sign::Zero { // a. Return ? AddInstant(epochNanoseconds, duration.[[Time]]). - return self.instant.add_to_instant(duration.time()); + return self + .instant + .add_to_instant(&duration.normalized_time_duration()); } // 2. Let isoDateTime be GetISODateTimeFor(timeZone, epochNanoseconds). let iso_datetime = self.tz.get_iso_datetime_for(&self.instant, provider)?; // 3. Let addedDate be ? CalendarDateAdd(calendar, isoDateTime.[[ISODate]], duration.[[Date]], overflow). - let added_date = self - .calendar() - .date_add(&iso_datetime.date, duration.date(), overflow)?; + let added_date = + self.calendar() + .date_add(&iso_datetime.date, &duration.date(), overflow)?; // 4. Let intermediateDateTime be CombineISODateAndTimeRecord(addedDate, isoDateTime.[[Time]]). let intermediate = IsoDateTime::new_unchecked(added_date.iso, iso_datetime.time); // 5. If ISODateTimeWithinLimits(intermediateDateTime) is false, throw a RangeError exception. @@ -300,7 +302,7 @@ impl ZonedDateTime { )?; // 7. Return ? AddInstant(intermediateNs, duration.[[Time]]). - Instant::from(intermediate_ns).add_to_instant(duration.time()) + Instant::from(intermediate_ns).add_to_instant(&duration.normalized_time_duration()) } /// Adds a duration to the current `ZonedDateTime`, returning the resulting `ZonedDateTime`. @@ -320,8 +322,9 @@ impl ZonedDateTime { // 5. Let calendar be zonedDateTime.[[Calendar]]. // 6. Let timeZone be zonedDateTime.[[TimeZone]]. // 7. Let internalDuration be ToInternalDurationRecord(duration). + let internal_duration = duration.to_internal_duration_record(); // 8. Let epochNanoseconds be ? AddZonedDateTime(zonedDateTime.[[EpochNanoseconds]], timeZone, calendar, internalDuration, overflow). - let epoch_ns = self.add_as_instant(duration, overflow, provider)?; + let epoch_ns = self.add_zoned_date_time(internal_duration, overflow, provider)?; // 9. Return ! CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar). Ok(Self::new_unchecked( epoch_ns, @@ -474,7 +477,7 @@ impl ZonedDateTime { let date_diff = self.calendar() .date_until(&start.date, &intermediate_dt.date, date_largest)?; - NormalizedDurationRecord::new(*date_diff.date(), time_duration) + NormalizedDurationRecord::new(date_diff.date(), time_duration) } /// `temporal_rs` equivalent to `DifferenceTemporalZonedDateTime`. @@ -507,7 +510,7 @@ impl ZonedDateTime { .instant .diff_instant_internal(&other.instant, resolved_options)?; // b. Let result be ! TemporalDurationFromInternal(internalDuration, settings.[[LargestUnit]]). - let result = Duration::from_normalized(internal, resolved_options.largest_unit)?; + let result = Duration::from_internal(internal, resolved_options.largest_unit)?; // c. If operation is since, set result to CreateNegatedTemporalDuration(result). // d. Return result. match op { @@ -537,7 +540,7 @@ impl ZonedDateTime { // 9. Let internalDuration be ? DifferenceZonedDateTimeWithRounding(zonedDateTime.[[EpochNanoseconds]], other.[[EpochNanoseconds]], zonedDateTime.[[TimeZone]], zonedDateTime.[[Calendar]], settings.[[LargestUnit]], settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[RoundingMode]]). let internal = self.diff_with_rounding(other, resolved_options, provider)?; // 10. Let result be ! TemporalDurationFromInternal(internalDuration, hour). - let result = Duration::from_normalized(internal, Unit::Hour)?; + let result = Duration::from_internal(internal, Unit::Hour)?; // 11. If operation is since, set result to CreateNegatedTemporalDuration(result). // 12. Return result. match op { diff --git a/src/error.rs b/src/error.rs index 2161a605e..c9f791986 100644 --- a/src/error.rs +++ b/src/error.rs @@ -171,6 +171,7 @@ pub(crate) enum ErrorMessage { InstantOutOfRange, IntermediateDateTimeOutOfRange, ZDTOutOfDayBounds, + LargestUnitCannotBeDateUnit, // Numerical errors NumberNotFinite, @@ -212,6 +213,7 @@ impl ErrorMessage { "Intermediate ISO datetime was not within a valid range." } Self::ZDTOutOfDayBounds => "ZonedDateTime is outside the expected day bounds", + Self::LargestUnitCannotBeDateUnit => "Largest unit cannot be a date unit", Self::NumberNotFinite => "number value is not a finite value.", Self::NumberNotIntegral => "value must be integral.", Self::NumberNotPositive => "integer must be positive.", diff --git a/src/iso.rs b/src/iso.rs index 2541461eb..1977acffa 100644 --- a/src/iso.rs +++ b/src/iso.rs @@ -30,9 +30,9 @@ use crate::{ calendar::Calendar, duration::{ normalized::{NormalizedDurationRecord, NormalizedTimeDuration}, - DateDuration, TimeDuration, + DateDuration, }, - Duration, PartialTime, PlainDate, + PartialTime, PlainDate, }, error::{ErrorMessage, TemporalError}, options::{ArithmeticOverflow, ResolvedRoundingOptions, Unit}, @@ -158,45 +158,6 @@ impl IsoDateTime { utc_epoch_nanos(self.date, &self.time) } - /// Specification equivalent to 5.5.9 `AddDateTime`. - pub(crate) fn add_date_duration( - &self, - calendar: Calendar, - date_duration: &DateDuration, - norm: NormalizedTimeDuration, - overflow: Option, - ) -> TemporalResult { - // 1. Assert: IsValidISODate(year, month, day) is true. - // 2. Assert: ISODateTimeWithinLimits(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond) is true. - // 3. Let timeResult be AddTime(hour, minute, second, millisecond, microsecond, nanosecond, norm). - let t_result = self.time.add(norm); - - // 4. Let datePart be ! CreateTemporalDate(year, month, day, calendarRec.[[Receiver]]). - let date = PlainDate::new_unchecked(self.date, calendar); - - // 5. Let dateDuration be ? CreateTemporalDuration(years, months, weeks, days + timeResult.[[Days]], 0, 0, 0, 0, 0, 0). - let date_duration = DateDuration::new( - date_duration.years, - date_duration.months, - date_duration.weeks, - date_duration - .days - .checked_add(t_result.0) - .ok_or(TemporalError::range())?, - )?; - let duration = Duration::from(date_duration); - - // 6. Let addedDate be ? AddDate(calendarRec, datePart, dateDuration, options). - // The within-limits check gets handled below in Self::new - let added_date = date.add_date(&duration, overflow)?; - - // 7. Return ISO Date-Time Record { [[Year]]: addedDate.[[ISOYear]], [[Month]]: addedDate.[[ISOMonth]], - // [[Day]]: addedDate.[[ISODay]], [[Hour]]: timeResult.[[Hour]], [[Minute]]: timeResult.[[Minute]], - // [[Second]]: timeResult.[[Second]], [[Millisecond]]: timeResult.[[Millisecond]], - // [[Microsecond]]: timeResult.[[Microsecond]], [[Nanosecond]]: timeResult.[[Nanosecond]] }. - Self::new(added_date.iso, t_result.1) - } - pub(crate) fn round(&self, resolved_options: ResolvedRoundingOptions) -> TemporalResult { let (rounded_days, rounded_time) = self.time.round(resolved_options)?; let balance_result = IsoDate::try_balance( @@ -207,6 +168,7 @@ impl IsoDateTime { Self::new(balance_result, rounded_time) } + // TODO: UPDATE TO CURRENT SPECIFICATION // 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( @@ -221,8 +183,7 @@ impl IsoDateTime { // 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)); + let mut time_duration = self.time.diff(&other.time); // 5. Let timeSign be NormalizedTimeDurationSign(timeDuration). let time_sign = time_duration.sign() as i8; @@ -423,8 +384,9 @@ impl IsoDate { Self::new_with_overflow(intermediate.0, intermediate.1, self.day, overflow)?; // 5. Set days to days + 7 × weeks. - let additional_days = duration.days + (7 * duration.weeks); // Verify - // 6. Let d be intermediate.[[Day]] + days. + let additional_days = duration.days + (7 * duration.weeks); + + // 6. Let d be intermediate.[[Day]] + days. let intermediate_days = i64::from(intermediate.day) + additional_days; // 7. Return BalanceISODate(intermediate.[[Year]], intermediate.[[Month]], d). @@ -735,7 +697,7 @@ impl IsoTime { } /// Difference this `IsoTime` against another and returning a `TimeDuration`. - pub(crate) fn diff(&self, other: &Self) -> TimeDuration { + pub(crate) fn diff(&self, other: &Self) -> NormalizedTimeDuration { let h = i64::from(other.hour) - i64::from(self.hour); let m = i64::from(other.minute) - i64::from(self.minute); let s = i64::from(other.second) - i64::from(self.second); @@ -743,7 +705,7 @@ impl IsoTime { let mis = i128::from(other.microsecond) - i128::from(self.microsecond); let ns = i128::from(other.nanosecond) - i128::from(self.nanosecond); - TimeDuration::new_unchecked(h, m, s, ms, mis, ns) + NormalizedTimeDuration::from_components(h, m, s, ms, mis, ns) } // NOTE (nekevss): Specification seemed to be off / not entirely working, so the below was adapted from the diff --git a/src/lib.rs b/src/lib.rs index 651fdbfc6..a0458f123 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -202,7 +202,7 @@ pub use crate::builtins::{ core::timezone::{TimeZone, UtcOffset}, core::DateDuration, Duration, Instant, PlainDate, PlainDateTime, PlainMonthDay, PlainTime, PlainYearMonth, - TimeDuration, ZonedDateTime, + ZonedDateTime, }; /// A library specific trait for unwrapping assertions. @@ -273,7 +273,7 @@ impl From for Sign { impl Sign { /// Coerces the current `Sign` to be either negative or positive. - pub(crate) fn as_sign_multiplier(&self) -> i8 { + pub(crate) const fn as_sign_multiplier(&self) -> i8 { if matches!(self, Self::Zero) { return 1; } diff --git a/temporal_capi/src/duration.rs b/temporal_capi/src/duration.rs index 690361ac2..f87d3a1fe 100644 --- a/temporal_capi/src/duration.rs +++ b/temporal_capi/src/duration.rs @@ -20,10 +20,6 @@ pub mod ffi { #[diplomat::opaque] pub struct Duration(pub(crate) temporal_rs::Duration); - #[diplomat::opaque] - #[diplomat::transparent_convert] - pub struct TimeDuration(pub(crate) temporal_rs::TimeDuration); - #[diplomat::opaque] #[diplomat::transparent_convert] pub struct DateDuration(pub(crate) temporal_rs::DateDuration); @@ -56,42 +52,6 @@ pub mod ffi { } } - impl TimeDuration { - pub fn try_new( - hours: i64, - minutes: i64, - seconds: i64, - milliseconds: i64, - microseconds: f64, - nanoseconds: f64, - ) -> Result, TemporalError> { - temporal_rs::TimeDuration::new( - hours, - minutes, - seconds, - milliseconds, - i128::from_f64(microseconds).ok_or(TemporalError::range("μs out of range"))?, - i128::from_f64(nanoseconds).ok_or(TemporalError::range("ns out of range"))?, - ) - .map(|x| Box::new(TimeDuration(x))) - .map_err(Into::into) - } - - pub fn abs(&self) -> Box { - Box::new(Self(self.0.abs())) - } - pub fn negated(&self) -> Box { - Box::new(Self(self.0.negated())) - } - - pub fn is_within_range(&self) -> bool { - self.0.is_within_range() - } - pub fn sign(&self) -> Sign { - self.0.sign().into() - } - } - impl DateDuration { pub fn try_new( years: i64, @@ -195,16 +155,8 @@ pub mod ffi { self.0.is_time_within_range() } - pub fn time<'a>(&'a self) -> &'a TimeDuration { - TimeDuration::transparent_convert(self.0.time()) - } - pub fn date<'a>(&'a self) -> &'a DateDuration { - DateDuration::transparent_convert(self.0.date()) - } - // set_time_duration is NOT safe to expose over FFI if the date()/time() methods are available // Diplomat plans to make this a hard error. - // If needed, implement it as with_time_duration(&self, TimeDuration) -> Self pub fn years(&self) -> i64 { self.0.years() diff --git a/temporal_capi/src/instant.rs b/temporal_capi/src/instant.rs index 96af06bce..149faadac 100644 --- a/temporal_capi/src/instant.rs +++ b/temporal_capi/src/instant.rs @@ -2,7 +2,7 @@ #[diplomat::abi_rename = "temporal_rs_{0}"] #[diplomat::attr(auto, namespace = "temporal_rs")] pub mod ffi { - use crate::duration::ffi::{Duration, TimeDuration}; + use crate::duration::ffi::Duration; use crate::error::ffi::TemporalError; use crate::options::ffi::{DifferenceSettings, RoundingOptions}; #[cfg(feature = "compiled_data")] @@ -72,31 +72,13 @@ pub mod ffi { pub fn add(&self, duration: &Duration) -> Result, TemporalError> { self.0 - .add(duration.0) - .map(|c| Box::new(Self(c))) - .map_err(Into::into) - } - pub fn add_time_duration( - &self, - duration: &TimeDuration, - ) -> Result, TemporalError> { - self.0 - .add_time_duration(&duration.0) + .add(&duration.0) .map(|c| Box::new(Self(c))) .map_err(Into::into) } pub fn subtract(&self, duration: &Duration) -> Result, TemporalError> { self.0 - .subtract(duration.0) - .map(|c| Box::new(Self(c))) - .map_err(Into::into) - } - pub fn subtract_time_duration( - &self, - duration: &TimeDuration, - ) -> Result, TemporalError> { - self.0 - .subtract_time_duration(&duration.0) + .subtract(&duration.0) .map(|c| Box::new(Self(c))) .map_err(Into::into) } diff --git a/temporal_capi/src/plain_time.rs b/temporal_capi/src/plain_time.rs index a74ca4ed1..ab3a3b835 100644 --- a/temporal_capi/src/plain_time.rs +++ b/temporal_capi/src/plain_time.rs @@ -4,7 +4,7 @@ pub mod ffi { use alloc::boxed::Box; - use crate::duration::ffi::{Duration, TimeDuration}; + use crate::duration::ffi::Duration; use crate::error::ffi::TemporalError; use crate::options::ffi::{ ArithmeticOverflow, DifferenceSettings, RoundingMode, ToStringRoundingOptions, Unit, @@ -136,24 +136,6 @@ pub mod ffi { .map(|x| Box::new(Self(x))) .map_err(Into::into) } - pub fn add_time_duration( - &self, - duration: &TimeDuration, - ) -> Result, TemporalError> { - self.0 - .add_time_duration(&duration.0) - .map(|x| Box::new(Self(x))) - .map_err(Into::into) - } - pub fn subtract_time_duration( - &self, - duration: &TimeDuration, - ) -> Result, TemporalError> { - self.0 - .subtract_time_duration(&duration.0) - .map(|x| Box::new(Self(x))) - .map_err(Into::into) - } pub fn until( &self, other: &Self, diff --git a/tools/tzif-inspect/src/main.rs b/tools/tzif-inspect/src/main.rs index ea2e1f019..828ceb31a 100644 --- a/tools/tzif-inspect/src/main.rs +++ b/tools/tzif-inspect/src/main.rs @@ -1,7 +1,8 @@ use std::env; use std::string::ToString; +use temporal_rs::partial::PartialDuration; use temporal_rs::tzdb::Tzif; -use temporal_rs::{Duration, PlainDate, PlainTime, TimeDuration, TimeZone, ZonedDateTime}; +use temporal_rs::{Duration, PlainDate, PlainTime, TimeZone, ZonedDateTime}; use tzif::data::posix::TransitionDay; use tzif::data::time::Seconds; use tzif::data::tzif::{StandardWallIndicator, UtLocalIndicator}; @@ -36,9 +37,10 @@ fn seconds_to_zdt_string(s: Seconds, time_zone: &TimeZone) -> String { fn seconds_to_offset_time(s: Seconds) -> String { let is_negative = s.0 < 0; let seconds = s.0.abs(); - let mut duration = TimeDuration::default(); - duration.seconds = seconds; - let time = PlainTime::default().add_time_duration(&duration).unwrap(); + let partial = PartialDuration::default().with_seconds(seconds); + let time = PlainTime::default() + .add(&Duration::from_partial_duration(partial).unwrap()) + .unwrap(); let string = time.to_ixdtf_string(Default::default()).unwrap(); if is_negative { format!("-{string}") From 459a20203ff4c32d479dd7bae95af93f331b275f Mon Sep 17 00:00:00 2001 From: Kevin Ness Date: Mon, 11 Aug 2025 19:43:19 -0500 Subject: [PATCH 2/6] Move away from old 'normalized' names to new spec names --- src/builtins/compiled/duration/tests.rs | 2 +- src/builtins/core/date.rs | 4 +- src/builtins/core/datetime.rs | 9 +- src/builtins/core/duration.rs | 40 ++++---- src/builtins/core/duration/normalized.rs | 112 +++++++++++------------ src/builtins/core/instant.rs | 16 ++-- src/builtins/core/time.rs | 12 +-- src/builtins/core/timezone.rs | 6 +- src/builtins/core/year_month.rs | 5 +- src/builtins/core/zoneddatetime.rs | 26 +++--- src/iso.rs | 22 ++--- 11 files changed, 122 insertions(+), 132 deletions(-) diff --git a/src/builtins/compiled/duration/tests.rs b/src/builtins/compiled/duration/tests.rs index 12eadb33e..155464643 100644 --- a/src/builtins/compiled/duration/tests.rs +++ b/src/builtins/compiled/duration/tests.rs @@ -609,7 +609,7 @@ fn balance_days_up_to_both_years_and_months() { ); } -// relativeto-plaindate-add24hourdaystonormalizedtimeduration-out-of-range.js +// relativeto-plaindate-add24hourdaystoTimeDuration-out-of-range.js #[test] fn add_normalized_time_duration_out_of_range() { let duration = Duration::from_partial_duration(PartialDuration { diff --git a/src/builtins/core/date.rs b/src/builtins/core/date.rs index 18dec32f5..c64a93424 100644 --- a/src/builtins/core/date.rs +++ b/src/builtins/core/date.rs @@ -23,7 +23,7 @@ use core::{cmp::Ordering, str::FromStr}; use icu_calendar::AnyCalendarKind; use writeable::Writeable; -use super::{duration::normalized::NormalizedDurationRecord, PlainMonthDay, PlainYearMonth}; +use super::{duration::normalized::InternalDurationRecord, PlainMonthDay, PlainYearMonth}; use tinystr::TinyAsciiStr; // TODO (potentially): Bump era up to TinyAsciiStr<18> to accomodate @@ -273,7 +273,7 @@ impl PlainDate { 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 mut duration = NormalizedDurationRecord::from_date_duration(result.date())?; + let mut duration = InternalDurationRecord::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 == Unit::Day && resolved.increment.get() == 1; diff --git a/src/builtins/core/datetime.rs b/src/builtins/core/datetime.rs index 50f304a25..7ec90e856 100644 --- a/src/builtins/core/datetime.rs +++ b/src/builtins/core/datetime.rs @@ -1,7 +1,7 @@ //! This module implements `DateTime` any directly related algorithms. use super::{ - duration::normalized::NormalizedDurationRecord, Duration, PartialTime, PlainDate, PlainTime, + duration::normalized::InternalDurationRecord, Duration, PartialTime, PlainDate, PlainTime, ZonedDateTime, }; use crate::parsed_intermediates::ParsedDateTime; @@ -226,8 +226,7 @@ impl PlainDateTime { ) -> TemporalResult { // SKIP: 1, 2, 3, 4 // 5. Let internalDuration be ToInternalDurationRecordWith24HourDays(duration). - let internal_duration = - NormalizedDurationRecord::from_duration_with_24_hour_days(duration)?; + let internal_duration = InternalDurationRecord::from_duration_with_24_hour_days(duration)?; // 6. Let timeResult be AddTime(dateTime.[[ISODateTime]].[[Time]], internalDuration.[[Time]]). let (days, time_result) = self .iso @@ -293,12 +292,12 @@ impl PlainDateTime { &self, other: &Self, options: ResolvedRoundingOptions, - ) -> TemporalResult { + ) -> TemporalResult { // 1. 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(NormalizedDurationRecord::default()); + return Ok(InternalDurationRecord::default()); } // 2. If ISODateTimeWithinLimits(isoDateTime1) is false or ISODateTimeWithinLimits(isoDateTime2) is false, throw a RangeError exception. self.iso.check_validity()?; diff --git a/src/builtins/core/duration.rs b/src/builtins/core/duration.rs index b782ddd84..a5338f005 100644 --- a/src/builtins/core/duration.rs +++ b/src/builtins/core/duration.rs @@ -19,10 +19,10 @@ use core::{cmp::Ordering, str::FromStr}; use ixdtf::{ encoding::Utf8, parsers::IsoDurationParser, records::Fraction, records::TimeDurationRecord, }; -use normalized::NormalizedDurationRecord; +use normalized::InternalDurationRecord; use num_traits::Euclid; -use self::normalized::NormalizedTimeDuration; +use self::normalized::TimeDuration; mod date; pub(crate) mod normalized; @@ -391,7 +391,7 @@ impl Duration { #[inline] pub(crate) fn from_internal( - duration_record: NormalizedDurationRecord, + duration_record: InternalDurationRecord, largest_unit: Unit, ) -> TemporalResult { // 1. Let days, hours, minutes, seconds, milliseconds, and microseconds be 0. @@ -532,10 +532,10 @@ impl Duration { ) } - /// Returns this `Duration` as a `NormalizedTimeDuration`. + /// Returns this `Duration` as a `TimeDuration`. #[inline] - pub(crate) fn to_normalized(self) -> NormalizedTimeDuration { - NormalizedTimeDuration::from_duration(&self) + pub(crate) fn to_normalized(self) -> TimeDuration { + TimeDuration::from_duration(&self) } /// Returns the a `Vec` of the fields values. @@ -568,12 +568,12 @@ impl Duration { } // 7.5.5 ToInternalDurationRecord ( duration ) - pub(crate) fn to_internal_duration_record(self) -> NormalizedDurationRecord { + pub(crate) fn to_internal_duration_record(self) -> InternalDurationRecord { // 1. Let dateDuration be ! CreateDateDurationRecord(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]]). let date_duration = DateDuration::new_unchecked(self.years(), self.months(), self.weeks(), self.days()); // 2. Let timeDuration be TimeDurationFromComponents(duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]]). - let time_duration = NormalizedTimeDuration::from_components( + let time_duration = TimeDuration::from_components( self.hours(), self.minutes(), self.seconds(), @@ -582,7 +582,7 @@ impl Duration { self.nanoseconds(), ); // 3. Return CombineDateAndTimeDuration(dateDuration, timeDuration). - NormalizedDurationRecord::combine(date_duration, time_duration) + InternalDurationRecord::combine(date_duration, time_duration) } /// Equivalent of [`7.5.7 ToDateDurationRecordWithoutTime ( duration )`][spec] @@ -593,7 +593,7 @@ impl Duration { #[allow(clippy::wrong_self_convention)] pub(crate) fn to_date_duration_record_without_time(&self) -> TemporalResult { // 1. Let internalDuration be ToInternalDurationRecordWith24HourDays(duration). - let internal_duration = NormalizedDurationRecord::from_duration_with_24_hour_days(self)?; + let internal_duration = InternalDurationRecord::from_duration_with_24_hour_days(self)?; // 2. Let days be truncate(internalDuration.[[Time]] / nsPerDay). // 3. Return ! CreateDateDurationRecord(internalDuration.[[Date]].[[Years]], internalDuration.[[Date]].[[Months]], internalDuration.[[Date]].[[Weeks]], days). @@ -1023,13 +1023,13 @@ impl Duration { } // 7. Let d1 be ToInternalDurationRecordWith24HourDays(duration). - let d1 = NormalizedDurationRecord::from_duration_with_24_hour_days(self)?; + let d1 = InternalDurationRecord::from_duration_with_24_hour_days(self)?; // 8. Let d2 be ToInternalDurationRecordWith24HourDays(other). - let d2 = NormalizedDurationRecord::from_duration_with_24_hour_days(other)?; + let d2 = InternalDurationRecord::from_duration_with_24_hour_days(other)?; // 9. Let timeResult be ? AddTimeDuration(d1.[[Time]], d2.[[Time]]). let time_result = (d1.normalized_time_duration() + d2.normalized_time_duration())?; // 10. Let result be CombineDateAndTimeDuration(ZeroDateDuration(), timeResult). - let result = NormalizedDurationRecord::combine(DateDuration::default(), time_result); + let result = InternalDurationRecord::combine(DateDuration::default(), time_result); // 11. Return ? TemporalDurationFromInternal(result, largestUnit). Duration::from_internal(result, largest_unit) } @@ -1187,7 +1187,7 @@ impl Duration { Some(RelativeTo::PlainDate(plain_relative_to)) => { // a. Let internalDuration be ToInternalDurationRecordWith24HourDays(duration). let internal_duration = - NormalizedDurationRecord::from_duration_with_24_hour_days(self)?; + InternalDurationRecord::from_duration_with_24_hour_days(self)?; // b. Let targetTime be AddTime(MidnightTimeRecord(), internalDuration.[[Time]]). let (target_time_days, target_time) = PlainTime::default() @@ -1243,7 +1243,7 @@ impl Duration { temporal_assert!(!resolved_options.smallest_unit.is_calendar_unit()); // 30. Let internalDuration be ToInternalDurationRecordWith24HourDays(duration). - let internal_duration = NormalizedDurationRecord::from_duration_with_24_hour_days(self)?; + let internal_duration = InternalDurationRecord::from_duration_with_24_hour_days(self)?; // 31. If smallestUnit is day, then let internal_duration = if resolved_options.smallest_unit == Unit::Day { @@ -1260,7 +1260,7 @@ impl Duration { let date = DateDuration::new(0, 0, 0, days)?; // d. Set internalDuration to CombineDateAndTimeDuration(dateDuration, 0). - NormalizedDurationRecord::new(date, NormalizedTimeDuration::default())? + InternalDurationRecord::new(date, TimeDuration::default())? } else { // 32. Else, // a. Let timeDuration be ? RoundTimeDuration(internalDuration.[[Time]], roundingIncrement, smallestUnit, roundingMode). @@ -1269,7 +1269,7 @@ impl Duration { .round(resolved_options)?; // b. Set internalDuration to CombineDateAndTimeDuration(ZeroDateDuration(), timeDuration). - NormalizedDurationRecord::new(DateDuration::default(), time_duration)? + InternalDurationRecord::new(DateDuration::default(), time_duration)? }; // 33. Return ? TemporalDurationFromInternal(internalDuration, largestUnit). @@ -1353,7 +1353,7 @@ impl Duration { return Err(TemporalError::range()); } // c. Let internalDuration be ToInternalDurationRecordWith24HourDays(duration). - let internal = NormalizedDurationRecord::from_duration_with_24_hour_days(self)?; + let internal = InternalDurationRecord::from_duration_with_24_hour_days(self)?; // d. Let total be TotalTimeDuration(internalDuration.[[Time]], unit). let total = internal.normalized_time_duration().total(unit)?; Ok(total) @@ -1390,7 +1390,7 @@ impl Duration { .round(rounding_options)?; // 15. Set internalDuration to CombineDateAndTimeDuration(internalDuration.[[Date]], timeDuration). let internal_duration = - NormalizedDurationRecord::combine(internal_duration.date(), time_duration); + InternalDurationRecord::combine(internal_duration.date(), time_duration); // 16. Let roundedLargestUnit be LargerOfTwoTemporalUnits(largestUnit, second). let rounded_largest_unit = largest.max(Unit::Second); @@ -1423,7 +1423,7 @@ pub fn duration_to_formattable( let hours = duration.hours().abs(); let minutes = duration.minutes().abs(); - let time = NormalizedTimeDuration::from_components( + let time = TimeDuration::from_components( 0, 0, duration.seconds(), diff --git a/src/builtins/core/duration/normalized.rs b/src/builtins/core/duration/normalized.rs index 44cdbe4b6..358bce2e4 100644 --- a/src/builtins/core/duration/normalized.rs +++ b/src/builtins/core/duration/normalized.rs @@ -27,7 +27,7 @@ const NS_PER_DAY_128BIT: i128 = NS_PER_DAY as i128; const NANOSECONDS_PER_MINUTE: i128 = 60 * 1_000_000_000; const NANOSECONDS_PER_HOUR: i128 = 60 * NANOSECONDS_PER_MINUTE; -// ==== NormalizedTimeDuration ==== +// ==== TimeDuration ==== // // A time duration represented in pure nanoseconds. // @@ -37,10 +37,10 @@ const NANOSECONDS_PER_HOUR: i128 = 60 * NANOSECONDS_PER_MINUTE; /// A Normalized `TimeDuration` that represents the current `TimeDuration` in nanoseconds. #[derive(Debug, Clone, Copy, Default, PartialEq, PartialOrd, Eq, Ord)] -pub(crate) struct NormalizedTimeDuration(pub(crate) i128); +pub(crate) struct TimeDuration(pub(crate) i128); -impl NormalizedTimeDuration { - /// Creates a `NormalizedTimeDuration` from signed integer components. +impl TimeDuration { + /// Creates a `TimeDuration` from signed integer components. /// This method preserves the sign of each component during the calculation. pub(crate) fn from_components( hours: i64, @@ -79,23 +79,24 @@ impl NormalizedTimeDuration { Self(nanoseconds) } - /// Equivalent to 7.5.27 NormalizedTimeDurationFromEpochNanosecondsDifference ( one, two ) + /// Equivalent to 7.5.27 TimeDurationFromEpochNanosecondsDifference ( one, two ) pub(crate) fn from_nanosecond_difference(one: i128, two: i128) -> TemporalResult { let result = one - two; if result.abs() > MAX_TIME_DURATION { - return Err(TemporalError::range() - .with_message("normalizedTimeDuration exceeds maxTimeDuration.")); + return Err( + TemporalError::range().with_message("TimeDuration exceeds maxTimeDuration.") + ); } Ok(Self(result)) } - /// Equivalent: 7.5.23 Add24HourDaysToNormalizedTimeDuration ( d, days ) + /// Equivalent: 7.5.23 Add24HourDaysToTimeDuration ( d, days ) /// Add24HourDaysToTimeDuration?? 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() - .with_message("SubtractNormalizedTimeDuration exceeded a valid Duration range.")); + .with_message("SubtractTimeDuration exceeded a valid Duration range.")); } Ok(Self(result)) } @@ -107,7 +108,7 @@ impl NormalizedTimeDuration { self.0 / i128::from(divisor) } - /// Equivalent: 7.5.31 NormalizedTimeDurationSign ( d ) + /// Equivalent: 7.5.31 TimeDurationSign ( d ) #[inline] #[must_use] pub(crate) fn sign(&self) -> Sign { @@ -115,14 +116,14 @@ impl NormalizedTimeDuration { } // NOTE(nekevss): non-euclid is required here for negative rounding. - /// Return the seconds value of the `NormalizedTimeDuration`. + /// Return the seconds value of the `TimeDuration`. pub(crate) fn seconds(&self) -> i64 { // SAFETY: See validate_second_cast test. (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`. + /// Returns the subsecond components of the `TimeDuration`. pub(crate) fn subseconds(&self) -> i32 { // SAFETY: Remainder is 10e9 which is in range of i32 (self.0 % 1_000_000_000) as i32 @@ -135,9 +136,8 @@ impl NormalizedTimeDuration { 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.", - )); + return Err(TemporalError::range() + .with_message("SubtractTimeDuration exceeded a valid TimeDuration range.")); } Ok(Self(result)) } @@ -147,13 +147,13 @@ impl NormalizedTimeDuration { // 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). + // c. Let total be DivideTimeDuration(norm, divisor). let increment = options .increment .as_extended_increment() .checked_mul(divisor) .temporal_unwrap()?; - // d. Set norm to ? RoundNormalizedTimeDurationToIncrement(norm, divisor × increment, roundingMode). + // d. Set norm to ? RoundTimeDurationToIncrement(norm, divisor × increment, roundingMode). self.round_inner(increment, options.rounding_mode) } @@ -182,7 +182,7 @@ impl NormalizedTimeDuration { Ok((rounded / NS_PER_DAY_128BIT) as i64) } - /// Round the current `NormalizedTimeDuration`. + /// Round the current `TimeDuration`. pub(super) fn round_inner( &self, increment: NonZeroU128, @@ -190,8 +190,9 @@ impl NormalizedTimeDuration { ) -> TemporalResult { let rounded = IncrementRounder::::from_signed_num(self.0, increment)?.round(mode); if rounded.abs() > MAX_TIME_DURATION { - return Err(TemporalError::range() - .with_message("normalizedTimeDuration exceeds maxTimeDuration.")); + return Err( + TemporalError::range().with_message("TimeDuration exceeds maxTimeDuration.") + ); } Ok(Self(rounded)) } @@ -199,23 +200,25 @@ impl NormalizedTimeDuration { pub(super) fn checked_add(&self, other: i128) -> TemporalResult { let result = self.0 + other; if result.abs() > MAX_TIME_DURATION { - return Err(TemporalError::range() - .with_message("normalizedTimeDuration exceeds maxTimeDuration.")); + return Err( + TemporalError::range().with_message("TimeDuration exceeds maxTimeDuration.") + ); } Ok(Self(result)) } } // NOTE(nekevss): As this `Add` impl is fallible. Maybe it would be best implemented as a method. -/// Equivalent: 7.5.22 AddNormalizedTimeDuration ( one, two ) -impl Add for NormalizedTimeDuration { +/// Equivalent: 7.5.22 AddTimeDuration ( one, two ) +impl Add for TimeDuration { type Output = TemporalResult; fn add(self, rhs: Self) -> Self::Output { let result = self.0 + rhs.0; if result.abs() > MAX_TIME_DURATION { - return Err(TemporalError::range() - .with_message("normalizedTimeDuration exceeds maxTimeDuration.")); + return Err( + TemporalError::range().with_message("TimeDuration exceeds maxTimeDuration.") + ); } Ok(Self(result)) } @@ -247,21 +250,21 @@ impl DurationTotal { } } -// ==== NormalizedDurationRecord ==== +// ==== Internal Duration record ==== // -// A record consisting of a DateDuration and NormalizedTimeDuration +// A record consisting of a DateDuration and TimeDuration // -/// A NormalizedDurationRecord is a duration record that contains -/// a `DateDuration` and `NormalizedTimeDuration`. +/// An InternalDurationRecord is a duration record that contains +/// a `DateDuration` and `TimeDuration`. #[derive(Debug, Default, Clone, Copy)] -pub struct NormalizedDurationRecord { +pub struct InternalDurationRecord { date: DateDuration, - norm: NormalizedTimeDuration, + norm: TimeDuration, } -impl NormalizedDurationRecord { - pub(crate) fn combine(date: DateDuration, norm: NormalizedTimeDuration) -> Self { +impl InternalDurationRecord { + pub(crate) fn combine(date: DateDuration, norm: TimeDuration) -> Self { // 1. Let dateSign be DateDurationSign(dateDuration). // 2. Let timeSign be TimeDurationSign(timeDuration). // 3. Assert: If dateSign ≠ 0 and timeSign ≠ 0, dateSign = timeSign. @@ -271,12 +274,11 @@ impl NormalizedDurationRecord { /// Creates a new `NormalizedDurationRecord`. /// - /// Equivalent: `CreateNormalizedDurationRecord` & `CombineDateAndNormalizedTimeDuration`. - pub(crate) fn new(date: DateDuration, norm: NormalizedTimeDuration) -> TemporalResult { + /// Equivalent: `CreateNormalizedDurationRecord` & `CombineDateAndTimeDuration`. + pub(crate) fn new(date: DateDuration, norm: TimeDuration) -> 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 if both are not zero.", - )); + return Err(TemporalError::range() + .with_message("DateDuration and TimeDuration must agree if both are not zero.")); } Ok(Self { date, norm }) } @@ -287,7 +289,7 @@ impl NormalizedDurationRecord { pub(crate) fn from_duration_with_24_hour_days(duration: &Duration) -> TemporalResult { // 1. Let timeDuration be TimeDurationFromComponents(duration.[[Hours]], duration.[[Minutes]], // duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]]). - let normalized_time = NormalizedTimeDuration::from_duration(duration); + let normalized_time = TimeDuration::from_duration(duration); // 2. Set timeDuration to ! Add24HourDaysToTimeDuration(timeDuration, duration.[[Days]]). let normalized_time = normalized_time.add_days(duration.days())?; // 3. Let dateDuration be ! CreateDateDurationRecord(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], 0). @@ -322,14 +324,14 @@ impl NormalizedDurationRecord { } pub(crate) fn from_date_duration(date: DateDuration) -> TemporalResult { - Self::new(date, NormalizedTimeDuration::default()) + Self::new(date, TimeDuration::default()) } pub(crate) fn date(&self) -> DateDuration { self.date } - pub(crate) fn normalized_time_duration(&self) -> NormalizedTimeDuration { + pub(crate) fn normalized_time_duration(&self) -> TimeDuration { self.norm } @@ -352,13 +354,13 @@ impl NormalizedDurationRecord { #[derive(Debug)] struct NudgeRecord { - normalized: NormalizedDurationRecord, + normalized: InternalDurationRecord, total: Option, nudge_epoch_ns: i128, expanded: bool, } -impl NormalizedDurationRecord { +impl InternalDurationRecord { // TODO: Add assertion into impl. // TODO: Add unit tests specifically for nudge_calendar_unit if possible. fn nudge_calendar_unit( @@ -619,10 +621,7 @@ impl NormalizedDurationRecord { // b. Let resultDuration be endDuration. // c. Let nudgedEpochNs be endEpochNs. Ok(NudgeRecord { - normalized: NormalizedDurationRecord::new( - end_duration, - NormalizedTimeDuration::default(), - )?, + normalized: InternalDurationRecord::new(end_duration, TimeDuration::default())?, total: Some(FiniteF64::try_from(total)?), nudge_epoch_ns: end_epoch_ns.0, expanded: true, @@ -633,10 +632,7 @@ impl NormalizedDurationRecord { // b. Let resultDuration be startDuration. // c. Let nudgedEpochNs be startEpochNs. Ok(NudgeRecord { - normalized: NormalizedDurationRecord::new( - start_duration, - NormalizedTimeDuration::default(), - )?, + normalized: InternalDurationRecord::new(start_duration, TimeDuration::default())?, total: Some(FiniteF64::try_from(total)?), nudge_epoch_ns: start_epoch_ns.0, expanded: false, @@ -677,7 +673,7 @@ impl NormalizedDurationRecord { // 6. Let endEpochNs be ? GetEpochNanosecondsFor(timeZone, endDateTime, compatible). let end_ns = tz.get_epoch_nanoseconds_for(end_dt, Disambiguation::Compatible, provider)?; // 7. Let daySpan be TimeDurationFromEpochNanosecondsDifference(endEpochNs, startEpochNs). - let day_span = NormalizedTimeDuration::from_nanosecond_difference(end_ns.0, start_ns.0)?; + let day_span = TimeDuration::from_nanosecond_difference(end_ns.0, start_ns.0)?; // 8. Assert: TimeDurationSign(daySpan) = sign. // 9. Let unitLength be the value in the "Length in Nanoseconds" column of the row of Table 21 whose "Value" column contains unit. let unit_length = options.smallest_unit.as_nanoseconds().temporal_unwrap()?; @@ -724,7 +720,7 @@ impl NormalizedDurationRecord { .ok_or(TemporalError::range())?, )?; // 15. Let resultDuration be CombineDateAndTimeDuration(dateDuration, roundedTimeDuration). - let normalized = NormalizedDurationRecord::new(date, rounded_time)?; + let normalized = InternalDurationRecord::new(date, rounded_time)?; // 16. Return Duration Nudge Result Record { [[Duration]]: resultDuration, [[NudgedEpochNs]]: nudgedEpochNs, [[DidExpandCalendarUnit]]: didRoundBeyondDay }. Ok(NudgeRecord { normalized, @@ -777,7 +773,7 @@ impl NormalizedDurationRecord { // a. Set days to roundedWholeDays. days = rounded_whole_days; // b. Set remainder to ! AddTimeDuration(roundedTime, TimeDurationFromComponents(-roundedWholeDays * HoursPerDay, 0, 0, 0, 0, 0)). - remainder = rounded_time.add(NormalizedTimeDuration::from_components( + remainder = rounded_time.add(TimeDuration::from_components( -rounded_whole_days * 24, 0, 0, @@ -816,7 +812,7 @@ impl NormalizedDurationRecord { calendar: &Calendar, largest_unit: Unit, smallest_unit: Unit, - ) -> TemporalResult { + ) -> TemporalResult { let mut duration = *self; // 1. If smallestUnit is largestUnit, return duration. @@ -918,7 +914,7 @@ impl NormalizedDurationRecord { // x. If beyondEndSign ≠ -sign, then if beyound_end_sign != -i128::from(sign.as_sign_multiplier()) { // 1. Set duration to CombineDateAndTimeDuration(endDuration, 0). - duration = NormalizedDurationRecord::from_date_duration(end_duration)?; + duration = InternalDurationRecord::from_date_duration(end_duration)?; } else { // 1. Set done to true. break; @@ -946,7 +942,7 @@ impl NormalizedDurationRecord { dt: &PlainDateTime, time_zone: Option<(&TimeZone, &impl TimeZoneProvider)>, options: ResolvedRoundingOptions, - ) -> TemporalResult { + ) -> TemporalResult { let duration = *self; // 1. Let irregularLengthUnit be false. diff --git a/src/builtins/core/instant.rs b/src/builtins/core/instant.rs index ccc37d8e0..ce5dbf6fd 100644 --- a/src/builtins/core/instant.rs +++ b/src/builtins/core/instant.rs @@ -23,7 +23,7 @@ use num_traits::Euclid; use writeable::Writeable; use super::{ - duration::normalized::{NormalizedDurationRecord, NormalizedTimeDuration}, + duration::normalized::{InternalDurationRecord, TimeDuration}, DateDuration, ZonedDateTime, }; @@ -161,7 +161,7 @@ impl Instant { /// Adds a `TimeDuration` to the current `Instant`. /// /// Temporal-Proposal equivalent: `AddInstant`. - pub(crate) fn add_to_instant(&self, duration: &NormalizedTimeDuration) -> TemporalResult { + pub(crate) fn add_to_instant(&self, duration: &TimeDuration) -> TemporalResult { // 1. Let result be AddTimeDurationToEpochNanoseconds(timeDuration, epochNanoseconds). let result = self.epoch_nanoseconds().0 + duration.0; let ns = EpochNanoseconds::from(result); @@ -181,8 +181,7 @@ impl Instant { return Err(TemporalError::range().with_enum(ErrorMessage::LargestUnitCannotBeDateUnit)); } // 5. Let internalDuration be ToInternalDurationRecordWith24HourDays(duration). - let internal_duration = - NormalizedDurationRecord::from_duration_with_24_hour_days(duration)?; + let internal_duration = InternalDurationRecord::from_duration_with_24_hour_days(duration)?; // 6. Let ns be ? AddInstant(instant.[[EpochNanoseconds]], internalDuration.[[Time]]). // 7. Return ! CreateTemporalInstant(ns). self.add_to_instant(&internal_duration.normalized_time_duration()) @@ -193,11 +192,10 @@ impl Instant { &self, other: &Self, resolved_options: ResolvedRoundingOptions, - ) -> TemporalResult { - let diff = - NormalizedTimeDuration::from_nanosecond_difference(other.as_i128(), self.as_i128())?; + ) -> TemporalResult { + let diff = TimeDuration::from_nanosecond_difference(other.as_i128(), self.as_i128())?; let normalized_time = diff.round(resolved_options)?; - NormalizedDurationRecord::new(DateDuration::default(), normalized_time) + InternalDurationRecord::new(DateDuration::default(), normalized_time) } // TODO: Add test for `diff_instant`. @@ -229,7 +227,7 @@ impl Instant { let result = Duration::from_internal(internal_record, resolved_options.largest_unit)?; - // 6. Let norm be diffRecord.[[NormalizedTimeDuration]]. + // 6. Let norm be diffRecord.[[TimeDuration]]. // 7. Let result be ! BalanceTimeDuration(norm, settings.[[LargestUnit]]). // 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]]). match op { diff --git a/src/builtins/core/time.rs b/src/builtins/core/time.rs index fd44169da..529347a32 100644 --- a/src/builtins/core/time.rs +++ b/src/builtins/core/time.rs @@ -1,7 +1,7 @@ //! This module implements `Time` and any directly related algorithms. use crate::{ - builtins::{core::Duration, duration::normalized::NormalizedDurationRecord}, + builtins::{core::Duration, duration::normalized::InternalDurationRecord}, iso::IsoTime, options::{ ArithmeticOverflow, DifferenceOperation, DifferenceSettings, ResolvedRoundingOptions, @@ -14,7 +14,7 @@ use alloc::string::String; use core::str::FromStr; use writeable::Writeable; -use super::{duration::normalized::NormalizedTimeDuration, PlainDateTime}; +use super::{duration::normalized::TimeDuration, PlainDateTime}; /// A `PartialTime` represents partially filled `Time` fields. #[derive(Debug, Default, Clone, Copy, PartialEq)] @@ -216,10 +216,10 @@ impl PlainTime { /// Specification equivalent to `4.5.15 AddTime ( time, timeDuration )` /// /// Spec: - pub(crate) fn add_normalized_time_duration(&self, norm: NormalizedTimeDuration) -> (i64, Self) { - // 1. Set second to second + NormalizedTimeDurationSeconds(norm). + pub(crate) fn add_normalized_time_duration(&self, norm: TimeDuration) -> (i64, Self) { + // 1. Set second to second + TimeDurationSeconds(norm). let second = i64::from(self.second()) + norm.seconds(); - // 2. Set nanosecond to nanosecond + NormalizedTimeDurationSubseconds(norm). + // 2. Set nanosecond to nanosecond + TimeDurationSubseconds(norm). let nanosecond = i32::from(self.nanosecond()) + norm.subseconds(); // 3. Return BalanceTime(hour, minute, second, millisecond, microsecond, nanosecond). let (day, balance_result) = IsoTime::balance( @@ -276,7 +276,7 @@ impl PlainTime { // 5. Set timeDuration to ! RoundTimeDuration(timeDuration, settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[RoundingMode]]). normalized_time = normalized_time.round(resolved)?; // 6. Let duration be CombineDateAndTimeDuration(ZeroDateDuration(), timeDuration). - let duration = NormalizedDurationRecord::combine(DateDuration::default(), normalized_time); + let duration = InternalDurationRecord::combine(DateDuration::default(), normalized_time); // 7. Let result be ! TemporalDurationFromInternal(duration, settings.[[LargestUnit]]). let result = Duration::from_internal(duration, resolved.largest_unit)?; // 8. If operation is since, set result to CreateNegatedTemporalDuration(result). diff --git a/src/builtins/core/timezone.rs b/src/builtins/core/timezone.rs index 5b79cf4f4..1f1d4e537 100644 --- a/src/builtins/core/timezone.rs +++ b/src/builtins/core/timezone.rs @@ -16,7 +16,7 @@ use crate::parsers::{ use crate::provider::{CandidateEpochNanoseconds, TimeZoneProvider}; use crate::Sign; use crate::{ - builtins::core::{duration::normalized::NormalizedTimeDuration, Instant}, + builtins::core::{duration::normalized::TimeDuration, Instant}, iso::{IsoDate, IsoDateTime, IsoTime}, options::Disambiguation, unix_time::EpochNanoseconds, @@ -448,7 +448,7 @@ impl TimeZone { // 16. If disambiguation is earlier, then if disambiguation == Disambiguation::Earlier { // a. Let timeDuration be TimeDurationFromComponents(0, 0, 0, 0, 0, -nanoseconds). - let time_duration = NormalizedTimeDuration(-nanoseconds); + let time_duration = TimeDuration(-nanoseconds); // b. Let earlierTime be AddTime(isoDateTime.[[Time]], timeDuration). let earlier_time = iso.time.add(time_duration); // c. Let earlierDate be BalanceISODate(isoDateTime.[[ISODate]].[[Year]], @@ -471,7 +471,7 @@ impl TimeZone { } // 17. Assert: disambiguation is compatible or later. // 18. Let timeDuration be TimeDurationFromComponents(0, 0, 0, 0, 0, nanoseconds). - let time_duration = NormalizedTimeDuration(nanoseconds); + let time_duration = TimeDuration(nanoseconds); // 19. Let laterTime be AddTime(isoDateTime.[[Time]], timeDuration). let later_time = iso.time.add(time_duration); // 20. Let laterDate be BalanceISODate(isoDateTime.[[ISODate]].[[Year]], diff --git a/src/builtins/core/year_month.rs b/src/builtins/core/year_month.rs index be6a349dd..c4819e341 100644 --- a/src/builtins/core/year_month.rs +++ b/src/builtins/core/year_month.rs @@ -22,8 +22,7 @@ use crate::{ }; use super::{ - duration::normalized::NormalizedDurationRecord, DateDuration, Duration, PlainDate, - PlainDateTime, + duration::normalized::InternalDurationRecord, DateDuration, Duration, PlainDate, PlainDateTime, }; use writeable::Writeable; @@ -319,7 +318,7 @@ impl PlainYearMonth { let result = result.date().adjust(0, Some(0), None)?; // 15. Let duration be CombineDateAndTimeDuration(yearsMonthsDifference, 0). - let mut duration = NormalizedDurationRecord::from_date_duration(result)?; + let mut duration = InternalDurationRecord::from_date_duration(result)?; // 16. If settings.[[SmallestUnit]] is not month or settings.[[RoundingIncrement]] ≠ 1, then if resolved.smallest_unit != Unit::Month || resolved.increment != RoundingIncrement::ONE { diff --git a/src/builtins/core/zoneddatetime.rs b/src/builtins/core/zoneddatetime.rs index f693b21e8..398097b6d 100644 --- a/src/builtins/core/zoneddatetime.rs +++ b/src/builtins/core/zoneddatetime.rs @@ -10,7 +10,7 @@ use crate::{ calendar::CalendarFields, core::{ calendar::Calendar, - duration::normalized::{NormalizedDurationRecord, NormalizedTimeDuration}, + duration::normalized::{InternalDurationRecord, TimeDuration}, timezone::{TimeZone, UtcOffset}, Duration, Instant, PlainDate, PlainDateTime, PlainTime, }, @@ -269,7 +269,7 @@ impl ZonedDateTime { pub(crate) fn add_zoned_date_time( &self, - duration: NormalizedDurationRecord, + duration: InternalDurationRecord, overflow: ArithmeticOverflow, provider: &impl TimeZoneProvider, ) -> TemporalResult { @@ -339,7 +339,7 @@ impl ZonedDateTime { other: &Self, resolved_options: ResolvedRoundingOptions, provider: &impl TimeZoneProvider, - ) -> TemporalResult { + ) -> TemporalResult { // 1. If UnitCategory(largestUnit) is time, then if resolved_options.largest_unit.is_time_unit() { // a. Return DifferenceInstant(ns1, ns2, roundingIncrement, smallestUnit, roundingMode). @@ -378,7 +378,7 @@ impl ZonedDateTime { // 1. If UnitCategory(unit) is time, then if unit.is_time_unit() { // a. Let difference be TimeDurationFromEpochNanosecondsDifference(ns2, ns1). - let diff = NormalizedTimeDuration::from_nanosecond_difference( + let diff = TimeDuration::from_nanosecond_difference( other.epoch_nanoseconds().as_i128(), self.epoch_nanoseconds().as_i128(), )?; @@ -406,10 +406,10 @@ impl ZonedDateTime { other: &Self, largest_unit: Unit, provider: &impl TimeZoneProvider, - ) -> TemporalResult { + ) -> TemporalResult { // 1. If ns1 = ns2, return CombineDateAndTimeDuration(ZeroDateDuration(), 0). if self.epoch_nanoseconds() == other.epoch_nanoseconds() { - return Ok(NormalizedDurationRecord::default()); + return Ok(InternalDurationRecord::default()); } // 2. Let startDateTime be GetISODateTimeFor(timeZone, ns1). let start = self.tz.get_iso_datetime_for(&self.instant, provider)?; @@ -435,7 +435,7 @@ impl ZonedDateTime { // 9. Let success be false. let mut intermediate_dt = IsoDateTime::default(); - let mut time_duration = NormalizedTimeDuration::default(); + let mut time_duration = TimeDuration::default(); let mut is_success = false; // 10. Repeat, while dayCorrection ≤ maxDayCorrection and success is false, while day_correction <= max_correction && !is_success { @@ -455,7 +455,7 @@ impl ZonedDateTime { provider, )?; // d. Set timeDuration to TimeDurationFromEpochNanosecondsDifference(ns2, intermediateNs). - time_duration = NormalizedTimeDuration::from_nanosecond_difference( + time_duration = TimeDuration::from_nanosecond_difference( other.epoch_nanoseconds().as_i128(), intermediate_ns.0, )?; @@ -477,7 +477,7 @@ impl ZonedDateTime { let date_diff = self.calendar() .date_until(&start.date, &intermediate_dt.date, date_largest)?; - NormalizedDurationRecord::new(date_diff.date(), time_duration) + InternalDurationRecord::new(date_diff.date(), time_duration) } /// `temporal_rs` equivalent to `DifferenceTemporalZonedDateTime`. @@ -793,7 +793,7 @@ impl ZonedDateTime { // 8. Let tomorrowNs be ? GetStartOfDay(timeZone, tomorrow). let tomorrow_ns = self.tz.get_start_of_day(&tomorrow, provider)?; // 9. Let diff be TimeDurationFromEpochNanosecondsDifference(tomorrowNs, todayNs). - let diff = NormalizedTimeDuration::from_nanosecond_difference(tomorrow_ns.0, today_ns.0)?; + let diff = TimeDuration::from_nanosecond_difference(tomorrow_ns.0, today_ns.0)?; // NOTE: The below should be safe as today_ns and tomorrow_ns should be at most 25 hours. // TODO: Tests for the below cast. // 10. Return 𝔽(TotalTimeDuration(diff, hour)). @@ -1254,10 +1254,8 @@ impl ZonedDateTime { } // g. Let dayLengthNs be ℝ(endNs - startNs). // h. Let dayProgressNs be TimeDurationFromEpochNanosecondsDifference(thisNs, startNs). - let day_len_ns = - NormalizedTimeDuration::from_nanosecond_difference(end_ns.0, start_ns.0)?; - let day_progress_ns = - NormalizedTimeDuration::from_nanosecond_difference(this_ns.0, start_ns.0)?; + let day_len_ns = TimeDuration::from_nanosecond_difference(end_ns.0, start_ns.0)?; + let day_progress_ns = TimeDuration::from_nanosecond_difference(this_ns.0, start_ns.0)?; // i. Let roundedDayNs be ! RoundTimeDurationToIncrement(dayProgressNs, dayLengthNs, roundingMode). let rounded = if let Some(increment) = NonZeroU128::new(day_len_ns.0.unsigned_abs()) { IncrementRounder::::from_signed_num(day_progress_ns.0, increment)? diff --git a/src/iso.rs b/src/iso.rs index 1977acffa..83eff3a19 100644 --- a/src/iso.rs +++ b/src/iso.rs @@ -29,7 +29,7 @@ use crate::{ builtins::core::{ calendar::Calendar, duration::{ - normalized::{NormalizedDurationRecord, NormalizedTimeDuration}, + normalized::{InternalDurationRecord, TimeDuration}, DateDuration, }, PartialTime, PlainDate, @@ -176,7 +176,7 @@ impl IsoDateTime { other: &Self, calendar: &Calendar, largest_unit: Unit, - ) -> TemporalResult { + ) -> 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 LargerOfTwoUnits(largestUnit, "day") @@ -185,7 +185,7 @@ impl IsoDateTime { // 4. Let timeDuration be DifferenceTime(h1, min1, s1, ms1, mus1, ns1, h2, min2, s2, ms2, mus2, ns2). let mut time_duration = self.time.diff(&other.time); - // 5. Let timeSign be NormalizedTimeDurationSign(timeDuration). + // 5. Let timeSign be TimeDurationSign(timeDuration). let time_sign = time_duration.sign() as i8; // 6. Let dateSign be CompareISODate(y2, mon2, d2, y1, mon1, d1). @@ -201,7 +201,7 @@ impl IsoDateTime { i32::from(adjusted_date.month), i32::from(adjusted_date.day) + i32::from(time_sign), ); - // b. Set timeDuration to ? Add24HourDaysToNormalizedTimeDuration(timeDuration, -timeSign). + // b. Set timeDuration to ? Add24HourDaysToTimeDuration(timeDuration, -timeSign). time_duration = time_duration.add_days(-i64::from(time_sign))?; } @@ -229,14 +229,14 @@ impl IsoDateTime { // 15. Let days be dateDifference.[[Days]]. date_diff.days() } else { - // a. Set timeDuration to ? Add24HourDaysToNormalizedTimeDuration(timeDuration, dateDifference.[[Days]]). + // a. Set timeDuration to ? Add24HourDaysToTimeDuration(timeDuration, dateDifference.[[Days]]). time_duration = time_duration.add_days(date_diff.days())?; // b. Set days to 0. 0 }; // 17. Return ? CreateNormalizedDurationRecord(dateDifference.[[Years]], dateDifference.[[Months]], dateDifference.[[Weeks]], days, timeDuration). - NormalizedDurationRecord::new( + InternalDurationRecord::new( DateDuration::new_unchecked( date_diff.years(), date_diff.months(), @@ -697,7 +697,7 @@ impl IsoTime { } /// Difference this `IsoTime` against another and returning a `TimeDuration`. - pub(crate) fn diff(&self, other: &Self) -> NormalizedTimeDuration { + pub(crate) fn diff(&self, other: &Self) -> TimeDuration { let h = i64::from(other.hour) - i64::from(self.hour); let m = i64::from(other.minute) - i64::from(self.minute); let s = i64::from(other.second) - i64::from(self.second); @@ -705,7 +705,7 @@ impl IsoTime { let mis = i128::from(other.microsecond) - i128::from(self.microsecond); let ns = i128::from(other.nanosecond) - i128::from(self.nanosecond); - NormalizedTimeDuration::from_components(h, m, s, ms, mis, ns) + TimeDuration::from_components(h, m, s, ms, mis, ns) } // NOTE (nekevss): Specification seemed to be off / not entirely working, so the below was adapted from the @@ -859,10 +859,10 @@ impl IsoTime { && sub_second.contains(&self.nanosecond) } - pub(crate) fn add(&self, norm: NormalizedTimeDuration) -> (i64, Self) { - // 1. Set second to second + NormalizedTimeDurationSeconds(norm). + pub(crate) fn add(&self, norm: TimeDuration) -> (i64, Self) { + // 1. Set second to second + TimeDurationSeconds(norm). let seconds = i64::from(self.second) + norm.seconds(); - // 2. Set nanosecond to nanosecond + NormalizedTimeDurationSubseconds(norm). + // 2. Set nanosecond to nanosecond + TimeDurationSubseconds(norm). let nanos = i32::from(self.nanosecond) + norm.subseconds(); // 3. Return BalanceTime(hour, minute, second, millisecond, microsecond, nanosecond). Self::balance( From 888c8d792d11408f3749185a25bc0ba485bd1448 Mon Sep 17 00:00:00 2001 From: Kevin Ness Date: Mon, 11 Aug 2025 19:44:49 -0500 Subject: [PATCH 3/6] Missed a doctest --- src/builtins/core/instant.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/builtins/core/instant.rs b/src/builtins/core/instant.rs index ce5dbf6fd..216a35174 100644 --- a/src/builtins/core/instant.rs +++ b/src/builtins/core/instant.rs @@ -83,7 +83,7 @@ const NANOSECONDS_PER_HOUR: i64 = 60 * NANOSECONDS_PER_MINUTE; /// let instant = Instant::try_new(1609459200000000000).unwrap(); // 2021-01-01T00:00:00Z /// /// // Add time duration (only time durations, not date durations) -/// let later = instant.add(Duration::from_str("PT1H30M").unwrap()).unwrap(); +/// let later = instant.add(&Duration::from_str("PT1H30M").unwrap()).unwrap(); /// let expected_ns = 1609459200000000000 + (1 * 3600 + 30 * 60) * 1_000_000_000; /// assert_eq!(later.epoch_nanoseconds().as_i128(), expected_ns); /// From 8d18899ff59d2eeab71987970022b3ce957d2b93 Mon Sep 17 00:00:00 2001 From: Kevin Ness Date: Mon, 11 Aug 2025 19:50:22 -0500 Subject: [PATCH 4/6] Update temporal_capi bindings --- temporal_capi/bindings/c/Duration.h | 6 -- temporal_capi/bindings/c/Instant.h | 7 -- temporal_capi/bindings/c/PlainTime.h | 7 -- temporal_capi/bindings/c/TimeDuration.d.h | 19 ---- temporal_capi/bindings/c/TimeDuration.h | 37 -------- .../bindings/cpp/temporal_rs/Duration.d.hpp | 8 -- .../bindings/cpp/temporal_rs/Duration.hpp | 16 ---- .../bindings/cpp/temporal_rs/Instant.d.hpp | 6 -- .../bindings/cpp/temporal_rs/Instant.hpp | 19 ---- .../bindings/cpp/temporal_rs/PlainTime.d.hpp | 6 -- .../bindings/cpp/temporal_rs/PlainTime.hpp | 19 ---- .../cpp/temporal_rs/TimeDuration.d.hpp | 57 ------------ .../bindings/cpp/temporal_rs/TimeDuration.hpp | 91 ------------------- 13 files changed, 298 deletions(-) delete mode 100644 temporal_capi/bindings/c/TimeDuration.d.h delete mode 100644 temporal_capi/bindings/c/TimeDuration.h delete mode 100644 temporal_capi/bindings/cpp/temporal_rs/TimeDuration.d.hpp delete mode 100644 temporal_capi/bindings/cpp/temporal_rs/TimeDuration.hpp diff --git a/temporal_capi/bindings/c/Duration.h b/temporal_capi/bindings/c/Duration.h index a7a648ca5..4d184341a 100644 --- a/temporal_capi/bindings/c/Duration.h +++ b/temporal_capi/bindings/c/Duration.h @@ -7,13 +7,11 @@ #include #include "diplomat_runtime.h" -#include "DateDuration.d.h" #include "PartialDuration.d.h" #include "RelativeTo.d.h" #include "RoundingOptions.d.h" #include "Sign.d.h" #include "TemporalError.d.h" -#include "TimeDuration.d.h" #include "ToStringRoundingOptions.d.h" #include "Unit.d.h" @@ -41,10 +39,6 @@ temporal_rs_Duration_from_utf16_result temporal_rs_Duration_from_utf16(DiplomatS bool temporal_rs_Duration_is_time_within_range(const Duration* self); -const TimeDuration* temporal_rs_Duration_time(const Duration* self); - -const DateDuration* temporal_rs_Duration_date(const Duration* self); - int64_t temporal_rs_Duration_years(const Duration* self); int64_t temporal_rs_Duration_months(const Duration* self); diff --git a/temporal_capi/bindings/c/Instant.h b/temporal_capi/bindings/c/Instant.h index 1157951a2..dd4626ccf 100644 --- a/temporal_capi/bindings/c/Instant.h +++ b/temporal_capi/bindings/c/Instant.h @@ -12,7 +12,6 @@ #include "I128Nanoseconds.d.h" #include "RoundingOptions.d.h" #include "TemporalError.d.h" -#include "TimeDuration.d.h" #include "TimeZone.d.h" #include "ToStringRoundingOptions.d.h" #include "ZonedDateTime.d.h" @@ -39,15 +38,9 @@ temporal_rs_Instant_from_utf16_result temporal_rs_Instant_from_utf16(DiplomatStr typedef struct temporal_rs_Instant_add_result {union {Instant* ok; TemporalError err;}; bool is_ok;} temporal_rs_Instant_add_result; temporal_rs_Instant_add_result temporal_rs_Instant_add(const Instant* self, const Duration* duration); -typedef struct temporal_rs_Instant_add_time_duration_result {union {Instant* ok; TemporalError err;}; bool is_ok;} temporal_rs_Instant_add_time_duration_result; -temporal_rs_Instant_add_time_duration_result temporal_rs_Instant_add_time_duration(const Instant* self, const TimeDuration* duration); - typedef struct temporal_rs_Instant_subtract_result {union {Instant* ok; TemporalError err;}; bool is_ok;} temporal_rs_Instant_subtract_result; temporal_rs_Instant_subtract_result temporal_rs_Instant_subtract(const Instant* self, const Duration* duration); -typedef struct temporal_rs_Instant_subtract_time_duration_result {union {Instant* ok; TemporalError err;}; bool is_ok;} temporal_rs_Instant_subtract_time_duration_result; -temporal_rs_Instant_subtract_time_duration_result temporal_rs_Instant_subtract_time_duration(const Instant* self, const TimeDuration* duration); - typedef struct temporal_rs_Instant_since_result {union {Duration* ok; TemporalError err;}; bool is_ok;} temporal_rs_Instant_since_result; temporal_rs_Instant_since_result temporal_rs_Instant_since(const Instant* self, const Instant* other, DifferenceSettings settings); diff --git a/temporal_capi/bindings/c/PlainTime.h b/temporal_capi/bindings/c/PlainTime.h index 75fa84cda..5d2acf431 100644 --- a/temporal_capi/bindings/c/PlainTime.h +++ b/temporal_capi/bindings/c/PlainTime.h @@ -13,7 +13,6 @@ #include "PartialTime.d.h" #include "RoundingMode.d.h" #include "TemporalError.d.h" -#include "TimeDuration.d.h" #include "TimeZone.d.h" #include "ToStringRoundingOptions.d.h" #include "Unit.d.h" @@ -64,12 +63,6 @@ temporal_rs_PlainTime_add_result temporal_rs_PlainTime_add(const PlainTime* self typedef struct temporal_rs_PlainTime_subtract_result {union {PlainTime* ok; TemporalError err;}; bool is_ok;} temporal_rs_PlainTime_subtract_result; temporal_rs_PlainTime_subtract_result temporal_rs_PlainTime_subtract(const PlainTime* self, const Duration* duration); -typedef struct temporal_rs_PlainTime_add_time_duration_result {union {PlainTime* ok; TemporalError err;}; bool is_ok;} temporal_rs_PlainTime_add_time_duration_result; -temporal_rs_PlainTime_add_time_duration_result temporal_rs_PlainTime_add_time_duration(const PlainTime* self, const TimeDuration* duration); - -typedef struct temporal_rs_PlainTime_subtract_time_duration_result {union {PlainTime* ok; TemporalError err;}; bool is_ok;} temporal_rs_PlainTime_subtract_time_duration_result; -temporal_rs_PlainTime_subtract_time_duration_result temporal_rs_PlainTime_subtract_time_duration(const PlainTime* self, const TimeDuration* duration); - typedef struct temporal_rs_PlainTime_until_result {union {Duration* ok; TemporalError err;}; bool is_ok;} temporal_rs_PlainTime_until_result; temporal_rs_PlainTime_until_result temporal_rs_PlainTime_until(const PlainTime* self, const PlainTime* other, DifferenceSettings settings); diff --git a/temporal_capi/bindings/c/TimeDuration.d.h b/temporal_capi/bindings/c/TimeDuration.d.h deleted file mode 100644 index f7211713e..000000000 --- a/temporal_capi/bindings/c/TimeDuration.d.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef TimeDuration_D_H -#define TimeDuration_D_H - -#include -#include -#include -#include -#include "diplomat_runtime.h" - - - - - -typedef struct TimeDuration TimeDuration; - - - - -#endif // TimeDuration_D_H diff --git a/temporal_capi/bindings/c/TimeDuration.h b/temporal_capi/bindings/c/TimeDuration.h deleted file mode 100644 index 091f830db..000000000 --- a/temporal_capi/bindings/c/TimeDuration.h +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef TimeDuration_H -#define TimeDuration_H - -#include -#include -#include -#include -#include "diplomat_runtime.h" - -#include "Sign.d.h" -#include "TemporalError.d.h" - -#include "TimeDuration.d.h" - - - - - - -typedef struct temporal_rs_TimeDuration_try_new_result {union {TimeDuration* ok; TemporalError err;}; bool is_ok;} temporal_rs_TimeDuration_try_new_result; -temporal_rs_TimeDuration_try_new_result temporal_rs_TimeDuration_try_new(int64_t hours, int64_t minutes, int64_t seconds, int64_t milliseconds, double microseconds, double nanoseconds); - -TimeDuration* temporal_rs_TimeDuration_abs(const TimeDuration* self); - -TimeDuration* temporal_rs_TimeDuration_negated(const TimeDuration* self); - -bool temporal_rs_TimeDuration_is_within_range(const TimeDuration* self); - -Sign temporal_rs_TimeDuration_sign(const TimeDuration* self); - -void temporal_rs_TimeDuration_destroy(TimeDuration* self); - - - - - -#endif // TimeDuration_H diff --git a/temporal_capi/bindings/cpp/temporal_rs/Duration.d.hpp b/temporal_capi/bindings/cpp/temporal_rs/Duration.d.hpp index e3e4c80bd..dd672538b 100644 --- a/temporal_capi/bindings/cpp/temporal_rs/Duration.d.hpp +++ b/temporal_capi/bindings/cpp/temporal_rs/Duration.d.hpp @@ -12,12 +12,8 @@ #include "../diplomat_runtime.hpp" namespace temporal_rs { -namespace capi { struct DateDuration; } -class DateDuration; namespace capi { struct Duration; } class Duration; -namespace capi { struct TimeDuration; } -class TimeDuration; struct PartialDuration; struct RelativeTo; struct RoundingOptions; @@ -53,10 +49,6 @@ class Duration { inline bool is_time_within_range() const; - inline const temporal_rs::TimeDuration& time() const; - - inline const temporal_rs::DateDuration& date() const; - inline int64_t years() const; inline int64_t months() const; diff --git a/temporal_capi/bindings/cpp/temporal_rs/Duration.hpp b/temporal_capi/bindings/cpp/temporal_rs/Duration.hpp index 0a7900e1e..7e1aeb213 100644 --- a/temporal_capi/bindings/cpp/temporal_rs/Duration.hpp +++ b/temporal_capi/bindings/cpp/temporal_rs/Duration.hpp @@ -12,13 +12,11 @@ #include #include #include "../diplomat_runtime.hpp" -#include "DateDuration.hpp" #include "PartialDuration.hpp" #include "RelativeTo.hpp" #include "RoundingOptions.hpp" #include "Sign.hpp" #include "TemporalError.hpp" -#include "TimeDuration.hpp" #include "ToStringRoundingOptions.hpp" #include "Unit.hpp" @@ -44,10 +42,6 @@ namespace capi { bool temporal_rs_Duration_is_time_within_range(const temporal_rs::capi::Duration* self); - const temporal_rs::capi::TimeDuration* temporal_rs_Duration_time(const temporal_rs::capi::Duration* self); - - const temporal_rs::capi::DateDuration* temporal_rs_Duration_date(const temporal_rs::capi::Duration* self); - int64_t temporal_rs_Duration_years(const temporal_rs::capi::Duration* self); int64_t temporal_rs_Duration_months(const temporal_rs::capi::Duration* self); @@ -150,16 +144,6 @@ inline bool temporal_rs::Duration::is_time_within_range() const { return result; } -inline const temporal_rs::TimeDuration& temporal_rs::Duration::time() const { - auto result = temporal_rs::capi::temporal_rs_Duration_time(this->AsFFI()); - return *temporal_rs::TimeDuration::FromFFI(result); -} - -inline const temporal_rs::DateDuration& temporal_rs::Duration::date() const { - auto result = temporal_rs::capi::temporal_rs_Duration_date(this->AsFFI()); - return *temporal_rs::DateDuration::FromFFI(result); -} - inline int64_t temporal_rs::Duration::years() const { auto result = temporal_rs::capi::temporal_rs_Duration_years(this->AsFFI()); return result; diff --git a/temporal_capi/bindings/cpp/temporal_rs/Instant.d.hpp b/temporal_capi/bindings/cpp/temporal_rs/Instant.d.hpp index 3767533a8..6b0e80fb5 100644 --- a/temporal_capi/bindings/cpp/temporal_rs/Instant.d.hpp +++ b/temporal_capi/bindings/cpp/temporal_rs/Instant.d.hpp @@ -16,8 +16,6 @@ namespace capi { struct Duration; } class Duration; namespace capi { struct Instant; } class Instant; -namespace capi { struct TimeDuration; } -class TimeDuration; namespace capi { struct TimeZone; } class TimeZone; namespace capi { struct ZonedDateTime; } @@ -50,12 +48,8 @@ class Instant { inline diplomat::result, temporal_rs::TemporalError> add(const temporal_rs::Duration& duration) const; - inline diplomat::result, temporal_rs::TemporalError> add_time_duration(const temporal_rs::TimeDuration& duration) const; - inline diplomat::result, temporal_rs::TemporalError> subtract(const temporal_rs::Duration& duration) const; - inline diplomat::result, temporal_rs::TemporalError> subtract_time_duration(const temporal_rs::TimeDuration& duration) const; - inline diplomat::result, temporal_rs::TemporalError> since(const temporal_rs::Instant& other, temporal_rs::DifferenceSettings settings) const; inline diplomat::result, temporal_rs::TemporalError> until(const temporal_rs::Instant& other, temporal_rs::DifferenceSettings settings) const; diff --git a/temporal_capi/bindings/cpp/temporal_rs/Instant.hpp b/temporal_capi/bindings/cpp/temporal_rs/Instant.hpp index dae39a6f1..cb9530491 100644 --- a/temporal_capi/bindings/cpp/temporal_rs/Instant.hpp +++ b/temporal_capi/bindings/cpp/temporal_rs/Instant.hpp @@ -17,7 +17,6 @@ #include "I128Nanoseconds.hpp" #include "RoundingOptions.hpp" #include "TemporalError.hpp" -#include "TimeDuration.hpp" #include "TimeZone.hpp" #include "ToStringRoundingOptions.hpp" #include "ZonedDateTime.hpp" @@ -42,15 +41,9 @@ namespace capi { typedef struct temporal_rs_Instant_add_result {union {temporal_rs::capi::Instant* ok; temporal_rs::capi::TemporalError err;}; bool is_ok;} temporal_rs_Instant_add_result; temporal_rs_Instant_add_result temporal_rs_Instant_add(const temporal_rs::capi::Instant* self, const temporal_rs::capi::Duration* duration); - typedef struct temporal_rs_Instant_add_time_duration_result {union {temporal_rs::capi::Instant* ok; temporal_rs::capi::TemporalError err;}; bool is_ok;} temporal_rs_Instant_add_time_duration_result; - temporal_rs_Instant_add_time_duration_result temporal_rs_Instant_add_time_duration(const temporal_rs::capi::Instant* self, const temporal_rs::capi::TimeDuration* duration); - typedef struct temporal_rs_Instant_subtract_result {union {temporal_rs::capi::Instant* ok; temporal_rs::capi::TemporalError err;}; bool is_ok;} temporal_rs_Instant_subtract_result; temporal_rs_Instant_subtract_result temporal_rs_Instant_subtract(const temporal_rs::capi::Instant* self, const temporal_rs::capi::Duration* duration); - typedef struct temporal_rs_Instant_subtract_time_duration_result {union {temporal_rs::capi::Instant* ok; temporal_rs::capi::TemporalError err;}; bool is_ok;} temporal_rs_Instant_subtract_time_duration_result; - temporal_rs_Instant_subtract_time_duration_result temporal_rs_Instant_subtract_time_duration(const temporal_rs::capi::Instant* self, const temporal_rs::capi::TimeDuration* duration); - typedef struct temporal_rs_Instant_since_result {union {temporal_rs::capi::Duration* ok; temporal_rs::capi::TemporalError err;}; bool is_ok;} temporal_rs_Instant_since_result; temporal_rs_Instant_since_result temporal_rs_Instant_since(const temporal_rs::capi::Instant* self, const temporal_rs::capi::Instant* other, temporal_rs::capi::DifferenceSettings settings); @@ -107,24 +100,12 @@ inline diplomat::result, temporal_rs::Temp return result.is_ok ? diplomat::result, temporal_rs::TemporalError>(diplomat::Ok>(std::unique_ptr(temporal_rs::Instant::FromFFI(result.ok)))) : diplomat::result, temporal_rs::TemporalError>(diplomat::Err(temporal_rs::TemporalError::FromFFI(result.err))); } -inline diplomat::result, temporal_rs::TemporalError> temporal_rs::Instant::add_time_duration(const temporal_rs::TimeDuration& duration) const { - auto result = temporal_rs::capi::temporal_rs_Instant_add_time_duration(this->AsFFI(), - duration.AsFFI()); - return result.is_ok ? diplomat::result, temporal_rs::TemporalError>(diplomat::Ok>(std::unique_ptr(temporal_rs::Instant::FromFFI(result.ok)))) : diplomat::result, temporal_rs::TemporalError>(diplomat::Err(temporal_rs::TemporalError::FromFFI(result.err))); -} - inline diplomat::result, temporal_rs::TemporalError> temporal_rs::Instant::subtract(const temporal_rs::Duration& duration) const { auto result = temporal_rs::capi::temporal_rs_Instant_subtract(this->AsFFI(), duration.AsFFI()); return result.is_ok ? diplomat::result, temporal_rs::TemporalError>(diplomat::Ok>(std::unique_ptr(temporal_rs::Instant::FromFFI(result.ok)))) : diplomat::result, temporal_rs::TemporalError>(diplomat::Err(temporal_rs::TemporalError::FromFFI(result.err))); } -inline diplomat::result, temporal_rs::TemporalError> temporal_rs::Instant::subtract_time_duration(const temporal_rs::TimeDuration& duration) const { - auto result = temporal_rs::capi::temporal_rs_Instant_subtract_time_duration(this->AsFFI(), - duration.AsFFI()); - return result.is_ok ? diplomat::result, temporal_rs::TemporalError>(diplomat::Ok>(std::unique_ptr(temporal_rs::Instant::FromFFI(result.ok)))) : diplomat::result, temporal_rs::TemporalError>(diplomat::Err(temporal_rs::TemporalError::FromFFI(result.err))); -} - inline diplomat::result, temporal_rs::TemporalError> temporal_rs::Instant::since(const temporal_rs::Instant& other, temporal_rs::DifferenceSettings settings) const { auto result = temporal_rs::capi::temporal_rs_Instant_since(this->AsFFI(), other.AsFFI(), diff --git a/temporal_capi/bindings/cpp/temporal_rs/PlainTime.d.hpp b/temporal_capi/bindings/cpp/temporal_rs/PlainTime.d.hpp index d9c4af286..29d9fb365 100644 --- a/temporal_capi/bindings/cpp/temporal_rs/PlainTime.d.hpp +++ b/temporal_capi/bindings/cpp/temporal_rs/PlainTime.d.hpp @@ -16,8 +16,6 @@ namespace capi { struct Duration; } class Duration; namespace capi { struct PlainTime; } class PlainTime; -namespace capi { struct TimeDuration; } -class TimeDuration; namespace capi { struct TimeZone; } class TimeZone; struct DifferenceSettings; @@ -70,10 +68,6 @@ class PlainTime { inline diplomat::result, temporal_rs::TemporalError> subtract(const temporal_rs::Duration& duration) const; - inline diplomat::result, temporal_rs::TemporalError> add_time_duration(const temporal_rs::TimeDuration& duration) const; - - inline diplomat::result, temporal_rs::TemporalError> subtract_time_duration(const temporal_rs::TimeDuration& duration) const; - inline diplomat::result, temporal_rs::TemporalError> until(const temporal_rs::PlainTime& other, temporal_rs::DifferenceSettings settings) const; inline diplomat::result, temporal_rs::TemporalError> since(const temporal_rs::PlainTime& other, temporal_rs::DifferenceSettings settings) const; diff --git a/temporal_capi/bindings/cpp/temporal_rs/PlainTime.hpp b/temporal_capi/bindings/cpp/temporal_rs/PlainTime.hpp index d19747434..e1ce7bfa1 100644 --- a/temporal_capi/bindings/cpp/temporal_rs/PlainTime.hpp +++ b/temporal_capi/bindings/cpp/temporal_rs/PlainTime.hpp @@ -18,7 +18,6 @@ #include "PartialTime.hpp" #include "RoundingMode.hpp" #include "TemporalError.hpp" -#include "TimeDuration.hpp" #include "TimeZone.hpp" #include "ToStringRoundingOptions.hpp" #include "Unit.hpp" @@ -67,12 +66,6 @@ namespace capi { typedef struct temporal_rs_PlainTime_subtract_result {union {temporal_rs::capi::PlainTime* ok; temporal_rs::capi::TemporalError err;}; bool is_ok;} temporal_rs_PlainTime_subtract_result; temporal_rs_PlainTime_subtract_result temporal_rs_PlainTime_subtract(const temporal_rs::capi::PlainTime* self, const temporal_rs::capi::Duration* duration); - typedef struct temporal_rs_PlainTime_add_time_duration_result {union {temporal_rs::capi::PlainTime* ok; temporal_rs::capi::TemporalError err;}; bool is_ok;} temporal_rs_PlainTime_add_time_duration_result; - temporal_rs_PlainTime_add_time_duration_result temporal_rs_PlainTime_add_time_duration(const temporal_rs::capi::PlainTime* self, const temporal_rs::capi::TimeDuration* duration); - - typedef struct temporal_rs_PlainTime_subtract_time_duration_result {union {temporal_rs::capi::PlainTime* ok; temporal_rs::capi::TemporalError err;}; bool is_ok;} temporal_rs_PlainTime_subtract_time_duration_result; - temporal_rs_PlainTime_subtract_time_duration_result temporal_rs_PlainTime_subtract_time_duration(const temporal_rs::capi::PlainTime* self, const temporal_rs::capi::TimeDuration* duration); - typedef struct temporal_rs_PlainTime_until_result {union {temporal_rs::capi::Duration* ok; temporal_rs::capi::TemporalError err;}; bool is_ok;} temporal_rs_PlainTime_until_result; temporal_rs_PlainTime_until_result temporal_rs_PlainTime_until(const temporal_rs::capi::PlainTime* self, const temporal_rs::capi::PlainTime* other, temporal_rs::capi::DifferenceSettings settings); @@ -188,18 +181,6 @@ inline diplomat::result, temporal_rs::Te return result.is_ok ? diplomat::result, temporal_rs::TemporalError>(diplomat::Ok>(std::unique_ptr(temporal_rs::PlainTime::FromFFI(result.ok)))) : diplomat::result, temporal_rs::TemporalError>(diplomat::Err(temporal_rs::TemporalError::FromFFI(result.err))); } -inline diplomat::result, temporal_rs::TemporalError> temporal_rs::PlainTime::add_time_duration(const temporal_rs::TimeDuration& duration) const { - auto result = temporal_rs::capi::temporal_rs_PlainTime_add_time_duration(this->AsFFI(), - duration.AsFFI()); - return result.is_ok ? diplomat::result, temporal_rs::TemporalError>(diplomat::Ok>(std::unique_ptr(temporal_rs::PlainTime::FromFFI(result.ok)))) : diplomat::result, temporal_rs::TemporalError>(diplomat::Err(temporal_rs::TemporalError::FromFFI(result.err))); -} - -inline diplomat::result, temporal_rs::TemporalError> temporal_rs::PlainTime::subtract_time_duration(const temporal_rs::TimeDuration& duration) const { - auto result = temporal_rs::capi::temporal_rs_PlainTime_subtract_time_duration(this->AsFFI(), - duration.AsFFI()); - return result.is_ok ? diplomat::result, temporal_rs::TemporalError>(diplomat::Ok>(std::unique_ptr(temporal_rs::PlainTime::FromFFI(result.ok)))) : diplomat::result, temporal_rs::TemporalError>(diplomat::Err(temporal_rs::TemporalError::FromFFI(result.err))); -} - inline diplomat::result, temporal_rs::TemporalError> temporal_rs::PlainTime::until(const temporal_rs::PlainTime& other, temporal_rs::DifferenceSettings settings) const { auto result = temporal_rs::capi::temporal_rs_PlainTime_until(this->AsFFI(), other.AsFFI(), diff --git a/temporal_capi/bindings/cpp/temporal_rs/TimeDuration.d.hpp b/temporal_capi/bindings/cpp/temporal_rs/TimeDuration.d.hpp deleted file mode 100644 index 5b7b40c1a..000000000 --- a/temporal_capi/bindings/cpp/temporal_rs/TimeDuration.d.hpp +++ /dev/null @@ -1,57 +0,0 @@ -#ifndef temporal_rs_TimeDuration_D_HPP -#define temporal_rs_TimeDuration_D_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include "../diplomat_runtime.hpp" - -namespace temporal_rs { -namespace capi { struct TimeDuration; } -class TimeDuration; -struct TemporalError; -class Sign; -} - - -namespace temporal_rs { -namespace capi { - struct TimeDuration; -} // namespace capi -} // namespace - -namespace temporal_rs { -class TimeDuration { -public: - - inline static diplomat::result, temporal_rs::TemporalError> try_new(int64_t hours, int64_t minutes, int64_t seconds, int64_t milliseconds, double microseconds, double nanoseconds); - - inline std::unique_ptr abs() const; - - inline std::unique_ptr negated() const; - - inline bool is_within_range() const; - - inline temporal_rs::Sign sign() const; - - inline const temporal_rs::capi::TimeDuration* AsFFI() const; - inline temporal_rs::capi::TimeDuration* AsFFI(); - inline static const temporal_rs::TimeDuration* FromFFI(const temporal_rs::capi::TimeDuration* ptr); - inline static temporal_rs::TimeDuration* FromFFI(temporal_rs::capi::TimeDuration* ptr); - inline static void operator delete(void* ptr); -private: - TimeDuration() = delete; - TimeDuration(const temporal_rs::TimeDuration&) = delete; - TimeDuration(temporal_rs::TimeDuration&&) noexcept = delete; - TimeDuration operator=(const temporal_rs::TimeDuration&) = delete; - TimeDuration operator=(temporal_rs::TimeDuration&&) noexcept = delete; - static void operator delete[](void*, size_t) = delete; -}; - -} // namespace -#endif // temporal_rs_TimeDuration_D_HPP diff --git a/temporal_capi/bindings/cpp/temporal_rs/TimeDuration.hpp b/temporal_capi/bindings/cpp/temporal_rs/TimeDuration.hpp deleted file mode 100644 index c435428ce..000000000 --- a/temporal_capi/bindings/cpp/temporal_rs/TimeDuration.hpp +++ /dev/null @@ -1,91 +0,0 @@ -#ifndef temporal_rs_TimeDuration_HPP -#define temporal_rs_TimeDuration_HPP - -#include "TimeDuration.d.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include "../diplomat_runtime.hpp" -#include "Sign.hpp" -#include "TemporalError.hpp" - - -namespace temporal_rs { -namespace capi { - extern "C" { - - typedef struct temporal_rs_TimeDuration_try_new_result {union {temporal_rs::capi::TimeDuration* ok; temporal_rs::capi::TemporalError err;}; bool is_ok;} temporal_rs_TimeDuration_try_new_result; - temporal_rs_TimeDuration_try_new_result temporal_rs_TimeDuration_try_new(int64_t hours, int64_t minutes, int64_t seconds, int64_t milliseconds, double microseconds, double nanoseconds); - - temporal_rs::capi::TimeDuration* temporal_rs_TimeDuration_abs(const temporal_rs::capi::TimeDuration* self); - - temporal_rs::capi::TimeDuration* temporal_rs_TimeDuration_negated(const temporal_rs::capi::TimeDuration* self); - - bool temporal_rs_TimeDuration_is_within_range(const temporal_rs::capi::TimeDuration* self); - - temporal_rs::capi::Sign temporal_rs_TimeDuration_sign(const temporal_rs::capi::TimeDuration* self); - - void temporal_rs_TimeDuration_destroy(TimeDuration* self); - - } // extern "C" -} // namespace capi -} // namespace - -inline diplomat::result, temporal_rs::TemporalError> temporal_rs::TimeDuration::try_new(int64_t hours, int64_t minutes, int64_t seconds, int64_t milliseconds, double microseconds, double nanoseconds) { - auto result = temporal_rs::capi::temporal_rs_TimeDuration_try_new(hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds); - return result.is_ok ? diplomat::result, temporal_rs::TemporalError>(diplomat::Ok>(std::unique_ptr(temporal_rs::TimeDuration::FromFFI(result.ok)))) : diplomat::result, temporal_rs::TemporalError>(diplomat::Err(temporal_rs::TemporalError::FromFFI(result.err))); -} - -inline std::unique_ptr temporal_rs::TimeDuration::abs() const { - auto result = temporal_rs::capi::temporal_rs_TimeDuration_abs(this->AsFFI()); - return std::unique_ptr(temporal_rs::TimeDuration::FromFFI(result)); -} - -inline std::unique_ptr temporal_rs::TimeDuration::negated() const { - auto result = temporal_rs::capi::temporal_rs_TimeDuration_negated(this->AsFFI()); - return std::unique_ptr(temporal_rs::TimeDuration::FromFFI(result)); -} - -inline bool temporal_rs::TimeDuration::is_within_range() const { - auto result = temporal_rs::capi::temporal_rs_TimeDuration_is_within_range(this->AsFFI()); - return result; -} - -inline temporal_rs::Sign temporal_rs::TimeDuration::sign() const { - auto result = temporal_rs::capi::temporal_rs_TimeDuration_sign(this->AsFFI()); - return temporal_rs::Sign::FromFFI(result); -} - -inline const temporal_rs::capi::TimeDuration* temporal_rs::TimeDuration::AsFFI() const { - return reinterpret_cast(this); -} - -inline temporal_rs::capi::TimeDuration* temporal_rs::TimeDuration::AsFFI() { - return reinterpret_cast(this); -} - -inline const temporal_rs::TimeDuration* temporal_rs::TimeDuration::FromFFI(const temporal_rs::capi::TimeDuration* ptr) { - return reinterpret_cast(ptr); -} - -inline temporal_rs::TimeDuration* temporal_rs::TimeDuration::FromFFI(temporal_rs::capi::TimeDuration* ptr) { - return reinterpret_cast(ptr); -} - -inline void temporal_rs::TimeDuration::operator delete(void* ptr) { - temporal_rs::capi::temporal_rs_TimeDuration_destroy(reinterpret_cast(ptr)); -} - - -#endif // temporal_rs_TimeDuration_HPP From ee488b1bda03ca1396ce2dde70fcb41d06953148 Mon Sep 17 00:00:00 2001 From: Kevin Ness Date: Wed, 13 Aug 2025 14:03:06 -0500 Subject: [PATCH 5/6] Add minimally viable test so CI fails --- src/builtins/core/instant.rs | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/builtins/core/instant.rs b/src/builtins/core/instant.rs index 216a35174..b55816540 100644 --- a/src/builtins/core/instant.rs +++ b/src/builtins/core/instant.rs @@ -177,7 +177,6 @@ impl Instant { let largest_unit = duration.default_largest_unit(); // 4. If TemporalUnitCategory(largestUnit) is date, throw a RangeError exception. if largest_unit.is_date_unit() { - // TODO: Add enum return Err(TemporalError::range().with_enum(ErrorMessage::LargestUnitCannotBeDateUnit)); } // 5. Let internalDuration be ToInternalDurationRecordWith24HourDays(duration). @@ -459,6 +458,7 @@ mod tests { use crate::{ builtins::{core::Instant, duration::duration_sign}, options::{DifferenceSettings, RoundingMode, Unit}, + partial::PartialDuration, unix_time::EpochNanoseconds, Duration, NS_MAX_INSTANT, NS_MIN_INSTANT, }; @@ -791,4 +791,34 @@ mod tests { assert_eq!(&one_string, "1969-12-15T12:23:45.678900434Z"); assert_eq!(&two_string, "1970-01-04T12:23:45.678902034Z"); } + + // Adapted from add[subtract]/minimum-maximum-instant.js + #[test] + fn instant_add_subtract_max_min() { + let min = Instant::try_new(-86_40000_00000_00000_00000).unwrap(); + let max = Instant::try_new(86_40000_00000_00000_00000).unwrap(); + + let zero = Duration::from_partial_duration(PartialDuration::default().with_nanoseconds(0)) + .unwrap(); + let one = Duration::from_partial_duration(PartialDuration::default().with_nanoseconds(1)) + .unwrap(); + let neg_one = + Duration::from_partial_duration(PartialDuration::default().with_nanoseconds(-1)) + .unwrap(); + // Adding/subtracting zero does not cross max/min boundary. + assert_eq!(min.add(&zero).unwrap(), min); + assert_eq!(min.subtract(&zero).unwrap(), min); + + // Addition or subtraction crosses boundary. + assert!(max.add(&one).is_err()); + assert!(max.subtract(&neg_one).is_err()); + assert!(min.add(&neg_one).is_err()); + assert!(min.subtract(&one).is_err()); + + let insanely_large_partial = + PartialDuration::default().with_nanoseconds(-86_40000_00000_00000_00000 * 2); + let large_duration = Duration::from_partial_duration(insanely_large_partial).unwrap(); + assert_eq!(min.subtract(&large_duration).unwrap(), max); + assert_eq!(max.add(&large_duration).unwrap(), min); + } } From 019974db9159a7cb11a1536769a95d6475f769d8 Mon Sep 17 00:00:00 2001 From: Kevin Ness Date: Thu, 14 Aug 2025 20:49:21 -0500 Subject: [PATCH 6/6] Fix bug when getting sign --- src/builtins/core/duration.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/builtins/core/duration.rs b/src/builtins/core/duration.rs index a5338f005..dd2a60fd0 100644 --- a/src/builtins/core/duration.rs +++ b/src/builtins/core/duration.rs @@ -641,8 +641,8 @@ impl Duration { minutes, seconds, milliseconds, - microseconds as i64, - nanoseconds as i64, + microseconds.signum() as i64, + nanoseconds.signum() as i64, ]); Ok(Duration::new_unchecked( sign,