// -*- coding: utf-8 -*- use anyhow as ah; pub type DateTime = utc_dt::UTCDatetime; pub type TimeOfDay = utc_dt::time::UTCTimeOfDay; pub use utc_dt::time::{UTCTimestamp, UTCTransformations}; pub trait TodExt { fn is_between(&self, begin: &Self, end: &Self) -> bool; } impl TodExt for TimeOfDay { fn is_between(&self, begin: &Self, end: &Self) -> bool { let t = self.as_millis() as i32; let begin = begin.as_millis() as i32; let end = end.as_millis() as i32; let day = 24 * 60 * 60 * 1000; (t - begin) % day >= 0 && (end - t) % day > 0 } } #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct TimeMask { mask: u128, } const TIMEMASK_NR_BITS: usize = 24 * 4; const TIMEMASK_STEP_MINS: usize = 15; impl TimeMask { pub fn includes(&self, t: &TimeOfDay) -> bool { for bit in 0..TIMEMASK_NR_BITS { if self.mask & 1 << bit != 0 { let mins_begin = bit * TIMEMASK_STEP_MINS; let mins_end = (bit + 1) * TIMEMASK_STEP_MINS; let bh = (mins_begin / 60) as u8; let bm = (mins_begin % 60) as u8; let begin = TimeOfDay::try_from_hhmmss(bh, bm, 0, 0).unwrap(); let eh = (mins_end / 60) as u8; let em = (mins_end % 60) as u8; let end = TimeOfDay::try_from_hhmmss(eh, em, 0, 0).unwrap(); if t.is_between(&begin, &end) { return true; } } } false } } impl From for u128 { fn from(msk: TimeMask) -> Self { msk.mask } } impl From for TimeMask { fn from(mask: u128) -> Self { TimeMask { mask } } } impl From for String { fn from(msk: TimeMask) -> Self { let mut mask = msk.mask; if mask == 0 { "none".to_string() } else { let mut s = "".to_string(); let mut bit = 0; while mask != 0 { if mask & 1 != 0 { let count = mask.trailing_ones() as usize; let mins_begin = bit * TIMEMASK_STEP_MINS; let mins_end = (bit + count) * TIMEMASK_STEP_MINS; let bh = (mins_begin / 60) as u8; let bm = (mins_begin % 60) as u8; let eh = (mins_end / 60) as u8; let em = (mins_end % 60) as u8; if !s.is_empty() { s.push(','); } s.push_str(&format!("{:02}:{:02}-{:02}:{:02}", bh, bm, eh, em)); mask >>= count; bit += count; } else if mask != 0 { let count = mask.trailing_zeros() as usize; mask >>= count; bit += count; } } s } } } impl TryFrom<&str> for TimeMask { type Error = ah::Error; fn try_from(mask_str: &str) -> Result { macro_rules! check { ($h:expr, $m:expr, $name:expr) => { if $h >= 24 { return Err(ah::format_err!("TimeMask: Invalid {} hour: {}", $name, $h)); } if $m >= 60 { return Err(ah::format_err!( "TimeMask: Invalid {} minute: {}", $name, $m )); } if $m % 15 != 0 { return Err(ah::format_err!( "TimeMask: Invalid {} minute (not a multiple of 15): {}", $name, $m )); } }; } let mut mask = 0; let mask_str = mask_str.trim().to_lowercase(); if mask_str != "none" && mask_str != "0" { for part in mask_str.split(',') { let begin_end: Vec<&str> = part.split('-').collect(); if begin_end.len() != 2 { return Err(ah::format_err!("TimeMask: Invalid part format.")); } let begin_time: Vec<&str> = begin_end[0].split(':').collect(); if begin_time.len() != 2 { return Err(ah::format_err!("TimeMask: Invalid begin time format.")); } let end_time: Vec<&str> = begin_end[1].split(':').collect(); if end_time.len() != 2 { return Err(ah::format_err!("TimeMask: Invalid end time format.")); } let begin_h = begin_time[0].parse::(); let begin_m = begin_time[1].parse::(); let end_h = end_time[0].parse::(); let end_m = end_time[1].parse::(); match (begin_h, begin_m, end_h, end_m) { (Ok(begin_h), Ok(begin_m), Ok(end_h), Ok(end_m)) => { check!(begin_h, begin_m, "begin"); check!(end_h, end_m, "end"); let begin_bit = (begin_h * (TIMEMASK_NR_BITS / 24) as u8) + (begin_m / TIMEMASK_STEP_MINS as u8); let end_bit = (end_h * (TIMEMASK_NR_BITS / 24) as u8) + (end_m / TIMEMASK_STEP_MINS as u8); if begin_bit >= end_bit { return Err(ah::format_err!( "TimeMask: Begin time \ {begin_h:02}:{begin_m:02} \ is equal to or after end time \ {end_h:02}:{end_m:02}." )); } for bit in begin_bit..end_bit { mask |= 1 << bit; } } _ => { return Err(ah::format_err!("TimeMask: Invalid time format.")); } } } } Ok(TimeMask { mask }) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_is_between() { assert!(TimeOfDay::try_from_hhmmss(1, 0, 0, 0).unwrap().is_between( &TimeOfDay::try_from_hhmmss(0, 0, 0, 0).unwrap(), &TimeOfDay::try_from_hhmmss(2, 0, 0, 0).unwrap() )); assert!(TimeOfDay::try_from_hhmmss(1, 0, 0, 0).unwrap().is_between( &TimeOfDay::try_from_hhmmss(1, 0, 0, 0).unwrap(), &TimeOfDay::try_from_hhmmss(1, 15, 0, 0).unwrap() )); assert!(TimeOfDay::try_from_hhmmss(1, 14, 59, 999999999) .unwrap() .is_between( &TimeOfDay::try_from_hhmmss(1, 0, 0, 0).unwrap(), &TimeOfDay::try_from_hhmmss(1, 15, 0, 0).unwrap() )); assert!( !TimeOfDay::try_from_hhmmss(1, 15, 0, 0).unwrap().is_between( &TimeOfDay::try_from_hhmmss(1, 0, 0, 0).unwrap(), &TimeOfDay::try_from_hhmmss(1, 15, 0, 0).unwrap() ) ); assert!(TimeOfDay::try_from_hhmmss(1, 15, 0, 0).unwrap().is_between( &TimeOfDay::try_from_hhmmss(1, 0, 0, 0).unwrap(), &TimeOfDay::try_from_hhmmss(23, 0, 0, 0).unwrap() )); assert!( !TimeOfDay::try_from_hhmmss(23, 0, 0, 0).unwrap().is_between( &TimeOfDay::try_from_hhmmss(1, 0, 0, 0).unwrap(), &TimeOfDay::try_from_hhmmss(23, 0, 0, 0).unwrap() ) ); assert!(!TimeOfDay::try_from_hhmmss(23, 30, 0, 0) .unwrap() .is_between( &TimeOfDay::try_from_hhmmss(1, 0, 0, 0).unwrap(), &TimeOfDay::try_from_hhmmss(23, 0, 0, 0).unwrap() )); assert!(!TimeOfDay::try_from_hhmmss(23, 59, 59, 999999999) .unwrap() .is_between( &TimeOfDay::try_from_hhmmss(1, 0, 0, 0).unwrap(), &TimeOfDay::try_from_hhmmss(23, 0, 0, 0).unwrap() )); } #[test] fn test_timemask_string() { let s0 = "00:15-00:45,13:30-16:00"; let m: TimeMask = s0.try_into().unwrap(); assert_eq!(m.mask, 0x00000000_00000000_FFC00000_00000006); let s1: String = m.into(); assert_eq!(s0, s1); let s0 = "none"; let m: TimeMask = s0.try_into().unwrap(); assert_eq!(m.mask, 0); let s1: String = m.into(); assert_eq!(s0, s1); } } // vim: ts=4 sw=4 expandtab