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).
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        Parts::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`](../enum.Scale.html#variant.TAI).
70    ///
71    /// ## Errors
72    ///
73    /// - [`DtErrKind::Empty`](../error/enum.DtErrKind.html#variant.Empty) if `input` is empty.
74    /// - [`DtErrKind::InvalidCodeId`](../error/enum.DtErrKind.html#variant.InvalidCodeId) if the Code ID is not `001`.
75    /// - [`DtErrKind::InvalidInput`](../error/enum.DtErrKind.html#variant.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::TFieldTooShort`](../error/enum.DtErrKind.html#variant.TFieldTooShort) if the declared coarse + fractional field lengths
79    ///   make the T-field longer than the remaining input bytes.
80    ///
81    /// Errors from [`Parts::finish`] and [`Parts::to_dt`] may also propagate.
82    #[inline(always)]
83    pub fn from_ccsds_cuc(input: &[u8]) -> Result<Dt, DtErr> {
84        Parts::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::Empty`](../error/enum.DtErrKind.html#variant.Empty) if `input` is empty.
132    /// - [`DtErrKind::PFieldTooShort`](../error/enum.DtErrKind.html#variant.PFieldTooShort) if the P-field indicates an extended second
133    ///   octet but the input is too short to contain it.
134    /// - [`DtErrKind::InvalidCodeId`](../error/enum.DtErrKind.html#variant.InvalidCodeId) if the Code ID is not `100` or the Epoch bit is
135    ///   set (non-Level-1 epoch).
136    /// - [`DtErrKind::InvalidSubmillisecond`](../error/enum.DtErrKind.html#variant.InvalidSubmillisecond) if the sub-millisecond code is `0b11`.
137    /// - [`DtErrKind::InvalidSyntax`](../error/enum.DtErrKind.html#variant.InvalidSyntax) if the declared field lengths make the
138    ///   T-field longer than the remaining input bytes.
139    ///
140    /// Errors from [`Parts::finish`] and [`Parts::to_dt`] may also propagate.
141    #[inline(always)]
142    pub fn from_ccsds_cds(input: &[u8]) -> Result<Dt, DtErr> {
143        Parts::from_ccsds_cds(input)?.to_dt()
144    }
145
146    /// Auto-detects and parses a CCSDS binary time code (CUC, CDS, or CCS)
147    /// based on the Code ID in the first P-field byte, then returns a [`Dt`].
148    ///
149    /// Convenience wrapper around [`Parts::from_ccsds_bin`].
150    ///
151    /// Dispatches as follows:
152    /// - Code ID `001` → [`from_ccsds_cuc`](Self::from_ccsds_cuc)
153    ///   [`Dt::from_ccsds_cuc`](../struct.Dt.html#method.from_ccsds_cuc) (CUC – Unsegmented)
154    /// - Code ID `100` →
155    ///   [`Dt::from_ccsds_cds`](../struct.Dt.html#method.from_ccsds_cds) (CDS – Day Segmented)
156    /// - Code ID `101` →
157    ///   [`Dt::from_ccsds_ccs`](../struct.Dt.html#method.from_ccsds_ccs) (CCS – Calendar Segmented)
158    ///
159    /// For stricter control or when the format is known in advance, prefer calling
160    /// the specific `from_ccsds_*` function directly.
161    ///
162    /// ## Returns
163    ///
164    /// A [`Dt`] whose `scale` and `target` depend on the detected format:
165    /// - CUC (`001`): `scale = TAI`, `target = TAI`
166    /// - CDS (`100`): `scale = TAI`, `target = UTC`
167    /// - CCS (`101`): depends on the CCS parser implementation
168    ///
169    /// ## Errors
170    ///
171    /// - [`DtErrKind::Empty`](../error/enum.DtErrKind.html#variant.Empty) if `input` is empty.
172    /// - [`DtErrKind::InvalidCodeId`](../error/enum.DtErrKind.html#variant.InvalidCodeId) if the Code ID is not one of the three
173    ///   recognized Level 1 values (`001`, `100`, or `101`).
174    ///
175    /// Any error returned by the dispatched parser is also propagated.
176    #[inline(always)]
177    pub fn from_ccsds_bin(input: &[u8]) -> Result<Dt, DtErr> {
178        Parts::from_ccsds_bin(input)?.to_dt()
179    }
180}