datasources-postgres 0.1.21

Postgres datasource provider
Documentation
use chrono::{DateTime, NaiveDateTime, Utc};
use datasources::{Result, TypeConverter, Value};

pub struct DateTimeConverter;

fn parse_text(text: &str) -> Option<DateTime<Utc>> {
    DateTime::parse_from_rfc3339(text)
        .ok()
        .map(|parsed| parsed.with_timezone(&Utc))
        .or_else(|| {
            let iso = text.replace(' ', "T");
            let padded = if iso.ends_with("+00") || iso.ends_with("-00") {
                format!("{iso}:00")
            } else {
                iso
            };
            DateTime::parse_from_rfc3339(&padded)
                .ok()
                .map(|parsed| parsed.with_timezone(&Utc))
        })
        .or_else(|| {
            NaiveDateTime::parse_from_str(text, "%Y-%m-%d %H:%M:%S%.f")
                .or_else(|_| NaiveDateTime::parse_from_str(text, "%Y-%m-%d %H:%M:%S"))
                .ok()
                .map(|naive| naive.and_utc())
        })
}

impl TypeConverter<Value, Value> for DateTimeConverter {
    fn convert(&self, from: &Value) -> Result<Value> {
        Ok(match from {
            Value::Text(text) => parse_text(text)
                .map(Value::DateTime)
                .unwrap_or_else(|| from.clone()),
            other => other.clone(),
        })
    }

    fn reverse_convert(&self, to: &Value) -> Result<Value> {
        Ok(match to {
            Value::DateTime(date) => Value::Text(Value::iso_datetime(*date)),
            other => other.clone(),
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use datasources::Value;

    #[test]
    fn maps_datetime() {
        let converter = DateTimeConverter;
        let native = converter
            .convert(&Value::from("2020-01-02T03:04:05.000Z"))
            .unwrap();
        assert!(matches!(native, Value::DateTime(_)));
        assert_eq!(
            converter.reverse_convert(&native).unwrap(),
            Value::from("2020-01-02T03:04:05.000Z")
        );
        assert_eq!(
            converter
                .convert(&Value::from("2020-01-02 03:04:05+00"))
                .unwrap(),
            native
        );
        assert_eq!(
            converter
                .convert(&Value::from("2020-01-02 03:04:05.000"))
                .unwrap(),
            native
        );
        assert_eq!(
            converter.convert(&Value::from("nope")).unwrap(),
            Value::from("nope")
        );
        assert_eq!(converter.convert(&Value::Int(1)).unwrap(), Value::Int(1));
        assert_eq!(
            converter.reverse_convert(&Value::Int(1)).unwrap(),
            Value::Int(1)
        );
    }
}