Skip to content

Commit

Permalink
Duration Normalization - Part 2 (#23)
Browse files Browse the repository at this point in the history
* Continued work on normalization and rounding change

* Update DateDuration::round
  • Loading branch information
nekevss authored Feb 16, 2024
1 parent c444d45 commit c644a26
Show file tree
Hide file tree
Showing 7 changed files with 256 additions and 245 deletions.
27 changes: 15 additions & 12 deletions src/components/duration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use crate::{
};
use std::str::FromStr;

use self::normalized::{NormalizedDurationRecord, NormalizedTimeDuration};

use super::{calendar::CalendarProtocol, tz::TzProtocol};

mod date;
Expand Down Expand Up @@ -803,14 +805,15 @@ impl Duration {
}

// TODO (nekevss): Refactor relative_to's into a RelativeTo struct?
// TODO (nekevss): Update to `Duration` normalization.
// TODO (nekevss): Impl `Duration::round` -> Potentially rename to `round_internal`
/// Abstract Operation 7.5.26 `RoundDuration ( years, months, weeks, days, hours, minutes,
/// seconds, milliseconds, microseconds, nanoseconds, increment, unit,
/// roundingMode [ , plainRelativeTo [, zonedRelativeTo [, precalculatedDateTime]]] )`
#[allow(clippy::type_complexity)]
pub fn round_duration<C: CalendarProtocol, Z: TzProtocol>(
#[allow(dead_code)]
pub(crate) fn round_duration<C: CalendarProtocol, Z: TzProtocol>(
&self,
increment: f64,
increment: u64,
unit: TemporalUnit,
rounding_mode: TemporalRoundingMode,
relative_targets: (
Expand All @@ -819,19 +822,22 @@ impl Duration {
Option<&DateTime<C>>,
),
context: &mut C::Context,
) -> TemporalResult<(Self, f64)> {
) -> TemporalResult<(NormalizedDurationRecord, f64)> {
match unit {
TemporalUnit::Year | TemporalUnit::Month | TemporalUnit::Week | TemporalUnit::Day => {
let round_result = self.date().round(
Some(self.time),
Some(self.time.to_normalized()),
increment,
unit,
rounding_mode,
relative_targets,
context,
)?;
let result = Self::from_date_duration(round_result.0);
Ok((result, round_result.1))
let norm_record = NormalizedDurationRecord::new(
round_result.0,
NormalizedTimeDuration::default(),
)?;
Ok((norm_record, round_result.1))
}
TemporalUnit::Hour
| TemporalUnit::Minute
Expand All @@ -840,11 +846,8 @@ impl Duration {
| TemporalUnit::Microsecond
| TemporalUnit::Nanosecond => {
let round_result = self.time().round(increment, unit, rounding_mode)?;
let result = Self {
date: self.date,
time: round_result.0,
};
Ok((result, round_result.1))
let norm = NormalizedDurationRecord::new(DateDuration::default(), round_result.0)?;
Ok((norm, round_result.1 as f64))
}
TemporalUnit::Auto => {
Err(TemporalError::range().with_message("Invalid TemporalUnit for Duration.round"))
Expand Down
62 changes: 36 additions & 26 deletions src/components/duration/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use crate::{
utils, TemporalError, TemporalResult, NS_PER_DAY,
};

use super::normalized::NormalizedTimeDuration;

/// `DateDuration` represents the [date duration record][spec] of the `Duration.`
///
/// These fields are laid out in the [Temporal Proposal][field spec] as 64-bit floating point numbers.
Expand Down Expand Up @@ -109,6 +111,13 @@ impl DateDuration {
}
}

/// Returns the sign for the current `DateDuration`.
#[inline]
#[must_use]
pub fn sign(&self) -> i32 {
super::duration_sign(&self.iter().collect())
}

/// Returns the `[[years]]` value.
#[must_use]
pub const fn years(&self) -> f64 {
Expand Down Expand Up @@ -148,8 +157,8 @@ impl DateDuration {
#[allow(clippy::type_complexity, clippy::let_and_return)]
pub fn round<C: CalendarProtocol, Z: TzProtocol>(
&self,
additional_time: Option<TimeDuration>,
increment: f64,
normalized_time: Option<NormalizedTimeDuration>,
increment: u64,
unit: TemporalUnit,
rounding_mode: TemporalRoundingMode,
relative_targets: (
Expand Down Expand Up @@ -177,23 +186,19 @@ impl DateDuration {
}
// 5. If unit is one of "year", "month", "week", or "day", then
TemporalUnit::Year | TemporalUnit::Month | TemporalUnit::Week | TemporalUnit::Day => {
// a. Let nanoseconds be TotalDurationNanoseconds(hours, minutes, seconds, milliseconds, microseconds, nanoseconds).
let nanoseconds = additional_time.unwrap_or_default().as_nanos();

// b. If zonedRelativeTo is not undefined, then
// i. Let intermediate be ? MoveRelativeZonedDateTime(zonedRelativeTo, years, months, weeks, days, precalculatedPlainDateTime).
// ii. Let result be ? NanosecondsToDays(nanoseconds, intermediate).
// iii. Let fractionalDays be days + result.[[Days]] + result.[[Nanoseconds]] / result.[[DayLength]].
// c. Else,
// i. Let fractionalDays be days + nanoseconds / nsPerDay.
// d. Set days, hours, minutes, seconds, milliseconds, microseconds, and nanoseconds to 0.
// e. Assert: fractionalSeconds is not used below.
if zoned_relative_to.is_none() {
self.days + nanoseconds / NS_PER_DAY as f64
// a. If zonedRelativeTo is not undefined, then
if let Some(_zoned_relative) = zoned_relative_to {
// TODO:
// i. Let intermediate be ? MoveRelativeZonedDateTime(zonedRelativeTo, calendarRec, timeZoneRec, years, months, weeks, days, precalculatedPlainDateTime).
// ii. Let result be ? NormalizedTimeDurationToDays(norm, intermediate, timeZoneRec).
// iii. Let fractionalDays be days + result.[[Days]] + DivideNormalizedTimeDuration(result.[[Remainder]], result.[[DayLength]]).
return Err(TemporalError::general("Not yet implemented."));
// b. Else,
} else {
// implementation of b: i-iii needed.
return Err(TemporalError::range().with_message("Not yet implemented."));
// i. Let fractionalDays be days + DivideNormalizedTimeDuration(norm, nsPerDay).
self.days + normalized_time.unwrap_or_default().divide(NS_PER_DAY) as f64
}
// c. Set days to 0.
}
_ => {
return Err(TemporalError::range()
Expand Down Expand Up @@ -300,11 +305,11 @@ impl DateDuration {

// ab. Set years to RoundNumberToIncrement(fractionalYears, increment, roundingMode).
let rounded_years =
utils::round_number_to_increment(frac_years, increment, rounding_mode);
utils::round_number_to_increment(frac_years as i64, increment, rounding_mode);

// ac. Set total to fractionalYears.
// ad. Set months and weeks to 0.
let result = Self::new(rounded_years, 0f64, 0f64, 0f64)?;
let result = Self::new(rounded_years as f64, 0f64, 0f64, 0f64)?;
Ok((result, frac_years))
}
// 9. Else if unit is "month", then
Expand Down Expand Up @@ -391,11 +396,11 @@ impl DateDuration {

// r. Set months to RoundNumberToIncrement(fractionalMonths, increment, roundingMode).
let rounded_months =
utils::round_number_to_increment(frac_months, increment, rounding_mode);
utils::round_number_to_increment(frac_months as i64, increment, rounding_mode);

// s. Set total to fractionalMonths.
// t. Set weeks to 0.
let result = Self::new(self.years, rounded_months, 0f64, 0f64)?;
let result = Self::new(self.years, rounded_months as f64, 0f64, 0f64)?;
Ok((result, frac_months))
}
// 10. Else if unit is "week", then
Expand Down Expand Up @@ -443,18 +448,23 @@ impl DateDuration {

// k. Set weeks to RoundNumberToIncrement(fractionalWeeks, increment, roundingMode).
let rounded_weeks =
utils::round_number_to_increment(frac_weeks, increment, rounding_mode);
utils::round_number_to_increment(frac_weeks as i64, increment, rounding_mode);
// l. Set total to fractionalWeeks.
let result = Self::new(self.years, self.months, rounded_weeks, 0f64)?;
let result = Self::new(self.years, self.months, rounded_weeks as f64, 0f64)?;
Ok((result, frac_weeks))
}
// 11. Else if unit is "day", then
TemporalUnit::Day => {
// a. Set days to RoundNumberToIncrement(fractionalDays, increment, roundingMode).
let rounded_days =
utils::round_number_to_increment(fractional_days, increment, rounding_mode);
let rounded_days = utils::round_number_to_increment(
fractional_days as i64,
increment,
rounding_mode,
);

// b. Set total to fractionalDays.
let result = Self::new(self.years, self.months, self.weeks, rounded_days)?;
// c. Set norm to ZeroTimeDuration().
let result = Self::new(self.years, self.months, self.weeks, rounded_days as f64)?;
Ok((result, fractional_days))
}
_ => unreachable!("All other TemporalUnits were returned early as invalid."),
Expand Down
90 changes: 70 additions & 20 deletions src/components/duration/normalized.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
//! This module implements the normalized `Duration` records.

use crate::{TemporalError, TemporalResult, NS_PER_DAY};
use std::ops::Add;

use super::TimeDuration;
use crate::{options::TemporalRoundingMode, utils, TemporalError, TemporalResult, NS_PER_DAY};

use super::{DateDuration, TimeDuration};

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

/// A Normalized `TimeDuration` that represents the current `TimeDuration` in nanoseconds.
#[derive(Debug, Clone, Copy, Default, PartialEq, PartialOrd)]
pub(crate) struct NormalizedTimeDuration(pub(crate) f64);
pub struct NormalizedTimeDuration(pub(super) f64);

impl NormalizedTimeDuration {
/// Equivalent: 7.5.20 NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds, nanoseconds )
pub(crate) fn from_time_duration(time: &TimeDuration) -> Self {
pub(super) 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;
Expand All @@ -22,38 +25,85 @@ impl NormalizedTimeDuration {
Self(nanoseconds)
}

/// Equivalent: 7.5.22 AddNormalizedTimeDuration ( one, two )
/// Equivalent: 7.5.23 Add24HourDaysToNormalizedTimeDuration ( d, days )
#[allow(unused)]
pub(crate) fn add(&self, other: &Self) -> TemporalResult<Self> {
let result = self.0 + other.0;
pub(super) 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))
}

/// 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;
// TODO: Implement as `ops::Div`
/// `Divide the NormalizedTimeDuraiton` by a divisor.
pub(super) fn divide(&self, divisor: i64) -> i64 {
// TODO: Validate.
self.0 as i64 / divisor
}

/// Equivalent: 7.5.31 NormalizedTimeDurationSign ( d )
#[inline]
#[must_use]
pub(super) fn sign(&self) -> i32 {
if self.0 < 0.0 {
return -1;
} else if self.0 > 0.0 {
return 1;
}
0
}

/// Return the seconds value of the `NormalizedTimeDuration`.
pub(super) fn seconds(&self) -> i64 {
(self.0 / 10e9).trunc() as i64
}

/// Returns the subsecond components of the `NormalizedTimeDuration`.
pub(super) fn subseconds(&self) -> i32 {
// SAFETY: Remainder is 10e9 which is in range of i32
(self.0 % 10e9f64) as i32
}

/// Round the current `NormalizedTimeDuration`.
pub(super) fn round(&self, increment: u64, mode: TemporalRoundingMode) -> TemporalResult<Self> {
let rounded = utils::round_number_to_increment(self.0 as i64, increment, mode);
if rounded.abs() > MAX_TIME_DURATION as i64 {
return Err(TemporalError::range()
.with_message("normalizedTimeDuration exceeds maxTimeDuration."));
}
Ok(Self(rounded as f64))
}
}

// NOTE(nekevss): As this `Add` impl is fallible. Maybe it would be best implemented as a method.
/// Equivalent: 7.5.22 AddNormalizedTimeDuration ( one, two )
impl Add<Self> for NormalizedTimeDuration {
type Output = TemporalResult<Self>;

fn add(self, rhs: Self) -> Self::Output {
let result = self.0 + rhs.0;
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
/// A normalized `DurationRecord` that contains a `DateDuration` and `NormalizedTimeDuration`.
pub struct NormalizedDurationRecord(pub(super) (DateDuration, NormalizedTimeDuration));

/// 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;
impl NormalizedDurationRecord {
/// Creates a new `NormalizedDurationRecord`.
///
/// Equivalent: `CreateNormalizedDurationRecord` & `CombineDateAndNormalizedTimeDuration`.
pub(super) fn new(date: DateDuration, norm: NormalizedTimeDuration) -> TemporalResult<Self> {
if date.sign() != 0 && norm.sign() != 0 && date.sign() != norm.sign() {
return Err(TemporalError::range()
.with_message("DateDuration and NormalizedTimeDuration must agree."));
}
0.0
Ok(Self((date, norm)))
}
}
Loading

0 comments on commit c644a26

Please sign in to comment.