diesel/sqlite/types/date_and_time/
mod.rs

1use crate::deserialize::{self, FromSql};
2use crate::serialize::{self, Output, ToSql};
3use crate::sql_types;
4use crate::sqlite::connection::SqliteValue;
5use crate::sqlite::Sqlite;
6
7#[cfg(feature = "chrono")]
8mod chrono;
9#[cfg(feature = "time")]
10mod time;
11
12#[cfg(feature = "sqlite")]
13impl FromSql<sql_types::Date, Sqlite> for String {
14    fn from_sql(value: SqliteValue<'_, '_, '_>) -> deserialize::Result<Self> {
15        FromSql::<sql_types::Text, Sqlite>::from_sql(value)
16    }
17}
18
19#[cfg(feature = "sqlite")]
20impl ToSql<sql_types::Date, Sqlite> for str {
21    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
22        ToSql::<sql_types::Text, Sqlite>::to_sql(self, out)
23    }
24}
25
26#[cfg(feature = "sqlite")]
27impl ToSql<sql_types::Date, Sqlite> for String {
28    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
29        <str as ToSql<sql_types::Date, Sqlite>>::to_sql(self as &str, out)
30    }
31}
32
33#[cfg(feature = "sqlite")]
34impl FromSql<sql_types::Time, Sqlite> for String {
35    fn from_sql(value: SqliteValue<'_, '_, '_>) -> deserialize::Result<Self> {
36        FromSql::<sql_types::Text, Sqlite>::from_sql(value)
37    }
38}
39
40#[cfg(feature = "sqlite")]
41impl ToSql<sql_types::Time, Sqlite> for str {
42    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
43        ToSql::<sql_types::Text, Sqlite>::to_sql(self, out)
44    }
45}
46
47#[cfg(feature = "sqlite")]
48impl ToSql<sql_types::Time, Sqlite> for String {
49    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
50        <str as ToSql<sql_types::Time, Sqlite>>::to_sql(self as &str, out)
51    }
52}
53
54#[cfg(feature = "sqlite")]
55impl FromSql<sql_types::Timestamp, Sqlite> for String {
56    fn from_sql(value: SqliteValue<'_, '_, '_>) -> deserialize::Result<Self> {
57        FromSql::<sql_types::Text, Sqlite>::from_sql(value)
58    }
59}
60
61#[cfg(feature = "sqlite")]
62impl ToSql<sql_types::Timestamp, Sqlite> for str {
63    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
64        ToSql::<sql_types::Text, Sqlite>::to_sql(self, out)
65    }
66}
67
68#[cfg(feature = "sqlite")]
69impl ToSql<sql_types::Timestamp, Sqlite> for String {
70    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
71        <str as ToSql<sql_types::Timestamp, Sqlite>>::to_sql(self as &str, out)
72    }
73}
74
75#[cfg(feature = "sqlite")]
76impl FromSql<sql_types::TimestamptzSqlite, Sqlite> for String {
77    fn from_sql(value: SqliteValue<'_, '_, '_>) -> deserialize::Result<Self> {
78        FromSql::<sql_types::Text, Sqlite>::from_sql(value)
79    }
80}
81
82#[cfg(feature = "sqlite")]
83impl ToSql<sql_types::TimestamptzSqlite, Sqlite> for str {
84    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
85        ToSql::<sql_types::Text, Sqlite>::to_sql(self, out)
86    }
87}
88
89#[cfg(feature = "sqlite")]
90impl ToSql<sql_types::TimestamptzSqlite, Sqlite> for String {
91    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
92        <str as ToSql<sql_types::TimestamptzSqlite, Sqlite>>::to_sql(self as &str, out)
93    }
94}
95
96#[cfg(all(test, feature = "chrono", feature = "time"))]
97#[allow(clippy::cast_possible_truncation)] // it's a test
98mod tests {
99    extern crate chrono;
100    extern crate time;
101
102    use chrono::{
103        DateTime, Datelike, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, Timelike, Utc,
104    };
105    use time::{
106        macros::{date, datetime, offset, time},
107        Date, OffsetDateTime, PrimitiveDateTime, Time,
108    };
109
110    use crate::insert_into;
111    use crate::prelude::*;
112    use crate::test_helpers::connection;
113
114    crate::table! {
115        table_timestamp_tz(id) {
116            id -> Integer,
117            timestamp_with_tz -> TimestamptzSqlite,
118        }
119    }
120    crate::table! {
121      table_timestamp(id) {
122          id -> Integer,
123          timestamp -> Timestamp
124      }
125    }
126    crate::table! {
127        table_date(id) {
128            id -> Integer,
129            date -> Date
130        }
131    }
132    crate::table! {
133        table_time(id) {
134            id -> Integer,
135            time -> Time
136        }
137    }
138
139    fn eq_date(left: Date, right: NaiveDate) -> bool {
140        left.year() == right.year()
141            && left.month() as u8 == right.month() as u8
142            && left.day() == right.day() as u8
143    }
144
145    fn eq_time(left: Time, right: NaiveTime) -> bool {
146        left.hour() == right.hour() as u8
147            && left.minute() == right.minute() as u8
148            && left.second() == right.second() as u8
149            && left.nanosecond() == right.nanosecond()
150    }
151
152    fn eq_datetime(left: PrimitiveDateTime, right: NaiveDateTime) -> bool {
153        eq_date(left.date(), right.date()) && eq_time(left.time(), right.time())
154    }
155
156    fn eq_datetime_utc(left: OffsetDateTime, right: DateTime<Utc>) -> bool {
157        left.unix_timestamp_nanos() == right.timestamp_nanos_opt().unwrap() as i128
158    }
159
160    fn eq_datetime_offset(left: OffsetDateTime, right: DateTime<FixedOffset>) -> bool {
161        left.unix_timestamp_nanos() == right.timestamp_nanos_opt().unwrap() as i128
162    }
163
164    fn create_tables(conn: &mut SqliteConnection) {
165        crate::sql_query(
166            "CREATE TABLE table_timestamp_tz(id INTEGER PRIMARY KEY, timestamp_with_tz TEXT);",
167        )
168        .execute(conn)
169        .unwrap();
170
171        crate::sql_query("CREATE TABLE table_timestamp(id INTEGER PRIMARY KEY, timestamp TEXT);")
172            .execute(conn)
173            .unwrap();
174
175        crate::sql_query("CREATE TABLE table_date(id INTEGER PRIMARY KEY, date TEXT);")
176            .execute(conn)
177            .unwrap();
178
179        crate::sql_query("CREATE TABLE table_time(id INTEGER PRIMARY KEY, time TEXT);")
180            .execute(conn)
181            .unwrap();
182    }
183
184    #[diesel_test_helper::test]
185    fn time_to_chrono_date() {
186        let conn = &mut connection();
187        create_tables(conn);
188
189        let original = date!(2000 - 1 - 1);
190
191        insert_into(table_date::table)
192            .values(vec![(table_date::id.eq(1), table_date::date.eq(original))])
193            .execute(conn)
194            .unwrap();
195
196        let translated = table_date::table
197            .select(table_date::date)
198            .get_result::<NaiveDate>(conn)
199            .unwrap();
200
201        assert!(eq_date(original, translated))
202    }
203
204    #[diesel_test_helper::test]
205    fn chrono_to_time_date() {
206        let conn = &mut connection();
207        create_tables(conn);
208
209        let original = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap();
210
211        insert_into(table_date::table)
212            .values(vec![(table_date::id.eq(1), table_date::date.eq(original))])
213            .execute(conn)
214            .unwrap();
215
216        let translated = table_date::table
217            .select(table_date::date)
218            .get_result::<Date>(conn)
219            .unwrap();
220
221        assert!(eq_date(translated, original))
222    }
223
224    #[diesel_test_helper::test]
225    fn time_to_chrono_time() {
226        let conn = &mut connection();
227        create_tables(conn);
228
229        let original = time!(1:1:1.001);
230
231        insert_into(table_time::table)
232            .values(vec![(table_time::id.eq(1), table_time::time.eq(original))])
233            .execute(conn)
234            .unwrap();
235
236        let translated = table_time::table
237            .select(table_time::time)
238            .get_result::<NaiveTime>(conn)
239            .unwrap();
240
241        assert!(eq_time(original, translated))
242    }
243
244    #[diesel_test_helper::test]
245    fn chrono_to_time_time() {
246        let conn = &mut connection();
247        create_tables(conn);
248
249        let original = NaiveTime::from_hms_milli_opt(1, 1, 1, 1).unwrap();
250
251        insert_into(table_time::table)
252            .values(vec![(table_time::id.eq(1), table_time::time.eq(original))])
253            .execute(conn)
254            .unwrap();
255
256        let translated = table_time::table
257            .select(table_time::time)
258            .get_result::<Time>(conn)
259            .unwrap();
260
261        assert!(eq_time(translated, original))
262    }
263
264    #[diesel_test_helper::test]
265    fn time_to_chrono_datetime() {
266        let conn = &mut connection();
267        create_tables(conn);
268
269        let original = datetime!(2000-1-1 1:1:1.001);
270
271        insert_into(table_timestamp::table)
272            .values(vec![(
273                table_timestamp::id.eq(1),
274                table_timestamp::timestamp.eq(original),
275            )])
276            .execute(conn)
277            .unwrap();
278
279        let translated = table_timestamp::table
280            .select(table_timestamp::timestamp)
281            .get_result::<NaiveDateTime>(conn)
282            .unwrap();
283
284        assert!(eq_datetime(original, translated))
285    }
286
287    #[diesel_test_helper::test]
288    fn chrono_to_time_datetime() {
289        let conn = &mut connection();
290        create_tables(conn);
291
292        let original = NaiveDate::from_ymd_opt(2000, 1, 1)
293            .unwrap()
294            .and_hms_milli_opt(1, 1, 1, 1)
295            .unwrap();
296
297        insert_into(table_timestamp::table)
298            .values(vec![(
299                table_timestamp::id.eq(1),
300                table_timestamp::timestamp.eq(original),
301            )])
302            .execute(conn)
303            .unwrap();
304
305        let translated = table_timestamp::table
306            .select(table_timestamp::timestamp)
307            .get_result::<PrimitiveDateTime>(conn)
308            .unwrap();
309
310        assert!(eq_datetime(translated, original))
311    }
312
313    #[diesel_test_helper::test]
314    fn chrono_to_time_datetime_utc() {
315        let conn = &mut connection();
316        create_tables(conn);
317
318        let original = Utc::now();
319
320        insert_into(table_timestamp_tz::table)
321            .values(vec![(
322                table_timestamp_tz::id.eq(1),
323                table_timestamp_tz::timestamp_with_tz.eq(original),
324            )])
325            .execute(conn)
326            .unwrap();
327
328        let translated = table_timestamp_tz::table
329            .select(table_timestamp_tz::timestamp_with_tz)
330            .get_result::<OffsetDateTime>(conn)
331            .unwrap();
332
333        assert!(eq_datetime_utc(translated, original))
334    }
335
336    #[diesel_test_helper::test]
337    fn time_to_chrono_datetime_utc() {
338        let conn = &mut connection();
339        create_tables(conn);
340
341        let original = OffsetDateTime::now_utc();
342
343        insert_into(table_timestamp_tz::table)
344            .values(vec![(
345                table_timestamp_tz::id.eq(1),
346                table_timestamp_tz::timestamp_with_tz.eq(original),
347            )])
348            .execute(conn)
349            .unwrap();
350
351        let translated = table_timestamp_tz::table
352            .select(table_timestamp_tz::timestamp_with_tz)
353            .get_result::<DateTime<Utc>>(conn)
354            .unwrap();
355
356        assert!(eq_datetime_utc(original, translated))
357    }
358
359    #[diesel_test_helper::test]
360    fn chrono_to_time_datetime_timezone() {
361        let conn = &mut connection();
362        create_tables(conn);
363
364        let original = Utc::now().with_timezone(&FixedOffset::east_opt(5 * 3600).unwrap());
365
366        insert_into(table_timestamp_tz::table)
367            .values(vec![(
368                table_timestamp_tz::id.eq(1),
369                table_timestamp_tz::timestamp_with_tz.eq(original),
370            )])
371            .execute(conn)
372            .unwrap();
373
374        let translated = table_timestamp_tz::table
375            .select(table_timestamp_tz::timestamp_with_tz)
376            .get_result::<OffsetDateTime>(conn)
377            .unwrap();
378
379        assert!(eq_datetime_offset(translated, original))
380    }
381
382    #[diesel_test_helper::test]
383    fn time_to_chrono_datetime_offset() {
384        let conn = &mut connection();
385        create_tables(conn);
386
387        let original = OffsetDateTime::now_utc().to_offset(offset!(+5));
388
389        insert_into(table_timestamp_tz::table)
390            .values(vec![(
391                table_timestamp_tz::id.eq(1),
392                table_timestamp_tz::timestamp_with_tz.eq(original),
393            )])
394            .execute(conn)
395            .unwrap();
396
397        let translated = table_timestamp_tz::table
398            .select(table_timestamp_tz::timestamp_with_tz)
399            .get_result::<DateTime<Utc>>(conn)
400            .unwrap();
401
402        assert!(eq_datetime_utc(original, translated))
403    }
404}