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}