Skip to main content

opening_hours_syntax/rules/
time.rs

1use alloc::vec::Vec;
2use core::cmp::Ordering;
3use core::fmt::Display;
4use core::ops::Range;
5
6use chrono::Duration;
7
8use crate::display::write_selector;
9use crate::extended_time::ExtendedTime;
10
11// TimeSelector
12
13#[derive(Clone, Debug, Hash, PartialEq, Eq)]
14pub struct TimeSelector {
15    pub spans: Vec<TimeSpan>,
16}
17
18impl TimeSelector {
19    /// Note that not all cases can be covered
20    pub(crate) fn is_00_24(&self) -> bool {
21        self.spans.len() == 1
22            && self.spans.first()
23                == Some(&TimeSpan::fixed_range(
24                    ExtendedTime::MIDNIGHT_00,
25                    ExtendedTime::MIDNIGHT_24,
26                ))
27    }
28}
29
30impl TimeSelector {
31    #[inline]
32    pub fn new(spans: Vec<TimeSpan>) -> Self {
33        if spans.is_empty() {
34            Self::default()
35        } else {
36            Self { spans }
37        }
38    }
39}
40
41impl Default for TimeSelector {
42    #[inline]
43    fn default() -> Self {
44        Self {
45            spans: vec![TimeSpan::fixed_range(
46                ExtendedTime::MIDNIGHT_00,
47                ExtendedTime::MIDNIGHT_24,
48            )],
49        }
50    }
51}
52
53impl Display for TimeSelector {
54    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
55        write_selector(f, &self.spans)
56    }
57}
58
59// TimeSpan
60
61#[derive(Clone, Debug, Hash, PartialEq, Eq)]
62pub struct TimeSpan {
63    pub range: Range<Time>,
64    pub open_end: bool,
65    pub repeats: Option<Duration>,
66}
67
68impl TimeSpan {
69    #[inline]
70    pub const fn fixed_range(start: ExtendedTime, end: ExtendedTime) -> Self {
71        Self {
72            range: Time::Fixed(start)..Time::Fixed(end),
73            open_end: false,
74            repeats: None,
75        }
76    }
77}
78
79impl Ord for TimeSpan {
80    fn cmp(&self, other: &Self) -> Ordering {
81        (self.range.start.cmp(&other.range.start))
82            .then_with(|| self.range.end.cmp(&other.range.end))
83            .then_with(|| self.open_end.cmp(&other.open_end))
84            .then_with(|| self.repeats.cmp(&other.repeats))
85    }
86}
87
88impl PartialOrd for TimeSpan {
89    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
90        Some(self.cmp(other))
91    }
92}
93
94impl Display for TimeSpan {
95    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
96        write!(f, "{}", self.range.start)?;
97
98        if !self.open_end || self.range.end != Time::Fixed(ExtendedTime::MIDNIGHT_24) {
99            write!(f, "-{}", self.range.end)?;
100        }
101
102        if self.open_end {
103            write!(f, "+")?;
104        }
105
106        if let Some(repeat) = self.repeats {
107            write!(f, "/")?;
108
109            if repeat.num_hours() > 0 {
110                write!(f, "{:02}:", repeat.num_hours())?;
111            }
112
113            write!(f, "{:02}", repeat.num_minutes() % 60)?;
114        }
115
116        Ok(())
117    }
118}
119
120// Time
121
122#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
123pub enum Time {
124    Fixed(ExtendedTime),
125    Variable(VariableTime),
126}
127
128impl Time {
129    /// TODO: doc
130    pub fn as_time_event(&self) -> Option<TimeEvent> {
131        if let Self::Variable(VariableTime { event, .. }) = self {
132            Some(*event)
133        } else {
134            None
135        }
136    }
137}
138
139impl Display for Time {
140    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
141        match self {
142            Self::Fixed(time) => write!(f, "{time}"),
143            Self::Variable(time) => write!(f, "{time}"),
144        }
145    }
146}
147
148// VariableTime
149
150#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
151pub struct VariableTime {
152    pub event: TimeEvent,
153    pub offset: i16,
154}
155
156impl VariableTime {
157    /// Implement a safe partial order for variable times. Some events such as
158    /// (dusk+02:00-dawn-02:00) may not be ordered the same way depending on the season.
159    pub fn stable_partial_ord(&self, other: &Self) -> Option<Ordering> {
160        match (self.event.cmp(&other.event), self.offset.cmp(&other.offset)) {
161            (Ordering::Equal, other_cmp) | (other_cmp, Ordering::Equal) => Some(other_cmp),
162            (event_cmp, offset_cmp) if event_cmp == offset_cmp => Some(event_cmp),
163            _ => None,
164        }
165    }
166
167    /// Checks is the event is guaranteed to happend before the other one.
168    pub fn is_before(&self, other: &Self) -> bool {
169        self.stable_partial_ord(other) == Some(Ordering::Less)
170    }
171}
172
173impl Display for VariableTime {
174    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
175        let Self { event, offset } = self;
176        let offset_h = offset.abs() / 60;
177        let offset_m = offset.abs() % 60;
178
179        match offset.cmp(&0) {
180            Ordering::Less => write!(f, "({event}-{offset_h:02}:{offset_m:02})"),
181            Ordering::Greater => write!(f, "({event}+{offset_h:02}:{offset_m:02})"),
182            Ordering::Equal => write!(f, "{event}"),
183        }
184    }
185}
186
187// TimeEvent
188
189#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
190pub enum TimeEvent {
191    Dawn,
192    Sunrise,
193    Sunset,
194    Dusk,
195}
196
197impl TimeEvent {
198    pub const fn as_str(&self) -> &'static str {
199        match self {
200            Self::Dawn => "dawn",
201            Self::Sunrise => "sunrise",
202            Self::Sunset => "sunset",
203            Self::Dusk => "dusk",
204        }
205    }
206}
207
208impl Display for TimeEvent {
209    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
210        write!(f, "{}", self.as_str())
211    }
212}