use crate::str::Str;
use crate::run::{Runtime,RuntimePad,RuntimeUnion};
use crate::macros::{
impl_common,impl_const,
impl_traits,impl_math,impl_impl_math,
};
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))]
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
pub struct RuntimeMilli(pub(super) f32, pub(super) Str<{ RuntimeMilli::MAX_LEN }>);
crate::run::runtime::impl_runtime! {
self = RuntimeMilli,
len = RuntimeMilli::MAX_LEN,
union = as_str_milli,
other = Runtime,
other = RuntimePad,
}
impl_math!(RuntimeMilli, f32);
impl_traits!(RuntimeMilli, f32);
impl RuntimeMilli {
pub const MAX_LEN: usize = 12;
pub const ZERO_F32: f32 = 0.0;
pub const SECOND_F32: f32 = 1.0;
pub const MINUTE_F32: f32 = 60.0;
pub const HOUR_F32: f32 = 3600.0;
pub const DAY_F32: f32 = 86400.0;
pub const MAX_F32: f32 = 359999.0;
pub const UNKNOWN: Self = Self(Self::ZERO_F32, Str::from_static_str("??:??:??.???"));
pub const ZERO: Self = Self(Self::ZERO_F32, Str::from_static_str("00:00:00.000"));
pub const SECOND: Self = Self(Self::SECOND_F32, Str::from_static_str("00:00:01.000"));
pub const MINUTE: Self = Self(Self::MINUTE_F32, Str::from_static_str("00:01:00.000"));
pub const HOUR: Self = Self(Self::HOUR_F32, Str::from_static_str("01:00:00.000"));
pub const DAY: Self = Self(Self::DAY_F32, Str::from_static_str("24:00:00.000"));
pub const MAX: Self = Self(Self::MAX_F32, Str::from_static_str("99:59:59.000"));
}
macro_rules! impl_as_str_runtime_inner {
($self:expr) => {{
let u = $self.0 as u32;
let (offset, end) = if u < 600 {
(4, 4)
} else if u < 3600 {
(3, 5)
} else if u < 36000 {
(1, 7)
} else {
debug_assert!(u >= 36000);
(0, 8)
};
unsafe {
let slice = std::slice::from_raw_parts(
$self.1.as_ptr().offset(offset),
end,
);
std::str::from_utf8_unchecked(slice)
}
}};
}
pub(super) use impl_as_str_runtime_inner;
impl RuntimeMilli {
impl_common!(f32);
impl_const!();
#[inline]
#[must_use]
pub const fn as_str_runtime(&self) -> &str {
impl_as_str_runtime_inner!(self)
}
#[inline]
#[must_use]
pub const fn as_str_pad(&self) -> &str {
const END: usize = 8;
unsafe {
let slice = std::slice::from_raw_parts(
self.1.as_ptr(),
END,
);
std::str::from_utf8_unchecked(slice)
}
}
#[inline]
#[must_use]
pub const fn is_unknown(&self) -> bool {
matches!(self.1.as_bytes(), b"??:??:??.???")
}
}
impl RuntimeMilli {
#[allow(unreachable_code)]
#[inline]
pub(super) fn priv_from(runtime: f32) -> Self {
let Some((h, m, s)) = Runtime::priv_from_inner(runtime) else {
return Self::UNKNOWN;
};
if (h, m, s) == (0.0, 0.0, 0.0) {
return Self::ZERO;
}
let mut buf = [0; Self::MAX_LEN];
Self::format(
&mut buf,
h as u8,
m as u8,
s as u8,
(1000.0 * s.fract()).round() as u16,
);
Self(runtime, unsafe { Str::from_raw(buf, Self::MAX_LEN as u8) })
}
#[inline]
fn format(buf: &mut [u8; Self::MAX_LEN], hour: u8, min: u8, sec: u8, milli: u16) {
const Z: u8 = b'0';
const C: u8 = b':';
debug_assert!(hour < 100);
debug_assert!(min < 60);
debug_assert!(sec < 60);
buf[2] = C;
buf[5] = C;
buf[8] = b'.';
let mut h = crate::toa::ItoaTmp::new();
let mut m = crate::toa::ItoaTmp::new();
let mut s = crate::toa::ItoaTmp::new();
let mut i = crate::toa::ItoaTmp::new();
let h = h.format(hour).as_bytes();
let m = m.format(min).as_bytes();
let s = s.format(sec).as_bytes();
let i = i.format(milli).as_bytes();
if h.len() == 1 {
buf[0] = Z;
buf[1] = h[0];
} else {
buf[0] = h[0];
buf[1] = h[1];
}
if m.len() == 1 {
buf[3] = Z;
buf[4] = m[0];
} else {
buf[3] = m[0];
buf[4] = m[1];
}
if s.len() == 1 {
buf[6] = Z;
buf[7] = s[0];
} else {
buf[6] = s[0];
buf[7] = s[1];
}
match i.len() {
1 => {
buf[9] = Z;
buf[10] = Z;
buf[11] = i[0];
},
2 => {
buf[9] = Z;
buf[10] = i[0];
buf[11] = i[1];
},
_ => {
buf[9] = i[0];
buf[10] = i[1];
buf[11] = i[2];
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn _format_hms() {
fn s(b: &[u8]) -> &str {
std::str::from_utf8(b).unwrap()
}
let buf = &mut [0; RuntimeMilli::MAX_LEN];
RuntimeMilli::format(buf, 1, 1, 1, 555);
assert_eq!(s(buf), "01:01:01.555");
RuntimeMilli::format(buf, 1, 10, 1, 123);
assert_eq!(s(buf), "01:10:01.123");
RuntimeMilli::format(buf, 1, 1, 10, 111);
assert_eq!(s(buf), "01:01:10.111");
RuntimeMilli::format(buf, 1, 10, 10, 33);
assert_eq!(s(buf), "01:10:10.033");
RuntimeMilli::format(buf, 10, 1, 1, 1);
assert_eq!(s(buf), "10:01:01.001");
RuntimeMilli::format(buf, 10, 10, 1, 11);
assert_eq!(s(buf), "10:10:01.011");
RuntimeMilli::format(buf, 10, 1, 10, 999);
assert_eq!(s(buf), "10:01:10.999");
RuntimeMilli::format(buf, 10, 10, 10, 512);
assert_eq!(s(buf), "10:10:10.512");
RuntimeMilli::format(buf, 0, 1, 1, 100);
assert_eq!(s(buf), "00:01:01.100");
RuntimeMilli::format(buf, 0, 10, 1, 101);
assert_eq!(s(buf), "00:10:01.101");
RuntimeMilli::format(buf, 0, 1, 10, 2);
assert_eq!(s(buf), "00:01:10.002");
RuntimeMilli::format(buf, 0, 10, 10, 3);
assert_eq!(s(buf), "00:10:10.003");
}
#[test]
fn all_uint() {
for i in 0..RuntimeMilli::MAX_F32 as u32 {
let rt = RuntimeMilli::from(i);
println!("rt: {rt} - i: {i}");
assert_eq!(rt.inner() as u32, i);
assert_eq!(rt.inner() as u32, i);
println!("{rt}");
}
}
#[test]
fn all_floats() {
let mut f = 1.0;
while f < RuntimeMilli::MAX_F32 {
let rt = RuntimeMilli::from(f);
println!("rt: {rt} - f: {f}");
assert_eq!(rt, f);
f += 0.1;
}
}
#[test]
fn overflow_float() {
assert_eq!(RuntimeMilli::from(RuntimeMilli::MAX_F32 + 1.0), 0.0);
assert_eq!(RuntimeMilli::from(RuntimeMilli::MAX_F32 + 1.0), RuntimeMilli::UNKNOWN);
}
#[test]
fn overflow_uint() {
assert_eq!(RuntimeMilli::from(RuntimeMilli::MAX_F32 + 1.0), 0.0);
assert_eq!(RuntimeMilli::from(RuntimeMilli::MAX_F32 + 1.0), RuntimeMilli::UNKNOWN);
}
#[test]
fn special() {
assert_eq!(RuntimeMilli::from(f32::NAN), RuntimeMilli::UNKNOWN);
assert_eq!(RuntimeMilli::from(f32::INFINITY), RuntimeMilli::UNKNOWN);
assert_eq!(RuntimeMilli::from(f32::NEG_INFINITY), RuntimeMilli::UNKNOWN);
assert_eq!(RuntimeMilli::from(f64::NAN), RuntimeMilli::UNKNOWN);
assert_eq!(RuntimeMilli::from(f64::INFINITY), RuntimeMilli::UNKNOWN);
assert_eq!(RuntimeMilli::from(f64::NEG_INFINITY), RuntimeMilli::UNKNOWN);
}
#[test]
#[cfg(feature = "serde")]
fn serde() {
let this: RuntimeMilli = RuntimeMilli::from(111.999);
let json = serde_json::to_string(&this).unwrap();
assert_eq!(json, r#"[111.999,"00:01:51.999"]"#);
let this: RuntimeMilli = serde_json::from_str(&json).unwrap();
assert_eq!(this, 111.999);
assert_eq!(this, "00:01:51.999");
assert!(serde_json::from_str::<RuntimeMilli>(&"---").is_err());
let json = serde_json::to_string(&RuntimeMilli::UNKNOWN).unwrap();
assert_eq!(json, r#"[0.0,"??:??:??.???"]"#);
assert!(serde_json::from_str::<RuntimeMilli>(&json).unwrap().is_unknown());
}
#[test]
#[cfg(feature = "bincode")]
fn bincode() {
let this: RuntimeMilli = RuntimeMilli::from(111.999);
let config = bincode::config::standard();
let bytes = bincode::encode_to_vec(&this, config).unwrap();
let this: RuntimeMilli = bincode::decode_from_slice(&bytes, config).unwrap().0;
assert_eq!(this, 111.999);
assert_eq!(this, "00:01:51.999");
let bytes = bincode::encode_to_vec(&RuntimeMilli::UNKNOWN, config).unwrap();
let this: RuntimeMilli = bincode::decode_from_slice(&bytes, config).unwrap().0;
assert!(this.is_unknown());
}
#[test]
#[cfg(feature = "bincode")]
fn borsh() {
let this: RuntimeMilli = RuntimeMilli::from(111.999);
let bytes = borsh::to_vec(&this).unwrap();
let this: RuntimeMilli = borsh::from_slice(&bytes).unwrap();
assert_eq!(this, 111.999);
assert_eq!(this, "00:01:51.999");
assert!(borsh::from_slice::<RuntimeMilli>(b"bad .-;[]124/ bytes").is_err());
let bytes = borsh::to_vec(&RuntimeMilli::UNKNOWN).unwrap();
let this: RuntimeMilli = borsh::from_slice(&bytes).unwrap();
assert!(this.is_unknown());
}
}