use chrono::{Local, Timelike};
use std::fmt::Display;
#[derive(Clone, Copy, Debug)]
pub enum Time {
Hm {
hour: u32,
minute: u32,
period: Period,
},
Hms {
hour: u32,
minute: u32,
second: u32,
period: Period,
},
}
impl Default for Time {
fn default() -> Self {
Self::Hm {
hour: 1,
minute: 0,
period: Period::Am,
}
}
}
impl Time {
#[must_use]
pub fn now_hm(use_24h: bool) -> Self {
let now = Local::now().naive_local().time();
let (hour, period) = if use_24h {
(now.hour(), Period::H24)
} else {
let (period, hour12) = now.hour12();
(hour12, if period { Period::Pm } else { Period::Am })
};
Self::Hm {
hour,
minute: now.minute(),
period,
}
}
#[must_use]
pub fn now_hms(use_24h: bool) -> Self {
let now = Local::now().naive_local().time();
let (hour, period) = if use_24h {
(now.hour(), Period::H24)
} else {
let (period, hour12) = now.hour12();
(hour12, if period { Period::Pm } else { Period::Am })
};
Self::Hms {
hour,
minute: now.minute(),
second: now.second(),
period,
}
}
#[must_use]
pub const fn default_hm(period: Period) -> Self {
Self::Hm {
hour: 0,
minute: 0,
period,
}
}
#[must_use]
pub const fn default_hms(period: Period) -> Self {
Self::Hms {
hour: 0,
minute: 0,
second: 0,
period,
}
}
}
impl Display for Time {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Hm {
hour,
minute,
period,
} => {
write!(f, "{hour:02}:{minute:02}{period}")
}
Self::Hms {
hour,
minute,
second,
period,
} => {
write!(f, "{hour:02}:{minute:02}:{second:02}{period}")
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Period {
H24,
Am,
Pm,
}
impl Display for Period {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::H24 => "",
Self::Am => " AM",
Self::Pm => " PM",
}
)
}
}
impl From<Time> for chrono::NaiveTime {
fn from(time: Time) -> Self {
let (h, m, s, p) = match time {
Time::Hm {
hour,
minute,
period,
} => (hour, minute, 0, period),
Time::Hms {
hour,
minute,
second,
period,
} => (hour, minute, second, period),
};
let h = if h == 12 && p != Period::H24 { 0 } else { h };
let h = match p {
Period::H24 | Period::Am => h,
Period::Pm => (h + 12) % 24,
};
Self::from_hms_opt(h, m, s).expect("Time Conversion failed. H, M, or S was too large.")
}
}
impl From<chrono::NaiveTime> for Time {
fn from(time: chrono::NaiveTime) -> Self {
Self::Hms {
hour: time.hour(),
minute: time.minute(),
second: time.second(),
period: Period::H24,
}
}
}
#[cfg(test)]
mod tests {
use chrono::NaiveTime;
use super::{Period, Time};
#[test]
fn time_to_naive() {
let time = Time::Hms {
hour: 8,
minute: 52,
second: 17,
period: Period::H24,
};
let naive: NaiveTime = time.into();
assert_eq!(
naive,
NaiveTime::from_hms_opt(8, 52, 17).expect("Time Conversion failed")
);
let time = Time::Hms {
hour: 23,
minute: 48,
second: 39,
period: Period::H24,
};
let naive: NaiveTime = time.into();
assert_eq!(
naive,
NaiveTime::from_hms_opt(23, 48, 39).expect("Time Conversion failed")
);
let time = Time::Hms {
hour: 8,
minute: 52,
second: 17,
period: Period::Am,
};
let naive: NaiveTime = time.into();
assert_eq!(
naive,
NaiveTime::from_hms_opt(8, 52, 17).expect("Time Conversion failed")
);
let time = Time::Hms {
hour: 8,
minute: 52,
second: 17,
period: Period::Pm,
};
let naive: NaiveTime = time.into();
assert_eq!(
naive,
NaiveTime::from_hms_opt(20, 52, 17).expect("Time Conversion failed")
);
let time = Time::Hms {
hour: 12,
minute: 52,
second: 17,
period: Period::Am,
};
let naive: NaiveTime = time.into();
assert_eq!(
naive,
NaiveTime::from_hms_opt(0, 52, 17).expect("Time Conversion failed")
);
let time = Time::Hms {
hour: 12,
minute: 52,
second: 17,
period: Period::Pm,
};
let naive: NaiveTime = time.into();
assert_eq!(
naive,
NaiveTime::from_hms_opt(12, 52, 17).expect("Time Conversion failed")
);
let time = Time::Hm {
hour: 17,
minute: 52,
period: Period::H24,
};
let naive: NaiveTime = time.into();
assert_eq!(
naive,
NaiveTime::from_hms_opt(17, 52, 0).expect("Time Conversion failed")
);
}
#[test]
fn time_default() {
let time = Time::default();
match time {
Time::Hm {
hour,
minute,
period,
} => {
assert_eq!(hour, 1);
assert_eq!(minute, 0);
assert_eq!(period, Period::Am);
}
#[allow(clippy::panic)]
Time::Hms { .. } => panic!("Expected Time::Hm variant"),
}
}
#[test]
fn time_default_hm_am() {
let time = Time::default_hm(Period::Am);
match time {
Time::Hm {
hour,
minute,
period,
} => {
assert_eq!(hour, 0);
assert_eq!(minute, 0);
assert_eq!(period, Period::Am);
}
#[allow(clippy::panic)]
Time::Hms { .. } => panic!("Expected Time::Hm variant"),
}
}
#[test]
fn time_default_hm_pm() {
let time = Time::default_hm(Period::Pm);
match time {
Time::Hm {
hour,
minute,
period,
} => {
assert_eq!(hour, 0);
assert_eq!(minute, 0);
assert_eq!(period, Period::Pm);
}
#[allow(clippy::panic)]
Time::Hms { .. } => panic!("Expected Time::Hm variant"),
}
}
#[test]
fn time_default_hm_h24() {
let time = Time::default_hm(Period::H24);
match time {
Time::Hm {
hour,
minute,
period,
} => {
assert_eq!(hour, 0);
assert_eq!(minute, 0);
assert_eq!(period, Period::H24);
}
#[allow(clippy::panic)]
Time::Hms { .. } => panic!("Expected Time::Hm variant"),
}
}
#[test]
fn time_default_hms_am() {
let time = Time::default_hms(Period::Am);
match time {
Time::Hms {
hour,
minute,
second,
period,
} => {
assert_eq!(hour, 0);
assert_eq!(minute, 0);
assert_eq!(second, 0);
assert_eq!(period, Period::Am);
}
#[allow(clippy::panic)]
Time::Hm { .. } => panic!("Expected Time::Hms variant"),
}
}
#[test]
fn time_default_hms_pm() {
let time = Time::default_hms(Period::Pm);
match time {
Time::Hms {
hour,
minute,
second,
period,
} => {
assert_eq!(hour, 0);
assert_eq!(minute, 0);
assert_eq!(second, 0);
assert_eq!(period, Period::Pm);
}
#[allow(clippy::panic)]
Time::Hm { .. } => panic!("Expected Time::Hms variant"),
}
}
#[test]
fn time_default_hms_h24() {
let time = Time::default_hms(Period::H24);
match time {
Time::Hms {
hour,
minute,
second,
period,
} => {
assert_eq!(hour, 0);
assert_eq!(minute, 0);
assert_eq!(second, 0);
assert_eq!(period, Period::H24);
}
#[allow(clippy::panic)]
Time::Hm { .. } => panic!("Expected Time::Hms variant"),
}
}
#[test]
fn time_hm_display_h24() {
let time = Time::Hm {
hour: 14,
minute: 30,
period: Period::H24,
};
assert_eq!(format!("{time}"), "14:30");
}
#[test]
fn time_hm_display_am() {
let time = Time::Hm {
hour: 9,
minute: 15,
period: Period::Am,
};
assert_eq!(format!("{time}"), "09:15 AM");
}
#[test]
fn time_hm_display_pm() {
let time = Time::Hm {
hour: 3,
minute: 45,
period: Period::Pm,
};
assert_eq!(format!("{time}"), "03:45 PM");
}
#[test]
fn time_hms_display_h24() {
let time = Time::Hms {
hour: 23,
minute: 59,
second: 59,
period: Period::H24,
};
assert_eq!(format!("{time}"), "23:59:59");
}
#[test]
fn time_hms_display_am() {
let time = Time::Hms {
hour: 6,
minute: 30,
second: 45,
period: Period::Am,
};
assert_eq!(format!("{time}"), "06:30:45 AM");
}
#[test]
fn time_hms_display_pm() {
let time = Time::Hms {
hour: 11,
minute: 22,
second: 33,
period: Period::Pm,
};
assert_eq!(format!("{time}"), "11:22:33 PM");
}
#[test]
fn period_display_h24() {
assert_eq!(format!("{}", Period::H24), "");
}
#[test]
fn period_display_am() {
assert_eq!(format!("{}", Period::Am), " AM");
}
#[test]
fn period_display_pm() {
assert_eq!(format!("{}", Period::Pm), " PM");
}
#[test]
fn naive_time_to_time() {
let naive =
NaiveTime::from_hms_opt(14, 30, 45).expect("naive_time_to_time should not be none");
let time: Time = naive.into();
match time {
Time::Hms {
hour,
minute,
second,
period,
} => {
assert_eq!(hour, 14);
assert_eq!(minute, 30);
assert_eq!(second, 45);
assert_eq!(period, Period::H24);
}
#[allow(clippy::panic)]
Time::Hm { .. } => panic!("Expected Time::Hms variant"),
}
}
#[test]
fn naive_time_midnight_to_time() {
let naive = NaiveTime::from_hms_opt(0, 0, 0)
.expect("naive_time_midnight_to_time should not be none");
let time: Time = naive.into();
match time {
Time::Hms {
hour,
minute,
second,
period,
} => {
assert_eq!(hour, 0);
assert_eq!(minute, 0);
assert_eq!(second, 0);
assert_eq!(period, Period::H24);
}
#[allow(clippy::panic)]
Time::Hm { .. } => panic!("Expected Time::Hms variant"),
}
}
#[test]
fn time_hm_midnight_am_to_naive() {
let time = Time::Hm {
hour: 12,
minute: 0,
period: Period::Am,
};
let naive: NaiveTime = time.into();
assert_eq!(
naive,
NaiveTime::from_hms_opt(0, 0, 0)
.expect("time_hm_midnight_am_to_naive Should not be none")
);
}
#[test]
fn time_hm_noon_pm_to_naive() {
let time = Time::Hm {
hour: 12,
minute: 0,
period: Period::Pm,
};
let naive: NaiveTime = time.into();
assert_eq!(
naive,
NaiveTime::from_hms_opt(12, 0, 0).expect("time_hm_noon_pm_to_naive Should not be none")
);
}
#[test]
fn time_hm_one_am_to_naive() {
let time = Time::Hm {
hour: 1,
minute: 30,
period: Period::Am,
};
let naive: NaiveTime = time.into();
assert_eq!(
naive,
NaiveTime::from_hms_opt(1, 30, 0).expect("time_hm_one_am_to_naive Should not be none")
);
}
#[test]
fn time_hm_eleven_pm_to_naive() {
let time = Time::Hm {
hour: 11,
minute: 45,
period: Period::Pm,
};
let naive: NaiveTime = time.into();
assert_eq!(
naive,
NaiveTime::from_hms_opt(23, 45, 0)
.expect("time_hm_eleven_pm_to_naive Should not be none")
);
}
#[test]
fn time_copy_trait() {
let time1 = Time::Hm {
hour: 10,
minute: 30,
period: Period::Am,
};
let time2 = time1; match time1 {
Time::Hm { hour, .. } => assert_eq!(hour, 10),
#[allow(clippy::panic)]
Time::Hms { .. } => panic!("Expected Time::Hm"),
}
match time2 {
Time::Hm { hour, .. } => assert_eq!(hour, 10),
#[allow(clippy::panic)]
Time::Hms { .. } => panic!("Expected Time::Hm"),
}
}
#[test]
fn time_clone_trait() {
let time1 = Time::Hms {
hour: 15,
minute: 45,
second: 30,
period: Period::H24,
};
let time2 = time1;
match (time1, time2) {
(
Time::Hms {
hour: h1,
minute: m1,
second: s1,
period: p1,
},
Time::Hms {
hour: h2,
minute: m2,
second: s2,
period: p2,
},
) => {
assert_eq!(h1, h2);
assert_eq!(m1, m2);
assert_eq!(s1, s2);
assert_eq!(p1, p2);
}
#[allow(clippy::panic)]
_ => panic!("Expected Time::Hms for both"),
}
}
#[test]
fn period_copy_trait() {
let p1 = Period::Am;
let p2 = p1; assert_eq!(p1, Period::Am);
assert_eq!(p2, Period::Am);
}
#[test]
fn period_clone_trait() {
let p1 = Period::Pm;
let p2 = p1;
assert_eq!(p1, p2);
}
#[test]
fn period_equality() {
assert_eq!(Period::Am, Period::Am);
assert_eq!(Period::Pm, Period::Pm);
assert_eq!(Period::H24, Period::H24);
assert_ne!(Period::Am, Period::Pm);
assert_ne!(Period::Am, Period::H24);
assert_ne!(Period::Pm, Period::H24);
}
}