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