Skip to content

Commit

Permalink
Implement Duration normalization - Part 1 (#20)
Browse files Browse the repository at this point in the history
* Initial work on duration-normalization

* Implement CalendarDateAdd for ISO and API changes

* Review feedback
  • Loading branch information
nekevss authored Feb 14, 2024
1 parent 43fa702 commit c444d45
Show file tree
Hide file tree
Showing 6 changed files with 251 additions and 187 deletions.
31 changes: 30 additions & 1 deletion src/components/calendar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
use std::str::FromStr;

use crate::{
components::{Date, DateTime, Duration, MonthDay, YearMonth},
components::{
duration::{DateDuration, TimeDuration},
Date, DateTime, Duration, MonthDay, YearMonth,
},
iso::{IsoDate, IsoDateSlots},
options::{ArithmeticOverflow, TemporalUnit},
TemporalError, TemporalFields, TemporalResult,
Expand Down Expand Up @@ -489,6 +492,32 @@ impl<C: CalendarProtocol> CalendarSlot<C> {
context: &mut C::Context,
) -> TemporalResult<Date<C>> {
match self {
CalendarSlot::Builtin(AnyCalendar::Iso(_)) => {
// 8. Let norm be NormalizeTimeDuration(duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]]).
// 9. Let balanceResult be BalanceTimeDuration(norm, "day").
let (balance_days, _) = TimeDuration::from_normalized(
duration.time().to_normalized(),
TemporalUnit::Day,
)?;
// 10. Let result be ? AddISODate(date.[[ISOYear]], date.[[ISOMonth]], date.[[ISODay]], duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]] + balanceResult.[[Days]], overflow).
let result = date.iso().add_iso_date(
&DateDuration::new_unchecked(
duration.days(),
duration.months(),
duration.weeks(),
duration.days() + balance_days,
),
overflow,
)?;
// 11. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], "iso8601").
Date::new(
result.year,
result.month.into(),
result.day.into(),
date.calendar().clone(),
ArithmeticOverflow::Reject,
)
}
CalendarSlot::Builtin(_) => {
Err(TemporalError::range().with_message("Not yet implemented."))
}
Expand Down
11 changes: 2 additions & 9 deletions src/components/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,15 +358,8 @@ impl<C: CalendarProtocol> Date<C> {

// 3. Let overflow be ? ToTemporalOverflow(options).
// 4. Let days be ? BalanceTimeDuration(duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], "day").[[Days]].
let (days, _) = TimeDuration::new_unchecked(
duration.hours(),
duration.minutes(),
duration.seconds(),
duration.milliseconds(),
duration.microseconds(),
duration.nanoseconds(),
)
.balance(duration.days(), TemporalUnit::Day)?;
let (days, _) =
TimeDuration::from_normalized(duration.time().to_normalized(), TemporalUnit::Day)?;

// 5. Let result be ? AddISODate(plainDate.[[ISOYear]], plainDate.[[ISOMonth]], plainDate.[[ISODay]], 0, 0, 0, days, overflow).
let result = self
Expand Down
14 changes: 4 additions & 10 deletions src/components/duration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use std::str::FromStr;
use super::{calendar::CalendarProtocol, tz::TzProtocol};

mod date;
pub(crate) mod normalized;
mod time;

#[doc(inline)]
Expand Down Expand Up @@ -801,7 +802,8 @@ impl Duration {
Ok(result)
}

// TODO: Refactor relative_to's into a RelativeTo struct?
// TODO (nekevss): Refactor relative_to's into a RelativeTo struct?
// TODO (nekevss): Update to `Duration` normalization.
/// Abstract Operation 7.5.26 `RoundDuration ( years, months, weeks, days, hours, minutes,
/// seconds, milliseconds, microseconds, nanoseconds, increment, unit,
/// roundingMode [ , plainRelativeTo [, zonedRelativeTo [, precalculatedDateTime]]] )`
Expand Down Expand Up @@ -922,15 +924,7 @@ impl Duration {
/// Calls `TimeDuration`'s balance method on the current `Duration`.
#[inline]
pub fn balance_time_duration(&self, unit: TemporalUnit) -> TemporalResult<(f64, TimeDuration)> {
TimeDuration::new_unchecked(
self.hours(),
self.minutes(),
self.seconds(),
self.milliseconds(),
self.microseconds(),
self.nanoseconds(),
)
.balance(self.days(), unit)
TimeDuration::from_normalized(self.time().to_normalized(), unit)
}
}

Expand Down
59 changes: 59 additions & 0 deletions src/components/duration/normalized.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//! This module implements the normalized `Duration` records.

use crate::{TemporalError, TemporalResult, NS_PER_DAY};

use super::TimeDuration;

const MAX_TIME_DURATION: f64 = 2e53 * 10e9 - 1.0;

#[derive(Debug, Clone, Copy, Default, PartialEq, PartialOrd)]
pub(crate) struct NormalizedTimeDuration(pub(crate) f64);

impl NormalizedTimeDuration {
/// Equivalent: 7.5.20 NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds, nanoseconds )
pub(crate) fn from_time_duration(time: &TimeDuration) -> Self {
let minutes = time.minutes + time.hours * 60.0;
let seconds = time.seconds + minutes * 60.0;
let milliseconds = time.milliseconds + seconds * 1000.0;
let microseconds = time.microseconds + milliseconds * 1000.0;
let nanoseconds = time.nanoseconds + microseconds * 1000.0;
// NOTE(nekevss): Is it worth returning a `RangeError` below.
debug_assert!(nanoseconds.abs() <= MAX_TIME_DURATION);
Self(nanoseconds)
}

/// Equivalent: 7.5.22 AddNormalizedTimeDuration ( one, two )
#[allow(unused)]
pub(crate) fn add(&self, other: &Self) -> TemporalResult<Self> {
let result = self.0 + other.0;
if result.abs() > MAX_TIME_DURATION {
return Err(TemporalError::range()
.with_message("normalizedTimeDuration exceeds maxTimeDuration."));
}
Ok(Self(result))
}

/// Equivalent: 7.5.23 Add24HourDaysToNormalizedTimeDuration ( d, days )
#[allow(unused)]
pub(crate) fn add_days(&self, days: f64) -> TemporalResult<Self> {
let result = self.0 + days * NS_PER_DAY as f64;
if result.abs() > MAX_TIME_DURATION {
return Err(TemporalError::range()
.with_message("normalizedTimeDuration exceeds maxTimeDuration."));
}
Ok(Self(result))
}

// NOTE: DivideNormalizedTimeDuration probably requires `__float128` support as `NormalizedTimeDuration` is not `safe integer`.
// Tracking issue: https://github.com/rust-lang/rfcs/pull/3453

/// Equivalent: 7.5.31 NormalizedTimeDurationSign ( d )
pub(crate) fn sign(&self) -> f64 {
if self.0 < 0.0 {
return -1.0;
} else if self.0 > 0.0 {
return 1.0;
}
0.0
}
}
Loading

0 comments on commit c444d45

Please sign in to comment.