1#![cfg(feature = "chrono")]
9
10use chrono::{DateTime, Datelike, NaiveDate, NaiveDateTime, Timelike, Utc};
11use sqlx_core::decode::Decode;
12use sqlx_core::encode::{Encode, IsNull};
13use sqlx_core::error::BoxDynError;
14use sqlx_core::types::Type;
15
16use spg_embedded::Value as EngineValue;
17
18use crate::arguments::SpgArgumentValue;
19use crate::database::Spg;
20use crate::type_info::{Kind, SpgTypeInfo};
21use crate::value::SpgValueRef;
22
23impl Type<Spg> for DateTime<Utc> {
26 fn type_info() -> SpgTypeInfo {
27 SpgTypeInfo::of(Kind::Timestamptz)
28 }
29 fn compatible(ty: &SpgTypeInfo) -> bool {
30 matches!(ty.kind(), Kind::Timestamptz | Kind::Timestamp)
31 }
32}
33
34impl<'q> Encode<'q, Spg> for DateTime<Utc> {
35 fn encode_by_ref(
36 &self,
37 buf: &mut Vec<SpgArgumentValue<'q>>,
38 ) -> Result<IsNull, BoxDynError> {
39 let s = self
43 .format("%Y-%m-%d %H:%M:%S%.6f+00")
44 .to_string();
45 buf.push(SpgArgumentValue {
46 value: EngineValue::Text(s),
47 type_info: Some(<DateTime<Utc> as Type<Spg>>::type_info()),
48 _phantom: core::marker::PhantomData,
49 });
50 Ok(IsNull::No)
51 }
52}
53
54impl<'r> Decode<'r, Spg> for DateTime<Utc> {
55 fn decode(value: SpgValueRef<'r>) -> Result<Self, BoxDynError> {
56 match value.engine() {
57 EngineValue::Timestamp(micros) => {
58 let secs = micros.div_euclid(1_000_000);
59 let nanos = (micros.rem_euclid(1_000_000) as u32) * 1_000;
60 let dt = DateTime::<Utc>::from_timestamp(secs, nanos)
61 .ok_or_else(|| {
62 format!("TIMESTAMPTZ value {micros} µs out of chrono range")
63 })?;
64 Ok(dt)
65 }
66 other => Err(format!("cannot decode {other:?} as chrono::DateTime<Utc>").into()),
67 }
68 }
69}
70
71impl Type<Spg> for NaiveDateTime {
74 fn type_info() -> SpgTypeInfo {
75 SpgTypeInfo::of(Kind::Timestamp)
76 }
77 fn compatible(ty: &SpgTypeInfo) -> bool {
78 matches!(ty.kind(), Kind::Timestamp | Kind::Timestamptz)
79 }
80}
81
82impl<'q> Encode<'q, Spg> for NaiveDateTime {
83 fn encode_by_ref(
84 &self,
85 buf: &mut Vec<SpgArgumentValue<'q>>,
86 ) -> Result<IsNull, BoxDynError> {
87 let s = self.format("%Y-%m-%d %H:%M:%S%.6f").to_string();
88 buf.push(SpgArgumentValue {
89 value: EngineValue::Text(s),
90 type_info: Some(<NaiveDateTime as Type<Spg>>::type_info()),
91 _phantom: core::marker::PhantomData,
92 });
93 Ok(IsNull::No)
94 }
95}
96
97impl<'r> Decode<'r, Spg> for NaiveDateTime {
98 fn decode(value: SpgValueRef<'r>) -> Result<Self, BoxDynError> {
99 match value.engine() {
100 EngineValue::Timestamp(micros) => {
101 let secs = micros.div_euclid(1_000_000);
102 let nanos = (micros.rem_euclid(1_000_000) as u32) * 1_000;
103 let dt = DateTime::<Utc>::from_timestamp(secs, nanos)
104 .ok_or_else(|| {
105 format!("TIMESTAMP value {micros} µs out of chrono range")
106 })?;
107 Ok(dt.naive_utc())
108 }
109 other => Err(format!("cannot decode {other:?} as chrono::NaiveDateTime").into()),
110 }
111 }
112}
113
114impl Type<Spg> for NaiveDate {
117 fn type_info() -> SpgTypeInfo {
118 SpgTypeInfo::of(Kind::Date)
119 }
120 fn compatible(ty: &SpgTypeInfo) -> bool {
121 matches!(ty.kind(), Kind::Date)
122 }
123}
124
125impl<'q> Encode<'q, Spg> for NaiveDate {
126 fn encode_by_ref(
127 &self,
128 buf: &mut Vec<SpgArgumentValue<'q>>,
129 ) -> Result<IsNull, BoxDynError> {
130 let s = format!("{:04}-{:02}-{:02}", self.year(), self.month(), self.day());
131 buf.push(SpgArgumentValue {
132 value: EngineValue::Text(s),
133 type_info: Some(<NaiveDate as Type<Spg>>::type_info()),
134 _phantom: core::marker::PhantomData,
135 });
136 Ok(IsNull::No)
137 }
138}
139
140impl<'r> Decode<'r, Spg> for NaiveDate {
141 fn decode(value: SpgValueRef<'r>) -> Result<Self, BoxDynError> {
142 match value.engine() {
143 EngineValue::Date(d) => {
144 let epoch = NaiveDate::from_ymd_opt(1970, 1, 1)
146 .ok_or("1970-01-01 should be a valid date")?;
147 epoch
148 .checked_add_signed(chrono::Duration::days(i64::from(*d)))
149 .ok_or_else(|| format!("DATE value {d} out of chrono range").into())
150 }
151 other => Err(format!("cannot decode {other:?} as chrono::NaiveDate").into()),
152 }
153 }
154}
155
156#[allow(dead_code)]
159fn _timelike_marker(t: NaiveDateTime) -> u32 {
160 t.hour()
161}