msgpack_timestamp/
lib.rs

1#![no_std]
2//! [This `no_std` crate](https://sr.ht/~nabijaczleweli/msgpack_datetime)
3//! implements parsing of
4//! [MessagePack](https://msgpack.org)
5//! [ext -1](https://github.com/msgpack/msgpack/blob/master/spec.md#timestamp-extension-type) types.
6//!
7//! ```
8//! let ts = msgpack_timestamp::Timestamp { sec: 1765371205, nsec: 0 }; // 2025-12-10 12:53:25Z
9//! assert_eq!(&*msgpack_timestamp::pack(&ts), &[105, 57, 109, 69]);
10//! assert_eq!(msgpack_timestamp::unpack(&[105, 57, 109, 69]), Some(ts));
11//! ```
12
13use core::ops::Deref;
14
15/// `-1`
16pub const EXTENSION: i8 = -1;
17
18/// `[u8; 0..=12]`
19#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
20pub struct Buffer(BufferInner);
21#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
22enum BufferInner {
23    Four([u8; 4]),
24    Eight([u8; 8]),
25    Twelve([u8; 12]),
26}
27impl Deref for Buffer {
28    type Target = [u8];
29
30    fn deref(&self) -> &Self::Target {
31        match &self.0 {
32            BufferInner::Four(a) => a,
33            BufferInner::Eight(a) => a,
34            BufferInner::Twelve(a) => a,
35        }
36    }
37}
38impl AsRef<[u8]> for Buffer {
39    fn as_ref(&self) -> &[u8] {
40        self.deref()
41    }
42}
43
44#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
45pub struct Timestamp {
46    pub sec: i64,
47    pub nsec: u32,
48}
49
50/// Serialise timestamp into MessagePack timestamp -1 extension data
51pub fn pack(stamp: &Timestamp) -> Buffer {
52    let upper = stamp.sec >> 34;
53    if upper == 0 {
54        let data = (stamp.nsec as u64) << 34 | (stamp.sec as u64);
55        if data & 0xFFFF_FFFF_0000_0000 == 0 {
56            Buffer(BufferInner::Four((data as u32).to_be_bytes()))
57        } else {
58            Buffer(BufferInner::Eight(data.to_be_bytes()))
59        }
60    } else {
61        let mut ret = [0u8; 12];
62        let (ns, s) = ret.split_at_mut(4);
63        ns.copy_from_slice(&stamp.nsec.to_be_bytes());
64        s.copy_from_slice(&stamp.sec.to_be_bytes());
65        Buffer(BufferInner::Twelve(ret))
66    }
67}
68
69/// Parse 4/8/12-byte MessagePack timestamp -1 extension
70///
71/// Returns `None` if the input slice is the wrong length.
72pub fn unpack(buf: &[u8]) -> Option<Timestamp> {
73    // https://github.com/msgpack/msgpack/blob/master/spec.md#timestamp-extension-type
74    if let Ok(four) = buf.try_into() {
75        Some(Timestamp {
76            sec: u32::from_be_bytes(four) as _,
77            nsec: 0,
78        })
79    } else if let Ok(eight) = buf.try_into() {
80        let data = u64::from_be_bytes(eight);
81        Some(Timestamp {
82            sec: (data & 0x0000_0003_FFFF_FFFF) as _,
83            nsec: (data >> 34) as _,
84        })
85    } else if let Some((four, rest)) = buf.split_at_checked(4)
86        && let Ok(four) = four.try_into()
87        && let Ok(eight) = rest.try_into()
88    {
89        Some(Timestamp {
90            sec: i64::from_be_bytes(eight),
91            nsec: u32::from_be_bytes(four),
92        })
93    } else {
94        None
95    }
96}