1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
use crate::{
    errors::Error,
    types::{BoltInteger, Result},
};
use chrono::{Days, NaiveDate, NaiveDateTime};
use neo4rs_macros::BoltStruct;
use std::convert::TryInto;

#[derive(Debug, PartialEq, Eq, Clone, BoltStruct)]
#[signature(0xB1, 0x44)]
pub struct BoltDate {
    pub(crate) days: BoltInteger,
}

impl From<NaiveDate> for BoltDate {
    fn from(value: NaiveDate) -> Self {
        let epoch = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap();
        let days = (value - epoch).num_days().into();
        BoltDate { days }
    }
}

impl BoltDate {
    pub(crate) fn try_to_chrono(&self) -> Result<NaiveDate> {
        self.try_into()
    }
}

impl TryFrom<&BoltDate> for NaiveDate {
    type Error = Error;

    fn try_from(value: &BoltDate) -> Result<Self> {
        let epoch = NaiveDateTime::from_timestamp_opt(0, 0).expect("UNIX epoch is always valid");
        let days = Days::new(value.days.value.unsigned_abs());
        if value.days.value >= 0 {
            epoch.checked_add_days(days)
        } else {
            epoch.checked_sub_days(days)
        }
        .map_or(Err(Error::ConversionError), |o| Ok(o.date()))
    }
}

impl TryInto<NaiveDate> for BoltDate {
    type Error = Error;

    fn try_into(self) -> Result<NaiveDate> {
        (&self).try_into()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{types::BoltWireFormat, version::Version};
    use bytes::Bytes;

    #[test]
    fn should_serialize_a_date() {
        let date: BoltDate = NaiveDate::from_ymd_opt(2010, 1, 1).unwrap().into();
        assert_eq!(
            date.into_bytes(Version::V4_1).unwrap(),
            Bytes::from_static(&[0xB1, 0x44, 0xC9, 0x39, 0x12])
        );
    }

    #[test]
    fn should_deserialize_a_date() {
        let mut bytes = Bytes::from_static(&[0xB1, 0x44, 0xC9, 0x39, 0x12]);

        let date: NaiveDate = BoltDate::parse(Version::V4_1, &mut bytes)
            .unwrap()
            .try_into()
            .unwrap();

        assert_eq!(date.to_string(), "2010-01-01");
    }

    #[test]
    fn convert_to_chrono() {
        let date = NaiveDate::from_ymd_opt(2010, 1, 1).unwrap();

        let bolt: BoltDate = date.into();
        let actual: NaiveDate = bolt.try_into().unwrap();

        assert_eq!(actual, date);
    }

    #[test]
    fn convert_to_chrono_negative() {
        let date = NaiveDate::from_ymd_opt(1910, 1, 1).unwrap();

        let bolt: BoltDate = date.into();
        let actual: NaiveDate = bolt.try_into().unwrap();

        assert_eq!(actual, date);
    }
}