diff --git a/src/builtins/core/date.rs b/src/builtins/core/date.rs index d99ac3046..6b73eb476 100644 --- a/src/builtins/core/date.rs +++ b/src/builtins/core/date.rs @@ -279,15 +279,15 @@ impl PlainDate { resolved.smallest_unit == Unit::Day && resolved.increment.get() == 1; // 12. If roundingGranularityIsNoop is false, then if !rounding_granularity_is_noop { + let iso_date_time = IsoDateTime::new_unchecked(self.iso, IsoTime::default()); + let origin_epoch_ns = iso_date_time.as_nanoseconds(); // a. Let destEpochNs be GetUTCEpochNanoseconds(other.[[ISOYear]], other.[[ISOMonth]], other.[[ISODay]], 0, 0, 0, 0, 0, 0). let dest_epoch_ns = other.iso.as_nanoseconds(); // b. Let dateTime be ISO Date-Time Record { [[Year]]: temporalDate.[[ISOYear]], [[Month]]: temporalDate.[[ISOMonth]], [[Day]]: temporalDate.[[ISODay]], [[Hour]]: 0, [[Minute]]: 0, [[Second]]: 0, [[Millisecond]]: 0, [[Microsecond]]: 0, [[Nanosecond]]: 0 }. - let dt = PlainDateTime::new_unchecked( - IsoDateTime::new_unchecked(self.iso, IsoTime::default()), - self.calendar.clone(), - ); + let dt = PlainDateTime::new_unchecked(iso_date_time, self.calendar.clone()); // c. Set duration to ? RoundRelativeDuration(duration, destEpochNs, dateTime, calendarRec, unset, settings.[[LargestUnit]], settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[RoundingMode]]). duration = duration.round_relative_duration( + origin_epoch_ns, dest_epoch_ns.0, &dt, Option::<(&TimeZone, &NeverProvider)>::None, diff --git a/src/builtins/core/datetime.rs b/src/builtins/core/datetime.rs index 819a278a9..a412e11ec 100644 --- a/src/builtins/core/datetime.rs +++ b/src/builtins/core/datetime.rs @@ -312,6 +312,7 @@ impl PlainDateTime { let dest_epoch_ns = other.iso.as_nanoseconds(); // 6. Return ? RoundRelativeDuration(diff, destEpochNs, isoDateTime1, unset, calendar, largestUnit, roundingIncrement, smallestUnit, roundingMode). diff.round_relative_duration( + self.iso.as_nanoseconds(), dest_epoch_ns.0, self, Option::<(&TimeZone, &NeverProvider)>::None, @@ -336,10 +337,12 @@ impl PlainDateTime { if unit == Unit::Nanosecond { return FiniteF64::try_from(diff.normalized_time_duration().0); } + let origin_epoch_ns = self.iso.as_nanoseconds(); // 5. Let destEpochNs be GetUTCEpochNanoseconds(isoDateTime2). let dest_epoch_ns = other.iso.as_nanoseconds(); // 6. Return ? TotalRelativeDuration(diff, destEpochNs, isoDateTime1, unset, calendar, unit). diff.total_relative_duration( + origin_epoch_ns, dest_epoch_ns.0, self, Option::<(&TimeZone, &NeverProvider)>::None, diff --git a/src/builtins/core/duration/normalized.rs b/src/builtins/core/duration/normalized.rs index 62bcb351a..177a95ee3 100644 --- a/src/builtins/core/duration/normalized.rs +++ b/src/builtins/core/duration/normalized.rs @@ -3,6 +3,7 @@ use core::{cmp, num::NonZeroU128, ops::Add}; use num_traits::AsPrimitive; +use timezone_provider::epoch_nanoseconds::EpochNanoseconds; use crate::{ builtins::core::{timezone::TimeZone, PlainDate, PlainDateTime}, @@ -370,6 +371,7 @@ impl InternalDurationRecord { fn nudge_calendar_unit( &self, sign: Sign, + origin_epoch_ns: EpochNanoseconds, dest_epoch_ns: i128, dt: &PlainDateTime, tz: Option<(&TimeZone, &impl TimeZoneProvider)>, // ??? @@ -562,33 +564,43 @@ impl InternalDurationRecord { _ => unreachable!(), // TODO: potentially reject with range error? }; - // 7. Let start be ? CalendarDateAdd(calendar, isoDateTime.[[ISODate]], startDuration, constrain). - let start = - dt.calendar() - .date_add(&dt.iso.date, &start_duration, ArithmeticOverflow::Constrain)?; + let start_epoch_ns = if r1 == 0 { + origin_epoch_ns + } else { + // 7. Let start be ? CalendarDateAdd(calendar, isoDateTime.[[ISODate]], startDuration, constrain). + let start = dt.calendar().date_add( + &dt.iso.date, + &start_duration, + ArithmeticOverflow::Constrain, + )?; + // 9. Let startDateTime be CombineISODateAndTimeRecord(start, isoDateTime.[[Time]]). + let start_date_time = IsoDateTime::new_unchecked(start.iso, dt.iso.time); + if let Some((tz, provider)) = tz { + tz.get_epoch_nanoseconds_for(start_date_time, Disambiguation::Compatible, provider)? + .ns + } else { + start_date_time.as_nanoseconds() + } + }; + // 8. Let end be ? CalendarDateAdd(calendar, isoDateTime.[[ISODate]], endDuration, constrain). let end = dt.calendar() .date_add(&dt.iso.date, &end_duration, ArithmeticOverflow::Constrain)?; - // 9. Let startDateTime be CombineISODateAndTimeRecord(start, isoDateTime.[[Time]]). - let start = IsoDateTime::new_unchecked(start.iso, dt.iso.time); // 10. Let endDateTime be CombineISODateAndTimeRecord(end, isoDateTime.[[Time]]). let end = IsoDateTime::new_unchecked(end.iso, dt.iso.time); // 12. Else, - let (start_epoch_ns, end_epoch_ns) = if let Some((tz, provider)) = tz { + let end_epoch_ns = if let Some((tz, provider)) = tz { // a. Let startEpochNs be ? GetEpochNanosecondsFor(timeZone, startDateTime, compatible). // b. Let endEpochNs be ? GetEpochNanosecondsFor(timeZone, endDateTime, compatible). - let start_epoch_ns = - tz.get_epoch_nanoseconds_for(start, Disambiguation::Compatible, provider)?; - let end_epoch_ns = - tz.get_epoch_nanoseconds_for(end, Disambiguation::Compatible, provider)?; - (start_epoch_ns.ns, end_epoch_ns.ns) + tz.get_epoch_nanoseconds_for(end, Disambiguation::Compatible, provider)? + .ns // 11. If timeZoneRec is unset, then } else { // a. Let startEpochNs be GetUTCEpochNanoseconds(start.[[Year]], start.[[Month]], start.[[Day]], start.[[Hour]], start.[[Minute]], start.[[Second]], start.[[Millisecond]], start.[[Microsecond]], start.[[Nanosecond]]). // b. Let endEpochNs be GetUTCEpochNanoseconds(end.[[Year]], end.[[Month]], end.[[Day]], end.[[Hour]], end.[[Minute]], end.[[Second]], end.[[Millisecond]], end.[[Microsecond]], end.[[Nanosecond]]). - (start.as_nanoseconds(), end.as_nanoseconds()) + end.as_nanoseconds() }; // TODO: look into handling asserts @@ -947,6 +959,7 @@ impl InternalDurationRecord { #[inline] pub(crate) fn round_relative_duration( &self, + origin_epoch_ns: EpochNanoseconds, dest_epoch_ns: i128, dt: &PlainDateTime, time_zone: Option<(&TimeZone, &impl TimeZoneProvider)>, @@ -967,7 +980,14 @@ impl InternalDurationRecord { let nudge_result = if irregular_length_unit { // a. Let record be ? NudgeToCalendarUnit(sign, duration, destEpochNs, isoDateTime, timeZone, calendar, increment, smallestUnit, roundingMode). // b. Let nudgeResult be record.[[NudgeResult]]. - duration.nudge_calendar_unit(sign, dest_epoch_ns, dt, time_zone, options)? + duration.nudge_calendar_unit( + sign, + origin_epoch_ns, + dest_epoch_ns, + dt, + time_zone, + options, + )? } else if let Some((time_zone, time_zone_provider)) = time_zone { // 6. Else if timeZone is not unset, then // a. Let nudgeResult be ? NudgeToZonedTime(sign, duration, isoDateTime, timeZone, calendar, increment, smallestUnit, roundingMode). @@ -1005,6 +1025,7 @@ impl InternalDurationRecord { // 7.5.38 TotalRelativeDuration ( duration, destEpochNs, isoDateTime, timeZone, calendar, unit ) pub(crate) fn total_relative_duration( &self, + origin_epoch_ns: EpochNanoseconds, dest_epoch_ns: i128, dt: &PlainDateTime, tz: Option<(&TimeZone, &impl TimeZoneProvider)>, @@ -1017,6 +1038,7 @@ impl InternalDurationRecord { // b. Let record be ? NudgeToCalendarUnit(sign, duration, destEpochNs, isoDateTime, timeZone, calendar, 1, unit, trunc). let record = self.nudge_calendar_unit( sign, + origin_epoch_ns, dest_epoch_ns, dt, tz, diff --git a/src/builtins/core/year_month.rs b/src/builtins/core/year_month.rs index af1ac6c7c..2b58c91d4 100644 --- a/src/builtins/core/year_month.rs +++ b/src/builtins/core/year_month.rs @@ -330,6 +330,7 @@ impl PlainYearMonth { let dest_epoch_ns = target_iso_date_time.as_nanoseconds(); // d. Set duration to ? RoundRelativeDuration(duration, destEpochNs, isoDateTime, unset, calendar, resolved.[[LargestUnit]], resolved.[[RoundingIncrement]], resolved.[[SmallestUnit]], resolved.[[RoundingMode]]). duration = duration.round_relative_duration( + iso_date_time.as_nanoseconds(), dest_epoch_ns.as_i128(), &PlainDateTime::new_unchecked(iso_date_time, self.calendar.clone()), Option::<(&TimeZone, &NeverProvider)>::None, diff --git a/src/builtins/core/zoneddatetime.rs b/src/builtins/core/zoneddatetime.rs index fa99126c8..f48552f2a 100644 --- a/src/builtins/core/zoneddatetime.rs +++ b/src/builtins/core/zoneddatetime.rs @@ -399,6 +399,7 @@ impl ZonedDateTime { let iso = self.get_iso_datetime(); // 5. Return ? RoundRelativeDuration(difference, ns2, dateTime, timeZone, calendar, largestUnit, roundingIncrement, smallestUnit, roundingMode). diff.round_relative_duration( + *self.epoch_nanoseconds(), other.epoch_nanoseconds().as_i128(), &PlainDateTime::new_unchecked(iso, self.calendar().clone()), Some((self.timezone(), provider)), @@ -430,6 +431,7 @@ impl ZonedDateTime { let iso = self.get_iso_datetime(); // 4. Return ? TotalRelativeDuration(difference, ns2, dateTime, timeZone, calendar, unit). diff.total_relative_duration( + *self.epoch_nanoseconds(), other.epoch_nanoseconds().as_i128(), &PlainDateTime::new_unchecked(iso, self.calendar().clone()), Some((self.timezone(), provider)),