//! The [`Time`] struct and its associated `impl`s. use core::fmt; use core::ops::{Add, Sub}; use core::time::Duration as StdDuration; #[cfg(feature = "formatting")] use std::io; #[cfg(feature = "formatting")] use crate::formatting::Formattable; #[cfg(feature = "parsing")] use crate::parsing::Parsable; use crate::util::DateAdjustment; use crate::{error, Duration}; /// By explicitly inserting this enum where padding is expected, the compiler is able to better /// perform niche value optimization. #[repr(u8)] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub(crate) enum Padding { #[allow(clippy::missing_docs_in_private_items)] Optimize, } /// The clock time within a given date. Nanosecond precision. /// /// All minutes are assumed to have exactly 60 seconds; no attempt is made to handle leap seconds /// (either positive or negative). /// /// When comparing two `Time`s, they are assumed to be in the same calendar date. #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Time { #[allow(clippy::missing_docs_in_private_items)] hour: u8, #[allow(clippy::missing_docs_in_private_items)] minute: u8, #[allow(clippy::missing_docs_in_private_items)] second: u8, #[allow(clippy::missing_docs_in_private_items)] nanosecond: u32, #[allow(clippy::missing_docs_in_private_items)] padding: Padding, } impl Time { /// Create a `Time` that is exactly midnight. /// /// ```rust /// # use time::Time; /// # use time_macros::time; /// assert_eq!(Time::MIDNIGHT, time!(0:00)); /// ``` pub const MIDNIGHT: Self = Self::__from_hms_nanos_unchecked(0, 0, 0, 0); /// The smallest value that can be represented by `Time`. /// /// `00:00:00.0` pub(crate) const MIN: Self = Self::__from_hms_nanos_unchecked(0, 0, 0, 0); /// The largest value that can be represented by `Time`. /// /// `23:59:59.999_999_999` pub(crate) const MAX: Self = Self::__from_hms_nanos_unchecked(23, 59, 59, 999_999_999); // region: constructors /// Create a `Time` from its components. #[doc(hidden)] pub const fn __from_hms_nanos_unchecked( hour: u8, minute: u8, second: u8, nanosecond: u32, ) -> Self { debug_assert!(hour < 24); debug_assert!(minute < 60); debug_assert!(second < 60); debug_assert!(nanosecond < 1_000_000_000); Self { hour, minute, second, nanosecond, padding: Padding::Optimize, } } /// Attempt to create a `Time` from the hour, minute, and second. /// /// ```rust /// # use time::Time; /// assert!(Time::from_hms(1, 2, 3).is_ok()); /// ``` /// /// ```rust /// # use time::Time; /// assert!(Time::from_hms(24, 0, 0).is_err()); // 24 isn't a valid hour. /// assert!(Time::from_hms(0, 60, 0).is_err()); // 60 isn't a valid minute. /// assert!(Time::from_hms(0, 0, 60).is_err()); // 60 isn't a valid second. /// ``` pub const fn from_hms(hour: u8, minute: u8, second: u8) -> Result { ensure_value_in_range!(hour in 0 => 23); ensure_value_in_range!(minute in 0 => 59); ensure_value_in_range!(second in 0 => 59); Ok(Self::__from_hms_nanos_unchecked(hour, minute, second, 0)) } /// Attempt to create a `Time` from the hour, minute, second, and millisecond. /// /// ```rust /// # use time::Time; /// assert!(Time::from_hms_milli(1, 2, 3, 4).is_ok()); /// ``` /// /// ```rust /// # use time::Time; /// assert!(Time::from_hms_milli(24, 0, 0, 0).is_err()); // 24 isn't a valid hour. /// assert!(Time::from_hms_milli(0, 60, 0, 0).is_err()); // 60 isn't a valid minute. /// assert!(Time::from_hms_milli(0, 0, 60, 0).is_err()); // 60 isn't a valid second. /// assert!(Time::from_hms_milli(0, 0, 0, 1_000).is_err()); // 1_000 isn't a valid millisecond. /// ``` pub const fn from_hms_milli( hour: u8, minute: u8, second: u8, millisecond: u16, ) -> Result { ensure_value_in_range!(hour in 0 => 23); ensure_value_in_range!(minute in 0 => 59); ensure_value_in_range!(second in 0 => 59); ensure_value_in_range!(millisecond in 0 => 999); Ok(Self::__from_hms_nanos_unchecked( hour, minute, second, millisecond as u32 * 1_000_000, )) } /// Attempt to create a `Time` from the hour, minute, second, and microsecond. /// /// ```rust /// # use time::Time; /// assert!(Time::from_hms_micro(1, 2, 3, 4).is_ok()); /// ``` /// /// ```rust /// # use time::Time; /// assert!(Time::from_hms_micro(24, 0, 0, 0).is_err()); // 24 isn't a valid hour. /// assert!(Time::from_hms_micro(0, 60, 0, 0).is_err()); // 60 isn't a valid minute. /// assert!(Time::from_hms_micro(0, 0, 60, 0).is_err()); // 60 isn't a valid second. /// assert!(Time::from_hms_micro(0, 0, 0, 1_000_000).is_err()); // 1_000_000 isn't a valid microsecond. /// ``` pub const fn from_hms_micro( hour: u8, minute: u8, second: u8, microsecond: u32, ) -> Result { ensure_value_in_range!(hour in 0 => 23); ensure_value_in_range!(minute in 0 => 59); ensure_value_in_range!(second in 0 => 59); ensure_value_in_range!(microsecond in 0 => 999_999); Ok(Self::__from_hms_nanos_unchecked( hour, minute, second, microsecond * 1_000, )) } /// Attempt to create a `Time` from the hour, minute, second, and nanosecond. /// /// ```rust /// # use time::Time; /// assert!(Time::from_hms_nano(1, 2, 3, 4).is_ok()); /// ``` /// /// ```rust /// # use time::Time; /// assert!(Time::from_hms_nano(24, 0, 0, 0).is_err()); // 24 isn't a valid hour. /// assert!(Time::from_hms_nano(0, 60, 0, 0).is_err()); // 60 isn't a valid minute. /// assert!(Time::from_hms_nano(0, 0, 60, 0).is_err()); // 60 isn't a valid second. /// assert!(Time::from_hms_nano(0, 0, 0, 1_000_000_000).is_err()); // 1_000_000_000 isn't a valid nanosecond. /// ``` pub const fn from_hms_nano( hour: u8, minute: u8, second: u8, nanosecond: u32, ) -> Result { ensure_value_in_range!(hour in 0 => 23); ensure_value_in_range!(minute in 0 => 59); ensure_value_in_range!(second in 0 => 59); ensure_value_in_range!(nanosecond in 0 => 999_999_999); Ok(Self::__from_hms_nanos_unchecked( hour, minute, second, nanosecond, )) } // endregion constructors // region: getters /// Get the clock hour, minute, and second. /// /// ```rust /// # use time_macros::time; /// assert_eq!(time!(0:00:00).as_hms(), (0, 0, 0)); /// assert_eq!(time!(23:59:59).as_hms(), (23, 59, 59)); /// ``` pub const fn as_hms(self) -> (u8, u8, u8) { (self.hour, self.minute, self.second) } /// Get the clock hour, minute, second, and millisecond. /// /// ```rust /// # use time_macros::time; /// assert_eq!(time!(0:00:00).as_hms_milli(), (0, 0, 0, 0)); /// assert_eq!(time!(23:59:59.999).as_hms_milli(), (23, 59, 59, 999)); /// ``` pub const fn as_hms_milli(self) -> (u8, u8, u8, u16) { ( self.hour, self.minute, self.second, (self.nanosecond / 1_000_000) as u16, ) } /// Get the clock hour, minute, second, and microsecond. /// /// ```rust /// # use time_macros::time; /// assert_eq!(time!(0:00:00).as_hms_micro(), (0, 0, 0, 0)); /// assert_eq!( /// time!(23:59:59.999_999).as_hms_micro(), /// (23, 59, 59, 999_999) /// ); /// ``` pub const fn as_hms_micro(self) -> (u8, u8, u8, u32) { (self.hour, self.minute, self.second, self.nanosecond / 1_000) } /// Get the clock hour, minute, second, and nanosecond. /// /// ```rust /// # use time_macros::time; /// assert_eq!(time!(0:00:00).as_hms_nano(), (0, 0, 0, 0)); /// assert_eq!( /// time!(23:59:59.999_999_999).as_hms_nano(), /// (23, 59, 59, 999_999_999) /// ); /// ``` pub const fn as_hms_nano(self) -> (u8, u8, u8, u32) { (self.hour, self.minute, self.second, self.nanosecond) } /// Get the clock hour. /// /// The returned value will always be in the range `0..24`. /// /// ```rust /// # use time_macros::time; /// assert_eq!(time!(0:00:00).hour(), 0); /// assert_eq!(time!(23:59:59).hour(), 23); /// ``` pub const fn hour(self) -> u8 { self.hour } /// Get the minute within the hour. /// /// The returned value will always be in the range `0..60`. /// /// ```rust /// # use time_macros::time; /// assert_eq!(time!(0:00:00).minute(), 0); /// assert_eq!(time!(23:59:59).minute(), 59); /// ``` pub const fn minute(self) -> u8 { self.minute } /// Get the second within the minute. /// /// The returned value will always be in the range `0..60`. /// /// ```rust /// # use time_macros::time; /// assert_eq!(time!(0:00:00).second(), 0); /// assert_eq!(time!(23:59:59).second(), 59); /// ``` pub const fn second(self) -> u8 { self.second } /// Get the milliseconds within the second. /// /// The returned value will always be in the range `0..1_000`. /// /// ```rust /// # use time_macros::time; /// assert_eq!(time!(0:00).millisecond(), 0); /// assert_eq!(time!(23:59:59.999).millisecond(), 999); /// ``` pub const fn millisecond(self) -> u16 { (self.nanosecond / 1_000_000) as _ } /// Get the microseconds within the second. /// /// The returned value will always be in the range `0..1_000_000`. /// /// ```rust /// # use time_macros::time; /// assert_eq!(time!(0:00).microsecond(), 0); /// assert_eq!(time!(23:59:59.999_999).microsecond(), 999_999); /// ``` pub const fn microsecond(self) -> u32 { self.nanosecond / 1_000 } /// Get the nanoseconds within the second. /// /// The returned value will always be in the range `0..1_000_000_000`. /// /// ```rust /// # use time_macros::time; /// assert_eq!(time!(0:00).nanosecond(), 0); /// assert_eq!(time!(23:59:59.999_999_999).nanosecond(), 999_999_999); /// ``` pub const fn nanosecond(self) -> u32 { self.nanosecond } // endregion getters // region: arithmetic helpers /// Add the sub-day time of the [`Duration`] to the `Time`. Wraps on overflow, returning whether /// the date is different. pub(crate) const fn adjusting_add(self, duration: Duration) -> (DateAdjustment, Self) { let mut nanoseconds = self.nanosecond as i32 + duration.subsec_nanoseconds(); let mut seconds = self.second as i8 + (duration.whole_seconds() % 60) as i8; let mut minutes = self.minute as i8 + (duration.whole_minutes() % 60) as i8; let mut hours = self.hour as i8 + (duration.whole_hours() % 24) as i8; let mut date_adjustment = DateAdjustment::None; cascade!(nanoseconds in 0..1_000_000_000 => seconds); cascade!(seconds in 0..60 => minutes); cascade!(minutes in 0..60 => hours); if hours >= 24 { hours -= 24; date_adjustment = DateAdjustment::Next; } else if hours < 0 { hours += 24; date_adjustment = DateAdjustment::Previous; } ( date_adjustment, Self::__from_hms_nanos_unchecked( hours as _, minutes as _, seconds as _, nanoseconds as _, ), ) } /// Subtract the sub-day time of the [`Duration`] to the `Time`. Wraps on overflow, returning /// whether the date is different. pub(crate) const fn adjusting_sub(self, duration: Duration) -> (DateAdjustment, Self) { let mut nanoseconds = self.nanosecond as i32 - duration.subsec_nanoseconds(); let mut seconds = self.second as i8 - (duration.whole_seconds() % 60) as i8; let mut minutes = self.minute as i8 - (duration.whole_minutes() % 60) as i8; let mut hours = self.hour as i8 - (duration.whole_hours() % 24) as i8; let mut date_adjustment = DateAdjustment::None; cascade!(nanoseconds in 0..1_000_000_000 => seconds); cascade!(seconds in 0..60 => minutes); cascade!(minutes in 0..60 => hours); if hours >= 24 { hours -= 24; date_adjustment = DateAdjustment::Next; } else if hours < 0 { hours += 24; date_adjustment = DateAdjustment::Previous; } ( date_adjustment, Self::__from_hms_nanos_unchecked( hours as _, minutes as _, seconds as _, nanoseconds as _, ), ) } /// Add the sub-day time of the [`std::time::Duration`] to the `Time`. Wraps on overflow, /// returning whether the date is the previous date as the first element of the tuple. pub(crate) const fn adjusting_add_std(self, duration: StdDuration) -> (bool, Self) { let mut nanosecond = self.nanosecond + duration.subsec_nanos(); let mut second = self.second + (duration.as_secs() % 60) as u8; let mut minute = self.minute + ((duration.as_secs() / 60) % 60) as u8; let mut hour = self.hour + ((duration.as_secs() / 3_600) % 24) as u8; let mut is_next_day = false; cascade!(nanosecond in 0..1_000_000_000 => second); cascade!(second in 0..60 => minute); cascade!(minute in 0..60 => hour); if hour >= 24 { hour -= 24; is_next_day = true; } ( is_next_day, Self::__from_hms_nanos_unchecked(hour, minute, second, nanosecond), ) } /// Subtract the sub-day time of the [`std::time::Duration`] to the `Time`. Wraps on overflow, /// returning whether the date is the previous date as the first element of the tuple. pub(crate) const fn adjusting_sub_std(self, duration: StdDuration) -> (bool, Self) { let mut nanosecond = self.nanosecond as i32 - duration.subsec_nanos() as i32; let mut second = self.second as i8 - (duration.as_secs() % 60) as i8; let mut minute = self.minute as i8 - ((duration.as_secs() / 60) % 60) as i8; let mut hour = self.hour as i8 - ((duration.as_secs() / 3_600) % 24) as i8; let mut is_previous_day = false; cascade!(nanosecond in 0..1_000_000_000 => second); cascade!(second in 0..60 => minute); cascade!(minute in 0..60 => hour); if hour < 0 { hour += 24; is_previous_day = true; } ( is_previous_day, Self::__from_hms_nanos_unchecked(hour as _, minute as _, second as _, nanosecond as _), ) } // endregion arithmetic helpers // region: replacement /// Replace the clock hour. /// /// ```rust /// # use time_macros::time; /// assert_eq!( /// time!(01:02:03.004_005_006).replace_hour(7), /// Ok(time!(07:02:03.004_005_006)) /// ); /// assert!(time!(01:02:03.004_005_006).replace_hour(24).is_err()); // 24 isn't a valid hour /// ``` #[must_use = "This method does not mutate the original `Time`."] pub const fn replace_hour(self, hour: u8) -> Result { ensure_value_in_range!(hour in 0 => 23); Ok(Self::__from_hms_nanos_unchecked( hour, self.minute, self.second, self.nanosecond, )) } /// Replace the minutes within the hour. /// /// ```rust /// # use time_macros::time; /// assert_eq!( /// time!(01:02:03.004_005_006).replace_minute(7), /// Ok(time!(01:07:03.004_005_006)) /// ); /// assert!(time!(01:02:03.004_005_006).replace_minute(60).is_err()); // 60 isn't a valid minute /// ``` #[must_use = "This method does not mutate the original `Time`."] pub const fn replace_minute(self, minute: u8) -> Result { ensure_value_in_range!(minute in 0 => 59); Ok(Self::__from_hms_nanos_unchecked( self.hour, minute, self.second, self.nanosecond, )) } /// Replace the seconds within the minute. /// /// ```rust /// # use time_macros::time; /// assert_eq!( /// time!(01:02:03.004_005_006).replace_second(7), /// Ok(time!(01:02:07.004_005_006)) /// ); /// assert!(time!(01:02:03.004_005_006).replace_second(60).is_err()); // 60 isn't a valid second /// ``` #[must_use = "This method does not mutate the original `Time`."] pub const fn replace_second(self, second: u8) -> Result { ensure_value_in_range!(second in 0 => 59); Ok(Self::__from_hms_nanos_unchecked( self.hour, self.minute, second, self.nanosecond, )) } /// Replace the milliseconds within the second. /// /// ```rust /// # use time_macros::time; /// assert_eq!( /// time!(01:02:03.004_005_006).replace_millisecond(7), /// Ok(time!(01:02:03.007)) /// ); /// assert!(time!(01:02:03.004_005_006).replace_millisecond(1_000).is_err()); // 1_000 isn't a valid millisecond /// ``` #[must_use = "This method does not mutate the original `Time`."] pub const fn replace_millisecond( self, millisecond: u16, ) -> Result { ensure_value_in_range!(millisecond in 0 => 999); Ok(Self::__from_hms_nanos_unchecked( self.hour, self.minute, self.second, millisecond as u32 * 1_000_000, )) } /// Replace the microseconds within the second. /// /// ```rust /// # use time_macros::time; /// assert_eq!( /// time!(01:02:03.004_005_006).replace_microsecond(7_008), /// Ok(time!(01:02:03.007_008)) /// ); /// assert!(time!(01:02:03.004_005_006).replace_microsecond(1_000_000).is_err()); // 1_000_000 isn't a valid microsecond /// ``` #[must_use = "This method does not mutate the original `Time`."] pub const fn replace_microsecond( self, microsecond: u32, ) -> Result { ensure_value_in_range!(microsecond in 0 => 999_999); Ok(Self::__from_hms_nanos_unchecked( self.hour, self.minute, self.second, microsecond * 1000, )) } /// Replace the nanoseconds within the second. /// /// ```rust /// # use time_macros::time; /// assert_eq!( /// time!(01:02:03.004_005_006).replace_nanosecond(7_008_009), /// Ok(time!(01:02:03.007_008_009)) /// ); /// assert!(time!(01:02:03.004_005_006).replace_nanosecond(1_000_000_000).is_err()); // 1_000_000_000 isn't a valid nanosecond /// ``` #[must_use = "This method does not mutate the original `Time`."] pub const fn replace_nanosecond(self, nanosecond: u32) -> Result { ensure_value_in_range!(nanosecond in 0 => 999_999_999); Ok(Self::__from_hms_nanos_unchecked( self.hour, self.minute, self.second, nanosecond, )) } // endregion replacement } // region: formatting & parsing #[cfg(feature = "formatting")] impl Time { /// Format the `Time` using the provided [format description](crate::format_description). pub fn format_into( self, output: &mut impl io::Write, format: &(impl Formattable + ?Sized), ) -> Result { format.format_into(output, None, Some(self), None) } /// Format the `Time` using the provided [format description](crate::format_description). /// /// ```rust /// # use time::format_description; /// # use time_macros::time; /// let format = format_description::parse("[hour]:[minute]:[second]")?; /// assert_eq!(time!(12:00).format(&format)?, "12:00:00"); /// # Ok::<_, time::Error>(()) /// ``` pub fn format( self, format: &(impl Formattable + ?Sized), ) -> Result { format.format(None, Some(self), None) } } #[cfg(feature = "parsing")] impl Time { /// Parse a `Time` from the input using the provided [format /// description](crate::format_description). /// /// ```rust /// # use time::Time; /// # use time_macros::{time, format_description}; /// let format = format_description!("[hour]:[minute]:[second]"); /// assert_eq!(Time::parse("12:00:00", &format)?, time!(12:00)); /// # Ok::<_, time::Error>(()) /// ``` pub fn parse( input: &str, description: &(impl Parsable + ?Sized), ) -> Result { description.parse_time(input.as_bytes()) } } impl fmt::Display for Time { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let (value, width) = match self.nanosecond() { nanos if nanos % 10 != 0 => (nanos, 9), nanos if (nanos / 10) % 10 != 0 => (nanos / 10, 8), nanos if (nanos / 100) % 10 != 0 => (nanos / 100, 7), nanos if (nanos / 1_000) % 10 != 0 => (nanos / 1_000, 6), nanos if (nanos / 10_000) % 10 != 0 => (nanos / 10_000, 5), nanos if (nanos / 100_000) % 10 != 0 => (nanos / 100_000, 4), nanos if (nanos / 1_000_000) % 10 != 0 => (nanos / 1_000_000, 3), nanos if (nanos / 10_000_000) % 10 != 0 => (nanos / 10_000_000, 2), nanos => (nanos / 100_000_000, 1), }; write!( f, "{}:{:02}:{:02}.{value:0width$}", self.hour, self.minute, self.second, ) } } impl fmt::Debug for Time { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self, f) } } // endregion formatting & parsing // region: trait impls impl Add for Time { type Output = Self; /// Add the sub-day time of the [`Duration`] to the `Time`. Wraps on overflow. /// /// ```rust /// # use time::ext::NumericalDuration; /// # use time_macros::time; /// assert_eq!(time!(12:00) + 2.hours(), time!(14:00)); /// assert_eq!(time!(0:00:01) + (-2).seconds(), time!(23:59:59)); /// ``` fn add(self, duration: Duration) -> Self::Output { self.adjusting_add(duration).1 } } impl Add for Time { type Output = Self; /// Add the sub-day time of the [`std::time::Duration`] to the `Time`. Wraps on overflow. /// /// ```rust /// # use time::ext::NumericalStdDuration; /// # use time_macros::time; /// assert_eq!(time!(12:00) + 2.std_hours(), time!(14:00)); /// assert_eq!(time!(23:59:59) + 2.std_seconds(), time!(0:00:01)); /// ``` fn add(self, duration: StdDuration) -> Self::Output { self.adjusting_add_std(duration).1 } } impl_add_assign!(Time: Duration, StdDuration); impl Sub for Time { type Output = Self; /// Subtract the sub-day time of the [`Duration`] from the `Time`. Wraps on overflow. /// /// ```rust /// # use time::ext::NumericalDuration; /// # use time_macros::time; /// assert_eq!(time!(14:00) - 2.hours(), time!(12:00)); /// assert_eq!(time!(23:59:59) - (-2).seconds(), time!(0:00:01)); /// ``` fn sub(self, duration: Duration) -> Self::Output { self.adjusting_sub(duration).1 } } impl Sub for Time { type Output = Self; /// Subtract the sub-day time of the [`std::time::Duration`] from the `Time`. Wraps on overflow. /// /// ```rust /// # use time::ext::NumericalStdDuration; /// # use time_macros::time; /// assert_eq!(time!(14:00) - 2.std_hours(), time!(12:00)); /// assert_eq!(time!(0:00:01) - 2.std_seconds(), time!(23:59:59)); /// ``` fn sub(self, duration: StdDuration) -> Self::Output { self.adjusting_sub_std(duration).1 } } impl_sub_assign!(Time: Duration, StdDuration); impl Sub for Time { type Output = Duration; /// Subtract two `Time`s, returning the [`Duration`] between. This assumes both `Time`s are in /// the same calendar day. /// /// ```rust /// # use time::ext::NumericalDuration; /// # use time_macros::time; /// assert_eq!(time!(0:00) - time!(0:00), 0.seconds()); /// assert_eq!(time!(1:00) - time!(0:00), 1.hours()); /// assert_eq!(time!(0:00) - time!(1:00), (-1).hours()); /// assert_eq!(time!(0:00) - time!(23:00), (-23).hours()); /// ``` fn sub(self, rhs: Self) -> Self::Output { let hour_diff = (self.hour as i8) - (rhs.hour as i8); let minute_diff = (self.minute as i8) - (rhs.minute as i8); let second_diff = (self.second as i8) - (rhs.second as i8); let nanosecond_diff = (self.nanosecond as i32) - (rhs.nanosecond as i32); let seconds = hour_diff as i64 * 3_600 + minute_diff as i64 * 60 + second_diff as i64; let (seconds, nanoseconds) = if seconds > 0 && nanosecond_diff < 0 { (seconds - 1, nanosecond_diff + 1_000_000_000) } else if seconds < 0 && nanosecond_diff > 0 { (seconds + 1, nanosecond_diff - 1_000_000_000) } else { (seconds, nanosecond_diff) }; Duration::new_unchecked(seconds, nanoseconds) } } // endregion trait impls