diesel/mysql_like/types/date_and_time/
chrono.rs1use 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)] 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}