Skip to content

Commit

Permalink
Temporal: Build out Time and its methods (#3613)
Browse files Browse the repository at this point in the history
* Build out Time and its methods

* Move public methods and remove #[allow(dead_code)]
  • Loading branch information
nekevss authored Jan 27, 2024
1 parent 806c3c9 commit 9ffbc03
Show file tree
Hide file tree
Showing 4 changed files with 433 additions and 31 deletions.
19 changes: 14 additions & 5 deletions core/temporal/src/components/duration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,6 @@ impl Duration {
pub(crate) fn one_week(week_value: f64) -> Self {
Self::from_date_duration(DateDuration::new_unchecked(0f64, 0f64, week_value, 0f64))
}

/// Utility that checks whether `Duration`'s `DateDuration` is empty.
pub(crate) fn is_time_duration(&self) -> bool {
self.date().iter().any(|x| x != 0f64)
}
}

// ==== Public Duration API ====
Expand Down Expand Up @@ -137,6 +132,20 @@ impl Duration {
pub fn is_time_within_range(&self) -> bool {
self.time.is_within_range()
}

/// Returns whether `Duration`'s `DateDuration` isn't empty and is therefore a `DateDuration` or `Duration`.
#[inline]
#[must_use]
pub fn is_date_duration(&self) -> bool {
self.date().iter().any(|x| x != 0.0) && self.time().iter().all(|x| x == 0.0)
}

/// Returns whether `Duration`'s `DateDuration` is empty and is therefore a `TimeDuration`.
#[inline]
#[must_use]
pub fn is_time_duration(&self) -> bool {
self.time().iter().any(|x| x != 0.0) && self.date().iter().all(|x| x == 0.0)
}
}

// ==== Public `Duration` Getters/Setters ====
Expand Down
230 changes: 227 additions & 3 deletions core/temporal/src/components/time.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
//! This module implements `Time` and any directly related algorithms.
use crate::{iso::IsoTime, options::ArithmeticOverflow, TemporalResult};
use crate::{
components::{duration::TimeDuration, Duration},
iso::IsoTime,
options::{ArithmeticOverflow, TemporalRoundingMode, TemporalUnit},
utils, TemporalError, TemporalResult,
};

/// The native Rust implementation of `Temporal.PlainTime`.
#[derive(Debug, Default, Clone, Copy)]
#[allow(dead_code)]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Time {
iso: IsoTime,
}
Expand All @@ -23,8 +27,28 @@ impl Time {
pub(crate) fn is_valid(&self) -> bool {
self.iso.is_valid()
}

/// Adds a `TimeDuration` to the current `Time`.
///
/// Spec Equivalent: `AddDurationToOrSubtractDurationFromPlainTime` AND `AddTime`.
pub(crate) fn add_to_time(&self, duration: &TimeDuration) -> Self {
let (_, result) = IsoTime::balance(
f64::from(self.hour()) + duration.hours(),
f64::from(self.minute()) + duration.minutes(),
f64::from(self.second()) + duration.seconds(),
f64::from(self.millisecond()) + duration.milliseconds(),
f64::from(self.microsecond()) + duration.microseconds(),
f64::from(self.nanosecond()) + duration.nanoseconds(),
);

// NOTE (nekevss): IsoTime::balance should never return an invalid `IsoTime`

Self::new_unchecked(result)
}
}

// ==== Public API ====

impl Time {
/// Creates a new `IsoTime` value.
pub fn new(
Expand All @@ -47,4 +71,204 @@ impl Time {
)?;
Ok(Self::new_unchecked(time))
}

/// Returns the internal `hour` field.
#[inline]
#[must_use]
pub const fn hour(&self) -> u8 {
self.iso.hour
}

/// Returns the internal `minute` field.
#[inline]
#[must_use]
pub const fn minute(&self) -> u8 {
self.iso.minute
}

/// Returns the internal `second` field.
#[inline]
#[must_use]
pub const fn second(&self) -> u8 {
self.iso.second
}

/// Returns the internal `millisecond` field.
#[inline]
#[must_use]
pub const fn millisecond(&self) -> u16 {
self.iso.millisecond
}

/// Returns the internal `microsecond` field.
#[inline]
#[must_use]
pub const fn microsecond(&self) -> u16 {
self.iso.microsecond
}

/// Returns the internal `nanosecond` field.
#[inline]
#[must_use]
pub const fn nanosecond(&self) -> u16 {
self.iso.nanosecond
}

/// Add a `Duration` to the current `Time`.
pub fn add(&self, duration: &Duration) -> TemporalResult<Self> {
if !duration.is_time_duration() {
return Err(TemporalError::range()
.with_message("DateDuration values cannot be added to `Time`."));
}
Ok(self.add_time_duration(duration.time()))
}

/// Adds a `TimeDuration` to the current `Time`.
#[inline]
#[must_use]
pub fn add_time_duration(&self, duration: &TimeDuration) -> Self {
self.add_to_time(duration)
}

/// Subtract a `Duration` to the current `Time`.
pub fn subtract(&self, duration: &Duration) -> TemporalResult<Self> {
if !duration.is_time_duration() {
return Err(TemporalError::range()
.with_message("DateDuration values cannot be added to `Time` component."));
}
Ok(self.add_time_duration(duration.time()))
}

/// Adds a `TimeDuration` to the current `Time`.
#[inline]
#[must_use]
pub fn subtract_time_duration(&self, duration: &TimeDuration) -> Self {
self.add_to_time(&duration.neg())
}

// TODO (nekevss): optimize and test rounding_increment type (f64 vs. u64).
/// Rounds the current `Time` according to provided options.
pub fn round(
&self,
smallest_unit: TemporalUnit,
rounding_increment: Option<f64>,
rounding_mode: Option<TemporalRoundingMode>,
) -> TemporalResult<Self> {
let increment = utils::to_rounding_increment(rounding_increment)?;
let mode = rounding_mode.unwrap_or(TemporalRoundingMode::HalfExpand);

let max = smallest_unit
.to_maximum_rounding_increment()
.ok_or_else(|| {
TemporalError::range().with_message("smallestUnit must be a time value.")
})?;

// Safety (nekevss): to_rounding_increment returns a value in the range of a u32.
utils::validate_temporal_rounding_increment(increment as u32, u64::from(max), false)?;

let (_, result) = self.iso.round(increment, smallest_unit, mode, None)?;

Ok(Self::new_unchecked(result))
}
}

// ==== Test land ====

#[cfg(test)]
mod tests {
use crate::{components::Duration, iso::IsoTime, options::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);
}

#[test]
fn time_round_millisecond() {
let base = Time::new_unchecked(IsoTime::new_unchecked(3, 34, 56, 987, 654, 321));

let result_1 = base
.round(TemporalUnit::Millisecond, Some(1.0), None)
.unwrap();
assert_time(result_1, (3, 34, 56, 988, 0, 0));

let result_2 = base
.round(TemporalUnit::Millisecond, Some(2.0), None)
.unwrap();
assert_time(result_2, (3, 34, 56, 988, 0, 0));

let result_3 = base
.round(TemporalUnit::Millisecond, Some(4.0), None)
.unwrap();
assert_time(result_3, (3, 34, 56, 988, 0, 0));

let result_4 = base
.round(TemporalUnit::Millisecond, Some(5.0), None)
.unwrap();
assert_time(result_4, (3, 34, 56, 990, 0, 0));
}

#[test]
fn time_round_microsecond() {
let base = Time::new_unchecked(IsoTime::new_unchecked(3, 34, 56, 987, 654, 321));

let result_1 = base
.round(TemporalUnit::Microsecond, Some(1.0), None)
.unwrap();
assert_time(result_1, (3, 34, 56, 987, 654, 0));

let result_2 = base
.round(TemporalUnit::Microsecond, Some(2.0), None)
.unwrap();
assert_time(result_2, (3, 34, 56, 987, 654, 0));

let result_3 = base
.round(TemporalUnit::Microsecond, Some(4.0), None)
.unwrap();
assert_time(result_3, (3, 34, 56, 987, 656, 0));

let result_4 = base
.round(TemporalUnit::Microsecond, Some(5.0), None)
.unwrap();
assert_time(result_4, (3, 34, 56, 987, 655, 0));
}

#[test]
fn time_round_nanoseconds() {
let base = Time::new_unchecked(IsoTime::new_unchecked(3, 34, 56, 987, 654, 321));

let result_1 = base
.round(TemporalUnit::Nanosecond, Some(1.0), None)
.unwrap();
assert_time(result_1, (3, 34, 56, 987, 654, 321));

let result_2 = base
.round(TemporalUnit::Nanosecond, Some(2.0), None)
.unwrap();
assert_time(result_2, (3, 34, 56, 987, 654, 322));

let result_3 = base
.round(TemporalUnit::Nanosecond, Some(4.0), None)
.unwrap();
assert_time(result_3, (3, 34, 56, 987, 654, 320));

let result_4 = base
.round(TemporalUnit::Nanosecond, Some(5.0), None)
.unwrap();
assert_time(result_4, (3, 34, 56, 987, 654, 320));
}

#[test]
fn add_duration_basic() {
let base = Time::new_unchecked(IsoTime::new_unchecked(15, 23, 30, 123, 456, 789));
let result = base.add(&"PT16H".parse::<Duration>().unwrap()).unwrap();

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

0 comments on commit 9ffbc03

Please sign in to comment.