Skip to content

Commit

Permalink
feat: support multiple time ranges in schedule
Browse files Browse the repository at this point in the history
  • Loading branch information
Riateche committed Jun 13, 2024
1 parent a467176 commit c948fe2
Showing 1 changed file with 69 additions and 43 deletions.
112 changes: 69 additions & 43 deletions src/agent/market_schedule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use {
chrono_tz::Tz,
std::{
fmt::Display,
ops::RangeInclusive,
str::FromStr,
},
winnow::{
Expand Down Expand Up @@ -192,19 +193,19 @@ impl Display for HolidayDaySchedule {
}
}

#[derive(Clone, Debug, Eq, PartialEq, Copy)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ScheduleDayKind {
Open,
Closed,
TimeRange(NaiveTime, NaiveTime),
TimeRanges(Vec<RangeInclusive<NaiveTime>>),
}

impl ScheduleDayKind {
pub fn can_publish_at(&self, when_local: NaiveTime) -> bool {
match self {
Self::Open => true,
Self::Closed => false,
Self::TimeRange(start, end) => start <= &when_local && &when_local <= end,
Self::TimeRanges(ranges) => ranges.iter().any(|range| range.contains(&when_local)),
}
}
}
Expand All @@ -220,8 +221,20 @@ impl Display for ScheduleDayKind {
match self {
Self::Open => write!(f, "O"),
Self::Closed => write!(f, "C"),
Self::TimeRange(start, end) => {
write!(f, "{}-{}", start.format("%H%M"), end.format("%H%M"))
Self::TimeRanges(ranges) => {
let mut ranges = ranges.iter().peekable();
while let Some(range) = ranges.next() {
write!(
f,
"{}-{}",
range.start().format("%H%M"),
range.end().format("%H%M")
)?;
if ranges.peek().is_some() {
write!(f, "&")?;
}
}
Ok(())
}
}
}
Expand All @@ -236,21 +249,21 @@ fn time_parser<'s>(input: &mut &'s str) -> PResult<NaiveTime> {
.parse_next(input)
}

fn time_range_parser<'s>(input: &mut &'s str) -> PResult<ScheduleDayKind> {
fn time_range_parser<'s>(input: &mut &'s str) -> PResult<RangeInclusive<NaiveTime>> {
seq!(
time_parser,
_: "-",
time_parser,
)
.map(|s| ScheduleDayKind::TimeRange(s.0, s.1))
.map(|s| s.0..=s.1)
.parse_next(input)
}

fn schedule_day_kind_parser<'s>(input: &mut &'s str) -> PResult<ScheduleDayKind> {
alt((
"C".map(|_| ScheduleDayKind::Closed),
"O".map(|_| ScheduleDayKind::Open),
time_range_parser,
separated(1.., time_range_parser, "&").map(ScheduleDayKind::TimeRanges),
))
.parse_next(input)
}
Expand All @@ -269,7 +282,7 @@ impl From<MHKind> for ScheduleDayKind {
match mhkind {
MHKind::Open => ScheduleDayKind::Open,
MHKind::Closed => ScheduleDayKind::Closed,
MHKind::TimeRange(start, end) => ScheduleDayKind::TimeRange(start, end),
MHKind::TimeRange(start, end) => ScheduleDayKind::TimeRanges(vec![start..=end]),
}
}
}
Expand All @@ -292,6 +305,7 @@ mod tests {
let open = "O";
let closed = "C";
let valid = "1234-1347";
let valid_double = "1234-1347&1400-1500";
let valid2400 = "1234-2400";
let invalid = "1234-5668";
let invalid_format = "1234-56";
Expand All @@ -306,17 +320,25 @@ mod tests {
);
assert_eq!(
valid.parse::<ScheduleDayKind>().unwrap(),
ScheduleDayKind::TimeRange(
NaiveTime::from_hms_opt(12, 34, 0).unwrap(),
NaiveTime::from_hms_opt(13, 47, 0).unwrap(),
)
ScheduleDayKind::TimeRanges(vec![
NaiveTime::from_hms_opt(12, 34, 0).unwrap()
..=NaiveTime::from_hms_opt(13, 47, 0).unwrap()
])
);
assert_eq!(
valid_double.parse::<ScheduleDayKind>().unwrap(),
ScheduleDayKind::TimeRanges(vec![
NaiveTime::from_hms_opt(12, 34, 0).unwrap()
..=NaiveTime::from_hms_opt(13, 47, 0).unwrap(),
NaiveTime::from_hms_opt(14, 0, 0).unwrap()
..=NaiveTime::from_hms_opt(15, 0, 0).unwrap(),
])
);
assert_eq!(
valid2400.parse::<ScheduleDayKind>().unwrap(),
ScheduleDayKind::TimeRange(
NaiveTime::from_hms_opt(12, 34, 0).unwrap(),
MAX_TIME_INSTANT,
)
ScheduleDayKind::TimeRanges(vec![
NaiveTime::from_hms_opt(12, 34, 0).unwrap()..=MAX_TIME_INSTANT
])
);
assert!(invalid.parse::<ScheduleDayKind>().is_err());
assert!(invalid_format.parse::<ScheduleDayKind>().is_err());
Expand Down Expand Up @@ -349,10 +371,10 @@ mod tests {
let expected = HolidayDaySchedule {
month: 04,
day: 12,
kind: ScheduleDayKind::TimeRange(
NaiveTime::from_hms_opt(12, 34, 0).unwrap(),
NaiveTime::from_hms_opt(13, 47, 0).unwrap(),
),
kind: ScheduleDayKind::TimeRanges(vec![
NaiveTime::from_hms_opt(12, 34, 0).unwrap()
..=NaiveTime::from_hms_opt(13, 47, 0).unwrap(),
]),
};
let parsed = input.parse::<HolidayDaySchedule>()?;
assert_eq!(parsed, expected);
Expand All @@ -373,14 +395,13 @@ mod tests {
timezone: Tz::America__New_York,
weekly_schedule: vec![
ScheduleDayKind::Open,
ScheduleDayKind::TimeRange(
NaiveTime::from_hms_opt(12, 34, 0).unwrap(),
NaiveTime::from_hms_opt(13, 47, 0).unwrap(),
),
ScheduleDayKind::TimeRange(
NaiveTime::from_hms_opt(09, 30, 0).unwrap(),
MAX_TIME_INSTANT,
),
ScheduleDayKind::TimeRanges(vec![
NaiveTime::from_hms_opt(12, 34, 0).unwrap()
..=NaiveTime::from_hms_opt(13, 47, 0).unwrap(),
]),
ScheduleDayKind::TimeRanges(vec![
NaiveTime::from_hms_opt(09, 30, 0).unwrap()..=MAX_TIME_INSTANT,
]),
ScheduleDayKind::Closed,
ScheduleDayKind::Closed,
ScheduleDayKind::Closed,
Expand All @@ -400,18 +421,17 @@ mod tests {
HolidayDaySchedule {
month: 04,
day: 14,
kind: ScheduleDayKind::TimeRange(
NaiveTime::from_hms_opt(12, 34, 0).unwrap(),
NaiveTime::from_hms_opt(13, 47, 0).unwrap(),
),
kind: ScheduleDayKind::TimeRanges(vec![
NaiveTime::from_hms_opt(12, 34, 0).unwrap()
..=NaiveTime::from_hms_opt(13, 47, 0).unwrap(),
]),
},
HolidayDaySchedule {
month: 12,
day: 30,
kind: ScheduleDayKind::TimeRange(
NaiveTime::from_hms_opt(09, 30, 0).unwrap(),
MAX_TIME_INSTANT,
),
kind: ScheduleDayKind::TimeRanges(vec![
NaiveTime::from_hms_opt(09, 30, 0).unwrap()..=MAX_TIME_INSTANT,
]),
},
],
};
Expand Down Expand Up @@ -478,18 +498,24 @@ mod tests {
}

prop_compose! {
fn schedule_day_kind()(
r in any::<u8>(),
fn time_range()(
t1 in any::<u32>(),
t2 in any::<u32>(),
) -> RangeInclusive<NaiveTime> {
NaiveTime::from_hms_opt(t1 % 24, t1 / 24 % 60, 0).unwrap()..=
NaiveTime::from_hms_opt(t2 % 24, t2 / 24 % 60, 0).unwrap()
}
}

prop_compose! {
fn schedule_day_kind()(
r in any::<u8>(),
ranges in proptest::collection::vec(time_range(), 1..3),
) -> ScheduleDayKind {
match r % 3 {
0 => ScheduleDayKind::Open,
1 => ScheduleDayKind::Closed,
_ => ScheduleDayKind::TimeRange(
NaiveTime::from_hms_opt(t1 % 24, t1 / 24 % 60, 0).unwrap(),
NaiveTime::from_hms_opt(t2 % 24, t2 / 24 % 60, 0).unwrap(),
),
_ => ScheduleDayKind::TimeRanges(ranges),
}
}
}
Expand Down

0 comments on commit c948fe2

Please sign in to comment.