sqlx_postgres/types/chrono/
datetime.rs

1use crate::decode::Decode;
2use crate::encode::{Encode, IsNull};
3use crate::error::BoxDynError;
4use crate::types::Type;
5use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres};
6use chrono::{
7    DateTime, Duration, FixedOffset, Local, NaiveDate, NaiveDateTime, Offset, TimeZone, Utc,
8};
9use std::mem;
10
11impl Type<Postgres> for NaiveDateTime {
12    fn type_info() -> PgTypeInfo {
13        PgTypeInfo::TIMESTAMP
14    }
15}
16
17impl<Tz: TimeZone> Type<Postgres> for DateTime<Tz> {
18    fn type_info() -> PgTypeInfo {
19        PgTypeInfo::TIMESTAMPTZ
20    }
21}
22
23impl PgHasArrayType for NaiveDateTime {
24    fn array_type_info() -> PgTypeInfo {
25        PgTypeInfo::TIMESTAMP_ARRAY
26    }
27}
28
29impl<Tz: TimeZone> PgHasArrayType for DateTime<Tz> {
30    fn array_type_info() -> PgTypeInfo {
31        PgTypeInfo::TIMESTAMPTZ_ARRAY
32    }
33}
34
35impl Encode<'_, Postgres> for NaiveDateTime {
36    fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
37        // TIMESTAMP is encoded as the microseconds since the epoch
38        let micros = (*self - postgres_epoch_datetime())
39            .num_microseconds()
40            .ok_or_else(|| format!("NaiveDateTime out of range for Postgres: {self:?}"))?;
41
42        Encode::<Postgres>::encode(micros, buf)
43    }
44
45    fn size_hint(&self) -> usize {
46        mem::size_of::<i64>()
47    }
48}
49
50impl<'r> Decode<'r, Postgres> for NaiveDateTime {
51    fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
52        Ok(match value.format() {
53            PgValueFormat::Binary => {
54                // TIMESTAMP is encoded as the microseconds since the epoch
55                let us = Decode::<Postgres>::decode(value)?;
56                postgres_epoch_datetime() + Duration::microseconds(us)
57            }
58
59            PgValueFormat::Text => {
60                let s = value.as_str()?;
61                NaiveDateTime::parse_from_str(
62                    s,
63                    if s.contains('+') {
64                        // Contains a time-zone specifier
65                        // This is given for timestamptz for some reason
66                        // Postgres already guarantees this to always be UTC
67                        "%Y-%m-%d %H:%M:%S%.f%#z"
68                    } else {
69                        "%Y-%m-%d %H:%M:%S%.f"
70                    },
71                )?
72            }
73        })
74    }
75}
76
77impl<Tz: TimeZone> Encode<'_, Postgres> for DateTime<Tz> {
78    fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
79        Encode::<Postgres>::encode(self.naive_utc(), buf)
80    }
81
82    fn size_hint(&self) -> usize {
83        mem::size_of::<i64>()
84    }
85}
86
87impl<'r> Decode<'r, Postgres> for DateTime<Local> {
88    fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
89        let fixed = <DateTime<FixedOffset> as Decode<Postgres>>::decode(value)?;
90        Ok(Local.from_utc_datetime(&fixed.naive_utc()))
91    }
92}
93
94impl<'r> Decode<'r, Postgres> for DateTime<Utc> {
95    fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
96        let fixed = <DateTime<FixedOffset> as Decode<Postgres>>::decode(value)?;
97        Ok(Utc.from_utc_datetime(&fixed.naive_utc()))
98    }
99}
100
101impl<'r> Decode<'r, Postgres> for DateTime<FixedOffset> {
102    fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
103        Ok(match value.format() {
104            PgValueFormat::Binary => {
105                let naive = <NaiveDateTime as Decode<Postgres>>::decode(value)?;
106                Utc.fix().from_utc_datetime(&naive)
107            }
108
109            PgValueFormat::Text => {
110                let s = value.as_str()?;
111                DateTime::parse_from_str(
112                    s,
113                    if s.contains('+') || s.contains('-') {
114                        // Contains a time-zone specifier
115                        // This is given for timestamptz for some reason
116                        // Postgres already guarantees this to always be UTC
117                        "%Y-%m-%d %H:%M:%S%.f%#z"
118                    } else {
119                        "%Y-%m-%d %H:%M:%S%.f"
120                    },
121                )?
122            }
123        })
124    }
125}
126
127#[inline]
128fn postgres_epoch_datetime() -> NaiveDateTime {
129    NaiveDate::from_ymd_opt(2000, 1, 1)
130        .expect("expected 2000-01-01 to be a valid NaiveDate")
131        .and_hms_opt(0, 0, 0)
132        .expect("expected 2000-01-01T00:00:00 to be a valid NaiveDateTime")
133}