sem_reg/cloud_store/night_light/
time.rs1use 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}