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
This commit is contained in:
Hannu Hartikainen 2020-06-09 23:39:39 +03:00 committed by GitHub
parent 6708f7637c
commit c4154bb8c4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -57,6 +57,18 @@ pub struct PageFrontMatter {
pub extra: Map<String, Value>, pub extra: Map<String, Value>,
} }
/// 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<NaiveDateTime> {
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 { impl PageFrontMatter {
pub fn parse(toml: &str) -> Result<PageFrontMatter> { pub fn parse(toml: &str) -> Result<PageFrontMatter> {
let mut f: PageFrontMatter = match toml::from_str(toml) { let mut f: PageFrontMatter = match toml::from_str(toml) {
@ -83,27 +95,20 @@ impl PageFrontMatter {
f.date_to_datetime(); 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) Ok(f)
} }
/// Converts the TOML datetime to a Chrono naive datetime /// Converts the TOML datetime to a Chrono naive datetime
/// Also grabs the year/month/day tuple that will be used in serialization /// Also grabs the year/month/day tuple that will be used in serialization
pub fn date_to_datetime(&mut self) { pub fn date_to_datetime(&mut self) {
self.datetime = if let Some(ref d) = self.date { self.datetime = self.date.as_ref().and_then(parse_datetime);
if d.contains('T') { self.datetime_tuple = self.datetime.map(|dt| (dt.year(), dt.month(), dt.day()));
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
};
} }
pub fn order(&self) -> usize { pub fn order(&self) -> usize {
@ -198,7 +203,7 @@ mod tests {
date = 2016-10-10 date = 2016-10-10
"#; "#;
let res = PageFrontMatter::parse(content).unwrap(); let res = PageFrontMatter::parse(content).unwrap();
assert!(res.date.is_some()); assert!(res.datetime.is_some());
} }
#[test] #[test]
@ -209,7 +214,51 @@ mod tests {
date = 2002-10-02T15:00:00Z date = 2002-10-02T15:00:00Z
"#; "#;
let res = PageFrontMatter::parse(content).unwrap(); 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] #[test]