use super::TimeWarpError;
use crate::day_of_week::DayOfWeek;
use crate::doy::{Doy, Tempus};
use crate::error::parse_error;
use crate::month_of_year::Month;
use pest::iterators::Pairs;
use pest::Parser;
use std::str::FromStr;
#[derive(Parser, Debug, Default)]
#[grammar = "date_matcher.pest"]
struct DateMatcher;
#[derive(Eq, PartialEq, Debug, Copy, Clone)]
pub enum Direction {
To,
From,
}
#[allow(clippy::unnecessary_wraps)]
#[inline]
fn ok_moment(d: Doy) -> Result<Tempus, TimeWarpError> {
Ok(Tempus::Moment(d))
}
fn correct_yyyy(yy: i32, relative: i32) -> i32 {
if yy > 100 {
return yy;
}
let offset = relative % 100;
let base = relative - offset;
if yy > offset + 50 {
base - 100 + yy
} else if yy < offset - 50 {
base + 100 + yy
} else {
base + yy
}
}
fn yy_mm_dd(pairs: Pairs<'_, Rule>, today: Doy) -> Result<Tempus, TimeWarpError> {
let mut yy = today.year;
let mut mm = 0;
let mut dd = 0;
for pair in pairs {
match pair.as_rule() {
Rule::yyyy => yy = correct_yyyy(i32::from_str(pair.as_str())?, today.year),
Rule::mm => mm = i32::from_str(pair.as_str())?,
Rule::dd => dd = i32::from_str(pair.as_str())?,
_ => return parse_error(format!("No date. Found more than expected: {pair:?}")),
};
}
ok_moment(Doy::from_ymd(yy, mm, dd))
}
fn date_long(pairs: Pairs<'_, Rule>, today: Doy) -> Result<Tempus, TimeWarpError> {
let mut yy = today.year;
let mut mm = 0;
let mut dd = 0;
for pair in pairs {
match pair.as_rule() {
Rule::yyyy => yy = correct_yyyy(i32::from_str(pair.as_str())?, today.year),
Rule::month => {
mm = Month::from_month(pair.into_inner().next().unwrap().as_rule()) as i32;
}
Rule::dd => dd = i32::from_str(pair.as_str())?,
_ => return parse_error(format!("No long-date. Found more than expected: {pair:?}")),
};
}
if yy < 100 {
yy += 2000;
}
ok_moment(Doy::from_ymd(yy, mm, dd))
}
fn date_week(pairs: Pairs<'_, Rule>, today: Doy) -> Result<Tempus, TimeWarpError> {
let mut yy = today.year;
let mut kw = 0;
for pair in pairs {
match pair.as_rule() {
Rule::yyyy => yy = correct_yyyy(i32::from_str(pair.as_str())?, today.year),
Rule::kw => kw = i32::from_str(pair.as_str())?,
_ => return parse_error(format!("No week-date. Found more than expected: {pair:?}")),
}
}
let start = Doy::from_week(yy, kw);
Ok(Tempus::Interval(start, start + 7))
}
pub fn date_matcher(
today: Doy,
direction: Direction,
date: impl Into<String>,
) -> Result<Tempus, TimeWarpError> {
let text = date.into();
let mut amount = 0i32;
let mut forwards = direction == Direction::To;
for pair in DateMatcher::parse(Rule::date_matcher, &text)?
.next()
.unwrap()
.into_inner()
{
match pair.as_rule() {
Rule::date_iso | Rule::date_en | Rule::date_de => {
return yy_mm_dd(pair.into_inner(), today)
}
Rule::date_long => return date_long(pair.into_inner(), today),
Rule::date_kw => return date_week(pair.into_inner(), today),
Rule::today => return ok_moment(today),
Rule::yesterday => return ok_moment(today - 1),
Rule::tomorrow => return ok_moment(today + 1),
Rule::last => forwards = false,
Rule::next => forwards = true,
Rule::amount => amount = i32::from_str(pair.as_str())?,
Rule::fore_last => {
forwards = false;
amount = 1;
}
Rule::after_next => {
forwards = true;
amount = 1;
}
Rule::day_of_week => {
let wd_today = today.day_of_week();
let target_wd =
DayOfWeek::from_day_of_week(pair.into_inner().next().unwrap().as_rule());
let date = if forwards {
today + target_wd.days_before(wd_today) + amount * 7
} else {
today - wd_today.days_before(target_wd) - amount * 7
};
return ok_moment(date);
}
Rule::month => {
let month = Month::from_month(pair.into_inner().next().unwrap().as_rule());
let date = find_rel_month(today, direction, forwards, month);
return ok_moment(date);
}
Rule::timeunit => {
return ok_moment(find_timeunit(
pair.into_inner().next().unwrap().as_rule(),
today,
amount,
))
}
_ => return parse_error(format!("date_matcher :: {pair:?}")),
};
}
parse_error("Nothing found")
}
fn find_rel_month(today: Doy, direction: Direction, future: bool, target_month: Month) -> Doy {
let target_month = target_month + i32::from(direction == Direction::To);
let today_m = today.month();
let add = if target_month > today_m && !future {
-1
} else {
i32::from(target_month < today_m && future)
};
Doy::from_ymd(today.year + add, target_month as i32, 1)
}
fn find_timeunit(rule: Rule, today: Doy, amount: i32) -> Doy {
match rule {
Rule::days => today + amount,
Rule::months => {
let mut m = today.month() as i32 + amount;
let mut y = today.year;
while m > 12 {
m -= 12;
y += 1;
}
while m < 1 {
m += 12;
y -= 1;
}
Doy::from_ymd(y, m, today.day_of_month())
}
Rule::years => Doy::new(today.doy, today.year + amount),
_ => today,
}
}
#[cfg(test)]
mod should {
use crate::date_matcher;
use crate::date_matcher::{correct_yyyy, find_rel_month};
use crate::Direction::{From, To};
use crate::Month::{Aug, Jan};
use crate::{Doy, Tempus};
#[test]
fn adjust_yyyy() {
assert_eq!(2023, correct_yyyy(2023, 2023));
assert_eq!(2023, correct_yyyy(23, 2023));
assert_eq!(2023, correct_yyyy(23, 1995));
assert_eq!(1989, correct_yyyy(89, 2023));
assert_eq!(2089, correct_yyyy(89, 2043));
}
#[test]
fn find_relative_months() {
let today = Doy::from_ymd(2023, 3, 17);
assert_eq!(Doy::new(1, 2023), find_rel_month(today, From, false, Jan));
assert_eq!(
Tempus::Moment(Doy::new(1, 2023)),
date_matcher(today, From, "last january").unwrap(),
);
assert_eq!(
Tempus::Moment(Doy::new(1, 2024)),
date_matcher(today, From, "next january").unwrap(),
);
assert_eq!(
Doy::from_ymd(2023, 9, 1),
find_rel_month(today, To, true, Aug)
);
assert_eq!(
Doy::from_ymd(2022, 8, 1),
find_rel_month(today, From, false, Aug)
);
assert_eq!(
Doy::from_ymd(2022, 9, 1),
find_rel_month(today, To, false, Aug)
);
assert_eq!(
Tempus::Moment(Doy::from_ymd(2024, 2, 1)),
date_matcher(today, To, "next january").unwrap(),
);
}
#[test]
fn find_relative_dates() {
let today = Doy::from_ymd(2023, 3, 17);
assert_eq!(
Tempus::Moment(Doy::from_ymd(2023, 3, 13)),
date_matcher(today, To, "last monday").unwrap(),
);
assert_eq!(
Tempus::Moment(Doy::from_ymd(2023, 3, 14)),
date_matcher(today, From, "tuesday").unwrap(),
);
assert_eq!(
Tempus::Moment(Doy::from_ymd(2023, 3, 21)),
date_matcher(today, To, "tuesday").unwrap(),
);
assert_eq!(
Tempus::Moment(Doy::from_ymd(2023, 3, 16)),
date_matcher(today, From, "letzten donnerstag").unwrap(),
);
assert_eq!(
Tempus::Moment(Doy::from_ymd(2023, 3, 10)),
date_matcher(today, To, "last friday").unwrap(),
);
assert_eq!(
Tempus::Moment(Doy::from_ymd(2023, 3, 24)),
date_matcher(today, To, "nächsten Fr").unwrap(),
);
assert_eq!(
Tempus::Moment(Doy::from_ymd(2023, 3, 23)),
date_matcher(today, To, "coming Thu").unwrap(),
);
assert_eq!(
Tempus::Moment(Doy::from_ymd(2023, 3, 30)),
date_matcher(today, To, "übernächsten Donnerstag").unwrap(),
);
assert_eq!(
Tempus::Moment(Doy::from_ymd(2023, 3, 20)),
date_matcher(today, To, "nächster Mo").unwrap(),
);
assert_eq!(
Tempus::Moment(Doy::from_ymd(2023, 3, 6)),
date_matcher(today, To, "vorletzter mo").unwrap(),
);
}
#[test]
fn find_yesterday() {
let first_of_march = Doy::from_ymd(2023, 3, 1);
assert_eq!(
Tempus::Moment(Doy::from_ymd(2023, 3, 1)),
date_matcher(first_of_march, To, "heute").unwrap(),
);
assert_eq!(
Tempus::Moment(Doy::from_ymd(2023, 2, 28)),
date_matcher(first_of_march, To, "yesterday").unwrap(),
);
assert_eq!(
Tempus::Moment(Doy::from_ymd(2023, 3, 2)),
date_matcher(first_of_march, To, "morgen").unwrap(),
);
}
#[test]
fn adding_times() {
let today = Doy::from_ymd(2023, 3, 17);
assert_eq!(
Tempus::Moment(Doy::from_ymd(2023, 3, 22)),
date_matcher(today, From, "+5 Tage").unwrap(),
);
let today = Doy::from_ymd(2023, 3, 17);
assert_eq!(
Tempus::Moment(Doy::from_ymd(2022, 3, 17)),
date_matcher(today, From, "-1 year").unwrap(),
);
assert_eq!(
Tempus::Moment(Doy::from_ymd(2022, 2, 17)),
date_matcher(today, From, "-13 month").unwrap(),
);
}
#[test]
fn parse_date() {
let today = Doy::from_ymd(2023, 3, 17);
assert_eq!(
Tempus::Moment(Doy::from_ymd(2023, 1, 22)),
date_matcher(today, From, "22.01.2023").unwrap(),
);
assert_eq!(
Tempus::Moment(Doy::from_ymd(2023, 1, 22)),
date_matcher(today, From, "22.1.23").unwrap(),
);
assert_eq!(
Tempus::Moment(Doy::from_ymd(2023, 1, 22)),
date_matcher(today, From, "22.1.").unwrap(),
);
assert_eq!(
Tempus::Moment(Doy::from_ymd(2023, 3, 16)),
date_matcher(today, From, "3/16/2023").unwrap(),
);
assert_eq!(
Tempus::Moment(Doy::from_ymd(2023, 3, 16)),
date_matcher(today, From, "2023-03-16").unwrap(),
);
assert_eq!(
Tempus::Moment(Doy::from_ymd(2023, 3, 16)),
date_matcher(today, From, " 23-03-16 ").unwrap(),
);
assert_eq!(
Tempus::Moment(Doy::from_ymd(2023, 3, 16)),
date_matcher(today, From, "16. Mär 2023").unwrap(),
);
assert_eq!(
Tempus::Moment(Doy::from_ymd(2023, 3, 16)),
date_matcher(today, From, "16. März 2023").unwrap(),
);
assert_eq!(
Tempus::Moment(Doy::from_ymd(2023, 3, 16)),
date_matcher(today, From, "March 16th 2023").unwrap(),
);
}
#[test]
fn parse_week() {
let today = Doy::from_ymd(2023, 3, 17);
assert_eq!(
Tempus::Interval(Doy::from_ymd(2023, 3, 27), Doy::from_ymd(2023, 4, 3)),
date_matcher(today, From, "2023-W13").unwrap(),
);
assert_eq!(
Tempus::Interval(Doy::from_ymd(2020, 12, 21), Doy::from_ymd(2020, 12, 28)),
date_matcher(today, From, "Woche 2020-52").unwrap(),
);
assert_eq!(
Tempus::Interval(Doy::from_ymd(2020, 12, 21), Doy::from_ymd(2020, 12, 28)),
date_matcher(today, From, "KW 20/52").unwrap(),
);
}
}