1pub use command::TimeCommand;
2pub use interval::Interval;
3
4mod command;
5mod interval;
6
7use std::fmt;
8use std::str::FromStr;
9
10#[derive(Clone, Debug, Eq, PartialEq)]
11pub struct Time {
12 days: i32,
13 hours: u8,
14 minutes: u8,
15 seconds: u8,
16}
17
18pub struct TimeShortView<'a>(&'a Time);
19
20pub struct TimeLongView<'a>(&'a Time);
21
22impl Time {
23 pub fn try_new(days: i32, hours: u8, minutes: u8, seconds: u8) -> Result<Self, ()> {
24 if hours < 24 && minutes < 60 && seconds < 60 {
25 Ok(Self {
26 days,
27 hours,
28 minutes,
29 seconds,
30 })
31 } else {
32 Err(())
33 }
34 }
35
36 pub fn checked_add(&self, interval: &Interval) -> Option<Self> {
37 let (mut days, mut hours, mut minutes, mut seconds) = (
38 (self.days as i64) + (interval.days as i64),
39 (self.hours as i64) + (interval.hours as i64),
40 (self.minutes as i64) + (interval.minutes as i64),
41 (self.seconds as i64) + (interval.seconds as i64),
42 );
43
44 if interval.rounds != 0 {
45 seconds += interval.rounds as i64 * 6;
46 }
47
48 if !(0..60).contains(&seconds) {
49 minutes += seconds.div_euclid(60);
50 seconds = seconds.rem_euclid(60);
51 }
52
53 if !(0..60).contains(&minutes) {
54 hours += minutes.div_euclid(60);
55 minutes = minutes.rem_euclid(60);
56 }
57
58 if !(0..24).contains(&hours) {
59 days += hours.div_euclid(24);
60 hours = hours.rem_euclid(24);
61 }
62
63 if let (Ok(days), Ok(hours), Ok(minutes), Ok(seconds)) = (
64 days.try_into(),
65 hours.try_into(),
66 minutes.try_into(),
67 seconds.try_into(),
68 ) {
69 Some(Self {
70 days,
71 hours,
72 minutes,
73 seconds,
74 })
75 } else {
76 None
77 }
78 }
79
80 pub fn checked_sub(&self, interval: &Interval) -> Option<Self> {
81 if let (Some(days), Some(hours), Some(minutes), Some(seconds), Some(rounds)) = (
82 0i32.checked_sub(interval.days),
83 0i32.checked_sub(interval.hours),
84 0i32.checked_sub(interval.minutes),
85 0i32.checked_sub(interval.seconds),
86 0i32.checked_sub(interval.rounds),
87 ) {
88 self.checked_add(&Interval {
89 days,
90 hours,
91 minutes,
92 seconds,
93 rounds,
94 })
95 } else {
96 None
97 }
98 }
99
100 pub fn display_short(&self) -> TimeShortView {
101 TimeShortView(self)
102 }
103
104 pub fn display_long(&self) -> TimeLongView {
105 TimeLongView(self)
106 }
107}
108
109impl Default for Time {
110 fn default() -> Self {
111 Self {
112 days: 1,
113 hours: 8,
114 minutes: 0,
115 seconds: 0,
116 }
117 }
118}
119
120impl FromStr for Time {
121 type Err = ();
122
123 fn from_str(raw: &str) -> Result<Self, Self::Err> {
124 let mut parts = raw.split(':');
125
126 let days = parts.next().ok_or(())?.parse().map_err(|_| ())?;
127 let hours = parts.next().ok_or(())?.parse().map_err(|_| ())?;
128 let minutes = parts.next().ok_or(())?.parse().map_err(|_| ())?;
129 let seconds = parts.next().ok_or(())?.parse().map_err(|_| ())?;
130
131 if parts.next().is_none() {
132 Time::try_new(days, hours, minutes, seconds)
133 } else {
134 Err(())
135 }
136 }
137}
138
139impl fmt::Display for TimeShortView<'_> {
140 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
141 let time = self.0;
142 write!(
143 f,
144 "{}:{:02}:{:02}:{:02}",
145 time.days, time.hours, time.minutes, time.seconds
146 )
147 }
148}
149
150impl fmt::Display for TimeLongView<'_> {
151 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
152 let time = self.0;
153
154 let (hours, am_pm) = match time.hours {
155 0 => (12, "am"),
156 1..=11 => (time.hours, "am"),
157 12 => (12, "pm"),
158 _ => (time.hours % 12, "pm"),
159 };
160
161 write!(
162 f,
163 "day {} at {}:{:02}:{:02} {}",
164 time.days, hours, time.minutes, time.seconds, am_pm
165 )
166 }
167}
168
169#[cfg(test)]
170mod test {
171 use super::*;
172
173 #[test]
174 fn time_try_new_test() {
175 assert_eq!(Ok(t(1, 2, 3, 4)), Time::try_new(1, 2, 3, 4));
176 assert!(Time::try_new(i32::MAX, 23, 59, 59).is_ok());
177 assert_eq!(Err(()), Time::try_new(0, 24, 0, 0));
178 assert_eq!(Err(()), Time::try_new(0, 0, 60, 0));
179 assert_eq!(Err(()), Time::try_new(0, 0, 0, 60));
180 }
181
182 #[test]
183 fn time_default_test() {
184 assert_eq!(t(1, 8, 0, 0), Time::default());
185 }
186
187 #[test]
188 fn time_checked_add_test() {
189 assert_eq!(
190 t(3, 6, 9, 12),
191 t(1, 2, 3, 4)
192 .checked_add(&Interval::new(2, 4, 6, 8, 0))
193 .unwrap(),
194 );
195
196 assert_eq!(
197 t(1, 1, 1, 1),
198 t0().checked_add(&Interval::new_seconds(86400 + 3600 + 60 + 1))
199 .unwrap(),
200 );
201
202 assert_eq!(
203 t(1, 1, 1, 0),
204 t0().checked_add(&Interval::new_minutes(1440 + 60 + 1))
205 .unwrap(),
206 );
207
208 assert_eq!(
209 t(1, 1, 0, 0),
210 t0().checked_add(&Interval::new_hours(24 + 1)).unwrap(),
211 );
212
213 assert_eq!(
214 t(2, 0, 0, 0),
215 t(1, 23, 59, 59)
216 .checked_add(&Interval::new_seconds(1))
217 .unwrap(),
218 );
219
220 assert_eq!(
221 t(0, 0, 0, 6),
222 t0().checked_add(&Interval::new_rounds(1)).unwrap(),
223 );
224 assert_eq!(
225 t(0, 0, 1, 0),
226 t0().checked_add(&Interval::new_rounds(10)).unwrap(),
227 );
228 }
229
230 #[test]
231 fn time_checked_add_test_limits() {
232 assert!(tmax().checked_add(&Interval::new_days(1)).is_none());
233 assert!(t1()
234 .checked_add(&Interval::new_days(i32::MAX - 1))
235 .is_some());
236 assert!(t1().checked_add(&Interval::new_days(i32::MAX)).is_none());
237
238 assert!(tmax().checked_add(&Interval::new_hours(1)).is_none());
239 assert!(t1().checked_add(&Interval::new_hours(i32::MAX)).is_some());
240
241 assert!(tmax().checked_add(&Interval::new_minutes(1)).is_none());
242 assert!(t1().checked_add(&Interval::new_minutes(i32::MAX)).is_some());
243
244 assert!(tmax().checked_add(&Interval::new_seconds(1)).is_none());
245 assert!(t1().checked_add(&Interval::new_seconds(i32::MAX)).is_some());
246
247 assert!(tmax().checked_add(&Interval::new_rounds(1)).is_none());
248 assert!(t1().checked_add(&Interval::new_rounds(i32::MAX)).is_some());
249 }
250
251 #[test]
252 fn time_checked_sub_test() {
253 assert_eq!(
254 t(-1, 0, 0, 0),
255 t0().checked_sub(&Interval::new_days(1)).unwrap(),
256 );
257
258 assert_eq!(
259 t0(),
260 t(0, 1, 0, 0).checked_sub(&Interval::new_hours(1)).unwrap(),
261 );
262 assert_eq!(
263 t(-1, 23, 0, 0),
264 t0().checked_sub(&Interval::new_hours(1)).unwrap(),
265 );
266
267 assert_eq!(
268 t0(),
269 t(0, 0, 1, 0)
270 .checked_sub(&Interval::new_minutes(1))
271 .unwrap(),
272 );
273 assert_eq!(
274 t(-1, 23, 59, 0),
275 t0().checked_sub(&Interval::new_minutes(1)).unwrap(),
276 );
277
278 assert_eq!(
279 t0(),
280 t(0, 0, 0, 1)
281 .checked_sub(&Interval::new_seconds(1))
282 .unwrap(),
283 );
284 assert_eq!(
285 t(-1, 23, 59, 59),
286 t0().checked_sub(&Interval::new_seconds(1)).unwrap(),
287 );
288 }
289
290 #[test]
291 fn time_checked_sub_test_limits() {
292 assert!(t0().checked_sub(&Interval::new_days(i32::MAX)).is_some());
293 assert!(t0().checked_sub(&Interval::new_days(i32::MIN)).is_none());
294
295 assert!(t0().checked_sub(&Interval::new_hours(i32::MAX)).is_some());
296 assert!(t0().checked_sub(&Interval::new_hours(i32::MIN)).is_none());
297
298 assert!(t0().checked_sub(&Interval::new_minutes(i32::MAX)).is_some());
299 assert!(t0().checked_sub(&Interval::new_minutes(i32::MIN)).is_none());
300
301 assert!(t0().checked_sub(&Interval::new_seconds(i32::MAX)).is_some());
302 assert!(t0().checked_sub(&Interval::new_seconds(i32::MIN)).is_none());
303 }
304
305 #[test]
306 fn time_display_short_test() {
307 assert_eq!("1:02:03:04", t(1, 2, 3, 4).display_short().to_string());
308 assert_eq!("1:23:59:59", t(1, 23, 59, 59).display_short().to_string());
309 }
310
311 #[test]
312 fn time_display_long_test() {
313 assert_eq!("day 0 at 12:00:00 am", t0().display_long().to_string());
314 assert_eq!(
315 "day 1 at 1:02:03 am",
316 t(1, 1, 2, 3).display_long().to_string(),
317 );
318 assert_eq!(
319 "day 2 at 11:59:59 am",
320 t(2, 11, 59, 59).display_long().to_string(),
321 );
322 assert_eq!(
323 "day 3 at 12:00:00 pm",
324 t(3, 12, 0, 0).display_long().to_string(),
325 );
326 assert_eq!(
327 "day 4 at 1:00:00 pm",
328 t(4, 13, 0, 0).display_long().to_string(),
329 );
330 assert_eq!(
331 "day 5 at 11:59:59 pm",
332 t(5, 23, 59, 59).display_long().to_string(),
333 );
334 }
335
336 #[test]
337 fn time_from_str_test() {
338 assert_eq!(Ok(t(1, 2, 3, 4)), "1:02:03:04".parse());
339 assert_eq!(Ok(t(1, 23, 59, 59)), "1:23:59:59".parse());
340 }
341
342 fn t(days: i32, hours: u8, minutes: u8, seconds: u8) -> Time {
343 Time {
344 days,
345 hours,
346 minutes,
347 seconds,
348 }
349 }
350
351 fn t0() -> Time {
352 t(0, 0, 0, 0)
353 }
354
355 fn t1() -> Time {
356 t(1, 1, 1, 1)
357 }
358
359 fn tmax() -> Time {
360 t(i32::MAX, 23, 59, 59)
361 }
362}