1extern crate chrono;
2
3use self::chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
4
5use crate::backend::Backend;
6use crate::deserialize::{self, FromSql};
7use crate::serialize::{self, IsNull, Output, ToSql};
8use crate::sql_types::{Date, Time, Timestamp, TimestamptzSqlite};
9use crate::sqlite::Sqlite;
10
11const DATE_FORMAT: &str = "%F";
19
20const ENCODE_TIME_FORMAT: &str = "%T%.f";
21
22const TIME_FORMATS: [&str; 9] = [
23 "%T%.f", "%T", "%R", "%RZ", "%R%:z", "%TZ", "%T%:z", "%T%.fZ", "%T%.f%:z",
26];
27
28const ENCODE_NAIVE_DATETIME_FORMAT: &str = "%F %T%.f";
29
30const ENCODE_DATETIME_FORMAT: &str = "%F %T%.f%:z";
31
32const NAIVE_DATETIME_FORMATS: [&str; 18] = [
33 "%F %T%.f",
35 "%F %T%.f%:z",
36 "%F %T",
37 "%F %T%:z",
38 "%F %R",
40 "%F %RZ",
41 "%F %R%:z",
42 "%F %TZ",
43 "%F %T%.fZ",
44 "%FT%R",
45 "%FT%RZ",
46 "%FT%R%:z",
47 "%FT%T",
48 "%FT%TZ",
49 "%FT%T%:z",
50 "%FT%T%.f",
51 "%FT%T%.fZ",
52 "%FT%T%.f%:z",
53];
54
55const DATETIME_FORMATS: [&str; 12] = [
56 "%F %T%.f%:z",
58 "%F %T%:z",
59 "%F %RZ",
61 "%F %R%:z",
62 "%F %TZ",
63 "%F %T%.fZ",
64 "%FT%RZ",
65 "%FT%R%:z",
66 "%FT%TZ",
67 "%FT%T%:z",
68 "%FT%T%.fZ",
69 "%FT%T%.f%:z",
70];
71
72fn parse_julian(julian_days: f64) -> Option<NaiveDateTime> {
73 const EPOCH_IN_JULIAN_DAYS: f64 = 2_440_587.5;
74 const SECONDS_IN_DAY: f64 = 86400.0;
75 let timestamp = (julian_days - EPOCH_IN_JULIAN_DAYS) * SECONDS_IN_DAY;
76 #[allow(clippy::cast_possible_truncation)] let seconds = timestamp.trunc() as i64;
78 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
80 let nanos = (timestamp.fract() * 1E9) as u32;
81 #[allow(deprecated)] NaiveDateTime::from_timestamp_opt(seconds, nanos)
83}
84
85#[cfg(all(feature = "sqlite", feature = "chrono"))]
86impl FromSql<Date, Sqlite> for NaiveDate {
87 fn from_sql(mut value: <Sqlite as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
88 value
89 .parse_string(|s| Self::parse_from_str(s, DATE_FORMAT))
90 .map_err(Into::into)
91 }
92}
93
94#[cfg(all(feature = "sqlite", feature = "chrono"))]
95impl ToSql<Date, Sqlite> for NaiveDate {
96 fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
97 out.set_value(self.format(DATE_FORMAT).to_string());
98 Ok(IsNull::No)
99 }
100}
101
102#[cfg(all(feature = "sqlite", feature = "chrono"))]
103impl FromSql<Time, Sqlite> for NaiveTime {
104 fn from_sql(mut value: <Sqlite as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
105 value.parse_string(|text| {
106 for format in TIME_FORMATS {
107 if let Ok(time) = Self::parse_from_str(text, format) {
108 return Ok(time);
109 }
110 }
111
112 Err(format!("Invalid time {text}").into())
113 })
114 }
115}
116
117#[cfg(all(feature = "sqlite", feature = "chrono"))]
118impl ToSql<Time, Sqlite> for NaiveTime {
119 fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
120 out.set_value(self.format(ENCODE_TIME_FORMAT).to_string());
121 Ok(IsNull::No)
122 }
123}
124
125#[cfg(all(feature = "sqlite", feature = "chrono"))]
126impl FromSql<Timestamp, Sqlite> for NaiveDateTime {
127 fn from_sql(mut value: <Sqlite as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
128 value.parse_string(|text| {
129 for format in NAIVE_DATETIME_FORMATS {
130 if let Ok(dt) = Self::parse_from_str(text, format) {
131 return Ok(dt);
132 }
133 }
134
135 if let Ok(julian_days) = text.parse::<f64>() {
136 if let Some(timestamp) = parse_julian(julian_days) {
137 return Ok(timestamp);
138 }
139 }
140
141 Err(format!("Invalid datetime {text}").into())
142 })
143 }
144}
145
146#[cfg(all(feature = "sqlite", feature = "chrono"))]
147impl ToSql<Timestamp, Sqlite> for NaiveDateTime {
148 fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
149 out.set_value(self.format(ENCODE_NAIVE_DATETIME_FORMAT).to_string());
150 Ok(IsNull::No)
151 }
152}
153
154#[cfg(all(feature = "sqlite", feature = "chrono"))]
155impl FromSql<TimestamptzSqlite, Sqlite> for NaiveDateTime {
156 fn from_sql(mut value: <Sqlite as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
157 value.parse_string(|text| {
158 for format in NAIVE_DATETIME_FORMATS {
159 if let Ok(dt) = Self::parse_from_str(text, format) {
160 return Ok(dt);
161 }
162 }
163
164 if let Ok(julian_days) = text.parse::<f64>() {
165 if let Some(timestamp) = parse_julian(julian_days) {
166 return Ok(timestamp);
167 }
168 }
169
170 Err(format!("Invalid datetime {text}").into())
171 })
172 }
173}
174
175#[cfg(all(feature = "sqlite", feature = "chrono"))]
176impl ToSql<TimestamptzSqlite, Sqlite> for NaiveDateTime {
177 fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
178 out.set_value(self.format(ENCODE_NAIVE_DATETIME_FORMAT).to_string());
179 Ok(IsNull::No)
180 }
181}
182
183#[cfg(all(feature = "sqlite", feature = "chrono"))]
184impl FromSql<TimestamptzSqlite, Sqlite> for DateTime<Utc> {
185 fn from_sql(mut value: <Sqlite as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
186 if let Ok(dt) = value.parse_string(|text| {
188 for format in DATETIME_FORMATS {
189 if let Ok(dt) = DateTime::parse_from_str(text, format) {
190 return Ok(dt.with_timezone(&Utc));
191 }
192 }
193
194 Err(())
195 }) {
196 return Ok(dt);
197 }
198
199 let naive_date_time =
201 <NaiveDateTime as FromSql<TimestamptzSqlite, Sqlite>>::from_sql(value)?;
202 Ok(Utc.from_utc_datetime(&naive_date_time))
203 }
204}
205
206#[cfg(all(feature = "sqlite", feature = "chrono"))]
207impl FromSql<TimestamptzSqlite, Sqlite> for DateTime<Local> {
208 fn from_sql(mut value: <Sqlite as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
209 if let Ok(dt) = value.parse_string(|text| {
211 for format in DATETIME_FORMATS {
212 if let Ok(dt) = DateTime::parse_from_str(text, format) {
213 return Ok(dt.with_timezone(&Local));
214 }
215 }
216
217 Err(())
218 }) {
219 return Ok(dt);
220 }
221
222 let naive_date_time =
224 <NaiveDateTime as FromSql<TimestamptzSqlite, Sqlite>>::from_sql(value)?;
225 Ok(Local::from_utc_datetime(&Local, &naive_date_time))
226 }
227}
228
229#[cfg(all(feature = "sqlite", feature = "chrono"))]
230impl<TZ: TimeZone> ToSql<TimestamptzSqlite, Sqlite> for DateTime<TZ> {
231 fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
232 let dt_utc = self.with_timezone(&Utc);
234 out.set_value(dt_utc.format(ENCODE_DATETIME_FORMAT).to_string());
235 Ok(IsNull::No)
236 }
237}
238
239#[cfg(test)]
240#[allow(clippy::unwrap_used)]
241mod tests {
242 extern crate chrono;
243 extern crate dotenvy;
244
245 use self::chrono::{
246 DateTime, Duration, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Timelike,
247 Utc,
248 };
249
250 use crate::dsl::{now, sql};
251 use crate::prelude::*;
252 use crate::select;
253 use crate::sql_types::{Text, Time, Timestamp, TimestamptzSqlite};
254 use crate::test_helpers::connection;
255
256 #[declare_sql_function]
257 extern "SQL" {
258 fn datetime(x: Text) -> Timestamp;
259 fn time(x: Text) -> Time;
260 fn date(x: Text) -> Date;
261 }
262
263 #[diesel_test_helper::test]
264 fn unix_epoch_encodes_correctly() {
265 let connection = &mut connection();
266 let time = NaiveDate::from_ymd_opt(1970, 1, 1)
267 .unwrap()
268 .and_hms_opt(0, 0, 0)
269 .unwrap();
270 let query = select(datetime("1970-01-01 00:00:00.000000").eq(time));
271 assert_eq!(Ok(true), query.get_result(connection));
272 }
273
274 #[diesel_test_helper::test]
275 fn unix_epoch_decodes_correctly_in_all_possible_formats() {
276 let connection = &mut connection();
277 let time = NaiveDate::from_ymd_opt(1970, 1, 1)
278 .unwrap()
279 .and_hms_opt(0, 0, 0)
280 .unwrap();
281 let valid_epoch_formats = vec![
282 "1970-01-01 00:00",
283 "1970-01-01 00:00:00",
284 "1970-01-01 00:00:00.000",
285 "1970-01-01 00:00:00.000000",
286 "1970-01-01T00:00",
287 "1970-01-01T00:00:00",
288 "1970-01-01T00:00:00.000",
289 "1970-01-01T00:00:00.000000",
290 "1970-01-01 00:00Z",
291 "1970-01-01 00:00:00Z",
292 "1970-01-01 00:00:00.000Z",
293 "1970-01-01 00:00:00.000000Z",
294 "1970-01-01T00:00Z",
295 "1970-01-01T00:00:00Z",
296 "1970-01-01T00:00:00.000Z",
297 "1970-01-01T00:00:00.000000Z",
298 "1970-01-01 00:00+00:00",
299 "1970-01-01 00:00:00+00:00",
300 "1970-01-01 00:00:00.000+00:00",
301 "1970-01-01 00:00:00.000000+00:00",
302 "1970-01-01T00:00+00:00",
303 "1970-01-01T00:00:00+00:00",
304 "1970-01-01T00:00:00.000+00:00",
305 "1970-01-01T00:00:00.000000+00:00",
306 "1970-01-01 00:00+01:00",
307 "1970-01-01 00:00:00+01:00",
308 "1970-01-01 00:00:00.000+01:00",
309 "1970-01-01 00:00:00.000000+01:00",
310 "1970-01-01T00:00+01:00",
311 "1970-01-01T00:00:00+01:00",
312 "1970-01-01T00:00:00.000+01:00",
313 "1970-01-01T00:00:00.000000+01:00",
314 "1970-01-01T00:00-01:00",
315 "1970-01-01T00:00:00-01:00",
316 "1970-01-01T00:00:00.000-01:00",
317 "1970-01-01T00:00:00.000000-01:00",
318 "1970-01-01T00:00-01:00",
319 "1970-01-01T00:00:00-01:00",
320 "1970-01-01T00:00:00.000-01:00",
321 "1970-01-01T00:00:00.000000-01:00",
322 "2440587.5",
323 ];
324
325 for s in valid_epoch_formats {
326 let epoch_from_sql =
327 select(sql::<Timestamp>(&format!("'{}'", s))).get_result(connection);
328 assert_eq!(Ok(time), epoch_from_sql, "format {} failed", s);
329 }
330 }
331
332 #[diesel_test_helper::test]
333 fn times_relative_to_now_encode_correctly() {
334 let connection = &mut connection();
335 let time = Utc::now().naive_utc() + Duration::try_seconds(60).unwrap();
336 let query = select(now.lt(time));
337 assert_eq!(Ok(true), query.get_result(connection));
338
339 let time = Utc::now().naive_utc() - Duration::try_seconds(600).unwrap();
340 let query = select(now.gt(time));
341 assert_eq!(Ok(true), query.get_result(connection));
342 }
343
344 #[diesel_test_helper::test]
345 fn times_of_day_encode_correctly() {
346 let connection = &mut connection();
347
348 let midnight = NaiveTime::from_hms_opt(0, 0, 0).unwrap();
349 let query = select(time("00:00:00.000000").eq(midnight));
350 assert!(query.get_result::<bool>(connection).unwrap());
351
352 let noon = NaiveTime::from_hms_opt(12, 0, 0).unwrap();
353 let query = select(time("12:00:00.000000").eq(noon));
354 assert!(query.get_result::<bool>(connection).unwrap());
355
356 let roughly_half_past_eleven = NaiveTime::from_hms_micro_opt(23, 37, 4, 2200).unwrap();
357 let query = select(sql::<Time>("'23:37:04.002200'").eq(roughly_half_past_eleven));
358 assert!(query.get_result::<bool>(connection).unwrap());
359 }
360
361 #[diesel_test_helper::test]
362 fn times_of_day_decode_correctly() {
363 let connection = &mut connection();
364 let midnight = NaiveTime::from_hms_opt(0, 0, 0).unwrap();
365 let valid_midnight_formats = &[
366 "00:00",
367 "00:00:00",
368 "00:00:00.000",
369 "00:00:00.000000",
370 "00:00Z",
371 "00:00:00Z",
372 "00:00:00.000Z",
373 "00:00:00.000000Z",
374 "00:00+00:00",
375 "00:00:00+00:00",
376 "00:00:00.000+00:00",
377 "00:00:00.000000+00:00",
378 "00:00+01:00",
379 "00:00:00+01:00",
380 "00:00:00.000+01:00",
381 "00:00:00.000000+01:00",
382 "00:00-01:00",
383 "00:00:00-01:00",
384 "00:00:00.000-01:00",
385 "00:00:00.000000-01:00",
386 ];
387 for format in valid_midnight_formats {
388 let query = select(sql::<Time>(&format!("'{}'", format)));
389 assert_eq!(
390 Ok(midnight),
391 query.get_result::<NaiveTime>(connection),
392 "format {} failed",
393 format
394 );
395 }
396
397 let noon = NaiveTime::from_hms_opt(12, 0, 0).unwrap();
398 let query = select(sql::<Time>("'12:00:00'"));
399 assert_eq!(Ok(noon), query.get_result::<NaiveTime>(connection));
400
401 let roughly_half_past_eleven = NaiveTime::from_hms_micro_opt(23, 37, 4, 2200).unwrap();
402 let query = select(sql::<Time>("'23:37:04.002200'"));
403 assert_eq!(
404 Ok(roughly_half_past_eleven),
405 query.get_result::<NaiveTime>(connection)
406 );
407 }
408
409 #[diesel_test_helper::test]
410 fn dates_encode_correctly() {
411 let connection = &mut connection();
412 let january_first_2000 = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap();
413 let query = select(date("2000-01-01").eq(january_first_2000));
414 assert!(query.get_result::<bool>(connection).unwrap());
415
416 let distant_past = NaiveDate::from_ymd_opt(0, 4, 11).unwrap();
417 let query = select(date("0000-04-11").eq(distant_past));
418 assert!(query.get_result::<bool>(connection).unwrap());
419
420 let january_first_2018 = NaiveDate::from_ymd_opt(2018, 1, 1).unwrap();
421 let query = select(date("2018-01-01").eq(january_first_2018));
422 assert!(query.get_result::<bool>(connection).unwrap());
423
424 let distant_future = NaiveDate::from_ymd_opt(9999, 1, 8).unwrap();
425 let query = select(date("9999-01-08").eq(distant_future));
426 assert!(query.get_result::<bool>(connection).unwrap());
427 }
428
429 #[diesel_test_helper::test]
430 fn dates_decode_correctly() {
431 let connection = &mut connection();
432 let january_first_2000 = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap();
433 let query = select(date("2000-01-01"));
434 assert_eq!(
435 Ok(january_first_2000),
436 query.get_result::<NaiveDate>(connection)
437 );
438
439 let distant_past = NaiveDate::from_ymd_opt(0, 4, 11).unwrap();
440 let query = select(date("0000-04-11"));
441 assert_eq!(Ok(distant_past), query.get_result::<NaiveDate>(connection));
442
443 let january_first_2018 = NaiveDate::from_ymd_opt(2018, 1, 1).unwrap();
444 let query = select(date("2018-01-01"));
445 assert_eq!(
446 Ok(january_first_2018),
447 query.get_result::<NaiveDate>(connection)
448 );
449
450 let distant_future = NaiveDate::from_ymd_opt(9999, 1, 8).unwrap();
451 let query = select(date("9999-01-08"));
452 assert_eq!(
453 Ok(distant_future),
454 query.get_result::<NaiveDate>(connection)
455 );
456 }
457
458 #[diesel_test_helper::test]
459 fn datetimes_decode_correctly() {
460 let connection = &mut connection();
461 let january_first_2000 = NaiveDate::from_ymd_opt(2000, 1, 1)
462 .unwrap()
463 .and_hms_opt(1, 1, 1)
464 .unwrap();
465 let query = select(datetime("2000-01-01 01:01:01.000000"));
466 assert_eq!(
467 Ok(january_first_2000),
468 query.get_result::<NaiveDateTime>(connection)
469 );
470
471 let distant_past = NaiveDate::from_ymd_opt(0, 4, 11)
472 .unwrap()
473 .and_hms_opt(2, 2, 2)
474 .unwrap();
475 let query = select(datetime("0000-04-11 02:02:02.000000"));
476 assert_eq!(
477 Ok(distant_past),
478 query.get_result::<NaiveDateTime>(connection)
479 );
480
481 let january_first_2018 = NaiveDate::from_ymd_opt(2018, 1, 1).unwrap();
482 let query = select(date("2018-01-01"));
483 assert_eq!(
484 Ok(january_first_2018),
485 query.get_result::<NaiveDate>(connection)
486 );
487
488 let distant_future = NaiveDate::from_ymd_opt(9999, 1, 8)
489 .unwrap()
490 .and_hms_opt(23, 59, 59)
491 .unwrap()
492 .with_nanosecond(100_000)
493 .unwrap();
494 let query = select(sql::<Timestamp>("'9999-01-08 23:59:59.000100'"));
495 assert_eq!(
496 Ok(distant_future),
497 query.get_result::<NaiveDateTime>(connection)
498 );
499 }
500
501 #[diesel_test_helper::test]
502 fn datetimes_encode_correctly() {
503 let connection = &mut connection();
504 let january_first_2000 = NaiveDate::from_ymd_opt(2000, 1, 1)
505 .unwrap()
506 .and_hms_opt(0, 0, 0)
507 .unwrap();
508 let query = select(datetime("2000-01-01 00:00:00.000000").eq(january_first_2000));
509 assert!(query.get_result::<bool>(connection).unwrap());
510
511 let distant_past = NaiveDate::from_ymd_opt(0, 4, 11)
512 .unwrap()
513 .and_hms_opt(20, 00, 20)
514 .unwrap();
515 let query = select(datetime("0000-04-11 20:00:20.000000").eq(distant_past));
516 assert!(query.get_result::<bool>(connection).unwrap());
517
518 let january_first_2018 = NaiveDate::from_ymd_opt(2018, 1, 1)
519 .unwrap()
520 .and_hms_opt(12, 00, 00)
521 .unwrap()
522 .with_nanosecond(500_000)
523 .unwrap();
524 let query = select(sql::<Timestamp>("'2018-01-01 12:00:00.000500'").eq(january_first_2018));
525 assert!(query.get_result::<bool>(connection).unwrap());
526
527 let distant_future = NaiveDate::from_ymd_opt(9999, 1, 8)
528 .unwrap()
529 .and_hms_opt(0, 0, 0)
530 .unwrap();
531 let query = select(datetime("9999-01-08 00:00:00.000000").eq(distant_future));
532 assert!(query.get_result::<bool>(connection).unwrap());
533 }
534
535 #[diesel_test_helper::test]
536 fn insert_timestamptz_into_table_as_text() {
537 crate::table! {
538 #[allow(unused_parens)]
539 test_insert_timestamptz_into_table_as_text(id) {
540 id -> Integer,
541 timestamp_with_tz -> TimestamptzSqlite,
542 }
543 }
544 let conn = &mut connection();
545 crate::sql_query(
546 "CREATE TABLE test_insert_timestamptz_into_table_as_text(id INTEGER PRIMARY KEY, timestamp_with_tz TEXT);",
547 )
548 .execute(conn)
549 .unwrap();
550
551 let time: DateTime<Utc> = Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).single().unwrap();
552
553 crate::insert_into(test_insert_timestamptz_into_table_as_text::table)
554 .values(vec![(
555 test_insert_timestamptz_into_table_as_text::id.eq(1),
556 test_insert_timestamptz_into_table_as_text::timestamp_with_tz.eq(sql::<
557 TimestamptzSqlite,
558 >(
559 "'1970-01-01 00:00:00.000000+00:00'",
560 )),
561 )])
562 .execute(conn)
563 .unwrap();
564
565 let result = test_insert_timestamptz_into_table_as_text::table
566 .select(test_insert_timestamptz_into_table_as_text::timestamp_with_tz)
567 .get_result::<DateTime<Utc>>(conn)
568 .unwrap();
569 assert_eq!(result, time);
570 }
571
572 #[diesel_test_helper::test]
573 fn can_query_timestamptz_column_with_between() {
574 crate::table! {
575 #[allow(unused_parens)]
576 test_query_timestamptz_column_with_between(id) {
577 id -> Integer,
578 timestamp_with_tz -> TimestamptzSqlite,
579 }
580 }
581 let conn = &mut connection();
582 crate::sql_query(
583 "CREATE TABLE test_query_timestamptz_column_with_between(id INTEGER PRIMARY KEY, timestamp_with_tz TEXT);",
584 )
585 .execute(conn)
586 .unwrap();
587
588 crate::insert_into(test_query_timestamptz_column_with_between::table)
589 .values(vec![
590 (
591 test_query_timestamptz_column_with_between::id.eq(1),
592 test_query_timestamptz_column_with_between::timestamp_with_tz.eq(sql::<
593 TimestamptzSqlite,
594 >(
595 "'1970-01-01 00:00:01.000000+00:00'",
596 )),
597 ),
598 (
599 test_query_timestamptz_column_with_between::id.eq(2),
600 test_query_timestamptz_column_with_between::timestamp_with_tz.eq(sql::<
601 TimestamptzSqlite,
602 >(
603 "'1970-01-01 00:00:02.000000+00:00'",
604 )),
605 ),
606 (
607 test_query_timestamptz_column_with_between::id.eq(3),
608 test_query_timestamptz_column_with_between::timestamp_with_tz.eq(sql::<
609 TimestamptzSqlite,
610 >(
611 "'1970-01-01 00:00:03.000000+00:00'",
612 )),
613 ),
614 (
615 test_query_timestamptz_column_with_between::id.eq(4),
616 test_query_timestamptz_column_with_between::timestamp_with_tz.eq(sql::<
617 TimestamptzSqlite,
618 >(
619 "'1970-01-01 00:00:04.000000+00:00'",
620 )),
621 ),
622 ])
623 .execute(conn)
624 .unwrap();
625
626 let result = test_query_timestamptz_column_with_between::table
627 .select(test_query_timestamptz_column_with_between::timestamp_with_tz)
628 .filter(
629 test_query_timestamptz_column_with_between::timestamp_with_tz
630 .gt(Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).single().unwrap()),
631 )
632 .filter(
633 test_query_timestamptz_column_with_between::timestamp_with_tz
634 .lt(Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 4).single().unwrap()),
635 )
636 .count()
637 .get_result::<_>(conn);
638 assert_eq!(result, Ok(3));
639 }
640
641 #[diesel_test_helper::test]
642 fn unix_epoch_encodes_correctly_with_timezone() {
643 let connection = &mut connection();
644 let time = FixedOffset::west_opt(3600)
646 .unwrap()
647 .with_ymd_and_hms(1970, 1, 1, 0, 0, 0)
648 .single()
649 .unwrap()
650 .with_nanosecond(1_000_000)
652 .unwrap();
653 let query = select(sql::<TimestamptzSqlite>("'1970-01-01 01:00:00.001+00:00'").eq(time));
654 assert!(query.get_result::<bool>(connection).unwrap());
655 }
656
657 #[diesel_test_helper::test]
658 fn unix_epoch_encodes_correctly_with_utc_timezone() {
659 let connection = &mut connection();
660 let time: DateTime<Utc> = Utc
661 .with_ymd_and_hms(1970, 1, 1, 0, 0, 0)
662 .single()
663 .unwrap()
664 .with_nanosecond(1_000_000)
666 .unwrap();
667 let query = select(sql::<TimestamptzSqlite>("'1970-01-01 00:00:00.001+00:00'").eq(time));
668 assert!(query.get_result::<bool>(connection).unwrap());
669
670 let time: DateTime<Utc> = Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).single().unwrap();
672 let query = select(sql::<TimestamptzSqlite>("'1970-01-01 00:00:00+00:00'").eq(time));
673 assert!(query.get_result::<bool>(connection).unwrap());
674 }
675
676 #[diesel_test_helper::test]
677 fn unix_epoch_decodes_correctly_with_utc_timezone_in_all_possible_formats() {
678 let connection = &mut connection();
679 let time: DateTime<Utc> = Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).single().unwrap();
680 let valid_epoch_formats = vec![
681 "1970-01-01 00:00Z",
682 "1970-01-01 00:00:00Z",
683 "1970-01-01 00:00:00.000Z",
684 "1970-01-01 00:00:00.000000Z",
685 "1970-01-01T00:00Z",
686 "1970-01-01T00:00:00Z",
687 "1970-01-01T00:00:00.000Z",
688 "1970-01-01T00:00:00.000000Z",
689 "1970-01-01 00:00+00:00",
690 "1970-01-01 00:00:00+00:00",
691 "1970-01-01 00:00:00.000+00:00",
692 "1970-01-01 00:00:00.000000+00:00",
693 "1970-01-01T00:00+00:00",
694 "1970-01-01T00:00:00+00:00",
695 "1970-01-01T00:00:00.000+00:00",
696 "1970-01-01T00:00:00.000000+00:00",
697 "2440587.5",
698 ];
699
700 for s in valid_epoch_formats {
701 let epoch_from_sql =
702 select(sql::<TimestamptzSqlite>(&format!("'{}'", s))).get_result(connection);
703 assert_eq!(Ok(time), epoch_from_sql, "format {} failed", s);
704 }
705 }
706}