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}