1use std::ops::Sub;
2use thiserror::Error;
3use time::{Duration, Time, ext::NumericalDuration};
4
5#[derive(Error, Debug)]
6pub enum TimeError {
7 #[error("Invalid time format. Expected HH:MM or H:MM, got: {0}")]
8 InvalidFormat(String),
9 #[error("Invalid time components: {0}:{1}")]
10 InvalidComponents(u8, u8),
11 #[error("Failed to reset seconds for time: {0:?}")]
12 ResetSecondsError(Time),
13 #[error("Invalid seconds value: {0}")]
14 InvalidSeconds(i64),
15 #[error("Invalid alignment unit: {0}")]
16 InvalidAlignmentUnit(u64),
17 #[error("Failed to add time: {0:?}")]
18 AddTimeError(Time),
19}
20
21pub trait ExtTime {
23 fn to_shorten(&self) -> String;
34
35 fn from_str(time_str: &str) -> Result<Time, TimeError>;
44
45 fn sub_ext(&self, right: Time) -> Duration;
53
54 fn reset_minute(&self) -> Result<Time, TimeError>;
56
57 fn is_same_minute(&self, other: &Time) -> bool;
59
60 fn is_between(&self, start: Time, end: Time) -> bool;
63
64 fn add_minutes(&self, minutes: i64) -> Time;
66
67 fn from_seconds(seconds: i64) -> Result<Time, TimeError>;
76
77 fn to_seconds(&self) -> i64;
82
83 fn align_to(&self, interval: i64) -> Result<Time, TimeError>;
92
93 fn next_day(&self) -> Time;
95
96 fn next_hour(&self) -> Time;
98
99 fn next_minute(&self) -> Time;
101
102 fn next_second(&self) -> Time;
104
105 fn to_hour_seconds(&self) -> i64;
112
113 fn to_minute_seconds(&self) -> i64;
120}
121
122impl ExtTime for Time {
123 fn to_shorten(&self) -> String {
124 format!("{}:{:02}", self.hour(), self.minute())
125 }
126
127 fn from_str(time_str: &str) -> Result<Time, TimeError> {
128 let parts: Vec<&str> = time_str.split(':').collect();
129 if parts.len() == 2 {
130 if let (Ok(hour), Ok(minute)) = (parts[0].parse::<u8>(), parts[1].parse::<u8>()) {
131 if hour < 24 && minute < 60 {
132 return Time::from_hms(hour, minute, 0)
133 .map_err(|_| TimeError::InvalidComponents(hour, minute));
134 }
135 }
136 }
137
138 Err(TimeError::InvalidFormat(time_str.to_string()))
139 }
140
141 fn sub_ext(&self, right: Time) -> Duration {
142 let diff = self.clone().sub(right);
143 if diff.is_negative() {
144 24.hours() + diff
145 } else {
146 diff
147 }
148 }
149
150 fn reset_minute(&self) -> Result<Time, TimeError> {
151 Time::from_hms(self.hour(), self.minute(), 0)
152 .map_err(|_| TimeError::ResetSecondsError(*self))
153 }
154
155 fn is_same_minute(&self, other: &Time) -> bool {
156 self.minute() == other.minute() && self.hour() == other.hour()
157 }
158
159 fn is_between(&self, start: Time, end: Time) -> bool {
160 if start <= end {
161 *self >= start && *self <= end
162 } else {
163 *self >= start || *self <= end
165 }
166 }
167
168 fn add_minutes(&self, minutes: i64) -> Time {
169 let total_minutes = self.hour() as i64 * 60 + self.minute() as i64 + minutes;
170 let normalized_minutes = total_minutes.rem_euclid(24 * 60);
171 let hours = (normalized_minutes / 60) as u8;
172 let minutes = (normalized_minutes % 60) as u8;
173 Time::from_hms(hours, minutes, self.second()).unwrap()
174 }
175
176 fn from_seconds(seconds: i64) -> Result<Time, TimeError> {
177 if seconds < 0 || seconds >= 24 * 3600 {
178 return Err(TimeError::InvalidSeconds(seconds));
179 }
180
181 let hours = (seconds / 3600) as u8;
182 let minutes = ((seconds % 3600) / 60) as u8;
183 let secs = (seconds % 60) as u8;
184
185 Time::from_hms(hours, minutes, secs)
186 .map_err(|_| TimeError::InvalidComponents(hours, minutes))
187 }
188
189 fn to_seconds(&self) -> i64 {
190 self.hour() as i64 * 3600 + self.minute() as i64 * 60 + self.second() as i64
191 }
192
193 fn align_to(&self, interval: i64) -> Result<Time, TimeError> {
194 if interval == 0 {
195 return Err(TimeError::InvalidAlignmentUnit(interval.abs() as u64));
196 }
197
198 let total_seconds = self.to_seconds();
199 let aligned_seconds = (total_seconds / interval) * interval;
200
201 Time::from_seconds(aligned_seconds).map_err(|_| TimeError::InvalidSeconds(aligned_seconds))
202 }
203
204 fn next_day(&self) -> Time {
205 *self
207 }
208
209 fn next_hour(&self) -> Time {
210 let next_hour = (self.hour() + 1) % 24;
211 Time::from_hms(next_hour, self.minute(), self.second()).unwrap()
212 }
213
214 fn next_minute(&self) -> Time {
215 if self.minute() == 59 {
216 let next_hour = (self.hour() + 1) % 24;
217 Time::from_hms(next_hour, 0, self.second()).unwrap()
218 } else {
219 Time::from_hms(self.hour(), self.minute() + 1, self.second()).unwrap()
220 }
221 }
222
223 fn next_second(&self) -> Time {
224 if self.second() == 59 {
225 if self.minute() == 59 {
226 let next_hour = (self.hour() + 1) % 24;
227 Time::from_hms(next_hour, 0, 0).unwrap()
228 } else {
229 Time::from_hms(self.hour(), self.minute() + 1, 0).unwrap()
230 }
231 } else {
232 Time::from_hms(self.hour(), self.minute(), self.second() + 1).unwrap()
233 }
234 }
235
236 fn to_hour_seconds(&self) -> i64 {
237 self.hour() as i64 * 3600
238 }
239
240 fn to_minute_seconds(&self) -> i64 {
241 self.hour() as i64 * 3600 + self.minute() as i64 * 60
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248 use time::macros::time;
249
250 #[test]
251 fn test_shorten() {
252 let t = time!(22:01);
253 assert_eq!(t.to_shorten(), "22:01");
254
255 let t = time!(9:05);
256 assert_eq!(t.to_shorten(), "9:05");
257 }
258
259 #[test]
260 fn test_from_str() {
261 let t = <Time as ExtTime>::from_str("9:30").unwrap();
262 assert_eq!(t.hour(), 9);
263 assert_eq!(t.minute(), 30);
264
265 assert!(<Time as ExtTime>::from_str("25:00").is_err());
266 assert!(<Time as ExtTime>::from_str("invalid").is_err());
267 }
268
269 #[test]
270 fn test_sub_ext() {
271 let t1 = time!(23:00);
272 let t2 = time!(1:00);
273 assert_eq!(t1.sub_ext(t2), Duration::hours(22));
274 assert_eq!(t2.sub_ext(t1), Duration::hours(2));
275 }
276
277 #[test]
278 fn test_is_between() {
279 let t = time!(23:30);
280 assert!(t.is_between(time!(23:00), time!(0:00)));
281
282 let t = time!(0:30);
283 assert!(t.is_between(time!(23:00), time!(1:00)));
284
285 let t = time!(12:00);
286 assert!(!t.is_between(time!(23:00), time!(1:00)));
287 }
288
289 #[test]
290 fn test_add_minutes() {
291 let t = time!(23:30);
292 assert_eq!(t.add_minutes(40).to_shorten(), "0:10");
293
294 let t = time!(12:00);
295 assert_eq!(t.add_minutes(-30).to_shorten(), "11:30");
296 }
297
298 #[test]
299 fn test_from_seconds() {
300 let t = <Time as ExtTime>::from_seconds(37230).unwrap(); assert_eq!(t.hour(), 10);
302 assert_eq!(t.minute(), 20);
303 assert_eq!(t.second(), 30);
304
305 let t = <Time as ExtTime>::from_seconds(3660).unwrap(); assert_eq!(t.hour(), 1);
307 assert_eq!(t.minute(), 1);
308 assert_eq!(t.second(), 0);
309
310 assert!(<Time as ExtTime>::from_seconds(-1).is_err());
311 assert!(<Time as ExtTime>::from_seconds(24 * 3600).is_err());
312 }
313
314 #[test]
315 fn test_to_seconds() {
316 let t = time!(10:20:30);
317 assert_eq!(t.to_seconds(), 37230);
318
319 let t = time!(1:01:00);
320 assert_eq!(t.to_seconds(), 3660);
321
322 let t = time!(0:00:00);
323 assert_eq!(t.to_seconds(), 0);
324
325 let t = time!(23:59:59);
326 assert_eq!(t.to_seconds(), 86399);
327 }
328
329 #[test]
330 fn test_align_to() {
331 let t = time!(10:34:00);
333 let aligned = t.align_to(300).unwrap(); assert_eq!(aligned.hour(), 10);
335 assert_eq!(aligned.minute(), 30);
336 assert_eq!(aligned.second(), 0);
337
338 let t = time!(00:00:03);
340 let aligned = t.align_to(5).unwrap();
341 assert_eq!(aligned.hour(), 0);
342 assert_eq!(aligned.minute(), 0);
343 assert_eq!(aligned.second(), 0);
344
345 let t = time!(14:30:45);
347 let aligned = t.align_to(3600).unwrap();
348 assert_eq!(aligned.hour(), 14);
349 assert_eq!(aligned.minute(), 0);
350 assert_eq!(aligned.second(), 0);
351
352 let t = time!(10:00:00);
354 assert!(t.align_to(0).is_err());
355 assert!(t.align_to(24 * 3600).is_err());
356 }
357
358 #[test]
359 fn test_next_hour() {
360 let t = time!(10:30:45);
361 let next = t.next_hour();
362 assert_eq!(next.hour(), 11);
363 assert_eq!(next.minute(), 30);
364 assert_eq!(next.second(), 45);
365
366 let t = time!(23:30:45);
367 let next = t.next_hour();
368 assert_eq!(next.hour(), 0);
369 assert_eq!(next.minute(), 30);
370 assert_eq!(next.second(), 45);
371 }
372
373 #[test]
374 fn test_next_minute() {
375 let t = time!(10:30:45);
376 let next = t.next_minute();
377 assert_eq!(next.hour(), 10);
378 assert_eq!(next.minute(), 31);
379 assert_eq!(next.second(), 45);
380
381 let t = time!(10:59:45);
382 let next = t.next_minute();
383 assert_eq!(next.hour(), 11);
384 assert_eq!(next.minute(), 0);
385 assert_eq!(next.second(), 45);
386
387 let t = time!(23:59:45);
388 let next = t.next_minute();
389 assert_eq!(next.hour(), 0);
390 assert_eq!(next.minute(), 0);
391 assert_eq!(next.second(), 45);
392 }
393
394 #[test]
395 fn test_next_second() {
396 let t = time!(10:30:45);
397 let next = t.next_second();
398 assert_eq!(next.hour(), 10);
399 assert_eq!(next.minute(), 30);
400 assert_eq!(next.second(), 46);
401
402 let t = time!(10:30:59);
403 let next = t.next_second();
404 assert_eq!(next.hour(), 10);
405 assert_eq!(next.minute(), 31);
406 assert_eq!(next.second(), 0);
407
408 let t = time!(10:59:59);
409 let next = t.next_second();
410 assert_eq!(next.hour(), 11);
411 assert_eq!(next.minute(), 0);
412 assert_eq!(next.second(), 0);
413
414 let t = time!(23:59:59);
415 let next = t.next_second();
416 assert_eq!(next.hour(), 0);
417 assert_eq!(next.minute(), 0);
418 assert_eq!(next.second(), 0);
419 }
420
421 #[test]
422 fn test_to_hour_seconds() {
423 let t = time!(10:20:30);
424 assert_eq!(t.to_hour_seconds(), 36000); let t = time!(0:30:45);
427 assert_eq!(t.to_hour_seconds(), 0);
428
429 let t = time!(23:59:59);
430 assert_eq!(t.to_hour_seconds(), 82800); }
432
433 #[test]
434 fn test_to_minute_seconds() {
435 let t = time!(10:20:30);
436 assert_eq!(t.to_minute_seconds(), 37200); let t = time!(0:30:45);
439 assert_eq!(t.to_minute_seconds(), 1800); let t = time!(23:59:59);
442 assert_eq!(t.to_minute_seconds(), 86340); }
444}