use crate::convert::{RangeError, SaturatingFrom};
#[cfg(feature = "chrono")]
mod chrono;
#[cfg(test)]
mod tests;
#[derive(Debug, PartialEq, Eq)]
enum NormalizeResult {
Ok(u32),
Overflow(u32, u32),
}
fn normalize_nsec(nsec: u32) -> NormalizeResult {
if nsec < 1_000_000_000 {
NormalizeResult::Ok(nsec)
} else {
let sec = nsec / 1_000_000_000;
NormalizeResult::Overflow(sec, nsec % 1_000_000_000)
}
}
impl NormalizeResult {
fn carry_i32(self, sec: i32) -> Option<(i32, u32)> {
match self {
Self::Ok(nsec) => Some((sec, nsec)),
Self::Overflow(extra_sec, nsec) => {
let Ok(extra_sec) = i32::try_from(extra_sec) else {
unreachable!("expected {extra_sec} to be within [0, 4]")
};
sec.checked_add(extra_sec).map(|sec| (sec, nsec))
}
}
}
fn carry_u32(self, sec: u32) -> Option<(u32, u32)> {
match self {
Self::Ok(nsec) => Some((sec, nsec)),
Self::Overflow(extra_sec, nsec) => sec.checked_add(extra_sec).map(|sec| (sec, nsec)),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, prost::Message)]
#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
pub struct Duration {
#[prost(int32, tag = "1")]
sec: i32,
#[prost(uint32, tag = "2")]
nsec: u32,
}
impl Duration {
pub const MAX: Self = Self {
sec: i32::MAX,
nsec: 999_999_999,
};
pub const MIN: Self = Self {
sec: i32::MIN,
nsec: 0,
};
pub fn new_checked(sec: i32, nsec: u32) -> Option<Self> {
normalize_nsec(nsec)
.carry_i32(sec)
.map(|(sec, nsec)| Self { sec, nsec })
}
pub fn new(sec: i32, nsec: u32) -> Self {
Self::new_checked(sec, nsec).unwrap()
}
pub fn sec(&self) -> i32 {
self.sec
}
pub fn nsec(&self) -> u32 {
self.nsec
}
pub fn normalize(self) -> Option<Self> {
Self::new_checked(self.sec, self.nsec)
}
pub fn try_from_secs_f64(secs: f64) -> Result<Self, RangeError> {
if secs < f64::from(i32::MIN) {
Err(RangeError::LowerBound)
} else if secs >= f64::from(i32::MAX) + 1.0 {
Err(RangeError::UpperBound)
} else {
let mut sec = secs as i32;
let mut nsec = ((secs - f64::from(sec)) * 1e9) as i32;
if nsec < 0 {
sec -= 1;
nsec += 1_000_000_000;
}
Ok(Self::new(
sec,
u32::try_from(nsec).unwrap_or_else(|e| {
unreachable!("expected {nsec} to be within [0, 1_000_000_000): {e}")
}),
))
}
}
pub fn saturating_from_secs_f64(secs: f64) -> Self {
match Self::try_from_secs_f64(secs) {
Ok(d) => d,
Err(RangeError::LowerBound) => Duration::MIN,
Err(RangeError::UpperBound) => Duration::MAX,
}
}
}
impl From<Duration> for prost_types::Duration {
fn from(v: Duration) -> Self {
Self {
seconds: i64::from(v.sec),
nanos: i32::try_from(v.nsec).unwrap_or_else(|e| {
unreachable!("expected {} to be within [0, 1_000_000_000): {e}", v.nsec)
}),
}
}
}
impl TryFrom<std::time::Duration> for Duration {
type Error = RangeError;
fn try_from(duration: std::time::Duration) -> Result<Self, Self::Error> {
let Ok(sec) = i32::try_from(duration.as_secs()) else {
return Err(RangeError::UpperBound);
};
let nsec = duration.subsec_nanos();
Ok(Self { sec, nsec })
}
}
impl<T> SaturatingFrom<T> for Duration
where
Self: TryFrom<T, Error = RangeError>,
{
fn saturating_from(value: T) -> Self {
match Self::try_from(value) {
Ok(d) => d,
Err(RangeError::LowerBound) => Duration::MIN,
Err(RangeError::UpperBound) => Duration::MAX,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, prost::Message)]
#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
pub struct Timestamp {
#[prost(uint32, tag = "1")]
sec: u32,
#[prost(uint32, tag = "2")]
nsec: u32,
}
impl Timestamp {
pub const MAX: Self = Self {
sec: u32::MAX,
nsec: 999_999_999,
};
pub const MIN: Self = Self { sec: 0, nsec: 0 };
pub fn new_checked(sec: u32, nsec: u32) -> Option<Self> {
normalize_nsec(nsec)
.carry_u32(sec)
.map(|(sec, nsec)| Self { sec, nsec })
}
pub fn new(sec: u32, nsec: u32) -> Self {
Self::new_checked(sec, nsec).unwrap()
}
pub fn now() -> Self {
let now = std::time::SystemTime::now();
Self::try_from(now).expect("timestamp out of range")
}
pub fn sec(&self) -> u32 {
self.sec
}
pub fn nsec(&self) -> u32 {
self.nsec
}
pub fn total_nanos(&self) -> u64 {
u64::from(self.sec) * 1_000_000_000 + u64::from(self.nsec)
}
pub fn normalize(self) -> Option<Self> {
Self::new_checked(self.sec, self.nsec)
}
pub fn try_from_epoch_secs_f64(secs: f64) -> Result<Self, RangeError> {
if secs < 0.0 {
Err(RangeError::LowerBound)
} else if secs >= f64::from(u32::MAX) + 1.0 {
Err(RangeError::UpperBound)
} else {
let sec = secs as u32;
let nsec = ((secs - f64::from(sec)) * 1e9) as u32;
Ok(Self::new(sec, nsec))
}
}
pub fn saturating_from_epoch_secs_f64(secs: f64) -> Self {
match Self::try_from_epoch_secs_f64(secs) {
Ok(d) => d,
Err(RangeError::LowerBound) => Timestamp::MIN,
Err(RangeError::UpperBound) => Timestamp::MAX,
}
}
}
impl From<Timestamp> for prost_types::Timestamp {
fn from(v: Timestamp) -> Self {
Self {
seconds: i64::from(v.sec),
nanos: i32::try_from(v.nsec).unwrap_or_else(|e| {
unreachable!("expected {} to be within [0, 1_000_000_000): {e}", v.nsec)
}),
}
}
}
impl TryFrom<std::time::SystemTime> for Timestamp {
type Error = RangeError;
fn try_from(time: std::time::SystemTime) -> Result<Self, Self::Error> {
let Ok(duration) = time.duration_since(std::time::UNIX_EPOCH) else {
return Err(RangeError::LowerBound);
};
let Ok(sec) = u32::try_from(duration.as_secs()) else {
return Err(RangeError::UpperBound);
};
let nsec = duration.subsec_nanos();
Ok(Self::new(sec, nsec))
}
}
impl<T> SaturatingFrom<T> for Timestamp
where
Self: TryFrom<T, Error = RangeError>,
{
fn saturating_from(value: T) -> Self {
match Self::try_from(value) {
Ok(d) => d,
Err(RangeError::LowerBound) => Timestamp::MIN,
Err(RangeError::UpperBound) => Timestamp::MAX,
}
}
}
#[cfg(test)]
mod test {
use bytes::BytesMut;
use prost::Message;
use super::*;
#[test]
fn test_timestamp_decode() {
let timestamp = Timestamp {
sec: 1750000000,
nsec: 99999,
};
let mut buf = BytesMut::new();
timestamp.encode(&mut buf).unwrap();
let decoded = Timestamp::decode(buf).unwrap();
assert_eq!(timestamp, decoded);
}
#[test]
fn test_duration_decode() {
let duration = Duration {
sec: 1,
nsec: 99999,
};
let mut buf = BytesMut::new();
duration.encode(&mut buf).unwrap();
let decoded = Duration::decode(buf).unwrap();
assert_eq!(duration, decoded);
}
}