rbdc_pg/types/
timetz.rs

1use crate::arguments::PgArgumentBuffer;
2use crate::types::decode::Decode;
3use crate::types::encode::{Encode, IsNull};
4use crate::value::{PgValue, PgValueFormat};
5use byteorder::{BigEndian, ReadBytesExt};
6use fastdate::time1::UtcOffset;
7use rbdc::Error;
8use rbs::Value;
9use std::fmt::{Debug, Display, Formatter};
10use std::io::Cursor;
11use std::time::Duration;
12
13#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Eq, PartialEq)]
14#[serde(rename = "Timez")]
15pub struct Timetz(pub OffsetTz);
16
17#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Eq, PartialEq)]
18#[serde(rename = "OffsetTz")]
19pub struct OffsetTz {
20    pub time: fastdate::Time,
21    pub offset: i32,
22}
23
24impl Display for OffsetTz {
25    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
26        std::fmt::Display::fmt(&self.time, f)?;
27        let offset = UtcOffset::from_whole_seconds(self.offset).unwrap();
28        let (h, m, _) = offset.as_hms();
29        if h != 0 || m != 0 {
30            if self.offset >= 0 {
31                f.write_str("+")?;
32            } else {
33                f.write_str("-")?;
34            }
35            f.write_str(&format!("{:02}", h.abs()))?;
36            f.write_str(":")?;
37            f.write_str(&format!("{:02}", m.abs()))?;
38        }
39        Ok(())
40    }
41}
42
43impl Display for Timetz {
44    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
45        write!(f, "{}", self.0)
46    }
47}
48
49impl From<Timetz> for Value {
50    fn from(arg: Timetz) -> Self {
51        rbs::value!(arg)
52    }
53}
54
55impl Decode for Timetz {
56    fn decode(value: PgValue) -> Result<Self, Error> {
57        match value.format() {
58            PgValueFormat::Binary => {
59                let mut buf = Cursor::new(value.as_bytes()?);
60
61                // TIME is encoded as the microseconds since midnight
62                let microseconds = buf.read_i64::<BigEndian>()?;
63
64                // OFFSET is encoded as seconds from UTC
65                let seconds = buf.read_i32::<BigEndian>()?;
66
67                Ok(Self(OffsetTz {
68                    time: fastdate::Time::from(Duration::from_micros(microseconds as u64)),
69                    offset: seconds,
70                }))
71            }
72            PgValueFormat::Text => {
73                // the `time` crate has a limited ability to parse and can't parse the
74                // timezone format
75                Err("reading a `TIMETZ` value in text format is not supported.".into())
76            }
77        }
78    }
79}
80
81impl Encode for Timetz {
82    fn encode(self, buf: &mut PgArgumentBuffer) -> Result<IsNull, Error> {
83        let _ = (Duration::from(self.0.time).as_micros() as i64).encode(buf)?;
84        let _ = self.0.offset.encode(buf)?;
85        Ok(IsNull::No)
86    }
87}
88
89#[cfg(test)]
90mod test {
91    use crate::types::timetz::OffsetTz;
92    use fastdate::{offset_sec, Time};
93
94    #[test]
95    fn test_display() {
96        let o = OffsetTz {
97            time: Time {
98                nano: 0,
99                sec: 0,
100                minute: 0,
101                hour: 0,
102            },
103            offset: offset_sec(),
104        };
105
106        println!("{}", o);
107    }
108}