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