1mod from_ccsds_bin;
2mod from_ccsds_str;
3mod from_str;
4mod to_ccsds_bin;
5mod to_deep_time;
6
7#[cfg(feature = "alloc")]
8mod to_ccsds_str;
9
10#[cfg(feature = "chrono")]
11mod to_chrono;
12
13#[cfg(feature = "jiff")]
14mod to_jiff;
15
16use crate::{AsciiStr, Scale};
17
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32#[cfg_attr(feature = "js", derive(tsify::Tsify))]
33#[derive(Debug, Clone, Copy, Default, PartialEq)]
34pub struct TimeParts {
35 pub year: Option<i64>,
37 pub month: Option<u8>,
39 pub day: Option<u8>,
41 pub hour: Option<u8>,
43 pub minute: Option<u8>,
45 pub second: Option<u8>,
47 pub attos: Option<u64>,
49 pub offset: Option<Offset>,
51 pub iana_name: Option<AsciiStr<49>>,
53 pub is_leap_second: bool,
55 pub scale: Scale,
57 pub weekday: Option<Weekday>,
59 pub day_of_year: Option<u16>,
61 pub iso_week_year: Option<i64>,
63 pub iso_week: Option<u8>,
65 pub week_sun: Option<u8>,
67 pub week_mon: Option<u8>,
69 pub meridiem: Option<Meridiem>,
71 pub unix_timestamp_seconds: Option<i64>,
73}
74
75impl TimeParts {
76 #[inline]
77 pub fn new_utc() -> Self {
78 Self {
79 scale: Scale::UTC,
80 ..Default::default()
81 }
82 }
83
84 #[inline]
89 pub fn set_iana_name(&mut self, name: Option<&str>) -> &mut Self {
90 self.iana_name = name.and_then(|s| AsciiStr::try_from_str(s).ok());
91 self
92 }
93}
94
95#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
97#[cfg_attr(feature = "js", derive(tsify::Tsify))]
98#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
99pub enum Meridiem {
100 #[default]
101 AM,
102 PM,
103}
104
105#[cfg(feature = "wire")]
106impl Meridiem {
107 pub const WIRE_SIZE: usize = 1;
108
109 #[inline]
110 pub const fn to_wire_byte(self) -> u8 {
111 match self {
112 Meridiem::AM => 0,
113 Meridiem::PM => 1,
114 }
115 }
116
117 #[inline]
118 pub const fn from_wire_byte(b: u8) -> Option<Self> {
119 match b {
120 0 => Some(Meridiem::AM),
121 1 => Some(Meridiem::PM),
122 _ => None,
123 }
124 }
125}
126
127#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
129#[cfg_attr(feature = "js", derive(tsify::Tsify))]
130#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
131pub enum Weekday {
132 #[default]
133 Sunday,
134 Monday,
135 Tuesday,
136 Wednesday,
137 Thursday,
138 Friday,
139 Saturday,
140}
141
142impl Weekday {
143 #[inline]
145 pub const fn from_sunday_zero_offset(n: i8) -> Option<Self> {
146 match n {
147 0 => Some(Weekday::Sunday),
148 1 => Some(Weekday::Monday),
149 2 => Some(Weekday::Tuesday),
150 3 => Some(Weekday::Wednesday),
151 4 => Some(Weekday::Thursday),
152 5 => Some(Weekday::Friday),
153 6 => Some(Weekday::Saturday),
154 _ => None,
155 }
156 }
157
158 #[inline]
160 pub const fn from_monday_one_offset(n: i8) -> Option<Self> {
161 match n {
162 1 => Some(Weekday::Monday),
163 2 => Some(Weekday::Tuesday),
164 3 => Some(Weekday::Wednesday),
165 4 => Some(Weekday::Thursday),
166 5 => Some(Weekday::Friday),
167 6 => Some(Weekday::Saturday),
168 7 => Some(Weekday::Sunday),
169 _ => None,
170 }
171 }
172
173 #[inline]
175 pub const fn wk_sun(self) -> u8 {
176 match self {
177 Weekday::Sunday => 0,
178 Weekday::Monday => 1,
179 Weekday::Tuesday => 2,
180 Weekday::Wednesday => 3,
181 Weekday::Thursday => 4,
182 Weekday::Friday => 5,
183 Weekday::Saturday => 6,
184 }
185 }
186
187 #[inline]
189 pub const fn wk_mon(self) -> u8 {
190 match self {
191 Weekday::Monday => 1,
192 Weekday::Tuesday => 2,
193 Weekday::Wednesday => 3,
194 Weekday::Thursday => 4,
195 Weekday::Friday => 5,
196 Weekday::Saturday => 6,
197 Weekday::Sunday => 7,
198 }
199 }
200}
201
202#[cfg(feature = "wire")]
203impl Weekday {
204 pub const WIRE_SIZE: usize = 1;
205
206 #[inline]
207 pub const fn to_wire_byte(self) -> u8 {
208 self.wk_sun()
209 }
210
211 #[inline]
212 pub const fn from_wire_byte(b: u8) -> Option<Self> {
213 Self::from_sunday_zero_offset(b as i8)
214 }
215}
216
217#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
219#[cfg_attr(feature = "js", derive(tsify::Tsify))]
220#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
221pub enum Offset {
222 #[default]
223 Utc,
224 None,
225 Fixed(i32),
227}
228
229#[cfg(feature = "wire")]
230impl Offset {
231 pub const WIRE_SIZE: usize = 5; pub fn to_wire_bytes(&self) -> [u8; Self::WIRE_SIZE] {
234 let mut buf = [0u8; Self::WIRE_SIZE];
235 match self {
236 Offset::Utc => buf[0] = 0,
237 Offset::None => buf[0] = 1,
238 Offset::Fixed(offset) => {
239 buf[0] = 2;
240 buf[1..5].copy_from_slice(&offset.to_le_bytes());
241 }
242 }
243 buf
244 }
245
246 pub fn from_wire_bytes(bytes: &[u8]) -> Option<Self> {
247 if bytes.len() != Self::WIRE_SIZE {
248 return None;
249 }
250 match bytes[0] {
251 0 => Some(Offset::Utc),
252 1 => Some(Offset::None),
253 2 => {
254 let offset = i32::from_le_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]);
255 Some(Offset::Fixed(offset))
256 }
257 _ => None,
258 }
259 }
260}
261
262#[cfg(feature = "wire")]
263impl TimeParts {
264 pub const WIRE_VERSION: u8 = 1;
266
267 pub const WIRE_SIZE: usize = 120;
269
270 pub fn to_wire_bytes(&self) -> [u8; Self::WIRE_SIZE] {
276 let mut buf = [0u8; Self::WIRE_SIZE];
277 buf[0] = Self::WIRE_VERSION;
278
279 let mut offset = 1usize;
280
281 let year = self.year.unwrap_or(i64::MIN);
283 buf[offset..offset + 8].copy_from_slice(&year.to_le_bytes());
284 offset += 8;
285
286 buf[offset] = self.month.unwrap_or(u8::MAX);
288 offset += 1;
289
290 buf[offset] = self.day.unwrap_or(u8::MAX);
292 offset += 1;
293
294 buf[offset] = self.hour.unwrap_or(u8::MAX);
296 offset += 1;
297
298 buf[offset] = self.minute.unwrap_or(u8::MAX);
300 offset += 1;
301
302 buf[offset] = self.second.unwrap_or(u8::MAX);
304 offset += 1;
305
306 let attos = self.attos.unwrap_or(u64::MAX);
308 buf[offset..offset + 8].copy_from_slice(&attos.to_le_bytes());
309 offset += 8;
310
311 let offset_bytes = self.offset.unwrap_or_default().to_wire_bytes();
313 buf[offset..offset + 5].copy_from_slice(&offset_bytes);
314 offset += 5;
315
316 if let Some(name) = &self.iana_name {
318 let name_bytes = name.to_wire_bytes();
319 buf[offset..offset + 49].copy_from_slice(&name_bytes);
320 }
321 offset += 49;
322
323 buf[offset] = if self.is_leap_second { 1 } else { 0 };
325 offset += 1;
326
327 buf[offset] = self.scale as u8;
329 offset += 1;
330
331 buf[offset] = self.weekday.map_or(255, |w| w.to_wire_byte());
333 offset += 1;
334
335 let doy = self.day_of_year.unwrap_or(u16::MAX);
337 buf[offset..offset + 2].copy_from_slice(&doy.to_le_bytes());
338 offset += 2;
339
340 let iso_y = self.iso_week_year.unwrap_or(i64::MIN);
342 buf[offset..offset + 8].copy_from_slice(&iso_y.to_le_bytes());
343 offset += 8;
344
345 buf[offset] = self.iso_week.unwrap_or(u8::MAX);
347 offset += 1;
348
349 buf[offset] = self.week_sun.unwrap_or(u8::MAX);
351 offset += 1;
352
353 buf[offset] = self.week_mon.unwrap_or(u8::MAX);
355 offset += 1;
356
357 buf[offset] = self.meridiem.map_or(255, |m| m.to_wire_byte());
359 offset += 1;
360
361 let unix = self.unix_timestamp_seconds.unwrap_or(i64::MIN);
363 buf[offset..offset + 8].copy_from_slice(&unix.to_le_bytes());
364
365 buf
366 }
367
368 pub fn from_wire_bytes(bytes: &[u8]) -> Option<Self> {
372 if bytes.len() != Self::WIRE_SIZE {
373 return None;
374 }
375 if bytes[0] != Self::WIRE_VERSION {
376 return None;
377 }
378
379 let mut dc = TimeParts::default();
380 let mut offset = 1usize;
381
382 let year = i64::from_le_bytes(bytes[offset..offset + 8].try_into().ok()?);
384 if year != i64::MIN {
385 dc.year = Some(year);
386 }
387 offset += 8;
388
389 let m = bytes[offset];
391 if m != u8::MAX {
392 dc.month = Some(m);
393 }
394 offset += 1;
395
396 let d = bytes[offset];
398 if d != u8::MAX {
399 dc.day = Some(d);
400 }
401 offset += 1;
402
403 let h = bytes[offset];
405 if h != u8::MAX {
406 dc.hour = Some(h);
407 }
408 offset += 1;
409
410 let min = bytes[offset];
412 if min != u8::MAX {
413 dc.minute = Some(min);
414 }
415 offset += 1;
416
417 let sec = bytes[offset];
419 if sec != u8::MAX {
420 dc.second = Some(sec);
421 }
422 offset += 1;
423
424 let attos = u64::from_le_bytes(bytes[offset..offset + 8].try_into().ok()?);
426 if attos != u64::MAX {
427 dc.attos = Some(attos);
428 }
429 offset += 8;
430
431 if let Some(offset) = Offset::from_wire_bytes(&bytes[offset..offset + 5]) {
433 dc.offset = Some(offset);
434 }
435 offset += 5;
436
437 let iana_bytes = &bytes[offset..offset + 49];
439 if let Some(name) = AsciiStr::<49>::from_wire_bytes(iana_bytes)
440 && !name.is_empty()
441 {
442 dc.iana_name = Some(name);
443 }
444 offset += 49;
445
446 dc.is_leap_second = bytes[offset] != 0;
448 offset += 1;
449
450 dc.scale = Scale::from_u8(bytes[offset]);
452 offset += 1;
453
454 let wd_byte = bytes[offset];
456 if wd_byte != 255
457 && let Some(wd) = Weekday::from_wire_byte(wd_byte)
458 {
459 dc.weekday = Some(wd);
460 }
461 offset += 1;
462
463 let doy = u16::from_le_bytes(bytes[offset..offset + 2].try_into().ok()?);
465 if doy != u16::MAX {
466 dc.day_of_year = Some(doy);
467 }
468 offset += 2;
469
470 let iso_y = i64::from_le_bytes(bytes[offset..offset + 8].try_into().ok()?);
472 if iso_y != i64::MIN {
473 dc.iso_week_year = Some(iso_y);
474 }
475 offset += 8;
476
477 let iw = bytes[offset];
479 if iw != u8::MAX {
480 dc.iso_week = Some(iw);
481 }
482 offset += 1;
483
484 let ws = bytes[offset];
486 if ws != u8::MAX {
487 dc.week_sun = Some(ws);
488 }
489 offset += 1;
490
491 let wm = bytes[offset];
493 if wm != u8::MAX {
494 dc.week_mon = Some(wm);
495 }
496 offset += 1;
497
498 let mer_byte = bytes[offset];
500 if mer_byte != 255
501 && let Some(m) = Meridiem::from_wire_byte(mer_byte)
502 {
503 dc.meridiem = Some(m);
504 }
505
506 offset += 1;
507
508 let unix = i64::from_le_bytes(bytes[offset..offset + 8].try_into().ok()?);
510 if unix != i64::MIN {
511 dc.unix_timestamp_seconds = Some(unix);
512 }
513
514 Some(dc)
515 }
516}