mod cmp;
mod consts;
mod convert;
mod fmt;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct Time(u16);
impl Time {
#[expect(clippy::missing_panics_doc)]
#[must_use]
pub fn new(time: u16) -> Option<Self> {
let (hour, minute, second) = (
(time >> 11)
.try_into()
.expect("hour should be in the range of `u8`"),
((time >> 5) & 0x3F)
.try_into()
.expect("minute should be in the range of `u8`"),
((time & 0x1F) * 2)
.try_into()
.expect("second should be in the range of `u8`"),
);
let time = time::Time::from_hms(hour, minute, second).ok()?;
Some(Self::from_time(time))
}
#[must_use]
pub const unsafe fn new_unchecked(time: u16) -> Self {
Self(time)
}
#[must_use]
pub fn from_time(time: time::Time) -> Self {
let (hour, minute, second) = (
u16::from(time.hour()),
u16::from(time.minute()),
u16::from(time.second() / 2),
);
let second = second.min(29);
let time = (hour << 11) | (minute << 5) | second;
unsafe { Self::new_unchecked(time) }
}
#[must_use]
pub fn is_valid(self) -> bool {
Self::new(self.to_raw()).is_some()
}
#[must_use]
pub const fn to_raw(self) -> u16 {
self.0
}
#[expect(clippy::missing_panics_doc)]
#[must_use]
pub fn hour(self) -> u8 {
(self.to_raw() >> 11)
.try_into()
.expect("hour should be in the range of `u8`")
}
#[expect(clippy::missing_panics_doc)]
#[must_use]
pub fn minute(self) -> u8 {
((self.to_raw() >> 5) & 0x3F)
.try_into()
.expect("minute should be in the range of `u8`")
}
#[expect(clippy::missing_panics_doc)]
#[must_use]
pub fn second(self) -> u8 {
((self.to_raw() & 0x1F) * 2)
.try_into()
.expect("second should be in the range of `u8`")
}
}
impl Default for Time {
fn default() -> Self {
Self::MIN
}
}
#[cfg(test)]
mod tests {
use core::mem;
#[cfg(feature = "std")]
use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
};
use time::macros::time;
use super::*;
#[test]
fn size_of() {
assert_eq!(mem::size_of::<Time>(), mem::size_of::<u16>());
}
#[test]
fn align_of() {
assert_eq!(mem::align_of::<Time>(), mem::align_of::<u16>());
}
#[test]
fn clone() {
assert_eq!(Time::MIN.clone(), Time::MIN);
}
#[test]
fn copy() {
let a = Time::MIN;
let b = a;
assert_eq!(a, b);
}
#[cfg(feature = "std")]
#[test]
fn hash() {
assert_ne!(
{
let mut hasher = DefaultHasher::new();
Time::MIN.hash(&mut hasher);
hasher.finish()
},
{
let mut hasher = DefaultHasher::new();
Time::MAX.hash(&mut hasher);
hasher.finish()
}
);
}
#[test]
fn new() {
assert_eq!(Time::new(u16::MIN).unwrap(), Time::MIN);
assert_eq!(Time::new(0b1011_1111_0111_1101).unwrap(), Time::MAX);
}
#[test]
fn new_with_invalid_time() {
assert!(Time::new(0b0000_0000_0001_1110).is_none());
assert!(Time::new(0b0000_0111_1000_0000).is_none());
assert!(Time::new(0b1100_0000_0000_0000).is_none());
}
#[test]
fn new_unchecked() {
assert_eq!(unsafe { Time::new_unchecked(u16::MIN) }, Time::MIN);
assert_eq!(
unsafe { Time::new_unchecked(0b1011_1111_0111_1101) },
Time::MAX
);
}
#[test]
const fn new_unchecked_is_const_fn() {
const _: Time = unsafe { Time::new_unchecked(u16::MIN) };
}
#[test]
fn from_time() {
assert_eq!(Time::from_time(time::Time::MIDNIGHT), Time::MIN);
assert_eq!(Time::from_time(time!(00:00:01)), Time::MIN);
assert_eq!(
Time::from_time(time!(19:25:00)),
Time::new(0b1001_1011_0010_0000).unwrap()
);
assert_eq!(
Time::from_time(time!(10:38:30)),
Time::new(0b0101_0100_1100_1111).unwrap()
);
assert_eq!(Time::from_time(time!(23:59:58)), Time::MAX);
assert_eq!(Time::from_time(time!(23:59:59)), Time::MAX);
}
#[test]
fn is_valid() {
assert!(Time::MIN.is_valid());
assert!(Time::new(0b1001_1011_0010_0000).unwrap().is_valid());
assert!(Time::new(0b0101_0100_1100_1111).unwrap().is_valid());
assert!(Time::MAX.is_valid());
}
#[test]
fn is_valid_with_invalid_time() {
assert!(!unsafe { Time::new_unchecked(0b0000_0000_0001_1110) }.is_valid());
assert!(!unsafe { Time::new_unchecked(0b0000_0111_1000_0000) }.is_valid());
assert!(!unsafe { Time::new_unchecked(0b1100_0000_0000_0000) }.is_valid());
}
#[test]
fn to_raw() {
assert_eq!(Time::MIN.to_raw(), u16::MIN);
assert_eq!(
Time::new(0b1001_1011_0010_0000).unwrap().to_raw(),
0b1001_1011_0010_0000
);
assert_eq!(
Time::new(0b0101_0100_1100_1111).unwrap().to_raw(),
0b0101_0100_1100_1111
);
assert_eq!(Time::MAX.to_raw(), 0b1011_1111_0111_1101);
}
#[test]
const fn to_raw_is_const_fn() {
const _: u16 = Time::MIN.to_raw();
}
#[test]
fn hour() {
assert_eq!(Time::MIN.hour(), u8::MIN);
assert_eq!(Time::new(0b1001_1011_0010_0000).unwrap().hour(), 19);
assert_eq!(Time::new(0b0101_0100_1100_1111).unwrap().hour(), 10);
assert_eq!(Time::MAX.hour(), 23);
}
#[test]
fn minute() {
assert_eq!(Time::MIN.minute(), u8::MIN);
assert_eq!(Time::new(0b1001_1011_0010_0000).unwrap().minute(), 25);
assert_eq!(Time::new(0b0101_0100_1100_1111).unwrap().minute(), 38);
assert_eq!(Time::MAX.minute(), 59);
}
#[test]
fn second() {
assert_eq!(Time::MIN.second(), u8::MIN);
assert_eq!(Time::new(0b1001_1011_0010_0000).unwrap().second(), u8::MIN);
assert_eq!(Time::new(0b0101_0100_1100_1111).unwrap().second(), 30);
assert_eq!(Time::MAX.second(), 58);
}
#[test]
fn default() {
assert_eq!(Time::default(), Time::MIN);
}
}