1 // Copyright (C) 2017 Sebastian Dröge <sebastian@centricular.com>
2 //
3 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6 // option. This file may not be copied, modified, or distributed
7 // except according to those terms.
8 
9 use std::cmp;
10 use std::fmt;
11 
12 use DateTime;
13 
14 impl DateTime {
15     /// Get the [`DateTime`](struct.DateTime.html) in UTC
to_utc(&self) -> Result<DateTime, glib::BoolError>16     pub fn to_utc(&self) -> Result<DateTime, glib::BoolError> {
17         if !self.has_time() {
18             // No time => no TZ offset
19             return Ok(self.clone());
20         }
21 
22         assert!(self.has_year() && self.has_month() && self.has_day() && self.has_time());
23 
24         // Can instantiate `gst::DateTime` without seconds using `gst::DateTime::new`
25         // with `-1f64` for the `second` argument
26         // however, the resulting instance can't be translated to `glib::DateTime`
27         if self.has_second() {
28             self.to_g_date_time()
29                 .expect("DateTime::to_utc: to_g_date_time")
30                 .to_utc()
31                 .as_ref()
32                 .ok_or_else(|| glib_bool_error!("Can't convert datetime to UTC"))
33                 .map(DateTime::new_from_g_date_time)
34         } else {
35             // It would be cheaper to build a `glib::DateTime` direcly, unfortunetaly
36             // this would require using `glib::TimeZone::new_offset` which is feature-gated
37             // to `glib/v2_58`. So we need to build a new `gst::DateTime` with `0f64`
38             // and then discard seconds again
39             DateTime::new(
40                 self.get_time_zone_offset(),
41                 self.get_year(),
42                 self.get_month(),
43                 self.get_day(),
44                 self.get_hour(),
45                 self.get_minute(),
46                 0f64,
47             )
48             .to_g_date_time()
49             .expect("DateTime::to_utc: to_g_date_time")
50             .to_utc()
51             .ok_or_else(|| glib_bool_error!("Can't convert datetime to UTC"))
52             .map(|g_date_time_utc| {
53                 DateTime::new(
54                     0f32, // UTC TZ offset
55                     g_date_time_utc.get_year(),
56                     g_date_time_utc.get_month(),
57                     g_date_time_utc.get_day_of_month(),
58                     g_date_time_utc.get_hour(),
59                     g_date_time_utc.get_minute(),
60                     -1f64, // No second
61                 )
62             })
63         }
64     }
65 }
66 
67 impl cmp::PartialOrd for DateTime {
68     /// *NOTE 1:* When comparing a partially defined [`DateTime`](struct.DateTime.html) `d1`
69     /// such as *"2019/8/20"* with a [`DateTime`](struct.DateTime.html) with a time part `d2`
70     /// such as *"2019/8/20 21:10"*:
71     ///
72     /// - `d1` includes `d2`,
73     /// - neither `d1` < `d2` nor `d1` > `d2`,
74     /// - and `d1` != `d2`,
75     ///
76     /// so we can only return `None`.
77     ///
78     /// This is the reason why [`DateTime`](struct.DateTime.html) neither implements
79     /// [`Ord`](https://doc.rust-lang.org/nightly/std/cmp/trait.Ord.html)
80     /// nor [`Eq`](https://doc.rust-lang.org/nightly/std/cmp/trait.Eq.html).
81     ///
82     /// *NOTE 2:* When comparing a [`DateTime`](struct.DateTime.html) `d1` without a TZ offset
83     /// such as *"2019/8/20"* with a [`DateTime`](struct.DateTime.html) `d2` with a TZ offset
84     /// such as *"2019/8/20 21:10 +02:00"*, we can't tell in which TZ `d1` is expressed and which
85     /// time should be considered for an offset, therefore the two [`DateTime`s](struct.DateTime.html)
86     /// are compared in the same TZ.
partial_cmp(&self, other: &Self) -> Option<cmp::Ordering>87     fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
88         #[inline]
89         fn get_cmp(delta: i32) -> Option<cmp::Ordering> {
90             Some(delta.cmp(&0))
91         }
92 
93         if !(self.has_year() && other.has_year()) {
94             // Can't compare anything
95             return None;
96         }
97 
98         // Normalize to UTC only if both members have time (see note 2).
99         let (self_norm, other_norm) = if self.has_time() && other.has_time() {
100             (self.to_utc().ok()?, other.to_utc().ok()?)
101         } else {
102             (self.clone(), other.clone())
103         };
104 
105         let year_delta = self_norm.get_year() - other_norm.get_year();
106         if year_delta != 0 {
107             return get_cmp(year_delta);
108         }
109 
110         // Same year
111 
112         if !self.has_month() && !other.has_month() {
113             // Nothing left to compare
114             return get_cmp(year_delta);
115         }
116 
117         if !(self.has_month() && other.has_month()) {
118             // One has month, the other doesn't => can't compare (note 1)
119             return None;
120         }
121 
122         let month_delta = self_norm.get_month() - other_norm.get_month();
123         if month_delta != 0 {
124             return get_cmp(month_delta);
125         }
126 
127         // Same year, same month
128 
129         if !self.has_day() && !other.has_day() {
130             // Nothing left to compare
131             return Some(cmp::Ordering::Equal);
132         }
133 
134         if !(self.has_day() && other.has_day()) {
135             // One has day, the other doesn't => can't compare (note 1)
136             return None;
137         }
138 
139         let day_delta = self_norm.get_day() - other_norm.get_day();
140         if day_delta != 0 {
141             return get_cmp(day_delta);
142         }
143 
144         // Same year, same month, same day
145 
146         if !self.has_time() && !other.has_time() {
147             // Nothing left to compare
148             return Some(cmp::Ordering::Equal);
149         }
150 
151         if !(self.has_time() && other.has_time()) {
152             // One has time, the other doesn't => can't compare (note 1)
153             return None;
154         }
155 
156         let hour_delta = self_norm.get_hour() - other_norm.get_hour();
157         if hour_delta != 0 {
158             return get_cmp(hour_delta);
159         }
160 
161         let minute_delta = self_norm.get_minute() - other_norm.get_minute();
162         if minute_delta != 0 {
163             return get_cmp(minute_delta);
164         }
165 
166         // Same year, same month, same day, same time
167 
168         if !self.has_second() && !other.has_second() {
169             // Nothing left to compare
170             return Some(cmp::Ordering::Equal);
171         }
172 
173         if !(self.has_second() && other.has_second()) {
174             // One has second, the other doesn't => can't compare (note 1)
175             return None;
176         }
177         let second_delta = self_norm.get_second() - other_norm.get_second();
178         if second_delta != 0 {
179             return get_cmp(second_delta);
180         }
181 
182         get_cmp(self_norm.get_microsecond() - other_norm.get_microsecond())
183     }
184 }
185 
186 impl cmp::PartialEq for DateTime {
187     /// See the notes for the [`DateTime` `PartialOrd` trait](struct.DateTime.html#impl-PartialOrd%3CDateTime%3E)
eq(&self, other: &Self) -> bool188     fn eq(&self, other: &Self) -> bool {
189         self.partial_cmp(other)
190             .map_or_else(|| false, |cmp| cmp == cmp::Ordering::Equal)
191     }
192 }
193 
194 impl fmt::Debug for DateTime {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result195     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
196         let mut debug_struct = f.debug_struct("DateTime");
197         if self.has_year() {
198             debug_struct.field("year", &self.get_year());
199         }
200         if self.has_month() {
201             debug_struct.field("month", &self.get_month());
202         }
203         if self.has_day() {
204             debug_struct.field("day", &self.get_day());
205         }
206         if self.has_time() {
207             debug_struct.field("hour", &self.get_hour());
208             debug_struct.field("minute", &self.get_minute());
209 
210             if self.has_second() {
211                 debug_struct.field("second", &self.get_second());
212                 debug_struct.field("microsecond", &self.get_microsecond());
213             }
214 
215             debug_struct.field("tz_offset", &self.get_time_zone_offset());
216         }
217 
218         debug_struct.finish()
219     }
220 }
221 
222 impl fmt::Display for DateTime {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result223     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
224         f.write_str(
225             self.to_iso8601_string()
226                 .unwrap_or_else(|_| "None".into())
227                 .as_str(),
228         )
229     }
230 }
231 
232 #[cfg(test)]
233 mod tests {
234     use super::*;
235 
236     #[allow(clippy::cognitive_complexity)]
237     #[test]
test_to_utc()238     fn test_to_utc() {
239         ::init().unwrap();
240 
241         // Hour offset
242         let utc_date_time = DateTime::new(2f32, 2019, 8, 20, 20, 9, 42.123_456f64)
243             .to_utc()
244             .unwrap();
245         assert_eq!(utc_date_time.get_year(), 2019);
246         assert_eq!(utc_date_time.get_month(), 8);
247         assert_eq!(utc_date_time.get_day(), 20);
248         assert_eq!(utc_date_time.get_hour(), 18);
249         assert_eq!(utc_date_time.get_minute(), 9);
250         assert_eq!(utc_date_time.get_second(), 42);
251         assert_eq!(utc_date_time.get_microsecond(), 123_456);
252 
253         // Year, month, day and hour offset
254         let utc_date_time = DateTime::new(2f32, 2019, 1, 1, 0, 0, 42.123_456f64)
255             .to_utc()
256             .unwrap();
257         assert_eq!(utc_date_time.get_year(), 2018);
258         assert_eq!(utc_date_time.get_month(), 12);
259         assert_eq!(utc_date_time.get_day(), 31);
260         assert_eq!(utc_date_time.get_hour(), 22);
261         assert_eq!(utc_date_time.get_minute(), 0);
262         assert_eq!(utc_date_time.get_second(), 42);
263         assert_eq!(utc_date_time.get_microsecond(), 123_456);
264 
265         // Date without an hour (which implies no TZ)
266         let utc_date_time = DateTime::new_ymd(2019, 1, 1).to_utc().unwrap();
267         assert_eq!(utc_date_time.get_year(), 2019);
268         assert_eq!(utc_date_time.get_month(), 1);
269         assert_eq!(utc_date_time.get_day(), 1);
270         assert!(!utc_date_time.has_time());
271         assert!(!utc_date_time.has_second());
272 
273         // Date without seconds
274         let utc_date_time = DateTime::new(2f32, 2018, 5, 28, 16, 6, -1f64)
275             .to_utc()
276             .unwrap();
277         assert_eq!(utc_date_time.get_year(), 2018);
278         assert_eq!(utc_date_time.get_month(), 5);
279         assert_eq!(utc_date_time.get_day(), 28);
280         assert_eq!(utc_date_time.get_hour(), 14);
281         assert_eq!(utc_date_time.get_minute(), 6);
282         assert!(!utc_date_time.has_second());
283     }
284 
285     #[test]
test_partial_ord()286     fn test_partial_ord() {
287         ::init().unwrap();
288 
289         // Different years
290         assert!(
291             DateTime::new(2f32, 2020, 8, 20, 19, 43, 42.123_456f64)
292                 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64)
293         );
294 
295         // Different months (order intentionally reversed)
296         assert!(
297             DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64)
298                 < DateTime::new(2f32, 2019, 9, 19, 19, 43, 42.123_456f64)
299         );
300 
301         // Different days
302         assert!(
303             DateTime::new(2f32, 2019, 8, 21, 19, 43, 42.123_456f64)
304                 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64)
305         );
306 
307         // Different hours
308         assert!(
309             DateTime::new(2f32, 2019, 8, 20, 19, 44, 42.123_456f64)
310                 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64)
311         );
312 
313         // Different minutes
314         assert!(
315             DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64)
316                 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64)
317         );
318 
319         // Different seconds
320         assert!(
321             DateTime::new(2f32, 2019, 8, 20, 19, 43, 43.123_456f64)
322                 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64)
323         );
324 
325         // Different micro-seconds
326         assert!(
327             DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_457f64)
328                 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64)
329         );
330 
331         // Different TZ offsets
332         assert!(
333             DateTime::new(1f32, 2019, 8, 20, 19, 43, 42.123_456f64)
334                 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64)
335         );
336 
337         // TZ offset leading to year, month, day, hour offset
338         assert!(
339             DateTime::new(2f32, 2019, 1, 1, 0, 0, 0f64)
340                 < DateTime::new(1f32, 2018, 12, 31, 23, 59, 0f64)
341         );
342 
343         // Partially defined `DateTime`
344         assert!(DateTime::new_ymd(2020, 8, 20) > DateTime::new_ymd(2019, 8, 20));
345         assert!(DateTime::new_ymd(2019, 9, 20) > DateTime::new_ymd(2019, 8, 20));
346         assert!(DateTime::new_ymd(2019, 8, 21) > DateTime::new_ymd(2019, 8, 20));
347 
348         assert!(DateTime::new_ym(2020, 8) > DateTime::new_ym(2019, 8));
349         assert!(DateTime::new_ym(2019, 9) > DateTime::new_ym(2019, 8));
350         assert!(DateTime::new_ym(2019, 9) > DateTime::new_ymd(2019, 8, 20));
351 
352         assert!(DateTime::new_y(2020) > DateTime::new_y(2019));
353         assert!(DateTime::new_ym(2020, 1) > DateTime::new_y(2019));
354 
355         assert!(
356             DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64)
357                 < DateTime::new_ymd(2020, 8, 20)
358         );
359 
360         assert!(
361             DateTime::new_ymd(2020, 8, 20)
362                 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64)
363         );
364 
365         // Comparison occurs on the same TZ when the `DateTime` doesn't have time (note 2)
366         assert!(DateTime::new_ymd(2020, 1, 1) > DateTime::new(-2f32, 2019, 12, 31, 23, 59, 0f64));
367 
368         // In the following cases, the partially defined `DateTime` is a range WRT
369         // the fully defined `DateTime` and this range includes the fully defined `DateTime`,
370         // but we can't tell if it's before or after and they are not equal (note 1)
371         assert!(DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64)
372             .partial_cmp(&DateTime::new_ymd(2019, 8, 20))
373             .is_none());
374 
375         assert!(DateTime::new_ymd(2019, 8, 20)
376             .partial_cmp(&DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64))
377             .is_none());
378 
379         assert!(DateTime::new_ym(2019, 1)
380             .partial_cmp(&DateTime::new_y(2019))
381             .is_none());
382     }
383 
384     #[test]
test_eq()385     fn test_eq() {
386         ::init().unwrap();
387 
388         assert_eq!(
389             DateTime::new(2f32, 2018, 5, 28, 16, 6, 42.123_456f64),
390             DateTime::new(2f32, 2018, 5, 28, 16, 6, 42.123_456f64)
391         );
392 
393         assert_eq!(
394             DateTime::new(2f32, 2018, 5, 28, 16, 6, 0f64),
395             DateTime::new(2f32, 2018, 5, 28, 16, 6, 0f64)
396         );
397 
398         assert_eq!(
399             DateTime::new(2f32, 2018, 5, 28, 16, 6, -1f64),
400             DateTime::new(2f32, 2018, 5, 28, 16, 6, -1f64)
401         );
402 
403         assert_eq!(
404             DateTime::new_ymd(2018, 5, 28),
405             DateTime::new_ymd(2018, 5, 28)
406         );
407 
408         // In the following cases, the partially defined `DateTime` is a range WRT
409         // the fully defined `DateTime` and this range includes the fully defined `DateTime`,
410         // but they are not equal (note 1)
411         assert_ne!(
412             DateTime::new_ymd(2018, 5, 28),
413             DateTime::new(2f32, 2018, 5, 28, 16, 6, -1f64)
414         );
415 
416         assert_ne!(
417             DateTime::new(2f32, 2018, 5, 28, 16, 6, -1f64),
418             DateTime::new_ym(2018, 5)
419         );
420 
421         assert_ne!(
422             DateTime::new(2f32, 2018, 5, 28, 16, 6, -1f64),
423             DateTime::new_y(2018)
424         );
425     }
426 }
427