Skip to main content

deep_time/dt/
from_ccsds.rs

1use crate::{Dt, DtErr, TimeParts};
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).
8    ///
9    /// ### P-field (exactly 1 byte)
10    ///
11    /// - Bit 7:     Extension flag → must be `0` (we reject extensions)
12    /// - Bits 6-4:  Code ID = `101`
13    /// - Bit 3:     Calendar type (`0` = Month/Day, `1` = Day-of-Year)
14    /// - Bits 2-0:  Number of subsecond BCD octets (`0`–`6`)
15    ///
16    /// ### T-field (BCD, big-endian)
17    ///
18    /// - 2 bytes: Year (0001–9999)
19    /// - 2 bytes: Month+Day (01-12,01-31) **or** Day-of-Year (001–366)
20    /// - 3 bytes: Hour (00-23), Minute (00-59), Second (00-60)
21    /// - 0–6 bytes: Fractional seconds (exactly 2 decimal digits per byte)
22    ///
23    /// Epoch: 1958-01-01 00:00:00 **UTC** (identical to CDS).
24    ///
25    /// ## See also
26    ///
27    /// - [`Dt::from_ccsds_cuc`](../struct.Dt.html#method.from_ccsds_cuc)
28    /// - [`Dt::from_ccsds_cds`](../struct.Dt.html#method.from_ccsds_cds)
29    #[inline(always)]
30    pub fn from_ccsds_ccs(input: &[u8]) -> Result<Dt, DtErr> {
31        TimeParts::from_ccsds_ccs(input)?.to_dt()
32    }
33
34    /// Parses a **CCSDS C (CUC – Unsegmented Time Code)** binary time code
35    /// directly into a [`Dt`].
36    ///
37    /// Implements **CCSDS 301.0-B-4 §3.2 (Level 1)**, including full support
38    /// for the extended 2-byte P-field defined in Issue 4.
39    ///
40    /// ## Supported formats (Level 1 only)
41    ///
42    /// - 1-byte or 2-byte P-field (further extension beyond 2 bytes is rejected).
43    /// - Code ID must be `001` (1958-01-01 TAI epoch).
44    /// - Coarse time: 1–7 octets total.
45    /// - Fractional time: 0–10 octets total.
46    ///
47    /// ## P-field decoding
48    ///
49    /// - **First octet (P1)**:
50    ///   - Bit 7:     Extension flag (1 = second P-field octet follows)
51    ///   - Bits 6-4:  Code ID (must be `001`)
52    ///   - Bits 3-2:  Coarse time octets minus 1 (0–3 → 1–4 octets)
53    ///   - Bits 1-0:  Fractional time octets (0–3)
54    ///
55    /// - **Second octet (P2, when extension flag is set)**:
56    ///   - Bit 7:     Further-extension flag (must be 0; 3+-byte P-fields are rejected)
57    ///   - Bits 6-5:  Additional coarse octets (0–3)
58    ///   - Bits 4-2:  Additional fractional octets (0–7)
59    ///   - Bits 1-0:  Reserved (ignored)
60    ///
61    /// ## T-field
62    ///
63    /// - Coarse time is interpreted as seconds since **1958-01-01 00:00:00 TAI**.
64    /// - Fractional time is converted to attoseconds using exact integer arithmetic
65    ///   (`value × 10¹⁸ / 2^(8·n_frac)`).
66    ///
67    /// ## Returns
68    ///
69    /// A [`Dt`] with both `scale` and `target` set to [`Scale::TAI`].
70    ///
71    /// ## Errors
72    ///
73    /// - [`DtErrKind::Incomplete`] if `input` is empty.
74    /// - [`DtErrKind::InvalidItem`] if the Code ID is not `001`.
75    /// - [`DtErrKind::InvalidInput`] if the input is too short to contain the declared
76    ///   extended P-field, or if the "further extension" flag (bit 7 of the second
77    ///   P-field octet) is set.
78    /// - [`DtErrKind::InvalidSyntax`] if the declared coarse + fractional field lengths
79    ///   make the T-field longer than the remaining input bytes.
80    ///
81    /// Errors from [`TimeParts::finish`] and [`TimeParts::to_dt`] may also propagate.
82    #[inline(always)]
83    pub fn from_ccsds_cuc(input: &[u8]) -> Result<Dt, DtErr> {
84        TimeParts::from_ccsds_cuc(input)?.to_dt()
85    }
86
87    /// Parses a **CCSDS D (CDS – Day Segmented Time Code)** binary time code
88    /// directly into a [`Dt`].
89    ///
90    /// Implements **CCSDS 301.0-B-4 §3.3 (Level 1)**.
91    ///
92    /// ## Supported formats (Level 1 only)
93    ///
94    /// - 1-byte or 2-byte P-field.
95    /// - Code ID must be `100` and the Epoch bit must be `0` (1958-01-01 UTC epoch).
96    /// - Day count: 2 or 3 bytes.
97    /// - Milliseconds since midnight: always 4 bytes.
98    /// - Sub-millisecond field (bits 1-0 of P-field):
99    ///   - `00`: no fractional field
100    ///   - `01`: 2 bytes (microseconds within the millisecond, 0–65535)
101    ///   - `10`: 4 bytes (fractional part of the millisecond as 2⁻³²)
102    ///   - `11`: rejected (unsupported)
103    ///
104    /// ## P-field bit layout (first octet)
105    ///
106    /// - Bit 7:     Extension flag (1 = second P-field octet follows)
107    /// - Bits 6-4:  Code ID (must be `100`)
108    /// - Bit 3:     Epoch (must be `0` for Level 1 / 1958 epoch)
109    /// - Bit 2:     Day count size (`0` = 2 bytes, `1` = 3 bytes)
110    /// - Bits 1-0:  Sub-millisecond code (see above)
111    ///
112    /// ## T-field
113    ///
114    /// - Day count is days since **1958-01-01 00:00:00 UTC**.
115    /// - Milliseconds since midnight are always present (4 bytes).
116    /// - Sub-millisecond data (if present) is converted to attoseconds with
117    ///   exact integer scaling.
118    ///
119    /// ## Leap-second handling
120    ///
121    /// Correctly supports leap seconds. When the millisecond-of-day value
122    /// represents 23:59:60 (i.e. `millis_of_day >= 86_400_000`), `sec` is set
123    /// to `60` in the resulting time.
124    ///
125    /// ## Returns
126    ///
127    /// A [`Dt`] with `scale = TAI` and `target = UTC`.
128    ///
129    /// ## Errors
130    ///
131    /// - [`DtErrKind::Incomplete`] if `input` is empty.
132    /// - [`DtErrKind::InvalidInput`] if the P-field indicates an extended second
133    ///   octet but the input is too short to contain it.
134    /// - [`DtErrKind::InvalidItem`] if the Code ID is not `100`, the Epoch bit is
135    ///   set (non-Level-1 epoch), or the sub-millisecond code is `0b11`.
136    /// - [`DtErrKind::InvalidSyntax`] if the declared field lengths make the
137    ///   T-field longer than the remaining input bytes.
138    ///
139    /// Errors from [`TimeParts::finish`] and [`TimeParts::to_dt`] may also propagate.
140    #[inline(always)]
141    pub fn from_ccsds_cds(input: &[u8]) -> Result<Dt, DtErr> {
142        TimeParts::from_ccsds_cds(input)?.to_dt()
143    }
144
145    /// Auto-detects and parses a CCSDS binary time code (CUC, CDS, or CCS)
146    /// based on the Code ID in the first P-field byte, then returns a [`Dt`].
147    ///
148    /// Convenience wrapper around [`TimeParts::from_ccsds_bin`].
149    ///
150    /// Dispatches as follows:
151    /// - Code ID `001` → [`from_ccsds_cuc`](Self::from_ccsds_cuc)
152    ///   [`Dt::from_ccsds_cuc`](../struct.Dt.html#method.from_ccsds_cuc) (CUC – Unsegmented)
153    /// - Code ID `100` →
154    ///   [`Dt::from_ccsds_cds`](../struct.Dt.html#method.from_ccsds_cds) (CDS – Day Segmented)
155    /// - Code ID `101` →
156    ///   [`Dt::from_ccsds_ccs`](../struct.Dt.html#method.from_ccsds_ccs) (CCS – Calendar Segmented)
157    ///
158    /// For stricter control or when the format is known in advance, prefer calling
159    /// the specific `from_ccsds_*` function directly.
160    ///
161    /// ## Returns
162    ///
163    /// A [`Dt`] whose `scale` and `target` depend on the detected format:
164    /// - CUC (`001`): `scale = TAI`, `target = TAI`
165    /// - CDS (`100`): `scale = TAI`, `target = UTC`
166    /// - CCS (`101`): depends on the CCS parser implementation
167    ///
168    /// ## Errors
169    ///
170    /// - [`DtErrKind::Incomplete`] if `input` is empty.
171    /// - [`DtErrKind::InvalidItem`] if the Code ID is not one of the three
172    ///   recognized Level 1 values (`001`, `100`, or `101`).
173    ///
174    /// Any error returned by the dispatched parser is also propagated.
175    #[inline(always)]
176    pub fn from_ccsds_bin(input: &[u8]) -> Result<Dt, DtErr> {
177        TimeParts::from_ccsds_bin(input)?.to_dt()
178    }
179}