date_time/
time_tuple.rs

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/// A wrapper for a particular time of day.
14///
15/// Precise to second-level.
16///
17/// **NOTE:** This cannot be 24 hours or greater - see `TimeTuple::new()` for more details.
18///
19/// The wrapping described in `TimeTuple::new()` also applies when adding and subtracting times.
20#[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    /// Produces a new TimeTuple.
33    ///
34    /// Times of 24 hours or greater and negative times
35    /// will wrap around 24 hours to always produce a positive time.
36    ///
37    /// The value is calculated from total number of seconds so a time
38    /// with a minute value of 90 would add an hour to the resulting tuple
39    /// and set the minutes to 30, for example.
40    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    /// Same as `TimeTuple::new()` but takes the total number of seconds
49    /// as its argument and calculates the hours, minutes, and seconds
50    /// from that.
51    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    /// Returns a `TimeTuple` of the current time as `std::time::SystemTime` provides it.
67    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    /// Produces a string such as 08:30 for 8 hours and 30 minutes.
84    ///
85    /// Ignores seconds.
86    pub fn to_hhmm_string(self) -> String {
87        format!("{:02}:{:02}", self.h, self.m)
88    }
89
90    /// Gets the total number of seconds in the tuple.
91    pub fn to_seconds(self) -> u32 {
92        3600 * u32::from(self.h) + 60 * u32::from(self.m) + u32::from(self.s)
93    }
94
95    /// Gets the total number of minutes in the tuple, ignoring seconds.
96    pub fn to_minutes(self) -> u32 {
97        60 * u32::from(self.h) + u32::from(self.m)
98    }
99
100    /// Adds a number of seconds to the TimeTuple,
101    /// wrapping the same way `TimeTuple::new()` does.
102    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    /// Subtracts a number of seconds from the TimeTuple,
108    /// wrapping the same way `TimeTuple::new()` does.
109    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    /// Adds a number of minutes to the TimeTuple,
115    /// wrapping the same way `TimeTuple::new()` does.
116    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    /// Subtracts a number of minutes from the TimeTuple,
122    /// wrapping the same way `TimeTuple::new()` does.
123    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    /// Adds a number of hours to the TimeTuple,
129    /// wrapping the same way `TimeTuple::new()` does.
130    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    /// Subtracts a number of hours from the TimeTuple,
136    /// wrapping the same way `TimeTuple::new()` does.
137    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/// A wrapper for a duration.
229///
230/// Does not count in days, but can have hours >24 (up to `u32::MAX`)
231///
232/// Precise to second-level.
233///
234/// Cannot be negative.
235#[derive(PartialEq, Eq, Debug, Copy, Clone)]
236pub struct Duration {
237    h: u32,
238    m: u8,
239    s: u8,
240}
241
242impl Duration {
243    /// Produces a new Duration.
244    ///
245    /// The value is calculated from total number of seconds so a duration
246    /// with a minute value of 90 would add an hour to the resulting tuple
247    /// and set the minutes to 30, for example.
248    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    /// Same as `Duration::new()` but takes the total number of seconds
254    /// as its argument and calculates the hours, minutes, and seconds
255    /// from that.
256    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    /// Calculates the `Duration` between two `DateTimeTuple`s.
269    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    /// Produces a string such as 8:30 for 8 hours and 30 minutes.
298    ///
299    /// Hours field will expand as necessary; 150:30 is a possible result.
300    ///
301    /// Ignores seconds.
302    pub fn to_hhmm_string(self) -> String {
303        format!("{}:{:02}", self.h, self.m)
304    }
305
306    /// Produces a string such as 8:30 for 8 hours and 30 minutes.
307    ///
308    /// Hours field will expand as necessary; 150:30 is a possible result.
309    ///
310    /// Ignores seconds.
311    #[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    /// Gets the total number of seconds in the Duration.
317    pub fn to_seconds(self) -> u64 {
318        3600 * u64::from(self.h) + 60 * u64::from(self.m) + u64::from(self.s)
319    }
320
321    /// Gets the total number of minutes in the Duration, ignoring seconds.
322    pub fn to_minutes(self) -> u32 {
323        60 * self.h + u32::from(self.m)
324    }
325
326    /// Adds a number of seconds to the Duration,
327    /// wrapping the same way `Duration::new()` does.
328    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    /// Subtracts a number of seconds from the Duration,
334    /// wrapping the same way `Duration::new()` does.
335    pub fn subtract_seconds(&mut self, seconds: u32) {
336        *self = Duration::from_seconds(self.to_seconds() - u64::from(seconds));
337    }
338
339    /// Adds a number of minutes to the Duration,
340    /// wrapping the same way `Duration::new()` does.
341    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    /// Subtracts a number of minutes from the Duration,
347    /// wrapping the same way `Duration::new()` does.
348    pub fn subtract_minutes(&mut self, minutes: u32) {
349        *self = Duration::from_seconds(self.to_seconds() - u64::from(minutes) * 60);
350    }
351
352    /// Adds a number of hours to the Duration,
353    /// wrapping the same way `Duration::new()` does.
354    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    /// Subtracts a number of hours from the Duration,
360    /// wrapping the same way `Duration::new()` does.
361    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}