Skip to content

Commit

Permalink
Implement until and since methods (#36)
Browse files Browse the repository at this point in the history
  • Loading branch information
nekevss authored Mar 31, 2024
1 parent 0efe96d commit efb607a
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 9 deletions.
2 changes: 1 addition & 1 deletion src/components/duration/normalized.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ 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 struct NormalizedTimeDuration(pub(super) f64);
pub struct NormalizedTimeDuration(pub(crate) f64);

impl NormalizedTimeDuration {
/// Equivalent: 7.5.20 NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds, nanoseconds )
Expand Down
175 changes: 168 additions & 7 deletions src/components/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub struct Time {
impl Time {
#[inline]
#[must_use]
/// Creates a new unvalidated `Time`.
pub(crate) fn new_unchecked(iso: IsoTime) -> Self {
Self { iso }
}
Expand Down Expand Up @@ -46,6 +47,69 @@ impl Time {

Self::new_unchecked(result)
}

/// Performs a desired difference op between two `Time`'s, returning the resulting `Duration`.
pub(crate) fn diff_time(
&self,
op: bool,
other: &Time,
rounding_mode: Option<TemporalRoundingMode>,
rounding_increment: Option<f64>,
largest_unit: Option<TemporalUnit>,
smallest_unit: Option<TemporalUnit>,
) -> TemporalResult<Duration> {
// 1. If operation is SINCE, let sign be -1. Otherwise, let sign be 1.
// 2. Set other to ? ToTemporalTime(other).
// 3. Let resolvedOptions be ? SnapshotOwnProperties(? GetOptionsObject(options), null).
// 4. Let settings be ? GetDifferenceSettings(operation, resolvedOptions, TIME, « », "nanosecond", "hour").
let increment = utils::to_rounding_increment(rounding_increment)?;
let (sign, rounding_mode) = if op {
(
-1.0,
rounding_mode
.unwrap_or(TemporalRoundingMode::Trunc)
.negate(),
)
} else {
(1.0, rounding_mode.unwrap_or(TemporalRoundingMode::Trunc))
};

let smallest_unit = smallest_unit.unwrap_or(TemporalUnit::Nanosecond);
// Use the defaultlargestunit which is max smallestlargestdefault and smallestunit
let largest_unit = largest_unit.unwrap_or(smallest_unit.max(TemporalUnit::Hour));

// 5. Let norm be ! DifferenceTime(temporalTime.[[ISOHour]], temporalTime.[[ISOMinute]],
// temporalTime.[[ISOSecond]], temporalTime.[[ISOMillisecond]], temporalTime.[[ISOMicrosecond]],
// temporalTime.[[ISONanosecond]], other.[[ISOHour]], other.[[ISOMinute]], other.[[ISOSecond]],
// other.[[ISOMillisecond]], other.[[ISOMicrosecond]], other.[[ISONanosecond]]).
let time = self.iso.diff(&other.iso);

// 6. If settings.[[SmallestUnit]] is not "nanosecond" or settings.[[RoundingIncrement]] ≠ 1, then
let norm = if smallest_unit != TemporalUnit::Nanosecond || rounding_increment != Some(1.0) {
// a. Let roundRecord be ! RoundDuration(0, 0, 0, 0, norm, settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[RoundingMode]]).
let round_record = time.round(increment, smallest_unit, rounding_mode)?;
// b. Set norm to roundRecord.[[NormalizedDuration]].[[NormalizedTime]].
round_record.0
} else {
time.to_normalized()
};

// 7. Let result be BalanceTimeDuration(norm, settings.[[LargestUnit]]).
let result = TimeDuration::from_normalized(norm, largest_unit)?.1;
// 8. Return ! CreateTemporalDuration(0, 0, 0, 0, sign × result.[[Hours]], sign × result.[[Minutes]], sign × result.[[Seconds]], sign × result.[[Milliseconds]], sign × result.[[Microseconds]], sign × result.[[Nanoseconds]]).
Duration::new(
0.0,
0.0,
0.0,
0.0,
sign * result.hours,
sign * result.minutes,
sign * result.seconds,
sign * result.milliseconds,
sign * result.microseconds,
sign * result.nanoseconds,
)
}
}

// ==== Public API ====
Expand Down Expand Up @@ -147,6 +211,50 @@ impl Time {
self.add_to_time(&duration.negated())
}

#[inline]
/// Returns the `Duration` until the provided `Time` from the current `Time`.
///
/// NOTE: `until` assumes the provided other time will occur in the future relative to the current.
pub fn until(
&self,
other: &Self,
rounding_mode: Option<TemporalRoundingMode>,
rounding_increment: Option<f64>,
largest_unit: Option<TemporalUnit>,
smallest_unit: Option<TemporalUnit>,
) -> TemporalResult<Duration> {
self.diff_time(
false,
other,
rounding_mode,
rounding_increment,
largest_unit,
smallest_unit,
)
}

#[inline]
/// Returns the `Duration` since the provided `Time` from the current `Time`.
///
/// NOTE: `since` assumes the provided other time is in the past relative to the current.
pub fn since(
&self,
other: &Self,
rounding_mode: Option<TemporalRoundingMode>,
rounding_increment: Option<f64>,
largest_unit: Option<TemporalUnit>,
smallest_unit: Option<TemporalUnit>,
) -> TemporalResult<Duration> {
self.diff_time(
true,
other,
rounding_mode,
rounding_increment,
largest_unit,
smallest_unit,
)
}

// TODO (nekevss): optimize and test rounding_increment type (f64 vs. u64).
/// Rounds the current `Time` according to provided options.
pub fn round(
Expand Down Expand Up @@ -177,17 +285,28 @@ impl Time {

#[cfg(test)]
mod tests {
use crate::{components::Duration, iso::IsoTime, options::TemporalUnit};
use crate::{
components::Duration,
iso::IsoTime,
options::{ArithmeticOverflow, TemporalUnit},
};

use super::Time;

fn assert_time(result: Time, values: (u8, u8, u8, u16, u16, u16)) {
assert!(result.hour() == values.0);
assert!(result.minute() == values.1);
assert!(result.second() == values.2);
assert!(result.millisecond() == values.3);
assert!(result.microsecond() == values.4);
assert!(result.nanosecond() == values.5);
assert_eq!(
result,
Time {
iso: IsoTime {
hour: values.0,
minute: values.1,
second: values.2,
millisecond: values.3,
microsecond: values.4,
nanosecond: values.5,
}
}
);
}

#[test]
Expand Down Expand Up @@ -272,4 +391,46 @@ mod tests {

assert_time(result, (7, 23, 30, 123, 456, 789));
}

#[test]
fn since_basic() {
let one = Time::new(15, 23, 30, 123, 456, 789, ArithmeticOverflow::Constrain).unwrap();
let two = Time::new(14, 23, 30, 123, 456, 789, ArithmeticOverflow::Constrain).unwrap();
let three = Time::new(13, 30, 30, 123, 456, 789, ArithmeticOverflow::Constrain).unwrap();

let result = one.since(&two, None, None, None, None).unwrap();
assert_eq!(result.hours(), 1.0);

let result = two.since(&one, None, None, None, None).unwrap();
assert_eq!(result.hours(), -1.0);

let result = one.since(&three, None, None, None, None).unwrap();
assert_eq!(result.hours(), 1.0);
assert_eq!(result.minutes(), 53.0);

let result = three.since(&one, None, None, None, None).unwrap();
assert_eq!(result.hours(), -1.0);
assert_eq!(result.minutes(), -53.0);
}

#[test]
fn until_basic() {
let one = Time::new(15, 23, 30, 123, 456, 789, ArithmeticOverflow::Constrain).unwrap();
let two = Time::new(16, 23, 30, 123, 456, 789, ArithmeticOverflow::Constrain).unwrap();
let three = Time::new(17, 0, 30, 123, 456, 789, ArithmeticOverflow::Constrain).unwrap();

let result = one.until(&two, None, None, None, None).unwrap();
assert_eq!(result.hours(), 1.0);

let result = two.until(&one, None, None, None, None).unwrap();
assert_eq!(result.hours(), -1.0);

let result = one.until(&three, None, None, None, None).unwrap();
assert_eq!(result.hours(), 1.0);
assert_eq!(result.minutes(), 37.0);

let result = three.until(&one, None, None, None, None).unwrap();
assert_eq!(result.hours(), -1.0);
assert_eq!(result.minutes(), -37.0);
}
}
14 changes: 13 additions & 1 deletion src/iso.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
//! An `IsoDateTime` has the internal slots of both an `IsoDate` and `IsoTime`.

use crate::{
components::duration::DateDuration,
components::duration::{DateDuration, TimeDuration},
error::TemporalError,
options::{ArithmeticOverflow, TemporalRoundingMode, TemporalUnit},
utils, TemporalResult, NS_PER_DAY,
Expand Down Expand Up @@ -495,6 +495,18 @@ impl IsoTime {
(days as i32, time)
}

/// Difference this `IsoTime` against another and returning a `TimeDuration`.
pub(crate) fn diff(&self, other: &Self) -> TimeDuration {
let h = f64::from(other.hour) - f64::from(self.hour);
let m = f64::from(other.minute) - f64::from(self.minute);
let s = f64::from(other.second) - f64::from(self.second);
let ms = f64::from(other.millisecond) - f64::from(self.millisecond);
let mis = f64::from(other.microsecond) - f64::from(self.microsecond);
let ns = f64::from(other.nanosecond) - f64::from(self.nanosecond);

TimeDuration::new_unchecked(h, m, s, ms, mis, ns)
}

// NOTE (nekevss): Specification seemed to be off / not entirely working, so the below was adapted from the
// temporal-polyfill
// TODO: DayLengthNS can probably be a u64, but keep as is for now and optimize.
Expand Down

0 comments on commit efb607a

Please sign in to comment.