1#[repr(C)]
7#[derive(Debug, Clone, Copy, Default, bytemuck::Zeroable, bytemuck::Pod)]
8pub struct UdfTimestamp {
9 pub type_and_tz: u16,
13 pub year: u16,
15 pub month: u8,
17 pub day: u8,
19 pub hour: u8,
21 pub minute: u8,
23 pub second: u8,
25 pub centiseconds: u8,
27 pub hundreds_of_microseconds: u8,
29 pub microseconds: u8,
31}
32
33impl UdfTimestamp {
34 #[cfg(all(feature = "alloc", any(feature = "sync", feature = "async")))]
35 pub(crate) fn into_native(mut self) -> Self {
36 self.type_and_tz = self.type_and_tz.to_le();
37 self.year = self.year.to_le();
38 self
39 }
40
41 pub fn timezone_type(&self) -> TimezoneType {
43 match (self.type_and_tz >> 12) & 0x0F {
44 0 => TimezoneType::Utc,
45 1 => TimezoneType::Local,
46 2 => TimezoneType::Agreement,
47 _ => TimezoneType::Reserved,
48 }
49 }
50
51 pub fn timezone_offset(&self) -> Option<i16> {
55 let tz_type = self.timezone_type();
56 if matches!(tz_type, TimezoneType::Utc | TimezoneType::Local) {
57 let offset = (self.type_and_tz & 0x0FFF) as i16;
58 let offset = if offset & 0x0800 != 0 {
60 offset | !0x0FFF
61 } else {
62 offset
63 };
64 Some(offset)
65 } else {
66 None
67 }
68 }
69
70 pub fn is_valid(&self) -> bool {
72 self.month >= 1
73 && self.month <= 12
74 && self.day >= 1
75 && self.day <= 31
76 && self.hour <= 23
77 && self.minute <= 59
78 && self.second <= 59
79 && self.centiseconds <= 99
80 && self.hundreds_of_microseconds <= 99
81 && self.microseconds <= 99
82 }
83}
84
85impl core::fmt::Display for UdfTimestamp {
86 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
87 write!(
88 f,
89 "{:04}-{:02}-{:02} {:02}:{:02}:{:02}",
90 self.year, self.month, self.day, self.hour, self.minute, self.second
91 )
92 }
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97pub enum TimezoneType {
98 Utc,
100 Local,
102 Agreement,
104 Reserved,
106}
107
108impl core::fmt::Display for TimezoneType {
109 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
110 match self {
111 Self::Utc => write!(f, "UTC"),
112 Self::Local => write!(f, "Local"),
113 Self::Agreement => write!(f, "Agreement"),
114 Self::Reserved => write!(f, "Reserved"),
115 }
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 static_assertions::const_assert_eq!(size_of::<UdfTimestamp>(), 12);
124
125 #[test]
126 fn test_timestamp_default() {
127 let ts = UdfTimestamp::default();
128 assert!(!ts.is_valid()); }
130
131 #[test]
132 fn test_timestamp_valid() {
133 let ts = UdfTimestamp {
134 type_and_tz: 0x1000, year: 2024,
136 month: 1,
137 day: 15,
138 hour: 10,
139 minute: 30,
140 second: 45,
141 centiseconds: 50,
142 hundreds_of_microseconds: 25,
143 microseconds: 10,
144 };
145 assert!(ts.is_valid());
146 assert_eq!(ts.timezone_type(), TimezoneType::Local);
147 }
148
149 #[test]
150 fn test_timezone_offset() {
151 let ts = UdfTimestamp {
153 type_and_tz: 0x0000, ..Default::default()
155 };
156 assert_eq!(ts.timezone_offset(), Some(0));
157
158 let ts = UdfTimestamp {
160 type_and_tz: 0x014A, ..Default::default()
162 };
163 assert_eq!(ts.timezone_offset(), Some(330));
164 }
165}