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(
52 ranges: &[AvailableRange],
53 after: Option<&RangeBound>,
54 before: Option<&RangeBound>,
55) -> Vec<AvailableRange> {
56 let mut out = ranges.to_vec();
57
58 if let Some(after) = after {
59 out.retain(|r| range_ends_after_or_is_unbounded(r, after));
60 }
61 if let Some(before) = before {
62 out.retain(|r| range_starts_before(r, before));
63 }
64
65 out
66}
67
68fn range_ends_after_or_is_unbounded(r: &AvailableRange, after: &RangeBound) -> bool {
69 match after {
70 RangeBound::Slot(slot) => r.max_bundle_end_slot.is_none_or(|end| end >= *slot),
71 RangeBound::Time(time) => match &r.max_bundle_end_slot_utc {
72 None => true, Some(utc_str) => parse_utc(utc_str).is_none_or(|end| end >= *time),
74 },
75 }
76}
77
78fn range_starts_before(r: &AvailableRange, before: &RangeBound) -> bool {
79 match before {
80 RangeBound::Slot(slot) => r.bundle_start_slot <= *slot,
81 RangeBound::Time(time) => match &r.bundle_start_slot_utc {
82 None => true, Some(utc_str) => parse_utc(utc_str).is_none_or(|start| start <= *time),
84 },
85 }
86}
87
88fn parse_utc(s: &str) -> Option<DateTime<Utc>> {
89 DateTime::parse_from_rfc3339(s)
90 .ok()
91 .map(|dt| dt.with_timezone(&Utc))
92}