postgres_types_extra/
pg_interval.rs1use 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}