deep_time/dt/from_ccsds.rs
1use crate::{Dt, DtErr, TimeParts};
2
3impl Dt {
4 /// Generalized CCSDS ASCII Time Code parser (A or B variant).
5 /// Handles both calendar (`%Y-%m-%d`) and day-of-year (`%Y-%j`) formats.
6 /// All time components after the date portion are optional.
7 #[inline]
8 pub fn from_str_ccsds(input: &str) -> Result<Self, DtErr> {
9 TimeParts::from_str_ccsds(input)?.to_dt()
10 }
11
12 /// Parses a **CCSDS CCS (Calendar Segmented Time Code)** binary time code
13 /// directly into [`TimeParts`].
14 ///
15 /// Implements **CCSDS 301.0-B-4 §3.4** (Level 1 only).
16 ///
17 /// ## P-field (exactly 1 byte)
18 ///
19 /// - Bit 7: Extension flag → must be `0` (we reject extensions)
20 /// - Bits 6-4: Code ID = `101`
21 /// - Bit 3: Calendar type (`0` = Month/Day, `1` = Day-of-Year)
22 /// - Bits 2-0: Number of subsecond BCD octets (`0`–`6`)
23 ///
24 /// ## T-field (BCD, big-endian)
25 ///
26 /// - 2 bytes: Year (0001–9999)
27 /// - 2 bytes: Month+Day (01-12,01-31) **or** Day-of-Year (001–366)
28 /// - 3 bytes: Hour (00-23), Minute (00-59), Second (00-60)
29 /// - 0–6 bytes: Fractional seconds (exactly 2 decimal digits per byte)
30 ///
31 /// Epoch: 1958-01-01 00:00:00 **UTC** (identical to CDS).
32 #[inline]
33 pub fn from_ccsds_ccs(input: &[u8]) -> Result<Dt, DtErr> {
34 TimeParts::from_ccsds_ccs(input)?.to_dt()
35 }
36
37 /// Parses a **CCSDS C (CUC – Unsegmented Time Code)** binary time code
38 /// directly into [`Dt`].
39 ///
40 /// This function implements **CCSDS 301.0-B-4 §3.2** (Level 1 only) **with full support
41 /// for the extended P-field** (second octet) as defined in the standard.
42 ///
43 /// ## Supported formats (Level 1 only)
44 ///
45 /// - 1-byte or 2-byte P-field (further extension beyond 2 bytes is rejected).
46 /// - Code ID must be `001` (1958-01-01 TAI epoch).
47 /// - Coarse time: 1–7 octets (base 1–4 from Octet 1 + up to 3 additional from Octet 2).
48 /// - Fractional time: 0–10 octets (base 0–3 from Octet 1 + up to 7 additional from Octet 2).
49 ///
50 /// ## P-field decoding (when Bit 0 of Octet 1 = 1)
51 ///
52 /// - **Octet 2**:
53 /// - Bit 0: Further-extension flag (must be 0; we reject 3+-byte P-fields).
54 /// - Bits 1-2: Additional coarse octets (0–3).
55 /// - Bits 3-5: Additional fractional octets (0–7).
56 /// - Bits 6-7: Reserved for mission definition (ignored).
57 ///
58 /// ## Precision
59 ///
60 /// Fractional seconds are converted to attoseconds with **exact** integer scaling
61 /// (`value / 2^(8·n_frac)`). Larger `n_frac` gives higher resolution (down to ~2⁻⁸⁰ s
62 /// with 10 fractional bytes).
63 ///
64 /// ## Returns
65 ///
66 /// A [`Dt`] with `scale = TAI` and `tz = Utc`.
67 ///
68 /// ## Errors
69 ///
70 /// - [`DtErrKind::CCSDSBinEmpty`] if the input is empty.
71 /// - [`DtErrKind::CCSDSBinTooShort`] if the input is too short for the declared P-field / T-field sizes
72 /// or otherwise malformed.
73 /// - [`DtErrKind::CCSDSBinInvalidCodeId`] if the Code ID is not `001`.
74 /// - [`DtErrKind::CCSDSBinInvalidPFieldExtension`] if the further-extension flag is set
75 /// (3+ byte P-field, unsupported).
76 #[inline]
77 pub fn from_ccsds_c(input: &[u8]) -> Result<Dt, DtErr> {
78 TimeParts::from_ccsds_c(input)?.to_dt()
79 }
80
81 /// Parses a **CCSDS D (CDS – Day Segmented Time Code)** binary time code
82 /// directly into [`Dt`].
83 ///
84 /// This function implements CCSDS 301.0-B-4 §3.3 (Level 1 only).
85 ///
86 /// ## Supported formats
87 ///
88 /// - 1-byte or 2-byte P-field.
89 /// - Code ID must be `100` and Epoch bit must be `0` (1958-01-01 UTC epoch).
90 /// - `n_day`: 2 or 3 bytes for the day count.
91 /// - Middle field is always 4 bytes of **milliseconds since midnight**.
92 /// - Sub-millisecond field (bits 6-7 of P-field):
93 /// - `00`: no fractional field
94 /// - `01`: 2 bytes (microseconds of the millisecond, 0–65535)
95 /// - `10`: 4 bytes (2⁻³² of the millisecond)
96 ///
97 /// ## Precision
98 ///
99 /// - The millisecond field is rounded to the nearest millisecond (in the encoder).
100 /// - With 2-byte sub-ms: maximum quantization error ≈ ±7.63 ns.
101 /// - With 4-byte sub-ms: maximum quantization error ≈ ±0.116 ps.
102 ///
103 /// ## Returns
104 ///
105 /// A [`Dt`] with `timescale = Utc` and `tz = Utc`.
106 ///
107 /// ## Errors
108 ///
109 /// - [`DtErrKind::CCSDSBinEmpty`] if the input is empty.
110 /// - [`DtErrKind::CCSDSBinTooShort`] if the input is too short for the declared field sizes.
111 /// - [`DtErrKind::CCSDSBinInvalidCodeId`] if the Code ID is not `100`.
112 /// - [`DtErrKind::CCSDSBinInvalidEpoch`] if the Epoch bit is set (non-Level-1 / non-1958 epoch).
113 /// - [`DtErrKind::CCSDSBinInvalidSubMillisecondCode`] if bits 6-7 encode an unsupported value (0b11).
114 #[inline]
115 pub fn from_ccsds_d(input: &[u8]) -> Result<Dt, DtErr> {
116 TimeParts::from_ccsds_d(input)?.to_dt()
117 }
118
119 /// Auto-detects and parses a CCSDS binary time code (CUC, CDS, or CCS)
120 /// based on the Code ID in the first P-field byte.
121 ///
122 /// Convenience wrapper around [`TimeParts::from_ccsds_bin`].
123 ///
124 /// ## Supported formats
125 /// - Code ID `001` → CUC (Unsegmented)
126 /// - Code ID `100` → CDS (Day Segmented)
127 /// - Code ID `101` → CCS (Calendar Segmented)
128 ///
129 /// ## Errors
130 /// - [`DtErrKind::CCSDSBinEmpty`] if the input is empty.
131 /// - [`DtErrKind::CCSDSBinInvalidCodeId`] for any other Code ID.
132 #[inline]
133 pub fn from_ccsds_bin(input: &[u8]) -> Result<Dt, DtErr> {
134 TimeParts::from_ccsds_bin(input)?.to_dt()
135 }
136}