simulator_client/
ranges.rs1use std::{error::Error, str::FromStr};
2
3use chrono::{DateTime, NaiveDate, TimeZone, Utc};
4use simulator_api::AvailableRange;
5
6#[derive(Debug, Clone)]
8pub enum RangeBound {
9 Slot(u64),
10 Time(DateTime<Utc>),
11}
12
13#[derive(Debug)]
14pub struct ParseRangeBoundError(String);
15
16impl std::fmt::Display for ParseRangeBoundError {
17 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18 f.write_str(&self.0)
19 }
20}
21
22impl Error for ParseRangeBoundError {}
23
24impl FromStr for RangeBound {
25 type Err = ParseRangeBoundError;
26
27 fn from_str(s: &str) -> Result<Self, Self::Err> {
30 if let Ok(slot) = s.parse::<u64>() {
31 return Ok(Self::Slot(slot));
32 }
33 if let Ok(dt) = DateTime::parse_from_rfc3339(s) {
34 return Ok(Self::Time(dt.with_timezone(&Utc)));
35 }
36 if let Ok(date) = NaiveDate::parse_from_str(s, "%Y-%m-%d") {
37 let dt = Utc.from_utc_datetime(&date.and_hms_opt(0, 0, 0).unwrap());
38 return Ok(Self::Time(dt));
39 }
40 Err(ParseRangeBoundError(format!(
41 "could not parse {s:?} as a slot number, RFC 3339 timestamp, or YYYY-MM-DD date"
42 )))
43 }
44}
45
46pub fn filter_ranges(
54 ranges: &[AvailableRange],
55 after: Option<&RangeBound>,
56 before: Option<&RangeBound>,
57) -> Vec<AvailableRange> {
58 let mut out = ranges.to_vec();
59
60 if let Some(bound) = after {
61 out.retain(|r| starts_after(r, bound));
62 }
63 if let Some(bound) = before {
64 out.retain(|r| ends_before(r, bound));
65 }
66
67 out
68}
69
70fn ends_before(r: &AvailableRange, bound: &RangeBound) -> bool {
71 match bound {
72 RangeBound::Slot(slot) => r.max_bundle_end_slot.is_none_or(|end| end <= *slot),
73 RangeBound::Time(time) => match &r.max_bundle_end_slot_utc {
74 None => true, Some(utc_str) => parse_utc(utc_str).is_none_or(|end| end <= *time),
76 },
77 }
78}
79
80fn starts_after(r: &AvailableRange, bound: &RangeBound) -> bool {
81 match bound {
82 RangeBound::Slot(slot) => r.bundle_start_slot >= *slot,
83 RangeBound::Time(time) => match &r.bundle_start_slot_utc {
84 None => true, Some(utc_str) => parse_utc(utc_str).is_none_or(|start| start >= *time),
86 },
87 }
88}
89
90fn parse_utc(s: &str) -> Option<DateTime<Utc>> {
91 DateTime::parse_from_rfc3339(s)
92 .ok()
93 .map(|dt| dt.with_timezone(&Utc))
94}