Skip to main content

sentinel_driver/types/
timetz.rs

1use bytes::{BufMut, BytesMut};
2
3use crate::error::{Error, Result};
4use crate::types::{FromSql, Oid, ToSql};
5
6/// PostgreSQL TIMETZ (TIME WITH TIME ZONE) type.
7///
8/// Wire format: i64 microseconds since midnight + i32 UTC offset in seconds (negated).
9/// PostgreSQL stores the offset with west-positive convention, so UTC+7 is stored as -25200.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub struct PgTimeTz {
12    pub time: chrono::NaiveTime,
13    pub offset_seconds: i32,
14}
15
16impl ToSql for PgTimeTz {
17    fn oid(&self) -> Oid {
18        Oid::TIMETZ
19    }
20
21    #[allow(clippy::expect_used)]
22    fn to_sql(&self, buf: &mut BytesMut) -> Result<()> {
23        let midnight = chrono::NaiveTime::from_hms_opt(0, 0, 0).expect("midnight is valid");
24        let us = self
25            .time
26            .signed_duration_since(midnight)
27            .num_microseconds()
28            .unwrap_or(0);
29        buf.put_i64(us);
30        // PG stores offset negated (west-positive)
31        buf.put_i32(-self.offset_seconds);
32        Ok(())
33    }
34}
35
36impl FromSql for PgTimeTz {
37    fn oid() -> Oid {
38        Oid::TIMETZ
39    }
40
41    fn from_sql(buf: &[u8]) -> Result<Self> {
42        if buf.len() != 12 {
43            return Err(Error::Decode(format!(
44                "timetz: expected 12 bytes, got {}",
45                buf.len()
46            )));
47        }
48        let us = i64::from_be_bytes([
49            buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
50        ]);
51        let pg_offset = i32::from_be_bytes([buf[8], buf[9], buf[10], buf[11]]);
52
53        let secs = (us / 1_000_000) as u32;
54        let micro = (us % 1_000_000) as u32;
55        let time = chrono::NaiveTime::from_num_seconds_from_midnight_opt(secs, micro * 1000)
56            .ok_or_else(|| {
57                Error::Decode(format!("timetz: time out of range: {us} microseconds"))
58            })?;
59
60        Ok(PgTimeTz {
61            time,
62            // Un-negate to get standard east-positive offset
63            offset_seconds: -pg_offset,
64        })
65    }
66}