From c4154bb8c4a2774197a4b1796f9e15f8d061a11e Mon Sep 17 00:00:00 2001 From: Hannu Hartikainen Date: Tue, 9 Jun 2020 23:39:39 +0300 Subject: [PATCH] Don't panic on bad date strings (#1051) * Don't panic on bad date strings Instead, show a helpful error message explaining only RFC3339 is supported. Fixes #993. * Try to parse the full range of TOML date formats --- components/front_matter/src/page.rs | 83 +++++++++++++++++++++++------ 1 file changed, 66 insertions(+), 17 deletions(-) diff --git a/components/front_matter/src/page.rs b/components/front_matter/src/page.rs index c26371af..605cde58 100644 --- a/components/front_matter/src/page.rs +++ b/components/front_matter/src/page.rs @@ -57,6 +57,18 @@ pub struct PageFrontMatter { pub extra: Map, } +/// Parse a TOML datetime from a string. +/// There are three alternatives: an offset datetime (plain RFC3339), a local datetime +/// (RFC3339 with timezone omitted) and a local date (YYYY-MM-DD). This tries each in +/// order. +fn parse_datetime(d: &String) -> Option { + DateTime::parse_from_rfc3339(d) + .or(DateTime::parse_from_rfc3339(format!("{}Z", d).as_ref())) + .map(|s| s.naive_local()) + .or(NaiveDate::parse_from_str(d, "%Y-%m-%d").map(|s| s.and_hms(0, 0, 0))) + .ok() +} + impl PageFrontMatter { pub fn parse(toml: &str) -> Result { let mut f: PageFrontMatter = match toml::from_str(toml) { @@ -83,27 +95,20 @@ impl PageFrontMatter { f.date_to_datetime(); + if let Some(ref date) = f.date { + if f.datetime.is_none() { + bail!("`date` could not be parsed: {}.", date); + } + } + Ok(f) } /// Converts the TOML datetime to a Chrono naive datetime /// Also grabs the year/month/day tuple that will be used in serialization pub fn date_to_datetime(&mut self) { - self.datetime = if let Some(ref d) = self.date { - if d.contains('T') { - DateTime::parse_from_rfc3339(&d).ok().map(|s| s.naive_local()) - } else { - NaiveDate::parse_from_str(&d, "%Y-%m-%d").ok().map(|s| s.and_hms(0, 0, 0)) - } - } else { - None - }; - - self.datetime_tuple = if let Some(ref dt) = self.datetime { - Some((dt.year(), dt.month(), dt.day())) - } else { - None - }; + self.datetime = self.date.as_ref().and_then(parse_datetime); + self.datetime_tuple = self.datetime.map(|dt| (dt.year(), dt.month(), dt.day())); } pub fn order(&self) -> usize { @@ -198,7 +203,7 @@ mod tests { date = 2016-10-10 "#; let res = PageFrontMatter::parse(content).unwrap(); - assert!(res.date.is_some()); + assert!(res.datetime.is_some()); } #[test] @@ -209,7 +214,51 @@ mod tests { date = 2002-10-02T15:00:00Z "#; let res = PageFrontMatter::parse(content).unwrap(); - assert!(res.date.is_some()); + assert!(res.datetime.is_some()); + } + + #[test] + fn can_parse_date_rfc3339_without_timezone() { + let content = r#" + title = "Hello" + description = "hey there" + date = 2002-10-02T15:00:00 + "#; + let res = PageFrontMatter::parse(content).unwrap(); + assert!(res.datetime.is_some()); + } + + #[test] + fn can_parse_date_rfc3339_with_space() { + let content = r#" + title = "Hello" + description = "hey there" + date = 2002-10-02 15:00:00+02:00 + "#; + let res = PageFrontMatter::parse(content).unwrap(); + assert!(res.datetime.is_some()); + } + + #[test] + fn can_parse_date_rfc3339_with_space_without_timezone() { + let content = r#" + title = "Hello" + description = "hey there" + date = 2002-10-02 15:00:00 + "#; + let res = PageFrontMatter::parse(content).unwrap(); + assert!(res.datetime.is_some()); + } + + #[test] + fn can_parse_date_rfc3339_with_microseconds() { + let content = r#" + title = "Hello" + description = "hey there" + date = 2002-10-02T15:00:00.123456Z + "#; + let res = PageFrontMatter::parse(content).unwrap(); + assert!(res.datetime.is_some()); } #[test]