Skip to main content

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}