From e88e66e65a548c858fde64e54ae6d20960269389 Mon Sep 17 00:00:00 2001 From: Matt Stone Date: Sat, 21 Jun 2025 20:22:13 -0700 Subject: [PATCH 01/26] Use bnum to define an InternalDuration struct --- Cargo.lock | 7 +++++++ Cargo.toml | 1 + src/builtins/core/duration.rs | 20 ++++++++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index fc1c4e43f..c0e7837c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -141,6 +141,12 @@ version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +[[package]] +name = "bnum" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "119771309b95163ec7aaf79810da82f7cd0599c19722d48b9c03894dca833966" + [[package]] name = "bumpalo" version = "3.17.0" @@ -835,6 +841,7 @@ dependencies = [ name = "temporal_rs" version = "0.0.9" dependencies = [ + "bnum", "combine", "core_maths", "iana-time-zone", diff --git a/Cargo.toml b/Cargo.toml index 48d3a18ad..a68302001 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,6 +75,7 @@ timezone_provider = { workspace = true, optional = true} # System time feature web-time = { workspace = true, optional = true } iana-time-zone = { workspace = true, optional = true } +bnum = "0.13.0" [features] default = ["sys"] diff --git a/src/builtins/core/duration.rs b/src/builtins/core/duration.rs index d13a85b37..bbd304a6e 100644 --- a/src/builtins/core/duration.rs +++ b/src/builtins/core/duration.rs @@ -14,6 +14,7 @@ use crate::{ }; use alloc::format; use alloc::string::String; +use bnum::{BUintD16, BUintD8}; use core::{cmp::Ordering, str::FromStr}; use ixdtf::parsers::{records::TimeDurationRecord, IsoDurationParser}; use normalized::NormalizedDurationRecord; @@ -77,6 +78,25 @@ pub struct Duration { time: TimeDuration, } +type u80 = BUintD16<5>; // 80 / 16 = 5 +type u88 = BUintD8<11>; // 88 / 8 = 11 +type u56 = BUintD8<7>; // 56 / 8 = 7 +type u48 = BUintD8<6>; // 48 / 8 = 6 +type u40 = BUintD8<5>; // 40 / 8 = 5 +struct InternalDuration { + years: u32, + months: u32, + weeks: u32, + days: u40, + hours: u48, + minutes: u48, + seconds: u56, + milliseconds: u64, + microseconds: u80, + nanoseconds: u88, + sign: bool, +} + impl core::fmt::Display for Duration { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_str( From 93f5315f65a4986ebf7eccf17f3beb95ebd5f8af Mon Sep 17 00:00:00 2001 From: Matt Stone Date: Sat, 21 Jun 2025 22:57:29 -0700 Subject: [PATCH 02/26] Finish addressing all implications --- src/builtins/core/calendar.rs | 9 +- src/builtins/core/date.rs | 11 +- src/builtins/core/datetime.rs | 11 +- src/builtins/core/duration.rs | 497 +++++++++++++++++------ src/builtins/core/duration/date.rs | 101 +++-- src/builtins/core/duration/normalized.rs | 82 ++-- src/builtins/core/instant.rs | 122 +++--- src/builtins/core/mod.rs | 2 +- src/builtins/core/time.rs | 27 +- src/builtins/core/year_month.rs | 2 +- src/builtins/core/zoneddatetime.rs | 6 +- src/iso.rs | 44 +- src/lib.rs | 11 +- temporal_capi/src/duration.rs | 50 +-- temporal_capi/src/instant.rs | 13 +- temporal_capi/src/plain_time.rs | 9 +- 16 files changed, 622 insertions(+), 375 deletions(-) diff --git a/src/builtins/core/calendar.rs b/src/builtins/core/calendar.rs index a472a6e23..ac0256bc4 100644 --- a/src/builtins/core/calendar.rs +++ b/src/builtins/core/calendar.rs @@ -4,14 +4,11 @@ //! Temporal compatible calendar implementations. use crate::{ - builtins::core::{ - duration::{DateDuration, TimeDuration}, - Duration, PlainDate, PlainDateTime, PlainMonthDay, PlainYearMonth, - }, + builtins::core::{Duration, PlainDate, PlainDateTime, PlainMonthDay, PlainYearMonth}, iso::IsoDate, options::{ArithmeticOverflow, Unit}, parsers::parse_allowed_calendar_formats, - TemporalError, TemporalResult, + DateDuration, TemporalError, TemporalResult, }; use alloc::string::ToString; use core::str::FromStr; @@ -296,7 +293,7 @@ impl Calendar { // duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]]). // 9. Let balanceResult be BalanceTimeDuration(norm, "day"). let (balance_days, _) = - TimeDuration::from_normalized(duration.time().to_normalized(), Unit::Day)?; + Duration::from_normalized_time(duration.to_normalized(), Unit::Day)?; // 10. Let result be ? AddISODate(date.[[ISOYear]], date.[[ISOMonth]], date.[[ISODay]], duration.[[Years]], // duration.[[Months]], duration.[[Weeks]], duration.[[Days]] + balanceResult.[[Days]], overflow). diff --git a/src/builtins/core/date.rs b/src/builtins/core/date.rs index 39d667b2b..161c516a3 100644 --- a/src/builtins/core/date.rs +++ b/src/builtins/core/date.rs @@ -20,9 +20,8 @@ use icu_calendar::AnyCalendarKind; use writeable::Writeable; use super::{ - calendar::month_to_month_code, - duration::{normalized::NormalizedDurationRecord, TimeDuration}, - PlainMonthDay, PlainYearMonth, + calendar::month_to_month_code, duration::normalized::NormalizedDurationRecord, PlainMonthDay, + PlainYearMonth, }; use tinystr::TinyAsciiStr; @@ -232,9 +231,7 @@ impl PlainDate { // "day").[[Days]]. let days = duration .days() - .checked_add( - TimeDuration::from_normalized(duration.time().to_normalized(), Unit::Day)?.0, - ) + .checked_add(Duration::from_normalized_time(duration.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). @@ -314,7 +311,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; diff --git a/src/builtins/core/datetime.rs b/src/builtins/core/datetime.rs index cfd420d2b..7082ff7b3 100644 --- a/src/builtins/core/datetime.rs +++ b/src/builtins/core/datetime.rs @@ -118,13 +118,16 @@ impl PlainDateTime { // 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()); + let norm = NormalizedTimeDuration::from_duration(duration); // 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)?; + 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]], diff --git a/src/builtins/core/duration.rs b/src/builtins/core/duration.rs index bbd304a6e..f197def97 100644 --- a/src/builtins/core/duration.rs +++ b/src/builtins/core/duration.rs @@ -1,5 +1,6 @@ //! This module implements `Duration` along with it's methods and components. +use self::normalized::NormalizedTimeDuration; use crate::{ builtins::core::{PlainDateTime, PlainTime, ZonedDateTime}, iso::{IsoDateTime, IsoTime}, @@ -16,22 +17,20 @@ use alloc::format; use alloc::string::String; use bnum::{BUintD16, BUintD8}; use core::{cmp::Ordering, str::FromStr}; +pub use date::DateDuration; use ixdtf::parsers::{records::TimeDurationRecord, IsoDurationParser}; use normalized::NormalizedDurationRecord; +use num_traits::Euclid; -use self::normalized::NormalizedTimeDuration; - -mod date; +pub mod date; pub(crate) mod normalized; -mod time; +// mod time; #[cfg(test)] mod tests; -#[doc(inline)] -pub use date::DateDuration; -#[doc(inline)] -pub use time::TimeDuration; +// #[doc(inline)] +// pub use time::TimeDuration; /// A `PartialDuration` is a Duration that may have fields not set. #[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)] @@ -67,34 +66,30 @@ impl PartialDuration { } } +type U80 = BUintD16<5>; // 80 / 16 = 5 +type U88 = BUintD8<11>; // 88 / 8 = 11 +type U56 = BUintD8<7>; // 56 / 8 = 7 +type U48 = BUintD8<6>; // 48 / 8 = 6 +type U40 = BUintD8<5>; // 40 / 8 = 5 + /// The native Rust implementation of `Temporal.Duration`. /// /// `Duration` is made up of a `DateDuration` and `TimeDuration` as primarily /// defined by Abtract Operation 7.5.1-5. -#[non_exhaustive] -#[derive(Debug, Clone, Copy, Default)] +// #[non_exhaustive] +#[derive(Debug, Clone, Copy, Default, PartialEq, PartialOrd)] pub struct Duration { - date: DateDuration, - time: TimeDuration, -} - -type u80 = BUintD16<5>; // 80 / 16 = 5 -type u88 = BUintD8<11>; // 88 / 8 = 11 -type u56 = BUintD8<7>; // 56 / 8 = 7 -type u48 = BUintD8<6>; // 48 / 8 = 6 -type u40 = BUintD8<5>; // 40 / 8 = 5 -struct InternalDuration { - years: u32, - months: u32, - weeks: u32, - days: u40, - hours: u48, - minutes: u48, - seconds: u56, - milliseconds: u64, - microseconds: u80, - nanoseconds: u88, - sign: bool, + sign: Sign, + pub(crate) years: u32, + pub(crate) months: u32, + pub(crate) weeks: u32, + pub(crate) days: U40, + pub(crate) hours: U48, + pub(crate) minutes: U48, + pub(crate) seconds: U56, + pub(crate) milliseconds: u64, + pub(crate) microseconds: U80, + pub(crate) nanoseconds: U88, } impl core::fmt::Display for Duration { @@ -121,20 +116,45 @@ 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), - ) + Self { + sign: Sign::from(value), + hours: U48::try_from(value.abs()).expect("Hours must be within range."), + ..Default::default() + } } } // ==== Private Creation methods ==== impl Duration { - /// Creates a new `Duration` from a `DateDuration` and `TimeDuration`. + /// Creates a new `Duration` with provided fields. #[inline] - pub(crate) const fn new_unchecked(date: DateDuration, time: TimeDuration) -> Self { - Self { date, time } + pub(crate) const fn new_unchecked( + sign: Sign, + years: u32, + months: u32, + weeks: u32, + days: U40, + hours: U48, + minutes: U48, + seconds: U56, + milliseconds: u64, + microseconds: U80, + nanoseconds: U88, + ) -> Self { + Self { + sign, + years, + months, + weeks, + days, + hours, + minutes, + seconds, + milliseconds, + microseconds, + nanoseconds, + } } #[inline] @@ -142,28 +162,208 @@ impl Duration { duration_record: NormalizedDurationRecord, largest_unit: Unit, ) -> TemporalResult { - let (overflow_day, time) = TimeDuration::from_normalized( + let (overflow_day, time) = Duration::from_normalized_time( duration_record.normalized_time_duration(), largest_unit, )?; Self::new( - duration_record.date().years, - duration_record.date().months, - duration_record.date().weeks, - duration_record - .date() - .days + duration_record.date().years.into(), + duration_record.date().months.into(), + duration_record.date().weeks.into(), + i64::try_from(duration_record.date().days) + .unwrap() .checked_add(overflow_day) .ok_or(TemporalError::range())?, - time.hours, - time.minutes, - time.seconds, - time.milliseconds, - time.microseconds, - time.nanoseconds, + time.hours.try_into().unwrap(), + time.minutes.try_into().unwrap(), + time.seconds.try_into().unwrap(), + time.milliseconds.try_into().unwrap(), + time.microseconds.try_into().unwrap(), + time.nanoseconds.try_into().unwrap(), ) } + /// Returns this `TimeDuration` as a `NormalizedTimeDuration`. + #[inline] + pub(crate) fn to_normalized(self) -> NormalizedTimeDuration { + NormalizedTimeDuration::from_duration(&self) + } + + /// 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_time( + norm: NormalizedTimeDuration, + largest_unit: Unit, + ) -> TemporalResult<(i64, Duration)> { + // 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; + + if !is_valid_duration( + 0, + 0, + 0, + days, + hours as i64 * sign, + minutes as i64 * sign, + seconds as i64 * sign, + milliseconds as i64 * sign, + microseconds * sign as i128, + nanoseconds * sign as i128, + ) { + return Err(TemporalError::range().with_message("Invalid balance TimeDuration.")); + } + + // TODO: Remove cast below. + Ok(( + days, + Duration { + sign: sign.into(), + days: days.try_into().expect("days must fit into u40"), + hours: hours.try_into().expect("hours must fit into u48"), + minutes: minutes.try_into().expect("minutes must fit into u48"), + seconds: seconds.try_into().expect("seconds must fit into u56"), + milliseconds: milliseconds + .try_into() + .expect("milliseconds must fit into u64"), + microseconds: microseconds + .try_into() + .expect("microseconds must fit into u80"), + nanoseconds: nanoseconds + .try_into() + .expect("nanoseconds must fit into u88"), + ..Default::default() + }, + )) + } + /// Returns the a `Vec` of the fields values. #[inline] #[must_use] @@ -218,17 +418,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, @@ -243,17 +432,51 @@ impl Duration { ) { return Err(TemporalError::range().with_message("Duration was not valid.")); } - Ok(duration) + Ok(Duration { + sign: Sign::from(years), + years: u32::try_from(years).expect("Years must be within range."), + months: u32::try_from(months).expect("Months must be within range."), + weeks: u32::try_from(weeks).expect("Weeks must be within range."), + days: U40::try_from(days).expect("Days must be within range."), + hours: U48::try_from(hours).expect("Hours must be within range."), + minutes: U48::try_from(minutes).expect("Minutes must be within range."), + seconds: U56::try_from(seconds).expect("Seconds must be within range."), + milliseconds: u64::try_from(milliseconds).expect("Milliseconds must be within range."), + microseconds: U80::try_from(microseconds).expect("Microseconds must be within range."), + nanoseconds: U88::try_from(nanoseconds).expect("Nanoseconds must be within range."), + }) } /// Creates a `Duration` from a provided a day and a `TimeDuration`. /// /// Note: `TimeDuration` records can store a day value to deal with overflow. #[must_use] - pub fn from_day_and_time(day: i64, time: &TimeDuration) -> Self { + pub fn from_day_and_time(day: i64, time: &Duration) -> Self { Self { - date: DateDuration::new_unchecked(0, 0, 0, day), - time: *time, + sign: time.sign(), + days: day.try_into().expect("Days must be within range."), + hours: time.hours.try_into().expect("Hours must be within range."), + minutes: time + .minutes + .try_into() + .expect("Minutes must be within range."), + seconds: time + .seconds + .try_into() + .expect("Seconds must be within range."), + milliseconds: time + .milliseconds + .try_into() + .expect("Milliseconds must be within range."), + microseconds: time + .microseconds + .try_into() + .expect("Microseconds must be within range."), + nanoseconds: time + .nanoseconds + .try_into() + .expect("Nanoseconds must be within range."), + ..Default::default() } } @@ -388,7 +611,12 @@ impl Duration { #[inline] #[must_use] pub fn is_time_within_range(&self) -> bool { - self.time.is_within_range() + self.hours < 24u8.into() + && self.minutes < 60u8.into() + && self.seconds < 60u8.into() + && self.milliseconds < 1000u16.into() + && self.microseconds < 1000u16.into() + && self.nanoseconds < 1000u16.into() } #[inline] @@ -398,7 +626,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). @@ -431,16 +659,19 @@ 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 = DateDuration::from(*self).days(pdt)?; + let days2 = DateDuration::from(*other).days(pdt)?; (days1, days2) } else { - (self.date.days, other.date.days) + ( + self.days.try_into().unwrap(), + other.days.try_into().unwrap(), + ) }; // 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)) } @@ -449,88 +680,88 @@ 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` + // /// Returns a reference to the inner `TimeDuration` + // #[inline] + // #[must_use] + // pub fn time(&self) -> &TimeDuration { + // &self.time + // } + + /// Returns the inner `DateDuration` as an owned value #[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 + pub fn years(&self) -> i64 { + self.years.into() } /// Returns the `months` field of duration. #[inline] #[must_use] - pub const fn months(&self) -> i64 { - self.date.months + pub fn months(&self) -> i64 { + self.months.into() } /// Returns the `weeks` field of duration. #[inline] #[must_use] - pub const fn weeks(&self) -> i64 { - self.date.weeks + pub fn weeks(&self) -> i64 { + self.weeks.into() } /// Returns the `days` field of duration. #[inline] #[must_use] - pub const fn days(&self) -> i64 { - self.date.days + pub fn days(&self) -> i64 { + self.days.try_into().unwrap() } /// Returns the `hours` field of duration. #[inline] #[must_use] - pub const fn hours(&self) -> i64 { - self.time.hours + pub fn hours(&self) -> i64 { + self.hours.try_into().unwrap() } /// Returns the `hours` field of duration. #[inline] #[must_use] - pub const fn minutes(&self) -> i64 { - self.time.minutes + pub fn minutes(&self) -> i64 { + self.minutes.try_into().unwrap() } /// Returns the `seconds` field of duration. #[inline] #[must_use] - pub const fn seconds(&self) -> i64 { - self.time.seconds + pub fn seconds(&self) -> i64 { + self.seconds.try_into().unwrap() } /// Returns the `hours` field of duration. #[inline] #[must_use] - pub const fn milliseconds(&self) -> i64 { - self.time.milliseconds + pub fn milliseconds(&self) -> i64 { + self.milliseconds.try_into().unwrap() } /// Returns the `microseconds` field of duration. #[inline] #[must_use] - pub const fn microseconds(&self) -> i128 { - self.time.microseconds + pub fn microseconds(&self) -> i128 { + self.microseconds.try_into().unwrap() } /// Returns the `nanoseconds` field of duration. #[inline] #[must_use] - pub const fn nanoseconds(&self) -> i128 { - self.time.nanoseconds + pub fn nanoseconds(&self) -> i128 { + self.nanoseconds.try_into().unwrap() } } @@ -558,8 +789,8 @@ impl Duration { #[must_use] pub fn negated(&self) -> Self { Self { - date: self.date().negated(), - time: self.time().negated(), + sign: self.sign().negate(), + ..Default::default() } } @@ -568,8 +799,8 @@ impl Duration { #[must_use] pub fn abs(&self) -> Self { Self { - date: self.date().abs(), - time: self.time().abs(), + sign: self.sign().as_sign_multiplier().abs().into(), + ..Default::default() } } @@ -586,9 +817,9 @@ impl Duration { // 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()); + let norm_one = NormalizedTimeDuration::from_duration(self); // 27. Let norm2 be NormalizeTimeDuration(h2, min2, s2, ms2, mus2, ns2). - let norm_two = NormalizedTimeDuration::from_time_duration(other.time()); + let norm_two = NormalizedTimeDuration::from_duration(other); // 28. If IsCalendarUnit(largestUnit), throw a RangeError exception. if largest_unit.is_calendar_unit() { @@ -609,7 +840,7 @@ impl Duration { )?; // 31. Let result be ? BalanceTimeDuration(normResult, largestUnit). - let (result_days, result_time) = TimeDuration::from_normalized(result, largest_unit)?; + let (result_days, result_time) = Duration::from_normalized_time(result, largest_unit)?; // 32. Return ! CreateTemporalDuration(0, 0, 0, result.[[Days]], result.[[Hours]], result.[[Minutes]], // result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]). @@ -892,7 +1123,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( @@ -961,8 +1192,8 @@ impl 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), + self.date(), + NormalizedTimeDuration::from_duration(&self), )?; // 13. Let timeDuration be ? RoundTimeDuration(internalDuration.[[Time]], precision.[[Increment]], precision.[[Unit]], roundingMode). let time = norm.normalized_time_duration().round(rounding_options)?; @@ -999,14 +1230,21 @@ pub fn duration_to_formattable( let hours = duration.hours().abs(); let minutes = duration.minutes().abs(); - let time = NormalizedTimeDuration::from_time_duration(&TimeDuration::new_unchecked( - 0, - 0, - duration.seconds(), - duration.milliseconds(), - duration.microseconds(), - duration.nanoseconds(), - )); + // let time = NormalizedTimeDuration::from_duration(&Duration::new_unchecked( + // 0, + // 0, + // duration.seconds(), + // duration.milliseconds(), + // duration.microseconds(), + // duration.nanoseconds(), + // )); + let time = NormalizedTimeDuration::from_duration(&Duration { + seconds: duration.seconds, + milliseconds: duration.milliseconds, + microseconds: duration.microseconds, + nanoseconds: duration.nanoseconds, + ..Default::default() + }); let seconds = time.seconds().unsigned_abs(); let subseconds = time.subseconds().unsigned_abs(); @@ -1135,20 +1373,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, + months: value.months, + weeks: value.weeks, + days: value.days, + ..Default::default() } } } diff --git a/src/builtins/core/duration/date.rs b/src/builtins/core/duration/date.rs index b50271ac5..c40716071 100644 --- a/src/builtins/core/duration/date.rs +++ b/src/builtins/core/duration/date.rs @@ -1,8 +1,10 @@ //! Implementation of a `DateDuration` use crate::{ - iso::iso_date_to_epoch_days, options::ArithmeticOverflow, Duration, PlainDate, Sign, - TemporalError, TemporalResult, + builtins::{duration::U40, Duration}, + iso::iso_date_to_epoch_days, + options::ArithmeticOverflow, + PlainDate, Sign, TemporalError, TemporalResult, }; use super::duration_sign; @@ -16,26 +18,28 @@ use super::duration_sign; #[non_exhaustive] #[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)] pub struct DateDuration { + pub sign: Sign, /// `DateDuration`'s internal year value. - pub years: i64, + pub years: u32, /// `DateDuration`'s internal month value. - pub months: i64, + pub months: u32, /// `DateDuration`'s internal week value. - pub weeks: i64, + pub weeks: u32, /// `DateDuration`'s internal day value. - pub days: i64, + pub days: U40, } impl DateDuration { /// Creates a new, non-validated `DateDuration`. #[inline] #[must_use] - pub(crate) const fn new_unchecked(years: i64, months: i64, weeks: i64, days: i64) -> Self { + pub(crate) fn new_unchecked(years: i64, months: i64, weeks: i64, days: i64) -> Self { Self { - years, - months, - weeks, - days, + sign: Sign::from(years), + years: years.try_into().unwrap(), + months: months.try_into().unwrap(), + weeks: weeks.try_into().unwrap(), + days: days.try_into().unwrap(), } } @@ -43,7 +47,46 @@ impl DateDuration { #[inline] #[must_use] pub(crate) fn fields(&self) -> [i64; 4] { - [self.years, self.months, self.weeks, self.days] + [ + self.years.into(), + self.months.into(), + self.weeks.into(), + self.days.try_into().unwrap(), + ] + } +} + +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] + fn from(duration: Duration) -> Self { + Self { + sign: duration.sign, + years: duration.years, + months: duration.months, + weeks: duration.weeks, + days: 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 { + sign: duration.sign, + years: duration.years, + months: duration.months, + weeks: duration.weeks, + days: duration.days, + } } } @@ -71,10 +114,8 @@ impl DateDuration { #[must_use] pub fn negated(&self) -> Self { Self { - years: self.years.saturating_neg(), - months: self.months.saturating_neg(), - weeks: self.weeks.saturating_neg(), - days: self.days.saturating_neg(), + sign: self.sign.negate(), + ..*self } } @@ -83,10 +124,8 @@ impl DateDuration { #[must_use] pub fn abs(&self) -> Self { Self { - years: self.years.abs(), - months: self.months.abs(), - weeks: self.weeks.abs(), - days: self.days.abs(), + sign: Sign::Positive, + ..*self } } @@ -103,13 +142,16 @@ impl DateDuration { let ymw_duration = self.adjust(0, None, None)?; // 2. If DateDurationSign(yearsMonthsWeeksDuration) = 0, return dateDuration.[[Days]]. if ymw_duration.sign() == Sign::Zero { - return Ok(self.days); + return Ok(self.days.try_into().unwrap()); } // 3. Let later be ? CalendarDateAdd(plainRelativeTo.[[Calendar]], plainRelativeTo.[[ISODate]], yearsMonthsWeeksDuration, constrain). let later = relative_to.add( &Duration { - date: *self, - time: Default::default(), + years: self.years, + months: self.months, + weeks: self.weeks, + days: self.days, + ..Default::default() }, Some(ArithmeticOverflow::Constrain), )?; @@ -128,7 +170,7 @@ impl DateDuration { // 6. Let yearsMonthsWeeksInDays be epochDays2 - epochDays1. let ymd_in_days = epoch_days_2 - epoch_days_1; // 7. Return dateDuration.[[Days]] + yearsMonthsWeeksInDays. - Ok(self.days + ymd_in_days) + Ok(i64::try_from(self.days).unwrap() + ymd_in_days) } /// `7.5.10 AdjustDateDurationRecord ( dateDuration, days [ , weeks [ , months ] ] )` @@ -143,17 +185,22 @@ impl DateDuration { months: Option, ) -> TemporalResult { // 1. If weeks is not present, set weeks to dateDuration.[[Weeks]]. - let weeks = weeks.unwrap_or(self.weeks); + let weeks = weeks + .map(|w| w.try_into().expect("weeks must fit in U40")) + .unwrap_or(self.weeks); // 2. If months is not present, set months to dateDuration.[[Months]]. - let months = months.unwrap_or(self.months); + let months = months + .map(|m| m.try_into().expect("months must fit in U40")) + .unwrap_or(self.months); // 3. Return ? CreateDateDurationRecord(dateDuration.[[Years]], months, weeks, days). Ok(Self { + sign: self.sign, years: self.years, months, weeks, - days, + days: days.try_into().expect("days must fit in U40"), }) } } diff --git a/src/builtins/core/duration/normalized.rs b/src/builtins/core/duration/normalized.rs index fe63ff038..c222f0a32 100644 --- a/src/builtins/core/duration/normalized.rs +++ b/src/builtins/core/duration/normalized.rs @@ -2,7 +2,7 @@ use core::{num::NonZeroU128, ops::Add}; -use num_traits::AsPrimitive; +use bnum::cast::As; use crate::{ builtins::core::{timezone::TimeZone, PlainDate, PlainDateTime}, @@ -17,7 +17,7 @@ use crate::{ Calendar, TemporalError, TemporalResult, TemporalUnwrap, NS_PER_DAY, }; -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; @@ -41,14 +41,14 @@ pub(crate) struct NormalizedTimeDuration(pub(crate) i128); impl NormalizedTimeDuration { /// 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 mut nanoseconds: i128 = i128::try_from(duration.hours).unwrap() * NANOSECONDS_PER_HOUR; + nanoseconds += i128::try_from(duration.minutes).unwrap() * NANOSECONDS_PER_MINUTE; + nanoseconds += i128::try_from(duration.seconds).unwrap() * 1_000_000_000; + nanoseconds += i128::try_from(duration.milliseconds).unwrap() * 1_000_000; + nanoseconds += i128::try_from(duration.microseconds).unwrap() * 1_000; + nanoseconds += i128::try_from(duration.nanoseconds).unwrap(); // NOTE(nekevss): Is it worth returning a `RangeError` below. debug_assert!(nanoseconds.abs() <= MAX_TIME_DURATION); Self(nanoseconds) @@ -260,7 +260,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). @@ -319,7 +319,7 @@ impl NormalizedDurationRecord { Unit::Year => { // a. Let years be RoundNumberToIncrement(duration.[[Years]], increment, "trunc"). let years = IncrementRounder::from_signed_num( - self.date().years, + i64::from(self.date().years), options.increment.as_extended_increment(), )? .round(RoundingMode::Trunc); @@ -351,7 +351,7 @@ impl NormalizedDurationRecord { Unit::Month => { // a. Let months be RoundNumberToIncrement(duration.[[Months]], increment, "trunc"). let months = IncrementRounder::from_signed_num( - self.date().months, + i64::from(self.date().months), options.increment.as_extended_increment(), )? .round(RoundingMode::Trunc); @@ -366,13 +366,13 @@ impl NormalizedDurationRecord { r1, r2, DateDuration::new( - self.date().years, + i64::from(self.date().years), i64::try_from(r1).map_err(|_| TemporalError::range())?, 0, 0, )?, DateDuration::new( - self.date().years, + i64::from(self.date().years), i64::try_from(r2).map_err(|_| TemporalError::range())?, 0, 0, @@ -406,7 +406,7 @@ impl NormalizedDurationRecord { let iso_two = IsoDate::try_balance( dt.iso_year() + self.date().years as i32, i32::from(dt.iso_month()) + self.date().months as i32, - i64::from(dt.iso_day()) + self.date().days, + i64::from(dt.iso_day()) + i64::try_from(self.date().days).unwrap(), )?; // c. Let weeksStart be ! CreateTemporalDate(isoResult1.[[Year]], isoResult1.[[Month]], isoResult1.[[Day]], @@ -435,7 +435,7 @@ impl NormalizedDurationRecord { // h. Let weeks be RoundNumberToIncrement(duration.[[Weeks]] + untilResult.[[Weeks]], increment, "trunc"). let weeks = IncrementRounder::from_signed_num( - self.date().weeks + until_result.weeks(), + i64::from(self.date().weeks) + until_result.weeks(), options.increment.as_extended_increment(), )? .round(RoundingMode::Trunc); @@ -451,14 +451,14 @@ impl NormalizedDurationRecord { r1, r2, DateDuration::new( - self.date().years, - self.date().months, + i64::from(self.date().years), + i64::from(self.date().months), i64::try_from(r1).map_err(|_| TemporalError::range())?, 0, )?, DateDuration::new( - self.date().years, - self.date().months, + i64::from(self.date().years), + i64::from(self.date().months), i64::try_from(r2).map_err(|_| TemporalError::range())?, 0, )?, @@ -469,7 +469,7 @@ impl NormalizedDurationRecord { // a. Assert: unit is "day". // b. Let days be RoundNumberToIncrement(duration.[[Days]], increment, "trunc"). let days = IncrementRounder::from_signed_num( - self.date().days, + i64::try_from(self.date().days).unwrap(), options.increment.as_extended_increment(), )? .round(RoundingMode::Trunc); @@ -484,15 +484,15 @@ impl NormalizedDurationRecord { r1, r2, DateDuration::new( - self.date().years, - self.date().months, - self.date().weeks, + i64::from(self.date().years), + i64::from(self.date().months), + i64::from(self.date().weeks), i64::try_from(r1).map_err(|_| TemporalError::range())?, )?, DateDuration::new( - self.date().years, - self.date().months, - self.date().weeks, + i64::from(self.date().years), + i64::from(self.date().months), + i64::from(self.date().weeks), i64::try_from(r2).map_err(|_| TemporalError::range())?, )?, ) @@ -680,11 +680,11 @@ impl NormalizedDurationRecord { }; // 14. Let dateDuration be ! AdjustDateDurationRecord(duration.[[Date]], duration.[[Date]].[[Days]] + dayDelta). let date = DateDuration::new( - self.date.years, - self.date.months, - self.date.weeks, - self.date - .days + i64::from(self.date.years), + i64::from(self.date.months), + i64::from(self.date.weeks), + i64::try_from(self.date.days) + .unwrap() .checked_add(day_delta.into()) .ok_or(TemporalError::range())?, )?; @@ -759,9 +759,9 @@ impl NormalizedDurationRecord { // 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, + self.date().years.into(), + self.date().months.into(), + self.date().weeks.into(), days as i64, )?, remainder, @@ -824,11 +824,11 @@ impl NormalizedDurationRecord { let years = self .date() .years - .checked_add(sign.as_sign_multiplier().into()) + .checked_add(sign.as_sign_multiplier().try_into().unwrap()) .ok_or(TemporalError::range())?; // 2. Let endDuration be ? CreateDateDurationRecord(years, 0, 0, 0). - DateDuration::new(years, 0, 0, 0)? + DateDuration::new(years.into(), 0, 0, 0)? } // ii. Else if unit is month, then Unit::Month => { @@ -836,11 +836,11 @@ impl NormalizedDurationRecord { let months = self .date() .months - .checked_add(sign.as_sign_multiplier().into()) + .checked_add(sign.as_sign_multiplier().try_into().unwrap()) .ok_or(TemporalError::range())?; // 2. Let endDuration be ? AdjustDateDurationRecord(duration.[[Date]], 0, 0, months). - duration.date().adjust(0, Some(0), Some(months))? + duration.date().adjust(0, Some(0), Some(months.into()))? } // iii. Else, unit => { @@ -851,11 +851,11 @@ impl NormalizedDurationRecord { let weeks = self .date() .weeks - .checked_add(sign.as_sign_multiplier().into()) + .checked_add(sign.as_sign_multiplier().try_into().unwrap()) .ok_or(TemporalError::range())?; // 3. Let endDuration be ? AdjustDateDurationRecord(duration.[[Date]], 0, weeks). - duration.date().adjust(0, Some(weeks), None)? + duration.date().adjust(0, Some(weeks.into()), None)? } }; diff --git a/src/builtins/core/instant.rs b/src/builtins/core/instant.rs index 97d90371b..ee861bcf7 100644 --- a/src/builtins/core/instant.rs +++ b/src/builtins/core/instant.rs @@ -4,9 +4,7 @@ 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}, iso::IsoDateTime, options::{ DifferenceOperation, DifferenceSettings, DisplayOffset, ResolvedRoundingOptions, @@ -16,7 +14,7 @@ use crate::{ provider::TimeZoneProvider, rounding::{IncrementRounder, Round}, unix_time::EpochNanoseconds, - Calendar, TemporalError, TemporalResult, TemporalUnwrap, TimeZone, + Calendar, DateDuration, TemporalError, TemporalResult, TemporalUnwrap, TimeZone, }; use ixdtf::parsers::records::UtcOffsetRecordOrZ; @@ -25,7 +23,7 @@ use writeable::Writeable; use super::{ duration::normalized::{NormalizedDurationRecord, NormalizedTimeDuration}, - DateDuration, ZonedDateTime, + ZonedDateTime, }; const NANOSECONDS_PER_SECOND: i64 = 1_000_000_000; @@ -50,8 +48,8 @@ impl Instant { /// Adds a `TimeDuration` to the current `Instant`. /// /// Temporal-Proposal equivalent: `AddInstant`. - pub(crate) fn add_to_instant(&self, duration: &TimeDuration) -> TemporalResult { - let norm = NormalizedTimeDuration::from_time_duration(duration); + pub fn add_to_instant(&self, duration: &Duration) -> TemporalResult { + let norm = NormalizedTimeDuration::from_duration(duration); let result = self.epoch_nanoseconds().0 + norm.0; Ok(Self::from(EpochNanoseconds::try_from(result)?)) } @@ -217,13 +215,7 @@ impl Instant { 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) + self.add_to_instant(&duration) } /// Subtract a `Duration` to the current `Instant`, returning an error if the `Duration` @@ -234,12 +226,12 @@ impl Instant { return Err(TemporalError::range() .with_message("DateDuration values cannot be added to instant.")); } - self.subtract_time_duration(duration.time()) + self.subtract_duration(&duration) } - /// Subtracts a `TimeDuration` to `Instant`. + /// Subtracts a `Duration` to `Instant`. #[inline] - pub fn subtract_time_duration(&self, duration: &TimeDuration) -> TemporalResult { + pub fn subtract_duration(&self, duration: &Duration) -> TemporalResult { self.add_to_instant(&duration.negated()) } @@ -343,10 +335,10 @@ mod tests { use core::str::FromStr; use crate::{ - builtins::core::{duration::TimeDuration, Instant}, + builtins::core::Instant, options::{DifferenceSettings, RoundingMode, Unit}, unix_time::EpochNanoseconds, - NS_MAX_INSTANT, NS_MIN_INSTANT, + Duration, Sign, NS_MAX_INSTANT, NS_MIN_INSTANT, }; #[test] @@ -447,20 +439,24 @@ 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::new_unchecked( + Sign::from(expected.0), + 0, + 0, + 0, + 0u8.into(), + expected.0.try_into().unwrap(), + expected.1.try_into().unwrap(), + expected.2.try_into().unwrap(), + expected.3.try_into().unwrap(), + expected.4.try_into().unwrap(), + expected.5.try_into().unwrap(), ) - }; + ); + }; let earlier = Instant::try_new( 217_178_610_123_456_789, /* 1976-11-18T15:23:30.123456789Z */ @@ -474,40 +470,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] @@ -521,20 +517,24 @@ 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_time_duration = |td: &Duration, expected: (i64, i64, i64, i64, i128, i128)| { + assert_eq!( + td, + &Duration::new_unchecked( + Sign::from(expected.0), + 0, + 0, + 0, + 0u8.into(), + expected.0.try_into().unwrap(), + expected.1.try_into().unwrap(), + expected.2.try_into().unwrap(), + expected.3.try_into().unwrap(), + expected.4.try_into().unwrap(), + expected.5.try_into().unwrap(), ) - }; + ); + }; let earlier = Instant::try_new( 217_178_610_123_456_789, /* 1976-11-18T15:23:30.123456789Z */ @@ -548,40 +548,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_time_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_time_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_time_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_time_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_time_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_time_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_time_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_time_duration(&negative_result, (-376435, -23, -8, -148, -529, -500)); } // /test/built-ins/Temporal/Instant/prototype/add/cross-epoch.js diff --git a/src/builtins/core/mod.rs b/src/builtins/core/mod.rs index 498402ab9..747e42a68 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::{PartialDateTime, PlainDateTime}; #[doc(inline)] -pub use duration::{DateDuration, Duration, PartialDuration, TimeDuration}; +pub use duration::{date::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 6ff203507..0278c216a 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::TimeDuration, Duration}, + builtins::core::Duration, iso::IsoTime, options::{ ArithmeticOverflow, DifferenceOperation, DifferenceSettings, ResolvedRoundingOptions, @@ -130,14 +130,15 @@ impl PlainTime { /// Adds a `TimeDuration` 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.try_into().unwrap()), + i64::from(self.minute()).saturating_add(duration.minutes.try_into().unwrap()), + i64::from(self.second()).saturating_add(duration.seconds.try_into().unwrap()), + i64::from(self.millisecond()).saturating_add(duration.milliseconds.try_into().unwrap()), + i128::from(self.microsecond()) + .saturating_add(duration.microseconds.try_into().unwrap()), + i128::from(self.nanosecond()).saturating_add(duration.nanoseconds.try_into().unwrap()), ); // NOTE (nekevss): IsoTime::balance should never return an invalid `IsoTime` @@ -181,7 +182,7 @@ impl PlainTime { }; // 7. Let result be BalanceTimeDuration(norm, settings.[[LargestUnit]]). - let result = TimeDuration::from_normalized(normalized_time, resolved.largest_unit)?.1; + let result = Duration::from_normalized_time(normalized_time, resolved.largest_unit)?.1; // 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 { @@ -402,12 +403,12 @@ impl PlainTime { return Err(TemporalError::range() .with_message("DateDuration values cannot be added to `Time`.")); } - self.add_time_duration(duration.time()) + self.add_time_duration(duration) } /// Adds a `TimeDuration` to the current `Time`. #[inline] - pub fn add_time_duration(&self, duration: &TimeDuration) -> TemporalResult { + pub fn add_time_duration(&self, duration: &Duration) -> TemporalResult { self.add_to_time(duration) } @@ -417,12 +418,12 @@ impl PlainTime { return Err(TemporalError::range() .with_message("DateDuration values cannot be added to `Time` component.")); } - self.subtract_time_duration(duration.time()) + self.subtract_time_duration(duration) } /// Adds a `TimeDuration` to the current `Time`. #[inline] - pub fn subtract_time_duration(&self, duration: &TimeDuration) -> TemporalResult { + pub fn subtract_time_duration(&self, duration: &Duration) -> TemporalResult { self.add_to_time(&duration.negated()) } diff --git a/src/builtins/core/year_month.rs b/src/builtins/core/year_month.rs index 370745fa0..9e57ddb59 100644 --- a/src/builtins/core/year_month.rs +++ b/src/builtins/core/year_month.rs @@ -115,7 +115,7 @@ impl PlainYearMonth { .date_until(&self.iso, &other.iso, resolved.largest_unit)?; // 15. Let duration be CombineDateAndTimeDuration(yearsMonthsDifference, 0). - let mut duration = NormalizedDurationRecord::from_date_duration(*result.date())?; + let mut duration = NormalizedDurationRecord::from_date_duration(result.date())?; // 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 e7bbd0e84..9fea91aaa 100644 --- a/src/builtins/core/zoneddatetime.rs +++ b/src/builtins/core/zoneddatetime.rs @@ -153,7 +153,7 @@ impl ZonedDateTime { // 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); } // 2. Let isoDateTime be GetISODateTimeFor(timeZone, epochNanoseconds). let iso_datetime = self.tz.get_iso_datetime_for(&self.instant, provider)?; @@ -176,7 +176,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) } /// Adds a duration to the current `ZonedDateTime`, returning the resulting `ZonedDateTime`. @@ -350,7 +350,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`. diff --git a/src/iso.rs b/src/iso.rs index 292826fb9..a79186ae3 100644 --- a/src/iso.rs +++ b/src/iso.rs @@ -30,7 +30,7 @@ use crate::{ calendar::Calendar, duration::{ normalized::{NormalizedDurationRecord, NormalizedTimeDuration}, - DateDuration, TimeDuration, + DateDuration, }, Duration, PartialTime, PlainDate, }, @@ -39,7 +39,7 @@ use crate::{ rounding::{IncrementRounder, Round}, temporal_assert, unix_time::EpochNanoseconds, - utils, TemporalResult, TemporalUnwrap, NS_PER_DAY, + utils, Sign, TemporalResult, TemporalUnwrap, NS_PER_DAY, }; use icu_calendar::{Date as IcuDate, Iso}; use num_traits::{cast::FromPrimitive, Euclid}; @@ -167,11 +167,11 @@ impl IsoDateTime { // 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 + date_duration.years.into(), + date_duration.months.into(), + date_duration.weeks.into(), + i64::try_from(date_duration.days) + .unwrap() .checked_add(t_result.0) .ok_or(TemporalError::range())?, )?; @@ -211,8 +211,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 = NormalizedTimeDuration::from_duration(&self.time.diff(&other.time)); // 5. Let timeSign be NormalizedTimeDurationSign(timeDuration). let time_sign = time_duration.sign() as i8; @@ -398,8 +397,8 @@ impl IsoDate { // 2. Assert: overflow is either "constrain" or "reject". // 3. Let intermediate be ! BalanceISOYearMonth(year + years, month + months). let intermediate = balance_iso_year_month_with_clamp( - i64::from(self.year) + duration.years, - i64::from(self.month) + duration.months, + i64::from(self.year) + i64::from(duration.years), + i64::from(self.month) + i64::from(duration.months), ); // 4. Let intermediate be ? RegulateISODate(intermediate.[[Year]], intermediate.[[Month]], day, overflow). @@ -407,8 +406,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 = + i64::try_from(duration.days).unwrap() + (7 * i64::from(duration.weeks)); // Verify + // 6. Let d be intermediate.[[Day]] + days. let intermediate_days = i64::from(intermediate.day) + additional_days; // 7. Return BalanceISODate(intermediate.[[Year]], intermediate.[[Month]], d). @@ -715,8 +715,8 @@ impl IsoTime { (days, time) } - /// Difference this `IsoTime` against another and returning a `TimeDuration`. - pub(crate) fn diff(&self, other: &Self) -> TimeDuration { + /// Difference this `IsoTime` against another and returning a `Duration`. + pub(crate) fn diff(&self, other: &Self) -> Duration { 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); @@ -724,7 +724,19 @@ 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) + Duration::new_unchecked( + Sign::from(h), + 0, + 0, + 0, + 0u8.into(), + h.try_into().unwrap(), + m.try_into().unwrap(), + s.try_into().unwrap(), + ms.try_into().unwrap(), + mis.try_into().unwrap(), + ns.try_into().unwrap(), + ) } // 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 e604a4f9e..617c91b08 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -186,7 +186,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. @@ -245,6 +245,15 @@ impl From for Sign { } } } +impl From for Sign { + fn from(value: i64) -> Self { + match value.cmp(&0) { + Ordering::Greater => Self::Positive, + Ordering::Equal => Self::Zero, + Ordering::Less => Self::Negative, + } + } +} impl Sign { /// Coerces the current `Sign` to be either negative or positive. diff --git a/temporal_capi/src/duration.rs b/temporal_capi/src/duration.rs index 58675ee5f..76b437834 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())?, - i128::from_f64(nanoseconds).ok_or(TemporalError::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, @@ -171,10 +131,7 @@ pub mod ffi { .map_err(Into::into) } - pub fn from_day_and_time( - day: i64, - time: &TimeDuration, - ) -> Result, TemporalError> { + pub fn from_day_and_time(day: i64, time: &Duration) -> Result, TemporalError> { Ok(Box::new(Duration( temporal_rs::Duration::from_day_and_time(day, &time.0), ))) @@ -203,11 +160,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()) + DateDuration::transparent_convert(&self.0.date()) } // set_time_duration is NOT safe to expose over FFI if the date()/time() methods are available diff --git a/temporal_capi/src/instant.rs b/temporal_capi/src/instant.rs index 8acf2c6c6..7d51f8ad4 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,12 +72,9 @@ pub mod ffi { .map(|c| Box::new(Self(c))) .map_err(Into::into) } - pub fn add_time_duration( - &self, - duration: &TimeDuration, - ) -> Result, TemporalError> { + pub fn add_time_duration(&self, duration: &Duration) -> Result, TemporalError> { self.0 - .add_time_duration(&duration.0) + .add_to_instant(&duration.0) .map(|c| Box::new(Self(c))) .map_err(Into::into) } @@ -89,10 +86,10 @@ pub mod ffi { } pub fn subtract_time_duration( &self, - duration: &TimeDuration, + duration: &Duration, ) -> Result, TemporalError> { self.0 - .subtract_time_duration(&duration.0) + .subtract_duration(&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 3fa5ad8af..c66e3527e 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,10 +136,7 @@ pub mod ffi { .map(|x| Box::new(Self(x))) .map_err(Into::into) } - pub fn add_time_duration( - &self, - duration: &TimeDuration, - ) -> Result, TemporalError> { + pub fn add_time_duration(&self, duration: &Duration) -> Result, TemporalError> { self.0 .add_time_duration(&duration.0) .map(|x| Box::new(Self(x))) @@ -147,7 +144,7 @@ pub mod ffi { } pub fn subtract_time_duration( &self, - duration: &TimeDuration, + duration: &Duration, ) -> Result, TemporalError> { self.0 .subtract_time_duration(&duration.0) From c546ae3d04119bbb9aaaf6383c5179bd78300756 Mon Sep 17 00:00:00 2001 From: Matt Stone Date: Sat, 21 Jun 2025 23:09:43 -0700 Subject: [PATCH 03/26] Remove TimeDuration completely --- src/builtins/compiled/duration/tests.rs | 2 +- src/builtins/core/duration.rs | 76 ++---- src/builtins/core/duration/normalized.rs | 2 +- src/builtins/core/duration/time.rs | 322 ----------------------- src/builtins/core/instant.rs | 6 +- src/builtins/core/time.rs | 6 +- temporal_capi/src/duration.rs | 2 +- 7 files changed, 34 insertions(+), 382 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 9dec067dc..29497903b 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}; diff --git a/src/builtins/core/duration.rs b/src/builtins/core/duration.rs index f197def97..ebf2f0728 100644 --- a/src/builtins/core/duration.rs +++ b/src/builtins/core/duration.rs @@ -29,9 +29,6 @@ pub(crate) mod normalized; #[cfg(test)] mod tests; -// #[doc(inline)] -// pub use time::TimeDuration; - /// A `PartialDuration` is a Duration that may have fields not set. #[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)] pub struct PartialDuration { @@ -74,8 +71,7 @@ type U40 = BUintD8<5>; // 40 / 8 = 5 /// The native Rust implementation of `Temporal.Duration`. /// -/// `Duration` is made up of a `DateDuration` and `TimeDuration` as primarily -/// defined by Abtract Operation 7.5.1-5. +/// primarily defined by Abtract Operation 7.5.1-5. // #[non_exhaustive] #[derive(Debug, Clone, Copy, Default, PartialEq, PartialOrd)] pub struct Duration { @@ -183,14 +179,14 @@ impl Duration { ) } - /// Returns this `TimeDuration` as a `NormalizedTimeDuration`. + /// Returns this `Duration` as a `NormalizedTimeDuration`. #[inline] pub(crate) fn to_normalized(self) -> NormalizedTimeDuration { NormalizedTimeDuration::from_duration(&self) } - /// Balances and creates `TimeDuration` from a `NormalizedTimeDuration`. This method will return - /// a tuple (f64, TimeDuration) where f64 is the overflow day value from balancing. + /// Balances and creates `Duration` from a `NormalizedTimeDuration`. This method will return + /// a tuple (f64, Duration) where f64 is the overflow day value from balancing. /// /// Equivalent: `BalanceTimeDuration` /// @@ -338,7 +334,7 @@ impl Duration { microseconds * sign as i128, nanoseconds * sign as i128, ) { - return Err(TemporalError::range().with_message("Invalid balance TimeDuration.")); + return Err(TemporalError::range().with_message("Invalid balance Duration.")); } // TODO: Remove cast below. @@ -382,7 +378,7 @@ impl Duration { ] } - /// Returns whether `Duration`'s `DateDuration` is empty and is therefore a `TimeDuration`. + /// Returns whether `Duration`'s `DateDuration` is empty. #[inline] #[must_use] pub(crate) fn is_time_duration(&self) -> bool { @@ -434,48 +430,33 @@ impl Duration { } Ok(Duration { sign: Sign::from(years), - years: u32::try_from(years).expect("Years must be within range."), - months: u32::try_from(months).expect("Months must be within range."), - weeks: u32::try_from(weeks).expect("Weeks must be within range."), - days: U40::try_from(days).expect("Days must be within range."), - hours: U48::try_from(hours).expect("Hours must be within range."), - minutes: U48::try_from(minutes).expect("Minutes must be within range."), - seconds: U56::try_from(seconds).expect("Seconds must be within range."), - milliseconds: u64::try_from(milliseconds).expect("Milliseconds must be within range."), - microseconds: U80::try_from(microseconds).expect("Microseconds must be within range."), - nanoseconds: U88::try_from(nanoseconds).expect("Nanoseconds must be within range."), + years: u32::try_from(years).unwrap(), + months: u32::try_from(months).unwrap(), + weeks: u32::try_from(weeks).unwrap(), + days: U40::try_from(days).unwrap(), + hours: U48::try_from(hours).unwrap(), + minutes: U48::try_from(minutes).unwrap(), + seconds: U56::try_from(seconds).unwrap(), + milliseconds: u64::try_from(milliseconds).unwrap(), + microseconds: U80::try_from(microseconds).unwrap(), + nanoseconds: U88::try_from(nanoseconds).unwrap(), }) } - /// Creates a `Duration` from a provided a day and a `TimeDuration`. + /// Creates a `Duration` from a provided a day and a `Duration`. /// - /// Note: `TimeDuration` records can store a day value to deal with overflow. + /// Note: `Duration` records can store a day value to deal with overflow. #[must_use] pub fn from_day_and_time(day: i64, time: &Duration) -> Self { Self { sign: time.sign(), - days: day.try_into().expect("Days must be within range."), - hours: time.hours.try_into().expect("Hours must be within range."), - minutes: time - .minutes - .try_into() - .expect("Minutes must be within range."), - seconds: time - .seconds - .try_into() - .expect("Seconds must be within range."), - milliseconds: time - .milliseconds - .try_into() - .expect("Milliseconds must be within range."), - microseconds: time - .microseconds - .try_into() - .expect("Microseconds must be within range."), - nanoseconds: time - .nanoseconds - .try_into() - .expect("Nanoseconds must be within range."), + days: day.try_into().unwrap(), + hours: time.hours.try_into().unwrap(), + minutes: time.minutes.try_into().unwrap(), + seconds: time.seconds.try_into().unwrap(), + milliseconds: time.milliseconds.try_into().unwrap(), + microseconds: time.microseconds.try_into().unwrap(), + nanoseconds: time.nanoseconds.try_into().unwrap(), ..Default::default() } } @@ -680,13 +661,6 @@ 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 the inner `DateDuration` as an owned value #[inline] #[must_use] diff --git a/src/builtins/core/duration/normalized.rs b/src/builtins/core/duration/normalized.rs index c222f0a32..d831d96b8 100644 --- a/src/builtins/core/duration/normalized.rs +++ b/src/builtins/core/duration/normalized.rs @@ -35,7 +35,7 @@ const NANOSECONDS_PER_HOUR: i128 = 60 * NANOSECONDS_PER_MINUTE; // // nanoseconds.abs() <= MAX_TIME_DURATION -/// A Normalized `TimeDuration` that represents the current `TimeDuration` in nanoseconds. +/// A Normalized `Duration` that represents the current `Duration` in nanoseconds. #[derive(Debug, Clone, Copy, Default, PartialEq, PartialOrd, Eq, Ord)] pub(crate) struct NormalizedTimeDuration(pub(crate) i128); 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 ee861bcf7..8eb007dca 100644 --- a/src/builtins/core/instant.rs +++ b/src/builtins/core/instant.rs @@ -45,7 +45,7 @@ impl From for Instant { impl Instant { // TODO: Update to `i128`? - /// Adds a `TimeDuration` to the current `Instant`. + /// Adds a `Duration` to the current `Instant`. /// /// Temporal-Proposal equivalent: `AddInstant`. pub fn add_to_instant(&self, duration: &Duration) -> TemporalResult { @@ -235,13 +235,13 @@ impl Instant { self.add_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) diff --git a/src/builtins/core/time.rs b/src/builtins/core/time.rs index 0278c216a..cb6437edd 100644 --- a/src/builtins/core/time.rs +++ b/src/builtins/core/time.rs @@ -127,7 +127,7 @@ 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: &Duration) -> TemporalResult { @@ -406,7 +406,7 @@ impl PlainTime { self.add_time_duration(duration) } - /// Adds a `TimeDuration` to the current `Time`. + /// Adds a `Duration` to the current `Time`. #[inline] pub fn add_time_duration(&self, duration: &Duration) -> TemporalResult { self.add_to_time(duration) @@ -421,7 +421,7 @@ impl PlainTime { self.subtract_time_duration(duration) } - /// Adds a `TimeDuration` to the current `Time`. + /// Adds a `Duration` to the current `Time`. #[inline] pub fn subtract_time_duration(&self, duration: &Duration) -> TemporalResult { self.add_to_time(&duration.negated()) diff --git a/temporal_capi/src/duration.rs b/temporal_capi/src/duration.rs index 76b437834..5852a0ede 100644 --- a/temporal_capi/src/duration.rs +++ b/temporal_capi/src/duration.rs @@ -166,7 +166,7 @@ pub mod ffi { // 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 + // If needed, implement it as with_time_duration(&self, Duration) -> Self pub fn years(&self) -> i64 { self.0.years() From 22a2d41d3980dde7c8921e932d5fbe8359fa1720 Mon Sep 17 00:00:00 2001 From: Matt Stone Date: Sat, 21 Jun 2025 23:24:17 -0700 Subject: [PATCH 04/26] Fix compile error --- temporal_capi/src/duration.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/temporal_capi/src/duration.rs b/temporal_capi/src/duration.rs index 5852a0ede..05edf940e 100644 --- a/temporal_capi/src/duration.rs +++ b/temporal_capi/src/duration.rs @@ -160,8 +160,8 @@ pub mod ffi { self.0.is_time_within_range() } - pub fn date<'a>(&'a self) -> &'a DateDuration { - DateDuration::transparent_convert(&self.0.date()) + pub fn date(&self) -> Box { + Box::new(DateDuration(self.0.date())) } // set_time_duration is NOT safe to expose over FFI if the date()/time() methods are available From 5cfb20661b47f10b84596526d027369ac128819b Mon Sep 17 00:00:00 2001 From: Matt Stone Date: Sat, 21 Jun 2025 23:25:27 -0700 Subject: [PATCH 05/26] Fix lint --- src/builtins/core/date.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/builtins/core/date.rs b/src/builtins/core/date.rs index 9a8e7cf08..4a37970a4 100644 --- a/src/builtins/core/date.rs +++ b/src/builtins/core/date.rs @@ -20,8 +20,8 @@ use icu_calendar::AnyCalendarKind; use writeable::Writeable; use super::{ - calendar::month_to_month_code, duration::normalized::NormalizedDurationRecord, PartialYearMonth, - PlainMonthDay, PlainYearMonth, + calendar::month_to_month_code, duration::normalized::NormalizedDurationRecord, + PartialYearMonth, PlainMonthDay, PlainYearMonth, }; use tinystr::TinyAsciiStr; From 87c8513b494195788868642952bf072c412a2e4e Mon Sep 17 00:00:00 2001 From: Matt Stone Date: Sat, 21 Jun 2025 23:44:57 -0700 Subject: [PATCH 06/26] Fix tests --- src/builtins/compiled/duration/tests.rs | 29 ++++++++++++++++++++++-- src/builtins/core/duration/normalized.rs | 5 ++-- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/builtins/compiled/duration/tests.rs b/src/builtins/compiled/duration/tests.rs index 29497903b..3e170d2c4 100644 --- a/src/builtins/compiled/duration/tests.rs +++ b/src/builtins/compiled/duration/tests.rs @@ -373,7 +373,20 @@ 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::new_unchecked( + Default::default(), + 0, + 0, + 0, + 0u8.into(), + 25u8.into(), + 0u8.into(), + 0u8.into(), + 0u8.into(), + 0u8.into(), + 0u8.into(), + ); + let options = RoundingOptions { largest_unit: Some(Unit::Day), smallest_unit: None, @@ -485,7 +498,19 @@ 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::new_unchecked( + Default::default(), + 0, + 0, + 0, + 0u8.into(), + 25u8.into(), + 0u8.into(), + 0u8.into(), + 0u8.into(), + 0u8.into(), + 0u8.into(), + ); let zdt = ZonedDateTime::try_new( 1_000_000_000_000_000_000, Calendar::default(), diff --git a/src/builtins/core/duration/normalized.rs b/src/builtins/core/duration/normalized.rs index d831d96b8..235589c3f 100644 --- a/src/builtins/core/duration/normalized.rs +++ b/src/builtins/core/duration/normalized.rs @@ -116,9 +116,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("SubtractNormalizedTimeDuration exceeded a valid Duration range.")); } Ok(Self(result)) } From 21db4f513c6b4c0f00944da576537c06d3a84718 Mon Sep 17 00:00:00 2001 From: Matt Stone Date: Sun, 22 Jun 2025 00:04:12 -0700 Subject: [PATCH 07/26] Fix tests and lint errors --- src/builtins/core/duration.rs | 92 +++++++++++++----------- src/builtins/core/duration/date.rs | 14 ++-- src/builtins/core/duration/normalized.rs | 41 +++++++---- src/builtins/core/time.rs | 43 ++++++++--- src/iso.rs | 21 +++--- temporal_capi/src/duration.rs | 2 +- 6 files changed, 133 insertions(+), 80 deletions(-) diff --git a/src/builtins/core/duration.rs b/src/builtins/core/duration.rs index ebf2f0728..4fc4ebeef 100644 --- a/src/builtins/core/duration.rs +++ b/src/builtins/core/duration.rs @@ -125,6 +125,7 @@ impl Duration { impl Duration { /// Creates a new `Duration` with provided fields. #[inline] + #[allow(clippy::too_many_arguments)] pub(crate) const fn new_unchecked( sign: Sign, years: u32, @@ -167,15 +168,21 @@ impl Duration { duration_record.date().months.into(), duration_record.date().weeks.into(), i64::try_from(duration_record.date().days) - .unwrap() + .or(Err(TemporalError::range()))? .checked_add(overflow_day) .ok_or(TemporalError::range())?, - time.hours.try_into().unwrap(), - time.minutes.try_into().unwrap(), - time.seconds.try_into().unwrap(), - time.milliseconds.try_into().unwrap(), - time.microseconds.try_into().unwrap(), - time.nanoseconds.try_into().unwrap(), + time.hours.try_into().or(Err(TemporalError::range()))?, + time.minutes.try_into().or(Err(TemporalError::range()))?, + time.seconds.try_into().or(Err(TemporalError::range()))?, + time.milliseconds + .try_into() + .or(Err(TemporalError::range()))?, + time.microseconds + .try_into() + .or(Err(TemporalError::range()))?, + time.nanoseconds + .try_into() + .or(Err(TemporalError::range()))?, ) } @@ -430,35 +437,34 @@ impl Duration { } Ok(Duration { sign: Sign::from(years), - years: u32::try_from(years).unwrap(), - months: u32::try_from(months).unwrap(), - weeks: u32::try_from(weeks).unwrap(), - days: U40::try_from(days).unwrap(), - hours: U48::try_from(hours).unwrap(), - minutes: U48::try_from(minutes).unwrap(), - seconds: U56::try_from(seconds).unwrap(), - milliseconds: u64::try_from(milliseconds).unwrap(), - microseconds: U80::try_from(microseconds).unwrap(), - nanoseconds: U88::try_from(nanoseconds).unwrap(), + years: u32::try_from(years).or(Err(TemporalError::range()))?, + months: u32::try_from(months).or(Err(TemporalError::range()))?, + weeks: u32::try_from(weeks).or(Err(TemporalError::range()))?, + days: U40::try_from(days).or(Err(TemporalError::range()))?, + hours: U48::try_from(hours).or(Err(TemporalError::range()))?, + minutes: U48::try_from(minutes).or(Err(TemporalError::range()))?, + seconds: U56::try_from(seconds).or(Err(TemporalError::range()))?, + milliseconds: u64::try_from(milliseconds).or(Err(TemporalError::range()))?, + microseconds: U80::try_from(microseconds).or(Err(TemporalError::range()))?, + nanoseconds: U88::try_from(nanoseconds).or(Err(TemporalError::range()))?, }) } /// Creates a `Duration` from a provided a day and a `Duration`. /// /// Note: `Duration` records can store a day value to deal with overflow. - #[must_use] - pub fn from_day_and_time(day: i64, time: &Duration) -> Self { - Self { + pub fn from_day_and_time(day: i64, time: &Duration) -> TemporalResult { + Ok(Self { sign: time.sign(), - days: day.try_into().unwrap(), - hours: time.hours.try_into().unwrap(), - minutes: time.minutes.try_into().unwrap(), - seconds: time.seconds.try_into().unwrap(), - milliseconds: time.milliseconds.try_into().unwrap(), - microseconds: time.microseconds.try_into().unwrap(), - nanoseconds: time.nanoseconds.try_into().unwrap(), + days: day.try_into().or(Err(TemporalError::range()))?, + hours: time.hours, + minutes: time.minutes, + seconds: time.seconds, + milliseconds: time.milliseconds, + microseconds: time.microseconds, + nanoseconds: time.nanoseconds, ..Default::default() - } + }) } /// Creates a `Duration` from a provided `PartialDuration`. @@ -645,8 +651,8 @@ impl Duration { (days1, days2) } else { ( - self.days.try_into().unwrap(), - other.days.try_into().unwrap(), + self.days.try_into().or(Err(TemporalError::range()))?, + other.days.try_into().or(Err(TemporalError::range()))?, ) }; // 15. Let timeDuration1 be ? Add24HourDaysToTimeDuration(duration1.[[Time]], days1). @@ -693,49 +699,55 @@ impl Duration { #[inline] #[must_use] pub fn days(&self) -> i64 { - self.days.try_into().unwrap() + self.days.try_into().expect("Days must fit into i64") } /// Returns the `hours` field of duration. #[inline] #[must_use] pub fn hours(&self) -> i64 { - self.hours.try_into().unwrap() + self.hours.try_into().expect("Hours must fit into i64") } - /// Returns the `hours` field of duration. + /// Returns the `minutes` field of duration. #[inline] #[must_use] pub fn minutes(&self) -> i64 { - self.minutes.try_into().unwrap() + self.minutes.try_into().expect("Minutes must fit into i64") } /// Returns the `seconds` field of duration. #[inline] #[must_use] pub fn seconds(&self) -> i64 { - self.seconds.try_into().unwrap() + self.seconds.try_into().expect("Seconds must fit into i64") } /// Returns the `hours` field of duration. #[inline] #[must_use] pub fn milliseconds(&self) -> i64 { - self.milliseconds.try_into().unwrap() + self.milliseconds + .try_into() + .expect("Milliseconds must fit into i64") } /// Returns the `microseconds` field of duration. #[inline] #[must_use] pub fn microseconds(&self) -> i128 { - self.microseconds.try_into().unwrap() + self.microseconds + .try_into() + .expect("Microseconds must fit into i128") } /// Returns the `nanoseconds` field of duration. #[inline] #[must_use] pub fn nanoseconds(&self) -> i128 { - self.nanoseconds.try_into().unwrap() + self.nanoseconds + .try_into() + .expect("Nanoseconds must fit into i128") } } @@ -818,7 +830,7 @@ impl Duration { // 32. Return ! CreateTemporalDuration(0, 0, 0, result.[[Days]], result.[[Hours]], result.[[Minutes]], // result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]). - Ok(Duration::from_day_and_time(result_days, &result_time)) + Duration::from_day_and_time(result_days, &result_time) } /// Returns the result of subtracting a `Duration` from the current `Duration` @@ -1167,7 +1179,7 @@ impl Duration { // 12. Let internalDuration be ToInternalDurationRecord(duration). let norm = NormalizedDurationRecord::new( self.date(), - NormalizedTimeDuration::from_duration(&self), + NormalizedTimeDuration::from_duration(self), )?; // 13. Let timeDuration be ? RoundTimeDuration(internalDuration.[[Time]], precision.[[Increment]], precision.[[Unit]], roundingMode). let time = norm.normalized_time_duration().round(rounding_options)?; diff --git a/src/builtins/core/duration/date.rs b/src/builtins/core/duration/date.rs index c40716071..ea36cd527 100644 --- a/src/builtins/core/duration/date.rs +++ b/src/builtins/core/duration/date.rs @@ -36,10 +36,10 @@ impl DateDuration { pub(crate) fn new_unchecked(years: i64, months: i64, weeks: i64, days: i64) -> Self { Self { sign: Sign::from(years), - years: years.try_into().unwrap(), - months: months.try_into().unwrap(), - weeks: weeks.try_into().unwrap(), - days: days.try_into().unwrap(), + years: years.try_into().expect("years must fit in u32"), + months: months.try_into().expect("months must fit in u32"), + weeks: weeks.try_into().expect("weeks must fit in u32"), + days: days.try_into().expect("days must fit in u40"), } } @@ -51,7 +51,7 @@ impl DateDuration { self.years.into(), self.months.into(), self.weeks.into(), - self.days.try_into().unwrap(), + self.days.try_into().expect("days must fit in i64"), ] } } @@ -142,7 +142,7 @@ impl DateDuration { let ymw_duration = self.adjust(0, None, None)?; // 2. If DateDurationSign(yearsMonthsWeeksDuration) = 0, return dateDuration.[[Days]]. if ymw_duration.sign() == Sign::Zero { - return Ok(self.days.try_into().unwrap()); + return self.days.try_into().or(Err(TemporalError::range())); } // 3. Let later be ? CalendarDateAdd(plainRelativeTo.[[Calendar]], plainRelativeTo.[[ISODate]], yearsMonthsWeeksDuration, constrain). let later = relative_to.add( @@ -170,7 +170,7 @@ impl DateDuration { // 6. Let yearsMonthsWeeksInDays be epochDays2 - epochDays1. let ymd_in_days = epoch_days_2 - epoch_days_1; // 7. Return dateDuration.[[Days]] + yearsMonthsWeeksInDays. - Ok(i64::try_from(self.days).unwrap() + ymd_in_days) + Ok(i64::try_from(self.days).or(Err(TemporalError::range()))? + ymd_in_days) } /// `7.5.10 AdjustDateDurationRecord ( dateDuration, days [ , weeks [ , months ] ] )` diff --git a/src/builtins/core/duration/normalized.rs b/src/builtins/core/duration/normalized.rs index 235589c3f..ab5be6ae5 100644 --- a/src/builtins/core/duration/normalized.rs +++ b/src/builtins/core/duration/normalized.rs @@ -43,12 +43,14 @@ impl NormalizedTimeDuration { /// Equivalent: 7.5.20 NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds, nanoseconds ) 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 = i128::try_from(duration.hours).unwrap() * NANOSECONDS_PER_HOUR; - nanoseconds += i128::try_from(duration.minutes).unwrap() * NANOSECONDS_PER_MINUTE; - nanoseconds += i128::try_from(duration.seconds).unwrap() * 1_000_000_000; - nanoseconds += i128::try_from(duration.milliseconds).unwrap() * 1_000_000; - nanoseconds += i128::try_from(duration.microseconds).unwrap() * 1_000; - nanoseconds += i128::try_from(duration.nanoseconds).unwrap(); + let mut nanoseconds: i128 = + i128::try_from(duration.hours).expect("hour overflow") * NANOSECONDS_PER_HOUR; + nanoseconds += + i128::try_from(duration.minutes).expect("minute overflow") * NANOSECONDS_PER_MINUTE; + nanoseconds += i128::try_from(duration.seconds).expect("second overflow") * 1_000_000_000; + nanoseconds += i128::from(duration.milliseconds) * 1_000_000; + nanoseconds += i128::try_from(duration.microseconds).expect("microsecond overflow") * 1_000; + nanoseconds += i128::try_from(duration.nanoseconds).expect("nanosecond overflow"); // NOTE(nekevss): Is it worth returning a `RangeError` below. debug_assert!(nanoseconds.abs() <= MAX_TIME_DURATION); Self(nanoseconds) @@ -259,7 +261,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 = 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). @@ -405,7 +407,8 @@ impl NormalizedDurationRecord { let iso_two = IsoDate::try_balance( dt.iso_year() + self.date().years as i32, i32::from(dt.iso_month()) + self.date().months as i32, - i64::from(dt.iso_day()) + i64::try_from(self.date().days).unwrap(), + i64::from(dt.iso_day()) + + i64::try_from(self.date().days).or(Err(TemporalError::range()))?, )?; // c. Let weeksStart be ! CreateTemporalDate(isoResult1.[[Year]], isoResult1.[[Month]], isoResult1.[[Day]], @@ -468,7 +471,7 @@ impl NormalizedDurationRecord { // a. Assert: unit is "day". // b. Let days be RoundNumberToIncrement(duration.[[Days]], increment, "trunc"). let days = IncrementRounder::from_signed_num( - i64::try_from(self.date().days).unwrap(), + i64::try_from(self.date().days).or(Err(TemporalError::range()))?, options.increment.as_extended_increment(), )? .round(RoundingMode::Trunc); @@ -683,7 +686,7 @@ impl NormalizedDurationRecord { i64::from(self.date.months), i64::from(self.date.weeks), i64::try_from(self.date.days) - .unwrap() + .or(Err(TemporalError::range()))? .checked_add(day_delta.into()) .ok_or(TemporalError::range())?, )?; @@ -823,7 +826,11 @@ impl NormalizedDurationRecord { let years = self .date() .years - .checked_add(sign.as_sign_multiplier().try_into().unwrap()) + .checked_add( + sign.as_sign_multiplier() + .try_into() + .or(Err(TemporalError::range()))?, + ) .ok_or(TemporalError::range())?; // 2. Let endDuration be ? CreateDateDurationRecord(years, 0, 0, 0). @@ -835,7 +842,11 @@ impl NormalizedDurationRecord { let months = self .date() .months - .checked_add(sign.as_sign_multiplier().try_into().unwrap()) + .checked_add( + sign.as_sign_multiplier() + .try_into() + .or(Err(TemporalError::range()))?, + ) .ok_or(TemporalError::range())?; // 2. Let endDuration be ? AdjustDateDurationRecord(duration.[[Date]], 0, 0, months). @@ -850,7 +861,11 @@ impl NormalizedDurationRecord { let weeks = self .date() .weeks - .checked_add(sign.as_sign_multiplier().try_into().unwrap()) + .checked_add( + sign.as_sign_multiplier() + .try_into() + .or(Err(TemporalError::range()))?, + ) .ok_or(TemporalError::range())?; // 3. Let endDuration be ? AdjustDateDurationRecord(duration.[[Date]], 0, weeks). diff --git a/src/builtins/core/time.rs b/src/builtins/core/time.rs index cb6437edd..f3bebb15f 100644 --- a/src/builtins/core/time.rs +++ b/src/builtins/core/time.rs @@ -132,13 +132,38 @@ impl PlainTime { /// Spec Equivalent: `AddDurationToOrSubtractDurationFromPlainTime`. pub(crate) fn add_to_time(&self, duration: &Duration) -> TemporalResult { let (_, result) = IsoTime::balance( - i64::from(self.hour()).saturating_add(duration.hours.try_into().unwrap()), - i64::from(self.minute()).saturating_add(duration.minutes.try_into().unwrap()), - i64::from(self.second()).saturating_add(duration.seconds.try_into().unwrap()), - i64::from(self.millisecond()).saturating_add(duration.milliseconds.try_into().unwrap()), - i128::from(self.microsecond()) - .saturating_add(duration.microseconds.try_into().unwrap()), - i128::from(self.nanosecond()).saturating_add(duration.nanoseconds.try_into().unwrap()), + i64::from(self.hour()) + .saturating_add(duration.hours.try_into().or(Err(TemporalError::range()))?), + i64::from(self.minute()).saturating_add( + duration + .minutes + .try_into() + .or(Err(TemporalError::range()))?, + ), + i64::from(self.second()).saturating_add( + duration + .seconds + .try_into() + .or(Err(TemporalError::range()))?, + ), + i64::from(self.millisecond()).saturating_add( + duration + .milliseconds + .try_into() + .or(Err(TemporalError::range()))?, + ), + i128::from(self.microsecond()).saturating_add( + duration + .microseconds + .try_into() + .or(Err(TemporalError::range()))?, + ), + i128::from(self.nanosecond()).saturating_add( + duration + .nanoseconds + .try_into() + .or(Err(TemporalError::range()))?, + ), ); // NOTE (nekevss): IsoTime::balance should never return an invalid `IsoTime` @@ -186,8 +211,8 @@ impl PlainTime { // 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()), } } } diff --git a/src/iso.rs b/src/iso.rs index a79186ae3..eb6f6c399 100644 --- a/src/iso.rs +++ b/src/iso.rs @@ -171,7 +171,7 @@ impl IsoDateTime { date_duration.months.into(), date_duration.weeks.into(), i64::try_from(date_duration.days) - .unwrap() + .or(Err(TemporalError::range()))? .checked_add(t_result.0) .ok_or(TemporalError::range())?, )?; @@ -406,9 +406,10 @@ impl IsoDate { Self::new_with_overflow(intermediate.0, intermediate.1, self.day, overflow)?; // 5. Set days to days + 7 × weeks. - let additional_days = - i64::try_from(duration.days).unwrap() + (7 * i64::from(duration.weeks)); // Verify - // 6. Let d be intermediate.[[Day]] + days. + let additional_days = i64::try_from(duration.days).or(Err(TemporalError::range()))? + + (7 * i64::from(duration.weeks)); // Verify + + // 6. Let d be intermediate.[[Day]] + days. let intermediate_days = i64::from(intermediate.day) + additional_days; // 7. Return BalanceISODate(intermediate.[[Year]], intermediate.[[Month]], d). @@ -730,12 +731,12 @@ impl IsoTime { 0, 0, 0u8.into(), - h.try_into().unwrap(), - m.try_into().unwrap(), - s.try_into().unwrap(), - ms.try_into().unwrap(), - mis.try_into().unwrap(), - ns.try_into().unwrap(), + h.try_into().expect("hour overflow"), + m.try_into().expect("minute overflow"), + s.try_into().expect("second overflow"), + ms.try_into().expect("millisecond overflow"), + mis.try_into().expect("microsecond overflow"), + ns.try_into().expect("nanosecond overflow"), ) } diff --git a/temporal_capi/src/duration.rs b/temporal_capi/src/duration.rs index 05edf940e..f3ba0d20d 100644 --- a/temporal_capi/src/duration.rs +++ b/temporal_capi/src/duration.rs @@ -133,7 +133,7 @@ pub mod ffi { pub fn from_day_and_time(day: i64, time: &Duration) -> Result, TemporalError> { Ok(Box::new(Duration( - temporal_rs::Duration::from_day_and_time(day, &time.0), + temporal_rs::Duration::from_day_and_time(day, &time.0)?, ))) } pub fn from_partial_duration(partial: PartialDuration) -> Result, TemporalError> { From 45d3262472bb6c4f786fff0a82f6e2e75135036a Mon Sep 17 00:00:00 2001 From: Matt Stone Date: Sun, 22 Jun 2025 00:06:29 -0700 Subject: [PATCH 08/26] Fix depcheck --- depcheck/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/depcheck/src/main.rs b/depcheck/src/main.rs index d1e41d226..092616ab2 100644 --- a/depcheck/src/main.rs +++ b/depcheck/src/main.rs @@ -174,6 +174,7 @@ pub const BASIC_RUNTIME_DEPS: &[&str] = &[ "zerotrie", "zerovec", // Other deps + "bnum", "libm", "num-traits", "stable_deref_trait", From a2e677a7adc223f5dae80c55f21b274c13a7d4a2 Mon Sep 17 00:00:00 2001 From: Matt Stone Date: Sun, 22 Jun 2025 01:16:07 -0700 Subject: [PATCH 09/26] cargo run -p diplomat-gen --- temporal_capi/bindings/c/Duration.h | 7 +- temporal_capi/bindings/c/Instant.h | 5 +- temporal_capi/bindings/c/PlainTime.h | 5 +- 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 | 18 +--- .../bindings/cpp/temporal_rs/Instant.d.hpp | 6 +- .../bindings/cpp/temporal_rs/Instant.hpp | 9 +- .../bindings/cpp/temporal_rs/PlainTime.d.hpp | 6 +- .../bindings/cpp/temporal_rs/PlainTime.hpp | 9 +- .../cpp/temporal_rs/TimeDuration.d.hpp | 57 ------------ .../bindings/cpp/temporal_rs/TimeDuration.hpp | 91 ------------------- 13 files changed, 25 insertions(+), 252 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 0909543f5..e84f1c5d6 100644 --- a/temporal_capi/bindings/c/Duration.h +++ b/temporal_capi/bindings/c/Duration.h @@ -13,7 +13,6 @@ #include "RoundingOptions.d.h" #include "Sign.d.h" #include "TemporalError.d.h" -#include "TimeDuration.d.h" #include "ToStringRoundingOptions.d.h" #include "Unit.d.h" @@ -31,7 +30,7 @@ typedef struct temporal_rs_Duration_try_new_result {union {Duration* ok; Tempora temporal_rs_Duration_try_new_result temporal_rs_Duration_try_new(int64_t years, int64_t months, int64_t weeks, int64_t days, int64_t hours, int64_t minutes, int64_t seconds, int64_t milliseconds, double microseconds, double nanoseconds); typedef struct temporal_rs_Duration_from_day_and_time_result {union {Duration* ok; TemporalError err;}; bool is_ok;} temporal_rs_Duration_from_day_and_time_result; -temporal_rs_Duration_from_day_and_time_result temporal_rs_Duration_from_day_and_time(int64_t day, const TimeDuration* time); +temporal_rs_Duration_from_day_and_time_result temporal_rs_Duration_from_day_and_time(int64_t day, const Duration* time); typedef struct temporal_rs_Duration_from_partial_duration_result {union {Duration* ok; TemporalError err;}; bool is_ok;} temporal_rs_Duration_from_partial_duration_result; temporal_rs_Duration_from_partial_duration_result temporal_rs_Duration_from_partial_duration(PartialDuration partial); @@ -44,9 +43,7 @@ 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); +DateDuration* temporal_rs_Duration_date(const Duration* self); int64_t temporal_rs_Duration_years(const Duration* self); diff --git a/temporal_capi/bindings/c/Instant.h b/temporal_capi/bindings/c/Instant.h index 2b6b4cd2c..71e19433d 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" @@ -40,13 +39,13 @@ typedef struct temporal_rs_Instant_add_result {union {Instant* ok; TemporalError 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); +temporal_rs_Instant_add_time_duration_result temporal_rs_Instant_add_time_duration(const Instant* self, const Duration* 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); +temporal_rs_Instant_subtract_time_duration_result temporal_rs_Instant_subtract_time_duration(const Instant* self, const Duration* 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 5d9616bea..f1fb818d4 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" @@ -65,10 +64,10 @@ typedef struct temporal_rs_PlainTime_subtract_result {union {PlainTime* ok; Temp 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); +temporal_rs_PlainTime_add_time_duration_result temporal_rs_PlainTime_add_time_duration(const PlainTime* self, const Duration* 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); +temporal_rs_PlainTime_subtract_time_duration_result temporal_rs_PlainTime_subtract_time_duration(const PlainTime* self, const Duration* 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 69b2e093c..a554dce60 100644 --- a/temporal_capi/bindings/cpp/temporal_rs/Duration.d.hpp +++ b/temporal_capi/bindings/cpp/temporal_rs/Duration.d.hpp @@ -16,8 +16,6 @@ namespace capi { struct DateDuration; } class DateDuration; namespace capi { struct Duration; } class Duration; -namespace capi { struct TimeDuration; } -class TimeDuration; struct PartialDuration; struct RelativeTo; struct RoundingOptions; @@ -45,7 +43,7 @@ class Duration { inline static diplomat::result, temporal_rs::TemporalError> try_new(int64_t years, int64_t months, int64_t weeks, int64_t days, int64_t hours, int64_t minutes, int64_t seconds, int64_t milliseconds, double microseconds, double nanoseconds); - inline static diplomat::result, temporal_rs::TemporalError> from_day_and_time(int64_t day, const temporal_rs::TimeDuration& time); + inline static diplomat::result, temporal_rs::TemporalError> from_day_and_time(int64_t day, const temporal_rs::Duration& time); inline static diplomat::result, temporal_rs::TemporalError> from_partial_duration(temporal_rs::PartialDuration partial); @@ -55,9 +53,7 @@ class Duration { inline bool is_time_within_range() const; - inline const temporal_rs::TimeDuration& time() const; - - inline const temporal_rs::DateDuration& date() const; + inline std::unique_ptr date() const; inline int64_t years() const; diff --git a/temporal_capi/bindings/cpp/temporal_rs/Duration.hpp b/temporal_capi/bindings/cpp/temporal_rs/Duration.hpp index b93c19ffa..fe652c642 100644 --- a/temporal_capi/bindings/cpp/temporal_rs/Duration.hpp +++ b/temporal_capi/bindings/cpp/temporal_rs/Duration.hpp @@ -18,7 +18,6 @@ #include "RoundingOptions.hpp" #include "Sign.hpp" #include "TemporalError.hpp" -#include "TimeDuration.hpp" #include "ToStringRoundingOptions.hpp" #include "Unit.hpp" @@ -34,7 +33,7 @@ namespace capi { temporal_rs_Duration_try_new_result temporal_rs_Duration_try_new(int64_t years, int64_t months, int64_t weeks, int64_t days, int64_t hours, int64_t minutes, int64_t seconds, int64_t milliseconds, double microseconds, double nanoseconds); typedef struct temporal_rs_Duration_from_day_and_time_result {union {temporal_rs::capi::Duration* ok; temporal_rs::capi::TemporalError err;}; bool is_ok;} temporal_rs_Duration_from_day_and_time_result; - temporal_rs_Duration_from_day_and_time_result temporal_rs_Duration_from_day_and_time(int64_t day, const temporal_rs::capi::TimeDuration* time); + temporal_rs_Duration_from_day_and_time_result temporal_rs_Duration_from_day_and_time(int64_t day, const temporal_rs::capi::Duration* time); typedef struct temporal_rs_Duration_from_partial_duration_result {union {temporal_rs::capi::Duration* ok; temporal_rs::capi::TemporalError err;}; bool is_ok;} temporal_rs_Duration_from_partial_duration_result; temporal_rs_Duration_from_partial_duration_result temporal_rs_Duration_from_partial_duration(temporal_rs::capi::PartialDuration partial); @@ -47,9 +46,7 @@ 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); + 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); @@ -131,7 +128,7 @@ inline diplomat::result, temporal_rs::Tem return result.is_ok ? diplomat::result, temporal_rs::TemporalError>(diplomat::Ok>(std::unique_ptr(temporal_rs::Duration::FromFFI(result.ok)))) : diplomat::result, temporal_rs::TemporalError>(diplomat::Err(temporal_rs::TemporalError::FromFFI(result.err))); } -inline diplomat::result, temporal_rs::TemporalError> temporal_rs::Duration::from_day_and_time(int64_t day, const temporal_rs::TimeDuration& time) { +inline diplomat::result, temporal_rs::TemporalError> temporal_rs::Duration::from_day_and_time(int64_t day, const temporal_rs::Duration& time) { auto result = temporal_rs::capi::temporal_rs_Duration_from_day_and_time(day, time.AsFFI()); return result.is_ok ? diplomat::result, temporal_rs::TemporalError>(diplomat::Ok>(std::unique_ptr(temporal_rs::Duration::FromFFI(result.ok)))) : diplomat::result, temporal_rs::TemporalError>(diplomat::Err(temporal_rs::TemporalError::FromFFI(result.err))); @@ -157,14 +154,9 @@ 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 { +inline std::unique_ptr temporal_rs::Duration::date() const { auto result = temporal_rs::capi::temporal_rs_Duration_date(this->AsFFI()); - return *temporal_rs::DateDuration::FromFFI(result); + return std::unique_ptr(temporal_rs::DateDuration::FromFFI(result)); } inline int64_t temporal_rs::Duration::years() const { diff --git a/temporal_capi/bindings/cpp/temporal_rs/Instant.d.hpp b/temporal_capi/bindings/cpp/temporal_rs/Instant.d.hpp index 09c325113..49f8b2b0e 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,11 +48,11 @@ 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> add_time_duration(const temporal_rs::Duration& 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> subtract_time_duration(const temporal_rs::Duration& duration) const; inline diplomat::result, temporal_rs::TemporalError> since(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 53b43fe45..37b9bc25a 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" @@ -43,13 +42,13 @@ namespace capi { 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); + temporal_rs_Instant_add_time_duration_result temporal_rs_Instant_add_time_duration(const temporal_rs::capi::Instant* self, const temporal_rs::capi::Duration* 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); + temporal_rs_Instant_subtract_time_duration_result temporal_rs_Instant_subtract_time_duration(const temporal_rs::capi::Instant* self, const temporal_rs::capi::Duration* 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); @@ -105,7 +104,7 @@ 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 { +inline diplomat::result, temporal_rs::TemporalError> temporal_rs::Instant::add_time_duration(const temporal_rs::Duration& 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))); @@ -117,7 +116,7 @@ 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::subtract_time_duration(const temporal_rs::TimeDuration& duration) const { +inline diplomat::result, temporal_rs::TemporalError> temporal_rs::Instant::subtract_time_duration(const temporal_rs::Duration& 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))); diff --git a/temporal_capi/bindings/cpp/temporal_rs/PlainTime.d.hpp b/temporal_capi/bindings/cpp/temporal_rs/PlainTime.d.hpp index eb34b14b3..8a73d4e77 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,9 +68,9 @@ 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> add_time_duration(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> subtract_time_duration(const temporal_rs::Duration& duration) const; inline diplomat::result, temporal_rs::TemporalError> until(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 ea5165726..181be3f96 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" @@ -68,10 +67,10 @@ namespace capi { 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); + temporal_rs_PlainTime_add_time_duration_result temporal_rs_PlainTime_add_time_duration(const temporal_rs::capi::PlainTime* self, const temporal_rs::capi::Duration* 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); + temporal_rs_PlainTime_subtract_time_duration_result temporal_rs_PlainTime_subtract_time_duration(const temporal_rs::capi::PlainTime* self, const temporal_rs::capi::Duration* 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); @@ -186,13 +185,13 @@ 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 { +inline diplomat::result, temporal_rs::TemporalError> temporal_rs::PlainTime::add_time_duration(const temporal_rs::Duration& 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 { +inline diplomat::result, temporal_rs::TemporalError> temporal_rs::PlainTime::subtract_time_duration(const temporal_rs::Duration& 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))); 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 9d46a147543d8e456e8776f18f523b6bb5380ab5 Mon Sep 17 00:00:00 2001 From: Matt Stone Date: Sun, 22 Jun 2025 11:19:07 -0700 Subject: [PATCH 10/26] Fix sign calculations --- src/builtins/core/duration.rs | 52 ++++++++++++++++++------------ src/builtins/core/duration/date.rs | 8 +++-- src/builtins/core/instant.rs | 22 ++++++++++--- src/iso.rs | 19 ++++++----- 4 files changed, 67 insertions(+), 34 deletions(-) diff --git a/src/builtins/core/duration.rs b/src/builtins/core/duration.rs index 4fc4ebeef..6722be353 100644 --- a/src/builtins/core/duration.rs +++ b/src/builtins/core/duration.rs @@ -154,7 +154,7 @@ impl Duration { } } - #[inline] + // #[inline] pub(crate) fn from_normalized( duration_record: NormalizedDurationRecord, largest_unit: Unit, @@ -212,7 +212,7 @@ impl Duration { let mut microseconds = 0; // 2. Let sign be NormalizedTimeDurationSign(norm). - let sign = i64::from(norm.sign() as i8); + let sign = norm.sign().as_sign_multiplier(); // 3. Let nanoseconds be NormalizedTimeDurationAbs(norm).[[TotalNanoseconds]]. let mut nanoseconds = norm.0.abs(); @@ -436,7 +436,18 @@ impl Duration { return Err(TemporalError::range().with_message("Duration was not valid.")); } Ok(Duration { - sign: Sign::from(years), + sign: duration_sign(&[ + years, + months, + weeks, + days, + hours, + minutes, + seconds, + milliseconds, + microseconds as i64, + nanoseconds as i64, + ]), years: u32::try_from(years).or(Err(TemporalError::range()))?, months: u32::try_from(months).or(Err(TemporalError::range()))?, weeks: u32::try_from(weeks).or(Err(TemporalError::range()))?, @@ -678,76 +689,77 @@ impl Duration { #[inline] #[must_use] pub fn years(&self) -> i64 { - self.years.into() + i64::from(self.years) * i64::from(self.sign.as_sign_multiplier()) } /// Returns the `months` field of duration. #[inline] #[must_use] pub fn months(&self) -> i64 { - self.months.into() + i64::from(self.months) * i64::from(self.sign.as_sign_multiplier()) } /// Returns the `weeks` field of duration. #[inline] #[must_use] pub fn weeks(&self) -> i64 { - self.weeks.into() + i64::from(self.weeks) * i64::from(self.sign.as_sign_multiplier()) } /// Returns the `days` field of duration. #[inline] #[must_use] pub fn days(&self) -> i64 { - self.days.try_into().expect("Days must fit into i64") + i64::try_from(self.days).expect("Days must fit into i64") + * i64::from(self.sign.as_sign_multiplier()) } /// Returns the `hours` field of duration. #[inline] #[must_use] pub fn hours(&self) -> i64 { - self.hours.try_into().expect("Hours must fit into i64") + i64::try_from(self.hours).expect("Hours must fit into i64") + * i64::from(self.sign.as_sign_multiplier()) } /// Returns the `minutes` field of duration. #[inline] #[must_use] pub fn minutes(&self) -> i64 { - self.minutes.try_into().expect("Minutes must fit into i64") + i64::try_from(self.minutes).expect("Minutes must fit into i64") + * i64::from(self.sign.as_sign_multiplier()) } /// Returns the `seconds` field of duration. #[inline] #[must_use] pub fn seconds(&self) -> i64 { - self.seconds.try_into().expect("Seconds must fit into i64") + i64::try_from(self.seconds).expect("Seconds must fit into i64") + * i64::from(self.sign.as_sign_multiplier()) } /// Returns the `hours` field of duration. #[inline] #[must_use] pub fn milliseconds(&self) -> i64 { - self.milliseconds - .try_into() - .expect("Milliseconds must fit into i64") + i64::try_from(self.milliseconds).expect("Milliseconds must fit into i64") + * i64::from(self.sign.as_sign_multiplier()) } /// Returns the `microseconds` field of duration. #[inline] #[must_use] pub fn microseconds(&self) -> i128 { - self.microseconds - .try_into() - .expect("Microseconds must fit into i128") + i128::try_from(self.microseconds).expect("Microseconds must fit into i128") + * i128::from(self.sign.as_sign_multiplier()) } /// Returns the `nanoseconds` field of duration. #[inline] #[must_use] pub fn nanoseconds(&self) -> i128 { - self.nanoseconds - .try_into() - .expect("Nanoseconds must fit into i128") + i128::try_from(self.nanoseconds).expect("Nanoseconds must fit into i128") + * i128::from(self.sign.as_sign_multiplier()) } } @@ -1344,7 +1356,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 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. diff --git a/src/builtins/core/duration/date.rs b/src/builtins/core/duration/date.rs index ea36cd527..26019788e 100644 --- a/src/builtins/core/duration/date.rs +++ b/src/builtins/core/duration/date.rs @@ -35,7 +35,7 @@ impl DateDuration { #[must_use] pub(crate) fn new_unchecked(years: i64, months: i64, weeks: i64, days: i64) -> Self { Self { - sign: Sign::from(years), + sign: duration_sign(&[years, months, weeks, days]), years: years.try_into().expect("years must fit in u32"), months: months.try_into().expect("months must fit in u32"), weeks: weeks.try_into().expect("weeks must fit in u32"), @@ -124,7 +124,11 @@ impl DateDuration { #[must_use] pub fn abs(&self) -> Self { Self { - sign: Sign::Positive, + sign: if self.sign == Sign::Zero { + Sign::Zero + } else { + Sign::Positive + }, ..*self } } diff --git a/src/builtins/core/instant.rs b/src/builtins/core/instant.rs index 8eb007dca..227f27feb 100644 --- a/src/builtins/core/instant.rs +++ b/src/builtins/core/instant.rs @@ -335,10 +335,10 @@ mod tests { use core::str::FromStr; use crate::{ - builtins::core::Instant, + builtins::{core::Instant, duration::duration_sign}, options::{DifferenceSettings, RoundingMode, Unit}, unix_time::EpochNanoseconds, - Duration, Sign, NS_MAX_INSTANT, NS_MIN_INSTANT, + Duration, NS_MAX_INSTANT, NS_MIN_INSTANT, }; #[test] @@ -443,7 +443,14 @@ mod tests { assert_eq!( td, &Duration::new_unchecked( - Sign::from(expected.0), + duration_sign(&[ + expected.0, + expected.1, + expected.2, + expected.3, + expected.4 as i64, + expected.5 as i64 + ]), 0, 0, 0, @@ -521,7 +528,14 @@ mod tests { assert_eq!( td, &Duration::new_unchecked( - Sign::from(expected.0), + duration_sign(&[ + expected.0, + expected.1, + expected.2, + expected.3, + expected.4 as i64, + expected.5 as i64 + ]), 0, 0, 0, diff --git a/src/iso.rs b/src/iso.rs index eb6f6c399..5e4920363 100644 --- a/src/iso.rs +++ b/src/iso.rs @@ -26,20 +26,23 @@ use core::num::NonZeroU128; use ixdtf::parsers::records::TimeRecord; use crate::{ - builtins::core::{ - calendar::Calendar, - duration::{ - normalized::{NormalizedDurationRecord, NormalizedTimeDuration}, - DateDuration, + builtins::{ + core::{ + calendar::Calendar, + duration::{ + normalized::{NormalizedDurationRecord, NormalizedTimeDuration}, + DateDuration, + }, + Duration, PartialTime, PlainDate, }, - Duration, PartialTime, PlainDate, + duration::duration_sign, }, error::TemporalError, options::{ArithmeticOverflow, ResolvedRoundingOptions, Unit}, rounding::{IncrementRounder, Round}, temporal_assert, unix_time::EpochNanoseconds, - utils, Sign, TemporalResult, TemporalUnwrap, NS_PER_DAY, + utils, TemporalResult, TemporalUnwrap, NS_PER_DAY, }; use icu_calendar::{Date as IcuDate, Iso}; use num_traits::{cast::FromPrimitive, Euclid}; @@ -726,7 +729,7 @@ impl IsoTime { let ns = i128::from(other.nanosecond) - i128::from(self.nanosecond); Duration::new_unchecked( - Sign::from(h), + duration_sign(&[h, m, s, ms, mis as i64, ns as i64]), 0, 0, 0, From cbd388e2850ec54006dcac81a016a0f60e7ea371 Mon Sep 17 00:00:00 2001 From: Matt Stone Date: Sun, 22 Jun 2025 11:35:12 -0700 Subject: [PATCH 11/26] Fix compile error --- src/builtins/core/duration.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/builtins/core/duration.rs b/src/builtins/core/duration.rs index 6722be353..49628d2ab 100644 --- a/src/builtins/core/duration.rs +++ b/src/builtins/core/duration.rs @@ -327,19 +327,19 @@ impl Duration { // 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 days = i64::try_from(days).map_err(|_| TemporalError::range())? * i64::from(sign); if !is_valid_duration( 0, 0, 0, days, - hours as i64 * sign, - minutes as i64 * sign, - seconds as i64 * sign, - milliseconds as i64 * sign, - microseconds * sign as i128, - nanoseconds * sign as i128, + hours as i64 * i64::from(sign), + minutes as i64 * i64::from(sign), + seconds as i64 * i64::from(sign), + milliseconds as i64 * i64::from(sign), + microseconds * i128::from(sign), + nanoseconds * i128::from(sign), ) { return Err(TemporalError::range().with_message("Invalid balance Duration.")); } From 0f67ff8228f239586df126f508b059d5a91cc13c Mon Sep 17 00:00:00 2001 From: Matthew Stone Date: Sun, 22 Jun 2025 11:47:06 -0700 Subject: [PATCH 12/26] Fix duration sign handling and time diff --- src/builtins/core/duration.rs | 52 ++++++++++++++++++++++++++---- src/builtins/core/duration/date.rs | 24 ++++++++++---- src/iso.rs | 16 +++++---- 3 files changed, 73 insertions(+), 19 deletions(-) diff --git a/src/builtins/core/duration.rs b/src/builtins/core/duration.rs index 49628d2ab..afc78cb90 100644 --- a/src/builtins/core/duration.rs +++ b/src/builtins/core/duration.rs @@ -73,7 +73,7 @@ type U40 = BUintD8<5>; // 40 / 8 = 5 /// /// primarily defined by Abtract Operation 7.5.1-5. // #[non_exhaustive] -#[derive(Debug, Clone, Copy, Default, PartialEq, PartialOrd)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] pub struct Duration { sign: Sign, pub(crate) years: u32, @@ -88,6 +88,24 @@ pub struct Duration { pub(crate) nanoseconds: U88, } +impl Default for Duration { + fn default() -> Self { + Self { + sign: Sign::Zero, + years: 0, + months: 0, + weeks: 0, + days: U40::from(0u8), + hours: U48::from(0u8), + minutes: U48::from(0u8), + seconds: U56::from(0u8), + milliseconds: 0, + microseconds: U80::from(0u8), + nanoseconds: U88::from(0u8), + } + } +} + impl core::fmt::Display for Duration { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_str( @@ -770,7 +788,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. @@ -787,8 +805,17 @@ impl Duration { #[must_use] pub fn negated(&self) -> Self { Self { - sign: self.sign().negate(), - ..Default::default() + sign: self.sign.negate(), + years: self.years, + months: self.months, + weeks: self.weeks, + days: self.days, + hours: self.hours, + minutes: self.minutes, + seconds: self.seconds, + milliseconds: self.milliseconds, + microseconds: self.microseconds, + nanoseconds: self.nanoseconds, } } @@ -797,8 +824,21 @@ impl Duration { #[must_use] pub fn abs(&self) -> Self { Self { - sign: self.sign().as_sign_multiplier().abs().into(), - ..Default::default() + sign: if self.sign == Sign::Zero { + Sign::Zero + } else { + Sign::Positive + }, + years: self.years, + months: self.months, + weeks: self.weeks, + days: self.days, + hours: self.hours, + minutes: self.minutes, + seconds: self.seconds, + milliseconds: self.milliseconds, + microseconds: self.microseconds, + nanoseconds: self.nanoseconds, } } diff --git a/src/builtins/core/duration/date.rs b/src/builtins/core/duration/date.rs index 26019788e..55991a1c1 100644 --- a/src/builtins/core/duration/date.rs +++ b/src/builtins/core/duration/date.rs @@ -16,7 +16,7 @@ use super::duration_sign; /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal-date-duration-records /// [field spec]: https://tc39.es/proposal-temporal/#sec-properties-of-temporal-duration-instances #[non_exhaustive] -#[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] pub struct DateDuration { pub sign: Sign, /// `DateDuration`'s internal year value. @@ -29,6 +29,18 @@ pub struct DateDuration { pub days: U40, } +impl Default for DateDuration { + fn default() -> Self { + Self { + sign: Sign::Zero, + years: 0, + months: 0, + weeks: 0, + days: U40::from(0u8), + } + } +} + impl DateDuration { /// Creates a new, non-validated `DateDuration`. #[inline] @@ -36,10 +48,10 @@ impl DateDuration { pub(crate) fn new_unchecked(years: i64, months: i64, weeks: i64, days: i64) -> Self { Self { sign: duration_sign(&[years, months, weeks, days]), - years: years.try_into().expect("years must fit in u32"), - months: months.try_into().expect("months must fit in u32"), - weeks: weeks.try_into().expect("weeks must fit in u32"), - days: days.try_into().expect("days must fit in u40"), + years: years.unsigned_abs().try_into().expect("years must fit in u32"), + months: months.unsigned_abs().try_into().expect("months must fit in u32"), + weeks: weeks.unsigned_abs().try_into().expect("weeks must fit in u32"), + days: days.unsigned_abs().try_into().expect("days must fit in u40"), } } @@ -137,7 +149,7 @@ impl DateDuration { #[inline] #[must_use] pub fn sign(&self) -> Sign { - duration_sign(self.fields().as_slice()) + self.sign } /// DateDurationDays diff --git a/src/iso.rs b/src/iso.rs index 5e4920363..bf6014d3c 100644 --- a/src/iso.rs +++ b/src/iso.rs @@ -728,18 +728,20 @@ impl IsoTime { let mis = i128::from(other.microsecond) - i128::from(self.microsecond); let ns = i128::from(other.nanosecond) - i128::from(self.nanosecond); + let sign = duration_sign(&[h, m, s, ms, mis as i64, ns as i64]); + Duration::new_unchecked( - duration_sign(&[h, m, s, ms, mis as i64, ns as i64]), + sign, 0, 0, 0, 0u8.into(), - h.try_into().expect("hour overflow"), - m.try_into().expect("minute overflow"), - s.try_into().expect("second overflow"), - ms.try_into().expect("millisecond overflow"), - mis.try_into().expect("microsecond overflow"), - ns.try_into().expect("nanosecond overflow"), + h.unsigned_abs().try_into().expect("hour overflow"), + m.unsigned_abs().try_into().expect("minute overflow"), + s.unsigned_abs().try_into().expect("second overflow"), + ms.unsigned_abs().try_into().expect("millisecond overflow"), + mis.unsigned_abs().try_into().expect("microsecond overflow"), + ns.unsigned_abs().try_into().expect("nanosecond overflow"), ) } From f22a1d1761115fc369780eb339e15e3ba7843aee Mon Sep 17 00:00:00 2001 From: Matthew Stone Date: Sun, 22 Jun 2025 12:06:31 -0700 Subject: [PATCH 13/26] Fix duration sign handling --- src/builtins/core/duration.rs | 67 +++++++++++++++++++---------------- src/iso.rs | 20 +++++++---- src/lib.rs | 7 ++-- 3 files changed, 55 insertions(+), 39 deletions(-) diff --git a/src/builtins/core/duration.rs b/src/builtins/core/duration.rs index afc78cb90..aa59bda8f 100644 --- a/src/builtins/core/duration.rs +++ b/src/builtins/core/duration.rs @@ -181,27 +181,26 @@ impl Duration { duration_record.normalized_time_duration(), largest_unit, )?; - Self::new( - duration_record.date().years.into(), - duration_record.date().months.into(), - duration_record.date().weeks.into(), + let sign = duration_record.sign()?; + let mut duration = Self::new( + i64::from(duration_record.date().years), + i64::from(duration_record.date().months), + i64::from(duration_record.date().weeks), i64::try_from(duration_record.date().days) .or(Err(TemporalError::range()))? .checked_add(overflow_day) .ok_or(TemporalError::range())?, - time.hours.try_into().or(Err(TemporalError::range()))?, - time.minutes.try_into().or(Err(TemporalError::range()))?, - time.seconds.try_into().or(Err(TemporalError::range()))?, - time.milliseconds - .try_into() - .or(Err(TemporalError::range()))?, - time.microseconds - .try_into() - .or(Err(TemporalError::range()))?, - time.nanoseconds - .try_into() - .or(Err(TemporalError::range()))?, - ) + i64::try_from(time.hours).or(Err(TemporalError::range()))?, + i64::try_from(time.minutes).or(Err(TemporalError::range()))?, + i64::try_from(time.seconds).or(Err(TemporalError::range()))?, + i64::try_from(time.milliseconds).or(Err(TemporalError::range()))?, + i128::try_from(time.microseconds).or(Err(TemporalError::range()))?, + i128::try_from(time.nanoseconds).or(Err(TemporalError::range()))?, + )?; + if sign == Sign::Negative { + duration = duration.negated(); + } + Ok(duration) } /// Returns this `Duration` as a `NormalizedTimeDuration`. @@ -1519,19 +1518,27 @@ impl FromStr for Duration { (0, 0, 0, 0) }; - let sign = parse_record.sign as i64; + let sign = if parse_record.sign == ixdtf::parsers::records::Sign::Negative { + Sign::Negative + } else { + Sign::Positive + }; - Self::new( - years as i64 * sign, - months as i64 * sign, - weeks as i64 * sign, - days as i64 * sign, - hours as i64 * sign, - minutes as i64 * sign, - seconds as i64 * sign, - millis as i64 * sign, - micros as i128 * sign as i128, - nanos as i128 * sign as i128, - ) + let mut duration = Self::new( + years as i64, + months as i64, + weeks as i64, + days as i64, + hours as i64, + minutes as i64, + seconds as i64, + millis as i64, + micros as i128, + nanos as i128, + )?; + if sign == Sign::Negative { + duration = duration.negated(); + } + Ok(duration) } } diff --git a/src/iso.rs b/src/iso.rs index bf6014d3c..21dbe875b 100644 --- a/src/iso.rs +++ b/src/iso.rs @@ -169,12 +169,14 @@ impl IsoDateTime { 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 sign = date_duration.sign.as_sign_multiplier(); let date_duration = DateDuration::new( - date_duration.years.into(), - date_duration.months.into(), - date_duration.weeks.into(), + i64::from(date_duration.years) * i64::from(sign), + i64::from(date_duration.months) * i64::from(sign), + i64::from(date_duration.weeks) * i64::from(sign), i64::try_from(date_duration.days) .or(Err(TemporalError::range()))? + * i64::from(sign) .checked_add(t_result.0) .ok_or(TemporalError::range())?, )?; @@ -399,9 +401,13 @@ impl IsoDate { // 1. Assert: year, month, day, years, months, weeks, and days are integers. // 2. Assert: overflow is either "constrain" or "reject". // 3. Let intermediate be ! BalanceISOYearMonth(year + years, month + months). + let year_offset = i64::from(duration.years) + * i64::from(duration.sign.as_sign_multiplier()); + let month_offset = i64::from(duration.months) + * i64::from(duration.sign.as_sign_multiplier()); let intermediate = balance_iso_year_month_with_clamp( - i64::from(self.year) + i64::from(duration.years), - i64::from(self.month) + i64::from(duration.months), + i64::from(self.year) + year_offset, + i64::from(self.month) + month_offset, ); // 4. Let intermediate be ? RegulateISODate(intermediate.[[Year]], intermediate.[[Month]], day, overflow). @@ -410,7 +416,9 @@ impl IsoDate { // 5. Set days to days + 7 × weeks. let additional_days = i64::try_from(duration.days).or(Err(TemporalError::range()))? - + (7 * i64::from(duration.weeks)); // Verify + * i64::from(duration.sign.as_sign_multiplier()) + + (7 * i64::from(duration.weeks) + * i64::from(duration.sign.as_sign_multiplier())); // 6. Let d be intermediate.[[Day]] + days. let intermediate_days = i64::from(intermediate.day) + additional_days; diff --git a/src/lib.rs b/src/lib.rs index 1bc372696..f2f6e21ed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -259,10 +259,11 @@ impl From for Sign { impl Sign { /// Coerces the current `Sign` to be either negative or positive. pub(crate) fn as_sign_multiplier(&self) -> i8 { - if matches!(self, Self::Zero) { - return 1; + match self { + Self::Positive => 1, + Self::Negative => -1, + Self::Zero => 0, } - *self as i8 } pub(crate) fn negate(&self) -> Sign { From 25ed765821b067599177c909fab0f6229716ae87 Mon Sep 17 00:00:00 2001 From: Matt Stone Date: Sun, 22 Jun 2025 12:06:45 -0700 Subject: [PATCH 14/26] Fix lint --- src/builtins/core/duration.rs | 22 ++-------------------- src/builtins/core/duration/date.rs | 17 +++++++++++++---- src/iso.rs | 12 ++++++------ 3 files changed, 21 insertions(+), 30 deletions(-) diff --git a/src/builtins/core/duration.rs b/src/builtins/core/duration.rs index afc78cb90..d4c126856 100644 --- a/src/builtins/core/duration.rs +++ b/src/builtins/core/duration.rs @@ -806,16 +806,7 @@ impl Duration { pub fn negated(&self) -> Self { Self { sign: self.sign.negate(), - years: self.years, - months: self.months, - weeks: self.weeks, - days: self.days, - hours: self.hours, - minutes: self.minutes, - seconds: self.seconds, - milliseconds: self.milliseconds, - microseconds: self.microseconds, - nanoseconds: self.nanoseconds, + ..*self } } @@ -829,16 +820,7 @@ impl Duration { } else { Sign::Positive }, - years: self.years, - months: self.months, - weeks: self.weeks, - days: self.days, - hours: self.hours, - minutes: self.minutes, - seconds: self.seconds, - milliseconds: self.milliseconds, - microseconds: self.microseconds, - nanoseconds: self.nanoseconds, + ..*self } } diff --git a/src/builtins/core/duration/date.rs b/src/builtins/core/duration/date.rs index 55991a1c1..d82b70caa 100644 --- a/src/builtins/core/duration/date.rs +++ b/src/builtins/core/duration/date.rs @@ -48,10 +48,19 @@ impl DateDuration { pub(crate) fn new_unchecked(years: i64, months: i64, weeks: i64, days: i64) -> Self { Self { sign: duration_sign(&[years, months, weeks, days]), - years: years.unsigned_abs().try_into().expect("years must fit in u32"), - months: months.unsigned_abs().try_into().expect("months must fit in u32"), - weeks: weeks.unsigned_abs().try_into().expect("weeks must fit in u32"), - days: days.unsigned_abs().try_into().expect("days must fit in u40"), + years: years + .unsigned_abs() + .try_into() + .expect("years must fit in u32"), + months: months + .unsigned_abs() + .try_into() + .expect("months must fit in u32"), + weeks: weeks + .unsigned_abs() + .try_into() + .expect("weeks must fit in u32"), + days: days.unsigned_abs().into(), } } diff --git a/src/iso.rs b/src/iso.rs index bf6014d3c..2a9a7fafd 100644 --- a/src/iso.rs +++ b/src/iso.rs @@ -736,12 +736,12 @@ impl IsoTime { 0, 0, 0u8.into(), - h.unsigned_abs().try_into().expect("hour overflow"), - m.unsigned_abs().try_into().expect("minute overflow"), - s.unsigned_abs().try_into().expect("second overflow"), - ms.unsigned_abs().try_into().expect("millisecond overflow"), - mis.unsigned_abs().try_into().expect("microsecond overflow"), - ns.unsigned_abs().try_into().expect("nanosecond overflow"), + h.unsigned_abs().into(), + m.unsigned_abs().into(), + s.unsigned_abs().into(), + ms.unsigned_abs(), + mis.unsigned_abs().into(), + ns.unsigned_abs().into(), ) } From 7123b039fab18f6ca84bf6453e617231f2640ff7 Mon Sep 17 00:00:00 2001 From: Matt Stone Date: Sun, 22 Jun 2025 12:08:53 -0700 Subject: [PATCH 15/26] Fix lint --- src/iso.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/iso.rs b/src/iso.rs index b3a6d2a43..7e2291a50 100644 --- a/src/iso.rs +++ b/src/iso.rs @@ -174,11 +174,10 @@ impl IsoDateTime { i64::from(date_duration.years) * i64::from(sign), i64::from(date_duration.months) * i64::from(sign), i64::from(date_duration.weeks) * i64::from(sign), - i64::try_from(date_duration.days) - .or(Err(TemporalError::range()))? + i64::try_from(date_duration.days).or(Err(TemporalError::range()))? * i64::from(sign) - .checked_add(t_result.0) - .ok_or(TemporalError::range())?, + .checked_add(t_result.0) + .ok_or(TemporalError::range())?, )?; let duration = Duration::from(date_duration); @@ -401,10 +400,9 @@ impl IsoDate { // 1. Assert: year, month, day, years, months, weeks, and days are integers. // 2. Assert: overflow is either "constrain" or "reject". // 3. Let intermediate be ! BalanceISOYearMonth(year + years, month + months). - let year_offset = i64::from(duration.years) - * i64::from(duration.sign.as_sign_multiplier()); - let month_offset = i64::from(duration.months) - * i64::from(duration.sign.as_sign_multiplier()); + let year_offset = i64::from(duration.years) * i64::from(duration.sign.as_sign_multiplier()); + let month_offset = + i64::from(duration.months) * i64::from(duration.sign.as_sign_multiplier()); let intermediate = balance_iso_year_month_with_clamp( i64::from(self.year) + year_offset, i64::from(self.month) + month_offset, @@ -417,8 +415,7 @@ impl IsoDate { // 5. Set days to days + 7 × weeks. let additional_days = i64::try_from(duration.days).or(Err(TemporalError::range()))? * i64::from(duration.sign.as_sign_multiplier()) - + (7 * i64::from(duration.weeks) - * i64::from(duration.sign.as_sign_multiplier())); + + (7 * i64::from(duration.weeks) * i64::from(duration.sign.as_sign_multiplier())); // 6. Let d be intermediate.[[Day]] + days. let intermediate_days = i64::from(intermediate.day) + additional_days; From 79cdb11f1255ccf2db505afb6a099f9c0b2862f9 Mon Sep 17 00:00:00 2001 From: Matt Stone Date: Sun, 22 Jun 2025 13:33:35 -0700 Subject: [PATCH 16/26] Fix duration calc --- src/builtins/core/duration/normalized.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/builtins/core/duration/normalized.rs b/src/builtins/core/duration/normalized.rs index ab5be6ae5..3085e3873 100644 --- a/src/builtins/core/duration/normalized.rs +++ b/src/builtins/core/duration/normalized.rs @@ -53,7 +53,7 @@ impl NormalizedTimeDuration { nanoseconds += i128::try_from(duration.nanoseconds).expect("nanosecond overflow"); // NOTE(nekevss): Is it worth returning a `RangeError` below. debug_assert!(nanoseconds.abs() <= MAX_TIME_DURATION); - Self(nanoseconds) + Self(nanoseconds * duration.sign().as_sign_multiplier() as i128) } /// Equivalent to 7.5.27 NormalizedTimeDurationFromEpochNanosecondsDifference ( one, two ) From 9fd53fe83e644cdaa4526b567812682198cb0eb2 Mon Sep 17 00:00:00 2001 From: Matt Stone Date: Sun, 22 Jun 2025 19:14:13 -0700 Subject: [PATCH 17/26] Respond to comments --- src/builtins/core/duration.rs | 11 ++--------- src/builtins/core/duration/normalized.rs | 24 ++++++++++++++++-------- src/builtins/core/time.rs | 2 +- src/iso.rs | 10 +++++----- src/primitive.rs | 7 +++++++ 5 files changed, 31 insertions(+), 23 deletions(-) diff --git a/src/builtins/core/duration.rs b/src/builtins/core/duration.rs index 9601e6192..12388932e 100644 --- a/src/builtins/core/duration.rs +++ b/src/builtins/core/duration.rs @@ -9,13 +9,12 @@ use crate::{ RoundingOptions, ToStringRoundingOptions, Unit, }, parsers::{FormattableDateDuration, FormattableDuration, FormattableTimeDuration, Precision}, - primitive::FiniteF64, + primitive::{FiniteF64, U40, U48, U56, U80, U88}, provider::TimeZoneProvider, temporal_assert, Sign, TemporalError, TemporalResult, NS_PER_DAY, }; use alloc::format; use alloc::string::String; -use bnum::{BUintD16, BUintD8}; use core::{cmp::Ordering, str::FromStr}; pub use date::DateDuration; use ixdtf::parsers::{records::TimeDurationRecord, IsoDurationParser}; @@ -63,12 +62,6 @@ impl PartialDuration { } } -type U80 = BUintD16<5>; // 80 / 16 = 5 -type U88 = BUintD8<11>; // 88 / 8 = 11 -type U56 = BUintD8<7>; // 56 / 8 = 7 -type U48 = BUintD8<6>; // 48 / 8 = 6 -type U40 = BUintD8<5>; // 40 / 8 = 5 - /// The native Rust implementation of `Temporal.Duration`. /// /// primarily defined by Abtract Operation 7.5.1-5. @@ -131,7 +124,7 @@ impl core::fmt::Display for Duration { impl Duration { pub(crate) fn hour(value: i64) -> Self { Self { - sign: Sign::from(value), + sign: Sign::from(value.signum()), hours: U48::try_from(value.abs()).expect("Hours must be within range."), ..Default::default() } diff --git a/src/builtins/core/duration/normalized.rs b/src/builtins/core/duration/normalized.rs index 3085e3873..a5f984373 100644 --- a/src/builtins/core/duration/normalized.rs +++ b/src/builtins/core/duration/normalized.rs @@ -43,17 +43,25 @@ impl NormalizedTimeDuration { /// Equivalent: 7.5.20 NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds, nanoseconds ) 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 = - i128::try_from(duration.hours).expect("hour overflow") * NANOSECONDS_PER_HOUR; + let sign_multiplier = duration.sign().as_sign_multiplier() as i128; + let mut nanoseconds: i128 = i128::try_from(duration.hours).expect("hour overflow") + * NANOSECONDS_PER_HOUR + * sign_multiplier; + nanoseconds += i128::try_from(duration.minutes).expect("minute overflow") + * NANOSECONDS_PER_MINUTE + * sign_multiplier; + nanoseconds += i128::try_from(duration.seconds).expect("second overflow") + * 1_000_000_000 + * sign_multiplier; + nanoseconds += i128::from(duration.milliseconds) * 1_000_000 * sign_multiplier; + nanoseconds += i128::try_from(duration.microseconds).expect("microsecond overflow") + * 1_000 + * sign_multiplier; nanoseconds += - i128::try_from(duration.minutes).expect("minute overflow") * NANOSECONDS_PER_MINUTE; - nanoseconds += i128::try_from(duration.seconds).expect("second overflow") * 1_000_000_000; - nanoseconds += i128::from(duration.milliseconds) * 1_000_000; - nanoseconds += i128::try_from(duration.microseconds).expect("microsecond overflow") * 1_000; - nanoseconds += i128::try_from(duration.nanoseconds).expect("nanosecond overflow"); + i128::try_from(duration.nanoseconds).expect("nanosecond overflow") * sign_multiplier; // NOTE(nekevss): Is it worth returning a `RangeError` below. debug_assert!(nanoseconds.abs() <= MAX_TIME_DURATION); - Self(nanoseconds * duration.sign().as_sign_multiplier() as i128) + Self(nanoseconds) } /// Equivalent to 7.5.27 NormalizedTimeDurationFromEpochNanosecondsDifference ( one, two ) diff --git a/src/builtins/core/time.rs b/src/builtins/core/time.rs index f3bebb15f..95f184cee 100644 --- a/src/builtins/core/time.rs +++ b/src/builtins/core/time.rs @@ -195,7 +195,7 @@ impl PlainTime { // 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(); + let mut normalized_time = self.iso.diff(&other.iso); // 6. If settings.[[SmallestUnit]] is not "nanosecond" or settings.[[RoundingIncrement]] ≠ 1, then if resolved.smallest_unit != Unit::Nanosecond diff --git a/src/iso.rs b/src/iso.rs index 7e2291a50..6a950ed14 100644 --- a/src/iso.rs +++ b/src/iso.rs @@ -215,7 +215,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_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; @@ -724,8 +724,8 @@ impl IsoTime { (days, time) } - /// Difference this `IsoTime` against another and returning a `Duration`. - pub(crate) fn diff(&self, other: &Self) -> Duration { + /// Difference this `IsoTime` against another and returning a `NormalizedTimeDuration`. + 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); @@ -735,7 +735,7 @@ impl IsoTime { let sign = duration_sign(&[h, m, s, ms, mis as i64, ns as i64]); - Duration::new_unchecked( + NormalizedTimeDuration::from_duration(&Duration::new_unchecked( sign, 0, 0, @@ -747,7 +747,7 @@ impl IsoTime { ms.unsigned_abs(), mis.unsigned_abs().into(), ns.unsigned_abs().into(), - ) + )) } // NOTE (nekevss): Specification seemed to be off / not entirely working, so the below was adapted from the diff --git a/src/primitive.rs b/src/primitive.rs index 89d6cc897..e0153e12a 100644 --- a/src/primitive.rs +++ b/src/primitive.rs @@ -3,6 +3,7 @@ use core::cmp::Ordering; use crate::{error::ErrorMessage, TemporalError, TemporalResult}; +use bnum::{BUintD16, BUintD8}; use num_traits::float::FloatCore; use num_traits::{AsPrimitive, FromPrimitive, PrimInt}; @@ -235,6 +236,12 @@ impl Ord for FiniteF64 { } } +pub type U80 = BUintD16<5>; // 80 / 16 = 5 +pub type U88 = BUintD8<11>; // 88 / 8 = 11 +pub type U56 = BUintD8<7>; // 56 / 8 = 7 +pub type U48 = BUintD8<6>; // 48 / 8 = 6 +pub type U40 = BUintD8<5>; // 40 / 8 = 5 + #[cfg(test)] mod tests { use super::FiniteF64; From c0eecb068dd34f78c51851b8de239d5b9909b7ae Mon Sep 17 00:00:00 2001 From: Matt Stone Date: Fri, 4 Jul 2025 16:08:11 -0700 Subject: [PATCH 18/26] Post-merge fixes --- src/builtins/core/date.rs | 7 ++----- src/builtins/core/datetime.rs | 4 ++-- src/builtins/core/duration/normalized.rs | 6 +++--- src/builtins/core/timezone.rs | 3 +-- src/iso.rs | 7 ++----- 5 files changed, 10 insertions(+), 17 deletions(-) diff --git a/src/builtins/core/date.rs b/src/builtins/core/date.rs index fe5e6ba50..73cc027f1 100644 --- a/src/builtins/core/date.rs +++ b/src/builtins/core/date.rs @@ -1,10 +1,7 @@ //! This module implements `Date` and any directly related algorithms. use crate::{ - builtins::core::{ - calendar::Calendar, duration::DateDuration, Duration, PlainDateTime, PlainTime, - ZonedDateTime, - }, + builtins::core::{calendar::Calendar, Duration, PlainDateTime, PlainTime, ZonedDateTime}, iso::{IsoDate, IsoDateTime, IsoTime}, options::{ ArithmeticOverflow, DifferenceOperation, DifferenceSettings, Disambiguation, @@ -12,7 +9,7 @@ use crate::{ }, parsers::{parse_date_time, IxdtfStringBuilder}, provider::{NeverProvider, TimeZoneProvider}, - MonthCode, TemporalError, TemporalResult, TemporalUnwrap, TimeZone, + DateDuration, MonthCode, TemporalError, TemporalResult, TemporalUnwrap, TimeZone, }; use alloc::{format, string::String}; use core::{cmp::Ordering, str::FromStr}; diff --git a/src/builtins/core/datetime.rs b/src/builtins/core/datetime.rs index a73097b6c..8a8c3bc09 100644 --- a/src/builtins/core/datetime.rs +++ b/src/builtins/core/datetime.rs @@ -964,8 +964,8 @@ mod tests { use crate::{ builtins::core::{ - calendar::Calendar, duration::DateDuration, Duration, PartialDate, PartialDateTime, - PartialTime, PlainDateTime, + calendar::Calendar, DateDuration, Duration, PartialDate, PartialDateTime, PartialTime, + PlainDateTime, }, iso::{IsoDate, IsoDateTime, IsoTime}, options::{ diff --git a/src/builtins/core/duration/normalized.rs b/src/builtins/core/duration/normalized.rs index cacf7eaef..53a41d2af 100644 --- a/src/builtins/core/duration/normalized.rs +++ b/src/builtins/core/duration/normalized.rs @@ -294,9 +294,9 @@ impl NormalizedDurationRecord { // 3. Return ! CreateDateDurationRecord(internalDuration.[[Date]].[[Years]], internalDuration.[[Date]].[[Months]], internalDuration.[[Date]].[[Weeks]], days). Ok(DateDuration::new_unchecked( - internal_duration.date().years, - internal_duration.date().months, - internal_duration.date().weeks, + internal_duration.date().years.into(), + internal_duration.date().months.into(), + internal_duration.date().weeks.into(), days.try_into().ok().temporal_unwrap()?, )) } diff --git a/src/builtins/core/timezone.rs b/src/builtins/core/timezone.rs index 9c704b43c..c65e56a55 100644 --- a/src/builtins/core/timezone.rs +++ b/src/builtins/core/timezone.rs @@ -11,7 +11,6 @@ use ixdtf::{ }; use num_traits::ToPrimitive; -use crate::builtins::core::duration::DateDuration; use crate::parsers::{ parse_allowed_timezone_formats, parse_identifier, FormattableOffset, FormattableTime, Precision, }; @@ -23,7 +22,7 @@ use crate::{ unix_time::EpochNanoseconds, TemporalError, TemporalResult, ZonedDateTime, }; -use crate::{Calendar, Sign}; +use crate::{Calendar, DateDuration, Sign}; const NS_IN_HOUR: i128 = 60 * 60 * 1000 * 1000 * 1000; diff --git a/src/iso.rs b/src/iso.rs index c449df8d3..2dc6633b3 100644 --- a/src/iso.rs +++ b/src/iso.rs @@ -29,10 +29,7 @@ use crate::{ builtins::{ core::{ calendar::Calendar, - duration::{ - normalized::{NormalizedDurationRecord, NormalizedTimeDuration}, - DateDuration, - }, + duration::normalized::{NormalizedDurationRecord, NormalizedTimeDuration}, Duration, PartialTime, PlainDate, }, duration::duration_sign, @@ -42,7 +39,7 @@ use crate::{ rounding::{IncrementRounder, Round}, temporal_assert, unix_time::EpochNanoseconds, - utils, TemporalResult, TemporalUnwrap, NS_PER_DAY, + utils, DateDuration, TemporalResult, TemporalUnwrap, NS_PER_DAY, }; use icu_calendar::{Date as IcuDate, Iso}; use num_traits::{cast::FromPrimitive, Euclid}; From 74442e48072af8827e7d488a7bb9f84ca6a5ff43 Mon Sep 17 00:00:00 2001 From: Matt Stone Date: Fri, 4 Jul 2025 17:24:50 -0700 Subject: [PATCH 19/26] Fix sign on NormalizedDurationRecord --- src/builtins/core/duration/normalized.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/builtins/core/duration/normalized.rs b/src/builtins/core/duration/normalized.rs index 53a41d2af..82ba76b86 100644 --- a/src/builtins/core/duration/normalized.rs +++ b/src/builtins/core/duration/normalized.rs @@ -314,7 +314,10 @@ impl NormalizedDurationRecord { } pub(crate) fn sign(&self) -> TemporalResult { - Ok(self.date.sign()) + if self.date.sign() != Sign::Zero { + return Ok(self.date.sign()); + } + Ok(self.norm.sign()) } } From 12abb62e5c1b9a1ecaab3122dbe0592ca384657d Mon Sep 17 00:00:00 2001 From: Matt Stone Date: Fri, 4 Jul 2025 19:01:57 -0700 Subject: [PATCH 20/26] Fix nudge_to_day_or_time --- src/builtins/core/duration/normalized.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/builtins/core/duration/normalized.rs b/src/builtins/core/duration/normalized.rs index 82ba76b86..065beb6d5 100644 --- a/src/builtins/core/duration/normalized.rs +++ b/src/builtins/core/duration/normalized.rs @@ -742,9 +742,9 @@ impl NormalizedDurationRecord { ) -> 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_())?; + let norm = self.normalized_time_duration().add_days( + self.date().days.as_::() * i64::from(self.date.sign().as_sign_multiplier()), + )?; // 3. Let unitLength be the value in the "Length in Nanoseconds" column of the row of Table 22 whose "Singular" column contains smallestUnit. let unit_length = options.smallest_unit.as_nanoseconds().temporal_unwrap()?; From 06e9f9d5dad0380add1028176b27183926d4ebee Mon Sep 17 00:00:00 2001 From: --global <--global> Date: Thu, 31 Jul 2025 12:57:18 -0700 Subject: [PATCH 21/26] Add from_components method to NormalizedTimeDuration for creating instances from signed integer components --- src/builtins/core/duration/normalized.rs | 23 +++++++++++++++++++++++ src/iso.rs | 17 +---------------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/builtins/core/duration/normalized.rs b/src/builtins/core/duration/normalized.rs index 065beb6d5..52a320eb4 100644 --- a/src/builtins/core/duration/normalized.rs +++ b/src/builtins/core/duration/normalized.rs @@ -40,6 +40,29 @@ 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_duration(duration: &Duration) -> Self { // Note: Calculations must be done after casting to `i128` in order to preserve precision diff --git a/src/iso.rs b/src/iso.rs index 2dc6633b3..10c042d72 100644 --- a/src/iso.rs +++ b/src/iso.rs @@ -32,7 +32,6 @@ use crate::{ duration::normalized::{NormalizedDurationRecord, NormalizedTimeDuration}, Duration, PartialTime, PlainDate, }, - duration::duration_sign, }, error::TemporalError, options::{ArithmeticOverflow, ResolvedRoundingOptions, Unit}, @@ -730,21 +729,7 @@ impl IsoTime { let mis = i128::from(other.microsecond) - i128::from(self.microsecond); let ns = i128::from(other.nanosecond) - i128::from(self.nanosecond); - let sign = duration_sign(&[h, m, s, ms, mis as i64, ns as i64]); - - NormalizedTimeDuration::from_duration(&Duration::new_unchecked( - sign, - 0, - 0, - 0, - 0u8.into(), - h.unsigned_abs().into(), - m.unsigned_abs().into(), - s.unsigned_abs().into(), - ms.unsigned_abs(), - mis.unsigned_abs().into(), - ns.unsigned_abs().into(), - )) + 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 From ee85fa0904c1f384742e43c7e3f9efd62c395ae6 Mon Sep 17 00:00:00 2001 From: --global <--global> Date: Thu, 31 Jul 2025 13:26:17 -0700 Subject: [PATCH 22/26] Refactor U48 type definition to use BUintD16 Fix two unit tests try_from to use cast_from --- src/builtins/core/instant.rs | 27 +++++++++++++++------------ src/primitive.rs | 2 +- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/builtins/core/instant.rs b/src/builtins/core/instant.rs index 0ece73da1..bbaa6ee84 100644 --- a/src/builtins/core/instant.rs +++ b/src/builtins/core/instant.rs @@ -446,9 +446,12 @@ mod tests { use core::str::FromStr; + use bnum::cast::CastFrom; + use crate::{ builtins::{core::Instant, duration::duration_sign}, options::{DifferenceSettings, RoundingMode, Unit}, + primitive::{U48, U56, U80, U88}, unix_time::EpochNanoseconds, Duration, NS_MAX_INSTANT, NS_MIN_INSTANT, }; @@ -567,12 +570,12 @@ mod tests { 0, 0, 0u8.into(), - expected.0.try_into().unwrap(), - expected.1.try_into().unwrap(), - expected.2.try_into().unwrap(), - expected.3.try_into().unwrap(), - expected.4.try_into().unwrap(), - expected.5.try_into().unwrap(), + U48::cast_from(expected.0.abs()), + U48::cast_from(expected.1.abs()), + U56::cast_from(expected.2.abs()), + u64::try_from(expected.3.abs()).unwrap(), + U80::cast_from(expected.4.abs()), + U88::cast_from(expected.5.abs()), ) ); }; @@ -652,12 +655,12 @@ mod tests { 0, 0, 0u8.into(), - expected.0.try_into().unwrap(), - expected.1.try_into().unwrap(), - expected.2.try_into().unwrap(), - expected.3.try_into().unwrap(), - expected.4.try_into().unwrap(), - expected.5.try_into().unwrap(), + U48::cast_from(expected.0.abs()), + U48::cast_from(expected.1.abs()), + U56::cast_from(expected.2.abs()), + u64::try_from(expected.3.abs()).unwrap(), + U80::cast_from(expected.4.abs()), + U88::cast_from(expected.5.abs()), ) ); }; diff --git a/src/primitive.rs b/src/primitive.rs index d154adff6..2a03acd83 100644 --- a/src/primitive.rs +++ b/src/primitive.rs @@ -239,7 +239,7 @@ impl Ord for FiniteF64 { pub type U80 = BUintD16<5>; // 80 / 16 = 5 pub type U88 = BUintD8<11>; // 88 / 8 = 11 pub type U56 = BUintD8<7>; // 56 / 8 = 7 -pub type U48 = BUintD8<6>; // 48 / 8 = 6 +pub type U48 = BUintD16<3>; // 48 / 16 = 3 pub type U40 = BUintD8<5>; // 40 / 8 = 5 #[cfg(test)] From ef1f02948ad1bdc4c3f0faa13f4edd4b0f62234a Mon Sep 17 00:00:00 2001 From: --global <--global> Date: Thu, 31 Jul 2025 13:33:23 -0700 Subject: [PATCH 23/26] Refactor Duration constructor to use absolute values for time components --- src/builtins/core/duration.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/builtins/core/duration.rs b/src/builtins/core/duration.rs index dc45a41cb..c46da57f6 100644 --- a/src/builtins/core/duration.rs +++ b/src/builtins/core/duration.rs @@ -15,6 +15,7 @@ use crate::{ }; use alloc::format; use alloc::string::String; +use bnum::cast::As; use core::{cmp::Ordering, str::FromStr}; use ixdtf::{encoding::Utf8, parsers::IsoDurationParser, records::TimeDurationRecord}; use normalized::NormalizedDurationRecord; @@ -617,16 +618,16 @@ impl Duration { microseconds as i64, nanoseconds as i64, ]), - years: u32::try_from(years).or(Err(TemporalError::range()))?, - months: u32::try_from(months).or(Err(TemporalError::range()))?, - weeks: u32::try_from(weeks).or(Err(TemporalError::range()))?, - days: U40::try_from(days).or(Err(TemporalError::range()))?, - hours: U48::try_from(hours).or(Err(TemporalError::range()))?, - minutes: U48::try_from(minutes).or(Err(TemporalError::range()))?, - seconds: U56::try_from(seconds).or(Err(TemporalError::range()))?, - milliseconds: u64::try_from(milliseconds).or(Err(TemporalError::range()))?, - microseconds: U80::try_from(microseconds).or(Err(TemporalError::range()))?, - nanoseconds: U88::try_from(nanoseconds).or(Err(TemporalError::range()))?, + years: u32::try_from(years.abs()).or(Err(TemporalError::range()))?, + months: u32::try_from(months.abs()).or(Err(TemporalError::range()))?, + weeks: u32::try_from(weeks.abs()).or(Err(TemporalError::range()))?, + days: days.abs().as_(), + hours: hours.abs().as_(), + minutes: minutes.abs().as_(), + seconds: seconds.abs().as_(), + milliseconds: u64::try_from(milliseconds.abs()).or(Err(TemporalError::range()))?, + microseconds: microseconds.abs().as_(), + nanoseconds: nanoseconds.abs().as_(), }) } From 0e72c84296b13e9d6a1f0830ef17ad94b2102f00 Mon Sep 17 00:00:00 2001 From: Kevin Ness Date: Sun, 3 Aug 2025 14:36:26 -0500 Subject: [PATCH 24/26] Fix broken tests + update various abstract operations Many broken tests were related to DateDuration needing to be an intermediate computational version. For instance, days can exceed the normal threshold in some calculations. There was also a number of operations that needed to be updated the the current specification in order to fix the bugs that came from this current update. Amongst them were nudge_to_calendar_unit, nudge_to_day_or_time, various InternalDuration record operations. It's worth noting that this current update preserves the old "normalized" specification language. Those should probably be updated in follow ups to the newer language for specification consistency, but that seemed to be out of scope for the current PR. --- src/builtins/core/date.rs | 2 +- src/builtins/core/datetime.rs | 73 ++++--- src/builtins/core/duration.rs | 252 ++++++++--------------- src/builtins/core/duration/date.rs | 48 ++--- src/builtins/core/duration/normalized.rs | 134 +++++------- src/builtins/core/instant.rs | 4 +- src/builtins/core/time.rs | 32 +-- src/builtins/core/year_month.rs | 6 +- src/builtins/core/zoneddatetime.rs | 4 +- src/iso.rs | 51 +---- 10 files changed, 210 insertions(+), 396 deletions(-) diff --git a/src/builtins/core/date.rs b/src/builtins/core/date.rs index 2a2740eb3..f42449a47 100644 --- a/src/builtins/core/date.rs +++ b/src/builtins/core/date.rs @@ -373,7 +373,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), diff --git a/src/builtins/core/datetime.rs b/src/builtins/core/datetime.rs index ec726c1d1..6600b3b8d 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, PartialDate, PartialTime, PlainDate, PlainTime, ZonedDateTime, + duration::normalized::NormalizedDurationRecord, Duration, PartialDate, PartialTime, PlainDate, + PlainTime, ZonedDateTime, }; use crate::{ builtins::core::{calendar::Calendar, Instant}, @@ -15,7 +15,7 @@ use crate::{ parsers::{parse_date_time, IxdtfStringBuilder}, primitive::FiniteF64, provider::{NeverProvider, TimeZoneProvider}, - temporal_assert, MonthCode, TemporalError, TemporalResult, TemporalUnwrap, TimeZone, + MonthCode, TemporalError, TemporalResult, TemporalUnwrap, TimeZone, }; use alloc::string::String; use core::{cmp::Ordering, str::FromStr}; @@ -209,36 +209,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_duration(duration); - - // 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, + // 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), )?; - - // 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())) + // 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. @@ -271,7 +261,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 { @@ -1535,4 +1525,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 59e3e1a89..1ec9c8d57 100644 --- a/src/builtins/core/duration.rs +++ b/src/builtins/core/duration.rs @@ -314,54 +314,10 @@ impl Duration { } #[inline] - pub(crate) fn from_normalized( + pub(crate) fn from_internal( duration_record: NormalizedDurationRecord, largest_unit: Unit, ) -> TemporalResult { - let (overflow_day, time) = Duration::from_normalized_time( - duration_record.normalized_time_duration(), - largest_unit, - )?; - let sign = duration_record.sign(); - let mut duration = Self::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())?, - i64::try_from(time.hours).or(Err(TemporalError::range()))?, - i64::try_from(time.minutes).or(Err(TemporalError::range()))?, - i64::try_from(time.seconds).or(Err(TemporalError::range()))?, - i64::try_from(time.milliseconds).or(Err(TemporalError::range()))?, - i128::try_from(time.microseconds).or(Err(TemporalError::range()))?, - i128::try_from(time.nanoseconds).or(Err(TemporalError::range()))?, - )?; - if sign == Sign::Negative { - duration = duration.negated(); - } - Ok(duration) - } - - /// Returns this `Duration` as a `NormalizedTimeDuration`. - #[inline] - pub(crate) fn to_normalized(self) -> NormalizedTimeDuration { - NormalizedTimeDuration::from_duration(&self) - } - - /// Balances and creates `Duration` from a `NormalizedTimeDuration`. This method will return - /// a tuple (f64, Duration) where f64 is the overflow day value from balancing. - /// - /// Equivalent: `BalanceTimeDuration` - /// - /// # Errors: - /// - Will error if provided duration is invalid - pub(crate) fn from_normalized_time( - norm: NormalizedTimeDuration, - largest_unit: Unit, - ) -> TemporalResult<(i64, Duration)> { // 1. Let days, hours, minutes, seconds, milliseconds, and microseconds be 0. let mut days = 0; let mut hours = 0; @@ -370,11 +326,14 @@ impl Duration { let mut milliseconds = 0; let mut microseconds = 0; - // 2. Let sign be NormalizedTimeDurationSign(norm). - let sign = norm.sign().as_sign_multiplier(); - // 3. Let nanoseconds be NormalizedTimeDurationAbs(norm).[[TotalNanoseconds]]. - let mut nanoseconds = norm.0.abs(); + // 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 => { @@ -476,54 +435,31 @@ impl Duration { // 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 + 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, + ) + } - // 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())? * i64::from(sign); - - if !is_valid_duration( - 0, - 0, - 0, - days, - hours as i64 * i64::from(sign), - minutes as i64 * i64::from(sign), - seconds as i64 * i64::from(sign), - milliseconds as i64 * i64::from(sign), - microseconds * i128::from(sign), - nanoseconds * i128::from(sign), - ) { - return Err(TemporalError::range().with_message("Invalid balance Duration.")); - } - - // TODO: Remove cast below. - Ok(( - days, - Duration { - sign: sign.into(), - days: days.try_into().expect("days must fit into u40"), - hours: hours.try_into().expect("hours must fit into u48"), - minutes: minutes.try_into().expect("minutes must fit into u48"), - seconds: seconds.try_into().expect("seconds must fit into u56"), - milliseconds: milliseconds - .try_into() - .expect("milliseconds must fit into u64"), - microseconds: microseconds - .try_into() - .expect("microseconds must fit into u80"), - nanoseconds: nanoseconds - .try_into() - .expect("nanoseconds must fit into u88"), - ..Default::default() - }, - )) + /// 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. @@ -558,13 +494,8 @@ impl Duration { // 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.sign(), - self.years(), - self.months(), - self.weeks(), - self.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(), @@ -1017,42 +948,30 @@ impl Duration { 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_duration(self); - // 27. Let norm2 be NormalizeTimeDuration(h2, min2, s2, ms2, mus2, ns2). - let norm_two = NormalizedTimeDuration::from_duration(other); - - // 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) = Duration::from_normalized_time(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` @@ -1201,7 +1120,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 @@ -1246,7 +1165,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 => {} } @@ -1294,7 +1213,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` @@ -1401,23 +1320,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_duration(self), - )?; - // 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()) } } @@ -1443,21 +1363,14 @@ pub fn duration_to_formattable( let hours = duration.hours().abs(); let minutes = duration.minutes().abs(); - // let time = NormalizedTimeDuration::from_duration(&Duration::new_unchecked( - // 0, - // 0, - // duration.seconds(), - // duration.milliseconds(), - // duration.microseconds(), - // duration.nanoseconds(), - // )); - let time = NormalizedTimeDuration::from_duration(&Duration { - seconds: duration.seconds, - milliseconds: duration.milliseconds, - microseconds: duration.microseconds, - nanoseconds: duration.nanoseconds, - ..Default::default() - }); + 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(); @@ -1604,12 +1517,13 @@ impl From for Duration { fn from(value: DateDuration) -> Self { // TODO: this needs to be tested or handled in a different way Self { - sign: value.sign, + 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 + .abs() .try_into() .expect("DateDuration days must be in a valid range"), ..Default::default() diff --git a/src/builtins/core/duration/date.rs b/src/builtins/core/duration/date.rs index b58bb1850..d91422a69 100644 --- a/src/builtins/core/duration/date.rs +++ b/src/builtins/core/duration/date.rs @@ -14,9 +14,8 @@ use super::duration_sign; /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal-date-duration-records /// [field spec]: https://tc39.es/proposal-temporal/#sec-properties-of-temporal-duration-instances #[non_exhaustive] -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] +#[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)] pub struct DateDuration { - pub sign: Sign, /// `DateDuration`'s internal year value. pub years: i64, /// `DateDuration`'s internal month value. @@ -27,31 +26,12 @@ pub struct DateDuration { pub days: i64, } -impl Default for DateDuration { - fn default() -> Self { - Self { - sign: Sign::Zero, - years: 0, - months: 0, - weeks: 0, - days: 0, - } - } -} - impl DateDuration { /// Creates a new, non-validated `DateDuration`. #[inline] #[must_use] - pub(crate) fn new_unchecked( - sign: Sign, - years: i64, - months: i64, - weeks: i64, - days: i64, - ) -> Self { + pub(crate) fn new_unchecked(years: i64, months: i64, weeks: i64, days: i64) -> Self { Self { - sign, years, months, weeks, @@ -68,7 +48,6 @@ impl From for DateDuration { #[inline] fn from(duration: Duration) -> Self { Self::new_unchecked( - duration.sign(), duration.years(), duration.months(), duration.weeks(), @@ -85,7 +64,6 @@ impl From<&Duration> for DateDuration { #[inline] fn from(duration: &Duration) -> Self { Self::new_unchecked( - duration.sign(), duration.years(), duration.months(), duration.weeks(), @@ -109,9 +87,8 @@ impl DateDuration { return Err(TemporalError::range().with_message("Invalid DateDuration.")); } - let sign = duration_sign(&[years, months, weeks, days]); // 2. Return Date Duration Record { [[Years]]: ℝ(𝔽(years)), [[Months]]: ℝ(𝔽(months)), [[Weeks]]: ℝ(𝔽(weeks)), [[Days]]: ℝ(𝔽(days)) }. - Ok(Self::new_unchecked(sign, years, months, weeks, days)) + Ok(Self::new_unchecked(years, months, weeks, days)) } /// Returns a negated `DateDuration`. @@ -119,8 +96,10 @@ impl DateDuration { #[must_use] pub fn negated(&self) -> Self { Self { - sign: self.sign.negate(), - ..*self + years: -self.years, + months: -self.months, + weeks: -self.weeks, + days: -self.days, } } @@ -129,12 +108,10 @@ impl DateDuration { #[must_use] pub fn abs(&self) -> Self { Self { - sign: if self.sign == Sign::Zero { - Sign::Zero - } else { - Sign::Positive - }, - ..*self + years: self.years.abs(), + months: self.months.abs(), + weeks: self.weeks.abs(), + days: self.days.abs(), } } @@ -142,7 +119,7 @@ impl DateDuration { #[inline] #[must_use] pub fn sign(&self) -> Sign { - self.sign + duration_sign(&[self.years, self.months, self.weeks, self.days]) } /// DateDurationDays @@ -196,7 +173,6 @@ impl DateDuration { // 3. Return ? CreateDateDurationRecord(dateDuration.[[Years]], months, weeks, days). Ok(Self { - sign: self.sign, years: self.years, months, weeks, diff --git a/src/builtins/core/duration/normalized.rs b/src/builtins/core/duration/normalized.rs index e36f310a2..a5052394c 100644 --- a/src/builtins/core/duration/normalized.rs +++ b/src/builtins/core/duration/normalized.rs @@ -115,12 +115,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] @@ -304,13 +298,8 @@ impl NormalizedDurationRecord { // 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). - let date = DateDuration::new_unchecked( - duration.sign(), - duration.years(), - duration.months(), - duration.weeks(), - 0, - ); + let date = + DateDuration::new_unchecked(duration.years(), duration.months(), duration.weeks(), 0); // 4. Return CombineDateAndTimeDuration(dateDuration, timeDuration). Self::new(date, normalized_time) } @@ -573,27 +562,18 @@ 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, - )?; + // 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); // NOTE: 7-8 are inversed // 8. Else, @@ -779,19 +759,12 @@ 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_::() * i64::from(self.date.sign().as_sign_multiplier()), - )?; - - // 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 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( unsafe { NonZeroU128::new_unchecked(unit_length.into()) .checked_mul(options.increment.as_extended_increment()) @@ -800,51 +773,48 @@ impl NormalizedDurationRecord { options.rounding_mode, )?; - // 6. Let diffNorm be ! SubtractNormalizedTimeDuration(roundedNorm, norm). - let diff_norm = rounded_norm.checked_sub(&norm)?; - - // 7. Let wholeDays be truncate(DivideNormalizedTimeDuration(norm, nsPerDay)). - let whole_days = norm.divide(NS_PER_DAY as i64); + // 4. Let diffTime be ! AddTimeDuration(roundedTime, -timeDuration). + let diff_time = rounded_time.checked_sub(&time_duration)?; - // 8. Let roundedFractionalDays be DivideNormalizedTimeDuration(roundedNorm, nsPerDay). - let (rounded_whole_days, rounded_remainder) = rounded_norm.div_rem(NS_PER_DAY); + // 5. Let wholeDays be truncate(TotalTimeDuration(timeDuration, day)). + let whole_days = time_duration.divide(NS_PER_DAY as i64) as i64; - // 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; - - // 14. Let days be 0. + // 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; + // 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/instant.rs b/src/builtins/core/instant.rs index 90b0fbacb..e63f4928e 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); /// @@ -226,7 +226,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]]). diff --git a/src/builtins/core/time.rs b/src/builtins/core/time.rs index 9cc567e9b..302fa4a36 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, + builtins::{core::Duration, duration::normalized::NormalizedDurationRecord}, error::ErrorMessage, iso::IsoTime, options::{ @@ -9,7 +9,7 @@ use crate::{ RoundingIncrement, RoundingMode, ToStringRoundingOptions, Unit, UnitGroup, }, parsers::{parse_time, IxdtfStringBuilder}, - TemporalError, TemporalResult, + DateDuration, TemporalError, TemporalResult, }; use alloc::string::String; use core::str::FromStr; @@ -327,26 +327,16 @@ 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]]). + // 4. Let timeDuration be DifferenceTime(temporalTime.[[Time]], other.[[Time]]). let mut normalized_time = self.iso.diff(&other.iso); - - // 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 = Duration::from_normalized_time(normalized_time, resolved.largest_unit)?.1; - - // 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]]). + // 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. match op { DifferenceOperation::Until => Ok(result), DifferenceOperation::Since => Ok(result.negated()), diff --git a/src/builtins/core/year_month.rs b/src/builtins/core/year_month.rs index 014f0abab..d6409b382 100644 --- a/src/builtins/core/year_month.rs +++ b/src/builtins/core/year_month.rs @@ -16,7 +16,7 @@ use crate::{ temporal_assert, unix_time::EpochNanoseconds, utils::pad_iso_year, - Calendar, MonthCode, Sign, TemporalError, TemporalResult, TemporalUnwrap, TimeZone, + Calendar, MonthCode, TemporalError, TemporalResult, TemporalUnwrap, TimeZone, }; use icu_calendar::AnyCalendarKind; @@ -321,7 +321,7 @@ impl PlainYearMonth { // 10. If sign < 0, then let date = if sign.as_sign_multiplier() < 0 { // a. Let oneMonthDuration be ! CreateDateDurationRecord(0, 1, 0, 0). - let one_month_duration = DateDuration::new_unchecked(Sign::Positive, 0, 1, 0, 0); + let one_month_duration = DateDuration::new_unchecked(0, 1, 0, 0); // b. Let nextMonth be ? CalendarDateAdd(calendar, intermediateDate, oneMonthDuration, constrain). let next_month = calendar.date_add( @@ -446,7 +446,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 be3e50501..eaa94697b 100644 --- a/src/builtins/core/zoneddatetime.rs +++ b/src/builtins/core/zoneddatetime.rs @@ -571,7 +571,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 { @@ -601,7 +601,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/iso.rs b/src/iso.rs index 07ed7e8f2..c53376d5d 100644 --- a/src/iso.rs +++ b/src/iso.rs @@ -29,7 +29,7 @@ use crate::{ builtins::core::{ calendar::Calendar, duration::normalized::{NormalizedDurationRecord, NormalizedTimeDuration}, - Duration, PartialTime, PlainDate, + PartialTime, PlainDate, }, error::{ErrorMessage, TemporalError}, options::{ArithmeticOverflow, ResolvedRoundingOptions, Unit}, @@ -155,45 +155,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_duration_to_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( @@ -274,7 +235,6 @@ impl IsoDateTime { // 17. Return ? CreateNormalizedDurationRecord(dateDifference.[[Years]], dateDifference.[[Months]], dateDifference.[[Weeks]], days, timeDuration). NormalizedDurationRecord::new( DateDuration::new_unchecked( - date_diff.sign(), date_diff.years(), date_diff.months(), date_diff.weeks(), @@ -411,11 +371,9 @@ impl IsoDate { // 1. Assert: year, month, day, years, months, weeks, and days are integers. // 2. Assert: overflow is either "constrain" or "reject". // 3. Let intermediate be ! BalanceISOYearMonth(year + years, month + months). - let year_offset = duration.years * i64::from(duration.sign.as_sign_multiplier()); - let month_offset = duration.months * i64::from(duration.sign.as_sign_multiplier()); let intermediate = balance_iso_year_month_with_clamp( - i64::from(self.year) + year_offset, - i64::from(self.month) + month_offset, + i64::from(self.year) + duration.years, + i64::from(self.month) + duration.months, ); // 4. Let intermediate be ? RegulateISODate(intermediate.[[Year]], intermediate.[[Month]], day, overflow). @@ -423,8 +381,7 @@ 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 * i64::from(duration.sign.as_sign_multiplier())); + let additional_days = duration.days + (7 * duration.weeks); // 6. Let d be intermediate.[[Day]] + days. let intermediate_days = i64::from(intermediate.day) + additional_days; From 3d14622e058dba905c585ea3f1bf2042d1362286 Mon Sep 17 00:00:00 2001 From: Kevin Ness Date: Sun, 3 Aug 2025 14:54:01 -0500 Subject: [PATCH 25/26] Update the FFI --- temporal_capi/bindings/c/Duration.h | 3 --- temporal_capi/bindings/cpp/temporal_rs/Duration.d.hpp | 3 --- temporal_capi/bindings/cpp/temporal_rs/Duration.hpp | 6 ------ temporal_capi/src/duration.rs | 2 +- temporal_capi/src/instant.rs | 10 ++++++---- 5 files changed, 7 insertions(+), 17 deletions(-) diff --git a/temporal_capi/bindings/c/Duration.h b/temporal_capi/bindings/c/Duration.h index ce8a367a2..6c6ab0765 100644 --- a/temporal_capi/bindings/c/Duration.h +++ b/temporal_capi/bindings/c/Duration.h @@ -29,12 +29,9 @@ temporal_rs_Duration_create_result temporal_rs_Duration_create(int64_t years, in typedef struct temporal_rs_Duration_try_new_result {union {Duration* ok; TemporalError err;}; bool is_ok;} temporal_rs_Duration_try_new_result; temporal_rs_Duration_try_new_result temporal_rs_Duration_try_new(int64_t years, int64_t months, int64_t weeks, int64_t days, int64_t hours, int64_t minutes, int64_t seconds, int64_t milliseconds, double microseconds, double nanoseconds); -<<<<<<< HEAD typedef struct temporal_rs_Duration_from_day_and_time_result {union {Duration* ok; TemporalError err;}; bool is_ok;} temporal_rs_Duration_from_day_and_time_result; temporal_rs_Duration_from_day_and_time_result temporal_rs_Duration_from_day_and_time(int64_t day, const Duration* time); -======= ->>>>>>> main typedef struct temporal_rs_Duration_from_partial_duration_result {union {Duration* ok; TemporalError err;}; bool is_ok;} temporal_rs_Duration_from_partial_duration_result; temporal_rs_Duration_from_partial_duration_result temporal_rs_Duration_from_partial_duration(PartialDuration partial); diff --git a/temporal_capi/bindings/cpp/temporal_rs/Duration.d.hpp b/temporal_capi/bindings/cpp/temporal_rs/Duration.d.hpp index a75efa46e..393fa3283 100644 --- a/temporal_capi/bindings/cpp/temporal_rs/Duration.d.hpp +++ b/temporal_capi/bindings/cpp/temporal_rs/Duration.d.hpp @@ -43,11 +43,8 @@ class Duration { inline static diplomat::result, temporal_rs::TemporalError> try_new(int64_t years, int64_t months, int64_t weeks, int64_t days, int64_t hours, int64_t minutes, int64_t seconds, int64_t milliseconds, double microseconds, double nanoseconds); -<<<<<<< HEAD inline static diplomat::result, temporal_rs::TemporalError> from_day_and_time(int64_t day, const temporal_rs::Duration& time); -======= ->>>>>>> main inline static diplomat::result, temporal_rs::TemporalError> from_partial_duration(temporal_rs::PartialDuration partial); inline static diplomat::result, temporal_rs::TemporalError> from_utf8(std::string_view s); diff --git a/temporal_capi/bindings/cpp/temporal_rs/Duration.hpp b/temporal_capi/bindings/cpp/temporal_rs/Duration.hpp index 1e3d313ce..ceb866911 100644 --- a/temporal_capi/bindings/cpp/temporal_rs/Duration.hpp +++ b/temporal_capi/bindings/cpp/temporal_rs/Duration.hpp @@ -32,12 +32,9 @@ namespace capi { typedef struct temporal_rs_Duration_try_new_result {union {temporal_rs::capi::Duration* ok; temporal_rs::capi::TemporalError err;}; bool is_ok;} temporal_rs_Duration_try_new_result; temporal_rs_Duration_try_new_result temporal_rs_Duration_try_new(int64_t years, int64_t months, int64_t weeks, int64_t days, int64_t hours, int64_t minutes, int64_t seconds, int64_t milliseconds, double microseconds, double nanoseconds); -<<<<<<< HEAD typedef struct temporal_rs_Duration_from_day_and_time_result {union {temporal_rs::capi::Duration* ok; temporal_rs::capi::TemporalError err;}; bool is_ok;} temporal_rs_Duration_from_day_and_time_result; temporal_rs_Duration_from_day_and_time_result temporal_rs_Duration_from_day_and_time(int64_t day, const temporal_rs::capi::Duration* time); -======= ->>>>>>> main typedef struct temporal_rs_Duration_from_partial_duration_result {union {temporal_rs::capi::Duration* ok; temporal_rs::capi::TemporalError err;}; bool is_ok;} temporal_rs_Duration_from_partial_duration_result; temporal_rs_Duration_from_partial_duration_result temporal_rs_Duration_from_partial_duration(temporal_rs::capi::PartialDuration partial); @@ -133,15 +130,12 @@ inline diplomat::result, temporal_rs::Tem return result.is_ok ? diplomat::result, temporal_rs::TemporalError>(diplomat::Ok>(std::unique_ptr(temporal_rs::Duration::FromFFI(result.ok)))) : diplomat::result, temporal_rs::TemporalError>(diplomat::Err(temporal_rs::TemporalError::FromFFI(result.err))); } -<<<<<<< HEAD inline diplomat::result, temporal_rs::TemporalError> temporal_rs::Duration::from_day_and_time(int64_t day, const temporal_rs::Duration& time) { auto result = temporal_rs::capi::temporal_rs_Duration_from_day_and_time(day, time.AsFFI()); return result.is_ok ? diplomat::result, temporal_rs::TemporalError>(diplomat::Ok>(std::unique_ptr(temporal_rs::Duration::FromFFI(result.ok)))) : diplomat::result, temporal_rs::TemporalError>(diplomat::Err(temporal_rs::TemporalError::FromFFI(result.err))); } -======= ->>>>>>> main inline diplomat::result, temporal_rs::TemporalError> temporal_rs::Duration::from_partial_duration(temporal_rs::PartialDuration partial) { auto result = temporal_rs::capi::temporal_rs_Duration_from_partial_duration(partial.AsFFI()); return result.is_ok ? diplomat::result, temporal_rs::TemporalError>(diplomat::Ok>(std::unique_ptr(temporal_rs::Duration::FromFFI(result.ok)))) : diplomat::result, temporal_rs::TemporalError>(diplomat::Err(temporal_rs::TemporalError::FromFFI(result.err))); diff --git a/temporal_capi/src/duration.rs b/temporal_capi/src/duration.rs index 2839c9384..2a9bd086c 100644 --- a/temporal_capi/src/duration.rs +++ b/temporal_capi/src/duration.rs @@ -133,7 +133,7 @@ pub mod ffi { pub fn from_day_and_time(day: i64, time: &Duration) -> Result, TemporalError> { Ok(Box::new(Duration( - temporal_rs::Duration::from_day_and_time(day, &time.0)?, + temporal_rs::Duration::try_from_day_and_time(day, &time.0)?, ))) } pub fn from_partial_duration(partial: PartialDuration) -> Result, TemporalError> { diff --git a/temporal_capi/src/instant.rs b/temporal_capi/src/instant.rs index eb2c9e04a..c0e919f69 100644 --- a/temporal_capi/src/instant.rs +++ b/temporal_capi/src/instant.rs @@ -72,28 +72,30 @@ pub mod ffi { pub fn add(&self, duration: &Duration) -> Result, TemporalError> { self.0 - .add(duration.0) + .add(&duration.0) .map(|c| Box::new(Self(c))) .map_err(Into::into) } + // TODO: deprecate? pub fn add_time_duration(&self, duration: &Duration) -> Result, TemporalError> { self.0 - .add_to_instant(&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) + .subtract(&duration.0) .map(|c| Box::new(Self(c))) .map_err(Into::into) } + // TODO: deprecate? pub fn subtract_time_duration( &self, duration: &Duration, ) -> Result, TemporalError> { self.0 - .subtract_duration(&duration.0) + .subtract(&duration.0) .map(|c| Box::new(Self(c))) .map_err(Into::into) } From 7895f65ee24b6eede5a6f46a5897d5f5d6f958a3 Mon Sep 17 00:00:00 2001 From: Kevin Ness Date: Sun, 3 Aug 2025 15:05:54 -0500 Subject: [PATCH 26/26] Update tzif-inspect + add test case to from_partial_duration --- src/builtins/core/duration.rs | 14 ++++++++++++++ tools/tzif-inspect/src/main.rs | 11 ++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/builtins/core/duration.rs b/src/builtins/core/duration.rs index 1ec9c8d57..523b6a0df 100644 --- a/src/builtins/core/duration.rs +++ b/src/builtins/core/duration.rs @@ -603,6 +603,20 @@ impl Duration { } /// 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() diff --git a/tools/tzif-inspect/src/main.rs b/tools/tzif-inspect/src/main.rs index 854f34f24..1807001a3 100644 --- a/tools/tzif-inspect/src/main.rs +++ b/tools/tzif-inspect/src/main.rs @@ -1,7 +1,9 @@ use std::env; use std::string::ToString; use temporal_rs::tzdb::Tzif; -use temporal_rs::{Duration, PlainDate, PlainTime, TimeDuration, TimeZone, ZonedDateTime}; +use temporal_rs::{ + partial::PartialDuration, Duration, PlainDate, PlainTime, TimeZone, ZonedDateTime, +}; use tzif::data::posix::TransitionDay; use tzif::data::time::Seconds; use tzif::data::tzif::{StandardWallIndicator, UtLocalIndicator}; @@ -36,8 +38,11 @@ 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 duration = Duration::from_partial_duration(PartialDuration { + seconds: Some(seconds), + ..Default::default() + }) + .unwrap(); let time = PlainTime::default().add_time_duration(&duration).unwrap(); let string = time.to_ixdtf_string(Default::default()).unwrap(); if is_negative {