use crate::errors::Error;
use crate::types::*;
use chrono::{DateTime, FixedOffset, NaiveDateTime, Offset, Timelike};
use neo4rs_macros::BoltStruct;
use std::convert::TryInto;
#[derive(Debug, PartialEq, Eq, Clone, BoltStruct)]
#[signature(0xB3, 0x46)]
pub struct BoltDateTime {
pub(crate) seconds: BoltInteger,
pub(crate) nanoseconds: BoltInteger,
pub(crate) tz_offset_seconds: BoltInteger,
}
#[derive(Debug, PartialEq, Eq, Clone, BoltStruct)]
#[signature(0xB2, 0x64)]
pub struct BoltLocalDateTime {
pub(crate) seconds: BoltInteger,
pub(crate) nanoseconds: BoltInteger,
}
#[derive(Debug, PartialEq, Eq, Clone, BoltStruct)]
#[signature(0xB3, 0x66)]
pub struct BoltDateTimeZoneId {
pub(crate) seconds: BoltInteger,
pub(crate) nanoseconds: BoltInteger,
pub(crate) tz_id: BoltString,
}
impl BoltDateTime {
pub(crate) fn try_to_chrono(&self) -> Result<DateTime<FixedOffset>> {
self.try_into()
}
}
impl BoltLocalDateTime {
pub(crate) fn try_to_chrono(&self) -> Result<NaiveDateTime> {
self.try_into()
}
}
impl BoltDateTimeZoneId {
pub(crate) fn try_to_chrono(&self) -> Result<DateTime<FixedOffset>> {
self.try_into()
}
pub fn tz_id(&self) -> &str {
&self.tz_id.value
}
}
impl From<(NaiveDateTime, &str)> for BoltDateTimeZoneId {
fn from(value: (NaiveDateTime, &str)) -> Self {
let seconds = value.0.timestamp().into();
let nanoseconds = (value.0.timestamp_subsec_nanos() as i64).into();
BoltDateTimeZoneId {
seconds,
nanoseconds,
tz_id: value.1.into(),
}
}
}
impl TryInto<(NaiveDateTime, String)> for BoltDateTimeZoneId {
type Error = Error;
fn try_into(self) -> Result<(NaiveDateTime, String)> {
NaiveDateTime::from_timestamp_opt(self.seconds.value, self.nanoseconds.value as u32)
.map(|datetime| (datetime, self.tz_id.into()))
.ok_or(Error::ConversionError)
}
}
impl TryFrom<&BoltDateTimeZoneId> for NaiveDateTime {
type Error = Error;
fn try_from(value: &BoltDateTimeZoneId) -> Result<Self, Self::Error> {
NaiveDateTime::from_timestamp_opt(value.seconds.value, value.nanoseconds.value as u32)
.ok_or(Error::ConversionError)
}
}
impl TryFrom<&BoltDateTimeZoneId> for DateTime<FixedOffset> {
type Error = Error;
fn try_from(value: &BoltDateTimeZoneId) -> std::result::Result<Self, Self::Error> {
let tz: chrono_tz::Tz = value
.tz_id
.value
.parse()
.map_err(|_| Error::ConversionError)?;
let seconds = value.seconds.value;
let nanoseconds = value.nanoseconds.value as u32;
let dt = NaiveDateTime::from_timestamp_opt(seconds, nanoseconds)
.ok_or(Error::ConversionError)?
.and_local_timezone(tz)
.earliest()
.ok_or(Error::ConversionError)?;
let dt = dt.with_timezone(&dt.offset().fix());
Ok(dt)
}
}
impl From<NaiveDateTime> for BoltLocalDateTime {
fn from(value: NaiveDateTime) -> Self {
let seconds = value.timestamp().into();
let nanoseconds = (value.nanosecond() as i64).into();
BoltLocalDateTime {
seconds,
nanoseconds,
}
}
}
impl TryInto<NaiveDateTime> for BoltLocalDateTime {
type Error = Error;
fn try_into(self) -> Result<NaiveDateTime> {
(&self).try_into()
}
}
impl TryFrom<&BoltLocalDateTime> for NaiveDateTime {
type Error = Error;
fn try_from(value: &BoltLocalDateTime) -> std::result::Result<Self, Self::Error> {
NaiveDateTime::from_timestamp_opt(value.seconds.value, value.nanoseconds.value as u32)
.ok_or(Error::ConversionError)
}
}
impl From<DateTime<FixedOffset>> for BoltDateTime {
fn from(value: DateTime<FixedOffset>) -> Self {
let seconds = (value.timestamp() + value.offset().fix().local_minus_utc() as i64).into();
let nanoseconds = (value.nanosecond() as i64).into();
let tz_offset_seconds = value.offset().fix().local_minus_utc().into();
BoltDateTime {
seconds,
nanoseconds,
tz_offset_seconds,
}
}
}
impl TryInto<DateTime<FixedOffset>> for BoltDateTime {
type Error = Error;
fn try_into(self) -> Result<DateTime<FixedOffset>> {
(&self).try_into()
}
}
impl TryFrom<&BoltDateTime> for DateTime<FixedOffset> {
type Error = Error;
fn try_from(
BoltDateTime {
seconds,
nanoseconds,
tz_offset_seconds,
}: &BoltDateTime,
) -> std::result::Result<Self, Self::Error> {
let seconds = seconds.value - tz_offset_seconds.value;
let offset =
FixedOffset::east_opt(tz_offset_seconds.value as i32).ok_or(Error::ConversionError)?;
let datetime = NaiveDateTime::from_timestamp_opt(seconds, nanoseconds.value as u32)
.ok_or(Error::ConversionError)?;
Ok(DateTime::from_utc(datetime, offset))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::version::Version;
use bytes::Bytes;
#[test]
fn should_serialize_a_datetime() {
let date: BoltDateTime = DateTime::parse_from_rfc2822("Wed, 24 Jun 2015 12:50:35 +0100")
.unwrap()
.into();
assert_eq!(
date.into_bytes(Version::V4_1).unwrap(),
Bytes::from_static(&[
0xB3, 0x46, 0xCA, 0x55, 0x8A, 0xA7, 0x9B, 0x00, 0xC9, 0x0E, 0x10,
])
);
}
#[test]
fn should_deserialize_a_datetime() {
let mut bytes = Bytes::from_static(&[
0xB3, 0x46, 0xCA, 0x55, 0x8A, 0xA7, 0x9B, 0x00, 0xC9, 0x0E, 0x10,
]);
let datetime: DateTime<FixedOffset> = BoltDateTime::parse(Version::V4_1, &mut bytes)
.unwrap()
.try_into()
.unwrap();
assert_eq!(datetime.to_rfc2822(), "Wed, 24 Jun 2015 12:50:35 +0100");
}
#[test]
fn should_serialize_a_localdatetime() {
let date: BoltLocalDateTime =
NaiveDateTime::parse_from_str("2015-07-01 08:59:60.123", "%Y-%m-%d %H:%M:%S%.f")
.unwrap()
.into();
assert_eq!(
date.into_bytes(Version::V4_1).unwrap(),
Bytes::from_static(&[
0xB2, 0x64, 0xCA, 0x55, 0x93, 0xAC, 0x0F, 0xCA, 0x42, 0xEF, 0x9E, 0xC0,
])
);
}
#[test]
fn should_deserialize_a_localdatetime() {
let mut bytes = Bytes::from_static(&[
0xB2, 0x64, 0xCA, 0x55, 0x93, 0xAC, 0x0F, 0xCA, 0x42, 0xEF, 0x9E, 0xC0,
]);
let datetime: NaiveDateTime = BoltLocalDateTime::parse(Version::V4_1, &mut bytes)
.unwrap()
.try_into()
.unwrap();
assert_eq!(datetime.to_string(), "2015-07-01 08:59:60.123");
}
#[test]
fn should_serialize_a_datetime_with_zoneid() {
let datetime =
NaiveDateTime::parse_from_str("2015-07-01 08:59:60.123", "%Y-%m-%d %H:%M:%S%.f")
.unwrap();
let date: BoltDateTimeZoneId = (datetime, "Europe/Paris").into();
assert_eq!(
date.into_bytes(Version::V4_1).unwrap(),
Bytes::from_static(&[
0xB3, 0x66, 0xCA, 0x55, 0x93, 0xAC, 0x0F, 0xCA, 0x42, 0xEF, 0x9E, 0xC0, 0x8C, 0x45,
0x75, 0x72, 0x6F, 0x70, 0x65, 0x2F, 0x50, 0x61, 0x72, 0x69, 0x73,
])
);
}
#[test]
fn should_deserialize_a_datetime_with_zoneid() {
let mut bytes = Bytes::from_static(&[
0xB3, 0x66, 0xCA, 0x55, 0x93, 0xAC, 0x0F, 0xCA, 0x42, 0xEF, 0x9E, 0xC0, 0x8C, 0x45,
0x75, 0x72, 0x6F, 0x70, 0x65, 0x2F, 0x50, 0x61, 0x72, 0x69, 0x73,
]);
let (datetime, zone_id) = BoltDateTimeZoneId::parse(Version::V4_1, &mut bytes)
.unwrap()
.try_into()
.unwrap();
assert_eq!(datetime.to_string(), "2015-07-01 08:59:60.123");
assert_eq!(zone_id, "Europe/Paris");
}
}