#[repr(C)]
#[derive(Debug, Clone, Copy, Default, bytemuck::Zeroable, bytemuck::Pod)]
pub struct UdfTimestamp {
pub type_and_tz: u16,
pub year: u16,
pub month: u8,
pub day: u8,
pub hour: u8,
pub minute: u8,
pub second: u8,
pub centiseconds: u8,
pub hundreds_of_microseconds: u8,
pub microseconds: u8,
}
impl UdfTimestamp {
pub fn timezone_type(&self) -> TimezoneType {
match (self.type_and_tz >> 12) & 0x0F {
0 => TimezoneType::Utc,
1 => TimezoneType::Local,
2 => TimezoneType::Agreement,
_ => TimezoneType::Reserved,
}
}
pub fn timezone_offset(&self) -> Option<i16> {
let tz_type = self.timezone_type();
if matches!(tz_type, TimezoneType::Utc | TimezoneType::Local) {
let offset = (self.type_and_tz & 0x0FFF) as i16;
let offset = if offset & 0x0800 != 0 {
offset | !0x0FFF
} else {
offset
};
Some(offset)
} else {
None
}
}
pub fn is_valid(&self) -> bool {
self.month >= 1
&& self.month <= 12
&& self.day >= 1
&& self.day <= 31
&& self.hour <= 23
&& self.minute <= 59
&& self.second <= 59
&& self.centiseconds <= 99
&& self.hundreds_of_microseconds <= 99
&& self.microseconds <= 99
}
}
impl core::fmt::Display for UdfTimestamp {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"{:04}-{:02}-{:02} {:02}:{:02}:{:02}",
self.year, self.month, self.day, self.hour, self.minute, self.second
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TimezoneType {
Utc,
Local,
Agreement,
Reserved,
}
impl core::fmt::Display for TimezoneType {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Utc => write!(f, "UTC"),
Self::Local => write!(f, "Local"),
Self::Agreement => write!(f, "Agreement"),
Self::Reserved => write!(f, "Reserved"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
static_assertions::const_assert_eq!(size_of::<UdfTimestamp>(), 12);
#[test]
fn test_timestamp_default() {
let ts = UdfTimestamp::default();
assert!(!ts.is_valid()); }
#[test]
fn test_timestamp_valid() {
let ts = UdfTimestamp {
type_and_tz: 0x1000, year: 2024,
month: 1,
day: 15,
hour: 10,
minute: 30,
second: 45,
centiseconds: 50,
hundreds_of_microseconds: 25,
microseconds: 10,
};
assert!(ts.is_valid());
assert_eq!(ts.timezone_type(), TimezoneType::Local);
}
#[test]
fn test_timezone_offset() {
let ts = UdfTimestamp {
type_and_tz: 0x0000, ..Default::default()
};
assert_eq!(ts.timezone_offset(), Some(0));
let ts = UdfTimestamp {
type_and_tz: 0x014A, ..Default::default()
};
assert_eq!(ts.timezone_offset(), Some(330));
}
}