1use crate::value::ValueRef;
2use crate::{
3 decode::Decode,
4 encode::{Encode, IsNull},
5 error::BoxDynError,
6 type_info::DataType,
7 types::Type,
8 Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef,
9};
10use time::format_description::{well_known::Rfc3339, BorrowedFormatItem};
11use time::macros::format_description as fd;
12use time::{Date, OffsetDateTime, PrimitiveDateTime, Time};
13
14impl Type<Sqlite> for OffsetDateTime {
15 fn type_info() -> SqliteTypeInfo {
16 SqliteTypeInfo(DataType::Datetime)
17 }
18
19 fn compatible(ty: &SqliteTypeInfo) -> bool {
20 <PrimitiveDateTime as Type<Sqlite>>::compatible(ty)
21 }
22}
23
24impl Type<Sqlite> for PrimitiveDateTime {
25 fn type_info() -> SqliteTypeInfo {
26 SqliteTypeInfo(DataType::Datetime)
27 }
28
29 fn compatible(ty: &SqliteTypeInfo) -> bool {
30 matches!(
31 ty.0,
32 DataType::Datetime | DataType::Text | DataType::Integer | DataType::Int4
33 )
34 }
35}
36
37impl Type<Sqlite> for Date {
38 fn type_info() -> SqliteTypeInfo {
39 SqliteTypeInfo(DataType::Date)
40 }
41
42 fn compatible(ty: &SqliteTypeInfo) -> bool {
43 matches!(ty.0, DataType::Date | DataType::Text)
44 }
45}
46
47impl Type<Sqlite> for Time {
48 fn type_info() -> SqliteTypeInfo {
49 SqliteTypeInfo(DataType::Time)
50 }
51
52 fn compatible(ty: &SqliteTypeInfo) -> bool {
53 matches!(ty.0, DataType::Time | DataType::Text)
54 }
55}
56
57impl Encode<'_, Sqlite> for OffsetDateTime {
58 fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> Result<IsNull, BoxDynError> {
59 Encode::<Sqlite>::encode(self.format(&Rfc3339)?, buf)
60 }
61}
62
63impl Encode<'_, Sqlite> for PrimitiveDateTime {
64 fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> Result<IsNull, BoxDynError> {
65 let format = fd!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]");
66 Encode::<Sqlite>::encode(self.format(&format)?, buf)
67 }
68}
69
70impl Encode<'_, Sqlite> for Date {
71 fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> Result<IsNull, BoxDynError> {
72 let format = fd!("[year]-[month]-[day]");
73 Encode::<Sqlite>::encode(self.format(&format)?, buf)
74 }
75}
76
77impl Encode<'_, Sqlite> for Time {
78 fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> Result<IsNull, BoxDynError> {
79 let format = fd!("[hour]:[minute]:[second].[subsecond]");
80 Encode::<Sqlite>::encode(self.format(&format)?, buf)
81 }
82}
83
84impl<'r> Decode<'r, Sqlite> for OffsetDateTime {
85 fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
86 decode_offset_datetime(value)
87 }
88}
89
90impl<'r> Decode<'r, Sqlite> for PrimitiveDateTime {
91 fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
92 decode_datetime(value)
93 }
94}
95
96impl<'r> Decode<'r, Sqlite> for Date {
97 fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
98 Ok(Date::parse(value.text()?, &fd!("[year]-[month]-[day]"))?)
99 }
100}
101
102impl<'r> Decode<'r, Sqlite> for Time {
103 fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
104 let value = value.text()?;
105
106 let sqlite_time_formats = &[
107 fd!("[hour]:[minute]:[second].[subsecond]"),
108 fd!("[hour]:[minute]:[second]"),
109 fd!("[hour]:[minute]"),
110 ];
111
112 for format in sqlite_time_formats {
113 if let Ok(dt) = Time::parse(value, &format) {
114 return Ok(dt);
115 }
116 }
117
118 Err(format!("invalid time: {value}").into())
119 }
120}
121
122fn decode_offset_datetime(value: SqliteValueRef<'_>) -> Result<OffsetDateTime, BoxDynError> {
123 let dt = match value.type_info().0 {
124 DataType::Text => decode_offset_datetime_from_text(value.text()?),
125 DataType::Int4 | DataType::Integer => {
126 Some(OffsetDateTime::from_unix_timestamp(value.int64())?)
127 }
128
129 _ => None,
130 };
131
132 if let Some(dt) = dt {
133 Ok(dt)
134 } else {
135 Err(format!("invalid offset datetime: {}", value.text()?).into())
136 }
137}
138
139fn decode_offset_datetime_from_text(value: &str) -> Option<OffsetDateTime> {
140 if let Ok(dt) = OffsetDateTime::parse(value, &Rfc3339) {
141 return Some(dt);
142 }
143
144 if let Ok(dt) = OffsetDateTime::parse(value, formats::OFFSET_DATE_TIME) {
145 return Some(dt);
146 }
147
148 if let Some(dt) = decode_datetime_from_text(value) {
149 return Some(dt.assume_utc());
150 }
151
152 None
153}
154
155fn decode_datetime(value: SqliteValueRef<'_>) -> Result<PrimitiveDateTime, BoxDynError> {
156 let dt = match value.type_info().0 {
157 DataType::Text => decode_datetime_from_text(value.text()?),
158 DataType::Int4 | DataType::Integer => {
159 let parsed = OffsetDateTime::from_unix_timestamp(value.int64()).unwrap();
160 Some(PrimitiveDateTime::new(parsed.date(), parsed.time()))
161 }
162
163 _ => None,
164 };
165
166 if let Some(dt) = dt {
167 Ok(dt)
168 } else {
169 Err(format!("invalid datetime: {}", value.text()?).into())
170 }
171}
172
173fn decode_datetime_from_text(value: &str) -> Option<PrimitiveDateTime> {
174 let default_format = fd!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]");
175 if let Ok(dt) = PrimitiveDateTime::parse(value, &default_format) {
176 return Some(dt);
177 }
178
179 let formats = [
180 BorrowedFormatItem::Compound(formats::PRIMITIVE_DATE_TIME_SPACE_SEPARATED),
181 BorrowedFormatItem::Compound(formats::PRIMITIVE_DATE_TIME_T_SEPARATED),
182 ];
183
184 if let Ok(dt) = PrimitiveDateTime::parse(value, &BorrowedFormatItem::First(&formats)) {
185 return Some(dt);
186 }
187
188 None
189}
190
191mod formats {
192 use time::format_description::BorrowedFormatItem::{Component, Literal, Optional};
193 use time::format_description::{modifier, BorrowedFormatItem, Component::*};
194
195 const YEAR: BorrowedFormatItem<'_> = Component(Year({
196 let mut value = modifier::Year::default();
197 value.padding = modifier::Padding::Zero;
198 value.repr = modifier::YearRepr::Full;
199 value.iso_week_based = false;
200 value.sign_is_mandatory = false;
201 value
202 }));
203
204 const MONTH: BorrowedFormatItem<'_> = Component(Month({
205 let mut value = modifier::Month::default();
206 value.padding = modifier::Padding::Zero;
207 value.repr = modifier::MonthRepr::Numerical;
208 value.case_sensitive = true;
209 value
210 }));
211
212 const DAY: BorrowedFormatItem<'_> = Component(Day({
213 let mut value = modifier::Day::default();
214 value.padding = modifier::Padding::Zero;
215 value
216 }));
217
218 const HOUR: BorrowedFormatItem<'_> = Component(Hour({
219 let mut value = modifier::Hour::default();
220 value.padding = modifier::Padding::Zero;
221 value.is_12_hour_clock = false;
222 value
223 }));
224
225 const MINUTE: BorrowedFormatItem<'_> = Component(Minute({
226 let mut value = modifier::Minute::default();
227 value.padding = modifier::Padding::Zero;
228 value
229 }));
230
231 const SECOND: BorrowedFormatItem<'_> = Component(Second({
232 let mut value = modifier::Second::default();
233 value.padding = modifier::Padding::Zero;
234 value
235 }));
236
237 const SUBSECOND: BorrowedFormatItem<'_> = Component(Subsecond({
238 let mut value = modifier::Subsecond::default();
239 value.digits = modifier::SubsecondDigits::OneOrMore;
240 value
241 }));
242
243 const OFFSET_HOUR: BorrowedFormatItem<'_> = Component(OffsetHour({
244 let mut value = modifier::OffsetHour::default();
245 value.sign_is_mandatory = true;
246 value.padding = modifier::Padding::Zero;
247 value
248 }));
249
250 const OFFSET_MINUTE: BorrowedFormatItem<'_> = Component(OffsetMinute({
251 let mut value = modifier::OffsetMinute::default();
252 value.padding = modifier::Padding::Zero;
253 value
254 }));
255
256 pub(super) const OFFSET_DATE_TIME: &[BorrowedFormatItem<'_>] = {
257 &[
258 YEAR,
259 Literal(b"-"),
260 MONTH,
261 Literal(b"-"),
262 DAY,
263 Optional(&Literal(b" ")),
264 Optional(&Literal(b"T")),
265 HOUR,
266 Literal(b":"),
267 MINUTE,
268 Optional(&Literal(b":")),
269 Optional(&SECOND),
270 Optional(&Literal(b".")),
271 Optional(&SUBSECOND),
272 Optional(&OFFSET_HOUR),
273 Optional(&Literal(b":")),
274 Optional(&OFFSET_MINUTE),
275 ]
276 };
277
278 pub(super) const PRIMITIVE_DATE_TIME_SPACE_SEPARATED: &[BorrowedFormatItem<'_>] = {
279 &[
280 YEAR,
281 Literal(b"-"),
282 MONTH,
283 Literal(b"-"),
284 DAY,
285 Literal(b" "),
286 HOUR,
287 Literal(b":"),
288 MINUTE,
289 Optional(&Literal(b":")),
290 Optional(&SECOND),
291 Optional(&Literal(b".")),
292 Optional(&SUBSECOND),
293 Optional(&Literal(b"Z")),
294 ]
295 };
296
297 pub(super) const PRIMITIVE_DATE_TIME_T_SEPARATED: &[BorrowedFormatItem<'_>] = {
298 &[
299 YEAR,
300 Literal(b"-"),
301 MONTH,
302 Literal(b"-"),
303 DAY,
304 Literal(b"T"),
305 HOUR,
306 Literal(b":"),
307 MINUTE,
308 Optional(&Literal(b":")),
309 Optional(&SECOND),
310 Optional(&Literal(b".")),
311 Optional(&SUBSECOND),
312 Optional(&Literal(b"Z")),
313 ]
314 };
315}