use std::{
num::{
NonZero,
NonZeroU32,
},
time::{
Duration,
Instant,
},
};
use super::{
nice_uint,
NiceChar,
};
#[derive(Clone, Copy)]
pub struct NiceClock {
data: [NiceChar; 8],
}
nice_uint!(@traits NiceClock);
macro_rules! from_small {
($($ty:ty),+ $(,)?) => ($(
impl From<$ty> for NiceClock {
#[inline]
fn from(num: $ty) -> Self { Self::from(u32::from(num)) }
}
impl From<NonZero<$ty>> for NiceClock {
#[inline]
fn from(num: NonZero<$ty>) -> Self { Self::from(num.get()) }
}
)+);
}
macro_rules! from_big {
($($ty:ty),+ $(,)?) => ($(
impl From<$ty> for NiceClock {
#[expect(clippy::cast_possible_truncation, reason = "False positive.")]
#[inline]
fn from(num: $ty) -> Self {
if num < 86_400 { Self::from(num as u32) }
else { Self::MAX }
}
}
impl From<NonZero<$ty>> for NiceClock {
#[inline]
fn from(num: NonZero<$ty>) -> Self { Self::from(num.get()) }
}
)+);
}
macro_rules! from_signed {
($($ty:ty),+ $(,)?) => ($(
impl From<$ty> for NiceClock {
#[inline]
fn from(num: $ty) -> Self {
if num <= 0 { Self::MIN }
else { Self::from(num.cast_unsigned()) }
}
}
impl From<NonZero<$ty>> for NiceClock {
#[inline]
fn from(num: NonZero<$ty>) -> Self { Self::from(num.get()) }
}
)+);
}
from_small!(u8, u16);
from_big!(u64, u128, usize);
from_signed!(i8, i16, i32, i64, i128, isize);
impl From<Duration> for NiceClock {
#[inline]
fn from(src: Duration) -> Self { Self::from(src.as_secs()) }
}
impl From<Instant> for NiceClock {
#[inline]
fn from(src: Instant) -> Self { Self::from(src.elapsed()) }
}
impl From<u32> for NiceClock {
#[expect(clippy::cast_possible_truncation, reason = "False positive.")]
#[expect(clippy::cast_lossless, reason = "For performance (inlining).")]
#[inline]
fn from(mut num: u32) -> Self {
if 86_399 < num { return Self::MAX; }
let h =
if num >= 3600 {
let tmp = ((num * 0x91A3) >> 27) as u8;
num -= tmp as u32 * 3600;
[
NiceChar::from_digit_u8(tmp / 10),
NiceChar::from_digit_u8(tmp),
]
}
else { [NiceChar::Digit0, NiceChar::Digit0] };
let m =
if num >= 60 {
let tmp = ((num * 0x889) >> 17) as u8;
num -= tmp as u32 * 60;
[
NiceChar::from_digit_u8(tmp / 10),
NiceChar::from_digit_u8(tmp),
]
}
else { [NiceChar::Digit0, NiceChar::Digit0] };
Self {
data: [
h[0], h[1], NiceChar::Colon,
m[0], m[1], NiceChar::Colon,
NiceChar::from_digit_u32(num / 10),
NiceChar::from_digit_u32(num),
]
}
}
}
impl From<NonZeroU32> for NiceClock {
#[inline]
fn from(num: NonZeroU32) -> Self { Self::from(num.get()) }
}
impl From<NiceClock> for [u8; 8] {
#[inline]
fn from(src: NiceClock) -> Self { src.data.map(|b| b as u8) }
}
impl NiceClock {
pub const MIN: Self = Self {
data: [
NiceChar::Digit0, NiceChar::Digit0, NiceChar::Colon,
NiceChar::Digit0, NiceChar::Digit0, NiceChar::Colon,
NiceChar::Digit0, NiceChar::Digit0,
],
};
pub const MAX: Self = Self {
data: [
NiceChar::Digit2, NiceChar::Digit3, NiceChar::Colon,
NiceChar::Digit5, NiceChar::Digit9, NiceChar::Colon,
NiceChar::Digit5, NiceChar::Digit9,
],
};
}
impl NiceClock {
#[must_use]
#[inline]
pub const fn as_bytes(&self) -> &[u8] {
NiceChar::as_bytes(self.data.as_slice())
}
#[must_use]
#[inline]
pub const fn as_str(&self) -> &str {
NiceChar::as_str(self.data.as_slice())
}
#[must_use]
#[inline]
pub const fn is_empty(&self) -> bool { false }
#[must_use]
#[inline]
pub const fn len(&self) -> usize { 8 }
#[must_use]
pub const fn hours(self) -> u8 {
(self.data[0] as u8 - b'0') * 10 + (self.data[1] as u8 - b'0')
}
#[must_use]
pub const fn minutes(self) -> u8 {
(self.data[3] as u8 - b'0') * 10 + (self.data[4] as u8 - b'0')
}
#[must_use]
pub const fn seconds(self) -> u8 {
(self.data[6] as u8 - b'0') * 10 + (self.data[7] as u8 - b'0')
}
}
impl NiceClock {
#[expect(clippy::cast_possible_truncation, reason = "False positive.")]
#[inline]
pub const fn replace(&mut self, mut num: u32) {
if 86_399 < num {
self.data = Self::MAX.data;
return;
}
if num >= 3600 {
let tmp = ((num * 0x91A3) >> 27) as u8;
num -= tmp as u32 * 3600;
self.data[0] = NiceChar::from_digit_u8(tmp / 10);
self.data[1] = NiceChar::from_digit_u8(tmp);
}
else {
self.data[0] = NiceChar::Digit0;
self.data[1] = NiceChar::Digit0;
}
if num >= 60 {
let tmp = ((num * 0x889) >> 17) as u8;
num -= tmp as u32 * 60;
self.data[3] = NiceChar::from_digit_u8(tmp / 10);
self.data[4] = NiceChar::from_digit_u8(tmp);
}
else {
self.data[3] = NiceChar::Digit0;
self.data[4] = NiceChar::Digit0;
}
self.data[6] = NiceChar::from_digit_u32(num / 10);
self.data[7] = NiceChar::from_digit_u32(num);
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn t_nice_clock() {
let mut set = Vec::new();
for h in 0..24_u32 {
for m in 0..60_u32 {
for s in 0..60_u32 {
set.push([h, m, s]);
}
}
}
#[cfg(miri)]
{
fastrand::shuffle(&mut set);
set.truncate(500);
set.push([0, 0, 0]);
set.push([23, 59, 59]);
set.sort();
set.dedup();
}
let mut last = NiceClock::MIN;
for [h, m, s] in set {
let total = s + m * 60 + h * 60 * 60;
let clock = NiceClock::from(total);
assert_eq!(clock.as_str(), format!("{h:02}:{m:02}:{s:02}"));
if total == 0 { assert_eq!(last, clock); }
else { assert_ne!(last, clock); }
last.replace(total);
assert_eq!(last, clock);
assert_eq!(clock, NiceClock::from(u64::from(total)));
assert_eq!(clock, NiceClock::from(u128::from(total)));
}
assert_eq!(last, NiceClock::MAX);
assert_eq!(last, NiceClock::from(i128::MAX));
assert_eq!(last, NiceClock::from(i32::MAX));
assert_eq!(last, NiceClock::from(i64::MAX));
assert_eq!(last, NiceClock::from(isize::MAX));
assert_eq!(last, NiceClock::from(u128::MAX));
assert_eq!(last, NiceClock::from(u32::MAX));
assert_eq!(last, NiceClock::from(u64::MAX));
assert_eq!(last, NiceClock::from(usize::MAX));
last.replace(0);
assert_eq!(last, NiceClock::MIN);
assert_eq!(last, NiceClock::from(i8::MIN));
assert_eq!(last, NiceClock::from(i16::MIN));
assert_eq!(last, NiceClock::from(i32::MIN));
assert_eq!(last, NiceClock::from(i64::MIN));
assert_eq!(last, NiceClock::from(i128::MIN));
assert_eq!(last, NiceClock::from(isize::MIN));
}
}