use std::io::Write;
use chrono::{DateTime, Utc};
use crate::{
deserialize::Deserializable, serialize::Serializable, types::number::SubunitNumber, Error,
GenError, GenResult,
};
#[derive(Debug, Clone, PartialEq)]
pub struct Timestamp {
pub seconds: u32,
pub nanoseconds: SubunitNumber,
}
impl Serializable for Timestamp {
fn wire_size(&self) -> GenResult<SubunitNumber> {
self.seconds.wire_size()? + self.nanoseconds.wire_size()?
}
fn serialize<W: Write>(&self, out: &mut W) -> GenResult<()> {
self.seconds.serialize(out)?;
self.nanoseconds.serialize(out)
}
}
impl Deserializable for Timestamp {
fn required_bytes(bytes: &[u8]) -> GenResult<usize> {
let required = 5;
if bytes.len() < required {
return Ok(required);
}
let required = SubunitNumber::required_bytes(&bytes[4..5])?;
Ok(4 + required)
}
fn deserialize(bytes: &[u8]) -> GenResult<(Timestamp, usize)> {
let required = Timestamp::required_bytes(bytes)?;
if bytes.len() < required {
return Err(Error::NotEnoughBytes.into());
}
let seconds = u32::from_be_bytes(bytes[..4].try_into().unwrap());
let nanoseconds = SubunitNumber::deserialize(&bytes[4..required])?.0;
Ok((
Timestamp {
seconds,
nanoseconds,
},
required,
))
}
}
impl TryFrom<DateTime<Utc>> for Timestamp {
type Error = GenError;
fn try_from(dt: DateTime<Utc>) -> GenResult<Self> {
let timestamp_i64 = dt.timestamp();
let seconds = u32::try_from(timestamp_i64)
.map_err(|_| Error::InvalidTimestamp(timestamp_i64 as u32, 0))?;
let nanoseconds = dt.timestamp_subsec_nanos().try_into()?;
Ok(Self {
seconds,
nanoseconds,
})
}
}
impl TryFrom<Timestamp> for DateTime<Utc> {
type Error = GenError;
fn try_from(ts: Timestamp) -> GenResult<Self> {
DateTime::from_timestamp(ts.seconds as i64, ts.nanoseconds.into())
.ok_or_else(|| Error::InvalidTimestamp(ts.seconds, ts.nanoseconds.into()).into())
}
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::TimeZone;
#[test]
fn test_timestamp_valid_epoch() {
let dt = Utc.timestamp_opt(0, 0).unwrap();
let ts = Timestamp::try_from(dt).unwrap();
assert_eq!(ts.seconds, 0);
assert_eq!(u32::from(ts.nanoseconds), 0);
let dt_back: DateTime<Utc> = ts.try_into().unwrap();
assert_eq!(dt, dt_back);
}
#[test]
fn test_timestamp_valid_recent() {
let dt = Utc.timestamp_opt(1704067200, 123456789).unwrap();
let ts = Timestamp::try_from(dt).unwrap();
assert_eq!(ts.seconds, 1704067200);
assert_eq!(u32::from(ts.nanoseconds), 123456789);
let dt_back: DateTime<Utc> = ts.try_into().unwrap();
assert_eq!(dt, dt_back);
}
#[test]
fn test_timestamp_max_u32() {
let dt = Utc.timestamp_opt(u32::MAX as i64, 0).unwrap();
let ts = Timestamp::try_from(dt).unwrap();
assert_eq!(ts.seconds, u32::MAX);
assert_eq!(u32::from(ts.nanoseconds), 0);
let dt_back: DateTime<Utc> = ts.try_into().unwrap();
assert_eq!(dt, dt_back);
}
#[test]
fn test_timestamp_overflow_beyond_u32() {
let dt = Utc.timestamp_opt(u32::MAX as i64 + 1, 0).unwrap();
let result = Timestamp::try_from(dt);
assert!(result.is_err());
}
#[test]
fn test_timestamp_negative() {
let dt = Utc.timestamp_opt(-1, 0).unwrap();
let result = Timestamp::try_from(dt);
assert!(result.is_err());
}
#[test]
fn test_timestamp_invalid_nanoseconds() {
let ts = Timestamp {
seconds: u32::MAX,
nanoseconds: SubunitNumber::try_from(999_999_999u32).unwrap(),
};
let result: Result<DateTime<Utc>, _> = ts.try_into();
assert!(result.is_ok());
}
#[test]
fn test_timestamp_roundtrip() {
let test_cases = vec![
(0, 0),
(1, 1),
(1000000000, 500000000),
(1704067200, 999999999),
(u32::MAX, 0),
];
for (secs, nanos) in test_cases {
let dt = Utc.timestamp_opt(secs as i64, nanos).unwrap();
let ts = Timestamp::try_from(dt).unwrap();
assert_eq!(ts.seconds, secs);
assert_eq!(u32::from(ts.nanoseconds), nanos);
let dt_back: DateTime<Utc> = ts.try_into().unwrap();
assert_eq!(dt, dt_back);
}
}
}