use chrono::{Datelike, NaiveDate}; use serde::{Deserialize, Serialize}; use std::num::ParseIntError; use thiserror::Error; use typeshare::typeshare; #[derive(Debug, Error, PartialEq)] pub enum Error { #[error("Failed to parse integer {0}")] ParseNumberError(ParseIntError), #[error("Invalid date")] InvalidDate, #[error("unsupported date format")] Unsupported, } #[derive(Clone, Debug, PartialEq, PartialOrd, Deserialize, Serialize)] #[typeshare] pub enum Date { Year(i32), YearMonth(i32, u32), Date(chrono::NaiveDate), } impl Date { pub fn to_string(&self) -> String { match self { Date::Year(y) => format!("{}", y), Date::YearMonth(y, m) => format!("{}-{}", y, m), Date::Date(date) => format!("{}-{}-{}", date.year(), date.month(), date.day()), } } } /* impl TryFrom<&str> for Date { type Error = String; fn try_from(s: &str) -> Result { let date_parts = s.split("-").collect::>(); if date_parts.len() >= 1 { let year = date_parts[0] .parse::() .map_err(|e| format!("{:?}", e))?; if date_parts.len() >= 2 { let month = date_parts[1] .parse::() .map_err(|e| format!("{:?}", e))?; if date_parts.len() >= 3 { let day = date_parts[2] .parse::() .map_err(|e| format!("{:?}", e))?; let date = chrono::NaiveDate::from_ymd_opt(year, month, day).ok_or("invalid date")?; Ok(Date::Date(date)) } else { Ok(Date::YearMonth(year, month)) } } else { Ok(Date::Year(year)) } } else { return Err("no elements".to_owned()); } } } */ fn parse_numbers(s: &str) -> Result, Error> { s.split("-") .map(|s| s.parse::().map_err(|err| Error::ParseNumberError(err))) .collect::, Error>>() } pub fn parse_date_field(s: &str) -> Result, Error> { let date_elements = s.split(","); let mut dates = Vec::new(); let mut most_recent: Option = None; for element in date_elements { let fields = parse_numbers(element)?; let new_date = match fields.as_slice() { [] => panic!("all segments must have a field"), [v1] => match most_recent { Some(Date::Year(_)) => Date::Year(*v1), Some(Date::YearMonth(y, _)) => Date::YearMonth(y, *v1 as u32), Some(Date::Date(d)) => { Date::Date(d.clone().with_day(*v1 as u32).ok_or(Error::InvalidDate)?) } None => Date::Year(*v1), }, [v1, v2] => Date::YearMonth(*v1, *v2 as u32), [v1, v2, v3, ..] => Date::Date( NaiveDate::from_ymd_opt(v1.clone(), v2.clone() as u32, v3.clone() as u32).unwrap(), ), }; dates.push(new_date.clone()); most_recent = Some(new_date); } Ok(dates) } #[cfg(test)] mod test { use super::*; #[test] fn it_parses_a_year() { assert_eq!(parse_date_field("1996"), Ok(vec![Date::Year(1996)])); } #[test] fn it_parses_a_month() { assert_eq!( parse_date_field("1996-12"), Ok(vec![Date::YearMonth(1996, 12)]) ); } #[test] fn it_parses_a_date() { assert_eq!( parse_date_field("1996-12-27"), Ok(vec![Date::Date( NaiveDate::from_ymd_opt(1996, 12, 27).unwrap() )]) ); } #[test] fn it_parses_date_continuation() { assert_eq!( parse_date_field("1996-12-27,28"), Ok(vec![ Date::Date(NaiveDate::from_ymd_opt(1996, 12, 27).unwrap()), Date::Date(NaiveDate::from_ymd_opt(1996, 12, 28).unwrap()) ]) ); } #[test] fn it_parses_date_crossing_year_boundary() { assert_eq!( parse_date_field("1996-12-27,28,1997-01-03,04"), Ok(vec![ Date::Date(NaiveDate::from_ymd_opt(1996, 12, 27).unwrap()), Date::Date(NaiveDate::from_ymd_opt(1996, 12, 28).unwrap()), Date::Date(NaiveDate::from_ymd_opt(1997, 1, 3).unwrap()), Date::Date(NaiveDate::from_ymd_opt(1997, 1, 4).unwrap()), ]) ); } }