Skip to content

Commit

Permalink
Implement CalendarDateAdd for ISO and API changes
Browse files Browse the repository at this point in the history
  • Loading branch information
nekevss committed Feb 10, 2024
1 parent 095e368 commit 2218d49
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 62 deletions.
29 changes: 28 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,30 @@ 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().as_norm(), 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(TemporalUnit::Day)?;
let (days, _) =
TimeDuration::from_normalized(duration.time().as_norm(), TemporalUnit::Day)?;

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

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

#[doc(inline)]
pub use date::DateDuration;
Expand Down Expand Up @@ -924,7 +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)> {
self.time().balance(unit)
TimeDuration::from_normalized(self.time().as_norm(), unit)
}
}

Expand Down
12 changes: 6 additions & 6 deletions src/components/duration/normalized.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ 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 {
Expand All @@ -28,7 +27,8 @@ impl NormalizedTimeDuration {
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."))
return Err(TemporalError::range()
.with_message("normalizedTimeDuration exceeds maxTimeDuration."));
}
Ok(Self(result))
}
Expand All @@ -38,7 +38,8 @@ impl NormalizedTimeDuration {
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."))
return Err(TemporalError::range()
.with_message("normalizedTimeDuration exceeds maxTimeDuration."));
}
Ok(Self(result))
}
Expand All @@ -49,11 +50,10 @@ impl NormalizedTimeDuration {
/// Equivalent: 7.5.31 NormalizedTimeDurationSign ( d )
pub(crate) fn sign(&self) -> f64 {
if self.0 < 0.0 {
return -1.0
return -1.0;
} else if self.0 > 0.0 {
return 1.0
return 1.0;
}
0.0
}
}

107 changes: 66 additions & 41 deletions src/components/duration/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,42 +135,17 @@ impl TimeDuration {
}
}

/// 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 * -1f64,
minutes: self.minutes * -1f64,
seconds: self.seconds * -1f64,
milliseconds: self.milliseconds * -1f64,
microseconds: self.microseconds * -1f64,
nanoseconds: self.nanoseconds * -1f64,
}
}

/// Balances a `TimeDuration` given a day value and the largest unit. `balance` will return
/// the balanced `day` and `TimeDuration`.
/// 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 fn balance(&self, largest_unit: TemporalUnit) -> TemporalResult<(f64, Self)> {
let norm = NormalizedTimeDuration::from_time_duration(&self);

pub(crate) fn from_normalized(
norm: NormalizedTimeDuration,
largest_unit: TemporalUnit,
) -> TemporalResult<(f64, Self)> {
// 1. Let days, hours, minutes, seconds, milliseconds, and microseconds be 0.
let mut days = 0f64;
let mut hours = 0f64;
Expand All @@ -186,10 +161,7 @@ impl TimeDuration {

match largest_unit {
// 4. If largestUnit is "year", "month", "week", or "day", then
TemporalUnit::Year
| TemporalUnit::Month
| TemporalUnit::Week
| TemporalUnit::Day => {
TemporalUnit::Year | TemporalUnit::Month | TemporalUnit::Week | TemporalUnit::Day => {
// a. Set microseconds to floor(nanoseconds / 1000).
microseconds = (nanoseconds / 1000f64).floor();
// b. Set nanoseconds to nanoseconds modulo 1000.
Expand Down Expand Up @@ -309,21 +281,69 @@ impl TimeDuration {
// a. Assert: largestUnit is "nanosecond".
_ => debug_assert!(largest_unit == TemporalUnit::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

// 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.

// 12. Return ! CreateTimeDurationRecord(days × sign, hours × sign, minutes × sign, seconds × sign, milliseconds × sign, microseconds × sign, nanoseconds × sign).
let days = days.mul_add(sign, 0.0);
let result = Self::new_unchecked(hours.mul_add(sign, 0.0), minutes.mul_add(sign, 0.0), seconds.mul_add(sign, 0.0), milliseconds.mul_add(sign, 0.0), microseconds.mul_add(sign, 0.0), nanoseconds.mul_add(sign, 0.0));
let result = Self::new_unchecked(
hours.mul_add(sign, 0.0),
minutes.mul_add(sign, 0.0),
seconds.mul_add(sign, 0.0),
milliseconds.mul_add(sign, 0.0),
microseconds.mul_add(sign, 0.0),
nanoseconds.mul_add(sign, 0.0),
);

let td = Vec::from(&[days, result.hours, result.minutes, result.seconds, result.milliseconds, result.microseconds, result.nanoseconds]);
let td = Vec::from(&[
days,
result.hours,
result.minutes,
result.seconds,
result.milliseconds,
result.microseconds,
result.nanoseconds,
]);
if !is_valid_duration(&td) {
return Err(TemporalError::range().with_message("Invalid balance TimeDuration."));
}

Ok((days, 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 * -1f64,
minutes: self.minutes * -1f64,
seconds: self.seconds * -1f64,
milliseconds: self.milliseconds * -1f64,
microseconds: self.microseconds * -1f64,
nanoseconds: self.nanoseconds * -1f64,
}
}

/// Utility function for returning if values in a valid range.
#[inline]
#[must_use]
Expand Down Expand Up @@ -377,6 +397,11 @@ impl TimeDuration {
pub fn iter(&self) -> TimeIter<'_> {
<&Self as IntoIterator>::into_iter(self)
}

/// Returns this `TimeDuration` as a `NormalizedTimeDuration`.
pub(crate) fn as_norm(&self) -> NormalizedTimeDuration {
NormalizedTimeDuration::from_time_duration(self)
}
}

// ==== TimeDuration method impls ====
Expand Down
8 changes: 5 additions & 3 deletions src/components/instant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,10 @@ impl Instant {
// Steps 11-13 of 13.47 GetDifferenceSettings

if smallest_unit == TemporalUnit::Nanosecond {
let (_, result) = TimeDuration::new_unchecked(0f64, 0f64, secs, millis, micros, nanos)
.balance(largest_unit)?;
let (_, result) = TimeDuration::from_normalized(
TimeDuration::new_unchecked(0f64, 0f64, secs, millis, micros, nanos).as_norm(),
largest_unit,
)?;
return Ok(result);
}

Expand All @@ -87,7 +89,7 @@ impl Instant {
smallest_unit,
rounding_mode,
)?;
let (_, result) = round_result.balance(largest_unit)?;
let (_, result) = TimeDuration::from_normalized(round_result.as_norm(), largest_unit)?;
Ok(result)
}

Expand Down

0 comments on commit 2218d49

Please sign in to comment.