mod format;
#[cfg(test)]
mod tests;
use crate::{
calendar::{
CalendarError, CalendarToken, Duration, Hour, MINUTES_PER_HOUR, Microsecond, Millisecond,
NANOSECONDS_PER_SECOND, SECONDS_PER_DAY, SECONDS_PER_HOUR, SECONDS_PER_MINUTE, Sixty,
misc::{i32i64, nanosecond_string, u8i32, u8u32, u16i32, u16u32, u32i64},
nanosecond::Nanosecond,
},
collection::{ArrayString, ArrayStringU8},
};
use core::{
fmt::{Debug, Display, Formatter},
hint::unreachable_unchecked,
};
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Time {
hour: Hour,
minute: Sixty,
second: Sixty,
nanosecond: Nanosecond,
}
impl Time {
pub const MAX: Self = Self::from_hms_ns(Hour::N23, Sixty::N59, Sixty::N59, Nanosecond::MAX);
pub const ZERO: Self = Self::from_hms_ns(Hour::N0, Sixty::N0, Sixty::N0, Nanosecond::ZERO);
#[inline]
pub const fn from_hms(hour: Hour, minute: Sixty, second: Sixty) -> Self {
Self { hour, minute, second, nanosecond: Nanosecond::ZERO }
}
#[inline]
pub const fn from_hms_ms(
hour: Hour,
minute: Sixty,
second: Sixty,
millisecond: Millisecond,
) -> Self {
Self::from_hms_ns(hour, minute, second, millisecond.to_ns())
}
#[inline]
pub const fn from_hms_ns(
hour: Hour,
minute: Sixty,
second: Sixty,
nanosecond: Nanosecond,
) -> Self {
Self { hour, minute, second, nanosecond }
}
#[inline]
pub const fn from_hms_us(
hour: Hour,
minute: Sixty,
second: Sixty,
microsecond: Microsecond,
) -> Self {
Self::from_hms_ns(hour, minute, second, microsecond.to_ns())
}
#[inline]
pub fn from_iso8601(bytes: &[u8]) -> crate::Result<Self> {
static TOKENS: &[CalendarToken] = &[
CalendarToken::TwoDigitHour,
CalendarToken::Colon,
CalendarToken::TwoDigitMinute,
CalendarToken::Colon,
CalendarToken::TwoDigitSecond,
CalendarToken::DotNano,
];
Self::parse(bytes, TOKENS.iter().copied())
}
#[inline]
pub const fn add(self, duration: Duration) -> Result<Self, CalendarError> {
let (this, remaining) = self.overflowing_add(duration);
if remaining != 0 {
return Err(CalendarError::ArithmeticOverflow);
}
Ok(this)
}
#[inline]
pub const fn hour(self) -> Hour {
self.hour
}
#[inline]
pub fn iso8601(self) -> ArrayStringU8<18> {
let mut array = ArrayString::new();
let _rslt0 = array.push_str(self.hour().num_str());
let _rslt1 = array.push(':');
let _rslt2 = array.push_str(self.minute().num_str());
let _rslt3 = array.push(':');
let _rslt4 = array.push_str(self.second().num_str());
let nanosecond = self.nanosecond();
if nanosecond.num() > 0 {
let _rslt5 = array.push('.');
let _rslt6 = array.push_str(&nanosecond_string(nanosecond.num()));
}
array
}
#[inline]
pub const fn minute(self) -> Sixty {
self.minute
}
#[inline]
pub const fn nanosecond(self) -> Nanosecond {
self.nanosecond
}
#[inline]
#[must_use]
pub const fn overflowing_add(self, duration: Duration) -> (Self, i64) {
if duration.is_zero() {
return (self, 0);
}
let mut seconds = u32i64(self.seconds_since_mn());
let mut nanosecond = self.nanosecond.num().cast_signed();
seconds = seconds.wrapping_add(duration.seconds());
nanosecond = nanosecond.wrapping_add(duration.nanoseconds());
manage_out_of_bounds!(@one, 0, NANOSECONDS_PER_SECOND.cast_signed(), nanosecond, seconds);
let (day_seconds, this_hours, this_minutes, this_seconds) = Time::hms_from_seconds(seconds);
(
Self::from_hms_ns(
match Hour::from_num(this_hours) {
Ok(elem) => elem,
Err(_) => unsafe { unreachable_unchecked() },
},
match Sixty::from_num(this_minutes) {
Ok(elem) => elem,
Err(_) => unsafe { unreachable_unchecked() },
},
match Sixty::from_num(this_seconds) {
Ok(elem) => elem,
Err(_) => unsafe { unreachable_unchecked() },
},
match Nanosecond::from_num(nanosecond.cast_unsigned()) {
Ok(elem) => elem,
Err(_) => unsafe { unreachable_unchecked() },
},
),
seconds.wrapping_sub(i32i64(day_seconds)),
)
}
#[inline]
pub const fn overflowing_sub(self, duration: Duration) -> (Self, i64) {
let (time, rhs) = self.overflowing_add(duration.neg());
#[expect(clippy::arithmetic_side_effects, reason = "`rhs` will never reach `i64::MAX`")]
(time, -rhs)
}
#[inline]
pub const fn second(self) -> Sixty {
self.second
}
#[inline]
pub const fn seconds_since_mn(self) -> u32 {
let mut rslt = (u8u32(self.hour().num())).wrapping_mul(u16u32(SECONDS_PER_HOUR));
rslt = rslt.wrapping_add(u8u32(self.minute().num()).wrapping_mul(u8u32(SECONDS_PER_MINUTE)));
rslt.wrapping_add(u8u32(self.second().num()))
}
#[inline]
pub const fn sub(self, duration: Duration) -> Result<Self, CalendarError> {
let (this, remaining) = self.overflowing_sub(duration);
if remaining != 0 {
return Err(CalendarError::ArithmeticOverflow);
}
Ok(this)
}
#[inline]
#[must_use]
pub const fn trunc_to_ms(self) -> Self {
let mut new = self;
new.nanosecond = new.nanosecond.to_ms().to_ns();
new
}
#[inline]
#[must_use]
pub const fn trunc_to_sec(self) -> Self {
let mut new = self;
new.nanosecond = Nanosecond::ZERO;
new
}
#[inline]
#[must_use]
pub const fn trunc_to_us(self) -> Self {
let mut new = self;
new.nanosecond = new.nanosecond.to_us().to_ns();
new
}
#[expect(clippy::arithmetic_side_effects, reason = "divisors are constants")]
#[expect(
clippy::cast_possible_truncation,
reason = "resulting values of divisions and modules don't extrapolate associated types"
)]
#[expect(
clippy::cast_sign_loss,
reason = "resulting values of divisions and modules don't extrapolate associated types"
)]
pub(crate) const fn hms_from_seconds(seconds: i64) -> (i32, u8, u8, u8) {
let day_seconds = seconds.rem_euclid(u32i64(SECONDS_PER_DAY)) as i32;
let hour = (day_seconds / u16i32(SECONDS_PER_HOUR)) as u8;
let minute = ((day_seconds % u16i32(SECONDS_PER_HOUR)) / u8i32(MINUTES_PER_HOUR)) as u8;
let second = (day_seconds % u8i32(SECONDS_PER_MINUTE)) as u8;
(day_seconds, hour, minute, second)
}
}
impl Debug for Time {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.write_str(&self.iso8601())
}
}
impl Default for Time {
#[inline]
fn default() -> Self {
Self::ZERO
}
}
impl Display for Time {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.write_str(&self.iso8601())
}
}
#[cfg(feature = "serde")]
mod serde {
use crate::calendar::Time;
use core::fmt;
use serde::{
Deserialize, Deserializer, Serialize, Serializer,
de::{Error, Visitor},
};
impl<'de> Deserialize<'de> for Time {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct LocalVisitor;
impl Visitor<'_> for LocalVisitor {
type Value = Time;
#[inline]
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a formatted time string")
}
#[inline]
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: Error,
{
Time::from_iso8601(v).map_err(E::custom)
}
#[inline]
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: Error,
{
Time::from_iso8601(value.as_bytes()).map_err(E::custom)
}
}
deserializer.deserialize_str(LocalVisitor)
}
}
impl Serialize for Time {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.iso8601())
}
}
}