mod parsed_comp;
mod separator;
pub(crate) use parsed_comp::ParsedComponent;
pub use separator::DateComponentSeparator;
use crate::{Date, GregorianDate, SacOrGreg};
use core::{fmt::Display, num::NonZeroU8};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ComponentOrder {
YMD,
DMY,
MDY,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ParsedSacOrGreg {
pub(crate) date: SacOrGreg,
pub(crate) format: ParsedFormat,
}
impl ParsedSacOrGreg {
pub fn to_sac13(self) -> Option<ParsedDate> {
match self.date {
SacOrGreg::Sac13(date) => Some(ParsedDate {
date,
format: self.format,
}),
_ => None,
}
}
pub fn to_greg(self) -> Option<ParsedGregorianDate> {
match self.date {
SacOrGreg::Gregorian(date) => Some(ParsedGregorianDate {
date,
format: self.format,
}),
_ => None,
}
}
pub fn date(&self) -> &SacOrGreg {
&self.date
}
pub fn format(&self) -> &ParsedFormat {
&self.format
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ParsedDate {
pub date: Date,
pub format: ParsedFormat,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ParsedGregorianDate {
pub date: GregorianDate,
pub format: ParsedFormat,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ParsedFormat {
pub(crate) comp_ord: ComponentOrder,
pub(crate) len_day: NonZeroU8,
pub(crate) len_month: NonZeroU8,
pub(crate) len_year: NonZeroU8,
pub(crate) separators: [DateComponentSeparator; 2],
}
impl ParsedFormat {
pub fn len_year(&self) -> NonZeroU8 {
self.len_year
}
pub fn len_month(&self) -> NonZeroU8 {
self.len_month
}
pub fn len_day(&self) -> NonZeroU8 {
self.len_day
}
pub fn comp_ord(&self) -> ComponentOrder {
self.comp_ord
}
pub fn separators(&self) -> &[DateComponentSeparator; 2] {
&self.separators
}
}
impl Display for ParsedFormat {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
fn write_component(
f: &mut core::fmt::Formatter<'_>,
x: (char, NonZeroU8),
) -> core::fmt::Result {
for _ in 0..x.1.get() as usize {
write!(f, "{}", x.0)?;
}
Ok(())
}
let y = ('Y', self.len_year);
let m = ('M', self.len_month);
let d = ('D', self.len_day);
let components = match self.comp_ord {
ComponentOrder::YMD => [y, m, d],
ComponentOrder::DMY => [d, m, y],
ComponentOrder::MDY => [m, d, y],
};
write_component(f, components[0])?;
write!(f, "{}", self.separators[0].as_str())?;
write_component(f, components[1])?;
write!(f, "{}", self.separators[1].as_str())?;
write_component(f, components[2])?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! assert_matches {
($left:expr, $right:pat) => {
assert!(matches!($left, $right));
};
}
macro_rules! assert_parse_error {
($inp:literal) => {
assert_matches!(SacOrGreg::parse_str($inp), None);
};
}
macro_rules! parse_expect_greg {
($inp:expr) => {{
const ERROR: &str = concat!(
"Expected ",
stringify!($inp),
" to parse as Gregorian date."
);
GregorianDate::parse_str($inp).expect(ERROR)
}};
}
macro_rules! parse_expect_sac13 {
($inp:expr) => {{
const ERROR: &str = concat!(
"Expected ",
stringify!($inp),
" to parse as Gregorian date."
);
Date::parse_str($inp).expect(ERROR)
}};
}
macro_rules! assert_sac13 {
($inp:expr, $y:ident - $m:literal - $d:literal) => {{
let parsed = parse_expect_sac13!($inp);
assert_eq!(parsed.date, date!($y - $m - $d));
}};
($inp:expr, $pat:expr, $y:ident - $m:literal - $d:literal) => {{
let parsed = parse_expect_sac13!($inp);
assert_eq!(parsed.date, date!($y - $m - $d));
let parsed_pattern = format!("{}", parsed.format);
assert_eq!(parsed_pattern, $pat);
}};
}
macro_rules! assert_greg {
($inp:expr, $y:literal - $m:literal - $d:literal) => {{
let parsed = parse_expect_greg!($inp);
assert_eq!(parsed.date, date_greg!($y - $m - $d));
}};
($inp:expr, $pat:expr, $y:literal - $m:literal - $d:literal) => {{
let parsed = parse_expect_greg!($inp);
assert_eq!(parsed.date, date_greg!($y - $m - $d));
let parsed_pattern = format!("{}", parsed.format);
assert_eq!(parsed_pattern, $pat);
}};
}
#[test]
fn parsing_gregorian() {
assert_greg!("11-12-2000", "DD-MM-YYYY", 2000 - 12 - 11);
assert_greg!("11.12.2000", "DD.MM.YYYY", 2000 - 12 - 11);
assert_greg!("2000-12-11", "YYYY-MM-DD", 2000 - 12 - 11);
assert_greg!("2000.12.11", "YYYY.MM.DD", 2000 - 12 - 11);
assert_greg!("2000/12/11", "YYYY/MM/DD", 2000 - 12 - 11);
assert_greg!("12/11/2000", "MM/DD/YYYY", 2000 - 12 - 11);
}
#[test]
fn negative_year_greg() {
assert_greg!("-2000-12-11", "YYYY-MM-DD", -2000 - 12 - 11);
assert_greg!("11-12--2000", "DD-MM-YYYY", -2000 - 12 - 11);
assert_greg!("-2000.12.11", "YYYY.MM.DD", -2000 - 12 - 11);
assert_greg!("11.12.-2000", "DD.MM.YYYY", -2000 - 12 - 11);
}
#[test]
fn parsing_sac13() {
assert_sac13!("M003-02-01", "YYYY-MM-DD", M003 - 02 - 01);
assert_sac13!("M003.02.01", "YYYY.MM.DD", M003 - 02 - 01);
assert_sac13!("M003/02/01", "YYYY/MM/DD", M003 - 02 - 01);
assert_sac13!("01-02-M003", "DD-MM-YYYY", M003 - 02 - 01);
assert_sac13!("01.02.M003", "DD.MM.YYYY", M003 - 02 - 01);
assert_sac13!("01/02/M003", "DD/MM/YYYY", M003 - 02 - 01);
}
#[test]
fn no_letter_allowed_as_month() {
assert_parse_error!("2001-L-03");
}
#[test]
fn no_letter_allowed_as_day() {
assert_parse_error!("2001-02-L");
}
#[test]
fn ambiguous_year_end_fails_to_parse() {
assert_parse_error!("2020-12-2020");
}
#[test]
fn no_year_end_fails_to_parse() {
assert_parse_error!("01-01-01");
}
#[test]
fn three_digit_years_can_be_parsed() {
assert_greg!("001-01-01", "YYY-MM-DD", 1 - 1 - 1);
assert_greg!("01-01-001", "DD-MM-YYY", 1 - 1 - 1);
}
#[test]
fn zero_component_is_assumed_to_be_the_year() {
assert_greg!("2-1-0", "D-M-Y", 0 - 1 - 2);
assert_greg!("0-1-2", "Y-M-D", 0 - 1 - 2);
assert_greg!("1/2/0", "M/D/Y", 0 - 1 - 2);
}
#[test]
fn negative_component_is_assumed_to_be_the_year() {
assert_greg!("2-1--1", "D-M-Y", -1 - 1 - 2);
assert_greg!("-1-1-2", "Y-M-D", -1 - 1 - 2);
assert_greg!("1/2/-1", "M/D/Y", -1 - 1 - 2);
}
#[test]
fn plus_is_not_a_sign_but_a_separator() {
assert_greg!("2-1-+2000", "D-M-+YYYY", 2000 - 1 - 2);
assert_parse_error!("+2000-1-2");
assert_greg!("1/2/+0", "D/M/+Y", 0 - 2 - 1);
}
#[test]
fn us_special_case_only_single_slash() {
assert_greg!("2/1/2000", "M/D/YYYY", 2000 - 2 - 1);
assert_greg!("2 /1 /2000", "M /D /YYYY", 2000 - 2 - 1);
assert_greg!("2/ 1/ 2000", "M/ D/ YYYY", 2000 - 2 - 1);
assert_greg!("2 / 1 / 2000", "M / D / YYYY", 2000 - 2 - 1);
assert_greg!("2 // 1 / 2000", "D // M / YYYY", 2000 - 1 - 2);
}
#[test]
fn weird_but_valid_cases() {
assert_greg!(" 2&%01()--1 ", "D&%MM()-Y", -1 - 1 - 2);
assert_greg!("2 --- 1 --- 0", "D --- M --- Y", 0 - 1 - 2);
assert_greg!("2 --- 1 --- 0", "D --- M --- Y", 0 - 1 - 2);
assert_greg!("23, 12 2004", "DD, MM YYYY", 2004 - 12 - 23);
assert_greg!("-11 - 11 - 11", "YY - MM - DD", -11 - 11 - 11);
assert_greg!(
"01######02######2005",
"DD######MM######YYYY",
2005 - 02 - 01
);
}
#[test]
fn seven_long_spacers_are_not_allowed() {
assert_parse_error!("01#######02#2005");
assert_parse_error!("01#02#######2005");
}
#[test]
fn long_day_month_components_fail_to_parse() {
assert_parse_error!("01-001-2000");
assert_parse_error!("001-01-2000");
}
}