#![forbid(unsafe_code)]
use crate::OverflowError;
use prost_types::{Duration as ProtoDuration, Timestamp};
use time::{Duration as TimeDuration, OffsetDateTime};
pub trait TimestampTimeExt {
fn to_offset_datetime(&self) -> Result<OffsetDateTime, OverflowError>;
fn from_offset_datetime(dt: OffsetDateTime) -> Timestamp;
}
impl TimestampTimeExt for Timestamp {
fn to_offset_datetime(&self) -> Result<OffsetDateTime, OverflowError> {
let dt = OffsetDateTime::from_unix_timestamp(self.seconds).map_err(|_| {
OverflowError::new(
"to_offset_datetime",
"Timestamp seconds out of time::OffsetDateTime range",
)
})?;
let nanos_clamped = self.nanos.clamp(0, 999_999_999);
let dt = dt + TimeDuration::nanoseconds(nanos_clamped as i64);
Ok(dt)
}
fn from_offset_datetime(dt: OffsetDateTime) -> Timestamp {
let seconds = dt.unix_timestamp();
let nanos = dt.nanosecond() as i32;
Timestamp { seconds, nanos }
}
}
pub trait DurationTimeExt {
fn to_time_duration(&self) -> Result<TimeDuration, OverflowError>;
fn from_time_duration(d: TimeDuration) -> Result<ProtoDuration, OverflowError>;
}
impl DurationTimeExt for ProtoDuration {
fn to_time_duration(&self) -> Result<TimeDuration, OverflowError> {
let total_nanos: i128 = (self.seconds as i128)
.checked_mul(1_000_000_000)
.and_then(|s| s.checked_add(self.nanos as i128))
.ok_or_else(|| {
OverflowError::new(
"to_time_duration",
"proto Duration nanoseconds overflow i128",
)
})?;
Ok(TimeDuration::nanoseconds_i128(total_nanos))
}
fn from_time_duration(d: TimeDuration) -> Result<ProtoDuration, OverflowError> {
let seconds = d.whole_seconds();
let nanos = d.subsec_nanoseconds();
Ok(ProtoDuration { seconds, nanos })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn timestamp_epoch_round_trip() {
let ts = Timestamp {
seconds: 0,
nanos: 0,
};
let dt = ts.to_offset_datetime().expect("epoch should convert");
assert_eq!(dt.unix_timestamp(), 0);
let back = Timestamp::from_offset_datetime(dt);
assert_eq!(back.seconds, 0);
assert_eq!(back.nanos, 0);
}
#[test]
fn timestamp_positive_nanos_round_trip() {
let ts = Timestamp {
seconds: 1_700_000_000,
nanos: 123_456_789,
};
let dt = ts.to_offset_datetime().expect("far future should convert");
let back = Timestamp::from_offset_datetime(dt);
assert_eq!(back.seconds, ts.seconds);
assert_eq!(back.nanos, ts.nanos);
}
#[test]
fn timestamp_pre_epoch_round_trip() {
let ts = Timestamp {
seconds: -2,
nanos: 500_000_000,
};
let dt = ts.to_offset_datetime().expect("pre-epoch should convert");
let back = Timestamp::from_offset_datetime(dt);
assert_eq!(back.seconds, ts.seconds);
assert_eq!(back.nanos, ts.nanos);
}
#[test]
fn duration_one_and_half_seconds_round_trip() {
let pd = ProtoDuration {
seconds: 1,
nanos: 500_000_000,
};
let td = pd.to_time_duration().expect("should convert");
assert_eq!(td.whole_seconds(), 1);
assert_eq!(td.subsec_nanoseconds(), 500_000_000);
let back = ProtoDuration::from_time_duration(td).expect("round trip");
assert_eq!(back.seconds, pd.seconds);
assert_eq!(back.nanos, pd.nanos);
}
#[test]
fn duration_negative_round_trip() {
let pd = ProtoDuration {
seconds: -3,
nanos: 0,
};
let td = pd.to_time_duration().expect("should convert");
assert_eq!(td.whole_seconds(), -3);
let back = ProtoDuration::from_time_duration(td).expect("round trip");
assert_eq!(back.seconds, pd.seconds);
assert_eq!(back.nanos, pd.nanos);
}
}