Skip to main content

objc2_core_foundation/
date.rs

1use core::{cmp::Ordering, ptr};
2
3use crate::CFDate;
4
5impl CFDate {
6    /// Create a `CFDate` from a [`SystemTime`].
7    ///
8    /// Nanosecond precision may be lost.
9    ///
10    /// [`SystemTime`]: std::time::SystemTime
11    #[cfg(feature = "std")]
12    pub fn from_system_time(time: &std::time::SystemTime) -> crate::CFRetained<Self> {
13        let since_1970 = match time.duration_since(std::time::UNIX_EPOCH) {
14            Ok(duration) => duration.as_secs_f64(),
15            Err(err) => -err.duration().as_secs_f64(),
16        } as core::ffi::c_double;
17
18        let since_2001 = since_1970 - unsafe { crate::kCFAbsoluteTimeIntervalSince1970 };
19        Self::new(None, since_2001).expect("failed creating CFDate")
20    }
21
22    /// Try to construct a [`SystemTime`] from the `CFDate`.
23    ///
24    /// Nanosecond precision may be lost.
25    ///
26    /// Returns `None` if the `CFDate` is too large to fit inside
27    /// [`SystemTime`].
28    ///
29    /// [`SystemTime`]: std::time::SystemTime
30    #[cfg(feature = "std")]
31    #[allow(clippy::unnecessary_cast)]
32    pub fn to_system_time(&self) -> Option<std::time::SystemTime> {
33        let since_2001 = self.absolute_time();
34        let since_1970 = (since_2001 + unsafe { crate::kCFAbsoluteTimeIntervalSince1970 }) as f64;
35
36        std::time::UNIX_EPOCH.checked_add(std::time::Duration::try_from_secs_f64(since_1970).ok()?)
37    }
38}
39
40impl PartialOrd for CFDate {
41    #[inline]
42    #[doc(alias = "CFDateCompare")]
43    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
44        Some(self.cmp(other))
45    }
46}
47
48impl Ord for CFDate {
49    #[inline]
50    #[doc(alias = "CFDateCompare")]
51    fn cmp(&self, other: &Self) -> Ordering {
52        // Documented that one should pass NULL here.
53        let context = ptr::null_mut();
54        unsafe { self.compare(Some(other), context) }.into()
55    }
56}
57
58#[cfg(test)]
59mod test {
60    use core::ffi::c_double;
61    use std::time::{Duration, SystemTime};
62
63    use crate::CFAbsoluteTimeGetCurrent;
64
65    use super::*;
66
67    #[test]
68    fn cmp() {
69        let now = CFDate::new(None, CFAbsoluteTimeGetCurrent()).unwrap();
70        let past = CFDate::new(None, now.absolute_time() - 1.0).unwrap();
71        assert_eq!(now.cmp(&past), Ordering::Greater);
72        assert_eq!(now.cmp(&now), Ordering::Equal);
73        assert_eq!(past.cmp(&now), Ordering::Less);
74
75        assert_eq!(now, now);
76        assert_ne!(now, past);
77    }
78
79    #[test]
80    fn system_time_roundtrip() {
81        let date1 = CFDate::new(None, CFAbsoluteTimeGetCurrent()).unwrap();
82        let date2 = CFDate::from_system_time(&date1.to_system_time().unwrap());
83        let diff = date1.absolute_time() - date2.absolute_time();
84        assert!(diff <= 1.0); // Some precision is lost
85    }
86
87    #[test]
88    fn system_time_cmp() {
89        let std_now_first = SystemTime::now();
90        let cf_now_first = CFDate::new(None, CFAbsoluteTimeGetCurrent() + 1.0).unwrap();
91        let std_now_second = std_now_first.checked_add(Duration::from_secs(2)).unwrap();
92        let cf_now_second = CFDate::new(None, CFAbsoluteTimeGetCurrent() + 3.0).unwrap();
93
94        assert!(std_now_first <= std_now_second);
95        assert!(cf_now_first <= cf_now_second);
96
97        assert!(std_now_first <= cf_now_first.to_system_time().unwrap());
98        assert!(cf_now_first.to_system_time().unwrap() <= std_now_second);
99
100        assert!(cf_now_first <= CFDate::from_system_time(&std_now_second));
101        assert!(CFDate::from_system_time(&std_now_second) <= cf_now_second);
102    }
103
104    #[test]
105    fn system_time_from_odd() {
106        let time = SystemTime::UNIX_EPOCH
107            .checked_sub(Duration::from_secs(10))
108            .unwrap();
109        let _ = CFDate::from_system_time(&time);
110    }
111
112    #[test]
113    fn system_time_unrepresentable() {
114        let date = CFDate::new(None, c_double::MIN).unwrap();
115        assert_eq!(date.to_system_time(), None);
116
117        let date = CFDate::new(None, c_double::MAX).unwrap();
118        assert_eq!(date.to_system_time(), None);
119    }
120}