opening_hours_syntax/extended_time.rs
1use std::convert::TryInto;
2use std::fmt::{Debug, Display};
3
4use chrono::{NaiveTime, Timelike};
5
6/// An hour+minute struct that can go up to 48h.
7#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
8pub struct ExtendedTime {
9 hour: u8,
10 minute: u8,
11}
12
13impl ExtendedTime {
14 /// 00:00 (start of day)
15 pub const MIDNIGHT_00: Self = Self::new(0, 0).unwrap();
16
17 /// 24:00 (end of day)
18 pub const MIDNIGHT_24: Self = Self::new(24, 0).unwrap();
19
20 /// 48:00 (end of next day)
21 pub const MIDNIGHT_48: Self = Self::new(48, 0).unwrap();
22
23 /// Create a new extended time, this may return `None` if input values are
24 /// out of range.
25 ///
26 /// ```
27 /// use opening_hours_syntax::ExtendedTime;
28 ///
29 /// assert!(ExtendedTime::new(28, 30).is_some());
30 /// assert!(ExtendedTime::new(72, 15).is_none()); // hours are out of bound
31 /// assert!(ExtendedTime::new(24, 60).is_none()); // minutes are out of bound
32 /// ```
33 #[inline]
34 pub const fn new(hour: u8, minute: u8) -> Option<Self> {
35 if hour > 48 || minute > 59 || (hour == 48 && minute > 0) {
36 None
37 } else {
38 Some(Self { hour, minute })
39 }
40 }
41
42 /// Get the number of full hours in this extended time.
43 ///
44 /// ```
45 /// use opening_hours_syntax::ExtendedTime;
46 ///
47 /// let time = ExtendedTime::new(27, 35).unwrap();
48 /// assert_eq!(time.hour(), 27);
49 /// ```
50 #[inline]
51 pub fn hour(self) -> u8 {
52 self.hour
53 }
54
55 /// Get the number of remaining minutes in this extended time.
56 ///
57 /// ```
58 /// use opening_hours_syntax::ExtendedTime;
59 ///
60 /// let time = ExtendedTime::new(27, 35).unwrap();
61 /// assert_eq!(time.minute(), 35);
62 /// ```
63 #[inline]
64 pub fn minute(self) -> u8 {
65 self.minute
66 }
67
68 /// Add plain minutes to the extended time and return `None` if this
69 /// results in it being out of bounds.
70 ///
71 /// ```
72 /// use opening_hours_syntax::ExtendedTime;
73 ///
74 /// let time = ExtendedTime::new(24, 0).unwrap();
75 /// assert_eq!(time.add_minutes(75), ExtendedTime::new(25, 15));
76 /// assert!(time.add_minutes(24 * 60 + 1).is_none());
77 /// assert!(time.add_minutes(-24 * 60 - 1).is_none());
78 /// ```
79 #[inline]
80 pub fn add_minutes(self, minutes: i16) -> Option<Self> {
81 let as_minutes = (self.mins_from_midnight() as i16).checked_add(minutes)?;
82 Self::from_mins_from_midnight(as_minutes.try_into().ok()?)
83 }
84
85 /// Add plain hours to the extended time and return `None` if this results
86 /// in it being out of bounds.
87 ///
88 /// ```
89 /// use opening_hours_syntax::ExtendedTime;
90 ///
91 /// let time = ExtendedTime::new(24, 15).unwrap();
92 /// assert_eq!(time.add_hours(3), ExtendedTime::new(27, 15));
93 /// assert!(time.add_hours(25).is_none());
94 /// assert!(time.add_hours(-25).is_none());
95 /// ```
96 #[inline]
97 pub fn add_hours(self, hours: i8) -> Option<Self> {
98 Self::new(
99 (i16::from(self.hour) + i16::from(hours)).try_into().ok()?,
100 self.minute,
101 )
102 }
103
104 /// Get the total number of minutes from *00:00*.
105 ///
106 /// ```
107 /// use opening_hours_syntax::ExtendedTime;
108 ///
109 /// let time = ExtendedTime::new(25, 15).unwrap();
110 /// assert_eq!(time.mins_from_midnight(), 25 * 60 + 15);
111 /// ```
112 #[inline]
113 pub fn mins_from_midnight(self) -> u16 {
114 u16::from(self.minute) + 60 * u16::from(self.hour)
115 }
116
117 /// Build an extended time from the total number of minutes from midnight
118 /// and return `None` if the result is out of bounds.
119 ///
120 /// ```
121 /// use opening_hours_syntax::ExtendedTime;
122 ///
123 /// assert_eq!(
124 /// ExtendedTime::from_mins_from_midnight(26 * 60 + 15),
125 /// ExtendedTime::new(26, 15),
126 /// );
127 ///
128 /// assert!(ExtendedTime::from_mins_from_midnight(65_000).is_none());
129 /// ```
130 #[inline]
131 pub fn from_mins_from_midnight(minute: u16) -> Option<Self> {
132 let hour = (minute / 60).try_into().ok()?;
133 let minute = (minute % 60).try_into().ok()?;
134 Self::new(hour, minute)
135 }
136}
137
138impl Display for ExtendedTime {
139 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140 write!(f, "{:02}:{:02}", self.hour, self.minute)
141 }
142}
143
144impl Debug for ExtendedTime {
145 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
146 write!(f, "{self}")
147 }
148}
149
150impl TryInto<NaiveTime> for ExtendedTime {
151 type Error = ();
152
153 #[inline]
154 fn try_into(self) -> Result<NaiveTime, Self::Error> {
155 NaiveTime::from_hms_opt(self.hour.into(), self.minute.into(), 0).ok_or(())
156 }
157}
158
159impl From<NaiveTime> for ExtendedTime {
160 #[inline]
161 fn from(time: NaiveTime) -> ExtendedTime {
162 Self {
163 hour: time.hour().try_into().expect("invalid NaiveTime"),
164 minute: time.minute().try_into().expect("invalid NaiveTime"),
165 }
166 }
167}