Rust: DateTime::parse_from_rfc3339 gives ParseError(Invalid)

Viewed 24

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)', src\main.rs:37:70

Can somebody please tell me, what could be wrong?

1 Answers

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.

Related