1#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
7#[cfg_attr(feature = "defmt", derive(defmt::Format))]
8pub struct Timestamp {
9 pub(crate) bits: u64,
10}
11
12impl Timestamp {
13 #[cfg(feature = "chrono")]
14 fn new(secs: u64, nanos: u64) -> Self {
15 let upper: u32 = secs
16 .try_into()
17 .unwrap_or_else(|_| secs.saturating_sub(1 << 32).try_into().unwrap_or(u32::MAX));
18 let lower: u32 = ((nanos * u64::from(u32::MAX)) / 1_000_000_000) as u32;
19 Self {
20 bits: ((upper as u64) << 32) | (lower as u64),
21 }
22 }
23
24 #[must_use]
25 #[cfg(any(feature = "chrono", feature = "time"))]
26 fn secs(&self) -> i64 {
27 let seconds_bits: u32 = (self.bits >> 32) as u32;
28 if seconds_bits & 0x8000_0000 != 0 {
30 i64::from(seconds_bits)
31 } else {
32 i64::from(seconds_bits) + i64::from(u32::MAX) + 1
35 }
36 }
37
38 #[must_use]
39 #[cfg(any(feature = "chrono", feature = "time"))]
40 fn nanos(&self) -> u32 {
41 ((self.bits & 0xFFFF_FFFF) * 1_000_000_000 / u64::from(u32::MAX)) as u32
43 }
44
45 #[must_use]
47 pub const fn to_bits(self) -> u64 {
48 self.bits
49 }
50
51 #[must_use]
53 pub const fn is_zero(&self) -> bool {
54 self.bits == 0
55 }
56}
57
58#[cfg(feature = "chrono")]
59fn origin_chrono() -> chrono::NaiveDateTime {
60 unwrap!(unwrap!(chrono::NaiveDate::from_ymd_opt(1900, 1, 1)).and_hms_opt(0, 0, 0))
62}
63
64#[derive(Debug, Copy, Clone, PartialEq, Eq)]
66#[cfg_attr(feature = "defmt", derive(defmt::Format))]
67pub struct TimestampError(pub(crate) ());
68
69#[cfg(feature = "chrono")]
70impl TryFrom<chrono::naive::NaiveDateTime> for Timestamp {
71 type Error = TimestampError;
72
73 fn try_from(ndt: chrono::naive::NaiveDateTime) -> Result<Self, Self::Error> {
74 let elapsed: chrono::TimeDelta = ndt.signed_duration_since(origin_chrono());
75 let secs: i64 = elapsed.num_seconds();
76 let secs_delta: chrono::TimeDelta =
77 chrono::TimeDelta::try_seconds(secs).ok_or(TimestampError(()))?;
78 let nanos: u64 = elapsed
79 .checked_sub(&secs_delta)
80 .unwrap_or_else(|| chrono::TimeDelta::try_seconds(0).unwrap())
81 .num_nanoseconds()
82 .unwrap_or(0)
83 .try_into()
84 .map_err(|_| TimestampError(()))?;
85 let secs: u64 = secs.try_into().map_err(|_| TimestampError(()))?;
86
87 Ok(Self::new(secs, nanos))
88 }
89}
90
91#[cfg(feature = "chrono")]
92impl TryFrom<Timestamp> for chrono::naive::NaiveDateTime {
93 type Error = TimestampError;
94
95 fn try_from(timestamp: Timestamp) -> Result<Self, Self::Error> {
96 let secs_delta: chrono::TimeDelta =
97 chrono::TimeDelta::try_seconds(timestamp.secs()).ok_or(TimestampError(()))?;
98 origin_chrono()
99 .checked_add_signed(secs_delta)
100 .ok_or(TimestampError(()))?
101 .checked_add_signed(chrono::TimeDelta::nanoseconds(timestamp.nanos().into()))
102 .ok_or(TimestampError(()))
103 }
104}
105
106#[cfg(feature = "time")]
107impl TryFrom<Timestamp> for time::PrimitiveDateTime {
108 type Error = TimestampError;
109
110 fn try_from(timestamp: Timestamp) -> Result<Self, Self::Error> {
111 const ORIGIN: time::PrimitiveDateTime = {
112 const DATE: time::Date =
113 match time::Date::from_calendar_date(1900, time::Month::January, 1) {
114 Ok(date) => date,
115 Err(_) => ::core::panic!("invalid date"),
116 };
117 const TIME: time::Time = match time::Time::from_hms(0, 0, 0) {
118 Ok(time) => time,
119 Err(_) => ::core::panic!("invalid time"),
120 };
121 time::PrimitiveDateTime::new(DATE, TIME)
122 };
123
124 ORIGIN
125 .checked_add(time::Duration::seconds(timestamp.secs()))
126 .ok_or(TimestampError(()))?
127 .checked_add(time::Duration::nanoseconds(timestamp.nanos().into()))
128 .ok_or(TimestampError(()))
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::Timestamp;
135 use chrono::naive::{NaiveDate, NaiveDateTime, NaiveTime};
136 use time::PrimitiveDateTime;
137
138 #[test]
139 fn chrono() {
140 let timestamp: Timestamp = Timestamp {
141 bits: 0xe5_fd_82_24_23_ec_4b_12,
142 };
143
144 let ndt: NaiveDateTime = timestamp.try_into().unwrap();
145
146 let expected_date: NaiveDate = NaiveDate::from_ymd_opt(2022, 4, 10).unwrap();
147 let expected_time: NaiveTime = NaiveTime::from_hms_nano_opt(16, 19, 48, 140324298).unwrap();
148 let expected_datetime: NaiveDateTime = NaiveDateTime::new(expected_date, expected_time);
149
150 core::assert_eq!(ndt, expected_datetime);
151
152 let timestamp_converted: Timestamp = ndt.try_into().unwrap();
153
154 core::assert_eq!(timestamp.secs(), timestamp_converted.secs());
155 let nanos_diff: u32 = timestamp.nanos().abs_diff(timestamp_converted.nanos());
156 core::assert!(nanos_diff <= 1, "nanos_diff={nanos_diff}");
157 }
158
159 #[test]
160 fn time() {
161 let timestamp: Timestamp = Timestamp {
162 bits: 0xe5_fd_82_24_23_ec_4b_12,
163 };
164
165 let pdt: PrimitiveDateTime = timestamp.try_into().unwrap();
166
167 core::assert_eq!(pdt.year(), 2022);
168 core::assert_eq!(pdt.month(), time::Month::April);
169 core::assert_eq!(pdt.day(), 10);
170 core::assert_eq!(pdt.hour(), 16);
171 core::assert_eq!(pdt.minute(), 19);
172 core::assert_eq!(pdt.second(), 48);
173 }
174
175 #[test]
176 fn chrono_zero() {
177 let timestamp: Timestamp = Timestamp { bits: 0 };
178
179 let ndt: NaiveDateTime = timestamp.try_into().unwrap();
180 let expected: NaiveDateTime = NaiveDate::from_ymd_opt(2036, 2, 7)
181 .unwrap()
182 .and_hms_opt(6, 28, 16)
183 .unwrap();
184
185 core::assert_eq!(ndt, expected);
186 }
187
188 #[test]
189 fn time_zero() {
190 let timestamp: Timestamp = Timestamp { bits: 0 };
191
192 let date: time::Date =
193 time::Date::from_calendar_date(2036, time::Month::February, 7).unwrap();
194 let time: time::Time = time::Time::from_hms(6, 28, 16).unwrap();
195 let expected: PrimitiveDateTime = PrimitiveDateTime::new(date, time);
196
197 let pdt: PrimitiveDateTime = timestamp.try_into().unwrap();
198
199 core::assert_eq!(pdt, expected);
200 }
201}