#![warn(clippy::all, clippy::pedantic, clippy::nursery)]
use std::cmp::Ordering;
use chrono::{Duration, Local, NaiveTime, Timelike};
use thiserror::Error;
use OfficeHoursError::InvalidTimeSlice;
mod r#macro;
#[derive(Error, Debug)]
pub enum OfficeHoursError<'a> {
#[error("Could not convert `{0:?}` to chrono::NaiveTime")]
InvalidTimeSlice(&'a [u32]),
}
pub enum Clock {
TwelveAm = 0,
OneAm = 1,
TwoAm = 2,
ThreeAm = 3,
FourAm = 4,
FiveAm = 5,
SixAm = 6,
SevenAm = 7,
EightAm = 8,
NineAm = 9,
TenAm = 10,
ElevenAm = 11,
TwelvePm = 12,
OnePm = 13,
TwoPm = 14,
ThreePm = 15,
FourPm = 16,
FivePm = 17,
SixPm = 18,
SevenPm = 19,
EightPm = 20,
NinePm = 21,
TenPm = 22,
ElevenPm = 23,
}
pub trait FromNaiveTime {
fn from_slice_u32(slice: &[u32]) -> Result<NaiveTime, OfficeHoursError>;
fn from_time(time: Clock) -> NaiveTime;
fn from_time_u32(hour: u32) -> Option<NaiveTime>;
}
impl FromNaiveTime for NaiveTime {
fn from_slice_u32(slice: &[u32]) -> Result<NaiveTime, OfficeHoursError> {
let time = Self::from_hms_milli_opt(
*slice.first().unwrap_or(&0),
*slice.get(1).unwrap_or(&0),
*slice.get(2).unwrap_or(&0),
*slice.get(3).unwrap_or(&0),
)
.ok_or(InvalidTimeSlice(slice))?;
Ok(time)
}
fn from_time(hour: Clock) -> NaiveTime {
unsafe { Self::from_time_u32(hour as u32).unwrap_unchecked() }
}
fn from_time_u32(hour: u32) -> Option<NaiveTime> {
Self::from_hms_opt(hour, 0, 0)
}
}
#[derive(Debug, Eq, PartialEq)]
pub struct OfficeHours {
pub start: NaiveTime,
pub finish: NaiveTime,
}
impl Default for OfficeHours {
fn default() -> Self {
Self {
start: NaiveTime::from_time(Clock::NineAm),
finish: NaiveTime::from_time(Clock::FivePm),
}
}
}
impl OfficeHours {
#[must_use]
pub fn new(start: Clock, finish: Clock) -> Self {
Self {
start: NaiveTime::from_time(start),
finish: NaiveTime::from_time(finish),
}
}
#[must_use]
pub const fn iter(&self) -> OfficeHoursIter {
OfficeHoursIter {
start: self.start,
finish: self.finish,
}
}
#[must_use]
pub fn hours(&self) -> Vec<u32> {
self.hours_iter().collect()
}
pub fn hours_iter(&self) -> impl Iterator<Item = u32> {
self.iter().map(|time| time.hour())
}
#[must_use]
pub fn now(&self) -> bool {
Self::now_from_time(&self.start, &self.finish, &Local::now().time())
}
#[doc(hidden)]
#[must_use]
pub fn now_from_time(start: &NaiveTime, finish: &NaiveTime, now: &NaiveTime) -> bool {
match start.cmp(finish) {
Ordering::Equal => start == now,
Ordering::Less => (start..finish).contains(&now),
Ordering::Greater => now >= start || now < finish,
}
}
}
impl<'a> TryFrom<(&'a [u32], &'a [u32])> for OfficeHours {
type Error = OfficeHoursError<'a>;
fn try_from(office_hours: (&'a [u32], &'a [u32])) -> Result<Self, Self::Error> {
let (start, finish) = office_hours;
Ok(Self {
start: NaiveTime::from_slice_u32(start)?,
finish: NaiveTime::from_slice_u32(finish)?,
})
}
}
pub struct OfficeHoursIter {
start: NaiveTime,
finish: NaiveTime,
}
impl Iterator for OfficeHoursIter {
type Item = NaiveTime;
fn next(&mut self) -> Option<NaiveTime> {
if self.start == self.finish {
None
} else {
let current_hour = NaiveTime::from_time_u32(self.start.hour()).unwrap();
let is_before_midnight = self.start == NaiveTime::from_time(Clock::ElevenPm);
self.start = if is_before_midnight {
NaiveTime::default()
} else {
self.start + Duration::hours(1)
};
Some(current_hour)
}
}
}
impl IntoIterator for &OfficeHours {
type Item = NaiveTime;
type IntoIter = OfficeHoursIter;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}