Skip to main content

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