1mod from_bin_ccsds;
11mod from_str;
12mod from_str_iso;
13mod to_bin_ccsds;
14mod to_deep_time;
15
16#[cfg(feature = "alloc")]
17mod to_str_ccsds;
18
19#[cfg(feature = "chrono")]
20mod to_chrono;
21
22#[cfg(feature = "jiff")]
23mod to_jiff;
24
25use crate::{LiteStr, Scale};
26
27#[derive(Debug, Clone, Copy, Default, PartialEq)]
45#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
46#[cfg_attr(feature = "tsify", derive(tsify::Tsify))]
47#[cfg_attr(feature = "defmt", derive(defmt::Format))]
48pub struct Parts {
49 pub yr: Option<i64>,
51 pub mo: Option<u8>,
53 pub day: Option<u8>,
55 pub hr: u8,
57 pub min: u8,
59 pub sec: u8,
61 pub attos: u64,
63 pub offset: Option<Offset>,
65 pub iana_name: Option<LiteStr<49>>,
67 pub scale: Scale,
69 pub wkday: Option<Weekday>,
71 pub day_of_yr: Option<u16>,
73 pub iso_wk_yr: Option<i64>,
75 pub iso_wk: Option<u8>,
77 pub wk_sun: Option<u8>,
79 pub wk_mon: Option<u8>,
81 pub meridiem: Option<Meridiem>,
83 pub timestamp: Option<Timestamp>,
85}
86
87#[derive(Clone, Copy)]
89pub(crate) struct ParsedReal {
90 pub(crate) negative: bool,
91 pub(crate) int_u: u64,
93 pub(crate) frac_attos: u64,
95 pub(crate) scale: Scale,
96}
97
98impl Parts {
99 #[inline(always)]
100 pub fn new_utc() -> Parts {
101 Self {
102 scale: Scale::UTC,
103 ..Default::default()
104 }
105 }
106
107 #[inline(always)]
109 pub fn set_iana_name(&mut self, name: Option<&str>) {
110 self.iana_name = name.map(LiteStr::new);
111 }
112}
113
114#[derive(Copy, Clone, Debug, PartialEq, Eq)]
118#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
119#[cfg_attr(feature = "tsify", derive(tsify::Tsify))]
120#[cfg_attr(feature = "defmt", derive(defmt::Format))]
121pub enum Epoch {
122 Unix,
123 Noon2000,
124}
125
126#[derive(Debug, Clone, Copy, PartialEq, Eq)]
130#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
131#[cfg_attr(feature = "tsify", derive(tsify::Tsify))]
132#[cfg_attr(feature = "defmt", derive(defmt::Format))]
133pub struct Timestamp {
134 pub attos: i128,
135 pub epoch: Epoch,
136}
137
138#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
140#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
141#[cfg_attr(feature = "tsify", derive(tsify::Tsify))]
142#[cfg_attr(feature = "defmt", derive(defmt::Format))]
143pub enum Meridiem {
144 #[default]
145 AM,
146 PM,
147}
148
149#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
151#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
152#[cfg_attr(feature = "tsify", derive(tsify::Tsify))]
153#[cfg_attr(feature = "defmt", derive(defmt::Format))]
154pub enum Weekday {
155 #[default]
156 Sunday,
157 Monday,
158 Tuesday,
159 Wednesday,
160 Thursday,
161 Friday,
162 Saturday,
163}
164
165impl Weekday {
166 pub const fn from_sunday_0_based(n: u8) -> Option<Self> {
168 match n {
169 0 => Some(Weekday::Sunday),
170 1 => Some(Weekday::Monday),
171 2 => Some(Weekday::Tuesday),
172 3 => Some(Weekday::Wednesday),
173 4 => Some(Weekday::Thursday),
174 5 => Some(Weekday::Friday),
175 6 => Some(Weekday::Saturday),
176 _ => None,
177 }
178 }
179
180 pub const fn from_monday_1_based(n: u8) -> Option<Self> {
182 match n {
183 1 => Some(Weekday::Monday),
184 2 => Some(Weekday::Tuesday),
185 3 => Some(Weekday::Wednesday),
186 4 => Some(Weekday::Thursday),
187 5 => Some(Weekday::Friday),
188 6 => Some(Weekday::Saturday),
189 7 => Some(Weekday::Sunday),
190 _ => None,
191 }
192 }
193
194 pub const fn wkday_sun_0_based(self) -> u8 {
196 match self {
197 Weekday::Sunday => 0,
198 Weekday::Monday => 1,
199 Weekday::Tuesday => 2,
200 Weekday::Wednesday => 3,
201 Weekday::Thursday => 4,
202 Weekday::Friday => 5,
203 Weekday::Saturday => 6,
204 }
205 }
206
207 pub const fn wkday_sun_1_based(self) -> u8 {
209 match self {
210 Weekday::Sunday => 1,
211 Weekday::Monday => 2,
212 Weekday::Tuesday => 3,
213 Weekday::Wednesday => 4,
214 Weekday::Thursday => 5,
215 Weekday::Friday => 6,
216 Weekday::Saturday => 7,
217 }
218 }
219
220 pub const fn wkday_mon_0_based(self) -> u8 {
222 match self {
223 Weekday::Monday => 0,
224 Weekday::Tuesday => 1,
225 Weekday::Wednesday => 2,
226 Weekday::Thursday => 3,
227 Weekday::Friday => 4,
228 Weekday::Saturday => 5,
229 Weekday::Sunday => 6,
230 }
231 }
232
233 pub const fn wkday_mon_1_based(self) -> u8 {
235 match self {
236 Weekday::Monday => 1,
237 Weekday::Tuesday => 2,
238 Weekday::Wednesday => 3,
239 Weekday::Thursday => 4,
240 Weekday::Friday => 5,
241 Weekday::Saturday => 6,
242 Weekday::Sunday => 7,
243 }
244 }
245}
246
247#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
249#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
250#[cfg_attr(feature = "tsify", derive(tsify::Tsify))]
251#[cfg_attr(feature = "defmt", derive(defmt::Format))]
252pub enum Offset {
253 #[default]
254 None,
255 Fixed(i32),
257}
258
259#[cfg(feature = "wire")]
260impl Meridiem {
261 pub const WIRE_SIZE: usize = 1;
262
263 #[inline]
264 pub const fn to_wire_byte(self) -> u8 {
265 match self {
266 Meridiem::AM => 0,
267 Meridiem::PM => 1,
268 }
269 }
270
271 #[inline]
272 pub const fn from_wire_byte(b: u8) -> Option<Self> {
273 match b {
274 0 => Some(Meridiem::AM),
275 1 => Some(Meridiem::PM),
276 _ => None,
277 }
278 }
279}
280
281#[cfg(feature = "wire")]
282impl Offset {
283 pub const WIRE_SIZE: usize = 5; pub fn to_wire_bytes(&self) -> [u8; Self::WIRE_SIZE] {
286 let mut buf = [0u8; Self::WIRE_SIZE];
287 match self {
288 Offset::None => buf[0] = 0,
289 Offset::Fixed(offset) => {
290 buf[0] = 1;
291 buf[1..5].copy_from_slice(&offset.to_le_bytes());
292 }
293 }
294 buf
295 }
296
297 pub fn from_wire_bytes(bytes: &[u8]) -> Option<Self> {
298 if bytes.len() != Self::WIRE_SIZE {
299 return None;
300 }
301 match bytes[0] {
302 0 => Some(Offset::None),
303 1 => {
304 let offset = i32::from_le_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]);
305 Some(Offset::Fixed(offset))
306 }
307 _ => None,
308 }
309 }
310}
311
312#[cfg(feature = "wire")]
313impl Weekday {
314 pub const WIRE_SIZE: usize = 1;
315
316 #[inline]
317 pub const fn to_wire_byte(self) -> u8 {
318 self.wkday_sun_0_based()
319 }
320
321 #[inline]
322 pub const fn from_wire_byte(b: u8) -> Option<Self> {
323 Self::from_sunday_0_based(b)
324 }
325}
326
327#[cfg(feature = "wire")]
328impl Parts {
329 pub const WIRE_VERSION: u8 = 1;
331
332 pub const WIRE_SIZE: usize = 120;
335
336 pub fn to_wire_bytes(&self) -> [u8; Self::WIRE_SIZE] {
338 let mut buf = [0u8; Self::WIRE_SIZE];
339 buf[0] = Self::WIRE_VERSION;
340
341 let mut offset = 1usize;
342
343 let year = self.yr.unwrap_or(i64::MIN);
345 buf[offset..offset + 8].copy_from_slice(&year.to_le_bytes());
346 offset += 8;
347
348 buf[offset] = self.mo.unwrap_or(u8::MAX);
350 offset += 1;
351
352 buf[offset] = self.day.unwrap_or(u8::MAX);
354 offset += 1;
355
356 buf[offset] = self.hr;
358 offset += 1;
359
360 buf[offset] = self.min;
362 offset += 1;
363
364 buf[offset] = self.sec;
366 offset += 1;
367
368 let attos = self.attos;
370 buf[offset..offset + 8].copy_from_slice(&attos.to_le_bytes());
371 offset += 8;
372
373 let offset_bytes = self.offset.unwrap_or_default().to_wire_bytes();
375 buf[offset..offset + 5].copy_from_slice(&offset_bytes);
376 offset += 5;
377
378 if let Some(name) = &self.iana_name {
380 let name_bytes = name.bytes;
381 buf[offset..offset + 49].copy_from_slice(&name_bytes);
382 }
383 offset += 49;
384
385 buf[offset] = self.scale as u8;
387 offset += 1;
388
389 buf[offset] = self.wkday.map_or(255, |w| w.to_wire_byte());
391 offset += 1;
392
393 let doy = self.day_of_yr.unwrap_or(u16::MAX);
395 buf[offset..offset + 2].copy_from_slice(&doy.to_le_bytes());
396 offset += 2;
397
398 let iso_y = self.iso_wk_yr.unwrap_or(i64::MIN);
400 buf[offset..offset + 8].copy_from_slice(&iso_y.to_le_bytes());
401 offset += 8;
402
403 buf[offset] = self.iso_wk.unwrap_or(u8::MAX);
405 offset += 1;
406
407 buf[offset] = self.wk_sun.unwrap_or(u8::MAX);
409 offset += 1;
410
411 buf[offset] = self.wk_mon.unwrap_or(u8::MAX);
413 offset += 1;
414
415 buf[offset] = self.meridiem.map_or(255, |m| m.to_wire_byte());
417 offset += 1;
418
419 let (tag, attos) = match self.timestamp {
422 None => (0u8, 0i128),
423 Some(ts) => {
424 let t = match ts.epoch {
425 Epoch::Unix => 1u8,
426 Epoch::Noon2000 => 2u8,
427 };
428 (t, ts.attos)
429 }
430 };
431 buf[offset] = tag;
432 offset += 1;
433 buf[offset..offset + 16].copy_from_slice(&attos.to_le_bytes());
434 buf
437 }
438
439 pub fn from_wire_bytes(bytes: &[u8]) -> Option<Self> {
441 if bytes.len() != Self::WIRE_SIZE {
442 return None;
443 }
444 if bytes[0] != Self::WIRE_VERSION {
445 return None;
446 }
447
448 let mut dc = Parts::default();
449 let mut offset = 1usize;
450
451 let year = i64::from_le_bytes(bytes[offset..offset + 8].try_into().ok()?);
453 if year != i64::MIN {
454 dc.yr = Some(year);
455 }
456 offset += 8;
457
458 let m = bytes[offset];
460 if m != u8::MAX {
461 dc.mo = Some(m);
462 }
463 offset += 1;
464
465 let d = bytes[offset];
467 if d != u8::MAX {
468 dc.day = Some(d);
469 }
470 offset += 1;
471
472 dc.hr = bytes[offset];
474 offset += 1;
475
476 dc.min = bytes[offset];
478 offset += 1;
479
480 dc.sec = bytes[offset];
482 offset += 1;
483
484 let attos = u64::from_le_bytes(bytes[offset..offset + 8].try_into().ok()?);
486 dc.attos = attos;
487 offset += 8;
488
489 if let Some(off) = Offset::from_wire_bytes(&bytes[offset..offset + 5]) {
491 dc.offset = Some(off);
492 }
493 offset += 5;
494
495 let iana_bytes = &bytes[offset..offset + 49];
497 let name = LiteStr::<49>::from_bytes(iana_bytes);
498 if !name.as_bytes().is_empty() {
499 dc.iana_name = Some(name);
500 }
501 offset += 49;
502
503 dc.scale = Scale::from_u8(bytes[offset]);
505 offset += 1;
506
507 let wd_byte = bytes[offset];
509 if wd_byte != 255
510 && let Some(wd) = Weekday::from_wire_byte(wd_byte)
511 {
512 dc.wkday = Some(wd);
513 }
514 offset += 1;
515
516 let doy = u16::from_le_bytes(bytes[offset..offset + 2].try_into().ok()?);
518 if doy != u16::MAX {
519 dc.day_of_yr = Some(doy);
520 }
521 offset += 2;
522
523 let iso_y = i64::from_le_bytes(bytes[offset..offset + 8].try_into().ok()?);
525 if iso_y != i64::MIN {
526 dc.iso_wk_yr = Some(iso_y);
527 }
528 offset += 8;
529
530 let iw = bytes[offset];
532 if iw != u8::MAX {
533 dc.iso_wk = Some(iw);
534 }
535 offset += 1;
536
537 let ws = bytes[offset];
539 if ws != u8::MAX {
540 dc.wk_sun = Some(ws);
541 }
542 offset += 1;
543
544 let wm = bytes[offset];
546 if wm != u8::MAX {
547 dc.wk_mon = Some(wm);
548 }
549 offset += 1;
550
551 let mer_byte = bytes[offset];
553 if mer_byte != 255
554 && let Some(m) = Meridiem::from_wire_byte(mer_byte)
555 {
556 dc.meridiem = Some(m);
557 }
558 offset += 1;
559
560 let tag = bytes[offset];
563 offset += 1;
564
565 if tag != 0 {
566 let attos_arr: [u8; 16] = bytes[offset..offset + 16].try_into().ok()?;
567 let attos = i128::from_le_bytes(attos_arr);
568 let epoch = match tag {
571 1 => Epoch::Unix,
572 2 => Epoch::Noon2000,
573 _ => return None,
574 };
575 dc.timestamp = Some(Timestamp { attos, epoch });
576 } else {
577 }
579
580 Some(dc)
581 }
582}