1use chrono::{DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
4
5use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
6use crate::Result;
7
8impl ToSql for NaiveDate {
10 #[inline]
11 fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
12 let date_str = self.format("%F").to_string();
13 Ok(ToSqlOutput::from(date_str))
14 }
15}
16
17impl FromSql for NaiveDate {
19 #[inline]
20 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
21 value
22 .as_str()
23 .and_then(|s| Self::parse_from_str(s, "%F").map_err(FromSqlError::other))
24 }
25}
26
27impl ToSql for NaiveTime {
29 #[inline]
30 fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
31 let date_str = self.format("%T%.f").to_string();
32 Ok(ToSqlOutput::from(date_str))
33 }
34}
35
36impl FromSql for NaiveTime {
38 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
39 value.as_str().and_then(|s| {
40 let fmt = match s.len() {
41 5 => "%H:%M",
42 8 => "%T",
43 _ => "%T%.f",
44 };
45 Self::parse_from_str(s, fmt).map_err(FromSqlError::other)
46 })
47 }
48}
49
50impl ToSql for NaiveDateTime {
53 #[inline]
54 fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
55 let date_str = self.format("%F %T%.f").to_string();
56 Ok(ToSqlOutput::from(date_str))
57 }
58}
59
60impl FromSql for NaiveDateTime {
64 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
65 value.as_str().and_then(|s| {
66 let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' {
67 "%FT%T%.f"
68 } else {
69 "%F %T%.f"
70 };
71
72 Self::parse_from_str(s, fmt).map_err(FromSqlError::other)
73 })
74 }
75}
76
77impl ToSql for DateTime<Utc> {
80 #[inline]
81 fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
82 let date_str = self.format("%F %T%.f%:z").to_string();
83 Ok(ToSqlOutput::from(date_str))
84 }
85}
86
87impl ToSql for DateTime<Local> {
90 #[inline]
91 fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
92 let date_str = self.with_timezone(&Utc).format("%F %T%.f%:z").to_string();
93 Ok(ToSqlOutput::from(date_str))
94 }
95}
96
97impl ToSql for DateTime<FixedOffset> {
100 #[inline]
101 fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
102 let date_str = self.format("%F %T%.f%:z").to_string();
103 Ok(ToSqlOutput::from(date_str))
104 }
105}
106
107impl FromSql for DateTime<Utc> {
109 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
110 {
111 let s = value.as_str()?;
113
114 let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' {
115 "%FT%T%.f%#z"
116 } else {
117 "%F %T%.f%#z"
118 };
119
120 if let Ok(dt) = DateTime::parse_from_str(s, fmt) {
121 return Ok(dt.with_timezone(&Utc));
122 }
123 }
124
125 NaiveDateTime::column_result(value).map(|dt| Utc.from_utc_datetime(&dt))
127 }
128}
129
130impl FromSql for DateTime<Local> {
132 #[inline]
133 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
134 let utc_dt = DateTime::<Utc>::column_result(value)?;
135 Ok(utc_dt.with_timezone(&Local))
136 }
137}
138
139impl FromSql for DateTime<FixedOffset> {
141 #[inline]
142 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
143 let s = String::column_result(value)?;
144 Self::parse_from_rfc3339(s.as_str())
145 .or_else(|_| Self::parse_from_str(s.as_str(), "%F %T%.f%:z"))
146 .map_err(FromSqlError::other)
147 }
148}
149
150#[cfg(test)]
151mod test {
152 use crate::{
153 types::{FromSql, ValueRef},
154 Connection, Result,
155 };
156 use chrono::{
157 DateTime, Duration, FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc,
158 };
159
160 fn checked_memory_handle() -> Result<Connection> {
161 let db = Connection::open_in_memory()?;
162 db.execute_batch("CREATE TABLE foo (t TEXT, i INTEGER, f FLOAT, b BLOB)")?;
163 Ok(db)
164 }
165
166 #[test]
167 fn test_naive_date() -> Result<()> {
168 let db = checked_memory_handle()?;
169 let date = NaiveDate::from_ymd_opt(2016, 2, 23).unwrap();
170 db.execute("INSERT INTO foo (t) VALUES (?1)", [date])?;
171
172 let s: String = db.one_column("SELECT t FROM foo", [])?;
173 assert_eq!("2016-02-23", s);
174 let t: NaiveDate = db.one_column("SELECT t FROM foo", [])?;
175 assert_eq!(date, t);
176 Ok(())
177 }
178
179 #[test]
180 fn test_naive_time() -> Result<()> {
181 let db = checked_memory_handle()?;
182 let time = NaiveTime::from_hms_opt(23, 56, 4).unwrap();
183 db.execute("INSERT INTO foo (t) VALUES (?1)", [time])?;
184
185 let s: String = db.one_column("SELECT t FROM foo", [])?;
186 assert_eq!("23:56:04", s);
187 let v: NaiveTime = db.one_column("SELECT t FROM foo", [])?;
188 assert_eq!(time, v);
189 Ok(())
190 }
191
192 #[test]
193 fn test_naive_date_time() -> Result<()> {
194 let db = checked_memory_handle()?;
195 let date = NaiveDate::from_ymd_opt(2016, 2, 23).unwrap();
196 let time = NaiveTime::from_hms_opt(23, 56, 4).unwrap();
197 let dt = NaiveDateTime::new(date, time);
198
199 db.execute("INSERT INTO foo (t) VALUES (?1)", [dt])?;
200
201 let s: String = db.one_column("SELECT t FROM foo", [])?;
202 assert_eq!("2016-02-23 23:56:04", s);
203 let v: NaiveDateTime = db.one_column("SELECT t FROM foo", [])?;
204 assert_eq!(dt, v);
205
206 db.execute("UPDATE foo set b = datetime(t)", [])?; let hms: NaiveDateTime = db.one_column("SELECT b FROM foo", [])?;
208 assert_eq!(dt, hms);
209 Ok(())
210 }
211
212 #[test]
213 fn test_date_time_utc() -> Result<()> {
214 let db = checked_memory_handle()?;
215 let date = NaiveDate::from_ymd_opt(2016, 2, 23).unwrap();
216 let time = NaiveTime::from_hms_milli_opt(23, 56, 4, 789).unwrap();
217 let dt = NaiveDateTime::new(date, time);
218 let utc = Utc.from_utc_datetime(&dt);
219
220 db.execute("INSERT INTO foo (t) VALUES (?1)", [utc])?;
221
222 let s: String = db.one_column("SELECT t FROM foo", [])?;
223 assert_eq!("2016-02-23 23:56:04.789+00:00", s);
224
225 let v1: DateTime<Utc> = db.one_column("SELECT t FROM foo", [])?;
226 assert_eq!(utc, v1);
227
228 let v2: DateTime<Utc> = db.one_column("SELECT '2016-02-23 23:56:04.789'", [])?;
229 assert_eq!(utc, v2);
230
231 let v3: DateTime<Utc> = db.one_column("SELECT '2016-02-23 23:56:04'", [])?;
232 assert_eq!(utc - Duration::try_milliseconds(789).unwrap(), v3);
233
234 let v4: DateTime<Utc> = db.one_column("SELECT '2016-02-23 23:56:04.789+00:00'", [])?;
235 assert_eq!(utc, v4);
236 Ok(())
237 }
238
239 #[test]
240 fn test_date_time_local() -> Result<()> {
241 let db = checked_memory_handle()?;
242 let date = NaiveDate::from_ymd_opt(2016, 2, 23).unwrap();
243 let time = NaiveTime::from_hms_milli_opt(23, 56, 4, 789).unwrap();
244 let dt = NaiveDateTime::new(date, time);
245 let local = Local.from_local_datetime(&dt).single().unwrap();
246
247 db.execute("INSERT INTO foo (t) VALUES (?1)", [local])?;
248
249 let s: String = db.one_column("SELECT t FROM foo", [])?;
251 assert!(s.ends_with("+00:00"));
252
253 let v: DateTime<Local> = db.one_column("SELECT t FROM foo", [])?;
254 assert_eq!(local, v);
255 Ok(())
256 }
257
258 #[test]
259 fn test_date_time_fixed() -> Result<()> {
260 let db = checked_memory_handle()?;
261 let time = DateTime::parse_from_rfc3339("2020-04-07T11:23:45+04:00").unwrap();
262
263 db.execute("INSERT INTO foo (t) VALUES (?1)", [time])?;
264
265 let s: String = db.one_column("SELECT t FROM foo", [])?;
267 assert!(s.ends_with("+04:00"));
268
269 let v: DateTime<FixedOffset> = db.one_column("SELECT t FROM foo", [])?;
270 assert_eq!(time.offset(), v.offset());
271 assert_eq!(time, v);
272 Ok(())
273 }
274
275 #[test]
276 fn test_sqlite_functions() -> Result<()> {
277 let db = checked_memory_handle()?;
278 db.one_column::<NaiveTime, _>("SELECT CURRENT_TIME", [])?;
279 db.one_column::<NaiveDate, _>("SELECT CURRENT_DATE", [])?;
280 db.one_column::<NaiveDateTime, _>("SELECT CURRENT_TIMESTAMP", [])?;
281 db.one_column::<DateTime<Utc>, _>("SELECT CURRENT_TIMESTAMP", [])?;
282 Ok(())
283 }
284
285 #[test]
286 fn test_naive_date_time_param() -> Result<()> {
287 let db = checked_memory_handle()?;
288 db.one_column::<bool, _>("SELECT 1 WHERE ?1 BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')", [Utc::now().naive_utc()])?;
289 Ok(())
290 }
291
292 #[test]
293 fn test_date_time_param() -> Result<()> {
294 let db = checked_memory_handle()?;
295 db.one_column::<bool, _>("SELECT 1 WHERE ?1 BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')", [Utc::now()])?;
296 Ok(())
297 }
298
299 #[test]
300 fn test_lenient_parse_timezone() {
301 DateTime::<Utc>::column_result(ValueRef::Text(b"1970-01-01T00:00:00Z")).unwrap();
302 DateTime::<Utc>::column_result(ValueRef::Text(b"1970-01-01T00:00:00+00")).unwrap();
303 }
304}