1use chrono::{DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
4
5use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, Type, 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 if value.data_type() == Type::Integer {
111 return value.as_i64().and_then(|i| {
112 DateTime::from_timestamp_secs(i).ok_or_else(|| FromSqlError::OutOfRange(i))
113 });
114 }
115 {
116 let s = value.as_str()?;
118
119 let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' {
120 "%FT%T%.f%#z"
121 } else {
122 "%F %T%.f%#z"
123 };
124
125 if let Ok(dt) = DateTime::parse_from_str(s, fmt) {
126 return Ok(dt.with_timezone(&Utc));
127 }
128 }
129
130 NaiveDateTime::column_result(value).map(|dt| Utc.from_utc_datetime(&dt))
132 }
133}
134
135impl FromSql for DateTime<Local> {
137 #[inline]
138 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
139 let utc_dt = DateTime::<Utc>::column_result(value)?;
140 Ok(utc_dt.with_timezone(&Local))
141 }
142}
143
144impl FromSql for DateTime<FixedOffset> {
146 #[inline]
147 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
148 let s = String::column_result(value)?;
149 Self::parse_from_rfc3339(s.as_str())
150 .or_else(|_| Self::parse_from_str(s.as_str(), "%F %T%.f%:z"))
151 .map_err(FromSqlError::other)
152 }
153}
154
155#[cfg(test)]
156mod test {
157 #[cfg(all(target_family = "wasm", target_os = "unknown"))]
158 use wasm_bindgen_test::wasm_bindgen_test as test;
159
160 use crate::{
161 types::{FromSql, ValueRef},
162 Connection, Result,
163 };
164 use chrono::{
165 DateTime, Duration, FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone,
166 Timelike, Utc,
167 };
168
169 fn checked_memory_handle() -> Result<Connection> {
170 let db = Connection::open_in_memory()?;
171 db.execute_batch("CREATE TABLE foo (t TEXT, i INTEGER AS (strftime('%s', t)), b BLOB)")?;
172 Ok(db)
173 }
174
175 #[test]
176 fn test_naive_date() -> Result<()> {
177 let db = checked_memory_handle()?;
178 let date = NaiveDate::from_ymd_opt(2016, 2, 23).unwrap();
179 db.execute("INSERT INTO foo (t) VALUES (?1)", [date])?;
180
181 let s: String = db.one_column("SELECT t FROM foo", [])?;
182 assert_eq!("2016-02-23", s);
183 let t: NaiveDate = db.one_column("SELECT t FROM foo", [])?;
184 assert_eq!(date, t);
185 Ok(())
186 }
187
188 #[test]
189 fn test_naive_time() -> Result<()> {
190 let db = checked_memory_handle()?;
191 let time = NaiveTime::from_hms_opt(23, 56, 4).unwrap();
192 db.execute("INSERT INTO foo (t) VALUES (?1)", [time])?;
193
194 let s: String = db.one_column("SELECT t FROM foo", [])?;
195 assert_eq!("23:56:04", s);
196 let v: NaiveTime = db.one_column("SELECT t FROM foo", [])?;
197 assert_eq!(time, v);
198 Ok(())
199 }
200
201 #[test]
202 fn test_naive_date_time() -> Result<()> {
203 let db = checked_memory_handle()?;
204 let date = NaiveDate::from_ymd_opt(2016, 2, 23).unwrap();
205 let time = NaiveTime::from_hms_opt(23, 56, 4).unwrap();
206 let dt = NaiveDateTime::new(date, time);
207
208 db.execute("INSERT INTO foo (t) VALUES (?1)", [dt])?;
209
210 let s: String = db.one_column("SELECT t FROM foo", [])?;
211 assert_eq!("2016-02-23 23:56:04", s);
212 let v: NaiveDateTime = db.one_column("SELECT t FROM foo", [])?;
213 assert_eq!(dt, v);
214
215 db.execute("UPDATE foo set b = datetime(t)", [])?; let hms: NaiveDateTime = db.one_column("SELECT b FROM foo", [])?;
217 assert_eq!(dt, hms);
218 Ok(())
219 }
220
221 #[test]
222 fn test_date_time_utc() -> Result<()> {
223 let db = checked_memory_handle()?;
224 let date = NaiveDate::from_ymd_opt(2016, 2, 23).unwrap();
225 let time = NaiveTime::from_hms_milli_opt(23, 56, 4, 789).unwrap();
226 let dt = NaiveDateTime::new(date, time);
227 let utc = Utc.from_utc_datetime(&dt);
228
229 db.execute("INSERT INTO foo (t) VALUES (?1)", [utc])?;
230
231 let s: String = db.one_column("SELECT t FROM foo", [])?;
232 assert_eq!("2016-02-23 23:56:04.789+00:00", s);
233
234 let v1: DateTime<Utc> = db.one_column("SELECT t FROM foo", [])?;
235 assert_eq!(utc, v1);
236 let v1: DateTime<Utc> = db.one_column("SELECT i FROM foo", [])?;
237 assert_eq!(utc.with_nanosecond(0).unwrap(), v1);
238
239 let v2: DateTime<Utc> = db.one_column("SELECT '2016-02-23 23:56:04.789'", [])?;
240 assert_eq!(utc, v2);
241
242 let v3: DateTime<Utc> = db.one_column("SELECT '2016-02-23 23:56:04'", [])?;
243 assert_eq!(utc - Duration::try_milliseconds(789).unwrap(), v3);
244
245 let v4: DateTime<Utc> = db.one_column("SELECT '2016-02-23 23:56:04.789+00:00'", [])?;
246 assert_eq!(utc, v4);
247 Ok(())
248 }
249
250 #[test]
251 fn test_date_time_local() -> Result<()> {
252 let db = checked_memory_handle()?;
253 let date = NaiveDate::from_ymd_opt(2016, 2, 23).unwrap();
254 let time = NaiveTime::from_hms_milli_opt(23, 56, 4, 789).unwrap();
255 let dt = NaiveDateTime::new(date, time);
256 let local = Local.from_local_datetime(&dt).single().unwrap();
257
258 db.execute("INSERT INTO foo (t) VALUES (?1)", [local])?;
259
260 let s: String = db.one_column("SELECT t FROM foo", [])?;
262 assert!(s.ends_with("+00:00"));
263
264 let v: DateTime<Local> = db.one_column("SELECT t FROM foo", [])?;
265 assert_eq!(local, v);
266 let v: DateTime<Local> = db.one_column("SELECT i FROM foo", [])?;
267 assert_eq!(local.with_nanosecond(0).unwrap(), v);
268 Ok(())
269 }
270
271 #[test]
272 fn test_date_time_fixed() -> Result<()> {
273 let db = checked_memory_handle()?;
274 let time = DateTime::parse_from_rfc3339("2020-04-07T11:23:45+04:00").unwrap();
275
276 db.execute("INSERT INTO foo (t) VALUES (?1)", [time])?;
277
278 let s: String = db.one_column("SELECT t FROM foo", [])?;
280 assert!(s.ends_with("+04:00"));
281
282 let v: DateTime<FixedOffset> = db.one_column("SELECT t FROM foo", [])?;
283 assert_eq!(time.offset(), v.offset());
284 assert_eq!(time, v);
285 Ok(())
286 }
287
288 #[test]
289 fn test_sqlite_functions() -> Result<()> {
290 let db = checked_memory_handle()?;
291 db.one_column::<NaiveTime, _>("SELECT CURRENT_TIME", [])?;
292 db.one_column::<NaiveDate, _>("SELECT CURRENT_DATE", [])?;
293 db.one_column::<NaiveDateTime, _>("SELECT CURRENT_TIMESTAMP", [])?;
294 db.one_column::<DateTime<Utc>, _>("SELECT CURRENT_TIMESTAMP", [])?;
295 Ok(())
296 }
297
298 #[test]
299 fn test_naive_date_time_param() -> Result<()> {
300 let db = checked_memory_handle()?;
301 db.one_column::<bool, _>("SELECT 1 WHERE ?1 BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')", [Utc::now().naive_utc()])?;
302 Ok(())
303 }
304
305 #[test]
306 fn test_date_time_param() -> Result<()> {
307 let db = checked_memory_handle()?;
308 db.one_column::<bool, _>("SELECT 1 WHERE ?1 BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')", [Utc::now()])?;
309 Ok(())
310 }
311
312 #[test]
313 fn test_lenient_parse_timezone() {
314 DateTime::<Utc>::column_result(ValueRef::Text(b"1970-01-01T00:00:00Z")).unwrap();
315 DateTime::<Utc>::column_result(ValueRef::Text(b"1970-01-01T00:00:00+00")).unwrap();
316 }
317}