158 lines
4.5 KiB
Rust
158 lines
4.5 KiB
Rust
use chrono::{Datelike, NaiveDate};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::{fmt, 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,
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, PartialOrd, Deserialize, Serialize)]
|
|
#[typeshare]
|
|
pub enum Date {
|
|
Year(i32),
|
|
YearMonth(i32, u32),
|
|
Date(chrono::NaiveDate),
|
|
}
|
|
|
|
impl fmt::Display for Date {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match self {
|
|
Date::Year(y) => write!(f, "{}", y),
|
|
Date::YearMonth(y, m) => write!(f, "{}-{}", y, m),
|
|
Date::Date(date) => write!(f, "{}-{}-{}", date.year(), date.month(), date.day()),
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
impl TryFrom<&str> for Date {
|
|
type Error = String;
|
|
fn try_from(s: &str) -> Result<Date, Self::Error> {
|
|
let date_parts = s.split("-").collect::<Vec<&str>>();
|
|
|
|
if date_parts.len() >= 1 {
|
|
let year = date_parts[0]
|
|
.parse::<i32>()
|
|
.map_err(|e| format!("{:?}", e))?;
|
|
|
|
if date_parts.len() >= 2 {
|
|
let month = date_parts[1]
|
|
.parse::<u32>()
|
|
.map_err(|e| format!("{:?}", e))?;
|
|
|
|
if date_parts.len() >= 3 {
|
|
let day = date_parts[2]
|
|
.parse::<u32>()
|
|
.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<Vec<i32>, Error> {
|
|
s.split('-')
|
|
.map(|s| s.parse::<i32>().map_err(Error::ParseNumberError))
|
|
.collect::<Result<Vec<i32>, Error>>()
|
|
}
|
|
|
|
pub fn parse_date_field(s: &str) -> Result<Vec<Date>, Error> {
|
|
let date_elements = s.split(',');
|
|
let mut dates = Vec::new();
|
|
|
|
let mut most_recent: Option<Date> = 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, *v2 as u32, *v3 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()),
|
|
])
|
|
);
|
|
}
|
|
}
|