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