gstreamer/
date_time.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::{cmp, fmt};
4
5use glib::translate::*;
6
7use crate::{ffi, DateTime};
8
9// Validate that the given values result in a valid DateTime
10fn validate(
11    tzoffset: Option<f32>,
12    year: i32,
13    month: Option<i32>,
14    day: Option<i32>,
15    hour: Option<i32>,
16    minute: Option<i32>,
17    seconds: Option<f64>,
18) -> Result<(), glib::BoolError> {
19    skip_assert_initialized!();
20
21    // Check for valid ranges
22    if year <= 0 || year > 9999 {
23        return Err(glib::bool_error!(
24            "Can't create DateTime: Year out of range"
25        ));
26    }
27
28    if let Some(month) = month {
29        if month <= 0 || month > 12 {
30            return Err(glib::bool_error!(
31                "Can't create DateTime: Month out of range"
32            ));
33        }
34    }
35
36    if let Some(day) = day {
37        if day <= 0 || day > 31 {
38            return Err(glib::bool_error!("Can't create DateTime: Day out of range"));
39        }
40    }
41
42    if let Some(hour) = hour {
43        if hour < 0 || hour >= 24 {
44            return Err(glib::bool_error!(
45                "Can't create DateTime: Hour out of range"
46            ));
47        }
48    }
49
50    if let Some(minute) = minute {
51        if minute < 0 || minute >= 60 {
52            return Err(glib::bool_error!(
53                "Can't create DateTime: Minute out of range"
54            ));
55        }
56    }
57
58    if let Some(seconds) = seconds {
59        if seconds < 0.0 || seconds >= 60.0 {
60            return Err(glib::bool_error!(
61                "Can't create DateTime: Seconds out of range"
62            ));
63        }
64    }
65
66    if let Some(tzoffset) = tzoffset {
67        if tzoffset < -12.0 || tzoffset > 12.0 {
68            return Err(glib::bool_error!(
69                "Can't create DateTime: Timezone offset out of range"
70            ));
71        }
72    }
73
74    // If day is provided, month also has to be provided
75    if day.is_some() && month.is_none() {
76        return Err(glib::bool_error!(
77            "Can't create DateTime: Need to provide month if providing day"
78        ));
79    }
80
81    // If hour is provided, day also has to be provided
82    if hour.is_some() && day.is_none() {
83        return Err(glib::bool_error!(
84            "Can't create DateTime: Need to provide day if providing hour"
85        ));
86    }
87
88    // If minutes are provided, hours also need to be provided and the other way around
89    if hour.is_none() && minute.is_some() {
90        return Err(glib::bool_error!(
91            "Can't create DateTime: Need to provide both hour and minute or neither"
92        ));
93    }
94
95    if minute.is_some() && hour.is_none() {
96        return Err(glib::bool_error!(
97            "Can't create DateTime: Need to provide both hour and minute or neither"
98        ));
99    }
100
101    // If seconds or tzoffset are provided then also hours and minutes must be provided
102    if (seconds.is_some() || tzoffset.is_some()) && (hour.is_none() || minute.is_none()) {
103        return Err(glib::bool_error!("Can't create DateTime: Need to provide hour and minute if providing seconds or timezone offset"));
104    }
105
106    Ok(())
107}
108
109impl DateTime {
110    #[doc(alias = "gst_date_time_new")]
111    pub fn new(
112        tzoffset: impl Into<Option<f32>>,
113        year: impl Into<i32>,
114        month: impl Into<Option<i32>>,
115        day: impl Into<Option<i32>>,
116        hour: impl Into<Option<i32>>,
117        minute: impl Into<Option<i32>>,
118        seconds: impl Into<Option<f64>>,
119    ) -> Result<DateTime, glib::BoolError> {
120        assert_initialized_main_thread!();
121
122        let tzoffset = tzoffset.into();
123        let year = year.into();
124        let month = month.into();
125        let day = day.into();
126        let hour = hour.into();
127        let minute = minute.into();
128        let seconds = seconds.into();
129
130        validate(tzoffset, year, month, day, hour, minute, seconds)?;
131
132        unsafe {
133            Option::<_>::from_glib_full(ffi::gst_date_time_new(
134                tzoffset.unwrap_or(0.0),
135                year,
136                month.unwrap_or(-1),
137                day.unwrap_or(-1),
138                hour.unwrap_or(-1),
139                minute.unwrap_or(-1),
140                seconds.unwrap_or(-1.0),
141            ))
142            .ok_or_else(|| glib::bool_error!("Can't create DateTime"))
143        }
144    }
145
146    #[doc(alias = "gst_date_time_new_local_time")]
147    pub fn from_local_time(
148        year: impl Into<i32>,
149        month: impl Into<Option<i32>>,
150        day: impl Into<Option<i32>>,
151        hour: impl Into<Option<i32>>,
152        minute: impl Into<Option<i32>>,
153        seconds: impl Into<Option<f64>>,
154    ) -> Result<DateTime, glib::BoolError> {
155        assert_initialized_main_thread!();
156
157        let year = year.into();
158        let month = month.into();
159        let day = day.into();
160        let hour = hour.into();
161        let minute = minute.into();
162        let seconds = seconds.into();
163
164        validate(None, year, month, day, hour, minute, seconds)?;
165
166        unsafe {
167            Option::<_>::from_glib_full(ffi::gst_date_time_new_local_time(
168                year,
169                month.unwrap_or(-1),
170                day.unwrap_or(-1),
171                hour.unwrap_or(-1),
172                minute.unwrap_or(-1),
173                seconds.unwrap_or(-1.0),
174            ))
175            .ok_or_else(|| glib::bool_error!("Can't create DateTime"))
176        }
177    }
178
179    #[doc(alias = "gst_date_time_new_y")]
180    pub fn from_y(year: i32) -> Result<DateTime, glib::BoolError> {
181        assert_initialized_main_thread!();
182
183        validate(None, year, None, None, None, None, None)?;
184
185        unsafe {
186            Option::<_>::from_glib_full(ffi::gst_date_time_new_y(year))
187                .ok_or_else(|| glib::bool_error!("Can't create DateTime"))
188        }
189    }
190
191    #[doc(alias = "gst_date_time_new_ym")]
192    pub fn from_ym(year: i32, month: i32) -> Result<DateTime, glib::BoolError> {
193        assert_initialized_main_thread!();
194
195        validate(None, year, Some(month), None, None, None, None)?;
196
197        unsafe {
198            Option::<_>::from_glib_full(ffi::gst_date_time_new_ym(year, month))
199                .ok_or_else(|| glib::bool_error!("Can't create DateTime"))
200        }
201    }
202
203    #[doc(alias = "gst_date_time_new_ymd")]
204    pub fn from_ymd(year: i32, month: i32, day: i32) -> Result<DateTime, glib::BoolError> {
205        assert_initialized_main_thread!();
206
207        validate(None, year, Some(month), Some(day), None, None, None)?;
208
209        unsafe {
210            Option::<_>::from_glib_full(ffi::gst_date_time_new_ymd(year, month, day))
211                .ok_or_else(|| glib::bool_error!("Can't create DateTime"))
212        }
213    }
214
215    #[doc(alias = "get_day")]
216    #[doc(alias = "gst_date_time_get_day")]
217    pub fn day(&self) -> Option<i32> {
218        if !self.has_day() {
219            return None;
220        }
221
222        unsafe { Some(ffi::gst_date_time_get_day(self.to_glib_none().0)) }
223    }
224
225    #[doc(alias = "get_hour")]
226    #[doc(alias = "gst_date_time_get_hour")]
227    pub fn hour(&self) -> Option<i32> {
228        if !self.has_time() {
229            return None;
230        }
231
232        unsafe { Some(ffi::gst_date_time_get_hour(self.to_glib_none().0)) }
233    }
234
235    #[doc(alias = "get_microsecond")]
236    #[doc(alias = "gst_date_time_get_microsecond")]
237    pub fn microsecond(&self) -> Option<i32> {
238        if !self.has_second() {
239            return None;
240        }
241
242        unsafe { Some(ffi::gst_date_time_get_microsecond(self.to_glib_none().0)) }
243    }
244
245    #[doc(alias = "get_minute")]
246    #[doc(alias = "gst_date_time_get_minute")]
247    pub fn minute(&self) -> Option<i32> {
248        if !self.has_time() {
249            return None;
250        }
251
252        unsafe { Some(ffi::gst_date_time_get_minute(self.to_glib_none().0)) }
253    }
254
255    #[doc(alias = "get_month")]
256    #[doc(alias = "gst_date_time_get_month")]
257    pub fn month(&self) -> Option<i32> {
258        if !self.has_month() {
259            return None;
260        }
261
262        unsafe { Some(ffi::gst_date_time_get_month(self.to_glib_none().0)) }
263    }
264
265    #[doc(alias = "get_second")]
266    #[doc(alias = "gst_date_time_get_second")]
267    pub fn second(&self) -> Option<i32> {
268        if !self.has_second() {
269            return None;
270        }
271
272        unsafe { Some(ffi::gst_date_time_get_second(self.to_glib_none().0)) }
273    }
274
275    #[doc(alias = "get_time_zone_offset")]
276    #[doc(alias = "gst_date_time_get_time_zone_offset")]
277    pub fn time_zone_offset(&self) -> Option<f32> {
278        if !self.has_time() {
279            return None;
280        }
281
282        unsafe {
283            Some(ffi::gst_date_time_get_time_zone_offset(
284                self.to_glib_none().0,
285            ))
286        }
287    }
288
289    pub fn to_utc(&self) -> Result<DateTime, glib::BoolError> {
290        if !self.has_time() {
291            // No time => no TZ offset
292            return Ok(self.clone());
293        }
294
295        assert!(self.has_year() && self.has_month() && self.has_day() && self.has_time());
296
297        // Can instantiate `gst::DateTime` without seconds using `gst::DateTime::new`
298        // with `-1f64` for the `second` argument
299        // however, the resulting instance can't be translated to `glib::DateTime`
300        if self.has_second() {
301            self.to_g_date_time()
302                .and_then(|d| d.to_utc())
303                .map(|d| d.into())
304        } else {
305            // It would be cheaper to build a `glib::DateTime` directly, unfortunetaly
306            // this would require using `glib::TimeZone::new_offset` which is feature-gated
307            // to `glib/v2_58`. So we need to build a new `gst::DateTime` with `0f64`
308            // and then discard seconds again
309            DateTime::new(
310                self.time_zone_offset(),
311                self.year(),
312                self.month(),
313                self.day(),
314                self.hour(),
315                self.minute(),
316                Some(0.0),
317            )
318            .and_then(|d| d.to_g_date_time())
319            .and_then(|d| d.to_utc())
320            .and_then(|d| {
321                DateTime::new(
322                    None, // UTC TZ offset
323                    d.year(),
324                    Some(d.month()),
325                    Some(d.day_of_month()),
326                    Some(d.hour()),
327                    Some(d.minute()),
328                    None, // No second
329                )
330            })
331        }
332    }
333}
334
335impl cmp::PartialOrd for DateTime {
336    // *NOTE 1:* When comparing a partially defined [`DateTime`](struct.DateTime.html) `d1`
337    // such as *"2019/8/20"* with a [`DateTime`](struct.DateTime.html) with a time part `d2`
338    // such as *"2019/8/20 21:10"*:
339    //
340    // - `d1` includes `d2`,
341    // - neither `d1` < `d2` nor `d1` > `d2`,
342    // - and `d1` != `d2`,
343    //
344    // so we can only return `None`.
345    //
346    // This is the reason why [`DateTime`](struct.DateTime.html) neither implements
347    // [`Ord`](https://doc.rust-lang.org/nightly/std/cmp/trait.Ord.html)
348    // nor [`Eq`](https://doc.rust-lang.org/nightly/std/cmp/trait.Eq.html).
349    //
350    // *NOTE 2:* When comparing a [`DateTime`](struct.DateTime.html) `d1` without a TZ offset
351    // such as *"2019/8/20"* with a [`DateTime`](struct.DateTime.html) `d2` with a TZ offset
352    // such as *"2019/8/20 21:10 +02:00"*, we can't tell in which TZ `d1` is expressed and which
353    // time should be considered for an offset, therefore the two [`DateTime`s](struct.DateTime.html)
354    // are compared in the same TZ.
355    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
356        #[inline]
357        #[allow(clippy::unnecessary_wraps)]
358        #[doc(alias = "get_cmp")]
359        fn cmp(delta: i32) -> Option<cmp::Ordering> {
360            skip_assert_initialized!();
361            Some(delta.cmp(&0))
362        }
363
364        if !(self.has_year() && other.has_year()) {
365            // Can't compare anything
366            return None;
367        }
368
369        // Normalize to UTC only if both members have time (see note 2).
370        let (self_norm, other_norm) = if self.has_time() && other.has_time() {
371            (self.to_utc().ok()?, other.to_utc().ok()?)
372        } else {
373            (self.clone(), other.clone())
374        };
375
376        let year_delta = self_norm.year() - other_norm.year();
377        if year_delta != 0 {
378            return cmp(year_delta);
379        }
380
381        // Same year
382
383        if !self.has_month() && !other.has_month() {
384            // Nothing left to compare
385            return cmp(year_delta);
386        }
387
388        if !(self.has_month() && other.has_month()) {
389            // One has month, the other doesn't => can't compare (note 1)
390            return None;
391        }
392
393        let month_delta = self_norm.month().unwrap() - other_norm.month().unwrap();
394        if month_delta != 0 {
395            return cmp(month_delta);
396        }
397
398        // Same year, same month
399
400        if !self.has_day() && !other.has_day() {
401            // Nothing left to compare
402            return Some(cmp::Ordering::Equal);
403        }
404
405        if !(self.has_day() && other.has_day()) {
406            // One has day, the other doesn't => can't compare (note 1)
407            return None;
408        }
409
410        let day_delta = self_norm.day().unwrap() - other_norm.day().unwrap();
411        if day_delta != 0 {
412            return cmp(day_delta);
413        }
414
415        // Same year, same month, same day
416
417        if !self.has_time() && !other.has_time() {
418            // Nothing left to compare
419            return Some(cmp::Ordering::Equal);
420        }
421
422        if !(self.has_time() && other.has_time()) {
423            // One has time, the other doesn't => can't compare (note 1)
424            return None;
425        }
426
427        let hour_delta = self_norm.hour().unwrap() - other_norm.hour().unwrap();
428        if hour_delta != 0 {
429            return cmp(hour_delta);
430        }
431
432        let minute_delta = self_norm.minute().unwrap() - other_norm.minute().unwrap();
433        if minute_delta != 0 {
434            return cmp(minute_delta);
435        }
436
437        // Same year, same month, same day, same time
438
439        if !self.has_second() && !other.has_second() {
440            // Nothing left to compare
441            return Some(cmp::Ordering::Equal);
442        }
443
444        if !(self.has_second() && other.has_second()) {
445            // One has second, the other doesn't => can't compare (note 1)
446            return None;
447        }
448        let second_delta = self_norm.second().unwrap() - other_norm.second().unwrap();
449        if second_delta != 0 {
450            return cmp(second_delta);
451        }
452
453        cmp(self_norm.microsecond().unwrap() - other_norm.microsecond().unwrap())
454    }
455}
456
457impl cmp::PartialEq for DateTime {
458    fn eq(&self, other: &Self) -> bool {
459        self.partial_cmp(other)
460            .map_or_else(|| false, |cmp| cmp == cmp::Ordering::Equal)
461    }
462}
463
464impl fmt::Debug for DateTime {
465    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
466        let mut debug_struct = f.debug_struct("DateTime");
467        if self.has_year() {
468            debug_struct.field("year", &self.year());
469        }
470        if self.has_month() {
471            debug_struct.field("month", &self.month());
472        }
473        if self.has_day() {
474            debug_struct.field("day", &self.day());
475        }
476        if self.has_time() {
477            debug_struct.field("hour", &self.hour());
478            debug_struct.field("minute", &self.minute());
479
480            if self.has_second() {
481                debug_struct.field("second", &self.second());
482                debug_struct.field("microsecond", &self.microsecond());
483            }
484
485            debug_struct.field("tz_offset", &self.time_zone_offset());
486        }
487
488        debug_struct.finish()
489    }
490}
491
492impl fmt::Display for DateTime {
493    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
494        f.write_str(
495            self.to_iso8601_string()
496                .unwrap_or_else(|_| "None".into())
497                .as_str(),
498        )
499    }
500}
501
502impl<'a> From<&'a glib::DateTime> for DateTime {
503    fn from(v: &'a glib::DateTime) -> DateTime {
504        skip_assert_initialized!();
505        DateTime::from_g_date_time(v.clone())
506    }
507}
508
509impl From<glib::DateTime> for DateTime {
510    fn from(v: glib::DateTime) -> DateTime {
511        skip_assert_initialized!();
512        DateTime::from_g_date_time(v)
513    }
514}
515
516impl<'a> TryFrom<&'a DateTime> for glib::DateTime {
517    type Error = glib::BoolError;
518
519    fn try_from(v: &'a DateTime) -> Result<glib::DateTime, glib::BoolError> {
520        skip_assert_initialized!();
521        v.to_g_date_time()
522    }
523}
524
525impl TryFrom<DateTime> for glib::DateTime {
526    type Error = glib::BoolError;
527
528    fn try_from(v: DateTime) -> Result<glib::DateTime, glib::BoolError> {
529        skip_assert_initialized!();
530        v.to_g_date_time()
531    }
532}
533
534#[cfg(test)]
535mod tests {
536    use super::*;
537
538    #[allow(clippy::cognitive_complexity)]
539    #[test]
540    fn test_to_utc() {
541        crate::init().unwrap();
542
543        // Hour offset
544        let utc_date_time = DateTime::new(2f32, 2019, 8, 20, 20, 9, 42.123_456f64)
545            .unwrap()
546            .to_utc()
547            .unwrap();
548        assert_eq!(utc_date_time.year(), 2019);
549        assert_eq!(utc_date_time.month().unwrap(), 8);
550        assert_eq!(utc_date_time.day().unwrap(), 20);
551        assert_eq!(utc_date_time.hour().unwrap(), 18);
552        assert_eq!(utc_date_time.minute().unwrap(), 9);
553        assert_eq!(utc_date_time.second().unwrap(), 42);
554        assert_eq!(utc_date_time.microsecond().unwrap(), 123_456);
555
556        // Year, month, day and hour offset
557        let utc_date_time = DateTime::new(2f32, 2019, 1, 1, 0, 0, 42.123_456f64)
558            .unwrap()
559            .to_utc()
560            .unwrap();
561        assert_eq!(utc_date_time.year(), 2018);
562        assert_eq!(utc_date_time.month().unwrap(), 12);
563        assert_eq!(utc_date_time.day().unwrap(), 31);
564        assert_eq!(utc_date_time.hour().unwrap(), 22);
565        assert_eq!(utc_date_time.minute().unwrap(), 0);
566        assert_eq!(utc_date_time.second().unwrap(), 42);
567        assert_eq!(utc_date_time.microsecond().unwrap(), 123_456);
568
569        // Date without an hour (which implies no TZ)
570        let utc_date_time = DateTime::from_ymd(2019, 1, 1).unwrap().to_utc().unwrap();
571        assert_eq!(utc_date_time.year(), 2019);
572        assert_eq!(utc_date_time.month().unwrap(), 1);
573        assert_eq!(utc_date_time.day().unwrap(), 1);
574        assert!(!utc_date_time.has_time());
575        assert!(!utc_date_time.has_second());
576
577        // Date without seconds
578        let utc_date_time = DateTime::new(2f32, 2018, 5, 28, 16, 6, None)
579            .unwrap()
580            .to_utc()
581            .unwrap();
582        assert_eq!(utc_date_time.year(), 2018);
583        assert_eq!(utc_date_time.month().unwrap(), 5);
584        assert_eq!(utc_date_time.day().unwrap(), 28);
585        assert_eq!(utc_date_time.hour().unwrap(), 14);
586        assert_eq!(utc_date_time.minute().unwrap(), 6);
587        assert!(!utc_date_time.has_second());
588    }
589
590    #[test]
591    fn test_partial_ord() {
592        crate::init().unwrap();
593
594        // Different years
595        assert!(
596            DateTime::new(2f32, 2020, 8, 20, 19, 43, 42.123_456f64).unwrap()
597                > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
598        );
599
600        // Different months (order intentionally reversed)
601        assert!(
602            DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
603                < DateTime::new(2f32, 2019, 9, 19, 19, 43, 42.123_456f64).unwrap()
604        );
605
606        // Different days
607        assert!(
608            DateTime::new(2f32, 2019, 8, 21, 19, 43, 42.123_456f64).unwrap()
609                > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
610        );
611
612        // Different hours
613        assert!(
614            DateTime::new(2f32, 2019, 8, 20, 19, 44, 42.123_456f64).unwrap()
615                > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
616        );
617
618        // Different minutes
619        assert!(
620            DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64).unwrap()
621                > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
622        );
623
624        // Different seconds
625        assert!(
626            DateTime::new(2f32, 2019, 8, 20, 19, 43, 43.123_456f64).unwrap()
627                > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
628        );
629
630        // Different micro-seconds
631        assert!(
632            DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_457f64).unwrap()
633                > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
634        );
635
636        // Different TZ offsets
637        assert!(
638            DateTime::new(1f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
639                > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
640        );
641
642        // TZ offset leading to year, month, day, hour offset
643        assert!(
644            DateTime::new(2f32, 2019, 1, 1, 0, 0, 0f64).unwrap()
645                < DateTime::new(1f32, 2018, 12, 31, 23, 59, 0f64).unwrap()
646        );
647
648        // Partially defined `DateTime`
649        assert!(
650            DateTime::from_ymd(2020, 8, 20).unwrap() > DateTime::from_ymd(2019, 8, 20).unwrap()
651        );
652        assert!(
653            DateTime::from_ymd(2019, 9, 20).unwrap() > DateTime::from_ymd(2019, 8, 20).unwrap()
654        );
655        assert!(
656            DateTime::from_ymd(2019, 8, 21).unwrap() > DateTime::from_ymd(2019, 8, 20).unwrap()
657        );
658
659        assert!(DateTime::from_ym(2020, 8).unwrap() > DateTime::from_ym(2019, 8).unwrap());
660        assert!(DateTime::from_ym(2019, 9).unwrap() > DateTime::from_ym(2019, 8).unwrap());
661        assert!(DateTime::from_ym(2019, 9).unwrap() > DateTime::from_ymd(2019, 8, 20).unwrap());
662
663        assert!(DateTime::from_y(2020).unwrap() > DateTime::from_y(2019).unwrap());
664        assert!(DateTime::from_ym(2020, 1).unwrap() > DateTime::from_y(2019).unwrap());
665
666        assert!(
667            DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64).unwrap()
668                < DateTime::from_ymd(2020, 8, 20).unwrap()
669        );
670
671        assert!(
672            DateTime::from_ymd(2020, 8, 20).unwrap()
673                > DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64).unwrap()
674        );
675
676        // Comparison occurs on the same TZ when the `DateTime` doesn't have time (note 2)
677        assert!(
678            DateTime::from_ymd(2020, 1, 1).unwrap()
679                > DateTime::new(-2f32, 2019, 12, 31, 23, 59, 0f64).unwrap()
680        );
681
682        // In the following cases, the partially defined `DateTime` is a range WRT
683        // the fully defined `DateTime` and this range includes the fully defined `DateTime`,
684        // but we can't tell if it's before or after and they are not equal (note 1)
685        assert!(DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64)
686            .unwrap()
687            .partial_cmp(&DateTime::from_ymd(2019, 8, 20).unwrap())
688            .is_none());
689
690        assert!(DateTime::from_ymd(2019, 8, 20)
691            .unwrap()
692            .partial_cmp(&DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64).unwrap())
693            .is_none());
694
695        assert!(DateTime::from_ym(2019, 1)
696            .unwrap()
697            .partial_cmp(&DateTime::from_y(2019).unwrap())
698            .is_none());
699    }
700
701    #[test]
702    fn test_eq() {
703        crate::init().unwrap();
704
705        assert_eq!(
706            DateTime::new(2f32, 2018, 5, 28, 16, 6, 42.123_456f64).unwrap(),
707            DateTime::new(2f32, 2018, 5, 28, 16, 6, 42.123_456f64).unwrap()
708        );
709
710        assert_eq!(
711            DateTime::new(2f32, 2018, 5, 28, 16, 6, 0f64).unwrap(),
712            DateTime::new(2f32, 2018, 5, 28, 16, 6, 0f64).unwrap()
713        );
714
715        assert_eq!(
716            DateTime::new(2f32, 2018, 5, 28, 16, 6, None).unwrap(),
717            DateTime::new(2f32, 2018, 5, 28, 16, 6, None).unwrap()
718        );
719
720        assert_eq!(
721            DateTime::from_ymd(2018, 5, 28).unwrap(),
722            DateTime::from_ymd(2018, 5, 28).unwrap()
723        );
724
725        // In the following cases, the partially defined `DateTime` is a range WRT
726        // the fully defined `DateTime` and this range includes the fully defined `DateTime`,
727        // but they are not equal (note 1)
728        assert_ne!(
729            DateTime::from_ymd(2018, 5, 28).unwrap(),
730            DateTime::new(2f32, 2018, 5, 28, 16, 6, None).unwrap()
731        );
732
733        assert_ne!(
734            DateTime::new(2f32, 2018, 5, 28, 16, 6, None).unwrap(),
735            DateTime::from_ym(2018, 5).unwrap()
736        );
737
738        assert_ne!(
739            DateTime::new(2f32, 2018, 5, 28, 16, 6, None).unwrap(),
740            DateTime::from_y(2018).unwrap()
741        );
742    }
743}