1use crate::date_time_tuple::DateTimeTuple;
2use date_utils;
3use regex::Regex;
4use std::cmp::Ordering;
5use std::convert::From;
6use std::fmt;
7use std::ops::{Add, AddAssign, Sub, SubAssign};
8use std::str::FromStr;
9
10pub type Time = TimeTuple;
11pub type TimeOfDay = TimeTuple;
12
13#[cfg_attr(
21 feature = "serde_support",
22 derive(serde::Serialize, serde::Deserialize)
23)]
24#[derive(PartialEq, Eq, Debug, Copy, Clone, Hash)]
25pub struct TimeTuple {
26 h: u8,
27 m: u8,
28 s: u8,
29}
30
31impl TimeTuple {
32 pub fn new(h: i32, m: i32, s: i32) -> TimeTuple {
41 let mut total_seconds = s + 60 * m + 3600 * h;
42 while total_seconds < 0 {
43 total_seconds += 86400;
44 }
45 TimeTuple::from_seconds(total_seconds as u64)
46 }
47
48 pub fn from_seconds(mut total_seconds: u64) -> TimeTuple {
52 while total_seconds >= 86400 {
53 total_seconds -= 86400;
54 }
55 let h = total_seconds / 3600;
56 total_seconds -= h * 3600;
57 let m = total_seconds / 60;
58 total_seconds -= m * 60;
59 TimeTuple {
60 h: h as u8,
61 m: m as u8,
62 s: total_seconds as u8,
63 }
64 }
65
66 pub fn now() -> TimeTuple {
68 date_utils::now_as_timetuple()
69 }
70
71 pub fn get_hours(self) -> u8 {
72 self.h
73 }
74
75 pub fn get_minutes(self) -> u8 {
76 self.m
77 }
78
79 pub fn get_seconds(self) -> u8 {
80 self.s
81 }
82
83 pub fn to_hhmm_string(self) -> String {
87 format!("{:02}:{:02}", self.h, self.m)
88 }
89
90 pub fn to_seconds(self) -> u32 {
92 3600 * u32::from(self.h) + 60 * u32::from(self.m) + u32::from(self.s)
93 }
94
95 pub fn to_minutes(self) -> u32 {
97 60 * u32::from(self.h) + u32::from(self.m)
98 }
99
100 pub fn add_seconds(&mut self, seconds: i32) {
103 let new_seconds = i32::from(self.s) + seconds;
104 *self = TimeTuple::new(i32::from(self.h), i32::from(self.m), new_seconds);
105 }
106
107 pub fn subtract_seconds(&mut self, seconds: i32) {
110 let new_seconds = i32::from(self.s) - seconds;
111 *self = TimeTuple::new(i32::from(self.h), i32::from(self.m), new_seconds);
112 }
113
114 pub fn add_minutes(&mut self, minutes: i32) {
117 let new_minutes = i32::from(self.m) + minutes;
118 *self = TimeTuple::new(i32::from(self.h), new_minutes, i32::from(self.s));
119 }
120
121 pub fn subtract_minutes(&mut self, minutes: i32) {
124 let new_minutes = i32::from(self.m) - minutes;
125 *self = TimeTuple::new(i32::from(self.h), new_minutes, i32::from(self.s));
126 }
127
128 pub fn add_hours(&mut self, hours: i32) {
131 let new_hours = i32::from(self.h) + hours;
132 *self = TimeTuple::new(new_hours, i32::from(self.m), i32::from(self.s));
133 }
134
135 pub fn subtract_hours(&mut self, hours: i32) {
138 let new_hours = i32::from(self.h) - hours;
139 *self = TimeTuple::new(new_hours, i32::from(self.m), i32::from(self.s));
140 }
141}
142
143impl fmt::Display for TimeTuple {
144 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
145 write!(f, "{:02}:{:02}:{:02}", self.h, self.m, self.s)
146 }
147}
148
149impl FromStr for TimeTuple {
150 type Err = String;
151
152 fn from_str(s: &str) -> Result<TimeTuple, Self::Err> {
153 lazy_static! {
154 static ref VALID_FORMAT: Regex = Regex::new(r"^\d{2}:\d{2}:\d{2}$").unwrap();
155 }
156
157 if !VALID_FORMAT.is_match(s) {
158 Err(format!(
159 "Invalid str formatting of TimeTuple: {}\nExpects a string formatted like 08:30:05",
160 s
161 ))
162 } else {
163 let mut parts = s.split(':');
164 Ok(TimeTuple::new(
165 i32::from_str(parts.next().unwrap()).unwrap(),
166 i32::from_str(parts.next().unwrap()).unwrap(),
167 i32::from_str(parts.next().unwrap()).unwrap(),
168 ))
169 }
170 }
171}
172
173impl PartialOrd for TimeTuple {
174 fn partial_cmp(&self, other: &TimeTuple) -> Option<Ordering> {
175 self.to_seconds().partial_cmp(&other.to_seconds())
176 }
177}
178
179#[cfg_attr(tarpaulin, skip)]
180impl Ord for TimeTuple {
181 fn cmp(&self, other: &TimeTuple) -> Ordering {
182 self.to_seconds().cmp(&other.to_seconds())
183 }
184}
185
186impl Add for TimeTuple {
187 type Output = TimeTuple;
188 fn add(self, other: TimeTuple) -> TimeTuple {
189 TimeTuple::new(
190 i32::from(self.h + other.h),
191 i32::from(self.m + other.m),
192 i32::from(self.s + other.s),
193 )
194 }
195}
196
197impl AddAssign for TimeTuple {
198 fn add_assign(&mut self, other: TimeTuple) {
199 *self = TimeTuple::new(
200 i32::from(self.h + other.h),
201 i32::from(self.m + other.m),
202 i32::from(self.s + other.s),
203 );
204 }
205}
206
207impl Sub for TimeTuple {
208 type Output = TimeTuple;
209 fn sub(self, other: TimeTuple) -> TimeTuple {
210 TimeTuple::new(
211 i32::from(self.h - other.h),
212 i32::from(self.m - other.m),
213 i32::from(self.s - other.s),
214 )
215 }
216}
217
218impl SubAssign for TimeTuple {
219 fn sub_assign(&mut self, other: TimeTuple) {
220 *self = TimeTuple::new(
221 i32::from(self.h - other.h),
222 i32::from(self.m - other.m),
223 i32::from(self.s - other.s),
224 );
225 }
226}
227
228#[derive(PartialEq, Eq, Debug, Copy, Clone)]
236pub struct Duration {
237 h: u32,
238 m: u8,
239 s: u8,
240}
241
242impl Duration {
243 pub fn new(h: u32, m: u32, s: u32) -> Duration {
249 let total_seconds: u64 = u64::from(s) + 60 * u64::from(m) + 3600 * u64::from(h);
250 Duration::from_seconds(total_seconds)
251 }
252
253 pub fn from_seconds(mut total_seconds: u64) -> Duration {
257 let h = total_seconds / 3600;
258 total_seconds -= h * 3600;
259 let m = total_seconds / 60;
260 total_seconds -= m * 60;
261 Duration {
262 h: h as u32,
263 m: m as u8,
264 s: total_seconds as u8,
265 }
266 }
267
268 pub fn between(dt1: DateTimeTuple, dt2: DateTimeTuple) -> Duration {
270 if dt1 == dt2 {
271 return Duration { h: 0, m: 0, s: 0 };
272 }
273 let smaller = if dt1 < dt2 { dt1 } else { dt2 };
274 let greater = if dt1 < dt2 { dt2 } else { dt1 };
275 let days_between = greater.get_date().to_days() - smaller.get_date().to_days();
276 if days_between == 0 {
277 Duration::from(greater.get_time()) - Duration::from(smaller.get_time())
278 } else {
279 let time_between = Duration::from(greater.get_time()) + Duration::new(24, 0, 0)
280 - Duration::from(smaller.get_time());
281 time_between + Duration::new(24 * (days_between - 1), 0, 0)
282 }
283 }
284
285 pub fn get_hours(self) -> u32 {
286 self.h
287 }
288
289 pub fn get_minutes(self) -> u8 {
290 self.m
291 }
292
293 pub fn get_seconds(self) -> u8 {
294 self.s
295 }
296
297 pub fn to_hhmm_string(self) -> String {
303 format!("{}:{:02}", self.h, self.m)
304 }
305
306 #[deprecated(since = "2.1.0", note = "Replace with to_hhmm_string()")]
312 pub fn to_hours_and_minutes_string(self) -> String {
313 format!("{}:{:02}", self.h, self.m)
314 }
315
316 pub fn to_seconds(self) -> u64 {
318 3600 * u64::from(self.h) + 60 * u64::from(self.m) + u64::from(self.s)
319 }
320
321 pub fn to_minutes(self) -> u32 {
323 60 * self.h + u32::from(self.m)
324 }
325
326 pub fn add_seconds(&mut self, seconds: u32) {
329 let new_seconds = u32::from(self.s) + seconds;
330 *self = Duration::new(self.h, u32::from(self.m), new_seconds);
331 }
332
333 pub fn subtract_seconds(&mut self, seconds: u32) {
336 *self = Duration::from_seconds(self.to_seconds() - u64::from(seconds));
337 }
338
339 pub fn add_minutes(&mut self, minutes: u32) {
342 let new_minutes = u32::from(self.m) + minutes;
343 *self = Duration::new(self.h, new_minutes, u32::from(self.s));
344 }
345
346 pub fn subtract_minutes(&mut self, minutes: u32) {
349 *self = Duration::from_seconds(self.to_seconds() - u64::from(minutes) * 60);
350 }
351
352 pub fn add_hours(&mut self, hours: u32) {
355 let new_hours = self.h + hours;
356 *self = Duration::new(new_hours, u32::from(self.m), u32::from(self.s));
357 }
358
359 pub fn subtract_hours(&mut self, hours: u32) {
362 let new_hours = self.h - hours;
363 *self = Duration::new(new_hours, u32::from(self.m), u32::from(self.s));
364 }
365}
366
367impl fmt::Display for Duration {
368 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
369 write!(f, "{}:{:02}:{:02}", self.h, self.m, self.s)
370 }
371}
372
373impl FromStr for Duration {
374 type Err = String;
375
376 fn from_str(s: &str) -> Result<Duration, Self::Err> {
377 lazy_static! {
378 static ref VALID_FORMAT: Regex = Regex::new(r"^\d+:\d{2}:\d{2}$").unwrap();
379 }
380 if !VALID_FORMAT.is_match(s) {
381 Err(format!(
382 "Invalid str formatting of Duration: {}\nExpects a string formatted like 8:30:05",
383 s
384 ))
385 } else {
386 let mut parts = s.split(':');
387 Ok(Duration::new(
388 u32::from_str(parts.next().unwrap()).unwrap(),
389 u32::from_str(parts.next().unwrap()).unwrap(),
390 u32::from_str(parts.next().unwrap()).unwrap(),
391 ))
392 }
393 }
394}
395
396impl PartialOrd for Duration {
397 fn partial_cmp(&self, other: &Duration) -> Option<Ordering> {
398 self.to_seconds().partial_cmp(&other.to_seconds())
399 }
400}
401
402#[cfg_attr(tarpaulin, skip)]
403impl Ord for Duration {
404 fn cmp(&self, other: &Duration) -> Ordering {
405 self.to_seconds().cmp(&other.to_seconds())
406 }
407}
408
409impl Add for Duration {
410 type Output = Duration;
411 fn add(self, other: Duration) -> Duration {
412 Duration::new(
413 self.h + other.h,
414 u32::from(self.m + other.m),
415 u32::from(self.s + other.s),
416 )
417 }
418}
419
420impl AddAssign for Duration {
421 fn add_assign(&mut self, other: Duration) {
422 *self = Duration::new(
423 self.h + other.h,
424 u32::from(self.m + other.m),
425 u32::from(self.s + other.s),
426 );
427 }
428}
429
430impl Sub for Duration {
431 type Output = Duration;
432 fn sub(self, other: Duration) -> Duration {
433 Duration::new(
434 self.h - other.h,
435 u32::from(self.m - other.m),
436 u32::from(self.s - other.s),
437 )
438 }
439}
440
441impl SubAssign for Duration {
442 fn sub_assign(&mut self, other: Duration) {
443 *self = Duration::new(
444 self.h - other.h,
445 u32::from(self.m - other.m),
446 u32::from(self.s - other.s),
447 );
448 }
449}
450
451impl From<TimeTuple> for Duration {
452 fn from(time: TimeTuple) -> Self {
453 Duration::from_seconds(u64::from(time.to_seconds()))
454 }
455}
456
457#[cfg(test)]
458mod tests {
459
460 use super::TimeTuple;
461
462 #[test]
463 fn test_no_seconds() {
464 let tuple = TimeTuple::new(5, 30, 0);
465 assert_eq!(5, tuple.h);
466 assert_eq!(30, tuple.m);
467 assert_eq!(0, tuple.s);
468 }
469
470 #[test]
471 fn test_no_overlap() {
472 let tuple = TimeTuple::new(5, 30, 30);
473 assert_eq!(5, tuple.h);
474 assert_eq!(30, tuple.m);
475 assert_eq!(30, tuple.s);
476 }
477
478 #[test]
479 fn test_second_overlap() {
480 let tuple = TimeTuple::new(6, 30, 90);
481 assert_eq!(6, tuple.h);
482 assert_eq!(31, tuple.m);
483 assert_eq!(30, tuple.s);
484 }
485
486 #[test]
487 fn test_minute_overlap() {
488 let tuple = TimeTuple::new(6, 90, 30);
489 assert_eq!(7, tuple.h);
490 assert_eq!(30, tuple.m);
491 assert_eq!(30, tuple.s);
492 }
493
494 #[test]
495 fn test_hour_overlap() {
496 let tuple = TimeTuple::new(25, 30, 30);
497 assert_eq!(1, tuple.h);
498 assert_eq!(30, tuple.m);
499 assert_eq!(30, tuple.s);
500 }
501
502 #[test]
503 fn test_all_overlap() {
504 let tuple = TimeTuple::new(25, 90, 90);
505 assert_eq!(2, tuple.h);
506 assert_eq!(31, tuple.m);
507 assert_eq!(30, tuple.s);
508 }
509
510 #[test]
511 fn test_minutes_to_hours_overlap() {
512 let tuple = TimeTuple::new(18, 420, 0);
513 assert_eq!(1, tuple.h);
514 assert_eq!(0, tuple.m);
515 assert_eq!(0, tuple.s);
516 }
517
518 #[test]
519 fn test_negative_seconds() {
520 let tuple = TimeTuple::new(6, 30, -60);
521 assert_eq!(6, tuple.h);
522 assert_eq!(29, tuple.m);
523 assert_eq!(0, tuple.s);
524 }
525
526 #[test]
527 fn test_all_negative_overlaps() {
528 let tuple = TimeTuple::new(-3, -116, -301);
529 assert_eq!(18, tuple.h);
530 assert_eq!(58, tuple.m);
531 assert_eq!(59, tuple.s);
532 }
533}