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_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}