sqlx_postgres/types/
time_tz.rs1use crate::decode::Decode;
2use crate::encode::{Encode, IsNull};
3use crate::error::BoxDynError;
4use crate::types::Type;
5use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres};
6use byteorder::{BigEndian, ReadBytesExt};
7use std::io::Cursor;
8use std::mem;
9
10#[cfg(feature = "time")]
11type DefaultTime = ::time::Time;
12
13#[cfg(all(not(feature = "time"), feature = "chrono"))]
14type DefaultTime = ::chrono::NaiveTime;
15
16#[cfg(feature = "time")]
17type DefaultOffset = ::time::UtcOffset;
18
19#[cfg(all(not(feature = "time"), feature = "chrono"))]
20type DefaultOffset = ::chrono::FixedOffset;
21
22#[derive(Debug, PartialEq, Clone, Copy)]
31pub struct PgTimeTz<Time = DefaultTime, Offset = DefaultOffset> {
32 pub time: Time,
33 pub offset: Offset,
34}
35
36impl<Time, Offset> PgHasArrayType for PgTimeTz<Time, Offset> {
37 fn array_type_info() -> PgTypeInfo {
38 PgTypeInfo::TIMETZ_ARRAY
39 }
40}
41
42#[cfg(feature = "chrono")]
43mod chrono {
44 use super::*;
45 use ::chrono::{DateTime, Duration, FixedOffset, NaiveTime};
46
47 impl Type<Postgres> for PgTimeTz<NaiveTime, FixedOffset> {
48 fn type_info() -> PgTypeInfo {
49 PgTypeInfo::TIMETZ
50 }
51 }
52
53 impl Encode<'_, Postgres> for PgTimeTz<NaiveTime, FixedOffset> {
54 fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
55 let _: IsNull = <NaiveTime as Encode<'_, Postgres>>::encode(self.time, buf)?;
56 let _: IsNull =
57 <i32 as Encode<'_, Postgres>>::encode(self.offset.utc_minus_local(), buf)?;
58
59 Ok(IsNull::No)
60 }
61
62 fn size_hint(&self) -> usize {
63 mem::size_of::<i64>() + mem::size_of::<i32>()
64 }
65 }
66
67 impl<'r> Decode<'r, Postgres> for PgTimeTz<NaiveTime, FixedOffset> {
68 fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
69 match value.format() {
70 PgValueFormat::Binary => {
71 let mut buf = Cursor::new(value.as_bytes()?);
72
73 let us = buf.read_i64::<BigEndian>()?;
75 let time = NaiveTime::default() + Duration::microseconds(us);
78
79 let offset_seconds = buf.read_i32::<BigEndian>()?;
81
82 let offset = FixedOffset::west_opt(offset_seconds).ok_or_else(|| {
83 format!(
84 "server returned out-of-range offset for `TIMETZ`: {offset_seconds} seconds"
85 )
86 })?;
87
88 Ok(PgTimeTz { time, offset })
89 }
90
91 PgValueFormat::Text => try_parse_timetz(value.as_str()?),
92 }
93 }
94 }
95
96 fn try_parse_timetz(s: &str) -> Result<PgTimeTz<NaiveTime, FixedOffset>, BoxDynError> {
97 let mut tmp = String::with_capacity(11 + s.len());
98 tmp.push_str("2001-07-08 ");
99 tmp.push_str(s);
100
101 let mut err = None;
102
103 for fmt in &["%Y-%m-%d %H:%M:%S%.f%#z", "%Y-%m-%d %H:%M:%S%.f"] {
104 match DateTime::parse_from_str(&tmp, fmt) {
105 Ok(dt) => {
106 let time = dt.time();
107 let offset = *dt.offset();
108
109 return Ok(PgTimeTz { time, offset });
110 }
111
112 Err(error) => {
113 err = Some(error);
114 }
115 }
116 }
117
118 Err(err
119 .expect("BUG: loop should have set `err` to `Some()` before exiting")
120 .into())
121 }
122}
123
124#[cfg(feature = "time")]
125mod time {
126 use super::*;
127 use ::time::{Duration, Time, UtcOffset};
128
129 impl Type<Postgres> for PgTimeTz<Time, UtcOffset> {
130 fn type_info() -> PgTypeInfo {
131 PgTypeInfo::TIMETZ
132 }
133 }
134
135 impl Encode<'_, Postgres> for PgTimeTz<Time, UtcOffset> {
136 fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
137 let _: IsNull = <Time as Encode<'_, Postgres>>::encode(self.time, buf)?;
138 let _: IsNull =
139 <i32 as Encode<'_, Postgres>>::encode(-self.offset.whole_seconds(), buf)?;
140
141 Ok(IsNull::No)
142 }
143
144 fn size_hint(&self) -> usize {
145 mem::size_of::<i64>() + mem::size_of::<i32>()
146 }
147 }
148
149 impl<'r> Decode<'r, Postgres> for PgTimeTz<Time, UtcOffset> {
150 fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
151 match value.format() {
152 PgValueFormat::Binary => {
153 let mut buf = Cursor::new(value.as_bytes()?);
154
155 let us = buf.read_i64::<BigEndian>()?;
157 let time = Time::MIDNIGHT + Duration::microseconds(us);
158
159 let seconds = buf.read_i32::<BigEndian>()?;
161
162 Ok(PgTimeTz {
163 time,
164 offset: -UtcOffset::from_whole_seconds(seconds)?,
165 })
166 }
167
168 PgValueFormat::Text => {
169 Err("reading a `TIMETZ` value in text format is not supported.".into())
172 }
173 }
174 }
175 }
176}