messagepack_core/timestamp/
mod.rs1mod decode;
4mod encode;
5use crate::extension::{ExtensionRef, FixedExtension};
6
7pub(crate) const TIMESTAMP_EXTENSION_TYPE: i8 = -1;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
11pub enum TryFromTimestampError {
12 InvalidType,
14 InvalidDataLength,
16 InvalidData,
18}
19
20impl core::fmt::Display for TryFromTimestampError {
21 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
22 match self {
23 TryFromTimestampError::InvalidType => write!(f, "invalid timestamp extension type"),
24 TryFromTimestampError::InvalidDataLength => write!(f, "invalid timestamp data length"),
25 TryFromTimestampError::InvalidData => write!(f, "invalid timestamp data fields"),
26 }
27 }
28}
29
30impl core::error::Error for TryFromTimestampError {}
31
32#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
35pub struct Timestamp32 {
36 secs: u32,
37}
38
39impl Timestamp32 {
40 pub fn new(seconds: u32) -> Self {
42 Self { secs: seconds }
43 }
44
45 pub fn seconds(&self) -> u32 {
47 self.secs
48 }
49
50 pub(crate) fn to_buf(self) -> [u8; 4] {
51 self.secs.to_be_bytes()
52 }
53
54 pub(crate) fn from_buf(buf: [u8; 4]) -> Self {
55 Self {
56 secs: u32::from_be_bytes(buf),
57 }
58 }
59}
60
61impl TryFrom<ExtensionRef<'_>> for Timestamp32 {
62 type Error = TryFromTimestampError;
63
64 fn try_from(value: ExtensionRef<'_>) -> Result<Self, Self::Error> {
65 if value.r#type != TIMESTAMP_EXTENSION_TYPE {
66 return Err(TryFromTimestampError::InvalidType);
67 }
68
69 let data = value.data;
70 let mut buf = [0u8; 4];
71 if data.len() != buf.len() {
72 return Err(TryFromTimestampError::InvalidDataLength);
73 }
74
75 buf.copy_from_slice(data);
76 Ok(Self::from_buf(buf))
77 }
78}
79
80impl TryFrom<FixedExtension<4>> for Timestamp32 {
81 type Error = TryFromTimestampError;
82
83 fn try_from(value: FixedExtension<4>) -> Result<Self, Self::Error> {
84 value.as_ref().try_into()
85 }
86}
87
88impl From<Timestamp32> for FixedExtension<4> {
89 fn from(value: Timestamp32) -> Self {
90 let buf = value.to_buf();
91 FixedExtension::new_fixed(TIMESTAMP_EXTENSION_TYPE, buf)
92 }
93}
94
95impl From<Timestamp32> for core::time::Duration {
96 fn from(value: Timestamp32) -> Self {
97 core::time::Duration::from_secs(value.seconds().into())
98 }
99}
100
101impl TryFrom<core::time::Duration> for Timestamp32 {
102 type Error = core::num::TryFromIntError;
103 fn try_from(value: core::time::Duration) -> Result<Self, Self::Error> {
104 let sec = value.as_secs();
105 u32::try_from(sec).map(Self::new)
106 }
107}
108
109pub(crate) const TIMESTAMP_NANO_MAX: u32 = 999_999_999;
113
114#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
117pub struct Timestamp64 {
118 data: [u8; 8],
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
123pub enum ConstructTimestampError {
124 ExceedSeconds,
126 ExceedNanos,
128}
129
130impl core::fmt::Display for ConstructTimestampError {
131 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
132 match self {
133 ConstructTimestampError::ExceedSeconds => {
134 write!(f, "seconds exceed representable range")
135 }
136 ConstructTimestampError::ExceedNanos => {
137 write!(f, "nanoseconds exceed {}", TIMESTAMP_NANO_MAX)
138 }
139 }
140 }
141}
142
143impl core::error::Error for ConstructTimestampError {}
144
145impl Timestamp64 {
146 pub fn new(seconds: u64, nanos: u32) -> Result<Self, ConstructTimestampError> {
148 const SECONDS_MAX_LIMIT: u64 = 1 << 34;
149
150 if seconds >= SECONDS_MAX_LIMIT {
151 return Err(ConstructTimestampError::ExceedSeconds);
152 }
153
154 if nanos > TIMESTAMP_NANO_MAX {
155 return Err(ConstructTimestampError::ExceedNanos);
156 }
157
158 let mut buf = [0u8; 8];
159 buf[..].copy_from_slice(&seconds.to_be_bytes());
160
161 let nano = (nanos << 2).to_be_bytes();
162 buf[..3].copy_from_slice(&nano[..3]);
163 buf[3] = (buf[3] & 0b0000_0011) | (nano[3] & 0b1111_1100);
165
166 Ok(Self::from_buf(buf))
167 }
168
169 pub fn nanos(&self) -> u32 {
171 let mut buf = [0u8; 4];
172 buf.copy_from_slice(&self.data[..4]);
173 let nanosec = u32::from_be_bytes(buf);
174 nanosec >> 2
175 }
176
177 pub fn seconds(&self) -> u64 {
179 const MASK: u64 = (1 << 34) - 1;
181 let mut buf = [0u8; 8];
182 buf.copy_from_slice(&self.data[..]);
183 let seconds = u64::from_be_bytes(buf);
184
185 seconds & MASK
186 }
187
188 pub(crate) fn to_buf(self) -> [u8; 8] {
189 self.data
190 }
191
192 pub(crate) fn from_buf(buf: [u8; 8]) -> Self {
193 Self { data: buf }
194 }
195}
196
197impl TryFrom<ExtensionRef<'_>> for Timestamp64 {
198 type Error = TryFromTimestampError;
199
200 fn try_from(value: ExtensionRef<'_>) -> Result<Self, Self::Error> {
201 if value.r#type != TIMESTAMP_EXTENSION_TYPE {
202 return Err(TryFromTimestampError::InvalidType);
203 }
204
205 let data = value.data;
206 let mut buf = [0u8; 8];
207 if data.len() != buf.len() {
208 return Err(TryFromTimestampError::InvalidDataLength);
209 }
210
211 buf.copy_from_slice(data);
212 let decoded = Self::from_buf(buf);
213 Self::new(decoded.seconds(), decoded.nanos())
215 .map_err(|_| TryFromTimestampError::InvalidData)
216 }
217}
218
219impl TryFrom<FixedExtension<8>> for Timestamp64 {
220 type Error = TryFromTimestampError;
221
222 fn try_from(value: FixedExtension<8>) -> Result<Self, Self::Error> {
223 value.as_ref().try_into()
224 }
225}
226
227impl From<Timestamp64> for FixedExtension<8> {
228 fn from(value: Timestamp64) -> Self {
229 let buf = value.to_buf();
230 FixedExtension::new_fixed(TIMESTAMP_EXTENSION_TYPE, buf)
231 }
232}
233
234impl From<Timestamp64> for core::time::Duration {
235 fn from(value: Timestamp64) -> Self {
236 let sec = value.seconds();
237 let nano = value.nanos();
238 core::time::Duration::from_secs(sec) + core::time::Duration::from_nanos(nano.into())
239 }
240}
241
242impl TryFrom<core::time::Duration> for Timestamp64 {
243 type Error = ConstructTimestampError;
244
245 fn try_from(value: core::time::Duration) -> Result<Self, Self::Error> {
246 let secs = value.as_secs();
247 let nanos = value.subsec_nanos();
248 Timestamp64::new(secs, nanos)
249 }
250}
251
252#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
255pub struct Timestamp96 {
256 nanos: u32,
257 secs: i64,
258}
259
260impl Timestamp96 {
261 pub fn new(seconds: i64, nanoseconds: u32) -> Result<Self, ConstructTimestampError> {
263 if nanoseconds > TIMESTAMP_NANO_MAX {
264 return Err(ConstructTimestampError::ExceedNanos);
265 }
266 Ok(Self {
267 nanos: nanoseconds,
268 secs: seconds,
269 })
270 }
271
272 pub fn nanos(&self) -> u32 {
274 self.nanos
275 }
276
277 pub fn seconds(&self) -> i64 {
279 self.secs
280 }
281
282 pub(crate) fn to_buf(self) -> [u8; 12] {
283 let mut buf = [0u8; 12];
284 buf[..4].copy_from_slice(&self.nanos.to_be_bytes());
285 buf[4..].copy_from_slice(&self.secs.to_be_bytes());
286
287 buf
288 }
289
290 pub(crate) fn from_buf(buf: [u8; 12]) -> Self {
291 let mut nano = [0u8; 4];
292 nano.copy_from_slice(&buf[..4]);
293
294 let mut second = [0u8; 8];
295 second.copy_from_slice(&buf[4..]);
296
297 Self {
298 nanos: u32::from_be_bytes(nano),
299 secs: i64::from_be_bytes(second),
300 }
301 }
302}
303
304impl TryFrom<ExtensionRef<'_>> for Timestamp96 {
305 type Error = TryFromTimestampError;
306
307 fn try_from(value: ExtensionRef<'_>) -> Result<Self, Self::Error> {
308 if value.r#type != TIMESTAMP_EXTENSION_TYPE {
309 return Err(TryFromTimestampError::InvalidType);
310 }
311
312 let data = value.data;
313 let mut buf = [0u8; 12];
314 if data.len() != buf.len() {
315 return Err(TryFromTimestampError::InvalidDataLength);
316 }
317
318 buf.copy_from_slice(data);
319 let decoded = Self::from_buf(buf);
320 Self::new(decoded.seconds(), decoded.nanos())
322 .map_err(|_| TryFromTimestampError::InvalidData)
323 }
324}
325
326impl TryFrom<FixedExtension<12>> for Timestamp96 {
327 type Error = TryFromTimestampError;
328
329 fn try_from(value: FixedExtension<12>) -> Result<Self, Self::Error> {
330 value.as_ref().try_into()
331 }
332}
333
334impl From<Timestamp96> for FixedExtension<12> {
335 fn from(value: Timestamp96) -> Self {
336 let buf = value.to_buf();
337 FixedExtension::new_fixed(TIMESTAMP_EXTENSION_TYPE, buf)
338 }
339}
340
341impl TryFrom<Timestamp96> for core::time::Duration {
342 type Error = core::num::TryFromIntError;
343
344 fn try_from(value: Timestamp96) -> Result<Self, Self::Error> {
345 let secs = u64::try_from(value.seconds())?;
346 let nanos = value.nanos();
347
348 Ok(core::time::Duration::from_secs(secs) + core::time::Duration::from_nanos(nanos.into()))
349 }
350}
351
352impl TryFrom<core::time::Duration> for Timestamp96 {
353 type Error = ConstructTimestampError;
354
355 fn try_from(value: core::time::Duration) -> Result<Self, Self::Error> {
356 let secs =
357 i64::try_from(value.as_secs()).map_err(|_| ConstructTimestampError::ExceedSeconds)?;
358 let nanos = value.subsec_nanos();
359 Self::new(secs, nanos)
360 }
361}
362
363#[cfg(test)]
364mod duration_tests {
365 use super::*;
366 use rstest::rstest;
367
368 #[rstest]
369 fn duration_to_timestamp32_roundtrip_within_range() {
370 let d = core::time::Duration::from_secs(123);
371 let ts32 = Timestamp32::try_from(d).unwrap();
372 assert_eq!(ts32.seconds(), 123);
373 let back: core::time::Duration = ts32.into();
374 assert_eq!(back.as_secs(), 123);
375 assert_eq!(back.subsec_nanos(), 0);
376 }
377
378 #[rstest]
379 fn duration_to_timestamp64_roundtrip() {
380 let d = core::time::Duration::from_secs(1_234_567) + core::time::Duration::from_nanos(890);
381 let ts64 = Timestamp64::try_from(d).unwrap();
382 assert_eq!(ts64.seconds(), 1_234_567);
383 assert_eq!(ts64.nanos(), 890);
384 let back: core::time::Duration = ts64.into();
385 assert_eq!(back, d);
386 }
387
388 #[rstest]
389 fn timestamp96_to_duration_fails_on_negative() {
390 let ts96 = Timestamp96::new(-1, 0).unwrap();
391 let res: Result<core::time::Duration, core::num::TryFromIntError> =
392 core::time::Duration::try_from(ts96);
393 assert!(res.is_err());
394 }
395
396 #[rstest]
397 fn duration_to_timestamp96_roundtrip() {
398 let d = core::time::Duration::from_secs(12_345) + core::time::Duration::from_nanos(678_901);
399 let ts = Timestamp96::try_from(d).unwrap();
400 assert_eq!(ts.seconds(), 12_345);
401 assert_eq!(ts.nanos(), 678_901);
402 let back = core::time::Duration::try_from(ts).unwrap();
403 assert_eq!(back, d);
404 }
405
406 #[rstest]
407 fn timestamp64_new_rejects_invalid_nanos() {
408 let err = Timestamp64::new(0, 1_000_000_000).unwrap_err();
409 assert_eq!(err, ConstructTimestampError::ExceedNanos);
410 }
411
412 #[rstest]
413 fn timestamp96_new_rejects_invalid_nanos() {
414 let err = Timestamp96::new(0, 1_000_000_000).unwrap_err();
415 assert_eq!(err, ConstructTimestampError::ExceedNanos);
416 }
417}
418
419