Skip to content

Commit

Permalink
fix: Use rfc3339 to decode date from text
Browse files Browse the repository at this point in the history
  • Loading branch information
pierre-wehbe committed Aug 7, 2024
1 parent a892ebc commit de9eab2
Showing 1 changed file with 52 additions and 6 deletions.
58 changes: 52 additions & 6 deletions sqlx-postgres/src/types/chrono/datetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,25 +86,71 @@ impl<Tz: TimeZone> Encode<'_, Postgres> for DateTime<Tz> {

impl<'r> Decode<'r, Postgres> for DateTime<Local> {
fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
let naive = <NaiveDateTime as Decode<Postgres>>::decode(value)?;
Ok(Local.from_utc_datetime(&naive))
let fixed = <DateTime<FixedOffset> as Decode<Postgres>>::decode(value)?;
Ok(Local.from_utc_datetime(&fixed.naive_utc()))
}
}

impl<'r> Decode<'r, Postgres> for DateTime<Utc> {
fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
let naive = <NaiveDateTime as Decode<Postgres>>::decode(value)?;
Ok(Utc.from_utc_datetime(&naive))
let fixed = <DateTime<FixedOffset> as Decode<Postgres>>::decode(value)?;
Ok(Utc.from_utc_datetime(&fixed.naive_utc()))
}
}

impl<'r> Decode<'r, Postgres> for DateTime<FixedOffset> {
fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
let naive = <NaiveDateTime as Decode<Postgres>>::decode(value)?;
Ok(Utc.fix().from_utc_datetime(&naive))
Ok(match value.format() {
PgValueFormat::Binary => {
let naive = <NaiveDateTime as Decode<Postgres>>::decode(value)?;
Utc.fix().from_utc_datetime(&naive)
}

PgValueFormat::Text => {
let s = value.as_str()?;
decode_datetime_from_text(s)
}
})
}
}

fn decode_datetime_from_text(value: &str) -> DateTime<FixedOffset> {
if let Ok(dt) = DateTime::parse_from_rfc3339(value) {
return dt;
}

// Loop over common date time patterns, inspired by Diesel
// https://github.com/diesel-rs/diesel/blob/93ab183bcb06c69c0aee4a7557b6798fd52dd0d8/diesel/src/sqlite/types/date_and_time/chrono.rs#L56-L97
let sqlite_datetime_formats = &[
// Most likely format
"%F %T%.f",
// Other formats in order of appearance in docs
"%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 dt;
}

if let Ok(dt) = NaiveDateTime::parse_from_str(value, format) {
return Utc.fix().from_utc_datetime(&dt);
}
}

panic!("BUG: failed to parse {value:?} as a DateTime")
}

#[inline]
fn postgres_epoch_datetime() -> NaiveDateTime {
NaiveDate::from_ymd_opt(2000, 1, 1)
Expand Down

0 comments on commit de9eab2

Please sign in to comment.