use chrono::{DateTime, NaiveDateTime, TimeZone, Utc};
#[must_use]
pub fn i64_nanos_to_datetime(nanos: i64) -> DateTime<Utc> {
let secs = nanos.div_euclid(1_000_000_000);
let nsecs: u32 = nanos.rem_euclid(1_000_000_000) as u32;
Utc.timestamp_opt(secs, nsecs)
.single()
.unwrap_or(if nanos < 0 {
DateTime::<Utc>::MIN_UTC
} else {
DateTime::<Utc>::MAX_UTC
})
}
#[must_use]
pub fn datetime_to_i64_nanos(dt: DateTime<Utc>) -> i64 {
dt.timestamp_nanos_opt().unwrap_or(i64::MAX)
}
#[must_use]
pub fn i64_micros_to_naive(micros: i64) -> NaiveDateTime {
chrono::DateTime::from_timestamp_micros(micros).map_or_else(
|| {
if micros < 0 {
DateTime::<Utc>::MIN_UTC.naive_utc()
} else {
DateTime::<Utc>::MAX_UTC.naive_utc()
}
},
|dt| dt.naive_utc(),
)
}
#[must_use]
pub const fn naive_to_i64_micros(dt: NaiveDateTime) -> i64 {
dt.and_utc().timestamp_micros()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn roundtrip_datetime() {
let now = Utc::now();
let wire = datetime_to_i64_nanos(now);
let back = i64_nanos_to_datetime(wire);
assert_eq!(now.timestamp(), back.timestamp());
}
#[test]
fn roundtrip_naive() {
let now = Utc::now().naive_utc();
let wire = naive_to_i64_micros(now);
let back = i64_micros_to_naive(wire);
assert_eq!(now.and_utc().timestamp(), back.and_utc().timestamp());
}
#[test]
fn zero_is_epoch() {
let dt = i64_nanos_to_datetime(0);
assert_eq!(dt.timestamp(), 0);
let naive = i64_micros_to_naive(0);
assert_eq!(naive.and_utc().timestamp(), 0);
}
#[test]
fn saturates_extremes() {
let too_big = i64_nanos_to_datetime(i64::MAX);
assert!(too_big.timestamp_nanos_opt().is_some());
let too_small = i64_nanos_to_datetime(i64::MIN);
assert!(too_small.timestamp_nanos_opt().is_some());
}
}