autd3_core/ethercat/
dc_sys_time.rs

1#[cfg(feature = "time")]
2#[derive(Debug, PartialEq, Clone)]
3pub struct InvalidDateTime;
4
5#[cfg(feature = "time")]
6impl core::fmt::Display for InvalidDateTime {
7    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
8        write!(f, "Invalid date time")
9    }
10}
11
12#[cfg(feature = "time")]
13impl std::error::Error for InvalidDateTime {}
14
15#[cfg(feature = "time")]
16use super::ECAT_DC_SYS_TIME_BASE;
17
18/// The system time of the Distributed Clock
19///
20/// The system time is the time expressed in 1ns units with 2000-01-01 0:00:00 UTC as the reference.
21/// It is expressed as a 64-bit unsigned integer and can represent about 584 years of time.
22/// See [EtherCAT Distributed Clock](https://infosys.beckhoff.com/english.php?content=../content/1033/ethercatsystem/2469118347.html) for more information.
23#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
24#[repr(C)]
25pub struct DcSysTime {
26    dc_sys_time: u64,
27}
28
29impl DcSysTime {
30    /// The zero point of the DcSysTime (2000-01-01 0:00:00 UTC)
31    pub const ZERO: Self = Self { dc_sys_time: 0 };
32
33    /// Creates a new instance with the given system time in nanoseconds since 2000-01-01 0:00:00 UTC
34    pub const fn new(dc_sys_time: u64) -> Self {
35        Self { dc_sys_time }
36    }
37
38    /// Returns the system time in nanoseconds
39    #[must_use]
40    pub const fn sys_time(&self) -> u64 {
41        self.dc_sys_time
42    }
43
44    /// Converts the system time to the UTC time
45    #[must_use]
46    #[cfg(feature = "time")]
47    pub fn to_utc(&self) -> time::OffsetDateTime {
48        ECAT_DC_SYS_TIME_BASE + core::time::Duration::from_nanos(self.dc_sys_time)
49    }
50
51    /// Creates a new instance from the UTC time
52    #[cfg(feature = "time")]
53    pub fn from_utc(utc: time::OffsetDateTime) -> Result<Self, InvalidDateTime> {
54        Ok(Self {
55            dc_sys_time: u64::try_from((utc - ECAT_DC_SYS_TIME_BASE).whole_nanoseconds())
56                .map_err(|_| InvalidDateTime)?,
57        })
58    }
59
60    /// Returns the system time of now
61    #[must_use]
62    #[cfg(feature = "time")]
63    pub fn now() -> Self {
64        Self::from_utc(time::OffsetDateTime::now_utc()).expect("system time is invalid")
65    }
66}
67
68impl core::ops::Add<core::time::Duration> for DcSysTime {
69    type Output = Self;
70
71    fn add(self, rhs: core::time::Duration) -> Self::Output {
72        Self {
73            dc_sys_time: self.dc_sys_time + rhs.as_nanos() as u64,
74        }
75    }
76}
77
78impl core::ops::AddAssign<core::time::Duration> for DcSysTime {
79    fn add_assign(&mut self, rhs: core::time::Duration) {
80        self.dc_sys_time += rhs.as_nanos() as u64;
81    }
82}
83
84impl core::ops::Sub<core::time::Duration> for DcSysTime {
85    type Output = Self;
86
87    fn sub(self, rhs: core::time::Duration) -> Self::Output {
88        Self {
89            dc_sys_time: self.dc_sys_time - rhs.as_nanos() as u64,
90        }
91    }
92}
93
94impl core::ops::SubAssign<core::time::Duration> for DcSysTime {
95    fn sub_assign(&mut self, rhs: core::time::Duration) {
96        self.dc_sys_time -= rhs.as_nanos() as u64;
97    }
98}
99
100impl core::ops::Sub for DcSysTime {
101    type Output = core::time::Duration;
102
103    fn sub(self, rhs: Self) -> Self::Output {
104        core::time::Duration::from_nanos(self.dc_sys_time - rhs.dc_sys_time)
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[test]
113    fn new() {
114        let t = DcSysTime::new(123456789);
115        assert_eq!(123456789, t.sys_time());
116    }
117
118    #[cfg(feature = "time")]
119    #[test]
120    fn err_display() {
121        let err = InvalidDateTime;
122        assert_eq!("Invalid date time", format!("{}", err));
123    }
124
125    #[cfg(feature = "time")]
126    #[test]
127    fn now_dc_sys_time() {
128        let t = DcSysTime::now();
129        assert!(t.sys_time() > 0);
130    }
131
132    #[cfg(feature = "time")]
133    #[rstest::rstest]
134    #[case(Ok(DcSysTime { dc_sys_time: 0 }), time::macros::datetime!(2000-01-01 0:0:0 UTC))]
135    #[case(Ok(DcSysTime { dc_sys_time: 1000000000 }), time::macros::datetime!(2000-01-01 0:0:1 UTC))]
136    #[case(Ok(DcSysTime { dc_sys_time: 31622400000000000 }), time::macros::datetime!(2001-01-01 0:0:0 UTC))]
137    #[case(Err(InvalidDateTime), time::macros::datetime!(1999-01-01 0:0:1 UTC))]
138    #[case(Err(InvalidDateTime), time::macros::datetime!(9999-01-01 0:0:1 UTC))]
139    fn from_utc(
140        #[case] expect: Result<DcSysTime, InvalidDateTime>,
141        #[case] utc: time::OffsetDateTime,
142    ) {
143        assert_eq!(expect, DcSysTime::from_utc(utc));
144    }
145
146    #[cfg(feature = "time")]
147    #[rstest::rstest]
148    #[case(time::macros::datetime!(2000-01-01 0:0:1 UTC))]
149    #[case(time::macros::datetime!(2001-01-01 0:0:0 UTC))]
150    fn to_utc(#[case] utc: time::OffsetDateTime) {
151        assert_eq!(utc, DcSysTime::from_utc(utc).unwrap().to_utc());
152    }
153
154    #[cfg(feature = "time")]
155    #[test]
156    fn add_sub() {
157        let t = DcSysTime::ZERO;
158
159        let mut t = t + core::time::Duration::from_secs(1);
160        assert_eq!(1000000000, t.sys_time());
161        t += core::time::Duration::from_secs(2);
162        assert_eq!(3000000000, t.sys_time());
163
164        let mut t = t - core::time::Duration::from_secs(1);
165        assert_eq!(2000000000, t.sys_time());
166        t -= core::time::Duration::from_secs(2);
167        assert_eq!(0, t.sys_time());
168
169        assert_eq!(
170            core::time::Duration::from_secs(2),
171            (DcSysTime::ZERO + core::time::Duration::from_secs(3))
172                - (DcSysTime::ZERO + core::time::Duration::from_secs(1))
173        );
174    }
175}