Skip to main content

zerodds_dcps/
time.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! `Time_t` und `Duration_t` (DDS-DCPS 1.4 §2.3.3 IDL-PSM).
4//!
5//! Spec definiert:
6//! * `struct Time_t { long sec; unsigned long nanosec; }`
7//! * `struct Duration_t { long sec; unsigned long nanosec; }`
8//! * Reservierte Sentinels: `TIME_INVALID`, `TIME_INFINITE`,
9//!   `DURATION_ZERO`, `DURATION_INFINITE`.
10//!
11//! Wire-Form ist 8 Byte (4 sec + 4 nanosec). Wir kapseln das hier mit
12//! einer Rust-API, die mit `core::time::Duration` und `std::time::SystemTime`
13//! koexistiert. Der RTPS-Wire-Layer hat eigenes `Duration` in
14//! `zerodds_rtps::participant_data::Duration` — wir konvertieren bei Bedarf.
15
16extern crate alloc;
17
18/// `Time_t` — Wall-Clock-Timestamp seit Unix-Epoch.
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
20pub struct Time {
21    /// Seconds seit 1970-01-01 UTC.
22    pub sec: i32,
23    /// Nanoseconds-Anteil [0, 999_999_999].
24    pub nanosec: u32,
25}
26
27impl Time {
28    /// `TIME_ZERO` — Spec-Konstante (DDSI-RTPS §8.3.2 + DCPS §2.3.3).
29    pub const ZERO: Self = Self { sec: 0, nanosec: 0 };
30
31    /// Reserviertes Sentinel "ungueltige Zeit" (Spec-Konvention:
32    /// sec=-1, nanosec=0xFFFF_FFFF).
33    pub const INVALID: Self = Self {
34        sec: -1,
35        nanosec: 0xFFFF_FFFF,
36    };
37
38    /// Reserviertes Sentinel "unendlich in der Zukunft".
39    pub const INFINITE: Self = Self {
40        sec: 0x7FFF_FFFF,
41        nanosec: 0xFFFF_FFFE,
42    };
43
44    /// `true` wenn der Wert dem [`Self::ZERO`]-Sentinel entspricht.
45    #[must_use]
46    pub const fn is_zero(&self) -> bool {
47        self.sec == 0 && self.nanosec == 0
48    }
49
50    /// Konstruktor.
51    #[must_use]
52    pub const fn new(sec: i32, nanosec: u32) -> Self {
53        Self { sec, nanosec }
54    }
55
56    /// `true` wenn der Wert dem [`Self::INVALID`]-Sentinel entspricht.
57    #[must_use]
58    pub const fn is_invalid(&self) -> bool {
59        self.sec == -1 && self.nanosec == 0xFFFF_FFFF
60    }
61
62    /// `true` wenn der Wert dem [`Self::INFINITE`]-Sentinel entspricht.
63    #[must_use]
64    pub const fn is_infinite(&self) -> bool {
65        self.sec == 0x7FFF_FFFF && self.nanosec == 0xFFFF_FFFE
66    }
67
68    /// Spec §7.5.6.1 — `seconds`-Accessor (DDS-PSM-Cxx Time::seconds()).
69    #[must_use]
70    pub const fn seconds(&self) -> i32 {
71        self.sec
72    }
73
74    /// Spec §7.5.6.1 — `nanoseconds`-Accessor.
75    #[must_use]
76    pub const fn nanoseconds(&self) -> u32 {
77        self.nanosec
78    }
79
80    /// Spec §7.5.6.2 — Time + Duration (Sekunden-Increment).
81    #[must_use]
82    pub fn add_duration(self, d: Duration) -> Self {
83        let total_ns = u64::from(self.nanosec) + u64::from(d.nanosec);
84        let extra_sec = (total_ns / 1_000_000_000) as i32;
85        let nanosec = (total_ns % 1_000_000_000) as u32;
86        Self {
87            sec: self.sec.saturating_add(d.sec).saturating_add(extra_sec),
88            nanosec,
89        }
90    }
91
92    /// Spec §7.5.6.3 — Time aus Millisekunden-Integer.
93    #[must_use]
94    pub const fn from_millis(ms: i64) -> Self {
95        let sec = (ms / 1000) as i32;
96        let nanosec = ((ms % 1000) * 1_000_000) as u32;
97        Self { sec, nanosec }
98    }
99
100    /// Spec §7.5.6.3 — Time als Millisekunden-Integer.
101    #[must_use]
102    pub const fn as_millis(&self) -> i64 {
103        (self.sec as i64) * 1000 + (self.nanosec as i64) / 1_000_000
104    }
105}
106
107/// `Duration_t` — relativer Zeitraum (kann negativ sein bei
108/// `sec < 0`).
109#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
110pub struct Duration {
111    /// Seconds.
112    pub sec: i32,
113    /// Nanoseconds-Anteil [0, 999_999_999].
114    pub nanosec: u32,
115}
116
117impl Duration {
118    /// `DURATION_ZERO` (Spec): 0 Sekunden.
119    pub const ZERO: Self = Self { sec: 0, nanosec: 0 };
120
121    /// `DURATION_INFINITE` (Spec): symbolisches Maximum.
122    pub const INFINITE: Self = Self {
123        sec: 0x7FFF_FFFF,
124        nanosec: 0xFFFF_FFFF,
125    };
126
127    /// Konstruktor.
128    #[must_use]
129    pub const fn new(sec: i32, nanosec: u32) -> Self {
130        Self { sec, nanosec }
131    }
132
133    /// `true` wenn der Wert [`Self::INFINITE`] ist.
134    #[must_use]
135    pub const fn is_infinite(&self) -> bool {
136        self.sec == 0x7FFF_FFFF && self.nanosec == 0xFFFF_FFFF
137    }
138
139    /// `true` wenn der Wert [`Self::ZERO`] ist.
140    #[must_use]
141    pub const fn is_zero(&self) -> bool {
142        self.sec == 0 && self.nanosec == 0
143    }
144
145    /// Konvertierung in [`core::time::Duration`]. `INFINITE` wird auf
146    /// `Duration::MAX` gemapped; negative Sekunden auf `ZERO`
147    /// (core::time::Duration ist unsigned).
148    #[must_use]
149    pub fn to_core(self) -> core::time::Duration {
150        if self.is_infinite() {
151            core::time::Duration::MAX
152        } else if self.sec < 0 {
153            core::time::Duration::ZERO
154        } else {
155            core::time::Duration::new(self.sec as u64, self.nanosec)
156        }
157    }
158
159    /// Konvertierung von [`core::time::Duration`] (lossy, kappt
160    /// Sekunden auf `i32::MAX`).
161    #[must_use]
162    pub fn from_core(d: core::time::Duration) -> Self {
163        let sec = i32::try_from(d.as_secs()).unwrap_or(i32::MAX);
164        Self {
165            sec,
166            nanosec: d.subsec_nanos(),
167        }
168    }
169
170    /// Spec §7.5.6.1 — `seconds`-Accessor (DDS-PSM-Cxx Duration::seconds()).
171    #[must_use]
172    pub const fn seconds(&self) -> i32 {
173        self.sec
174    }
175
176    /// Spec §7.5.6.1 — `nanoseconds`-Accessor.
177    #[must_use]
178    pub const fn nanoseconds(&self) -> u32 {
179        self.nanosec
180    }
181
182    /// Spec §7.5.6.4 — Duration + Duration (Increment).
183    #[must_use]
184    pub fn add_duration(self, d: Duration) -> Self {
185        let total_ns = u64::from(self.nanosec) + u64::from(d.nanosec);
186        let extra_sec = (total_ns / 1_000_000_000) as i32;
187        let nanosec = (total_ns % 1_000_000_000) as u32;
188        Self {
189            sec: self.sec.saturating_add(d.sec).saturating_add(extra_sec),
190            nanosec,
191        }
192    }
193
194    /// Spec §7.5.6.5 — Duration aus Millisekunden-Integer.
195    #[must_use]
196    pub const fn from_millis(ms: i64) -> Self {
197        let sec = (ms / 1000) as i32;
198        let nanosec = ((ms % 1000) * 1_000_000) as u32;
199        Self { sec, nanosec }
200    }
201
202    /// Spec §7.5.6.5 — Duration als Millisekunden-Integer.
203    #[must_use]
204    pub const fn as_millis(&self) -> i64 {
205        (self.sec as i64) * 1000 + (self.nanosec as i64) / 1_000_000
206    }
207}
208
209/// Aktuelle Wall-Clock-Zeit als [`Time`]. Auf `std`-Plattformen via
210/// `std::time::SystemTime`. Auf `no_std` (kein std-Feature) liefert
211/// die Funktion `Time::INVALID` — Caller muss sich um die Quelle
212/// selbst kuemmern (z.B. monotonic Hardware-Clock).
213///
214/// Spec-Referenz: DDS-DCPS 1.4 §2.2.2.2.1.32 `get_current_time`.
215#[must_use]
216#[cfg(feature = "std")]
217pub fn get_current_time() -> Time {
218    use std::time::{SystemTime, UNIX_EPOCH};
219    SystemTime::now()
220        .duration_since(UNIX_EPOCH)
221        .map(|d| Time {
222            sec: i32::try_from(d.as_secs()).unwrap_or(i32::MAX),
223            nanosec: d.subsec_nanos(),
224        })
225        .unwrap_or(Time::INVALID)
226}
227
228/// `no_std`-Stub fuer [`get_current_time`].
229#[must_use]
230#[cfg(not(feature = "std"))]
231pub fn get_current_time() -> Time {
232    Time::INVALID
233}
234
235#[cfg(test)]
236#[allow(clippy::expect_used)]
237mod tests {
238    use super::*;
239
240    #[test]
241    fn time_zero_sentinel() {
242        assert!(Time::ZERO.is_zero());
243        assert!(!Time::ZERO.is_invalid());
244        assert!(!Time::ZERO.is_infinite());
245        assert_eq!(Time::ZERO, Time::default());
246    }
247
248    #[test]
249    fn time_invalid_sentinel() {
250        assert!(Time::INVALID.is_invalid());
251        assert!(!Time::INVALID.is_infinite());
252        let zero = Time::default();
253        assert!(!zero.is_invalid());
254    }
255
256    #[test]
257    fn time_infinite_sentinel() {
258        assert!(Time::INFINITE.is_infinite());
259        assert!(!Time::INFINITE.is_invalid());
260    }
261
262    #[test]
263    fn duration_zero_and_infinite_sentinels() {
264        assert!(Duration::ZERO.is_zero());
265        assert!(Duration::INFINITE.is_infinite());
266        assert!(!Duration::INFINITE.is_zero());
267    }
268
269    #[test]
270    fn duration_to_core_maps_infinite_to_max() {
271        assert_eq!(Duration::INFINITE.to_core(), core::time::Duration::MAX);
272    }
273
274    #[test]
275    fn duration_to_core_preserves_value() {
276        let d = Duration::new(5, 500_000_000);
277        let c = d.to_core();
278        assert_eq!(c.as_secs(), 5);
279        assert_eq!(c.subsec_nanos(), 500_000_000);
280    }
281
282    #[test]
283    fn duration_from_core_roundtrip() {
284        let c = core::time::Duration::new(42, 123_456_789);
285        let d = Duration::from_core(c);
286        assert_eq!(d.sec, 42);
287        assert_eq!(d.nanosec, 123_456_789);
288        assert_eq!(d.to_core(), c);
289    }
290
291    #[test]
292    fn duration_negative_sec_maps_to_zero() {
293        let d = Duration::new(-1, 0);
294        assert_eq!(d.to_core(), core::time::Duration::ZERO);
295    }
296
297    #[cfg(feature = "std")]
298    #[test]
299    fn get_current_time_is_recent() {
300        let t = get_current_time();
301        assert!(!t.is_invalid());
302        // sec sollte > 1_700_000_000 sein (Nov 2023+).
303        assert!(t.sec > 1_700_000_000);
304    }
305
306    // ========================================================================
307    // Spec §7.5.6 (DDS-PSM-Cxx 1.0) Iron-Rule-Tracker
308    // ========================================================================
309
310    #[test]
311    fn time_seconds_and_nanoseconds_accessors() {
312        // Spec §7.5.6.1: Time::seconds() / nanoseconds().
313        let t = Time::new(7, 250_000_000);
314        assert_eq!(t.seconds(), 7);
315        assert_eq!(t.nanoseconds(), 250_000_000);
316    }
317
318    #[test]
319    fn duration_seconds_and_nanoseconds_accessors() {
320        // Spec §7.5.6.1: Duration::seconds() / nanoseconds().
321        let d = Duration::new(3, 100_000_000);
322        assert_eq!(d.seconds(), 3);
323        assert_eq!(d.nanoseconds(), 100_000_000);
324    }
325
326    #[test]
327    fn time_add_duration_carries_seconds() {
328        // Spec §7.5.6.2: Time-Increment ueber Duration mit
329        // Nanosekunden-Carry.
330        let t = Time::new(10, 800_000_000);
331        let inc = t.add_duration(Duration::new(2, 500_000_000));
332        assert_eq!(inc.seconds(), 13);
333        assert_eq!(inc.nanoseconds(), 300_000_000);
334    }
335
336    #[test]
337    fn time_from_and_as_millis_roundtrip() {
338        // Spec §7.5.6.3: Conversion zu/von Millisekunden.
339        let t = Time::from_millis(12_345);
340        assert_eq!(t.seconds(), 12);
341        assert_eq!(t.nanoseconds(), 345_000_000);
342        assert_eq!(t.as_millis(), 12_345);
343    }
344
345    #[test]
346    fn duration_add_duration_carries_seconds() {
347        // Spec §7.5.6.4: Duration + Duration mit Nanosekunden-Carry.
348        let d = Duration::new(5, 700_000_000);
349        let inc = d.add_duration(Duration::new(1, 600_000_000));
350        assert_eq!(inc.seconds(), 7);
351        assert_eq!(inc.nanoseconds(), 300_000_000);
352    }
353
354    #[test]
355    fn duration_from_and_as_millis_roundtrip() {
356        // Spec §7.5.6.5: Conversion zu/von Millisekunden.
357        let d = Duration::from_millis(2_500);
358        assert_eq!(d.seconds(), 2);
359        assert_eq!(d.nanoseconds(), 500_000_000);
360        assert_eq!(d.as_millis(), 2_500);
361    }
362}