Skip to main content

deep_time/dt/
from_ccsds.rs

1use crate::{Dt, DtErr, Parts};
2
3impl Dt {
4    /// Parses a **CCSDS CCS (Calendar Segmented Time Code)** binary time code
5    /// into a [`Dt`].
6    ///
7    /// Implements **CCSDS 301.0-B-4 §3.4** (Level 1 only). Delegates to
8    /// [`Parts::from_ccsds_ccs`](../civil_parts/struct.Parts.html#method.from_ccsds_ccs).
9    ///
10    /// ### P-field (exactly 1 byte)
11    ///
12    /// - Bit 7:     Extension flag → must be `0` (extensions rejected)
13    /// - Bits 6-4:  Code ID = `101`
14    /// - Bit 3:     Calendar type (`0` = Month/Day, `1` = Day-of-Year)
15    /// - Bits 2-0:  Number of subsecond BCD octets (`0`–`6`)
16    ///
17    /// ### T-field (BCD, big-endian)
18    ///
19    /// - 2 bytes: Year (**0001–9999**)
20    /// - 2 bytes: Month+Day (01–12, 01–31) **or** Day-of-Year (001–366; high nibble of
21    ///   the first DOY octet is unused and should be zero)
22    /// - 3 bytes: Hour (00–23), Minute (00–59), Second (00–60 for leap seconds)
23    /// - 0–6 bytes: Fractional seconds (exactly 2 decimal digits per octet)
24    ///
25    /// ### Epoch / scale
26    ///
27    /// CCS is a **Gregorian calendar** code (year 1–9999), **UTC** civil time
28    /// (CCSDS 301.0-B-4 §3.4 / §3.5). It does **not** use the 1958 CDS/CUC epoch.
29    ///
30    /// The returned [`Dt`] is converted to TAI (`scale = TAI`) with `target = UTC`.
31    ///
32    /// ## See also
33    ///
34    /// - [`Dt::from_ccsds_cuc`](../struct.Dt.html#method.from_ccsds_cuc)
35    /// - [`Dt::from_ccsds_cds`](../struct.Dt.html#method.from_ccsds_cds)
36    #[inline(always)]
37    pub fn from_ccsds_ccs(input: &[u8]) -> Result<Dt, DtErr> {
38        Parts::from_ccsds_ccs(input)?.to_dt()
39    }
40
41    /// Parses a **CCSDS C (CUC – Unsegmented Time Code)** binary time code
42    /// directly into a [`Dt`].
43    ///
44    /// Implements **CCSDS 301.0-B-4 §3.2 (Level 1)**, including the extended
45    /// 2-byte P-field from Issue 4. Delegates to
46    /// [`Parts::from_ccsds_cuc`](../civil_parts/struct.Parts.html#method.from_ccsds_cuc).
47    ///
48    /// ## Supported formats (Level 1 only)
49    ///
50    /// - 1-byte or 2-byte P-field (further extension beyond 2 bytes is rejected).
51    /// - Code ID must be `001` (1958-01-01 **TAI** epoch).
52    /// - Coarse time: 1–7 octets total.
53    /// - Fractional time: 0–10 octets total.
54    ///
55    /// ## P-field decoding
56    ///
57    /// - **First octet (P1)**:
58    ///   - Bit 7:     Extension flag (1 = second P-field octet follows)
59    ///   - Bits 6-4:  Code ID (must be `001`)
60    ///   - Bits 3-2:  Coarse time octets minus 1 (0–3 → 1–4 octets)
61    ///   - Bits 1-0:  Fractional time octets (0–3)
62    ///
63    /// - **Second octet (P2, when extension flag is set)**:
64    ///   - Bit 7:     Further-extension flag (must be 0)
65    ///   - Bits 6-5:  Additional coarse octets (0–3)
66    ///   - Bits 4-2:  Additional fractional octets (0–7)
67    ///   - Bits 1-0:  Reserved (ignored)
68    ///
69    /// ## T-field
70    ///
71    /// - Coarse time: seconds since **1958-01-01 00:00:00 TAI**.
72    /// - Fine time: binary fraction of a second, decoded as
73    ///   `floor(value × 10¹⁸ / 2^(8·n_frac))` attoseconds (inverse of the encoder’s
74    ///   truncation).
75    ///
76    /// ## Returns
77    ///
78    /// A [`Dt`] with `scale = TAI` and `target = TAI`.
79    ///
80    /// ## Errors
81    ///
82    /// - [`DtErrKind::Empty`](../error/enum.DtErrKind.html#variant.Empty) if `input` is empty.
83    /// - [`DtErrKind::InvalidCodeId`](../error/enum.DtErrKind.html#variant.InvalidCodeId) if the Code ID is not `001`.
84    /// - [`DtErrKind::PFieldTooShort`](../error/enum.DtErrKind.html#variant.PFieldTooShort) if an extended P-field is indicated but missing.
85    /// - [`DtErrKind::UnsupportedItem`](../error/enum.DtErrKind.html#variant.UnsupportedItem) if a third P-field octet is indicated.
86    /// - [`DtErrKind::TFieldTooShort`](../error/enum.DtErrKind.html#variant.TFieldTooShort) if the T-field is shorter than declared.
87    ///
88    /// Errors from [`Parts::finish`](../civil_parts/struct.Parts.html#method.finish) and
89    /// [`Parts::to_dt`](../civil_parts/struct.Parts.html#method.to_dt) may also propagate.
90    #[inline(always)]
91    pub fn from_ccsds_cuc(input: &[u8]) -> Result<Dt, DtErr> {
92        Parts::from_ccsds_cuc(input)?.to_dt()
93    }
94
95    /// Parses a **CCSDS D (CDS – Day Segmented Time Code)** binary time code
96    /// directly into a [`Dt`].
97    ///
98    /// Implements **CCSDS 301.0-B-4 §3.3 (Level 1)**. Delegates to
99    /// [`Parts::from_ccsds_cds`](../civil_parts/struct.Parts.html#method.from_ccsds_cds).
100    ///
101    /// ## Supported formats (Level 1 only)
102    ///
103    /// - 1-byte or 2-byte P-field.
104    /// - Code ID must be `100` and the Epoch bit must be `0` (1958-01-01 UTC epoch).
105    /// - Day count: 2 or 3 bytes (calendar days since the epoch).
106    /// - Milliseconds of day: always 4 bytes (Annex A: 0–86_399_999, or up to
107    ///   86_400_999 during a positive leap second).
108    /// - Sub-millisecond field (bits 1-0 of P-field) — **cascaded unit counters**:
109    ///   - `00`: absent (millisecond resolution)
110    ///   - `01`: 2 bytes — microsecond-of-millisecond, range **0–999** (Annex A)
111    ///   - `10`: 4 bytes — picosecond-of-millisecond, range **0–999_999_999**
112    ///   - `11`: rejected (reserved)
113    ///
114    /// ## P-field bit layout (first octet)
115    ///
116    /// - Bit 7:     Extension flag (1 = second P-field octet follows)
117    /// - Bits 6-4:  Code ID (must be `100`)
118    /// - Bit 3:     Epoch (must be `0` for Level 1 / 1958 epoch)
119    /// - Bit 2:     Day count size (`0` = 2 bytes, `1` = 3 bytes)
120    /// - Bits 1-0:  Sub-millisecond code (see above)
121    ///
122    /// ## T-field
123    ///
124    /// - Day count is **calendar days** since **1958-01-01 00:00:00 UTC**.
125    /// - Milliseconds of day are always present (4 bytes).
126    /// - Sub-millisecond segments use unit scaling (µs × 10¹² attos, ps × 10⁶ attos),
127    ///   not binary fractions of the millisecond.
128    ///
129    /// ## Leap-second handling
130    ///
131    /// When `millis_of_day` is in `[86_400_000, 86_400_999]`, civil `second` is `60`
132    /// (positive leap second). Values above `86_400_999` are rejected.
133    ///
134    /// ## Returns
135    ///
136    /// A [`Dt`] with `scale = TAI` and `target = UTC` (via
137    /// [`Parts::to_dt`](../civil_parts/struct.Parts.html#method.to_dt)).
138    ///
139    /// ## Errors
140    ///
141    /// - [`DtErrKind::Empty`](../error/enum.DtErrKind.html#variant.Empty) if `input` is empty.
142    /// - [`DtErrKind::PFieldTooShort`](../error/enum.DtErrKind.html#variant.PFieldTooShort) if an extended P-field is indicated but missing.
143    /// - [`DtErrKind::InvalidCodeId`](../error/enum.DtErrKind.html#variant.InvalidCodeId) if the Code ID is not `100`.
144    /// - [`DtErrKind::ExpectedValue`](../error/enum.DtErrKind.html#variant.ExpectedValue) if the Epoch bit is set (non–Level-1).
145    /// - [`DtErrKind::InvalidSubmillisecond`](../error/enum.DtErrKind.html#variant.InvalidSubmillisecond) if the code is `0b11`, or µs/ps exceed their ranges.
146    /// - [`DtErrKind::TFieldTooShort`](../error/enum.DtErrKind.html#variant.TFieldTooShort) if the T-field is shorter than declared.
147    /// - [`DtErrKind::OutOfRange`](../error/enum.DtErrKind.html#variant.OutOfRange) if `millis_of_day > 86_400_999`.
148    ///
149    /// Errors from [`Parts::finish`](../civil_parts/struct.Parts.html#method.finish) and
150    /// [`Parts::to_dt`](../civil_parts/struct.Parts.html#method.to_dt) may also propagate.
151    #[inline(always)]
152    pub fn from_ccsds_cds(input: &[u8]) -> Result<Dt, DtErr> {
153        Parts::from_ccsds_cds(input)?.to_dt()
154    }
155
156    /// Auto-detects and parses a CCSDS binary time code (CUC, CDS, or CCS)
157    /// based on the Code ID in the first P-field byte, then returns a [`Dt`].
158    ///
159    /// Convenience wrapper around
160    /// [`Parts::from_ccsds_bin`](../civil_parts/struct.Parts.html#method.from_ccsds_bin).
161    ///
162    /// Dispatches as follows:
163    /// - Code ID `001` → [`Dt::from_ccsds_cuc`](../struct.Dt.html#method.from_ccsds_cuc)
164    /// - Code ID `100` → [`Dt::from_ccsds_cds`](../struct.Dt.html#method.from_ccsds_cds)
165    /// - Code ID `101` → [`Dt::from_ccsds_ccs`](../struct.Dt.html#method.from_ccsds_ccs)
166    ///
167    /// Prefer a specific `from_ccsds_*` when the format is known.
168    ///
169    /// ## Returns
170    ///
171    /// A [`Dt`] whose `scale` / `target` follow the decoded format after
172    /// [`Parts::to_dt`](../civil_parts/struct.Parts.html#method.to_dt):
173    /// - CUC (`001`): `scale = TAI`, `target = TAI`
174    /// - CDS (`100`): `scale = TAI`, `target = UTC`
175    /// - CCS (`101`): `scale = TAI`, `target = UTC`
176    ///
177    /// ## Errors
178    ///
179    /// - [`DtErrKind::Empty`](../error/enum.DtErrKind.html#variant.Empty) if `input` is empty.
180    /// - [`DtErrKind::InvalidCodeId`](../error/enum.DtErrKind.html#variant.InvalidCodeId) if the Code ID is not `001`, `100`, or `101`.
181    ///
182    /// Any error from the dispatched parser is also propagated.
183    #[inline(always)]
184    pub fn from_ccsds_bin(input: &[u8]) -> Result<Dt, DtErr> {
185        Parts::from_ccsds_bin(input)?.to_dt()
186    }
187}