mod from_ccsds_bin;
mod from_ccsds_str;
mod from_str;
mod to_ccsds_bin;
mod to_deep_time;
#[cfg(feature = "alloc")]
mod to_ccsds_str;
#[cfg(feature = "chrono")]
mod to_chrono;
#[cfg(feature = "jiff")]
mod to_jiff;
use crate::{AsciiStr, Scale};
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "js", derive(tsify::Tsify))]
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct TimeParts {
pub year: Option<i64>,
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>,
pub iana_name: Option<AsciiStr<49>>,
pub is_leap_second: bool,
pub scale: Scale,
pub weekday: Option<Weekday>,
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>,
pub unix_timestamp_seconds: Option<i64>, }
impl TimeParts {
#[inline]
pub fn new_utc() -> Self {
Self {
scale: Scale::UTC,
..Default::default()
}
}
#[inline]
pub fn set_iana_name(&mut self, name: Option<&str>) -> &mut Self {
self.iana_name = name.and_then(|s| AsciiStr::try_from_str(s).ok());
self
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "js", derive(tsify::Tsify))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Meridiem {
#[default]
AM,
PM,
}
#[cfg(feature = "wire")]
impl Meridiem {
pub const WIRE_SIZE: usize = 1;
#[inline]
pub const fn to_wire_byte(self) -> u8 {
match self {
Meridiem::AM => 0,
Meridiem::PM => 1,
}
}
#[inline]
pub const fn from_wire_byte(b: u8) -> Option<Self> {
match b {
0 => Some(Meridiem::AM),
1 => Some(Meridiem::PM),
_ => None,
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "js", derive(tsify::Tsify))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Weekday {
#[default]
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
}
impl Weekday {
#[inline]
pub const fn from_sunday_zero_offset(n: i8) -> Option<Self> {
match n {
0 => Some(Weekday::Sunday),
1 => Some(Weekday::Monday),
2 => Some(Weekday::Tuesday),
3 => Some(Weekday::Wednesday),
4 => Some(Weekday::Thursday),
5 => Some(Weekday::Friday),
6 => Some(Weekday::Saturday),
_ => None,
}
}
#[inline]
pub const fn from_monday_one_offset(n: i8) -> Option<Self> {
match n {
1 => Some(Weekday::Monday),
2 => Some(Weekday::Tuesday),
3 => Some(Weekday::Wednesday),
4 => Some(Weekday::Thursday),
5 => Some(Weekday::Friday),
6 => Some(Weekday::Saturday),
7 => Some(Weekday::Sunday),
_ => None,
}
}
#[inline]
pub const fn wk_sun(self) -> u8 {
match self {
Weekday::Sunday => 0,
Weekday::Monday => 1,
Weekday::Tuesday => 2,
Weekday::Wednesday => 3,
Weekday::Thursday => 4,
Weekday::Friday => 5,
Weekday::Saturday => 6,
}
}
#[inline]
pub const fn wk_mon(self) -> u8 {
match self {
Weekday::Monday => 1,
Weekday::Tuesday => 2,
Weekday::Wednesday => 3,
Weekday::Thursday => 4,
Weekday::Friday => 5,
Weekday::Saturday => 6,
Weekday::Sunday => 7,
}
}
}
#[cfg(feature = "wire")]
impl Weekday {
pub const WIRE_SIZE: usize = 1;
#[inline]
pub const fn to_wire_byte(self) -> u8 {
self.wk_sun()
}
#[inline]
pub const fn from_wire_byte(b: u8) -> Option<Self> {
Self::from_sunday_zero_offset(b as i8)
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "js", derive(tsify::Tsify))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Offset {
#[default]
Utc,
None,
Fixed(i32),
}
#[cfg(feature = "wire")]
impl Offset {
pub const WIRE_SIZE: usize = 5;
pub fn to_wire_bytes(&self) -> [u8; Self::WIRE_SIZE] {
let mut buf = [0u8; Self::WIRE_SIZE];
match self {
Offset::Utc => buf[0] = 0,
Offset::None => buf[0] = 1,
Offset::Fixed(offset) => {
buf[0] = 2;
buf[1..5].copy_from_slice(&offset.to_le_bytes());
}
}
buf
}
pub fn from_wire_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() != Self::WIRE_SIZE {
return None;
}
match bytes[0] {
0 => Some(Offset::Utc),
1 => Some(Offset::None),
2 => {
let offset = i32::from_le_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]);
Some(Offset::Fixed(offset))
}
_ => None,
}
}
}
#[cfg(feature = "wire")]
impl TimeParts {
pub const WIRE_VERSION: u8 = 1;
pub const WIRE_SIZE: usize = 120;
pub fn to_wire_bytes(&self) -> [u8; Self::WIRE_SIZE] {
let mut buf = [0u8; Self::WIRE_SIZE];
buf[0] = Self::WIRE_VERSION;
let mut offset = 1usize;
let year = self.year.unwrap_or(i64::MIN);
buf[offset..offset + 8].copy_from_slice(&year.to_le_bytes());
offset += 8;
buf[offset] = self.month.unwrap_or(u8::MAX);
offset += 1;
buf[offset] = self.day.unwrap_or(u8::MAX);
offset += 1;
buf[offset] = self.hour.unwrap_or(u8::MAX);
offset += 1;
buf[offset] = self.minute.unwrap_or(u8::MAX);
offset += 1;
buf[offset] = self.second.unwrap_or(u8::MAX);
offset += 1;
let attos = self.attos.unwrap_or(u64::MAX);
buf[offset..offset + 8].copy_from_slice(&attos.to_le_bytes());
offset += 8;
let offset_bytes = self.offset.unwrap_or_default().to_wire_bytes();
buf[offset..offset + 5].copy_from_slice(&offset_bytes);
offset += 5;
if let Some(name) = &self.iana_name {
let name_bytes = name.to_wire_bytes();
buf[offset..offset + 49].copy_from_slice(&name_bytes);
}
offset += 49;
buf[offset] = if self.is_leap_second { 1 } else { 0 };
offset += 1;
buf[offset] = self.scale as u8;
offset += 1;
buf[offset] = self.weekday.map_or(255, |w| w.to_wire_byte());
offset += 1;
let doy = self.day_of_year.unwrap_or(u16::MAX);
buf[offset..offset + 2].copy_from_slice(&doy.to_le_bytes());
offset += 2;
let iso_y = self.iso_week_year.unwrap_or(i64::MIN);
buf[offset..offset + 8].copy_from_slice(&iso_y.to_le_bytes());
offset += 8;
buf[offset] = self.iso_week.unwrap_or(u8::MAX);
offset += 1;
buf[offset] = self.week_sun.unwrap_or(u8::MAX);
offset += 1;
buf[offset] = self.week_mon.unwrap_or(u8::MAX);
offset += 1;
buf[offset] = self.meridiem.map_or(255, |m| m.to_wire_byte());
offset += 1;
let unix = self.unix_timestamp_seconds.unwrap_or(i64::MIN);
buf[offset..offset + 8].copy_from_slice(&unix.to_le_bytes());
buf
}
pub fn from_wire_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() != Self::WIRE_SIZE {
return None;
}
if bytes[0] != Self::WIRE_VERSION {
return None;
}
let mut dc = TimeParts::default();
let mut offset = 1usize;
let year = i64::from_le_bytes(bytes[offset..offset + 8].try_into().ok()?);
if year != i64::MIN {
dc.year = Some(year);
}
offset += 8;
let m = bytes[offset];
if m != u8::MAX {
dc.month = Some(m);
}
offset += 1;
let d = bytes[offset];
if d != u8::MAX {
dc.day = Some(d);
}
offset += 1;
let h = bytes[offset];
if h != u8::MAX {
dc.hour = Some(h);
}
offset += 1;
let min = bytes[offset];
if min != u8::MAX {
dc.minute = Some(min);
}
offset += 1;
let sec = bytes[offset];
if sec != u8::MAX {
dc.second = Some(sec);
}
offset += 1;
let attos = u64::from_le_bytes(bytes[offset..offset + 8].try_into().ok()?);
if attos != u64::MAX {
dc.attos = Some(attos);
}
offset += 8;
if let Some(offset) = Offset::from_wire_bytes(&bytes[offset..offset + 5]) {
dc.offset = Some(offset);
}
offset += 5;
let iana_bytes = &bytes[offset..offset + 49];
if let Some(name) = AsciiStr::<49>::from_wire_bytes(iana_bytes) {
if !name.is_empty() {
dc.iana_name = Some(name);
}
}
offset += 49;
dc.is_leap_second = bytes[offset] != 0;
offset += 1;
dc.scale = Scale::from_u8(bytes[offset]);
offset += 1;
let wd_byte = bytes[offset];
if wd_byte != 255 {
if let Some(wd) = Weekday::from_wire_byte(wd_byte) {
dc.weekday = Some(wd);
}
}
offset += 1;
let doy = u16::from_le_bytes(bytes[offset..offset + 2].try_into().ok()?);
if doy != u16::MAX {
dc.day_of_year = Some(doy);
}
offset += 2;
let iso_y = i64::from_le_bytes(bytes[offset..offset + 8].try_into().ok()?);
if iso_y != i64::MIN {
dc.iso_week_year = Some(iso_y);
}
offset += 8;
let iw = bytes[offset];
if iw != u8::MAX {
dc.iso_week = Some(iw);
}
offset += 1;
let ws = bytes[offset];
if ws != u8::MAX {
dc.week_sun = Some(ws);
}
offset += 1;
let wm = bytes[offset];
if wm != u8::MAX {
dc.week_mon = Some(wm);
}
offset += 1;
let mer_byte = bytes[offset];
if mer_byte != 255 {
if let Some(m) = Meridiem::from_wire_byte(mer_byte) {
dc.meridiem = Some(m);
}
}
offset += 1;
let unix = i64::from_le_bytes(bytes[offset..offset + 8].try_into().ok()?);
if unix != i64::MIN {
dc.unix_timestamp_seconds = Some(unix);
}
Some(dc)
}
}