msgpack-time 1.0.0

A tiny library for converting from RMPV's MessagePack-encoded datetime objects to time-rs' UtcDateTime.
Documentation
#![warn(missing_docs)]
#![doc = include_str!("../README.md")]

use std::fmt::{Debug, Display};
use std::time::{Duration, SystemTime};

use rmpv::Value;
use time::UtcDateTime;

/// Trait providing a function to convert the object to a [UtcDateTime].
pub trait ToDateTime {
    /// Converts the object into a [UtcDateTime], or throws an [Error].
    fn as_datetime(&self) -> Result<UtcDateTime, Error>;
}

impl ToDateTime for Value {
    fn as_datetime(&self) -> Result<UtcDateTime, Error> {
        let ext = self.as_ext().ok_or(Error::NotExt)?;
        if ext.0 != -1 {
            return Err(Error::ExtNotDateTime);
        }

        let datetime_bytes = ext.1;

        match datetime_bytes.len() {
            4 => {
                let s = i64::from(u32::from_be_bytes(
                    datetime_bytes[0..4]
                        .iter()
                        .as_slice()
                        .try_into()
                        .map_err(|_| Error::InvalidLength)?,
                ));
                UtcDateTime::from_unix_timestamp(s).map_err(|_| Error::OutOfRange)
            }
            8 => {
                let d = u64::from_be_bytes(
                    datetime_bytes[0..8]
                        .iter()
                        .as_slice()
                        .try_into()
                        .map_err(|_| Error::InvalidLength)?,
                );
                let s = d & 0x00000003ffffffff;
                let n = d >> 34;
                let s_dur = Duration::from_secs(s);
                let n_dur = Duration::from_nanos(n);
                Ok(UtcDateTime::from(
                    SystemTime::UNIX_EPOCH
                        .checked_add(s_dur)
                        .ok_or(Error::OutOfRange)?
                        .checked_add(n_dur)
                        .ok_or(Error::OutOfRange)?,
                ))
            }
            12 => {
                let n = u32::from_be_bytes(
                    datetime_bytes[0..4]
                        .iter()
                        .as_slice()
                        .try_into()
                        .map_err(|_| Error::InvalidLength)?,
                );
                let s = i64::from_be_bytes(
                    datetime_bytes[5..12]
                        .iter()
                        .as_slice()
                        .try_into()
                        .map_err(|_| Error::InvalidLength)?,
                );
                let s_dur = Duration::from_secs(s.unsigned_abs());
                let n_dur = Duration::from_nanos(u64::from(n));
                let mut systime = SystemTime::UNIX_EPOCH
                    .checked_add(n_dur)
                    .ok_or(Error::OutOfRange)?;
                if s.is_negative() {
                    systime = systime.checked_sub(s_dur).ok_or(Error::OutOfRange)?;
                } else {
                    systime = systime.checked_add(s_dur).ok_or(Error::OutOfRange)?;
                }
                Ok(UtcDateTime::from(systime))
            }
            _ => Err(Error::InvalidLength),
        }
    }
}

/// Errors thrown by [ToDateTime::as_datetime].
#[derive(Debug)]
pub enum Error {
    /// The provided [Value] is not a [Value::Ext].
    NotExt,
    /// The provided [Value::Ext] does not represent a Datetime.
    ExtNotDateTime,
    /// The provided data is not one of the defined lengths for MessagePack Datetime values.
    InvalidLength,
    /// The provided date is out of the acceptible range for a [UtcDateTime].
    OutOfRange,
}
impl Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::NotExt => write!(f, "Value is not an Ext"),
            Self::ExtNotDateTime => write!(f, "Ext is not a Datetime"),
            Self::InvalidLength => {
                write!(f, "Ext value is an invalid length for MessagePack Datetime")
            }
            Self::OutOfRange => write!(f, "Ext value is out of range for UtcDateTime"),
        }
    }
}
impl std::error::Error for Error {}