Skip to main content

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