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