sqlx_rxqlite/types/
chrono.rs

1use std::fmt::Display;
2
3use crate::value::ValueRef;
4use crate::{
5    decode::Decode,
6    encode::{Encode, IsNull},
7    error::BoxDynError,
8    type_info::DataType,
9    types::Type,
10    RXQLite, /*RXQLiteArgumentValue,*/ RXQLiteTypeInfo, RXQLiteValueRef,
11    RXQLiteValueData,
12};
13use chrono::FixedOffset;
14use chrono::{
15    DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, Offset, SecondsFormat, TimeZone, Utc,
16};
17
18impl<Tz: TimeZone> Type<RXQLite> for DateTime<Tz> {
19    fn type_info() -> RXQLiteTypeInfo {
20        RXQLiteTypeInfo(DataType::Datetime)
21    }
22
23    fn compatible(ty: &RXQLiteTypeInfo) -> bool {
24        <NaiveDateTime as Type<RXQLite>>::compatible(ty)
25    }
26}
27
28impl Type<RXQLite> for NaiveDateTime {
29    fn type_info() -> RXQLiteTypeInfo {
30        RXQLiteTypeInfo(DataType::Datetime)
31    }
32
33    fn compatible(ty: &RXQLiteTypeInfo) -> bool {
34        matches!(
35            ty.0,
36            DataType::Datetime | DataType::Text | DataType::Int64 | DataType::Int | DataType::Float
37        )
38    }
39}
40
41impl Type<RXQLite> for NaiveDate {
42    fn type_info() -> RXQLiteTypeInfo {
43        RXQLiteTypeInfo(DataType::Date)
44    }
45
46    fn compatible(ty: &RXQLiteTypeInfo) -> bool {
47        matches!(ty.0, DataType::Date | DataType::Text)
48    }
49}
50
51impl Type<RXQLite> for NaiveTime {
52    fn type_info() -> RXQLiteTypeInfo {
53        RXQLiteTypeInfo(DataType::Time)
54    }
55
56    fn compatible(ty: &RXQLiteTypeInfo) -> bool {
57        matches!(ty.0, DataType::Time | DataType::Text)
58    }
59}
60
61impl<Tz: TimeZone> Encode<'_, RXQLite> for DateTime<Tz>
62where
63    Tz::Offset: Display,
64{
65    fn encode_by_ref(&self, buf: &mut Vec<rxqlite_common::Value>) -> IsNull {
66        Encode::<RXQLite>::encode(self.to_rfc3339_opts(SecondsFormat::AutoSi, false), buf)
67    }
68}
69
70impl Encode<'_, RXQLite> for NaiveDateTime {
71    fn encode_by_ref(&self, buf: &mut Vec<rxqlite_common::Value>) -> IsNull {
72        Encode::<RXQLite>::encode(self.format("%F %T%.f").to_string(), buf)
73    }
74}
75
76impl Encode<'_, RXQLite> for NaiveDate {
77    fn encode_by_ref(&self, buf: &mut Vec<rxqlite_common::Value>) -> IsNull {
78        Encode::<RXQLite>::encode(self.format("%F").to_string(), buf)
79    }
80}
81
82impl Encode<'_, RXQLite> for NaiveTime {
83    fn encode_by_ref(&self, buf: &mut Vec<rxqlite_common::Value>) -> IsNull {
84        Encode::<RXQLite>::encode(self.format("%T%.f").to_string(), buf)
85    }
86}
87
88impl<'r> Decode<'r, RXQLite> for DateTime<Utc> {
89    fn decode(value: RXQLiteValueRef<'r>) -> Result<Self, BoxDynError> {
90        Ok(Utc.from_utc_datetime(&decode_datetime(value)?.naive_utc()))
91    }
92}
93
94impl<'r> Decode<'r, RXQLite> for DateTime<Local> {
95    fn decode(value: RXQLiteValueRef<'r>) -> Result<Self, BoxDynError> {
96        Ok(Local.from_utc_datetime(&decode_datetime(value)?.naive_utc()))
97    }
98}
99
100impl<'r> Decode<'r, RXQLite> for DateTime<FixedOffset> {
101    fn decode(value: RXQLiteValueRef<'r>) -> Result<Self, BoxDynError> {
102        decode_datetime(value)
103    }
104}
105
106fn decode_datetime(value: RXQLiteValueRef<'_>) -> Result<DateTime<FixedOffset>, BoxDynError> {
107    let dt = match value.type_info().0 {
108        DataType::Null => decode_datetime_from_text(&value.text()?),
109        DataType::Text => decode_datetime_from_text(&value.text()?),
110        DataType::Int | DataType::Int64 => decode_datetime_from_int(value.int64()?),
111        DataType::Float => decode_datetime_from_float(value.double()?),
112        DataType::Datetime => {
113          match value.0 {
114            RXQLiteValueData::Value(value)=>{
115              match &*value.handle {
116                rxqlite_common::Value::DateTime(utc) => {
117                  let fixed_offset_time = utc.with_timezone(&Utc.fix());
118                  Some(fixed_offset_time)
119                }
120                _=>None,
121              }
122            }
123          }
124        }
125        _ => None,
126    };
127
128    if let Some(dt) = dt {
129        Ok(dt)
130    } else {
131        Err(format!("invalid datetime: {}", value.text()?).into())
132    }
133}
134
135fn decode_datetime_from_text(value: &str) -> Option<DateTime<FixedOffset>> {
136    if let Ok(dt) = DateTime::parse_from_rfc3339(value) {
137        return Some(dt);
138    }
139    if let Ok::<DateTime<FixedOffset>,_>(dt) = value.parse() {
140        return Some(dt);
141    }
142    // Loop over common date time patterns, inspired by Diesel
143    // https://github.com/diesel-rs/diesel/blob/93ab183bcb06c69c0aee4a7557b6798fd52dd0d8/diesel/src/sqlite/types/date_and_time/chrono.rs#L56-L97
144    let sqlite_datetime_formats = &[
145        // Most likely format
146        "%F %T%.f",
147        // Other formats in order of appearance in docs
148        "%F %R",
149        "%F %RZ",
150        "%F %R%:z",
151        "%F %T%.fZ",
152        "%F %T%.f%:z",
153        "%FT%R",
154        "%FT%RZ",
155        "%FT%R%:z",
156        "%FT%T%.f",
157        "%FT%T%.fZ",
158        "%FT%T%.f%:z",
159    ];
160
161    for format in sqlite_datetime_formats {
162        if let Ok(dt) = DateTime::parse_from_str(value, format) {
163            return Some(dt);
164        }
165
166        if let Ok(dt) = NaiveDateTime::parse_from_str(value, format) {
167            return Some(Utc.fix().from_utc_datetime(&dt));
168        }
169    }
170
171    None
172}
173
174fn decode_datetime_from_int(value: i64) -> Option<DateTime<FixedOffset>> {
175    Utc.fix().timestamp_opt(value, 0).single()
176}
177
178fn decode_datetime_from_float(value: f64) -> Option<DateTime<FixedOffset>> {
179    let epoch_in_julian_days = 2_440_587.5;
180    let seconds_in_day = 86400.0;
181    let timestamp = (value - epoch_in_julian_days) * seconds_in_day;
182    let seconds = timestamp as i64;
183    let nanos = (timestamp.fract() * 1E9) as u32;
184
185    Utc.fix().timestamp_opt(seconds, nanos).single()
186}
187
188impl<'r> Decode<'r, RXQLite> for NaiveDateTime {
189    fn decode(value: RXQLiteValueRef<'r>) -> Result<Self, BoxDynError> {
190        Ok(decode_datetime(value)?.naive_local())
191    }
192}
193
194impl<'r> Decode<'r, RXQLite> for NaiveDate {
195    fn decode(value: RXQLiteValueRef<'r>) -> Result<Self, BoxDynError> {
196        Ok(NaiveDate::parse_from_str(&value.text()?, "%F")?)
197    }
198}
199
200impl<'r> Decode<'r, RXQLite> for NaiveTime {
201    fn decode(value: RXQLiteValueRef<'r>) -> Result<Self, BoxDynError> {
202        let value = value.text()?;
203
204        // Loop over common time patterns, inspired by Diesel
205        // https://github.com/diesel-rs/diesel/blob/93ab183bcb06c69c0aee4a7557b6798fd52dd0d8/diesel/src/sqlite/types/date_and_time/chrono.rs#L29-L47
206        #[rustfmt::skip] // don't like how rustfmt mangles the comments
207        let sqlite_time_formats = &[
208            // Most likely format
209            "%T.f", "%T%.f",
210            // Other formats in order of appearance in docs
211            "%R", "%RZ", "%T%.fZ", "%R%:z", "%T%.f%:z",
212        ];
213
214        for format in sqlite_time_formats {
215            if let Ok(dt) = NaiveTime::parse_from_str(&value, format) {
216                return Ok(dt);
217            }
218        }
219
220        Err(format!("invalid time: {value}").into())
221    }
222}