Skip to content

Commit

Permalink
Rework, expose unit conversions
Browse files Browse the repository at this point in the history
  • Loading branch information
jhpratt committed Sep 24, 2023
1 parent 313f980 commit 6d0b341
Show file tree
Hide file tree
Showing 17 changed files with 300 additions and 294 deletions.
168 changes: 83 additions & 85 deletions time-core/src/convert.rs
Original file line number Diff line number Diff line change
@@ -1,106 +1,104 @@
//! Conversion between units of time.
/// Declare the structs that represent a unit of time.
macro_rules! declare_structs {
($($t:ident $str:expr)*) => {$(
use self::sealed::Per;

mod sealed {
/// A trait for defining the ratio of two units of time.
///
/// This trait is used to implement the `per` method on the various structs.
pub trait Per<T> {
/// The smallest unsigned integer type that can represent [`VALUE`](Self::VALUE).
type Output;

/// The number of one unit of time in the other.
const VALUE: Self::Output;
}
}

/// Declare and implement `Per` for all relevant types. Identity implementations are automatic.
macro_rules! impl_per {
($($t:ident ($str:literal) per {$(
$larger:ident : $output:ty = $value:expr
)*})*) => {$(
#[doc = concat!("A unit of time representing exactly one ", $str, ".")]
#[derive(Debug, Copy, Clone)]
#[derive(Debug, Clone, Copy)]
pub struct $t;

impl $t {
/// Obtain the integer ratio between the two units of time.
#[doc = concat!("Obtain the number of times `", stringify!($t), "` can fit into `T`.")]
#[doc = concat!("If `T` is smaller than `", stringify!($t), "`, the code will fail to")]
/// compile. The return type is the smallest unsigned integer type that can represent
/// the value.
///
/// Valid calls:
///
/// If the ratio is less than one, the call will fail to compile.
pub const fn per<T>(self, _: T) -> <(Self, T) as Per>::Output
#[doc = concat!(" - `", stringify!($t), "::per(", stringify!($t), ")` (returns `u8`)")]
$(#[doc = concat!(" - `", stringify!($t), "::per(", stringify!($larger), ")` (returns `", stringify!($output), "`)")])*
pub const fn per<T>(_larger: T) -> <Self as Per<T>>::Output
where
(Self, T): Per,
Self: Per<T>,
T: Copy,
{
<(Self, T)>::VALUE
Self::VALUE
}
}
)*};
}

declare_structs! {
Nanosecond "nanosecond"
Microsecond "microsecond"
Millisecond "millisecond"
Second "second"
Minute "minute"
Hour "hour"
Day "day"
Week "week"
}

mod sealed {
pub trait Sealed {}
}

/// A trait for defining the ratio of two units of time.
///
/// This trait is used to implement the `per` method on the various structs.
pub trait Per: sealed::Sealed {
/// The smallest unsigned integer type that can represent [`VALUE`](Self::VALUE).
type Output;

/// The number of one unit of time in the other.
const VALUE: Self::Output;
}
impl Per<$t> for $t {
type Output = u8;

/// Implement the `Per` trait for pairs of types.
macro_rules! impl_per {
($($t:ty : $x:ident in $y:ident = $val:expr)*) => {$(
impl sealed::Sealed for ($x, $y) {}
const VALUE: u8 = 1;
}

impl Per for ($x, $y) {
type Output = $t;
$(impl Per<$larger> for $t {
type Output = $output;

const VALUE: $t = $val;
}
const VALUE: $output = $value;
})*
)*};
}

impl_per! {
u8: Nanosecond in Nanosecond = 1
u16: Nanosecond in Microsecond = 1_000
u32: Nanosecond in Millisecond = 1_000_000
u32: Nanosecond in Second = 1_000_000_000
u64: Nanosecond in Minute = 60_000_000_000
u64: Nanosecond in Hour = 3_600_000_000_000
u64: Nanosecond in Day = 86_400_000_000_000
u64: Nanosecond in Week = 604_800_000_000_000

u8: Microsecond in Microsecond = 1
u16: Microsecond in Millisecond = 1_000
u32: Microsecond in Second = 1_000_000
u32: Microsecond in Minute = 60_000_000
u32: Microsecond in Hour = 3_600_000_000
u64: Microsecond in Day = 86_400_000_000
u64: Microsecond in Week = 604_800_000_000

u8: Millisecond in Millisecond = 1
u16: Millisecond in Second = 1_000
u16: Millisecond in Minute = 60_000
u32: Millisecond in Hour = 3_600_000
u32: Millisecond in Day = 86_400_000
u32: Millisecond in Week = 604_800_000

u8: Second in Second = 1
u8: Second in Minute = 60
u16: Second in Hour = 3_600
u32: Second in Day = 86_400
u32: Second in Week = 604_800

u8: Minute in Minute = 1
u8: Minute in Hour = 60
u16: Minute in Day = 1_440
u16: Minute in Week = 10_080

u8: Hour in Hour = 1
u8: Hour in Day = 24
u8: Hour in Week = 168

u8: Day in Day = 1
u8: Day in Week = 7
Nanosecond ("nanosecond") per {
Microsecond: u16 = 1_000
Millisecond: u32 = 1_000_000
Second: u32 = 1_000_000_000
Minute: u64 = 60_000_000_000
Hour: u64 = 3_600_000_000_000
Day: u64 = 86_400_000_000_000
Week: u64 = 604_800_000_000_000
}
Microsecond ("microsecond") per {
Millisecond: u16 = 1_000
Second: u32 = 1_000_000
Minute: u32 = 60_000_000
Hour: u32 = 3_600_000_000
Day: u64 = 86_400_000_000
Week: u64 = 604_800_000_000
}
Millisecond ("millisecond") per {
Second: u16 = 1_000
Minute: u16 = 60_000
Hour: u32 = 3_600_000
Day: u32 = 86_400_000
Week: u32 = 604_800_000
}
Second ("second") per {
Minute: u8 = 60
Hour: u16 = 3_600
Day: u32 = 86_400
Week: u32 = 604_800
}
Minute ("minute") per {
Hour: u8 = 60
Day: u16 = 1_440
Week: u16 = 10_080
}
Hour ("hour") per {
Day: u8 = 24
Week: u8 = 168
}
Day ("day") per {
Week: u8 = 7
}
Week ("week") per {}
}
4 changes: 2 additions & 2 deletions time-macros/src/offset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,14 @@ pub(crate) fn parse(chars: &mut Peekable<token_stream::IntoIter>) -> Result<Offs
span_start: Some(hours_span),
span_end: Some(hours_span),
})
} else if minutes >= Minute.per(Hour) as _ {
} else if minutes >= Minute::per(Hour) as _ {
Err(Error::InvalidComponent {
name: "minute",
value: minutes.to_string(),
span_start: Some(minutes_span),
span_end: Some(minutes_span),
})
} else if seconds >= Second.per(Minute) as _ {
} else if seconds >= Second::per(Minute) as _ {
Err(Error::InvalidComponent {
name: "second",
value: seconds.to_string(),
Expand Down
8 changes: 4 additions & 4 deletions time-macros/src/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,21 +73,21 @@ pub(crate) fn parse(chars: &mut Peekable<token_stream::IntoIter>) -> Result<Time
(hour, Period::Pm) => hour + 12,
};

if hour >= Hour.per(Day) {
if hour >= Hour::per(Day) {
Err(Error::InvalidComponent {
name: "hour",
value: hour.to_string(),
span_start: Some(hour_span),
span_end: Some(period_span.unwrap_or(hour_span)),
})
} else if minute >= Minute.per(Hour) {
} else if minute >= Minute::per(Hour) {
Err(Error::InvalidComponent {
name: "minute",
value: minute.to_string(),
span_start: Some(minute_span),
span_end: Some(minute_span),
})
} else if second >= Second.per(Minute) as _ {
} else if second >= Second::per(Minute) as _ {
Err(Error::InvalidComponent {
name: "second",
value: second.to_string(),
Expand All @@ -99,7 +99,7 @@ pub(crate) fn parse(chars: &mut Peekable<token_stream::IntoIter>) -> Result<Time
hour,
minute,
second: second.trunc() as _,
nanosecond: (second.fract() * Nanosecond.per(Second) as f64).round() as _,
nanosecond: (second.fract() * Nanosecond::per(Second) as f64).round() as _,
})
}
}
Expand Down
4 changes: 2 additions & 2 deletions time/src/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -798,7 +798,7 @@ impl Date {
/// );
/// ```
pub const fn checked_add_std(self, duration: StdDuration) -> Option<Self> {
let whole_days = duration.as_secs() / Second.per(Day) as u64;
let whole_days = duration.as_secs() / Second::per(Day) as u64;
if whole_days > i32::MAX as u64 {
return None;
}
Expand Down Expand Up @@ -886,7 +886,7 @@ impl Date {
/// );
/// ```
pub const fn checked_sub_std(self, duration: StdDuration) -> Option<Self> {
let whole_days = duration.as_secs() / Second.per(Day) as u64;
let whole_days = duration.as_secs() / Second::per(Day) as u64;
if whole_days > i32::MAX as u64 {
return None;
}
Expand Down
38 changes: 19 additions & 19 deletions time/src/date_time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,16 +266,16 @@ impl<O: MaybeOffset> DateTime<O> {

// Use the unchecked method here, as the input validity has already been verified.
let date = Date::from_julian_day_unchecked(
UNIX_EPOCH_JULIAN_DAY + div_floor!(timestamp, Second.per(Day) as i64) as i32,
UNIX_EPOCH_JULIAN_DAY + div_floor!(timestamp, Second::per(Day) as i64) as i32,
);

let seconds_within_day = timestamp.rem_euclid(Second.per(Day) as _);
let seconds_within_day = timestamp.rem_euclid(Second::per(Day) as _);
// Safety: All values are in range.
let time = unsafe {
Time::__from_hms_nanos_unchecked(
(seconds_within_day / Second.per(Hour) as i64) as _,
((seconds_within_day % Second.per(Hour) as i64) / Minute.per(Hour) as i64) as _,
(seconds_within_day % Second.per(Minute) as i64) as _,
(seconds_within_day / Second::per(Hour) as i64) as _,
((seconds_within_day % Second::per(Hour) as i64) / Minute::per(Hour) as i64) as _,
(seconds_within_day % Second::per(Minute) as i64) as _,
0,
)
};
Expand All @@ -293,7 +293,7 @@ impl<O: MaybeOffset> DateTime<O> {
{
let datetime = const_try!(Self::from_unix_timestamp(div_floor!(
timestamp,
Nanosecond.per(Second) as i128
Nanosecond::per(Second) as i128
) as i64));

Ok(Self {
Expand All @@ -304,7 +304,7 @@ impl<O: MaybeOffset> DateTime<O> {
datetime.hour(),
datetime.minute(),
datetime.second(),
timestamp.rem_euclid(Nanosecond.per(Second) as _) as u32,
timestamp.rem_euclid(Nanosecond::per(Second) as _) as u32,
)
},
offset: maybe_offset_from_offset::<O>(UtcOffset::UTC),
Expand Down Expand Up @@ -467,9 +467,9 @@ impl<O: MaybeOffset> DateTime<O> {
let offset = maybe_offset_as_offset::<O>(self.offset).whole_seconds() as i64;

let days =
(self.to_julian_day() as i64 - UNIX_EPOCH_JULIAN_DAY as i64) * Second.per(Day) as i64;
let hours = self.hour() as i64 * Second.per(Hour) as i64;
let minutes = self.minute() as i64 * Second.per(Minute) as i64;
(self.to_julian_day() as i64 - UNIX_EPOCH_JULIAN_DAY as i64) * Second::per(Day) as i64;
let hours = self.hour() as i64 * Second::per(Hour) as i64;
let minutes = self.minute() as i64 * Second::per(Minute) as i64;
let seconds = self.second() as i64;
days + hours + minutes + seconds - offset
}
Expand All @@ -478,7 +478,7 @@ impl<O: MaybeOffset> DateTime<O> {
where
O: HasLogicalOffset,
{
self.unix_timestamp() as i128 * Nanosecond.per(Second) as i128 + self.nanosecond() as i128
self.unix_timestamp() as i128 * Nanosecond::per(Second) as i128 + self.nanosecond() as i128
}
// endregion unix timestamp getters
// endregion: getters
Expand Down Expand Up @@ -571,12 +571,12 @@ impl<O: MaybeOffset> DateTime<O> {
let mut ordinal = ordinal as i16;

// Cascade the values twice. This is needed because the values are adjusted twice above.
cascade!(second in 0..Second.per(Minute) as i16 => minute);
cascade!(second in 0..Second.per(Minute) as i16 => minute);
cascade!(minute in 0..Minute.per(Hour) as i16 => hour);
cascade!(minute in 0..Minute.per(Hour) as i16 => hour);
cascade!(hour in 0..Hour.per(Day) as i8 => ordinal);
cascade!(hour in 0..Hour.per(Day) as i8 => ordinal);
cascade!(second in 0..Second::per(Minute) as i16 => minute);
cascade!(second in 0..Second::per(Minute) as i16 => minute);
cascade!(minute in 0..Minute::per(Hour) as i16 => hour);
cascade!(minute in 0..Minute::per(Hour) as i16 => hour);
cascade!(hour in 0..Hour::per(Day) as i8 => ordinal);
cascade!(hour in 0..Hour::per(Day) as i8 => ordinal);
cascade!(ordinal => year);

debug_assert!(ordinal > 0);
Expand Down Expand Up @@ -1187,7 +1187,7 @@ impl From<DateTime<offset_kind::Fixed>> for SystemTime {
impl From<js_sys::Date> for DateTime<offset_kind::Fixed> {
fn from(js_date: js_sys::Date) -> Self {
// get_time() returns milliseconds
let timestamp_nanos = (js_date.get_time() * Nanosecond.per(Millisecond) as f64) as i128;
let timestamp_nanos = (js_date.get_time() * Nanosecond::per(Millisecond) as f64) as i128;
Self::from_unix_timestamp_nanos(timestamp_nanos)
.expect("invalid timestamp: Timestamp cannot fit in range")
}
Expand All @@ -1202,7 +1202,7 @@ impl From<DateTime<offset_kind::Fixed>> for js_sys::Date {
fn from(datetime: DateTime<offset_kind::Fixed>) -> Self {
// new Date() takes milliseconds
let timestamp =
(datetime.unix_timestamp_nanos() / Nanosecond.per(Millisecond) as i128) as f64;
(datetime.unix_timestamp_nanos() / Nanosecond::per(Millisecond) as i128) as f64;
js_sys::Date::new(&timestamp.into())
}
}
Expand Down
Loading

0 comments on commit 6d0b341

Please sign in to comment.