Skip to main content

zerodds_time_service/
uto.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! Universal Time Object (UTO) — OMG Time Service 1.1 §1.3.4.
4//!
5//! Spec-IDL:
6//! ```idl
7//! interface UTO {
8//!     readonly attribute TimeBase::TimeT       time;
9//!     readonly attribute TimeBase::InaccuracyT inaccuracy;
10//!     readonly attribute TimeBase::TdfT        tdf;
11//!     readonly attribute TimeBase::UtcT        utc_time;
12//!     UTO absolute_time();
13//!     TimeComparison compare_time(in ComparisonType, in UTO);
14//!     TIO time_to_interval(in UTO);
15//!     TIO interval();
16//! };
17//! ```
18//!
19//! Wir bilden das ohne CORBA-Interface ab — die "Operations" werden als
20//! Rust-Methods auf [`Uto`] realisiert.
21
22#[cfg(feature = "std")]
23use crate::time_base::current_time;
24use crate::time_base::{InaccuracyT, IntervalT, TdfT, TimeT, UtcT};
25use crate::tio::Tio;
26
27/// `CosTime::ComparisonType` (Spec §1.3.2.6).
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum ComparisonType {
30    /// IntervalC — vergleicht mit Beruecksichtigung der Inaccuracy-Range.
31    IntervalC,
32    /// MidC — vergleicht nur die Base-Times. Spec §1.3.2.6: "MidC
33    /// comparison can never return TCIndeterminate".
34    MidC,
35}
36
37/// `CosTime::TimeComparison` (Spec §1.3.2.7).
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum TimeComparison {
40    /// `TCEqualTo`.
41    EqualTo,
42    /// `TCLessThan`.
43    LessThan,
44    /// `TCGreaterThan`.
45    GreaterThan,
46    /// `TCIndeterminate` — wenn die Inaccuracy-Envelopes ueberlappen.
47    Indeterminate,
48}
49
50/// Universal Time Object — Spec §1.3.4. Immutable per Spec
51/// ("It is intended that UTOs are immutable.")
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub struct Uto {
54    inner: UtcT,
55}
56
57impl Uto {
58    /// Konstruiert ein UTO aus `UtcT`. Spec §2.1.2.2 (`uto_from_utc`).
59    #[must_use]
60    pub const fn from_utc(utc: UtcT) -> Self {
61        Self { inner: utc }
62    }
63
64    /// Konstruktor aus den Einzelfeldern (Spec §2.1.2.1
65    /// `new_universal_time`).
66    #[must_use]
67    pub const fn new(time: TimeT, inaccuracy: InaccuracyT, tdf: TdfT) -> Self {
68        Self {
69            inner: UtcT::new(time, inaccuracy, tdf),
70        }
71    }
72
73    /// Spec §1.3.4.1 — `time` Attribute.
74    #[must_use]
75    pub const fn time(self) -> TimeT {
76        self.inner.time
77    }
78
79    /// Spec §1.3.4.2 — `inaccuracy` Attribute.
80    #[must_use]
81    pub const fn inaccuracy(self) -> InaccuracyT {
82        self.inner.inaccuracy()
83    }
84
85    /// Spec §1.3.4.3 — `tdf` Attribute.
86    #[must_use]
87    pub const fn tdf(self) -> TdfT {
88        self.inner.tdf
89    }
90
91    /// Spec §1.3.4.4 — `utc_time` Attribute.
92    #[must_use]
93    pub const fn utc_time(self) -> UtcT {
94        self.inner
95    }
96
97    /// Spec §1.3.4.5 — `absolute_time()`. Wandelt einen relativen UTO
98    /// in einen absoluten um: `absolute = current + relative.time`.
99    /// Raises `DATA_CONVERSION` bei Overflow — wir liefern `None`.
100    #[cfg(feature = "std")]
101    #[must_use]
102    pub fn absolute_time(self) -> Option<Self> {
103        let now = current_time();
104        let absolute = now.checked_add(self.inner.time)?;
105        Some(Self::from_utc(UtcT::new(
106            absolute,
107            self.inner.inaccuracy(),
108            self.inner.tdf,
109        )))
110    }
111
112    /// Spec §1.3.4.6 — `compare_time(comparison_type, uto)`.
113    /// Self ist erster Parameter, `other` der zweite.
114    #[must_use]
115    pub fn compare_time(self, comparison_type: ComparisonType, other: Self) -> TimeComparison {
116        match comparison_type {
117            ComparisonType::MidC => {
118                // Spec §1.3.2.6: MidC vergleicht nur Base-Times.
119                match self.inner.time.cmp(&other.inner.time) {
120                    core::cmp::Ordering::Less => TimeComparison::LessThan,
121                    core::cmp::Ordering::Greater => TimeComparison::GreaterThan,
122                    core::cmp::Ordering::Equal => TimeComparison::EqualTo,
123                }
124            }
125            ComparisonType::IntervalC => {
126                // Spec §1.3.2.7: IntervalC mit Inaccuracy-Envelope.
127                // Equal nur wenn Time + beide Inaccuracies = 0
128                // (genaues Match) — sonst Indeterminate bei Overlap.
129                let self_lo = self.inner.time.saturating_sub(self.inaccuracy());
130                let self_hi = self.inner.time.saturating_add(self.inaccuracy());
131                let other_lo = other.inner.time.saturating_sub(other.inaccuracy());
132                let other_hi = other.inner.time.saturating_add(other.inaccuracy());
133
134                if self.inner.time == other.inner.time
135                    && self.inaccuracy() == 0
136                    && other.inaccuracy() == 0
137                {
138                    TimeComparison::EqualTo
139                } else if self_hi < other_lo {
140                    TimeComparison::LessThan
141                } else if self_lo > other_hi {
142                    TimeComparison::GreaterThan
143                } else {
144                    // Envelopes ueberlappen sich -> Indeterminate.
145                    TimeComparison::Indeterminate
146                }
147            }
148        }
149    }
150
151    /// Spec §1.3.4.7 — `time_to_interval(uto)`. Liefert TIO mit
152    /// Mid-Punkten der beiden UTOs als Bounds. Inaccuracies werden
153    /// nicht beruecksichtigt.
154    #[must_use]
155    pub fn time_to_interval(self, other: Self) -> Option<Tio> {
156        let (lo, hi) = if self.inner.time <= other.inner.time {
157            (self.inner.time, other.inner.time)
158        } else {
159            (other.inner.time, self.inner.time)
160        };
161        IntervalT::new(lo, hi).map(Tio::from_interval)
162    }
163
164    /// Spec §1.3.4.8 — `interval()`. Liefert die Inaccuracy-Envelope
165    /// als TIO: `[time-inaccuracy, time+inaccuracy]`.
166    #[must_use]
167    pub fn interval(self) -> Tio {
168        let lo = self.inner.time.saturating_sub(self.inaccuracy());
169        let hi = self.inner.time.saturating_add(self.inaccuracy());
170        Tio::from_interval(IntervalT::new(lo, hi).unwrap_or(IntervalT {
171            lower_bound: lo,
172            upper_bound: lo,
173        }))
174    }
175}
176
177#[cfg(test)]
178#[allow(clippy::expect_used, clippy::unwrap_used)]
179mod tests {
180    use super::*;
181
182    #[test]
183    fn attributes_return_constructor_values() {
184        // Spec §1.3.4.1-4.
185        let uto = Uto::new(1_000, 50, 60);
186        assert_eq!(uto.time(), 1_000);
187        assert_eq!(uto.inaccuracy(), 50);
188        assert_eq!(uto.tdf(), 60);
189        let utc = uto.utc_time();
190        assert_eq!(utc.time, 1_000);
191        assert_eq!(utc.tdf, 60);
192    }
193
194    #[test]
195    fn compare_time_midc_equal() {
196        let a = Uto::new(100, 50, 0);
197        let b = Uto::new(100, 200, 0);
198        assert_eq!(
199            a.compare_time(ComparisonType::MidC, b),
200            TimeComparison::EqualTo
201        );
202    }
203
204    #[test]
205    fn compare_time_midc_less_than() {
206        let a = Uto::new(100, 0, 0);
207        let b = Uto::new(200, 0, 0);
208        assert_eq!(
209            a.compare_time(ComparisonType::MidC, b),
210            TimeComparison::LessThan
211        );
212    }
213
214    #[test]
215    fn compare_time_midc_greater_than() {
216        let a = Uto::new(300, 0, 0);
217        let b = Uto::new(200, 0, 0);
218        assert_eq!(
219            a.compare_time(ComparisonType::MidC, b),
220            TimeComparison::GreaterThan
221        );
222    }
223
224    #[test]
225    fn compare_time_intervalc_equal_when_inaccuracy_zero_and_time_match() {
226        let a = Uto::new(100, 0, 0);
227        let b = Uto::new(100, 0, 0);
228        assert_eq!(
229            a.compare_time(ComparisonType::IntervalC, b),
230            TimeComparison::EqualTo
231        );
232    }
233
234    #[test]
235    fn compare_time_intervalc_indeterminate_on_envelope_overlap() {
236        // Spec §1.3.2.7: TCIndeterminate wenn Envelopes ueberlappen.
237        let a = Uto::new(100, 50, 0); // [50, 150]
238        let b = Uto::new(120, 50, 0); // [70, 170]
239        assert_eq!(
240            a.compare_time(ComparisonType::IntervalC, b),
241            TimeComparison::Indeterminate
242        );
243    }
244
245    #[test]
246    fn compare_time_intervalc_less_than_when_envelopes_disjoint() {
247        let a = Uto::new(100, 10, 0); // [90, 110]
248        let b = Uto::new(200, 10, 0); // [190, 210]
249        assert_eq!(
250            a.compare_time(ComparisonType::IntervalC, b),
251            TimeComparison::LessThan
252        );
253    }
254
255    #[test]
256    fn compare_time_intervalc_greater_than_when_envelopes_disjoint() {
257        let a = Uto::new(300, 10, 0);
258        let b = Uto::new(200, 10, 0);
259        assert_eq!(
260            a.compare_time(ComparisonType::IntervalC, b),
261            TimeComparison::GreaterThan
262        );
263    }
264
265    #[test]
266    fn time_to_interval_uses_midpoints() {
267        // Spec §1.3.4.7: Interval zwischen den Midpoints, Inaccuracies
268        // werden nicht beruecksichtigt.
269        let a = Uto::new(100, 9999, 0);
270        let b = Uto::new(200, 9999, 0);
271        let tio = a.time_to_interval(b).expect("ok");
272        assert_eq!(tio.time_interval().lower_bound, 100);
273        assert_eq!(tio.time_interval().upper_bound, 200);
274    }
275
276    #[test]
277    fn interval_returns_inaccuracy_envelope() {
278        // Spec §1.3.4.8: TIO.upper = time + inaccuracy, TIO.lower = time - inaccuracy.
279        let uto = Uto::new(1_000, 50, 0);
280        let tio = uto.interval();
281        assert_eq!(tio.time_interval().lower_bound, 950);
282        assert_eq!(tio.time_interval().upper_bound, 1_050);
283    }
284
285    #[cfg(feature = "std")]
286    #[test]
287    fn absolute_time_adds_current_to_relative() {
288        let relative = Uto::new(1_000, 0, 0);
289        let absolute = relative.absolute_time().expect("ok");
290        // Absolute > current_time (sollte zumindest > base time sein).
291        assert!(absolute.time() > 1_000);
292    }
293}