[SOLVED] Rust: DateTime::parse_from_rfc3339 gives ParseError(Invalid)

Issue

This Content is from Stack Overflow. Question asked by Roman Theuer

I am trying to get some data using Bungie API. There is last played date in json response in format like “2022-09-18T02:17:59Z”. I would like to parse it and create DateTime object.

I have following code (personal data replaced by XXX):

const API_ROOT: &str = "https://www.bungie.net/Platform";
const API_KEY_HEADER: &str = "X-API-Key";

const MY_API_KEY: &str = "XXX";

const MEMBERSHIP_TYPE: &str = "XXX";
const MEMBERSHIP_ID: &str = "XXX";

#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
    let url = format!("{}/Destiny2/{}/Profile/{}/?components=100", API_ROOT, MEMBERSHIP_TYPE, MEMBERSHIP_ID);

    let req = reqwest::Client::new()
        .get(url)
        .header(API_KEY_HEADER, MY_API_KEY)
        .send()
        .await?
        .text()
        .await?;

    let data: JsonValue = serde_json::from_str(&req).expect("Failed to read json from response!");

    let name = &data["Response"]["profile"]["data"]["userInfo"]["displayName"].to_string();
    println!("Name: {}", name);

    let last_played_raw = &data["Response"]["profile"]["data"]["dateLastPlayed"].to_string();
    println!("{}", last_played_raw);
    let last_played_raw = last_played_raw.replace("Z", "+00:00");
    println!("{}", last_played_raw);

    let last_played = DateTime::parse_from_rfc3339(&last_played_raw).unwrap();

    println!("Last played: {}", last_played);

    Ok(())
}

Problem is that parsing last_played_raw string panicks:

"2022-09-18T02:17:59Z"
"2022-09-18T02:17:59+00:00"
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: ParseError(Invalid)', srcmain.rs:37:70

Can somebody please tell me, what could be wrong?



Solution

The reason this happens is fairly simple. It happens because the leading and trailing quotation marks (") are included in the string that you passed into DateTime::parse_from_rfc3339(). Probably the least incisive change to fix this would be to replace these with empty string literals like so:

extern crate serde_json;
extern crate serde;

use serde_json::Value as JsonValue;
use chrono::DateTime;

fn main() {
    let json_string = r#"{"dateLastPlayed": "2022-09-18T02:17:59Z"}"#;

    let data: JsonValue = serde_json::from_str(json_string).unwrap();

    let last_played = data["dateLastPlayed"].to_string().replace("\"", "");
    println!("{}", last_played.as_str());

    let datetime = DateTime::parse_from_rfc3339(last_played.as_str()).expect("Failed to parse");
    println!("{}", datetime);
}

Here is a link to the playground where you can see this easily.

However in serde you typically don’t work with the raw Value and instead handle deserializing like so:

use serde::Deserialize;
use chrono::DateTime;

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct MyData {
    date_last_played: String,
}

fn main() {
    let json_string = r#"{"dateLastPlayed": "2022-09-18T02:17:59Z"}"#;

    let data: MyData = serde_json::from_str(json_string).unwrap();

    let datetime = DateTime::parse_from_rfc3339(data.date_last_played.as_str()).expect("Failed to parse");
    println!("{}", datetime);
}

Here is the playground for the above.

You should also be able to directly parse the JSON using this struct

struct MyData {
    date_last_played: DateTime,
}

which would be the preferred choice, but you will need a custom serde Deserializer for this.


This Question was asked in StackOverflow by Roman Theuer and Answered by frankenapps It is licensed under the terms of CC BY-SA 2.5. - CC BY-SA 3.0. - CC BY-SA 4.0.

people found this article helpful. What about you?