Parsing DateTime with serde_json

Hi. I am parsing a JSON-array passed to an actix-web-endpoint. One of the elements is a RFC 3339-formatted DateTime with time zone like 2021-03-24T20:50:00+01:00. I wrote tests which pass on my local machine. But when I tested using Github Actions the same tests failed. I asked on stackoverflow and one suggested it was an issue with the time zone where the test was run. I first dismissed the idea but changed the test to UTC (+00:00) and tests passed on GH-actions. And failed on my local machine which is GMT+1 (+2 during summertime).

I pass the JSON-array to this endpoint:

#[post("/test_weather_data")]
pub(crate) async fn test_weather_data_post(weather_data: String) -> Result<String> {
    let wd: Vec<Reading> = serde_json::from_str(&*weather_data).unwrap();

    Ok(format!("id: {}, index: {}", &wd[0].id, &wd[0].index))
}

The struct is:

#[derive(Deserialize, Insertable)]
pub struct Reading {
    #[serde(with = "my_date_format")]
    pub measurement_time_default: DateTime<Local>,
    pub id: i32,
    pub index: i32,
    pub field_description: String,
    pub measurement: f32,
}

my_date_format is

mod my_date_format {
    use chrono::{DateTime, Local, TimeZone};
    use serde::{self, Deserialize, Deserializer};

    const FORMAT: &str = "%+";

    pub fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<Local>, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        Local
            .datetime_from_str(&s, FORMAT)
            .map_err(serde::de::Error::custom)
    }
}

The error I get is

thread 'test::test_weather_data_ok' panicked at 'called `Result::unwrap()` on an `Err` value: Error("no possible date and time matching input", line: 1, column: 56)', src/controller.rs:63:65

It seems the DateTime being parsed must match the time zone on the machine being run on. Am I using serde_json the wrong way?

Regards
Claus

What does my_date_format look like? It’s not shown in your snippets

I'll update the question. Thank you.

Thanks for updating. So the documentation says that datetime_from_str will only succeed if there's no time zone in the text or the time zone matches the target TimeZone, which explains the failure if the time zone is different. The docs point to DateTime::parse_from_str to parse a DateTime with any TimeZone into a DateTime<FixedOffset> object, so something like this would work

DateTime::parse_from_str(&s, FORMAT)
            .map(Into::into)
            .map_err(serde::de::Error::custom)

Into::into is needed to map the type from DateTime<FixedOffset> to DateTime<Local> to match your return type.

That said, chrono implements serialization directly with rfc3339 as the default format. You need to enable the serde feature flag. Then you can get rid of your custom deserialization code and just go with

#[derive(Deserialize, Insertable)]
pub struct Reading {
    pub measurement_time_default: DateTime<Local>,
    pub id: i32,
    pub index: i32,
    pub field_description: String,
    pub measurement: f32,
}
1 Like

Wow, that works. Thank you. :smile:

If you don't mind you can answer the question on Stackoverflow and I'll mark it as the correct answer (rust - Cargo test pass on local machine but fails on github actions - Stack Overflow).

np and done.

I'm assuming my first ever Stack Overflow response will bring me instant fame and fortune

2 Likes

Tested both the change to the custom deserialization and then changed the chrono-import in Cargo.toml to include feature serde and removed the custom deserialization. Both solutions work. Makes the code a lot cleaner.

Thank you for your concise explanation and suggestions.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.