sem_reg/cloud_store/night_light/
time.rs

1use std::{num::ParseIntError, str::FromStr};
2
3use map_self::MapSelf;
4use serde::Serialize;
5use thiserror::Error;
6
7use crate::data_conversion::byte_seq::{ByteSeq, ParseError};
8
9#[derive(Clone, Copy, PartialEq, Serialize, Debug)]
10pub struct ClockTimeFrame {
11    pub start: ClockTime,
12    pub end: ClockTime,
13}
14
15impl ClockTimeFrame {
16    pub const MIDNIGHT_TO_MIDNIGHT: Self = Self {
17        start: ClockTime::MIDNIGHT,
18        end: ClockTime::MIDNIGHT,
19    };
20
21    pub fn format(&self, use_12_hour_clock: bool) -> String {
22        format!(
23            "{}-{}",
24            self.start.format(use_12_hour_clock),
25            self.end.format(use_12_hour_clock)
26        )
27    }
28}
29
30impl FromStr for ClockTimeFrame {
31    type Err = ClockTimeOrFrameFromStrError;
32
33    fn from_str(s: &str) -> Result<Self, Self::Err> {
34        let mut clock_time_iter = s.splitn(2, '-');
35        if let (Some(start), Some(end)) = (clock_time_iter.next(), clock_time_iter.next()) {
36            Ok(Self {
37                start: start.parse()?,
38                end: end.parse()?,
39            })
40        } else {
41            Err(ClockTimeOrFrameFromStrError)
42        }
43    }
44}
45
46#[derive(Clone, Copy, PartialEq, Serialize, Debug)]
47pub struct ClockTime {
48    pub(super) hour: u8,
49    pub(super) minute: u8,
50}
51
52impl ClockTime {
53    pub const MIDNIGHT: Self = Self { hour: 0, minute: 0 };
54
55    pub fn from_h_min(hour: u8, minute: u8) -> Option<Self> {
56        if hour <= 23 && minute <= 59 {
57            Some(Self { hour, minute })
58        } else {
59            None
60        }
61    }
62
63    pub fn from_h_min_with_meridiem(mut hour: u8, minute: u8, meridiem: Meridiem) -> Option<Self> {
64        if hour > 12 {
65            return None;
66        }
67
68        match (meridiem, &mut hour) {
69            (Meridiem::Am, hour) if *hour == 12 => *hour = 0,
70            (Meridiem::Pm, hour) if *hour < 12 => *hour += 12,
71            _ => {}
72        };
73
74        Self::from_h_min(hour, minute)
75    }
76
77    pub fn hour(&self) -> u8 {
78        self.hour
79    }
80
81    pub fn hour_meridiem(&self) -> (u8, Meridiem) {
82        if self.hour == 0 {
83            (12, Meridiem::Am)
84        } else if self.hour == 12 {
85            (12, Meridiem::Pm)
86        } else if self.hour > 12 {
87            (self.hour - 12, Meridiem::Pm)
88        } else {
89            (self.hour, Meridiem::Am)
90        }
91    }
92
93    pub fn minute(&self) -> u8 {
94        self.minute
95    }
96
97    pub fn is_midnight(&self) -> bool {
98        self.hour == 0 && self.minute == 0
99    }
100
101    pub fn format(&self, use_12_hour_clock: bool) -> String {
102        let (hour, meridiem) = if use_12_hour_clock {
103            self.hour_meridiem()
104                .map_self(|(hour, meridiem)| (hour, Some(meridiem)))
105        } else {
106            (self.hour, None)
107        };
108
109        let mut string = format!("{:02}:{:02}", hour, self.minute,);
110
111        if let Some(meridiem) = meridiem {
112            string.push_str(meridiem.as_str());
113        }
114
115        string
116    }
117}
118
119impl FromStr for ClockTime {
120    type Err = ClockTimeOrFrameFromStrError;
121
122    fn from_str(s: &str) -> Result<Self, Self::Err> {
123        let lowercase_string = s.to_ascii_lowercase();
124        let mut number_iter = lowercase_string.splitn(2, ':');
125
126        if let (Some(hour), Some(minute_meridiem)) = (number_iter.next(), number_iter.next()) {
127            let hour = hour.parse()?;
128
129            let (minute, meridiem) = if let Some(minute) = minute_meridiem.strip_suffix("am") {
130                (minute, Some(Meridiem::Am))
131            } else if let Some(minute) = minute_meridiem.strip_suffix("pm") {
132                (minute, Some(Meridiem::Pm))
133            } else {
134                (minute_meridiem, None)
135            };
136            let minute = minute.parse()?;
137
138            Ok(if let Some(meridiem) = meridiem {
139                Self::from_h_min_with_meridiem(hour, minute, meridiem)
140            } else {
141                Self::from_h_min(hour, minute)
142            }
143            .ok_or(ClockTimeOrFrameFromStrError)?)
144        } else {
145            Err(ClockTimeOrFrameFromStrError)
146        }
147    }
148}
149
150#[derive(Clone, Copy, PartialEq)]
151pub enum Meridiem {
152    Am,
153    Pm,
154}
155
156impl Meridiem {
157    fn as_str(&self) -> &'static str {
158        match self {
159            Meridiem::Am => "am",
160            Meridiem::Pm => "pm",
161        }
162    }
163}
164
165pub(super) trait BinConvertClockTime {
166    fn read_clock_time(&mut self) -> Result<ClockTime, ParseError>;
167    fn push_clock_time(&mut self, clock_time: ClockTime);
168}
169
170impl BinConvertClockTime for ByteSeq {
171    fn read_clock_time(&mut self) -> Result<ClockTime, ParseError> {
172        let hour = if self.assert_const(&[0x0e]).is_ok() {
173            self.read_int()?
174        } else {
175            0
176        };
177
178        let minute = if self.assert_const(&[0x2e]).is_ok() {
179            self.read_int()?
180        } else {
181            0
182        };
183
184        Ok(ClockTime::from_h_min(hour, minute).ok_or(ParseError::ValueNotInRange)?)
185    }
186
187    fn push_clock_time(&mut self, clock_time: ClockTime) {
188        if clock_time.hour != 0 {
189            self.push_const(&[0x0e]);
190            self.push_int(clock_time.hour);
191        }
192        if clock_time.minute != 0 {
193            self.push_const(&[0x2e]);
194            self.push_int(clock_time.minute);
195        }
196    }
197}
198
199#[derive(Error, PartialEq, Debug)]
200#[error("couldn't parse `ClockTime` or `ClockTimeFrame`")]
201pub struct ClockTimeOrFrameFromStrError;
202
203impl From<ParseIntError> for ClockTimeOrFrameFromStrError {
204    fn from(_error: ParseIntError) -> Self {
205        ClockTimeOrFrameFromStrError
206    }
207}
208
209#[cfg(test)]
210mod tests {
211    use crate::cloud_store::night_light::{ClockTime, ClockTimeFrame, Meridiem};
212
213    #[test]
214    fn clock_time_frame_from_str() {
215        assert_eq!(
216            "20:21-6:00".parse::<ClockTimeFrame>(),
217            Ok(ClockTimeFrame {
218                start: ClockTime::from_h_min_with_meridiem(8, 21, Meridiem::Pm).unwrap(),
219                end: ClockTime::from_h_min_with_meridiem(6, 0, Meridiem::Am).unwrap()
220            })
221        );
222        assert_eq!(
223            "08:00pm-05:45AM".parse::<ClockTimeFrame>(),
224            Ok(ClockTimeFrame {
225                start: ClockTime::from_h_min(20, 00).unwrap(),
226                end: ClockTime::from_h_min(5, 45).unwrap()
227            })
228        );
229        assert_eq!(
230            "9:59-9:59am".parse::<ClockTimeFrame>(),
231            Ok(ClockTimeFrame {
232                start: ClockTime::from_h_min_with_meridiem(9, 59, Meridiem::Am).unwrap(),
233                end: ClockTime::from_h_min(9, 59).unwrap()
234            })
235        );
236
237        assert!("9:59-9:59am-".parse::<ClockTimeFrame>().is_err());
238        assert!("10:00 - 10:00".parse::<ClockTimeFrame>().is_err());
239        assert!("10.00-10.00".parse::<ClockTimeFrame>().is_err());
240        assert!("10:00-10:60".parse::<ClockTimeFrame>().is_err());
241        assert!("10:00-24:00".parse::<ClockTimeFrame>().is_err());
242    }
243}