postgres_types_extra/
pg_interval.rs

1use byteorder::{NetworkEndian, ReadBytesExt};
2use bytes::BufMut;
3use postgres_types::{FromSql, IsNull, ToSql, Type, accepts, to_sql_checked};
4use std::{error::Error, fmt, io::Cursor};
5
6#[derive(Debug, Eq, PartialEq, Clone, Hash, Default)]
7pub struct PgInterval {
8    pub months: i32,
9    pub days: i32,
10    pub microseconds: i64,
11}
12
13impl fmt::Display for PgInterval {
14    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
15        write!(f, "{}", format_pg_interval(self))
16    }
17}
18
19impl<'a> FromSql<'a> for PgInterval {
20    fn from_sql(ty: &Type, raw: &'a [u8]) -> Result<Self, Box<dyn Error + Sync + Send>> {
21        if ty.name() != "interval" {
22            return Err("Unexpected type".into());
23        }
24
25        let mut buf = Cursor::new(raw);
26
27        let microseconds = buf.read_i64::<NetworkEndian>()?;
28        let days = buf.read_i32::<NetworkEndian>()?;
29        let months = buf.read_i32::<NetworkEndian>()?;
30
31        Ok(PgInterval {
32            months,
33            days,
34            microseconds,
35        })
36    }
37
38    accepts!(INTERVAL);
39}
40
41fn format_pg_interval(interval: &PgInterval) -> String {
42    let mut parts = Vec::new();
43    if interval.months != 0 {
44        parts.push(format!(
45            "{} month{}",
46            interval.months,
47            if interval.months == 1 { "" } else { "s" }
48        ));
49    }
50    if interval.days != 0 {
51        parts.push(format!(
52            "{} day{}",
53            interval.days,
54            if interval.days == 1 { "" } else { "s" }
55        ));
56    }
57    if interval.microseconds != 0 {
58        let seconds = interval.microseconds / 1_000_000;
59        let microseconds = interval.microseconds % 1_000_000;
60        if seconds != 0 {
61            parts.push(format!(
62                "{} second{}",
63                seconds,
64                if seconds == 1 { "" } else { "s" }
65            ));
66        }
67        if microseconds != 0 {
68            parts.push(format!(
69                "{} microsecond{}",
70                microseconds,
71                if microseconds == 1 { "" } else { "s" }
72            ));
73        }
74    }
75    if parts.is_empty() {
76        "0 seconds".to_string()
77    } else {
78        parts.join(", ")
79    }
80}
81
82impl ToSql for PgInterval {
83    fn to_sql(
84        &self,
85        ty: &Type,
86        out: &mut bytes::BytesMut,
87    ) -> Result<postgres_types::IsNull, Box<dyn Error + Sync + Send>>
88    where
89        Self: Sized,
90    {
91        if ty.name() != "interval" {
92            return Err("Unexpected type".into());
93        }
94
95        out.put_i64(self.microseconds);
96        out.put_i32(self.days);
97        out.put_i32(self.months);
98
99        Ok(IsNull::No)
100    }
101
102    accepts!(INTERVAL);
103
104    to_sql_checked!();
105}