use super::{AmPm, Resolution, SixHour};
use std::{fmt, num::NonZero};
#[derive(Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Time(NonZero<u32>);
impl PartialOrd for Time {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
if self.0.trailing_zeros() == other.0.trailing_zeros() {
Some(self.0.cmp(&other.0))
} else {
None
}
}
}
#[cfg(feature = "jiff")]
impl From<jiff::civil::Time> for Time {
fn from(t: jiff::civil::Time) -> Self {
Time::new()
.with_hour(t.hour() as u8)
.with_minute(t.minute() as u8)
.with_second(t.second() as u8)
.with_millis(t.millisecond() as u16)
}
}
#[cfg(feature = "jiff")]
impl From<Time> for jiff::civil::Time {
fn from(t: Time) -> Self {
jiff::civil::time(
t.hour() as i8,
t.minute() as i8,
t.second() as i8,
t.millis() as i32,
)
}
}
#[cfg(feature = "chrono")]
impl From<chrono::NaiveTime> for Time {
fn from(t: chrono::NaiveTime) -> Self {
use chrono::Timelike;
Time::new()
.with_hour(t.hour() as u8)
.with_minute(t.minute() as u8)
.with_second(t.second() as u8)
.with_millis((t.nanosecond() / 1_000_000) as u16)
}
}
#[cfg(feature = "chrono")]
impl From<Time> for chrono::NaiveTime {
fn from(t: Time) -> Self {
chrono::NaiveTime::from_hms_milli_opt(
t.hour() as u32,
t.minute() as u32,
t.second() as u32,
t.millis() as u32,
)
.unwrap()
}
}
impl Time {
pub fn resolution(self) -> Resolution {
Resolution::from_trailing_zeros(self.0.trailing_zeros() as u8)
}
pub fn reduce_to(&mut self, res: Resolution) {
if res >= self.resolution() {
return;
}
let mut x = self.0.get();
x &= u32::MAX << res.trailing_zeros();
x |= 1 << res.trailing_zeros();
self.0 = NonZero::new(x).unwrap();
}
pub fn with_res(self, res: Resolution) -> Option<Self> {
if res > self.resolution() {
return None;
}
let mut x = self.0.get();
x &= u32::MAX << res.trailing_zeros();
x |= 1 << res.trailing_zeros();
Some(Time(NonZero::new(x).unwrap()))
}
fn from_bits(mut x: u32, res: Resolution) -> Self {
x &= u32::MAX << res.trailing_zeros();
x |= 1 << res.trailing_zeros();
Time(NonZero::new(x).unwrap())
}
pub fn coarse_cmp(self, other: Time) -> std::cmp::Ordering {
let zeroes = self.0.trailing_zeros().max(other.0.trailing_zeros());
let mut x = self.0.get();
x &= u32::MAX << zeroes;
x |= 1 << zeroes;
let mut y = other.0.get();
y &= u32::MAX << zeroes;
y |= 1 << zeroes;
x.cmp(&y)
}
}
impl Default for Time {
fn default() -> Self {
Self::WHOLE_DAY
}
}
impl Time {
pub fn new() -> Self {
Self::WHOLE_DAY
}
}
impl Time {
pub const WHOLE_DAY: Self = Time(NonZero::new(0b10000000_00000000_00000000_00000000).unwrap());
pub const AM: Self = Time(NonZero::new(0b01000000_00000000_00000000_00000000).unwrap());
pub const PM: Self = Time(NonZero::new(0b11000000_00000000_00000000_00000000).unwrap());
pub const NIGHT: Self = Time(NonZero::new(0b00100000_00000000_00000000_00000000).unwrap());
pub const MORNING: Self = Time(NonZero::new(0b01100000_00000000_00000000_00000000).unwrap());
pub const AFTERNOON: Self = Time(NonZero::new(0b10100000_00000000_00000000_00000000).unwrap());
pub const EVENING: Self = Time(NonZero::new(0b11100000_00000000_00000000_00000000).unwrap());
pub fn from_hour(h: u8) -> Self {
Time::new().with_hour(h)
}
}
fn set_res_bits(bits: &mut u32, res: Resolution, x: &mut u32) {
let mask = !(u32::MAX << res.n_bits()) << (res.trailing_zeros() + 1);
*bits &= !mask;
let subdivision = res.subdivision() as u32;
*bits |= (*x % subdivision) << (res.trailing_zeros() + 1);
*x /= subdivision;
}
impl Time {
pub fn try_with_am_pm(self, x: AmPm) -> Option<Self> {
if self.resolution() != Resolution::Day {
return None;
}
let mut x = u8::from(x) as u32;
let mut ret = self.0.get();
set_res_bits(&mut ret, Resolution::AmPm, &mut x);
Some(Time::from_bits(ret, Resolution::AmPm))
}
pub fn try_with_time_of_day(self, x: SixHour) -> Option<Self> {
if self.resolution() != Resolution::Day {
return None;
}
let mut x = u8::from(x) as u32;
let mut ret = self.0.get();
set_res_bits(&mut ret, Resolution::SixHour, &mut x);
set_res_bits(&mut ret, Resolution::AmPm, &mut x);
Some(Time::from_bits(ret, Resolution::SixHour))
}
pub fn try_with_hour(self, x: u8) -> Option<Self> {
if x > 23 {
return None;
}
if self.resolution() != Resolution::Day {
return None;
}
let mut x = x as u32;
let mut ret = self.0.get();
set_res_bits(&mut ret, Resolution::Hour, &mut x);
set_res_bits(&mut ret, Resolution::ThreeHour, &mut x);
set_res_bits(&mut ret, Resolution::SixHour, &mut x);
set_res_bits(&mut ret, Resolution::AmPm, &mut x);
Some(Time::from_bits(ret, Resolution::Hour))
}
pub fn try_with_minute(self, x: u8) -> Option<Self> {
if x > 59 {
return None;
}
if self.resolution() != Resolution::Hour {
return None;
}
let mut x = x as u32;
let mut ret = self.0.get();
set_res_bits(&mut ret, Resolution::Minute, &mut x);
set_res_bits(&mut ret, Resolution::FiveMinute, &mut x);
set_res_bits(&mut ret, Resolution::FifteenMinute, &mut x);
set_res_bits(&mut ret, Resolution::ThirtyMinute, &mut x);
Some(Time::from_bits(ret, Resolution::Minute))
}
pub fn try_with_second(self, x: u8) -> Option<Self> {
if x > 59 {
return None;
}
if self.resolution() != Resolution::Minute {
return None;
}
let mut x = x as u32;
let mut ret = self.0.get();
set_res_bits(&mut ret, Resolution::Second, &mut x);
set_res_bits(&mut ret, Resolution::FiveSecond, &mut x);
set_res_bits(&mut ret, Resolution::FifteenSecond, &mut x);
set_res_bits(&mut ret, Resolution::ThirtySecond, &mut x);
Some(Time::from_bits(ret, Resolution::Second))
}
pub fn try_with_millis(self, x: u16) -> Option<Self> {
if x > 999 {
return None;
}
if self.resolution() != Resolution::Second {
return None;
}
let mut x = x as u32;
let mut ret = self.0.get();
set_res_bits(&mut ret, Resolution::Millisecond, &mut x);
set_res_bits(&mut ret, Resolution::FiveMilli, &mut x);
set_res_bits(&mut ret, Resolution::TenMilli, &mut x);
set_res_bits(&mut ret, Resolution::FiftyMilli, &mut x);
set_res_bits(&mut ret, Resolution::HundredMilli, &mut x);
set_res_bits(&mut ret, Resolution::FiveHundredMilli, &mut x);
Some(Time::from_bits(ret, Resolution::Millisecond))
}
}
impl Time {
pub fn with_am_pm(self, x: AmPm) -> Self {
self.try_with_am_pm(x).unwrap_or(self)
}
pub fn with_time_of_day(self, x: SixHour) -> Self {
self.try_with_time_of_day(x).unwrap_or(self)
}
pub fn with_hour(self, x: u8) -> Self {
self.try_with_hour(x).unwrap_or(self)
}
pub fn with_minute(self, x: u8) -> Self {
self.try_with_minute(x).unwrap_or(self)
}
pub fn with_second(self, x: u8) -> Self {
self.try_with_second(x).unwrap_or(self)
}
pub fn with_millis(self, x: u16) -> Self {
self.try_with_millis(x).unwrap_or(self)
}
pub fn set_millis(&mut self, x: u16) {
*self = self.with_millis(x);
}
pub fn set_second(&mut self, x: u8) {
*self = self.with_second(x);
}
pub fn set_minute(&mut self, x: u8) {
*self = self.with_minute(x);
}
pub fn set_hour(&mut self, x: u8) {
*self = self.with_hour(x);
}
pub fn set_time_of_day(&mut self, x: SixHour) {
*self = self.with_time_of_day(x);
}
pub fn set_am_pm(&mut self, x: AmPm) {
*self = self.with_am_pm(x);
}
}
impl Time {
fn add_res(self, res: Resolution, x: &mut u32) {
let subdivision = res.subdivision() as u32;
*x *= subdivision;
if res <= self.resolution() {
let mut bits = self.0.get();
bits >>= res.trailing_zeros() + 1;
let n_bits = res.n_bits();
bits &= !(u32::MAX << n_bits);
*x += bits;
}
}
pub fn millis(self) -> u16 {
let mut ret = 0;
self.add_res(Resolution::FiveHundredMilli, &mut ret);
self.add_res(Resolution::HundredMilli, &mut ret);
self.add_res(Resolution::FiftyMilli, &mut ret);
self.add_res(Resolution::TenMilli, &mut ret);
self.add_res(Resolution::FiveMilli, &mut ret);
self.add_res(Resolution::Millisecond, &mut ret);
ret as u16
}
pub fn second(self) -> u8 {
let mut ret = 0;
self.add_res(Resolution::ThirtySecond, &mut ret);
self.add_res(Resolution::FifteenSecond, &mut ret);
self.add_res(Resolution::FiveSecond, &mut ret);
self.add_res(Resolution::Second, &mut ret);
ret as u8
}
pub fn minute(self) -> u8 {
let mut ret = 0;
self.add_res(Resolution::ThirtyMinute, &mut ret);
self.add_res(Resolution::FifteenMinute, &mut ret);
self.add_res(Resolution::FiveMinute, &mut ret);
self.add_res(Resolution::Minute, &mut ret);
ret as u8
}
pub fn hour(self) -> u8 {
let mut ret = 0;
self.add_res(Resolution::AmPm, &mut ret);
self.add_res(Resolution::SixHour, &mut ret);
self.add_res(Resolution::ThreeHour, &mut ret);
self.add_res(Resolution::Hour, &mut ret);
ret as u8
}
pub fn time_of_day(self) -> Option<SixHour> {
if self.resolution() < Resolution::AmPm {
return None;
}
let mut ret = 0;
self.add_res(Resolution::AmPm, &mut ret);
self.add_res(Resolution::SixHour, &mut ret);
Some((ret as u8).try_into().unwrap())
}
pub fn am_pm(self) -> Option<AmPm> {
if self.resolution() < Resolution::AmPm {
return None;
}
let mut ret = 0;
self.add_res(Resolution::AmPm, &mut ret);
Some((ret as u8).try_into().unwrap())
}
}
impl fmt::Debug for Time {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Time(")?;
let mut map = f.debug_map();
for res in Resolution::range(self.resolution(), Resolution::Day).rev() {
let mut bits = 0;
self.add_res(res, &mut bits);
map.entry(&res, &bits);
}
map.finish()?;
f.write_str(")")?;
Ok(())
}
}
impl fmt::Display for Time {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.resolution() {
Resolution::Day => f.write_str("whole day"),
Resolution::AmPm => write!(f, "{}", self.am_pm().unwrap()),
Resolution::SixHour => write!(f, "{}", self.time_of_day().unwrap()),
_ => {
write!(f, "{:02}:{:02}", self.hour(), self.minute())?;
if self.resolution() > Resolution::Minute {
write!(f, ":{:02}", self.second())?;
}
match self.resolution() {
Resolution::HundredMilli | Resolution::FiveHundredMilli => {
write!(f, ".{:01}", self.millis() / 100)?
}
Resolution::TenMilli | Resolution::FiftyMilli => {
write!(f, ".{:02}", self.millis() / 10)?
}
Resolution::Millisecond | Resolution::FiveMilli => {
write!(f, ".{:03}", self.millis())?
}
_ => (),
}
Ok(())
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use linearize::LinearizeExt;
#[test]
fn test_set_get() {
let mut x = Time::default();
for hour in 0..24 {
let x = x.with_hour(hour);
assert_eq!(x.hour(), hour, "{:#b}", x.0);
}
x.set_hour(11);
for minute in 0..60 {
let x = x.with_minute(minute);
assert_eq!(x.minute(), minute, "{:#b}", x.0);
}
x.set_minute(43);
for second in 0..60 {
let x = x.with_second(second);
assert_eq!(x.second(), second, "{:#b}", x.0);
}
x.set_second(59);
for millis in 0..1000 {
let x = x.with_millis(millis);
assert_eq!(x.millis(), millis, "{:#b}", x.0);
}
}
#[test]
fn test_fmt() {
let mut x = Time::default();
assert_eq!(x.to_string(), "whole day");
x.set_hour(11);
assert_eq!(x.to_string(), "11:00");
x.set_minute(56);
assert_eq!(x.to_string(), "11:56");
x.set_second(24);
assert_eq!(x.to_string(), "11:56:24");
x.reduce_to(Resolution::FiveSecond);
assert_eq!(x.to_string(), "11:56:20");
x.reduce_to(Resolution::FifteenSecond);
assert_eq!(x.to_string(), "11:56:15");
x.reduce_to(Resolution::Minute);
assert_eq!(x.to_string(), "11:56");
x.reduce_to(Resolution::FiveMinute);
assert_eq!(x.to_string(), "11:55");
x.reduce_to(Resolution::FifteenMinute);
assert_eq!(x.to_string(), "11:45");
x.reduce_to(Resolution::Hour);
assert_eq!(x.to_string(), "11:00");
x.reduce_to(Resolution::Day);
assert_eq!(x.to_string(), "whole day");
}
#[test]
fn test_am_pm() {
let t = Time::new()
.with_hour(0)
.with_minute(0)
.with_second(0)
.with_millis(0);
assert_eq!(t.am_pm(), Some(AmPm::AM));
let t = Time::new()
.with_hour(11)
.with_minute(59)
.with_second(59)
.with_millis(999);
assert_eq!(t.am_pm(), Some(AmPm::AM));
let t = Time::new()
.with_hour(12)
.with_minute(0)
.with_second(0)
.with_millis(0);
assert_eq!(t.am_pm(), Some(AmPm::PM));
let t = Time::new()
.with_hour(23)
.with_minute(59)
.with_second(59)
.with_millis(999);
assert_eq!(t.am_pm(), Some(AmPm::PM));
}
#[test]
fn test_time_of_day() {
let t = Time::new().with_hour(0).with_minute(0);
assert_eq!(t.time_of_day(), Some(SixHour::Night));
let t = Time::new().with_hour(5).with_minute(59);
assert_eq!(t.time_of_day(), Some(SixHour::Night));
let t = Time::new().with_hour(6).with_minute(0);
assert_eq!(t.time_of_day(), Some(SixHour::Morning));
let t = Time::new().with_hour(11).with_minute(59);
assert_eq!(t.time_of_day(), Some(SixHour::Morning));
let t = Time::new().with_hour(12).with_minute(0);
assert_eq!(t.time_of_day(), Some(SixHour::Afternoon));
let t = Time::new().with_hour(17).with_minute(59);
assert_eq!(t.time_of_day(), Some(SixHour::Afternoon));
let t = Time::new().with_hour(18).with_minute(0);
assert_eq!(t.time_of_day(), Some(SixHour::Evening));
let t = Time::new().with_hour(23).with_minute(59);
assert_eq!(t.time_of_day(), Some(SixHour::Evening));
}
#[test]
fn test_res_fmt() {
let t = Time::new()
.with_hour(15)
.with_minute(7)
.with_second(24)
.with_millis(76);
eprintln!("{t}, res={:?}, {:#b}", t.resolution(), t.0);
assert_eq!(t.to_string(), "15:07:24.076");
for res in Resolution::variants() {
let actual = t.with_res(res).unwrap().to_string();
eprintln!("{res:?} => {:?} => {}", t.with_res(res), actual);
let expected = match res {
Resolution::Millisecond => "15:07:24.076",
Resolution::FiveMilli => "15:07:24.075",
Resolution::TenMilli => "15:07:24.07",
Resolution::FiftyMilli => "15:07:24.05",
Resolution::HundredMilli => "15:07:24.0",
Resolution::FiveHundredMilli => "15:07:24.0",
Resolution::Second => "15:07:24",
Resolution::FiveSecond => "15:07:20",
Resolution::FifteenSecond => "15:07:15",
Resolution::ThirtySecond => "15:07:00",
Resolution::Minute => "15:07",
Resolution::FiveMinute => "15:05",
Resolution::FifteenMinute => "15:00",
Resolution::ThirtyMinute => "15:00",
Resolution::Hour => "15:00",
Resolution::ThreeHour => "15:00",
Resolution::SixHour => "afternoon",
Resolution::AmPm => "PM",
Resolution::Day => "whole day",
};
assert_eq!(actual, expected);
}
}
}