Skip to content

Commit

Permalink
feat: support multiple time ranges in schedule (#127)
Browse files Browse the repository at this point in the history
* chore: fix deprecated warnings

* feat: support multiple time ranges in schedule

* chore: bump version
  • Loading branch information
Riateche authored Jun 14, 2024
1 parent 660b216 commit fee170f
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 45 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pyth-agent"
version = "2.7.0"
version = "2.8.0"
edition = "2021"

[[bin]]
Expand Down
1 change: 1 addition & 0 deletions src/agent/legacy_schedule.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//! Market hours metadata parsing and evaluation logic
#![allow(deprecated)]

use {
anyhow::{
Expand Down
114 changes: 71 additions & 43 deletions src/agent/market_schedule.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Holiday hours metadata parsing and evaluation logic

#[allow(deprecated)]
use {
super::legacy_schedule::{
LegacySchedule,
Expand All @@ -19,6 +20,7 @@ use {
chrono_tz::Tz,
std::{
fmt::Display,
ops::RangeInclusive,
str::FromStr,
},
winnow::{
Expand Down Expand Up @@ -129,6 +131,7 @@ impl FromStr for MarketSchedule {
}
}

#[allow(deprecated)]
impl From<LegacySchedule> for MarketSchedule {
fn from(legacy: LegacySchedule) -> Self {
Self {
Expand Down Expand Up @@ -190,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 @@ -218,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 @@ -234,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 @@ -267,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 @@ -290,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 @@ -304,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 @@ -347,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 @@ -371,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 @@ -398,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 @@ -476,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
2 changes: 2 additions & 0 deletions src/agent/solana/oracle.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// This module is responsible for loading the current state of the
// on-chain Oracle program accounts from Solana.
#[allow(deprecated)]
use {
self::subscriber::Subscriber,
super::{
Expand Down Expand Up @@ -648,6 +649,7 @@ impl Poller {
let product = load_product_account(prod_acc.data.as_slice())
.context(format!("Could not parse product account {}", product_key))?;

#[allow(deprecated)]
let legacy_schedule: LegacySchedule = if let Some((_wsched_key, wsched_val)) =
product.iter().find(|(k, _v)| *k == "weekly_schedule")
{
Expand Down

0 comments on commit fee170f

Please sign in to comment.