Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Duration Normalization - Part 2 #23

Merged
merged 2 commits into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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