1use std::{
46 fmt,
47 ops::{Add, AddAssign, Sub, SubAssign},
48};
49
50#[derive(Debug, Clone, Copy, Default)]
60pub struct Watch {
61 pub start: i64,
63
64 pub offset: i64,
66
67 pub meridiem: bool,
69}
70
71impl Watch {
72 pub fn new(time: &str, meridiem: bool) -> Self {
74 let secs = Watch::str_to_secs(time, true);
75 Watch {
76 start: secs,
77 meridiem,
78 ..Default::default()
79 }
80 }
81
82 pub fn str_to_secs(time: &str, is_time_span: bool) -> i64 {
84 let pm = {
85 let time = time.replace('.', "").to_uppercase();
86 time.contains("PM") && is_time_span
87 };
88 let mut time = time.split(' ').next().unwrap_or("").split(':');
89 let mut hours = time.next().unwrap_or("").parse::<i64>().unwrap_or(0);
90 let minutes = time.next().unwrap_or("").parse::<i64>().unwrap_or(0);
91 let seconds = time.next().unwrap_or("").parse::<i64>().unwrap_or(0);
92
93 if pm {
94 hours += 12;
95 }
96 hours * 3600 + minutes * 60 + seconds
97 }
98
99 pub fn secs_to_mil(secs: i64) -> String {
101 let hours = secs / 3600 % 24;
102 let minutes = (secs % 3600) / 60;
103 let seconds = secs % 60;
104 format!("{:02}:{:02}:{:02}", hours, minutes, seconds)
105 }
106
107 pub fn secs_to_mer(secs: i64) -> String {
109 let mut hours = secs / 3600 % 24;
110 let minutes = (secs % 3600) / 60;
111 let seconds = secs % 60;
112
113 let meridiem = if hours >= 12 {
114 hours %= 12;
115 "PM"
116 } else {
117 "AM"
118 };
119
120 if hours == 0 {
121 hours = 12;
122 }
123
124 format!("{:02}:{:02}:{:02} {}", hours, minutes, seconds, meridiem)
125 }
126
127 pub fn diff_to_days(diff: i64) -> String {
129 let days = diff / 86400;
130 match days.cmp(&0) {
131 std::cmp::Ordering::Greater => format!(" +{} days", days),
132 std::cmp::Ordering::Less => format!(" -{} days", days.abs()),
133 std::cmp::Ordering::Equal => "".to_string(),
134 }
135 }
136
137 pub fn add_offset(&self) -> i64 {
139 self.start + self.offset
140 }
141
142 pub fn change_meridiem(&mut self, meridiem: bool) {
144 self.meridiem = meridiem;
145 }
146}
147
148impl Add<i64> for Watch {
150 type Output = Watch;
151
152 fn add(self, rhs: i64) -> Self::Output {
153 Watch {
154 offset: self.offset + rhs,
155 ..self
156 }
157 }
158}
159
160impl Sub<i64> for Watch {
161 type Output = Watch;
162
163 fn sub(self, rhs: i64) -> Self::Output {
164 Watch {
165 offset: self.offset - rhs,
166 ..self
167 }
168 }
169}
170
171impl Add<&str> for Watch {
172 type Output = Watch;
173
174 fn add(self, rhs: &str) -> Self::Output {
175 let secs = Watch::str_to_secs(rhs, false);
176 self + secs
177 }
178}
179
180impl Sub<&str> for Watch {
181 type Output = Watch;
182
183 fn sub(self, rhs: &str) -> Self::Output {
184 let secs = Watch::str_to_secs(rhs, false);
185 self - secs
186 }
187}
188
189trait AddableToWatch {}
191
192impl AddableToWatch for i64 {}
194impl AddableToWatch for &str {}
195
196impl<T: AddableToWatch + Copy> AddAssign<T> for Watch
197where
198 Watch: Add<T, Output = Watch>,
199{
200 fn add_assign(&mut self, other: T) {
201 *self = *self + other;
202 }
203}
204
205impl<T: AddableToWatch + Copy> SubAssign<T> for Watch
206where
207 Watch: Sub<T, Output = Watch>,
208{
209 fn sub_assign(&mut self, other: T) {
210 *self = *self - other;
211 }
212}
213
214impl fmt::Display for Watch {
216 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
217 let end = (self.add_offset() % 86400 + 86400) % 86400;
218
219 let diff = self.offset + self.start - end;
220
221 let end_str = if self.meridiem {
222 Watch::secs_to_mer(end)
223 } else {
224 Watch::secs_to_mil(end)
225 };
226
227 let diff_str = Watch::diff_to_days(diff);
228
229 write!(f, "{}{}", end_str, diff_str)
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use crate::Watch;
236
237 #[test]
238 fn basic_adding() {
239 let mut watch = Watch::new("13:33:23", false);
240 watch += "0:23:03";
241 assert_eq!(format!("{}", watch), "13:56:26");
242
243 watch += 1000;
244 assert_eq!(format!("{}", watch), "14:13:06");
245 }
246
247 #[test]
248 fn basic_subtracting() {
249 let mut watch = Watch::new("13:33:23", false);
250 watch -= "0:23:03";
251 assert_eq!(format!("{}", watch), "13:10:20");
252
253 watch -= 1000;
254 assert_eq!(format!("{}", watch), "12:53:40");
255 }
256
257 #[test]
258 fn basic_meridiem_adding() {
259 let mut watch = Watch::new("01:33:23 PM", true);
260 watch += "0:23:03";
261 assert_eq!(format!("{}", watch), "01:56:26 PM");
262
263 watch += 1000;
264 assert_eq!(format!("{}", watch), "02:13:06 PM");
265 }
266
267 #[test]
268 fn basic_meridiem_subtracting() {
269 let mut watch = Watch::new("13:33:23", true);
270 watch -= "0:23:03";
271 assert_eq!(format!("{}", watch), "01:10:20 PM");
272
273 watch -= 1000;
274 assert_eq!(format!("{}", watch), "12:53:40 PM");
275 }
276
277 #[test]
278 fn day_overflow_adding() {
279 let mut watch = Watch::new("13:33:23", false);
280 watch += "23:44:03";
281 assert_eq!(format!("{}", watch), "13:17:26 +1 days");
282
283 watch += 7989;
284 assert_eq!(format!("{}", watch), "15:30:35 +1 days");
285 }
286
287 #[test]
288 fn day_overflow_subtracting() {
289 let mut watch = Watch::new("13:33:23", false);
290 watch -= "23:44:03";
291 assert_eq!(format!("{}", watch), "13:49:20 -1 days");
292
293 watch -= 7989;
294 assert_eq!(format!("{}", watch), "11:36:11 -1 days");
295 }
296
297 #[test]
298 fn day_overflow_meridiem_adding() {
299 let mut watch = Watch::new("01:33:23 PM", true);
300 watch += "23:44:03";
301 assert_eq!(format!("{}", watch), "01:17:26 PM +1 days");
302
303 watch += 7989;
304 assert_eq!(format!("{}", watch), "03:30:35 PM +1 days");
305 }
306
307 #[test]
308 fn day_overflow_meridiem_subtracting() {
309 let mut watch = Watch::new("13:33:23", true);
310 watch -= "23:44:03";
311 assert_eq!(format!("{}", watch), "01:49:20 PM -1 days");
312
313 watch -= 7989;
314 assert_eq!(format!("{}", watch), "11:36:11 AM -1 days");
315 }
316
317 #[test]
318 fn large_subtraction() {
319 let mut watch = Watch::new("13:34", true);
320 watch -= 100000000;
321 assert_eq!(format!("{}", watch), "03:47:20 AM -1157 days");
322 }
323
324 #[test]
325 fn changing_meridiem() {
326 let mut watch = Watch::new("13:34", true);
327 watch -= 100000000;
328 assert_eq!(format!("{}", watch), "03:47:20 AM -1157 days");
329
330 watch.change_meridiem(false);
331 watch += "13:23:03";
332 assert_eq!(format!("{}", watch), "17:10:23 -1157 days");
333 }
334}