sqlx_core_oldapi/postgres/types/time/
datetime.rs

1use crate::decode::Decode;
2use crate::encode::{Encode, IsNull};
3use crate::error::BoxDynError;
4use crate::postgres::types::time::PG_EPOCH;
5use crate::postgres::{
6    PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres,
7};
8use crate::types::Type;
9use std::borrow::Cow;
10use std::mem;
11use time::macros::format_description;
12use time::macros::offset;
13use time::{Duration, OffsetDateTime, PrimitiveDateTime};
14
15impl Type<Postgres> for PrimitiveDateTime {
16    fn type_info() -> PgTypeInfo {
17        PgTypeInfo::TIMESTAMP
18    }
19}
20
21impl Type<Postgres> for OffsetDateTime {
22    fn type_info() -> PgTypeInfo {
23        PgTypeInfo::TIMESTAMPTZ
24    }
25}
26
27impl PgHasArrayType for PrimitiveDateTime {
28    fn array_type_info() -> PgTypeInfo {
29        PgTypeInfo::TIMESTAMP_ARRAY
30    }
31}
32
33impl PgHasArrayType for OffsetDateTime {
34    fn array_type_info() -> PgTypeInfo {
35        PgTypeInfo::TIMESTAMPTZ_ARRAY
36    }
37}
38
39impl Encode<'_, Postgres> for PrimitiveDateTime {
40    fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
41        // TIMESTAMP is encoded as the microseconds since the epoch
42        let us =
43            i64::try_from((*self - PG_EPOCH.midnight()).whole_microseconds()).unwrap_or(i64::MAX);
44        Encode::<Postgres>::encode(us, buf)
45    }
46
47    fn size_hint(&self) -> usize {
48        mem::size_of::<i64>()
49    }
50}
51
52impl<'r> Decode<'r, Postgres> for PrimitiveDateTime {
53    fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
54        Ok(match value.format() {
55            PgValueFormat::Binary => {
56                // TIMESTAMP is encoded as the microseconds since the epoch
57                let us = Decode::<Postgres>::decode(value)?;
58                PG_EPOCH.midnight() + Duration::microseconds(us)
59            }
60
61            PgValueFormat::Text => {
62                let s = value.as_str()?;
63
64                // If there is no decimal point we need to add one.
65                let s = if s.contains('.') {
66                    Cow::Borrowed(s)
67                } else {
68                    Cow::Owned(format!("{}.0", s))
69                };
70
71                // Contains a time-zone specifier
72                // This is given for timestamptz for some reason
73                // Postgres already guarantees this to always be UTC
74                if s.contains('+') {
75                    PrimitiveDateTime::parse(&s, &format_description!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond][offset_hour]"))?
76                } else {
77                    PrimitiveDateTime::parse(
78                        &s,
79                        &format_description!(
80                            "[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]"
81                        ),
82                    )?
83                }
84            }
85        })
86    }
87}
88
89impl Encode<'_, Postgres> for OffsetDateTime {
90    fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
91        let utc = self.to_offset(offset!(UTC));
92        let primitive = PrimitiveDateTime::new(utc.date(), utc.time());
93
94        Encode::<Postgres>::encode(primitive, buf)
95    }
96
97    fn size_hint(&self) -> usize {
98        mem::size_of::<i64>()
99    }
100}
101
102impl<'r> Decode<'r, Postgres> for OffsetDateTime {
103    fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
104        Ok(<PrimitiveDateTime as Decode<Postgres>>::decode(value)?.assume_utc())
105    }
106}