Skip to main content

diesel/mysql_like/types/date_and_time/
chrono.rs

1use chrono::{Datelike, NaiveDate, NaiveDateTime, NaiveTime, Timelike};
2use std::os::raw as libc;
3
4use crate::deserialize::{self, FromSql};
5use crate::mysql_like::{MysqlLikeBackend, MysqlValue};
6use crate::serialize::{self, Output, ToSql};
7use crate::sql_types::{Date, Datetime, Time, Timestamp};
8
9use super::{MysqlTime, MysqlTimestampType};
10
11#[cfg(feature = "chrono")]
12impl<DB: MysqlLikeBackend> ToSql<Datetime, DB> for NaiveDateTime {
13    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> serialize::Result {
14        <NaiveDateTime as ToSql<Timestamp, DB>>::to_sql(self, out)
15    }
16}
17
18#[cfg(feature = "chrono")]
19impl<DB: MysqlLikeBackend> FromSql<Datetime, DB> for NaiveDateTime {
20    fn from_sql(bytes: MysqlValue<'_>) -> deserialize::Result<Self> {
21        <NaiveDateTime as FromSql<Timestamp, DB>>::from_sql(bytes)
22    }
23}
24
25#[cfg(feature = "chrono")]
26impl<DB: MysqlLikeBackend> ToSql<Timestamp, DB> for NaiveDateTime {
27    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> serialize::Result {
28        let mysql_time = MysqlTime {
29            year: self.year().try_into()?,
30            month: self.month() as libc::c_uint,
31            day: self.day() as libc::c_uint,
32            hour: self.hour() as libc::c_uint,
33            minute: self.minute() as libc::c_uint,
34            second: self.second() as libc::c_uint,
35            #[allow(deprecated)] // otherwise we would need to bump our minimal chrono version
36            second_part: libc::c_ulong::from(self.timestamp_subsec_micros()),
37            neg: false,
38            time_type: MysqlTimestampType::MYSQL_TIMESTAMP_DATETIME,
39            time_zone_displacement: 0,
40        };
41
42        <MysqlTime as ToSql<Timestamp, DB>>::to_sql(&mysql_time, &mut out.reborrow())
43    }
44}
45
46#[cfg(feature = "chrono")]
47impl<DB: MysqlLikeBackend> FromSql<Timestamp, DB> for NaiveDateTime {
48    fn from_sql(bytes: MysqlValue<'_>) -> deserialize::Result<Self> {
49        let mysql_time = <MysqlTime as FromSql<Timestamp, DB>>::from_sql(bytes)?;
50
51        let micro = mysql_time.second_part.try_into()?;
52        NaiveDate::from_ymd_opt(
53            mysql_time.year.try_into()?,
54            mysql_time.month,
55            mysql_time.day,
56        )
57        .and_then(|v| {
58            v.and_hms_micro_opt(mysql_time.hour, mysql_time.minute, mysql_time.second, micro)
59        })
60        .ok_or_else(|| ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("Cannot parse this date: {0:?}",
                mysql_time))
    })format!("Cannot parse this date: {mysql_time:?}").into())
61    }
62}
63
64#[cfg(feature = "chrono")]
65impl<DB: MysqlLikeBackend> ToSql<Time, DB> for NaiveTime {
66    fn to_sql<'b>(&'b self, out: &mut serialize::Output<'b, '_, DB>) -> serialize::Result {
67        let mysql_time = MysqlTime {
68            hour: self.hour() as libc::c_uint,
69            minute: self.minute() as libc::c_uint,
70            second: self.second() as libc::c_uint,
71            day: 0,
72            month: 0,
73            second_part: libc::c_ulong::from(self.nanosecond() / 1_000),
74            year: 0,
75            neg: false,
76            time_type: MysqlTimestampType::MYSQL_TIMESTAMP_TIME,
77            time_zone_displacement: 0,
78        };
79
80        <MysqlTime as ToSql<Time, DB>>::to_sql(&mysql_time, &mut out.reborrow())
81    }
82}
83
84#[cfg(feature = "chrono")]
85impl<DB: MysqlLikeBackend> FromSql<Time, DB> for NaiveTime {
86    fn from_sql(bytes: MysqlValue<'_>) -> deserialize::Result<Self> {
87        let mysql_time = <MysqlTime as FromSql<Time, DB>>::from_sql(bytes)?;
88        let micro = mysql_time.second_part.try_into()?;
89        NaiveTime::from_hms_micro_opt(mysql_time.hour, mysql_time.minute, mysql_time.second, micro)
90            .ok_or_else(|| ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("Unable to convert {0:?} to chrono",
                mysql_time))
    })format!("Unable to convert {mysql_time:?} to chrono").into())
91    }
92}
93
94#[cfg(feature = "chrono")]
95impl<DB: MysqlLikeBackend> ToSql<Date, DB> for NaiveDate {
96    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> serialize::Result {
97        let mysql_time = MysqlTime {
98            year: self.year().try_into()?,
99            month: self.month() as libc::c_uint,
100            day: self.day() as libc::c_uint,
101            hour: 0,
102            minute: 0,
103            second: 0,
104            second_part: 0,
105            neg: false,
106            time_type: MysqlTimestampType::MYSQL_TIMESTAMP_DATE,
107            time_zone_displacement: 0,
108        };
109
110        <MysqlTime as ToSql<Date, DB>>::to_sql(&mysql_time, &mut out.reborrow())
111    }
112}
113
114#[cfg(feature = "chrono")]
115impl<DB: MysqlLikeBackend> FromSql<Date, DB> for NaiveDate {
116    fn from_sql(bytes: MysqlValue<'_>) -> deserialize::Result<Self> {
117        let mysql_time = <MysqlTime as FromSql<Date, DB>>::from_sql(bytes)?;
118        NaiveDate::from_ymd_opt(
119            mysql_time.year.try_into()?,
120            mysql_time.month,
121            mysql_time.day,
122        )
123        .ok_or_else(|| ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("Unable to convert {0:?} to chrono",
                mysql_time))
    })format!("Unable to convert {mysql_time:?} to chrono").into())
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    extern crate chrono;
130    extern crate dotenvy;
131
132    use self::chrono::{Duration, NaiveDate, NaiveTime, Utc};
133
134    use crate::dsl::{now, sql};
135    use crate::prelude::*;
136    use crate::select;
137    use crate::sql_types::{Date, Datetime, Time, Timestamp};
138    use crate::test_helpers::connection;
139
140    #[diesel_test_helper::test]
141    fn unix_epoch_encodes_correctly() {
142        let connection = &mut connection();
143        let time = NaiveDate::from_ymd_opt(1970, 1, 1)
144            .unwrap()
145            .and_hms_opt(0, 0, 0)
146            .unwrap();
147        let query = select(sql::<Timestamp>("CAST('1970-01-01' AS DATETIME)").eq(time));
148        assert!(query.get_result::<bool>(connection).unwrap());
149        let query = select(sql::<Datetime>("CAST('1970-01-01' AS DATETIME)").eq(time));
150        assert!(query.get_result::<bool>(connection).unwrap());
151    }
152
153    #[diesel_test_helper::test]
154    fn unix_epoch_decodes_correctly() {
155        let connection = &mut connection();
156        let time = NaiveDate::from_ymd_opt(1970, 1, 1)
157            .unwrap()
158            .and_hms_opt(0, 0, 0)
159            .unwrap();
160        let epoch_from_sql =
161            select(sql::<Timestamp>("CAST('1970-01-01' AS DATETIME)")).get_result(connection);
162        assert_eq!(Ok(time), epoch_from_sql);
163        let epoch_from_sql =
164            select(sql::<Datetime>("CAST('1970-01-01' AS DATETIME)")).get_result(connection);
165        assert_eq!(Ok(time), epoch_from_sql);
166    }
167
168    #[diesel_test_helper::test]
169    fn times_relative_to_now_encode_correctly() {
170        let connection = &mut connection();
171        let time = Utc::now().naive_utc() + Duration::try_days(1).unwrap();
172        let query = select(now.lt(time));
173        assert!(query.get_result::<bool>(connection).unwrap());
174
175        let time = Utc::now().naive_utc() - Duration::try_days(1).unwrap();
176        let query = select(now.gt(time));
177        assert!(query.get_result::<bool>(connection).unwrap());
178    }
179
180    #[diesel_test_helper::test]
181    fn times_of_day_encode_correctly() {
182        let connection = &mut connection();
183
184        let midnight = NaiveTime::from_hms_opt(0, 0, 0).unwrap();
185        let query = select(sql::<Time>("CAST('00:00:00' AS TIME)").eq(midnight));
186        assert!(query.get_result::<bool>(connection).unwrap());
187
188        let noon = NaiveTime::from_hms_opt(12, 0, 0).unwrap();
189        let query = select(sql::<Time>("CAST('12:00:00' AS TIME)").eq(noon));
190        assert!(query.get_result::<bool>(connection).unwrap());
191
192        let roughly_half_past_eleven = NaiveTime::from_hms_opt(23, 37, 4).unwrap();
193        let query = select(sql::<Time>("CAST('23:37:04' AS TIME)").eq(roughly_half_past_eleven));
194        assert!(query.get_result::<bool>(connection).unwrap());
195    }
196
197    #[diesel_test_helper::test]
198    fn times_of_day_decode_correctly() {
199        let connection = &mut connection();
200        let midnight = NaiveTime::from_hms_opt(0, 0, 0).unwrap();
201        let query = select(sql::<Time>("CAST('00:00:00' AS TIME)"));
202        assert_eq!(Ok(midnight), query.get_result::<NaiveTime>(connection));
203
204        let noon = NaiveTime::from_hms_opt(12, 0, 0).unwrap();
205        let query = select(sql::<Time>("CAST('12:00:00' AS TIME)"));
206        assert_eq!(Ok(noon), query.get_result::<NaiveTime>(connection));
207
208        let roughly_half_past_eleven = NaiveTime::from_hms_opt(23, 37, 4).unwrap();
209        let query = select(sql::<Time>("CAST('23:37:04' AS TIME)"));
210        assert_eq!(
211            Ok(roughly_half_past_eleven),
212            query.get_result::<NaiveTime>(connection)
213        );
214    }
215
216    #[diesel_test_helper::test]
217    fn times_of_day_with_microseconds_encode_correctly() {
218        let connection = &mut connection();
219
220        let with_micros = NaiveTime::from_hms_micro_opt(8, 30, 0, 123_456).unwrap();
221        let query = select(sql::<Time>("CAST('08:30:00.123456' AS TIME(6))").eq(with_micros));
222        assert!(query.get_result::<bool>(connection).unwrap());
223    }
224
225    #[diesel_test_helper::test]
226    fn times_of_day_with_microseconds_decode_correctly() {
227        let connection = &mut connection();
228
229        let with_micros = NaiveTime::from_hms_micro_opt(8, 30, 0, 123_456).unwrap();
230        let query = select(sql::<Time>("CAST('08:30:00.123456' AS TIME(6))"));
231        assert_eq!(Ok(with_micros), query.get_result::<NaiveTime>(connection));
232    }
233
234    #[diesel_test_helper::test]
235    fn dates_encode_correctly() {
236        let connection = &mut connection();
237        let january_first_2000 = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap();
238        let query = select(sql::<Date>("CAST('2000-1-1' AS DATE)").eq(january_first_2000));
239        assert!(query.get_result::<bool>(connection).unwrap());
240
241        let january_first_2018 = NaiveDate::from_ymd_opt(2018, 1, 1).unwrap();
242        let query = select(sql::<Date>("CAST('2018-1-1' AS DATE)").eq(january_first_2018));
243        assert!(query.get_result::<bool>(connection).unwrap());
244    }
245
246    #[diesel_test_helper::test]
247    fn dates_decode_correctly() {
248        let connection = &mut connection();
249        let january_first_2000 = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap();
250        let query = select(sql::<Date>("CAST('2000-1-1' AS DATE)"));
251        assert_eq!(
252            Ok(january_first_2000),
253            query.get_result::<NaiveDate>(connection)
254        );
255
256        let january_first_2018 = NaiveDate::from_ymd_opt(2018, 1, 1).unwrap();
257        let query = select(sql::<Date>("CAST('2018-1-1' AS DATE)"));
258        assert_eq!(
259            Ok(january_first_2018),
260            query.get_result::<NaiveDate>(connection)
261        );
262
263        crate::sql_query("SET sql_mode = (SELECT REPLACE(@@sql_mode, 'NO_ZERO_DATE,', ''))")
264            .execute(connection)
265            .unwrap();
266        let query = select(sql::<Date>("CAST('0000-00-00' AS DATE)"));
267        assert!(query.get_result::<NaiveDate>(connection).is_err());
268    }
269}