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}