[Thiago Cafe] Programming is fun!

How to deserialise DateTime from toml configuration

Created by Thiago Guedes on 2024-04-25 17:07:56

Tags: #rust   #toml  

When implementing a new configuration parameter for Texted, I needed something to represent the date the blog started.

When looking into the TOML website, I was fortunate to discover that it has a data type for dates, so I could create the new parameter

blog_start_date = 2016-06-25

However, my fortune was gone as I found that the Toml crate does not support deserialising.

First, I tried to find if any other crate implemented this, but I found nothing that worked and most of them were over complicated for my use case.

Then I thought, I am pretty sure I am not the only one with this need and with some searching, I found in Arnau Siches website a nice solution for what I needed and I am sharing here a slightly modified version of their solution

use std::str::FromStr;  
  
use chrono::{NaiveDate, ParseError};  
use serde::Deserialize;  
  
// Code adapted from https://www.seachess.net/notes/toml-dates/  
#[derive(Copy, Clone, PartialEq, Debug)]  
pub struct TomlDate(pub NaiveDate);  
  
impl<'de> Deserialize<'de> for TomlDate {  
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>  
        where  
            D: serde::Deserializer<'de>,  
    {  
        use serde::de::Error;  
        let value = toml::value::Datetime::deserialize(deserializer)?;  
        let date = TomlDate::from_str(&value.to_string()).map_err(Error::custom)?;  
        Ok(date)  
    }  
}  
  
impl FromStr for TomlDate {  
    type Err = ParseError;  
  
    fn from_str(s: &str) -> Result<Self, Self::Err> {  
        let naive = NaiveDate::from_str(s)?;  
        Ok(Self(naive))  
    }  
}

Of course, one small unit test to make sure it works.

#[cfg(test)]  
mod tests {  
    use serde::Deserialize;  
  
    use super::*;  
  
    #[derive(Deserialize)]  
    pub struct Personal {  
        pub activity_start_year: i32,  
        pub blog_start_date: TomlDate,  
    }  
  
    #[derive(Deserialize)]  
    pub struct Config {  
        pub personal: Personal,  
    }  
  
    #[test]  
    fn test_date_time() {  
        let toml_str = r##"  
[personal]  
activity_start_year = 2000  
blog_start_date = 2024-04-22  
"##;  
        let cfg: Config = toml::from_str::<Config>(toml_str).unwrap();  
        assert_eq!(cfg.personal.blog_start_date, TomlDate(NaiveDate::from_ymd_opt(2024, 04, 22).unwrap()));  
    }  
}

To conclude, please note that this code was adapted from a CC BY-NC 4.0 license

Tell me your opinion!

Reach me on Twitter - @thiedri