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))]
19#[cfg_attr(feature = "js", derive(tsify::Tsify))]
20#[derive(Debug, Clone, Copy, Default, PartialEq)]
21pub struct TimeParts {
22 pub year: Option<i64>,
23 pub month: Option<u8>, pub day: Option<u8>, pub hour: Option<u8>, pub minute: Option<u8>, pub second: Option<u8>, pub attos: Option<u64>, pub offset: Option<Offset>,
30 pub iana_name: Option<AsciiStr<49>>,
31 pub is_leap_second: bool,
32 pub scale: Scale,
33 pub weekday: Option<Weekday>,
34 pub day_of_year: Option<u16>, pub iso_week_year: Option<i64>, pub iso_week: Option<u8>, pub week_sun: Option<u8>, pub week_mon: Option<u8>, pub meridiem: Option<Meridiem>,
40 pub unix_timestamp_seconds: Option<i64>, }
42
43impl TimeParts {
44 #[inline]
45 pub fn new_utc() -> Self {
46 Self {
47 scale: Scale::UTC,
48 ..Default::default()
49 }
50 }
51
52 #[inline]
57 pub fn set_iana_name(&mut self, name: Option<&str>) -> &mut Self {
58 self.iana_name = name.and_then(|s| AsciiStr::try_from_str(s).ok());
59 self
60 }
61}
62
63#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
64#[cfg_attr(feature = "js", derive(tsify::Tsify))]
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
66pub enum Meridiem {
67 #[default]
68 AM,
69 PM,
70}
71
72#[cfg(feature = "wire")]
73impl Meridiem {
74 pub const WIRE_SIZE: usize = 1;
75
76 #[inline]
77 pub const fn to_wire_byte(self) -> u8 {
78 match self {
79 Meridiem::AM => 0,
80 Meridiem::PM => 1,
81 }
82 }
83
84 #[inline]
85 pub const fn from_wire_byte(b: u8) -> Option<Self> {
86 match b {
87 0 => Some(Meridiem::AM),
88 1 => Some(Meridiem::PM),
89 _ => None,
90 }
91 }
92}
93
94#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
95#[cfg_attr(feature = "js", derive(tsify::Tsify))]
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
97pub enum Weekday {
98 #[default]
99 Sunday,
100 Monday,
101 Tuesday,
102 Wednesday,
103 Thursday,
104 Friday,
105 Saturday,
106}
107
108impl Weekday {
109 #[inline]
111 pub const fn from_sunday_zero_offset(n: i8) -> Option<Self> {
112 match n {
113 0 => Some(Weekday::Sunday),
114 1 => Some(Weekday::Monday),
115 2 => Some(Weekday::Tuesday),
116 3 => Some(Weekday::Wednesday),
117 4 => Some(Weekday::Thursday),
118 5 => Some(Weekday::Friday),
119 6 => Some(Weekday::Saturday),
120 _ => None,
121 }
122 }
123
124 #[inline]
126 pub const fn from_monday_one_offset(n: i8) -> Option<Self> {
127 match n {
128 1 => Some(Weekday::Monday),
129 2 => Some(Weekday::Tuesday),
130 3 => Some(Weekday::Wednesday),
131 4 => Some(Weekday::Thursday),
132 5 => Some(Weekday::Friday),
133 6 => Some(Weekday::Saturday),
134 7 => Some(Weekday::Sunday),
135 _ => None,
136 }
137 }
138
139 #[inline]
141 pub const fn wk_sun(self) -> u8 {
142 match self {
143 Weekday::Sunday => 0,
144 Weekday::Monday => 1,
145 Weekday::Tuesday => 2,
146 Weekday::Wednesday => 3,
147 Weekday::Thursday => 4,
148 Weekday::Friday => 5,
149 Weekday::Saturday => 6,
150 }
151 }
152
153 #[inline]
155 pub const fn wk_mon(self) -> u8 {
156 match self {
157 Weekday::Monday => 1,
158 Weekday::Tuesday => 2,
159 Weekday::Wednesday => 3,
160 Weekday::Thursday => 4,
161 Weekday::Friday => 5,
162 Weekday::Saturday => 6,
163 Weekday::Sunday => 7,
164 }
165 }
166}
167
168#[cfg(feature = "wire")]
169impl Weekday {
170 pub const WIRE_SIZE: usize = 1;
171
172 #[inline]
173 pub const fn to_wire_byte(self) -> u8 {
174 self.wk_sun()
175 }
176
177 #[inline]
178 pub const fn from_wire_byte(b: u8) -> Option<Self> {
179 Self::from_sunday_zero_offset(b as i8)
180 }
181}
182
183#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
184#[cfg_attr(feature = "js", derive(tsify::Tsify))]
185#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
186pub enum Offset {
187 #[default]
188 Utc,
189 None,
190 Fixed(i32),
192}
193
194#[cfg(feature = "wire")]
195impl Offset {
196 pub const WIRE_SIZE: usize = 5; pub fn to_wire_bytes(&self) -> [u8; Self::WIRE_SIZE] {
199 let mut buf = [0u8; Self::WIRE_SIZE];
200 match self {
201 Offset::Utc => buf[0] = 0,
202 Offset::None => buf[0] = 1,
203 Offset::Fixed(offset) => {
204 buf[0] = 2;
205 buf[1..5].copy_from_slice(&offset.to_le_bytes());
206 }
207 }
208 buf
209 }
210
211 pub fn from_wire_bytes(bytes: &[u8]) -> Option<Self> {
212 if bytes.len() != Self::WIRE_SIZE {
213 return None;
214 }
215 match bytes[0] {
216 0 => Some(Offset::Utc),
217 1 => Some(Offset::None),
218 2 => {
219 let offset = i32::from_le_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]);
220 Some(Offset::Fixed(offset))
221 }
222 _ => None,
223 }
224 }
225}
226
227#[cfg(feature = "wire")]
228impl TimeParts {
229 pub const WIRE_VERSION: u8 = 1;
231
232 pub const WIRE_SIZE: usize = 120;
234
235 pub fn to_wire_bytes(&self) -> [u8; Self::WIRE_SIZE] {
241 let mut buf = [0u8; Self::WIRE_SIZE];
242 buf[0] = Self::WIRE_VERSION;
243
244 let mut offset = 1usize;
245
246 let year = self.year.unwrap_or(i64::MIN);
248 buf[offset..offset + 8].copy_from_slice(&year.to_le_bytes());
249 offset += 8;
250
251 buf[offset] = self.month.unwrap_or(u8::MAX);
253 offset += 1;
254
255 buf[offset] = self.day.unwrap_or(u8::MAX);
257 offset += 1;
258
259 buf[offset] = self.hour.unwrap_or(u8::MAX);
261 offset += 1;
262
263 buf[offset] = self.minute.unwrap_or(u8::MAX);
265 offset += 1;
266
267 buf[offset] = self.second.unwrap_or(u8::MAX);
269 offset += 1;
270
271 let attos = self.attos.unwrap_or(u64::MAX);
273 buf[offset..offset + 8].copy_from_slice(&attos.to_le_bytes());
274 offset += 8;
275
276 let offset_bytes = self.offset.unwrap_or_default().to_wire_bytes();
278 buf[offset..offset + 5].copy_from_slice(&offset_bytes);
279 offset += 5;
280
281 if let Some(name) = &self.iana_name {
283 let name_bytes = name.to_wire_bytes();
284 buf[offset..offset + 49].copy_from_slice(&name_bytes);
285 }
286 offset += 49;
287
288 buf[offset] = if self.is_leap_second { 1 } else { 0 };
290 offset += 1;
291
292 buf[offset] = self.scale as u8;
294 offset += 1;
295
296 buf[offset] = self.weekday.map_or(255, |w| w.to_wire_byte());
298 offset += 1;
299
300 let doy = self.day_of_year.unwrap_or(u16::MAX);
302 buf[offset..offset + 2].copy_from_slice(&doy.to_le_bytes());
303 offset += 2;
304
305 let iso_y = self.iso_week_year.unwrap_or(i64::MIN);
307 buf[offset..offset + 8].copy_from_slice(&iso_y.to_le_bytes());
308 offset += 8;
309
310 buf[offset] = self.iso_week.unwrap_or(u8::MAX);
312 offset += 1;
313
314 buf[offset] = self.week_sun.unwrap_or(u8::MAX);
316 offset += 1;
317
318 buf[offset] = self.week_mon.unwrap_or(u8::MAX);
320 offset += 1;
321
322 buf[offset] = self.meridiem.map_or(255, |m| m.to_wire_byte());
324 offset += 1;
325
326 let unix = self.unix_timestamp_seconds.unwrap_or(i64::MIN);
328 buf[offset..offset + 8].copy_from_slice(&unix.to_le_bytes());
329
330 buf
331 }
332
333 pub fn from_wire_bytes(bytes: &[u8]) -> Option<Self> {
337 if bytes.len() != Self::WIRE_SIZE {
338 return None;
339 }
340 if bytes[0] != Self::WIRE_VERSION {
341 return None;
342 }
343
344 let mut dc = TimeParts::default();
345 let mut offset = 1usize;
346
347 let year = i64::from_le_bytes(bytes[offset..offset + 8].try_into().ok()?);
349 if year != i64::MIN {
350 dc.year = Some(year);
351 }
352 offset += 8;
353
354 let m = bytes[offset];
356 if m != u8::MAX {
357 dc.month = Some(m);
358 }
359 offset += 1;
360
361 let d = bytes[offset];
363 if d != u8::MAX {
364 dc.day = Some(d);
365 }
366 offset += 1;
367
368 let h = bytes[offset];
370 if h != u8::MAX {
371 dc.hour = Some(h);
372 }
373 offset += 1;
374
375 let min = bytes[offset];
377 if min != u8::MAX {
378 dc.minute = Some(min);
379 }
380 offset += 1;
381
382 let sec = bytes[offset];
384 if sec != u8::MAX {
385 dc.second = Some(sec);
386 }
387 offset += 1;
388
389 let attos = u64::from_le_bytes(bytes[offset..offset + 8].try_into().ok()?);
391 if attos != u64::MAX {
392 dc.attos = Some(attos);
393 }
394 offset += 8;
395
396 if let Some(offset) = Offset::from_wire_bytes(&bytes[offset..offset + 5]) {
398 dc.offset = Some(offset);
399 }
400 offset += 5;
401
402 let iana_bytes = &bytes[offset..offset + 49];
404 if let Some(name) = AsciiStr::<49>::from_wire_bytes(iana_bytes) {
405 if !name.is_empty() {
406 dc.iana_name = Some(name);
407 }
408 }
409 offset += 49;
410
411 dc.is_leap_second = bytes[offset] != 0;
413 offset += 1;
414
415 dc.scale = Scale::from_u8(bytes[offset]);
417 offset += 1;
418
419 let wd_byte = bytes[offset];
421 if wd_byte != 255 {
422 if let Some(wd) = Weekday::from_wire_byte(wd_byte) {
423 dc.weekday = Some(wd);
424 }
425 }
426 offset += 1;
427
428 let doy = u16::from_le_bytes(bytes[offset..offset + 2].try_into().ok()?);
430 if doy != u16::MAX {
431 dc.day_of_year = Some(doy);
432 }
433 offset += 2;
434
435 let iso_y = i64::from_le_bytes(bytes[offset..offset + 8].try_into().ok()?);
437 if iso_y != i64::MIN {
438 dc.iso_week_year = Some(iso_y);
439 }
440 offset += 8;
441
442 let iw = bytes[offset];
444 if iw != u8::MAX {
445 dc.iso_week = Some(iw);
446 }
447 offset += 1;
448
449 let ws = bytes[offset];
451 if ws != u8::MAX {
452 dc.week_sun = Some(ws);
453 }
454 offset += 1;
455
456 let wm = bytes[offset];
458 if wm != u8::MAX {
459 dc.week_mon = Some(wm);
460 }
461 offset += 1;
462
463 let mer_byte = bytes[offset];
465 if mer_byte != 255 {
466 if let Some(m) = Meridiem::from_wire_byte(mer_byte) {
467 dc.meridiem = Some(m);
468 }
469 }
470 offset += 1;
471
472 let unix = i64::from_le_bytes(bytes[offset..offset + 8].try_into().ok()?);
474 if unix != i64::MIN {
475 dc.unix_timestamp_seconds = Some(unix);
476 }
477
478 Some(dc)
479 }
480}