use std::fmt::Display;
use crate::value::ValueRef;
use crate::{
decode::Decode,
encode::{Encode, IsNull},
error::BoxDynError,
type_info::DataType,
types::Type,
Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef,
};
use chrono::FixedOffset;
use chrono::{
DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, Offset, SecondsFormat, TimeZone, Utc,
};
impl<Tz: TimeZone> Type<Sqlite> for DateTime<Tz> {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Datetime)
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
<NaiveDateTime as Type<Sqlite>>::compatible(ty)
}
}
impl Type<Sqlite> for NaiveDateTime {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Datetime)
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
matches!(
ty.0,
DataType::Datetime
| DataType::Text
| DataType::Integer
| DataType::Int4
| DataType::Float
)
}
}
impl Type<Sqlite> for NaiveDate {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Date)
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
matches!(ty.0, DataType::Date | DataType::Text)
}
}
impl Type<Sqlite> for NaiveTime {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Time)
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
matches!(ty.0, DataType::Time | DataType::Text)
}
}
impl<Tz: TimeZone> Encode<'_, Sqlite> for DateTime<Tz>
where
Tz::Offset: Display,
{
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> Result<IsNull, BoxDynError> {
Encode::<Sqlite>::encode(self.to_rfc3339_opts(SecondsFormat::AutoSi, false), buf)
}
}
impl Encode<'_, Sqlite> for NaiveDateTime {
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> Result<IsNull, BoxDynError> {
Encode::<Sqlite>::encode(self.format("%F %T%.f").to_string(), buf)
}
}
impl Encode<'_, Sqlite> for NaiveDate {
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> Result<IsNull, BoxDynError> {
Encode::<Sqlite>::encode(self.format("%F").to_string(), buf)
}
}
impl Encode<'_, Sqlite> for NaiveTime {
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> Result<IsNull, BoxDynError> {
Encode::<Sqlite>::encode(self.format("%T%.f").to_string(), buf)
}
}
impl<'r> Decode<'r, Sqlite> for DateTime<Utc> {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(Utc.from_utc_datetime(&decode_datetime(value)?.naive_utc()))
}
}
impl<'r> Decode<'r, Sqlite> for DateTime<Local> {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(Local.from_utc_datetime(&decode_datetime(value)?.naive_utc()))
}
}
impl<'r> Decode<'r, Sqlite> for DateTime<FixedOffset> {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
decode_datetime(value)
}
}
fn decode_datetime(value: SqliteValueRef<'_>) -> Result<DateTime<FixedOffset>, BoxDynError> {
let dt = match value.type_info().0 {
DataType::Text => decode_datetime_from_text(value.text()?),
DataType::Int4 | DataType::Integer => decode_datetime_from_int(value.int64()),
DataType::Float => decode_datetime_from_float(value.double()),
_ => None,
};
if let Some(dt) = dt {
Ok(dt)
} else {
Err(format!("invalid datetime: {}", value.text()?).into())
}
}
fn decode_datetime_from_text(value: &str) -> Option<DateTime<FixedOffset>> {
if let Ok(dt) = DateTime::parse_from_rfc3339(value) {
return Some(dt);
}
let sqlite_datetime_formats = &[
"%F %T%.f",
"%F %R",
"%F %RZ",
"%F %R%:z",
"%F %T%.fZ",
"%F %T%.f%:z",
"%FT%R",
"%FT%RZ",
"%FT%R%:z",
"%FT%T%.f",
"%FT%T%.fZ",
"%FT%T%.f%:z",
];
for format in sqlite_datetime_formats {
if let Ok(dt) = DateTime::parse_from_str(value, format) {
return Some(dt);
}
if let Ok(dt) = NaiveDateTime::parse_from_str(value, format) {
return Some(Utc.fix().from_utc_datetime(&dt));
}
}
None
}
fn decode_datetime_from_int(value: i64) -> Option<DateTime<FixedOffset>> {
Utc.fix().timestamp_opt(value, 0).single()
}
fn decode_datetime_from_float(value: f64) -> Option<DateTime<FixedOffset>> {
let epoch_in_julian_days = 2_440_587.5;
let seconds_in_day = 86400.0;
let timestamp = (value - epoch_in_julian_days) * seconds_in_day;
if !timestamp.is_finite() {
return None;
}
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
{
let seconds = timestamp.trunc() as i64;
let nanos = (timestamp.fract() * 1E9).abs() as u32;
Utc.fix().timestamp_opt(seconds, nanos).single()
}
}
impl<'r> Decode<'r, Sqlite> for NaiveDateTime {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(decode_datetime(value)?.naive_local())
}
}
impl<'r> Decode<'r, Sqlite> for NaiveDate {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(NaiveDate::parse_from_str(value.text()?, "%F")?)
}
}
impl<'r> Decode<'r, Sqlite> for NaiveTime {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
let value = value.text()?;
#[rustfmt::skip] let sqlite_time_formats = &[
"%T.f", "%T%.f",
"%R", "%RZ", "%T%.fZ", "%R%:z", "%T%.f%:z",
];
for format in sqlite_time_formats {
if let Ok(dt) = NaiveTime::parse_from_str(value, format) {
return Ok(dt);
}
}
Err(format!("invalid time: {value}").into())
}
}