diff --git a/Cargo.lock b/Cargo.lock index 6c891287b..7262e35ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -142,6 +142,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" @@ -836,6 +842,7 @@ dependencies = [ name = "temporal_rs" version = "0.0.11" dependencies = [ + "bnum", "combine", "core_maths", "iana-time-zone", diff --git a/Cargo.toml b/Cargo.toml index da6cee0ec..8e5e35e6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -82,6 +82,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/compiled/duration/tests.rs b/src/builtins/compiled/duration/tests.rs index 4fcf9cae3..24a254001 100644 --- a/src/builtins/compiled/duration/tests.rs +++ b/src/builtins/compiled/duration/tests.rs @@ -3,7 +3,7 @@ use crate::{ OffsetDisambiguation, RelativeTo, RoundingIncrement, RoundingMode, RoundingOptions, Unit, }, partial::PartialDuration, - Calendar, DateDuration, PlainDate, TimeDuration, TimeZone, ZonedDateTime, + Calendar, DateDuration, PlainDate, TimeZone, ZonedDateTime, }; use core::{num::NonZeroU32, str::FromStr}; @@ -373,7 +373,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, @@ -383,7 +396,7 @@ fn rounding_to_fractional_day_tests() { let result = twenty_five_hours.round(options, None).unwrap(); assert_duration(result, (0, 0, 0, 1, 1, 0, 0, 0, 0, 0)); - let sixty_days = Duration::from(DateDuration::new_unchecked(0, 0, 0, 64)); + let sixty_days = Duration::from(DateDuration::new(0, 0, 0, 64).unwrap()); let options = RoundingOptions { largest_unit: None, smallest_unit: Some(Unit::Day), @@ -393,7 +406,7 @@ fn rounding_to_fractional_day_tests() { let result = sixty_days.round(options, None).unwrap(); assert_duration(result, (0, 0, 0, 60, 0, 0, 0, 0, 0, 0)); - let sixty_days = Duration::from(DateDuration::new_unchecked(0, 0, 0, 64)); + let sixty_days = Duration::from(DateDuration::new(0, 0, 0, 64).unwrap()); let options = RoundingOptions { largest_unit: None, smallest_unit: Some(Unit::Day), @@ -403,7 +416,7 @@ fn rounding_to_fractional_day_tests() { let result = sixty_days.round(options, None).unwrap(); assert_duration(result, (0, 0, 0, 60, 0, 0, 0, 0, 0, 0)); - let sixty_days = Duration::from(DateDuration::new_unchecked(0, 0, 0, 64)); + let sixty_days = Duration::from(DateDuration::new(0, 0, 0, 64).unwrap()); let options = RoundingOptions { largest_unit: None, smallest_unit: Some(Unit::Day), @@ -413,7 +426,7 @@ fn rounding_to_fractional_day_tests() { let result = sixty_days.round(options, None).unwrap(); assert_duration(result, (0, 0, 0, 70, 0, 0, 0, 0, 0, 0)); - let sixty_days = Duration::from(DateDuration::new_unchecked(0, 0, 0, 1000)); + let sixty_days = Duration::from(DateDuration::new(0, 0, 0, 1000).unwrap()); let options = RoundingOptions { largest_unit: None, smallest_unit: Some(Unit::Day), @@ -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/calendar.rs b/src/builtins/core/calendar.rs index 43aaafb15..4c4a4e29f 100644 --- a/src/builtins/core/calendar.rs +++ b/src/builtins/core/calendar.rs @@ -4,13 +4,11 @@ //! Temporal compatible calendar implementations. use crate::{ - builtins::core::{ - duration::DateDuration, 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; diff --git a/src/builtins/core/date.rs b/src/builtins/core/date.rs index d72bab8da..f42449a47 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}; @@ -21,8 +18,7 @@ use ixdtf::records::DateRecord; use writeable::Writeable; use super::{ - calendar::month_to_month_code, - duration::{normalized::NormalizedDurationRecord, TimeDuration}, + calendar::month_to_month_code, duration::normalized::NormalizedDurationRecord, PartialYearMonth, PlainMonthDay, PlainYearMonth, }; use tinystr::TinyAsciiStr; @@ -265,53 +261,31 @@ impl PlainDate { Self { iso, calendar } } + // Updated: 2025-08-03 /// Returns the date after adding the given duration to date. /// - /// Temporal Equivalent: 3.5.13 `AddDate ( calendar, plainDate, duration [ , options [ , dateAdd ] ] )` + /// 3.5.14 `AddDurationToDate` + /// + /// More information: + /// + /// - [AO specification](https://tc39.es/proposal-temporal/#sec-temporal-adddurationtodate) #[inline] - pub(crate) fn add_date( + pub(crate) fn add_duration_to_date( &self, duration: &Duration, overflow: Option, ) -> TemporalResult { - // 2. If options is not present, set options to undefined. + // 3. If operation is subtract, set duration to CreateNegatedTemporalDuration(duration). + // 4. Let dateDuration be ToDateDurationRecordWithoutTime(duration). + // TODO: Look into why this is fallible, and make some adjustments + let date_duration = duration.to_date_duration_record_without_time()?; + // 5. Let resolvedOptions be ? GetOptionsObject(options). + // 6. Let overflow be ? GetTemporalOverflowOption(resolvedOptions). let overflow = overflow.unwrap_or(ArithmeticOverflow::Constrain); - // 3. If duration.[[Years]] ≠ 0, or duration.[[Months]] ≠ 0, or duration.[[Weeks]] ≠ 0, then - if duration.date().years != 0 || duration.date().months != 0 || duration.date().weeks != 0 { - // a. If dateAdd is not present, then - // i. Set dateAdd to unused. - // ii. If calendar is an Object, set dateAdd to ? GetMethod(calendar, "dateAdd"). - // b. Return ? CalendarDateAdd(calendar, plainDate, duration, options, dateAdd). - return self - .calendar() - .date_add(&self.iso, duration.date(), overflow); - } - - // 4. Let overflow be ? ToTemporalOverflow(options). - // 5. Let norm be NormalizeTimeDuration(duration.[[Hours]], - // duration.[[Minutes]], duration.[[Seconds]], - // duration.[[Milliseconds]], duration.[[Microseconds]], - // duration.[[Nanoseconds]]). - // 6. Let days be duration.[[Days]] + BalanceTimeDuration(norm, - // "day").[[Days]]. - let days = duration - .days() - .checked_add( - TimeDuration::from_normalized(duration.time().to_normalized(), Unit::Day)?.0, - ) - .ok_or(TemporalError::range())?; - - // 7. Let result be ? AddISODate(plainDate.[[ISOYear]], plainDate.[[ISOMonth]], plainDate.[[ISODay]], 0, 0, 0, days, overflow). - let result = self - .iso - .add_date_duration(&DateDuration::new(0, 0, 0, days)?, overflow)?; - - Self::try_new( - result.year, - result.month, - result.day, - self.calendar().clone(), - ) + // 7. Let result be ? CalendarDateAdd(calendar, temporalDate.[[ISODate]], dateDuration, overflow). + // 8. Return ! CreateTemporalDate(result, calendar). + self.calendar() + .date_add(&self.iso, &date_duration, overflow) } /// Returns a duration representing the difference between the dates one and two. @@ -378,7 +352,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; @@ -399,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), @@ -593,7 +567,7 @@ impl PlainDate { duration: &Duration, overflow: Option, ) -> TemporalResult { - self.add_date(duration, overflow) + self.add_duration_to_date(duration, overflow) } #[inline] @@ -603,7 +577,7 @@ impl PlainDate { duration: &Duration, overflow: Option, ) -> TemporalResult { - self.add_date(&duration.negated(), overflow) + self.add_duration_to_date(&duration.negated(), overflow) } #[inline] diff --git a/src/builtins/core/datetime.rs b/src/builtins/core/datetime.rs index 94821303f..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,33 +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_time_duration(duration.time()); - - // TODO: validate Constrain is default with all the recent changes. - // 6. Let result be ? AddDateTime(dateTime.[[ISOYear]], dateTime.[[ISOMonth]], dateTime.[[ISODay]], dateTime.[[ISOHour]], dateTime.[[ISOMinute]], dateTime.[[ISOSecond]], dateTime.[[ISOMillisecond]], dateTime.[[ISOMicrosecond]], dateTime.[[ISONanosecond]], calendarRec, sign × duration.[[Years]], sign × duration.[[Months]], sign × duration.[[Weeks]], sign × duration.[[Days]], norm, options). - let result = - self.iso - .add_date_duration(self.calendar().clone(), duration.date(), norm, overflow)?; - - // 7. Assert: IsValidISODate(result.[[Year]], result.[[Month]], result.[[Day]]) is true. - // 8. Assert: IsValidTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], - // result.[[Microsecond]], result.[[Nanosecond]]) is true. - temporal_assert!( - result.is_within_limits(), - "Assertion failed: the below datetime is not within valid limits:\n{:?}", - result - ); - - // 9. Return ? CreateTemporalDateTime(result.[[Year]], result.[[Month]], result.[[Day]], result.[[Hour]], - // result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], - // result.[[Nanosecond]], dateTime.[[Calendar]]). - Ok(Self::new_unchecked(result, self.calendar.clone())) + // 5. Let internalDuration be ToInternalDurationRecordWith24HourDays(duration). + let internal_duration = + NormalizedDurationRecord::from_duration_with_24_hour_days(duration)?; + // 6. Let timeResult be AddTime(dateTime.[[ISODateTime]].[[Time]], internalDuration.[[Time]]). + let (days, time_result) = self + .iso + .time + .add(internal_duration.normalized_time_duration()); + // 7. Let dateDuration be ? AdjustDateDurationRecord(internalDuration.[[Date]], timeResult.[[Days]]). + let date_duration = internal_duration.date().adjust(days, None, None)?; + // 8. Let addedDate be ? CalendarDateAdd(dateTime.[[Calendar]], dateTime.[[ISODateTime]].[[ISODate]], dateDuration, overflow). + let added_date = self.calendar().date_add( + &self.iso.date, + &date_duration, + overflow.unwrap_or(ArithmeticOverflow::Constrain), + )?; + // 9. Let result be CombineISODateAndTimeRecord(addedDate, timeResult). + let result = IsoDateTime::new(added_date.iso, time_result)?; + // 10. Return ? CreateTemporalDateTime(result, dateTime.[[Calendar]]). + Ok(Self::new_unchecked(result, self.calendar().clone())) } /// Difference two `DateTime`s together. @@ -268,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 { @@ -963,8 +956,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::{ @@ -1532,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 18e804a4a..523b6a0df 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}, error::ErrorMessage, @@ -9,32 +10,27 @@ 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, + temporal_assert, DateDuration, Sign, TemporalError, TemporalResult, NS_PER_DAY, }; use alloc::format; use alloc::string::String; +use bnum::cast::As; use core::{cmp::Ordering, str::FromStr}; use ixdtf::{ encoding::Utf8, parsers::IsoDurationParser, records::Fraction, records::TimeDurationRecord, }; 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; - /// A `PartialDuration` is a Duration that may have fields not set. #[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)] pub struct PartialDuration { @@ -218,10 +214,37 @@ impl PartialDuration { /// /// [mdn-duration]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration #[non_exhaustive] -#[derive(Debug, Clone, Copy, Default)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] pub struct Duration { - date: DateDuration, - time: TimeDuration, + 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 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 { @@ -248,70 +271,197 @@ 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.signum()), + 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) fn try_new_from_durations( - date: DateDuration, - time: TimeDuration, - ) -> TemporalResult { - if !is_valid_duration( - date.years, - date.months, - date.weeks, - date.days, - time.hours, - time.minutes, - time.seconds, - time.milliseconds, - time.microseconds, - time.nanoseconds, - ) { - return Err(TemporalError::range().with_message("Duration was not valid.")); + #[allow(clippy::too_many_arguments)] + pub(crate) const fn new_unchecked( + sign: Sign, + years: u32, + months: u32, + weeks: u32, + days: 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, } - Ok(Self::new_unchecked(date, time)) } #[inline] - pub(crate) fn from_normalized( + pub(crate) fn from_internal( duration_record: NormalizedDurationRecord, largest_unit: Unit, ) -> TemporalResult { - let (overflow_day, time) = TimeDuration::from_normalized( - duration_record.normalized_time_duration(), - largest_unit, - )?; - Self::new( + // 1. Let days, hours, minutes, seconds, milliseconds, and microseconds be 0. + let mut days = 0; + let mut hours = 0; + let mut minutes = 0; + let mut seconds = 0; + let mut milliseconds = 0; + let mut microseconds = 0; + + // 2. Let sign be TimeDurationSign(internalDuration.[[Time]]). + let sign = duration_record + .normalized_time_duration() + .sign() + .as_sign_multiplier(); + + // 3. Let nanoseconds be abs(internalDuration.[[Time]]). + let mut nanoseconds = duration_record.normalized_time_duration().0.abs(); + match largest_unit { + // 4. If largestUnit is "year", "month", "week", or "day", then + Unit::Year | Unit::Month | Unit::Week | Unit::Day => { + // a. Set microseconds to floor(nanoseconds / 1000). + // b. Set nanoseconds to nanoseconds modulo 1000. + (microseconds, nanoseconds) = nanoseconds.div_rem_euclid(&1_000); + + // c. Set milliseconds to floor(microseconds / 1000). + // d. Set microseconds to microseconds modulo 1000. + (milliseconds, microseconds) = microseconds.div_rem_euclid(&1_000); + + // e. Set seconds to floor(milliseconds / 1000). + // f. Set milliseconds to milliseconds modulo 1000. + (seconds, milliseconds) = milliseconds.div_rem_euclid(&1_000); + + // g. Set minutes to floor(seconds / 60). + // h. Set seconds to seconds modulo 60. + (minutes, seconds) = seconds.div_rem_euclid(&60); + + // i. Set hours to floor(minutes / 60). + // j. Set minutes to minutes modulo 60. + (hours, minutes) = minutes.div_rem_euclid(&60); + + // k. Set days to floor(hours / 24). + // l. Set hours to hours modulo 24. + (days, hours) = hours.div_rem_euclid(&24); + } + // 5. Else if largestUnit is "hour", then + Unit::Hour => { + // a. Set microseconds to floor(nanoseconds / 1000). + // b. Set nanoseconds to nanoseconds modulo 1000. + (microseconds, nanoseconds) = nanoseconds.div_rem_euclid(&1_000); + + // c. Set milliseconds to floor(microseconds / 1000). + // d. Set microseconds to microseconds modulo 1000. + (milliseconds, microseconds) = microseconds.div_rem_euclid(&1_000); + + // e. Set seconds to floor(milliseconds / 1000). + // f. Set milliseconds to milliseconds modulo 1000. + (seconds, milliseconds) = milliseconds.div_rem_euclid(&1_000); + + // g. Set minutes to floor(seconds / 60). + // h. Set seconds to seconds modulo 60. + (minutes, seconds) = seconds.div_rem_euclid(&60); + + // i. Set hours to floor(minutes / 60). + // j. Set minutes to minutes modulo 60. + (hours, minutes) = minutes.div_rem_euclid(&60); + } + // 6. Else if largestUnit is "minute", then + Unit::Minute => { + // a. Set microseconds to floor(nanoseconds / 1000). + // b. Set nanoseconds to nanoseconds modulo 1000. + (microseconds, nanoseconds) = nanoseconds.div_rem_euclid(&1_000); + + // c. Set milliseconds to floor(microseconds / 1000). + // d. Set microseconds to microseconds modulo 1000. + (milliseconds, microseconds) = microseconds.div_rem_euclid(&1_000); + + // e. Set seconds to floor(milliseconds / 1000). + // f. Set milliseconds to milliseconds modulo 1000. + (seconds, milliseconds) = milliseconds.div_rem_euclid(&1_000); + + // g. Set minutes to floor(seconds / 60). + // h. Set seconds to seconds modulo 60. + (minutes, seconds) = seconds.div_rem_euclid(&60); + } + // 7. Else if largestUnit is "second", then + Unit::Second => { + // a. Set microseconds to floor(nanoseconds / 1000). + // b. Set nanoseconds to nanoseconds modulo 1000. + (microseconds, nanoseconds) = nanoseconds.div_rem_euclid(&1_000); + + // c. Set milliseconds to floor(microseconds / 1000). + // d. Set microseconds to microseconds modulo 1000. + (milliseconds, microseconds) = microseconds.div_rem_euclid(&1_000); + + // e. Set seconds to floor(milliseconds / 1000). + // f. Set milliseconds to milliseconds modulo 1000. + (seconds, milliseconds) = milliseconds.div_rem_euclid(&1_000); + } + // 8. Else if largestUnit is "millisecond", then + Unit::Millisecond => { + // a. Set microseconds to floor(nanoseconds / 1000). + // b. Set nanoseconds to nanoseconds modulo 1000. + (microseconds, nanoseconds) = nanoseconds.div_rem_euclid(&1_000); + + // c. Set milliseconds to floor(microseconds / 1000). + // d. Set microseconds to microseconds modulo 1000. + (milliseconds, microseconds) = microseconds.div_rem_euclid(&1_000); + } + // 9. Else if largestUnit is "microsecond", then + Unit::Microsecond => { + // a. Set microseconds to floor(nanoseconds / 1000). + // b. Set nanoseconds to nanoseconds modulo 1000. + (microseconds, nanoseconds) = nanoseconds.div_rem_euclid(&1_000); + } + // 10. Else, + // a. Assert: largestUnit is "nanosecond". + _ => temporal_assert!(largest_unit == Unit::Nanosecond), + } + // 11. NOTE: When largestUnit is millisecond, microsecond, or nanosecond, milliseconds, microseconds, or + // nanoseconds may be an unsafe integer. In this case, care must be taken when implementing the calculation using + // floating point arithmetic. It can be implemented in C++ using std::fma(). String manipulation will also give an + // exact result, since the multiplication is by a power of 10. + // 12. Return ? CreateTemporalDuration(internalDuration.[[Date]].[[Years]], internalDuration.[[Date]].[[Months]], + // internalDuration.[[Date]].[[Weeks]], internalDuration.[[Date]].[[Days]] + days × sign, hours × sign, minutes × sign, + // seconds × sign, milliseconds × sign, microseconds × sign, nanoseconds × sign). + Duration::new( duration_record.date().years, duration_record.date().months, duration_record.date().weeks, - duration_record - .date() - .days - .checked_add(overflow_day) - .ok_or(TemporalError::range())?, - time.hours, - time.minutes, - time.seconds, - time.milliseconds, - time.microseconds, - time.nanoseconds, + duration_record.date().days + days as i64 * sign as i64, + hours as i64 * sign as i64, + minutes as i64 * sign as i64, + seconds as i64 * sign as i64, + milliseconds as i64 * sign as i64, + microseconds * sign as i128, + nanoseconds * sign as i128, ) } + /// Returns this `Duration` as a `NormalizedTimeDuration`. + #[inline] + pub(crate) fn to_normalized(self) -> NormalizedTimeDuration { + NormalizedTimeDuration::from_duration(&self) + } + /// Returns the a `Vec` of the fields values. #[inline] #[must_use] @@ -330,13 +480,6 @@ impl Duration { ] } - /// Returns whether `Duration`'s `DateDuration` is empty and is therefore a `TimeDuration`. - #[inline] - #[must_use] - pub(crate) fn is_time_duration(&self) -> bool { - self.date().fields().iter().all(|x| x == &0) - } - /// Returns the `Unit` corresponding to the largest non-zero field. #[inline] pub(crate) fn default_largest_unit(&self) -> Unit { @@ -348,6 +491,24 @@ impl Duration { .unwrap_or(Unit::Nanosecond) } + // 7.5.5 ToInternalDurationRecord ( duration ) + pub(crate) fn to_internal_duration_record(self) -> NormalizedDurationRecord { + // 1. Let dateDuration be ! CreateDateDurationRecord(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]]). + let date_duration = + DateDuration::new_unchecked(self.years(), self.months(), self.weeks(), self.days()); + // 2. Let timeDuration be TimeDurationFromComponents(duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]]). + let time_duration = NormalizedTimeDuration::from_components( + self.hours(), + self.minutes(), + self.seconds(), + self.milliseconds(), + self.microseconds(), + self.nanoseconds(), + ); + // 3. Return CombineDateAndTimeDuration(dateDuration, timeDuration). + NormalizedDurationRecord::combine(date_duration, time_duration) + } + /// Equivalent of [`7.5.7 ToDateDurationRecordWithoutTime ( duration )`][spec] /// /// [spec]: @@ -381,17 +542,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, @@ -406,17 +556,67 @@ impl Duration { ) { return Err(TemporalError::range().with_message("Duration was not valid.")); } - Ok(duration) + let sign = duration_sign(&[ + years, + months, + weeks, + days, + hours, + minutes, + seconds, + milliseconds, + microseconds as i64, + nanoseconds as i64, + ]); + Ok(Duration::new_unchecked( + sign, + u32::try_from(years.abs()).or(Err(TemporalError::range()))?, + u32::try_from(months.abs()).or(Err(TemporalError::range()))?, + u32::try_from(weeks.abs()).or(Err(TemporalError::range()))?, + days.abs().as_(), + hours.abs().as_(), + minutes.abs().as_(), + seconds.abs().as_(), + u64::try_from(milliseconds.abs()).or(Err(TemporalError::range()))?, + microseconds.abs().as_(), + nanoseconds.abs().as_(), + )) } - /// Creates a `Duration` from a provided a day and a `TimeDuration`. + // TODO: Double check i64 on milliseconds + /// Creates a `Duration` from a provided a day and a `Duration`. /// - /// Note: `TimeDuration` records can store a day value to deal with overflow. - pub(crate) fn try_from_day_and_time(day: i64, time: &TimeDuration) -> TemporalResult { - Self::try_new_from_durations(DateDuration::new_unchecked(0, 0, 0, day), *time) + /// Note: `Duration` records can store a day value to deal with overflow. + pub fn try_from_day_and_time(days: i64, time: &Duration) -> TemporalResult { + Duration::new( + 0, + 0, + 0, + days, + time.hours(), + time.minutes(), + time.seconds(), + time.milliseconds(), + time.microseconds(), + time.nanoseconds(), + ) } /// 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() @@ -556,7 +756,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] @@ -566,7 +771,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). @@ -574,7 +779,9 @@ impl Duration { let largest_unit_1 = self.default_largest_unit(); let largest_unit_2 = other.default_largest_unit(); // 10. Let duration1 be ToInternalDurationRecord(one). + let duration_one = self.to_internal_duration_record(); // 11. Let duration2 be ToInternalDurationRecord(two). + let duration_two = other.to_internal_duration_record(); // 12. If zonedRelativeTo is not undefined, and either UnitCategory(largestUnit1) or UnitCategory(largestUnit2) is date, then if let Some(RelativeTo::ZonedDateTime(zdt)) = relative_to.as_ref() { if largest_unit_1.is_date_unit() || largest_unit_2.is_date_unit() { @@ -582,8 +789,10 @@ impl Duration { // b. Let calendar be zonedRelativeTo.[[Calendar]]. // c. Let after1 be ? AddZonedDateTime(zonedRelativeTo.[[EpochNanoseconds]], timeZone, calendar, duration1, constrain). // d. Let after2 be ? AddZonedDateTime(zonedRelativeTo.[[EpochNanoseconds]], timeZone, calendar, duration2, constrain). - let after1 = zdt.add_as_instant(self, ArithmeticOverflow::Constrain, provider)?; - let after2 = zdt.add_as_instant(other, ArithmeticOverflow::Constrain, provider)?; + let after1 = + zdt.add_zoned_date_time(duration_one, ArithmeticOverflow::Constrain, provider)?; + let after2 = + zdt.add_zoned_date_time(duration_two, ArithmeticOverflow::Constrain, provider)?; // e. If after1 > after2, return 1𝔽. // f. If after1 < after2, return -1𝔽. // g. Return +0𝔽. @@ -599,16 +808,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().or(Err(TemporalError::range()))?, + other.days.try_into().or(Err(TemporalError::range()))?, + ) }; // 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)) } @@ -617,88 +829,88 @@ impl Duration { // ==== Public `Duration` Getters/Setters ==== impl Duration { - /// Returns a reference to the inner `TimeDuration` + /// Returns the inner `DateDuration` as an owned value #[inline] #[must_use] - pub fn time(&self) -> &TimeDuration { - &self.time - } - - /// Returns a reference to the inner `DateDuration` - #[inline] - #[must_use] - pub fn date(&self) -> &DateDuration { - &self.date + pub fn date(&self) -> DateDuration { + DateDuration::from(self) } /// Returns the `years` field of duration. #[inline] #[must_use] - pub const fn years(&self) -> i64 { - self.date.years + pub fn years(&self) -> i64 { + i64::from(self.years) * i64::from(self.sign.as_sign_multiplier()) } /// Returns the `months` field of duration. #[inline] #[must_use] - pub const fn months(&self) -> i64 { - self.date.months + pub fn months(&self) -> i64 { + i64::from(self.months) * i64::from(self.sign.as_sign_multiplier()) } /// Returns the `weeks` field of duration. #[inline] #[must_use] - pub const fn weeks(&self) -> i64 { - self.date.weeks + pub fn weeks(&self) -> i64 { + i64::from(self.weeks) * i64::from(self.sign.as_sign_multiplier()) } /// Returns the `days` field of duration. #[inline] #[must_use] - pub const fn days(&self) -> i64 { - self.date.days + pub fn days(&self) -> 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 const fn hours(&self) -> i64 { - self.time.hours + pub fn hours(&self) -> i64 { + i64::try_from(self.hours).expect("Hours must fit into i64") + * i64::from(self.sign.as_sign_multiplier()) } - /// Returns the `hours` field of duration. + /// Returns the `minutes` field of duration. #[inline] #[must_use] - pub const fn minutes(&self) -> i64 { - self.time.minutes + pub fn minutes(&self) -> 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 const fn seconds(&self) -> i64 { - self.time.seconds + pub fn seconds(&self) -> 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 const fn milliseconds(&self) -> i64 { - self.time.milliseconds + pub fn milliseconds(&self) -> 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 const fn microseconds(&self) -> i128 { - self.time.microseconds + pub fn microseconds(&self) -> 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 const fn nanoseconds(&self) -> i128 { - self.time.nanoseconds + pub fn nanoseconds(&self) -> i128 { + i128::try_from(self.nanoseconds).expect("Nanoseconds must fit into i128") + * i128::from(self.sign.as_sign_multiplier()) } } @@ -709,7 +921,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. @@ -726,8 +938,8 @@ impl Duration { #[must_use] pub fn negated(&self) -> Self { Self { - date: self.date().negated(), - time: self.time().negated(), + sign: self.sign.negate(), + ..*self } } @@ -736,8 +948,12 @@ impl Duration { #[must_use] pub fn abs(&self) -> Self { Self { - date: self.date().abs(), - time: self.time().abs(), + sign: if self.sign == Sign::Zero { + Sign::Zero + } else { + Sign::Positive + }, + ..*self } } @@ -746,42 +962,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_time_duration(self.time()); - // 27. Let norm2 be NormalizeTimeDuration(h2, min2, s2, ms2, mus2, ns2). - let norm_two = NormalizedTimeDuration::from_time_duration(other.time()); - - // 28. If IsCalendarUnit(largestUnit), throw a RangeError exception. + // 1. Set other to ? ToTemporalDuration(other). + // 2. If operation is subtract, set other to CreateNegatedTemporalDuration(other). + // 3. Let largestUnit1 be DefaultTemporalLargestUnit(duration). + let largest_unit_one = self.default_largest_unit(); + // 4. Let largestUnit2 be DefaultTemporalLargestUnit(other). + let largest_unit_two = other.default_largest_unit(); + // 5. Let largestUnit be LargerOfTwoTemporalUnits(largestUnit1, largestUnit2). + let largest_unit = largest_unit_one.max(largest_unit_two); + // 6. If IsCalendarUnit(largestUnit) is true, throw a RangeError exception. if largest_unit.is_calendar_unit() { return Err(TemporalError::range().with_message( "Largest unit cannot be a calendar unit when adding two durations.", )); } - - // NOTE: for lines 488-489 - // - // Maximum amount of days in a valid duration: 104_249_991_374 * 2 < i64::MAX - // 29. Let normResult be ? AddNormalizedTimeDuration(norm1, norm2). - // 30. Set normResult to ? Add24HourDaysToNormalizedTimeDuration(normResult, d1 + d2). - let result = (norm_one + norm_two)?.add_days( - self.days() - .checked_add(other.days()) - .ok_or(TemporalError::range())?, - )?; - - // 31. Let result be ? BalanceTimeDuration(normResult, largestUnit). - let (result_days, result_time) = TimeDuration::from_normalized(result, largest_unit)?; - - // 32. Return ! CreateTemporalDuration(0, 0, 0, result.[[Days]], result.[[Hours]], result.[[Minutes]], - // result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]). - Duration::try_from_day_and_time(result_days, &result_time) + // 7. Let d1 be ToInternalDurationRecordWith24HourDays(duration). + let d1 = NormalizedDurationRecord::from_duration_with_24_hour_days(self)?; + // 8. Let d2 be ToInternalDurationRecordWith24HourDays(other). + let d2 = NormalizedDurationRecord::from_duration_with_24_hour_days(other)?; + // 9. Let timeResult be ? AddTimeDuration(d1.[[Time]], d2.[[Time]]). + let time_result = (d1.normalized_time_duration() + d2.normalized_time_duration())?; + // 10. Let result be CombineDateAndTimeDuration(ZeroDateDuration(), timeResult). + let result = NormalizedDurationRecord::combine(DateDuration::default(), time_result); + // 11. Return ? TemporalDurationFromInternal(result, largestUnit). + Duration::from_internal(result, largest_unit) } /// Returns the result of subtracting a `Duration` from the current `Duration` @@ -898,6 +1102,7 @@ impl Duration { // 26. If zonedRelativeTo is not undefined, then Some(RelativeTo::ZonedDateTime(zoned_relative_to)) => { // a. Let internalDuration be ToInternalDurationRecord(duration). + let internal_duration = self.to_internal_duration_record(); // b. Let timeZone be zonedRelativeTo.[[TimeZone]]. let time_zone = zoned_relative_to.timezone().clone(); @@ -909,8 +1114,8 @@ impl Duration { // let relative_epoch_ns = zoned_relative_to.epoch_nanoseconds(); // e. Let targetEpochNs be ? AddZonedDateTime(relativeEpochNs, timeZone, calendar, internalDuration, constrain). - let target_epoch_ns = zoned_relative_to.add_as_instant( - self, + let target_epoch_ns = zoned_relative_to.add_zoned_date_time( + internal_duration, ArithmeticOverflow::Constrain, provider, )?; @@ -929,7 +1134,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 @@ -974,7 +1179,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 => {} } @@ -1022,7 +1227,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` @@ -1037,12 +1242,16 @@ impl Duration { // 11. If zonedRelativeTo is not undefined, then Some(RelativeTo::ZonedDateTime(zoned_datetime)) => { // a. Let internalDuration be ToInternalDurationRecord(duration). + let internal_duration = self.to_internal_duration_record(); // b. Let timeZone be zonedRelativeTo.[[TimeZone]]. // c. Let calendar be zonedRelativeTo.[[Calendar]]. // d. Let relativeEpochNs be zonedRelativeTo.[[EpochNanoseconds]]. // e. Let targetEpochNs be ? AddZonedDateTime(relativeEpochNs, timeZone, calendar, internalDuration, constrain). - let target_epcoh_ns = - zoned_datetime.add_as_instant(self, ArithmeticOverflow::Constrain, provider)?; + let target_epcoh_ns = zoned_datetime.add_zoned_date_time( + internal_duration, + ArithmeticOverflow::Constrain, + provider, + )?; // f. Let total be ? DifferenceZonedDateTimeWithTotal(relativeEpochNs, targetEpochNs, timeZone, calendar, unit). let total = zoned_datetime.diff_with_total( &ZonedDateTime::new_unchecked( @@ -1060,7 +1269,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( @@ -1125,23 +1334,24 @@ impl Duration { let rounding_options = ResolvedRoundingOptions::from_to_string_options(&resolved_options); - // 11. Let largestUnit be DefaultTemporalLargestUnit(duration). + // 12. Let largestUnit be DefaultTemporalLargestUnit(duration). let largest = self.default_largest_unit(); - // 12. Let internalDuration be ToInternalDurationRecord(duration). - let norm = NormalizedDurationRecord::new( - self.date, - NormalizedTimeDuration::from_time_duration(&self.time), - )?; - // 13. Let timeDuration be ? RoundTimeDuration(internalDuration.[[Time]], precision.[[Increment]], precision.[[Unit]], roundingMode). - let time = norm.normalized_time_duration().round(rounding_options)?; - // 14. Set internalDuration to CombineDateAndTimeDuration(internalDuration.[[Date]], timeDuration). - let norm = NormalizedDurationRecord::new(norm.date(), time)?; - // 15. Let roundedLargestUnit be LargerOfTwoUnits(largestUnit, second). - let rounded_largest = largest.max(Unit::Second); - // 16. Let roundedDuration be ? TemporalDurationFromInternal(internalDuration, roundedLargestUnit). - let rounded = Self::from_normalized(norm, rounded_largest)?; - - // 17. Return TemporalDurationToString(roundedDuration, precision.[[Precision]]). + // 13. Let internalDuration be ToInternalDurationRecord(duration). + let internal_duration = self.to_internal_duration_record(); + // 14. Let timeDuration be ? RoundTimeDuration(internalDuration.[[Time]], precision.[[Increment]], precision.[[Unit]], roundingMode). + let time_duration = internal_duration + .normalized_time_duration() + .round(rounding_options)?; + // 15. Set internalDuration to CombineDateAndTimeDuration(internalDuration.[[Date]], timeDuration). + let internal_duration = + NormalizedDurationRecord::combine(internal_duration.date(), time_duration); + // 16. Let roundedLargestUnit be LargerOfTwoTemporalUnits(largestUnit, second). + let rounded_largest_unit = largest.max(Unit::Second); + + // 17. Let roundedDuration be ? TemporalDurationFromInternal(internalDuration, roundedLargestUnit). + let rounded = Self::from_internal(internal_duration, rounded_largest_unit)?; + + // 18. Return TemporalDurationToString(roundedDuration, precision.[[Precision]]). Ok(duration_to_formattable(&rounded, resolved_options.precision)?.to_string()) } } @@ -1167,14 +1377,14 @@ pub fn duration_to_formattable( let hours = duration.hours().abs(); let minutes = duration.minutes().abs(); - let time = NormalizedTimeDuration::from_time_duration(&TimeDuration::new_unchecked( + let time = NormalizedTimeDuration::from_components( 0, 0, duration.seconds(), duration.milliseconds(), duration.microseconds(), duration.nanoseconds(), - )); + ); let seconds = time.seconds().unsigned_abs(); let subseconds = time.subseconds().unsigned_abs(); @@ -1302,7 +1512,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. @@ -1317,20 +1527,20 @@ 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 { + // TODO: this needs to be tested or handled in a different way Self { - date: value, - time: TimeDuration::default(), + sign: value.sign(), + years: value.years.unsigned_abs() as u32, + months: value.months.unsigned_abs() as u32, + weeks: value.weeks.unsigned_abs() as u32, + days: value + .days + .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 b50271ac5..d91422a69 100644 --- a/src/builtins/core/duration/date.rs +++ b/src/builtins/core/duration/date.rs @@ -1,7 +1,7 @@ //! Implementation of a `DateDuration` use crate::{ - iso::iso_date_to_epoch_days, options::ArithmeticOverflow, Duration, PlainDate, Sign, + builtins::Duration, iso::iso_date_to_epoch_days, options::ArithmeticOverflow, PlainDate, Sign, TemporalError, TemporalResult, }; @@ -30,7 +30,7 @@ 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, @@ -38,12 +38,37 @@ impl DateDuration { days, } } +} - /// Returns the iterator for `DateDuration` +impl From for DateDuration { + /// Converts a `Duration` into a `DateDuration`. + /// + /// This conversion is lossy, as `Duration` can represent time durations + /// that are not strictly date durations. #[inline] - #[must_use] - pub(crate) fn fields(&self) -> [i64; 4] { - [self.years, self.months, self.weeks, self.days] + fn from(duration: Duration) -> Self { + Self::new_unchecked( + duration.years(), + duration.months(), + duration.weeks(), + duration.days(), + ) + } +} + +impl From<&Duration> for DateDuration { + /// Converts a `Duration` into a `DateDuration`. + /// + /// This conversion is lossy, as `Duration` can represent time durations + /// that are not strictly date durations. + #[inline] + fn from(duration: &Duration) -> Self { + Self::new_unchecked( + duration.years(), + duration.months(), + duration.weeks(), + duration.days(), + ) } } @@ -71,10 +96,10 @@ 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(), + years: -self.years, + months: -self.months, + weeks: -self.weeks, + days: -self.days, } } @@ -94,7 +119,7 @@ impl DateDuration { #[inline] #[must_use] pub fn sign(&self) -> Sign { - duration_sign(self.fields().as_slice()) + duration_sign(&[self.years, self.months, self.weeks, self.days]) } /// DateDurationDays @@ -106,12 +131,10 @@ impl DateDuration { return Ok(self.days); } // 3. Let later be ? CalendarDateAdd(plainRelativeTo.[[Calendar]], plainRelativeTo.[[ISODate]], yearsMonthsWeeksDuration, constrain). - let later = relative_to.add( - &Duration { - date: *self, - time: Default::default(), - }, - Some(ArithmeticOverflow::Constrain), + let later = relative_to.calendar().date_add( + &relative_to.iso, + &ymw_duration, + ArithmeticOverflow::Constrain, )?; // 4. Let epochDays1 be ISODateToEpochDays(plainRelativeTo.[[ISODate]].[[Year]], plainRelativeTo.[[ISODate]].[[Month]] - 1, plainRelativeTo.[[ISODate]].[[Day]]). let epoch_days_1 = iso_date_to_epoch_days( diff --git a/src/builtins/core/duration/normalized.rs b/src/builtins/core/duration/normalized.rs index b5cb9ae29..a5052394c 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; @@ -35,20 +35,53 @@ 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); impl NormalizedTimeDuration { + /// Creates a `NormalizedTimeDuration` from signed integer components. + /// This method preserves the sign of each component during the calculation. + pub(crate) fn from_components( + hours: i64, + minutes: i64, + seconds: i64, + milliseconds: i64, + microseconds: i128, + nanoseconds: i128, + ) -> Self { + let mut total_nanoseconds: i128 = 0; + + total_nanoseconds += i128::from(hours) * NANOSECONDS_PER_HOUR; + total_nanoseconds += i128::from(minutes) * NANOSECONDS_PER_MINUTE; + total_nanoseconds += i128::from(seconds) * 1_000_000_000; + total_nanoseconds += i128::from(milliseconds) * 1_000_000; + total_nanoseconds += microseconds * 1_000; + total_nanoseconds += nanoseconds; + + debug_assert!(total_nanoseconds.abs() <= MAX_TIME_DURATION); + Self(total_nanoseconds) + } + /// Equivalent: 7.5.20 NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds, nanoseconds ) - pub(crate) fn from_time_duration(time: &TimeDuration) -> Self { + pub(crate) fn from_duration(duration: &Duration) -> Self { // Note: Calculations must be done after casting to `i128` in order to preserve precision - let mut nanoseconds: i128 = time.hours as i128 * NANOSECONDS_PER_HOUR; - nanoseconds += time.minutes as i128 * NANOSECONDS_PER_MINUTE; - nanoseconds += time.seconds as i128 * 1_000_000_000; - nanoseconds += time.milliseconds as i128 * 1_000_000; - nanoseconds += time.microseconds * 1_000; - nanoseconds += time.nanoseconds; + let sign_multiplier = duration.sign().as_sign_multiplier() as i128; + let mut nanoseconds: i128 = i128::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.nanoseconds).expect("nanosecond overflow") * sign_multiplier; // NOTE(nekevss): Is it worth returning a `RangeError` below. debug_assert!(nanoseconds.abs() <= MAX_TIME_DURATION); Self(nanoseconds) @@ -82,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] @@ -116,9 +143,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)) } @@ -242,6 +268,14 @@ pub struct NormalizedDurationRecord { } impl NormalizedDurationRecord { + pub(crate) fn combine(date: DateDuration, norm: NormalizedTimeDuration) -> Self { + // 1. Let dateSign be DateDurationSign(dateDuration). + // 2. Let timeSign be TimeDurationSign(timeDuration). + // 3. Assert: If dateSign ≠ 0 and timeSign ≠ 0, dateSign = timeSign. + // 4. Return Internal Duration Record { [[Date]]: dateDuration, [[Time]]: timeDuration }. + Self { date, norm } + } + /// Creates a new `NormalizedDurationRecord`. /// /// Equivalent: `CreateNormalizedDurationRecord` & `CombineDateAndNormalizedTimeDuration`. @@ -260,7 +294,7 @@ impl NormalizedDurationRecord { pub(crate) fn from_duration_with_24_hour_days(duration: &Duration) -> TemporalResult { // 1. Let timeDuration be TimeDurationFromComponents(duration.[[Hours]], duration.[[Minutes]], // duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]]). - let normalized_time = NormalizedTimeDuration::from_time_duration(&duration.time); + let normalized_time = NormalizedTimeDuration::from_duration(duration); // 2. Set timeDuration to ! Add24HourDaysToTimeDuration(timeDuration, duration.[[Days]]). let normalized_time = normalized_time.add_days(duration.days())?; // 3. Let dateDuration be ! CreateDateDurationRecord(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], 0). @@ -280,16 +314,18 @@ impl NormalizedDurationRecord { // 1. Let internalDuration be ToInternalDurationRecordWith24HourDays(duration). let internal_duration = self; + // NOTE: days SHOULD be in range of an i64. + // MAX_TIME_DURATION / NS_PER_DAY <= i64::MAX // 2. Let days be truncate(internalDuration.[[Time]] / nsPerDay). - let days = internal_duration.normalized_time_duration().0 / i128::from(NS_PER_DAY); + let days = (internal_duration.normalized_time_duration().0 / i128::from(NS_PER_DAY)) as i64; // 3. Return ! CreateDateDurationRecord(internalDuration.[[Date]].[[Years]], internalDuration.[[Date]].[[Months]], internalDuration.[[Date]].[[Weeks]], days). - Ok(DateDuration::new_unchecked( + DateDuration::new( internal_duration.date().years, internal_duration.date().months, internal_duration.date().weeks, - days.try_into().ok().temporal_unwrap()?, - )) + days, + ) } pub(crate) fn from_date_duration(date: DateDuration) -> TemporalResult { @@ -526,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, @@ -732,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_())?; - - // 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()) @@ -753,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, }) @@ -851,7 +868,7 @@ impl NormalizedDurationRecord { let years = self .date() .years - .checked_add(sign.as_sign_multiplier().into()) + .checked_add(sign.as_sign_multiplier() as i64) .ok_or(TemporalError::range())?; // 2. Let endDuration be ? CreateDateDurationRecord(years, 0, 0, 0). @@ -863,7 +880,7 @@ impl NormalizedDurationRecord { let months = self .date() .months - .checked_add(sign.as_sign_multiplier().into()) + .checked_add(sign.as_sign_multiplier() as i64) .ok_or(TemporalError::range())?; // 2. Let endDuration be ? AdjustDateDurationRecord(duration.[[Date]], 0, 0, months). @@ -878,7 +895,7 @@ impl NormalizedDurationRecord { let weeks = self .date() .weeks - .checked_add(sign.as_sign_multiplier().into()) + .checked_add(sign.as_sign_multiplier() as i64) .ok_or(TemporalError::range())?; // 3. Let endDuration be ? AdjustDateDurationRecord(duration.[[Date]], 0, weeks). diff --git a/src/builtins/core/duration/time.rs b/src/builtins/core/duration/time.rs deleted file mode 100644 index aa2790652..000000000 --- a/src/builtins/core/duration/time.rs +++ /dev/null @@ -1,322 +0,0 @@ -//! An implementation of `TimeDuration` and it's methods. - -use crate::{options::Unit, temporal_assert, Sign, TemporalError, TemporalResult}; - -use super::{duration_sign, is_valid_duration, normalized::NormalizedTimeDuration}; - -use num_traits::Euclid; - -/// `TimeDuration` represents the [Time Duration record][spec] of the `Duration.` -/// -/// These fields are laid out in the [Temporal Proposal][field spec] as 64-bit floating point numbers. -/// -/// [spec]: https://tc39.es/proposal-temporal/#sec-temporal-time-duration-records -/// [field spec]: https://tc39.es/proposal-temporal/#sec-properties-of-temporal-duration-instances -#[non_exhaustive] -#[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)] -pub struct TimeDuration { - /// `TimeDuration`'s internal hour value. - pub hours: i64, - /// `TimeDuration`'s internal minute value. - pub minutes: i64, - /// `TimeDuration`'s internal second value. - pub seconds: i64, - /// `TimeDuration`'s internal millisecond value. - pub milliseconds: i64, - /// `TimeDuration`'s internal microsecond value. - pub microseconds: i128, - /// `TimeDuration`'s internal nanosecond value. - pub nanoseconds: i128, -} -// ==== TimeDuration Private API ==== - -impl TimeDuration { - /// Creates a new `TimeDuration`. - #[must_use] - pub(crate) const fn new_unchecked( - hours: i64, - minutes: i64, - seconds: i64, - milliseconds: i64, - microseconds: i128, - nanoseconds: i128, - ) -> Self { - Self { - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, - } - } - - /// Balances and creates `TimeDuration` from a `NormalizedTimeDuration`. This method will return - /// a tuple (f64, TimeDuration) where f64 is the overflow day value from balancing. - /// - /// Equivalent: `BalanceTimeDuration` - /// - /// # Errors: - /// - Will error if provided duration is invalid - pub(crate) fn from_normalized( - norm: NormalizedTimeDuration, - largest_unit: Unit, - ) -> TemporalResult<(i64, Self)> { - // 1. Let days, hours, minutes, seconds, milliseconds, and microseconds be 0. - let mut days = 0; - let mut hours = 0; - let mut minutes = 0; - let mut seconds = 0; - let mut milliseconds = 0; - let mut microseconds = 0; - - // 2. Let sign be NormalizedTimeDurationSign(norm). - let sign = i64::from(norm.sign() as i8); - // 3. Let nanoseconds be NormalizedTimeDurationAbs(norm).[[TotalNanoseconds]]. - let mut nanoseconds = norm.0.abs(); - - match largest_unit { - // 4. If largestUnit is "year", "month", "week", or "day", then - Unit::Year | Unit::Month | Unit::Week | Unit::Day => { - // a. Set microseconds to floor(nanoseconds / 1000). - // b. Set nanoseconds to nanoseconds modulo 1000. - (microseconds, nanoseconds) = nanoseconds.div_rem_euclid(&1_000); - - // c. Set milliseconds to floor(microseconds / 1000). - // d. Set microseconds to microseconds modulo 1000. - (milliseconds, microseconds) = microseconds.div_rem_euclid(&1_000); - - // e. Set seconds to floor(milliseconds / 1000). - // f. Set milliseconds to milliseconds modulo 1000. - (seconds, milliseconds) = milliseconds.div_rem_euclid(&1_000); - - // g. Set minutes to floor(seconds / 60). - // h. Set seconds to seconds modulo 60. - (minutes, seconds) = seconds.div_rem_euclid(&60); - - // i. Set hours to floor(minutes / 60). - // j. Set minutes to minutes modulo 60. - (hours, minutes) = minutes.div_rem_euclid(&60); - - // k. Set days to floor(hours / 24). - // l. Set hours to hours modulo 24. - (days, hours) = hours.div_rem_euclid(&24); - } - // 5. Else if largestUnit is "hour", then - Unit::Hour => { - // a. Set microseconds to floor(nanoseconds / 1000). - // b. Set nanoseconds to nanoseconds modulo 1000. - (microseconds, nanoseconds) = nanoseconds.div_rem_euclid(&1_000); - - // c. Set milliseconds to floor(microseconds / 1000). - // d. Set microseconds to microseconds modulo 1000. - (milliseconds, microseconds) = microseconds.div_rem_euclid(&1_000); - - // e. Set seconds to floor(milliseconds / 1000). - // f. Set milliseconds to milliseconds modulo 1000. - (seconds, milliseconds) = milliseconds.div_rem_euclid(&1_000); - - // g. Set minutes to floor(seconds / 60). - // h. Set seconds to seconds modulo 60. - (minutes, seconds) = seconds.div_rem_euclid(&60); - - // i. Set hours to floor(minutes / 60). - // j. Set minutes to minutes modulo 60. - (hours, minutes) = minutes.div_rem_euclid(&60); - } - // 6. Else if largestUnit is "minute", then - Unit::Minute => { - // a. Set microseconds to floor(nanoseconds / 1000). - // b. Set nanoseconds to nanoseconds modulo 1000. - (microseconds, nanoseconds) = nanoseconds.div_rem_euclid(&1_000); - - // c. Set milliseconds to floor(microseconds / 1000). - // d. Set microseconds to microseconds modulo 1000. - (milliseconds, microseconds) = microseconds.div_rem_euclid(&1_000); - - // e. Set seconds to floor(milliseconds / 1000). - // f. Set milliseconds to milliseconds modulo 1000. - (seconds, milliseconds) = milliseconds.div_rem_euclid(&1_000); - - // g. Set minutes to floor(seconds / 60). - // h. Set seconds to seconds modulo 60. - (minutes, seconds) = seconds.div_rem_euclid(&60); - } - // 7. Else if largestUnit is "second", then - Unit::Second => { - // a. Set microseconds to floor(nanoseconds / 1000). - // b. Set nanoseconds to nanoseconds modulo 1000. - (microseconds, nanoseconds) = nanoseconds.div_rem_euclid(&1_000); - - // c. Set milliseconds to floor(microseconds / 1000). - // d. Set microseconds to microseconds modulo 1000. - (milliseconds, microseconds) = microseconds.div_rem_euclid(&1_000); - - // e. Set seconds to floor(milliseconds / 1000). - // f. Set milliseconds to milliseconds modulo 1000. - (seconds, milliseconds) = milliseconds.div_rem_euclid(&1_000); - } - // 8. Else if largestUnit is "millisecond", then - Unit::Millisecond => { - // a. Set microseconds to floor(nanoseconds / 1000). - // b. Set nanoseconds to nanoseconds modulo 1000. - (microseconds, nanoseconds) = nanoseconds.div_rem_euclid(&1_000); - - // c. Set milliseconds to floor(microseconds / 1000). - // d. Set microseconds to microseconds modulo 1000. - (milliseconds, microseconds) = microseconds.div_rem_euclid(&1_000); - } - // 9. Else if largestUnit is "microsecond", then - Unit::Microsecond => { - // a. Set microseconds to floor(nanoseconds / 1000). - // b. Set nanoseconds to nanoseconds modulo 1000. - (microseconds, nanoseconds) = nanoseconds.div_rem_euclid(&1_000); - } - // 10. Else, - // a. Assert: largestUnit is "nanosecond". - _ => temporal_assert!(largest_unit == Unit::Nanosecond), - } - - // NOTE(nekevss): `mul_add` is essentially the Rust's implementation of `std::fma()`, so that's handy, but - // this should be tested much further. - // 11. NOTE: When largestUnit is "millisecond", "microsecond", or "nanosecond", milliseconds, microseconds, or - // nanoseconds may be an unsafe integer. In this case, care must be taken when implementing the calculation - // using floating point arithmetic. It can be implemented in C++ using std::fma(). String manipulation will also - // give an exact result, since the multiplication is by a power of 10. - - // NOTE: days may have the potentially to exceed i64 - // 12. Return ! CreateTimeDurationRecord(days × sign, hours × sign, minutes × sign, seconds × sign, milliseconds × sign, microseconds × sign, nanoseconds × sign). - let days = i64::try_from(days).map_err(|_| TemporalError::range())? * sign; - let result = Self::new_unchecked( - hours as i64 * sign, - minutes as i64 * sign, - seconds as i64 * sign, - milliseconds as i64 * sign, - microseconds * sign as i128, - nanoseconds * sign as i128, - ); - - if !is_valid_duration( - 0, - 0, - 0, - days, - result.hours, - result.minutes, - result.seconds, - result.milliseconds, - result.microseconds, - result.nanoseconds, - ) { - return Err(TemporalError::range().with_message("Invalid balance TimeDuration.")); - } - - // TODO: Remove cast below. - Ok((days, result)) - } - - /// Returns this `TimeDuration` as a `NormalizedTimeDuration`. - #[inline] - pub(crate) fn to_normalized(self) -> NormalizedTimeDuration { - NormalizedTimeDuration::from_time_duration(&self) - } - - /// Returns the value of `TimeDuration`'s fields. - #[inline] - #[must_use] - pub(crate) fn fields(&self) -> [i64; 6] { - [ - self.hours, - self.minutes, - self.seconds, - self.milliseconds, - self.microseconds.signum() as i64, - self.nanoseconds.signum() as i64, - ] - } -} - -// ==== TimeDuration's public API ==== - -impl TimeDuration { - /// Creates a new validated `TimeDuration`. - pub fn new( - hours: i64, - minutes: i64, - seconds: i64, - milliseconds: i64, - microseconds: i128, - nanoseconds: i128, - ) -> TemporalResult { - let result = Self::new_unchecked( - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, - ); - if !is_valid_duration( - 0, - 0, - 0, - 0, - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, - ) { - return Err( - TemporalError::range().with_message("Attempted to create an invalid TimeDuration.") - ); - } - Ok(result) - } - - /// Returns a new `TimeDuration` representing the absolute value of the current. - #[inline] - #[must_use] - pub fn abs(&self) -> Self { - Self { - hours: self.hours.abs(), - minutes: self.minutes.abs(), - seconds: self.seconds.abs(), - milliseconds: self.milliseconds.abs(), - microseconds: self.microseconds.abs(), - nanoseconds: self.nanoseconds.abs(), - } - } - - /// Returns a negated `TimeDuration`. - #[inline] - #[must_use] - pub fn negated(&self) -> Self { - Self { - hours: self.hours.saturating_neg(), - minutes: self.minutes.saturating_neg(), - seconds: self.seconds.saturating_neg(), - milliseconds: self.milliseconds.saturating_neg(), - microseconds: self.microseconds.saturating_neg(), - nanoseconds: self.nanoseconds.saturating_neg(), - } - } - - /// Utility function for returning if values in a valid range. - #[inline] - #[must_use] - pub fn is_within_range(&self) -> bool { - self.hours.abs() < 24 - && self.minutes.abs() < 60 - && self.seconds.abs() < 60 - && self.milliseconds.abs() < 1000 - && self.milliseconds.abs() < 1000 - && self.milliseconds.abs() < 1000 - } - - #[inline] - pub fn sign(&self) -> Sign { - duration_sign(&self.fields()) - } -} diff --git a/src/builtins/core/instant.rs b/src/builtins/core/instant.rs index 7f2187a13..e63f4928e 100644 --- a/src/builtins/core/instant.rs +++ b/src/builtins/core/instant.rs @@ -4,9 +4,8 @@ use alloc::string::String; use core::{num::NonZeroU128, str::FromStr}; use crate::{ - builtins::core::{ - duration::TimeDuration, zoneddatetime::nanoseconds_to_formattable_offset_minutes, Duration, - }, + builtins::core::{zoneddatetime::nanoseconds_to_formattable_offset_minutes, Duration}, + error::ErrorMessage, iso::IsoDateTime, options::{ DifferenceOperation, DifferenceSettings, DisplayOffset, ResolvedRoundingOptions, @@ -16,7 +15,7 @@ use crate::{ provider::TimeZoneProvider, rounding::IncrementRounder, unix_time::EpochNanoseconds, - Calendar, TemporalError, TemporalResult, TemporalUnwrap, TimeZone, + Calendar, DateDuration, TemporalError, TemporalResult, TemporalUnwrap, TimeZone, }; use ixdtf::records::UtcOffsetRecordOrZ; @@ -25,7 +24,7 @@ use writeable::Writeable; use super::{ duration::normalized::{NormalizedDurationRecord, NormalizedTimeDuration}, - DateDuration, ZonedDateTime, + ZonedDateTime, }; const NANOSECONDS_PER_SECOND: i64 = 1_000_000_000; @@ -84,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); /// @@ -158,14 +157,12 @@ impl From for Instant { // ==== Private API ==== 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(crate) fn add_to_instant(&self, duration: &TimeDuration) -> TemporalResult { + pub(crate) fn add_to_instant(&self, duration: &NormalizedTimeDuration) -> TemporalResult { // 1. Let result be AddTimeDurationToEpochNanoseconds(timeDuration, epochNanoseconds). - let norm = NormalizedTimeDuration::from_time_duration(duration); - let result = self.epoch_nanoseconds().0 + norm.0; + let result = self.epoch_nanoseconds().0 + duration.0; let ns = EpochNanoseconds::from(result); // 2. If IsValidEpochNanoseconds(result) is false, throw a RangeError exception. ns.check_validity()?; @@ -173,6 +170,23 @@ impl Instant { Ok(Self::from(ns)) } + /// 8.5.10 AddDurationToInstant ( operation, instant, temporalDurationLike ) + pub(crate) fn add_duration_to_instant(&self, duration: &Duration) -> TemporalResult { + // 3. Let largestUnit be DefaultTemporalLargestUnit(duration). + let largest_unit = duration.default_largest_unit(); + // 4. If TemporalUnitCategory(largestUnit) is date, throw a RangeError exception. + if largest_unit.is_date_unit() { + // TODO: Add enum + return Err(TemporalError::range().with_enum(ErrorMessage::LargestUnitCannotBeDateUnit)); + } + // 5. Let internalDuration be ToInternalDurationRecordWith24HourDays(duration). + let internal_duration = + NormalizedDurationRecord::from_duration_with_24_hour_days(duration)?; + // 6. Let ns be ? AddInstant(instant.[[EpochNanoseconds]], internalDuration.[[Time]]). + // 7. Return ! CreateTemporalInstant(ns). + self.add_to_instant(&internal_duration.normalized_time_duration()) + } + /// `temporal_rs` equivalent of `DifferenceInstant` pub(crate) fn diff_instant_internal( &self, @@ -212,7 +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]]). @@ -333,44 +347,24 @@ impl Instant { /// Adds a `Duration` to the current `Instant`, returning an error if the `Duration` /// contains a `DateDuration`. #[inline] - pub fn add(&self, duration: Duration) -> TemporalResult { - if !duration.is_time_duration() { - return Err(TemporalError::range() - .with_message("DateDuration values cannot be added to instant.")); - } - self.add_time_duration(duration.time()) - } - - /// Adds a `TimeDuration` to `Instant`. - #[inline] - pub fn add_time_duration(&self, duration: &TimeDuration) -> TemporalResult { - self.add_to_instant(duration) + pub fn add(&self, duration: &Duration) -> TemporalResult { + self.add_duration_to_instant(duration) } /// Subtract a `Duration` to the current `Instant`, returning an error if the `Duration` /// contains a `DateDuration`. #[inline] - pub fn subtract(&self, duration: Duration) -> TemporalResult { - if !duration.is_time_duration() { - return Err(TemporalError::range() - .with_message("DateDuration values cannot be added to instant.")); - } - self.subtract_time_duration(duration.time()) - } - - /// Subtracts a `TimeDuration` to `Instant`. - #[inline] - pub fn subtract_time_duration(&self, duration: &TimeDuration) -> TemporalResult { - self.add_to_instant(&duration.negated()) + pub fn subtract(&self, duration: &Duration) -> TemporalResult { + self.add_duration_to_instant(&duration.negated()) } - /// Returns a `TimeDuration` representing the duration since provided `Instant` + /// Returns a `Duration` representing the duration since provided `Instant` #[inline] pub fn since(&self, other: &Self, settings: DifferenceSettings) -> TemporalResult { self.diff_instant(DifferenceOperation::Since, other, settings) } - /// Returns a `TimeDuration` representing the duration until provided `Instant` + /// Returns a `Duration` representing the duration until provided `Instant` #[inline] pub fn until(&self, other: &Self, settings: DifferenceSettings) -> TemporalResult { self.diff_instant(DifferenceOperation::Until, other, settings) @@ -463,11 +457,14 @@ mod tests { use core::str::FromStr; + use bnum::cast::CastFrom; + use crate::{ - builtins::core::{duration::TimeDuration, Instant}, + builtins::{core::Instant, duration::duration_sign}, options::{DifferenceSettings, RoundingMode, Unit}, + primitive::{U48, U56, U80, U88}, unix_time::EpochNanoseconds, - NS_MAX_INSTANT, NS_MIN_INSTANT, + Duration, NS_MAX_INSTANT, NS_MIN_INSTANT, }; #[test] @@ -568,20 +565,31 @@ 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( + duration_sign(&[ + expected.0, + expected.1, + expected.2, + expected.3, + expected.4 as i64, + expected.5 as i64 + ]), + 0, + 0, + 0, + 0u8.into(), + 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()), ) - }; + ); + }; let earlier = Instant::try_new( 217_178_610_123_456_789, /* 1976-11-18T15:23:30.123456789Z */ @@ -595,40 +603,40 @@ mod tests { let positive_result = earlier .until(&later, init_diff_setting(Unit::Hour)) .unwrap(); - assert_time_duration(positive_result.time(), (376436, 0, 0, 0, 0, 0)); + assert_duration(&positive_result, (376436, 0, 0, 0, 0, 0)); let negative_result = later .until(&earlier, init_diff_setting(Unit::Hour)) .unwrap(); - assert_time_duration(negative_result.time(), (-376435, 0, 0, 0, 0, 0)); + assert_duration(&negative_result, (-376435, 0, 0, 0, 0, 0)); let positive_result = earlier .until(&later, init_diff_setting(Unit::Minute)) .unwrap(); - assert_time_duration(positive_result.time(), (376435, 24, 0, 0, 0, 0)); + assert_duration(&positive_result, (376435, 24, 0, 0, 0, 0)); let negative_result = later .until(&earlier, init_diff_setting(Unit::Minute)) .unwrap(); - assert_time_duration(negative_result.time(), (-376435, -23, 0, 0, 0, 0)); + assert_duration(&negative_result, (-376435, -23, 0, 0, 0, 0)); // ... Skip to lower units ... let positive_result = earlier .until(&later, init_diff_setting(Unit::Microsecond)) .unwrap(); - assert_time_duration(positive_result.time(), (376435, 23, 8, 148, 530, 0)); + assert_duration(&positive_result, (376435, 23, 8, 148, 530, 0)); let negative_result = later .until(&earlier, init_diff_setting(Unit::Microsecond)) .unwrap(); - assert_time_duration(negative_result.time(), (-376435, -23, -8, -148, -529, 0)); + assert_duration(&negative_result, (-376435, -23, -8, -148, -529, 0)); let positive_result = earlier .until(&later, init_diff_setting(Unit::Nanosecond)) .unwrap(); - assert_time_duration(positive_result.time(), (376435, 23, 8, 148, 529, 500)); + assert_duration(&positive_result, (376435, 23, 8, 148, 529, 500)); let negative_result = later .until(&earlier, init_diff_setting(Unit::Nanosecond)) .unwrap(); - assert_time_duration(negative_result.time(), (-376435, -23, -8, -148, -529, -500)); + assert_duration(&negative_result, (-376435, -23, -8, -148, -529, -500)); } #[test] @@ -642,20 +650,31 @@ 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( + duration_sign(&[ + expected.0, + expected.1, + expected.2, + expected.3, + expected.4 as i64, + expected.5 as i64 + ]), + 0, + 0, + 0, + 0u8.into(), + 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()), ) - }; + ); + }; let earlier = Instant::try_new( 217_178_610_123_456_789, /* 1976-11-18T15:23:30.123456789Z */ @@ -669,40 +688,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 @@ -718,7 +737,7 @@ mod tests { let instant = Instant::from_str("1969-12-25T12:23:45.678901234Z").unwrap(); let one = instant .subtract( - Duration::from_partial_duration(PartialDuration { + &Duration::from_partial_duration(PartialDuration { hours: Some(240.into()), nanoseconds: Some(800.into()), ..Default::default() @@ -728,7 +747,7 @@ mod tests { .unwrap(); let two = instant .add( - Duration::from_partial_duration(PartialDuration { + &Duration::from_partial_duration(PartialDuration { hours: Some(240.into()), nanoseconds: Some(800.into()), ..Default::default() @@ -738,7 +757,7 @@ mod tests { .unwrap(); let three = two .subtract( - Duration::from_partial_duration(PartialDuration { + &Duration::from_partial_duration(PartialDuration { hours: Some(480.into()), nanoseconds: Some(1600.into()), ..Default::default() @@ -748,7 +767,7 @@ mod tests { .unwrap(); let four = one .add( - Duration::from_partial_duration(PartialDuration { + &Duration::from_partial_duration(PartialDuration { hours: Some(480.into()), nanoseconds: Some(1600.into()), ..Default::default() diff --git a/src/builtins/core/mod.rs b/src/builtins/core/mod.rs index f827add13..a4ab8776c 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 d381bf46b..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::TimeDuration, 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; @@ -264,17 +264,43 @@ impl PlainTime { (day, Self::new_unchecked(balance_result)) } - /// Adds a `TimeDuration` to the current `Time`. + /// Adds a `Duration` to the current `Time`. /// /// Spec Equivalent: `AddDurationToOrSubtractDurationFromPlainTime`. - pub(crate) fn add_to_time(&self, duration: &TimeDuration) -> TemporalResult { + pub(crate) fn add_to_time(&self, duration: &Duration) -> TemporalResult { let (_, result) = IsoTime::balance( - i64::from(self.hour()).saturating_add(duration.hours), - i64::from(self.minute()).saturating_add(duration.minutes), - i64::from(self.second()).saturating_add(duration.seconds), - i64::from(self.millisecond()).saturating_add(duration.milliseconds), - i128::from(self.microsecond()).saturating_add(duration.microseconds), - i128::from(self.nanosecond()).saturating_add(duration.nanoseconds), + i64::from(self.hour()) + .saturating_add(duration.hours.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` @@ -301,29 +327,19 @@ impl PlainTime { Unit::Hour, Unit::Nanosecond, )?; - - // 5. Let norm be ! DifferenceTime(temporalTime.[[ISOHour]], temporalTime.[[ISOMinute]], - // temporalTime.[[ISOSecond]], temporalTime.[[ISOMillisecond]], temporalTime.[[ISOMicrosecond]], - // temporalTime.[[ISONanosecond]], other.[[ISOHour]], other.[[ISOMinute]], other.[[ISOSecond]], - // other.[[ISOMillisecond]], other.[[ISOMicrosecond]], other.[[ISONanosecond]]). - let mut normalized_time = self.iso.diff(&other.iso).to_normalized(); - - // 6. If settings.[[SmallestUnit]] is not "nanosecond" or settings.[[RoundingIncrement]] ≠ 1, then - if resolved.smallest_unit != Unit::Nanosecond - || resolved.increment != RoundingIncrement::ONE - { - // a. Let roundRecord be ! RoundDuration(0, 0, 0, 0, norm, settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[RoundingMode]]). - // b. Set norm to roundRecord.[[NormalizedDuration]].[[NormalizedTime]]. - normalized_time = normalized_time.round(resolved)?; - }; - - // 7. Let result be BalanceTimeDuration(norm, settings.[[LargestUnit]]). - let result = TimeDuration::from_normalized(normalized_time, resolved.largest_unit)?.1; - - // 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]]). + // 4. Let timeDuration be DifferenceTime(temporalTime.[[Time]], other.[[Time]]). + let mut normalized_time = self.iso.diff(&other.iso); + // 5. Set timeDuration to ! RoundTimeDuration(timeDuration, settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[RoundingMode]]). + normalized_time = normalized_time.round(resolved)?; + // 6. Let duration be CombineDateAndTimeDuration(ZeroDateDuration(), timeDuration). + let duration = NormalizedDurationRecord::combine(DateDuration::default(), normalized_time); + // 7. Let result be ! TemporalDurationFromInternal(duration, settings.[[LargestUnit]]). + let result = Duration::from_internal(duration, resolved.largest_unit)?; + // 8. If operation is since, set result to CreateNegatedTemporalDuration(result). + // 9. Return result. match op { - DifferenceOperation::Until => Ok(Duration::from(result)), - DifferenceOperation::Since => Ok(Duration::from(result.negated())), + DifferenceOperation::Until => Ok(result), + DifferenceOperation::Since => Ok(result.negated()), } } } @@ -535,23 +551,25 @@ impl PlainTime { /// Add a `Duration` to the current `Time`. pub fn add(&self, duration: &Duration) -> TemporalResult { - self.add_time_duration(duration.time()) + self.add_time_duration(duration) } - /// Adds a `TimeDuration` to the current `Time`. + // TODO: deprecate + /// Adds a `Duration` 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) } /// Subtract a `Duration` to the current `Time`. pub fn subtract(&self, duration: &Duration) -> TemporalResult { - self.subtract_time_duration(duration.time()) + self.subtract_time_duration(&duration.negated()) } - /// Adds a `TimeDuration` to the current `Time`. + // TODO: deprecate + /// Adds a `Duration` 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/timezone.rs b/src/builtins/core/timezone.rs index 5b79cf4f4..dea136727 100644 --- a/src/builtins/core/timezone.rs +++ b/src/builtins/core/timezone.rs @@ -22,6 +22,7 @@ use crate::{ unix_time::EpochNanoseconds, TemporalError, TemporalResult, TemporalUnwrap, ZonedDateTime, }; +// use crate::{Calendar, DateDuration, Sign}; const NS_IN_S: i64 = 1_000_000_000; const NS_IN_MIN: i64 = 60_000_000_000; diff --git a/src/builtins/core/year_month.rs b/src/builtins/core/year_month.rs index f8260362b..d6409b382 100644 --- a/src/builtins/core/year_month.rs +++ b/src/builtins/core/year_month.rs @@ -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 8a57ac0c5..eaa94697b 100644 --- a/src/builtins/core/zoneddatetime.rs +++ b/src/builtins/core/zoneddatetime.rs @@ -328,23 +328,25 @@ impl ZonedDateTime { } } - pub(crate) fn add_as_instant( + pub(crate) fn add_zoned_date_time( &self, - duration: &Duration, + duration: NormalizedDurationRecord, overflow: ArithmeticOverflow, provider: &impl TimeZoneProvider, ) -> TemporalResult { // 1. If DateDurationSign(duration.[[Date]]) = 0, then if duration.date().sign() == Sign::Zero { // a. Return ? AddInstant(epochNanoseconds, duration.[[Time]]). - return self.instant.add_to_instant(duration.time()); + return self + .instant + .add_to_instant(&duration.normalized_time_duration()); } // 2. Let isoDateTime be GetISODateTimeFor(timeZone, epochNanoseconds). let iso_datetime = self.tz.get_iso_datetime_for(&self.instant, provider)?; // 3. Let addedDate be ? CalendarDateAdd(calendar, isoDateTime.[[ISODate]], duration.[[Date]], overflow). - let added_date = self - .calendar() - .date_add(&iso_datetime.date, duration.date(), overflow)?; + let added_date = + self.calendar() + .date_add(&iso_datetime.date, &duration.date(), overflow)?; // 4. Let intermediateDateTime be CombineISODateAndTimeRecord(addedDate, isoDateTime.[[Time]]). let intermediate = IsoDateTime::new_unchecked(added_date.iso, iso_datetime.time); // 5. If ISODateTimeWithinLimits(intermediateDateTime) is false, throw a RangeError exception. @@ -361,7 +363,7 @@ impl ZonedDateTime { )?; // 7. Return ? AddInstant(intermediateNs, duration.[[Time]]). - Instant::from(intermediate_ns).add_to_instant(duration.time()) + Instant::from(intermediate_ns).add_to_instant(&duration.normalized_time_duration()) } /// Adds a duration to the current `ZonedDateTime`, returning the resulting `ZonedDateTime`. @@ -381,8 +383,9 @@ impl ZonedDateTime { // 5. Let calendar be zonedDateTime.[[Calendar]]. // 6. Let timeZone be zonedDateTime.[[TimeZone]]. // 7. Let internalDuration be ToInternalDurationRecord(duration). + let internal_duration = duration.to_internal_duration_record(); // 8. Let epochNanoseconds be ? AddZonedDateTime(zonedDateTime.[[EpochNanoseconds]], timeZone, calendar, internalDuration, overflow). - let epoch_ns = self.add_as_instant(duration, overflow, provider)?; + let epoch_ns = self.add_zoned_date_time(internal_duration, overflow, provider)?; // 9. Return ! CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar). Ok(Self::new_unchecked( epoch_ns, @@ -535,7 +538,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`. @@ -568,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 { @@ -598,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/error.rs b/src/error.rs index 2161a605e..c9f791986 100644 --- a/src/error.rs +++ b/src/error.rs @@ -171,6 +171,7 @@ pub(crate) enum ErrorMessage { InstantOutOfRange, IntermediateDateTimeOutOfRange, ZDTOutOfDayBounds, + LargestUnitCannotBeDateUnit, // Numerical errors NumberNotFinite, @@ -212,6 +213,7 @@ impl ErrorMessage { "Intermediate ISO datetime was not within a valid range." } Self::ZDTOutOfDayBounds => "ZonedDateTime is outside the expected day bounds", + Self::LargestUnitCannotBeDateUnit => "Largest unit cannot be a date unit", Self::NumberNotFinite => "number value is not a finite value.", Self::NumberNotIntegral => "value must be integral.", Self::NumberNotPositive => "integer must be positive.", diff --git a/src/iso.rs b/src/iso.rs index 64cba9cb9..c53376d5d 100644 --- a/src/iso.rs +++ b/src/iso.rs @@ -28,18 +28,15 @@ use ixdtf::records::TimeRecord; use crate::{ builtins::core::{ calendar::Calendar, - duration::{ - normalized::{NormalizedDurationRecord, NormalizedTimeDuration}, - DateDuration, TimeDuration, - }, - Duration, PartialTime, PlainDate, + duration::normalized::{NormalizedDurationRecord, NormalizedTimeDuration}, + PartialTime, PlainDate, }, error::{ErrorMessage, TemporalError}, options::{ArithmeticOverflow, ResolvedRoundingOptions, Unit}, rounding::IncrementRounder, 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}; @@ -158,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_date(&duration, overflow)?; - - // 7. Return ISO Date-Time Record { [[Year]]: addedDate.[[ISOYear]], [[Month]]: addedDate.[[ISOMonth]], - // [[Day]]: addedDate.[[ISODay]], [[Hour]]: timeResult.[[Hour]], [[Minute]]: timeResult.[[Minute]], - // [[Second]]: timeResult.[[Second]], [[Millisecond]]: timeResult.[[Millisecond]], - // [[Microsecond]]: timeResult.[[Microsecond]], [[Nanosecond]]: timeResult.[[Nanosecond]] }. - Self::new(added_date.iso, t_result.1) - } - pub(crate) fn round(&self, resolved_options: ResolvedRoundingOptions) -> TemporalResult { let (rounded_days, rounded_time) = self.time.round(resolved_options)?; let balance_result = IsoDate::try_balance( @@ -207,6 +165,7 @@ impl IsoDateTime { Self::new(balance_result, rounded_time) } + // TODO: UPDATE TO CURRENT SPECIFICATION // TODO: Determine whether to provide an options object...seems duplicative. /// 5.5.11 DifferenceISODateTime ( y1, mon1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, d2, h2, min2, s2, ms2, mus2, ns2, calendarRec, largestUnit, options ) pub(crate) fn diff( @@ -221,8 +180,7 @@ impl IsoDateTime { // is not "day", CalendarMethodsRecordHasLookedUp(calendarRec, date-until) is true. // 4. Let timeDuration be DifferenceTime(h1, min1, s1, ms1, mus1, ns1, h2, min2, s2, ms2, mus2, ns2). - let mut time_duration = - NormalizedTimeDuration::from_time_duration(&self.time.diff(&other.time)); + let mut time_duration = self.time.diff(&other.time); // 5. Let timeSign be NormalizedTimeDurationSign(timeDuration). let time_sign = time_duration.sign() as i8; @@ -423,8 +381,9 @@ impl IsoDate { Self::new_with_overflow(intermediate.0, intermediate.1, self.day, overflow)?; // 5. Set days to days + 7 × weeks. - let additional_days = duration.days + (7 * duration.weeks); // Verify - // 6. Let d be intermediate.[[Day]] + days. + let additional_days = duration.days + (7 * duration.weeks); + + // 6. Let d be intermediate.[[Day]] + days. let intermediate_days = i64::from(intermediate.day) + additional_days; // 7. Return BalanceISODate(intermediate.[[Year]], intermediate.[[Month]], d). @@ -732,8 +691,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 `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); @@ -741,7 +700,7 @@ impl IsoTime { let mis = i128::from(other.microsecond) - i128::from(self.microsecond); let ns = i128::from(other.nanosecond) - i128::from(self.nanosecond); - TimeDuration::new_unchecked(h, m, s, ms, mis, ns) + NormalizedTimeDuration::from_components(h, m, s, ms, mis, ns) } // NOTE (nekevss): Specification seemed to be off / not entirely working, so the below was adapted from the diff --git a/src/lib.rs b/src/lib.rs index 379ef101a..cd4aad0a7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -188,7 +188,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. @@ -247,14 +247,24 @@ 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. 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 { diff --git a/src/primitive.rs b/src/primitive.rs index bc7cfe302..2a03acd83 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 = BUintD16<3>; // 48 / 16 = 3 +pub type U40 = BUintD8<5>; // 40 / 8 = 5 + #[cfg(test)] mod tests { use super::FiniteF64; diff --git a/temporal_capi/bindings/c/Duration.h b/temporal_capi/bindings/c/Duration.h index a7a648ca5..6c6ab0765 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" @@ -30,6 +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); +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); + 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); @@ -41,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 1157951a2..af931a049 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 75fa84cda..584fc38dd 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 e3e4c80bd..393fa3283 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,6 +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); + 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); inline static diplomat::result, temporal_rs::TemporalError> from_utf8(std::string_view s); @@ -53,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 0a7900e1e..ceb866911 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" @@ -33,6 +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); + 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); + 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); @@ -44,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); @@ -130,6 +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))); } +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))); +} + 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))); @@ -150,14 +156,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 3767533a8..b62c080a0 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 dae39a6f1..6df506f3f 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); @@ -107,7 +106,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))); @@ -119,7 +118,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 d9c4af286..4b4a5c09a 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 d19747434..08f4542b3 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); @@ -188,13 +187,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 diff --git a/temporal_capi/src/duration.rs b/temporal_capi/src/duration.rs index 690361ac2..2a9bd086c 100644 --- a/temporal_capi/src/duration.rs +++ b/temporal_capi/src/duration.rs @@ -20,10 +20,6 @@ pub mod ffi { #[diplomat::opaque] pub struct Duration(pub(crate) temporal_rs::Duration); - #[diplomat::opaque] - #[diplomat::transparent_convert] - pub struct TimeDuration(pub(crate) temporal_rs::TimeDuration); - #[diplomat::opaque] #[diplomat::transparent_convert] pub struct DateDuration(pub(crate) temporal_rs::DateDuration); @@ -56,42 +52,6 @@ pub mod ffi { } } - impl TimeDuration { - pub fn try_new( - hours: i64, - minutes: i64, - seconds: i64, - milliseconds: i64, - microseconds: f64, - nanoseconds: f64, - ) -> Result, TemporalError> { - temporal_rs::TimeDuration::new( - hours, - minutes, - seconds, - milliseconds, - i128::from_f64(microseconds).ok_or(TemporalError::range("μs out of range"))?, - i128::from_f64(nanoseconds).ok_or(TemporalError::range("ns out of range"))?, - ) - .map(|x| Box::new(TimeDuration(x))) - .map_err(Into::into) - } - - pub fn abs(&self) -> Box { - Box::new(Self(self.0.abs())) - } - pub fn negated(&self) -> Box { - Box::new(Self(self.0.negated())) - } - - pub fn is_within_range(&self) -> bool { - self.0.is_within_range() - } - pub fn sign(&self) -> Sign { - self.0.sign().into() - } - } - impl DateDuration { pub fn try_new( years: i64, @@ -171,6 +131,11 @@ pub mod ffi { .map_err(Into::into) } + pub fn from_day_and_time(day: i64, time: &Duration) -> Result, TemporalError> { + Ok(Box::new(Duration( + temporal_rs::Duration::try_from_day_and_time(day, &time.0)?, + ))) + } pub fn from_partial_duration(partial: PartialDuration) -> Result, TemporalError> { temporal_rs::Duration::from_partial_duration(partial.try_into()?) .map(|x| Box::new(Duration(x))) @@ -195,16 +160,13 @@ 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()) + 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 // 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() diff --git a/temporal_capi/src/instant.rs b/temporal_capi/src/instant.rs index 96af06bce..c0e919f69 100644 --- a/temporal_capi/src/instant.rs +++ b/temporal_capi/src/instant.rs @@ -2,7 +2,7 @@ #[diplomat::abi_rename = "temporal_rs_{0}"] #[diplomat::attr(auto, namespace = "temporal_rs")] pub mod ffi { - use crate::duration::ffi::{Duration, TimeDuration}; + use crate::duration::ffi::Duration; use crate::error::ffi::TemporalError; use crate::options::ffi::{DifferenceSettings, RoundingOptions}; #[cfg(feature = "compiled_data")] @@ -72,31 +72,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) } - pub fn add_time_duration( - &self, - duration: &TimeDuration, - ) -> Result, TemporalError> { + // TODO: deprecate? + pub fn add_time_duration(&self, duration: &Duration) -> Result, TemporalError> { self.0 - .add_time_duration(&duration.0) + .add(&duration.0) .map(|c| Box::new(Self(c))) .map_err(Into::into) } pub fn subtract(&self, duration: &Duration) -> Result, TemporalError> { self.0 - .subtract(duration.0) + .subtract(&duration.0) .map(|c| Box::new(Self(c))) .map_err(Into::into) } + // TODO: deprecate? pub fn subtract_time_duration( &self, - duration: &TimeDuration, + duration: &Duration, ) -> Result, TemporalError> { self.0 - .subtract_time_duration(&duration.0) + .subtract(&duration.0) .map(|c| Box::new(Self(c))) .map_err(Into::into) } diff --git a/temporal_capi/src/plain_time.rs b/temporal_capi/src/plain_time.rs index a74ca4ed1..68767cc7e 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) diff --git a/tools/depcheck/src/main.rs b/tools/depcheck/src/main.rs index d1e41d226..092616ab2 100644 --- a/tools/depcheck/src/main.rs +++ b/tools/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", 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 {