use crate::types::BoltInteger;
use chrono::{FixedOffset, NaiveTime, Offset, Timelike};
use neo4rs_macros::BoltStruct;
#[derive(Debug, PartialEq, Eq, Clone, BoltStruct)]
#[signature(0xB2, 0x54)]
pub struct BoltTime {
pub(crate) nanoseconds: BoltInteger,
pub(crate) tz_offset_seconds: BoltInteger,
}
#[derive(Debug, PartialEq, Eq, Clone, BoltStruct)]
#[signature(0xB1, 0x74)]
pub struct BoltLocalTime {
pub(crate) nanoseconds: BoltInteger,
}
impl From<(NaiveTime, FixedOffset)> for BoltTime {
fn from(value: (NaiveTime, FixedOffset)) -> Self {
let seconds_from_midnight = value.0.num_seconds_from_midnight() as i64;
let nanoseconds = seconds_from_midnight * 1_000_000_000 + value.0.nanosecond() as i64;
BoltTime {
nanoseconds: nanoseconds.into(),
tz_offset_seconds: value.1.fix().local_minus_utc().into(),
}
}
}
impl From<NaiveTime> for BoltLocalTime {
fn from(value: NaiveTime) -> Self {
let seconds_from_midnight = value.num_seconds_from_midnight() as i64;
let nanoseconds = seconds_from_midnight * 1_000_000_000 + value.nanosecond() as i64;
BoltLocalTime {
nanoseconds: nanoseconds.into(),
}
}
}
impl BoltTime {
pub(crate) fn to_chrono(&self) -> (NaiveTime, FixedOffset) {
self.into()
}
}
impl BoltLocalTime {
pub(crate) fn to_chrono(&self) -> NaiveTime {
self.into()
}
}
impl From<&BoltTime> for (NaiveTime, FixedOffset) {
fn from(value: &BoltTime) -> Self {
let time = BoltLocalTime {
nanoseconds: value.nanoseconds.clone(),
}
.to_chrono();
let offset = FixedOffset::east_opt(value.tz_offset_seconds.value as i32)
.unwrap_or_else(|| panic!("invald timezone offset {}", value.tz_offset_seconds.value));
(time, offset)
}
}
impl From<BoltTime> for (NaiveTime, FixedOffset) {
fn from(value: BoltTime) -> Self {
(&value).into()
}
}
impl From<&BoltLocalTime> for NaiveTime {
fn from(value: &BoltLocalTime) -> Self {
let nanos = value.nanoseconds.value;
let seconds = (nanos / 1_000_000_000) as u32;
let nanoseconds = (nanos % 1_000_000_000) as u32;
NaiveTime::from_num_seconds_from_midnight_opt(seconds, nanoseconds).unwrap_or_else(|| {
panic!(
"invalid number of seconds {} and nanoseconds {}",
seconds, nanoseconds
)
})
}
}
impl From<BoltLocalTime> for NaiveTime {
fn from(value: BoltLocalTime) -> Self {
(&value).into()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{types::BoltWireFormat, version::Version};
use bytes::Bytes;
#[test]
fn should_serialize_time() {
let time = NaiveTime::from_hms_nano_opt(7, 8, 9, 100).unwrap();
let offset = FixedOffset::east_opt(2 * 3600).unwrap();
let time: BoltTime = (time, offset).into();
assert_eq!(
time.into_bytes(Version::V4_1).unwrap(),
Bytes::from_static(&[
0xB2, 0x54, 0xCB, 0x00, 0x00, 0x17, 0x5D, 0x2F, 0xB8, 0x3A, 0x64, 0xC9, 0x1C, 0x20,
])
);
}
#[test]
fn should_deserialize_time() {
let mut bytes = Bytes::from_static(&[
0xB2, 0x54, 0xCB, 0x00, 0x00, 0x17, 0x5D, 0x2F, 0xB8, 0x3A, 0x64, 0xC9, 0x1C, 0x20,
]);
let (time, offset) = BoltTime::parse(Version::V4_1, &mut bytes)
.unwrap()
.try_into()
.unwrap();
assert_eq!(time.to_string(), "07:08:09.000000100");
assert_eq!(offset, FixedOffset::east_opt(2 * 3600).unwrap());
}
#[test]
fn should_serialize_local_time() {
let naive_time = NaiveTime::from_hms_nano_opt(7, 8, 9, 100).unwrap();
let time: BoltLocalTime = naive_time.into();
assert_eq!(
time.into_bytes(Version::V4_1).unwrap(),
Bytes::from_static(&[
0xB1, 0x74, 0xCB, 0x00, 0x00, 0x17, 0x5D, 0x2F, 0xB8, 0x3A, 0x64,
])
);
}
#[test]
fn should_deserialize_local_time() {
let mut bytes = Bytes::from_static(&[
0xB1, 0x74, 0xCB, 0x00, 0x00, 0x17, 0x5D, 0x2F, 0xB8, 0x3A, 0x64,
]);
let time: NaiveTime = BoltLocalTime::parse(Version::V4_1, &mut bytes)
.unwrap()
.try_into()
.unwrap();
assert_eq!(time.to_string(), "07:08:09.000000100");
}
}