extern crate chrono;
use chrono::prelude::DateTime;
use chrono::prelude::NaiveDateTime;
use chrono::prelude::Utc;
use std::convert::TryFrom;
const ZERO_OFFSET_100NS: u64 = 621_355_968_000_000_000;
const ZERO_OFFSET_1SEC: i64 = 62_135_596_800;
pub fn from_100ns(gcom_datetime: u64) -> Option<chrono::DateTime<Utc>> {
const SECONDS_FACTOR: u64 = 10_000_000;
const NANOSECONDS_FACTOR: u32 = 100;
if let Some(timestamp_100ns) = gcom_datetime.checked_sub(ZERO_OFFSET_100NS) {
let ts_sec: u64 = timestamp_100ns / SECONDS_FACTOR; let ts_nano: u32 =
u32::try_from(timestamp_100ns - ts_sec * SECONDS_FACTOR).unwrap() * NANOSECONDS_FACTOR; if let Some(ndt) =
NaiveDateTime::from_timestamp_opt(i64::try_from(ts_sec).unwrap(), ts_nano)
{
Some(DateTime::<Utc>::from_utc(ndt, Utc))
} else {
None
}
} else {
None
}
}
pub fn to_100ns(datetime: chrono::DateTime<Utc>) -> Option<u64> {
const SECONDS_FACTOR: u64 = 10_000_000;
const NANOSECONDS_FACTOR: u32 = 100;
if let Some(zero_offset_sec) = datetime.timestamp().checked_add(ZERO_OFFSET_1SEC) {
if let Ok(zero_offset_sec) = u64::try_from(zero_offset_sec) {
if let Some(gcom_datetime) = zero_offset_sec.checked_mul(SECONDS_FACTOR) {
gcom_datetime.checked_add(u64::from(
datetime.timestamp_subsec_nanos() / NANOSECONDS_FACTOR,
))
} else {
None
}
} else {
None
}
} else {
None
}
}
pub fn add_offset_100ns(
datetime: chrono::DateTime<Utc>,
gcom_timespan: i64,
) -> Option<chrono::DateTime<Utc>> {
if let Some(gcom_datetime) = to_100ns(datetime) {
#[allow(clippy::collapsible_if)]
if gcom_timespan == i64::MIN {
if let Some(gcom_datetime) = gcom_datetime.checked_sub((-(gcom_timespan + 1)) as u64) {
if let Some(gcom_datetime) = gcom_datetime.checked_sub(1) {
from_100ns(gcom_datetime)
} else {
None
}
} else {
None
}
} else if gcom_timespan < 0 {
if let Some(gcom_datetime) = gcom_datetime.checked_sub((-gcom_timespan) as u64) {
from_100ns(gcom_datetime)
} else {
None
}
} else {
if let Some(gcom_datetime) = gcom_datetime.checked_add(gcom_timespan as u64) {
from_100ns(gcom_datetime)
} else {
None
}
}
} else {
None
}
}
pub fn from_timestamp_ms(timestamp_ms: u64) -> Option<chrono::DateTime<Utc>> {
const SECONDS_FACTOR: u64 = 1000;
const NANOSECONDS_FACTOR: u32 = 1_000_000;
let ts_sec: u64 = timestamp_ms / SECONDS_FACTOR;
let ts_nano: u32 =
u32::try_from(timestamp_ms - ts_sec * SECONDS_FACTOR).unwrap() * NANOSECONDS_FACTOR; if let Some(ndt) = NaiveDateTime::from_timestamp_opt(i64::try_from(ts_sec).unwrap(), ts_nano) {
Some(DateTime::<Utc>::from_utc(ndt, Utc))
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
fn validate_gcom_datetime(gcom_datetime: u64, rfc339_nanosec: &str) {
let date_time = from_100ns(gcom_datetime).expect("Conversion to Rust DateTime failed");
assert_eq!(
rfc339_nanosec,
date_time.to_rfc3339_opts(chrono::SecondsFormat::Nanos, true)
);
let date_time_val = to_100ns(date_time).expect("Conversion back to GCOM DateTime failed");
assert_eq!(gcom_datetime, date_time_val);
}
#[test]
fn test_conversions() {
validate_gcom_datetime(637287826990857502, "2020-06-26T15:38:19.085750200Z");
validate_gcom_datetime(637287826990862498, "2020-06-26T15:38:19.086249800Z");
validate_gcom_datetime(637287826990867494, "2020-06-26T15:38:19.086749400Z");
validate_gcom_datetime(637287826990872490, "2020-06-26T15:38:19.087249000Z");
}
#[test]
fn test_offset_addition() {
let dt1 = from_100ns(637287826990872490).expect("Conversion to Rust DateTime failed");
let dt2 = add_offset_100ns(dt1, -14_988).expect("Addition of offset failed");
let dt2_val = to_100ns(dt2).expect("Conversion back to GCOM DateTime failed");
assert_eq!(637287826990857502, dt2_val);
assert_eq!(
dt2.to_rfc3339_opts(chrono::SecondsFormat::Nanos, true),
"2020-06-26T15:38:19.085750200Z"
)
}
}