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(&self, buf: &mut Vec<SpgArgumentValue<'q>>) -> Result<IsNull, BoxDynError> {
36 let s = self.format("%Y-%m-%d %H:%M:%S%.6f+00").to_string();
40 buf.push(SpgArgumentValue {
41 value: EngineValue::Text(s),
42 type_info: Some(<DateTime<Utc> as Type<Spg>>::type_info()),
43 _phantom: core::marker::PhantomData,
44 });
45 Ok(IsNull::No)
46 }
47}
48
49impl<'r> Decode<'r, Spg> for DateTime<Utc> {
50 fn decode(value: SpgValueRef<'r>) -> Result<Self, BoxDynError> {
51 match value.engine() {
52 EngineValue::Timestamp(micros) => {
53 let secs = micros.div_euclid(1_000_000);
54 let nanos = (micros.rem_euclid(1_000_000) as u32) * 1_000;
55 let dt = DateTime::<Utc>::from_timestamp(secs, nanos)
56 .ok_or_else(|| format!("TIMESTAMPTZ value {micros} µs out of chrono range"))?;
57 Ok(dt)
58 }
59 other => Err(format!("cannot decode {other:?} as chrono::DateTime<Utc>").into()),
60 }
61 }
62}
63
64impl Type<Spg> for NaiveDateTime {
67 fn type_info() -> SpgTypeInfo {
68 SpgTypeInfo::of(Kind::Timestamp)
69 }
70 fn compatible(ty: &SpgTypeInfo) -> bool {
71 matches!(ty.kind(), Kind::Timestamp | Kind::Timestamptz)
72 }
73}
74
75impl<'q> Encode<'q, Spg> for NaiveDateTime {
76 fn encode_by_ref(&self, buf: &mut Vec<SpgArgumentValue<'q>>) -> Result<IsNull, BoxDynError> {
77 let s = self.format("%Y-%m-%d %H:%M:%S%.6f").to_string();
78 buf.push(SpgArgumentValue {
79 value: EngineValue::Text(s),
80 type_info: Some(<NaiveDateTime as Type<Spg>>::type_info()),
81 _phantom: core::marker::PhantomData,
82 });
83 Ok(IsNull::No)
84 }
85}
86
87impl<'r> Decode<'r, Spg> for NaiveDateTime {
88 fn decode(value: SpgValueRef<'r>) -> Result<Self, BoxDynError> {
89 match value.engine() {
90 EngineValue::Timestamp(micros) => {
91 let secs = micros.div_euclid(1_000_000);
92 let nanos = (micros.rem_euclid(1_000_000) as u32) * 1_000;
93 let dt = DateTime::<Utc>::from_timestamp(secs, nanos)
94 .ok_or_else(|| format!("TIMESTAMP value {micros} µs out of chrono range"))?;
95 Ok(dt.naive_utc())
96 }
97 other => Err(format!("cannot decode {other:?} as chrono::NaiveDateTime").into()),
98 }
99 }
100}
101
102impl Type<Spg> for NaiveDate {
105 fn type_info() -> SpgTypeInfo {
106 SpgTypeInfo::of(Kind::Date)
107 }
108 fn compatible(ty: &SpgTypeInfo) -> bool {
109 matches!(ty.kind(), Kind::Date)
110 }
111}
112
113impl<'q> Encode<'q, Spg> for NaiveDate {
114 fn encode_by_ref(&self, buf: &mut Vec<SpgArgumentValue<'q>>) -> Result<IsNull, BoxDynError> {
115 let s = format!("{:04}-{:02}-{:02}", self.year(), self.month(), self.day());
116 buf.push(SpgArgumentValue {
117 value: EngineValue::Text(s),
118 type_info: Some(<NaiveDate as Type<Spg>>::type_info()),
119 _phantom: core::marker::PhantomData,
120 });
121 Ok(IsNull::No)
122 }
123}
124
125impl<'r> Decode<'r, Spg> for NaiveDate {
126 fn decode(value: SpgValueRef<'r>) -> Result<Self, BoxDynError> {
127 match value.engine() {
128 EngineValue::Date(d) => {
129 let epoch = NaiveDate::from_ymd_opt(1970, 1, 1)
131 .ok_or("1970-01-01 should be a valid date")?;
132 epoch
133 .checked_add_signed(chrono::Duration::days(i64::from(*d)))
134 .ok_or_else(|| format!("DATE value {d} out of chrono range").into())
135 }
136 other => Err(format!("cannot decode {other:?} as chrono::NaiveDate").into()),
137 }
138 }
139}
140
141#[allow(dead_code)]
144fn _timelike_marker(t: NaiveDateTime) -> u32 {
145 t.hour()
146}