pub use chrono::Weekday;
use chrono::{Date, Datelike, Local};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use tracing::{debug, trace};
#[cfg(test)]
use mockall::automock;
pub trait Off {
fn is_off_time(&self) -> bool;
}
#[derive(Serialize, Deserialize, Debug)]
pub enum Parity {
EveryWeek,
OddWeek,
EvenWeek,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(transparent)]
pub struct OffDays(HashMap<Weekday, Parity>);
struct Time {}
#[cfg_attr(test, automock)] pub trait Now {
fn now(&self) -> Date<Local>;
}
impl Now for Time {
fn now(&self) -> Date<Local> {
Local::now().date()
}
}
impl OffDays {
pub fn new() -> OffDays {
OffDays(HashMap::new())
}
#[allow(dead_code)]
fn insert(&mut self, day: Weekday, parity: Parity) -> Option<Parity> {
self.0.insert(day, parity)
}
fn is_off_at_date(&self, date: impl Now) -> bool {
let now = date.now();
trace!("now: {:?}", now);
trace!("now.weekday: {:?}", now.weekday());
let res: bool;
if let Some(parity) = self.0.get(&now.weekday()) {
trace!("match and parity = {:?}", parity);
res = match parity {
Parity::EveryWeek => true,
Parity::OddWeek => &now.iso_week().week() % 2 == 1,
Parity::EvenWeek => &now.iso_week().week() % 2 == 0,
};
} else {
res = false;
}
debug!(
"{:?} {:?} is {} off",
&now.weekday(),
&now.iso_week(),
if !res { "not" } else { "" }
);
res
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl Default for OffDays {
fn default() -> Self {
OffDays::new()
}
}
impl Off for OffDays {
fn is_off_time(&self) -> bool {
self.is_off_at_date(Time {})
}
}
#[cfg(test)]
mod is_off_should {
use super::*;
use anyhow::Result;
use chrono::{Local, TimeZone, Weekday};
use test_env_log::test;
#[test]
fn return_false_when_day_dont_match() -> Result<()> {
let mut leave = OffDays::new();
leave.insert(Weekday::Mon, Parity::EveryWeek);
let mut mock = MockNow::new();
mock.expect_now()
.times(1)
.returning(|| Local.isoywd(2015, 1, Weekday::Tue));
assert_eq!(leave.is_off_at_date(mock), false);
Ok(())
}
#[test]
fn return_true_when_match_and_no_parity() -> Result<()> {
let mut leave = OffDays::new();
leave.insert(Weekday::Tue, Parity::EveryWeek);
let mut mock = MockNow::new();
mock.expect_now()
.times(1)
.returning(|| Local.isoywd(2015, 1, Weekday::Tue));
assert_eq!(leave.is_off_at_date(mock), true);
Ok(())
}
#[test]
fn return_true_when_day_and_parity_match() -> Result<()> {
let mut leave = OffDays::new();
leave.insert(Weekday::Wed, Parity::OddWeek);
let mut mock = MockNow::new();
mock.expect_now()
.times(1)
.returning(|| Local.isoywd(2015, 15, Weekday::Wed));
assert_eq!(leave.is_off_at_date(mock), true);
leave.insert(Weekday::Thu, Parity::EvenWeek);
let mut mock = MockNow::new();
mock.expect_now()
.times(1)
.returning(|| Local.isoywd(2015, 16, Weekday::Thu));
assert_eq!(leave.is_off_at_date(mock), true);
Ok(())
}
#[test]
fn return_false_when_day_match_but_not_parity() -> Result<()> {
let mut leave = OffDays::new();
leave.insert(Weekday::Fri, Parity::EvenWeek);
let mut mock = MockNow::new();
mock.expect_now()
.times(1)
.returning(|| Local.isoywd(2015, 15, Weekday::Fri));
assert_eq!(leave.is_off_at_date(mock), false);
leave.insert(Weekday::Sun, Parity::OddWeek);
let mut mock = MockNow::new();
mock.expect_now()
.times(1)
.returning(|| Local.isoywd(2015, 16, Weekday::Sun));
assert_eq!(leave.is_off_at_date(mock), false);
Ok(())
}
}