langtime 0.2.1

A rust library to parse english dates
Documentation
use chrono::prelude::*;
use chrono::Duration;
use nom::branch::alt;
use nom::character::complete::space0;
use nom::sequence::terminated;
use nom::sequence::tuple;
use nom::{Parser, IResult};
use nom::bytes::complete::tag;
use nom::combinator::opt;

use crate::parsers::generic::*;
use crate::utils::extract_datetime;
use crate::utils::month_string_to_int;

pub fn parse_dmy(input: &str) -> IResult<&str, DateTime<Local>, ()> {
    let (tail, day) = day1(input)?;
    let (tail, _) = tag("/").parse(tail)?;
    let (tail, month) = month1(tail)?;
    let (tail, _) = tag("/").parse(tail)?;
    let (tail, year) = year(tail)?;

    let dt_opt = Local.with_ymd_and_hms(year, month, day, 0, 0, 0);

    let dt = extract_datetime(dt_opt)?;

    Ok((tail, dt))
}

pub fn parse_mdy(input: &str) -> IResult<&str, DateTime<Local>, ()> {
    let (tail, month) = month1(input)?;
    let (tail, _) = tag("/").parse(tail)?;
    let (tail, day) = day1(tail)?;
    let (tail, _) = tag("/").parse(tail)?;
    let (tail, year) = year(tail)?;

    let dt_opt = Local.with_ymd_and_hms(year, month, day, 0, 0, 0);

    let dt = extract_datetime(dt_opt)?;

    Ok((tail, dt))
}

pub fn parse_my(input: &str) -> IResult<&str, DateTime<Local>, ()> {
    let (tail, month) = month1(input)?;
    let (tail, _) = tag("/").parse(tail)?;
    let (tail, year) = year(tail)?;

    let dt_opt = Local.with_ymd_and_hms(year, month, 1, 0, 0, 0);

    let dt = extract_datetime(dt_opt)?;

    Ok((tail, dt))
}

pub fn named_dates(input: &str) -> IResult<&str, DateTime<Local>, ()> {
    let (tail, data) = alt((
        tag("yesterday"),
        tag("tomorrow"),
        tag("today")
    )).parse(input)?;

    let cur = Local::now().round_subsecs(0);

    match data {
        "yesterday" => Ok((tail, cur - Duration::days(1))),
        "tomorrow" => Ok((tail, cur + Duration::days(1))),
        "today" => Ok((tail, cur)),
        _ => Err(nom::Err::<()>::Error(()))
    }
}

pub fn spelled_dates_uk(input: &str) -> IResult<&str, DateTime<Local>, ()> {
    let (tail, (day, _, month_str, year_opt)) = tuple((
        terminated(
            day1,
            opt(alt((
                tag("st"),
                tag("nd"),
                tag("rd"),
                tag("th")
            )))
        ),
        tag(" "),
        month_name,
        opt(tuple((
            tag(" "),
            year
        )))
    )).parse(input)?;

    let month = month_string_to_int(month_str)
                .map_err(|_| nom::Err::<()>::Error(()))?;

    let year = match year_opt {
        Some((_, y)) => y,
        None => Local::now().year()
    };

    let dt_opt = Local.with_ymd_and_hms(year, month, day, 0, 0, 0);
    let dt = extract_datetime(dt_opt)?;

    Ok((tail, dt))
}

pub fn spelled_dates_us(input: &str) -> IResult<&str, DateTime<Local>, ()> {
    let (tail, (month_str, _, day, year_opt)) = tuple((
        month_name,
        tag(" "),
        terminated(
            day1,
            opt(alt((
                tag("st"),
                tag("nd"),
                tag("rd"),
                tag("th")
            )))
        ),
        opt(tuple((
            tuple((
                opt(tag(",")),
                tag(" ")
            )),
            year
        )))
    )).parse(input)?;

    let month = month_string_to_int(month_str)
                .map_err(|_| nom::Err::<()>::Error(()))?;

    let year = match year_opt {
        Some((_, y)) => y,
        None => Local::now().year()
    };

    let dt_opt = Local.with_ymd_and_hms(year, month, day, 0, 0, 0);
    let dt = extract_datetime(dt_opt)?;

    Ok((tail, dt))
}

pub fn named_months(input: &str) -> IResult<&str, DateTime<Local>, ()> {
    let (tail, m_str) = month_name(input)?;
    let (tail, _) = tag(" ").parse(tail)?;
    let (tail, y) = year(tail)?;
    
    let m = month_string_to_int(m_str)
                .map_err(|_| nom::Err::<()>::Error(()))?;

    let dt_opt = Local.with_ymd_and_hms(y, m, 1, 0, 0, 0);
    let dt = extract_datetime(dt_opt)?;

    Ok((tail, dt))
}

pub fn parse_time(input: &str) -> IResult<&str, DateTime<Local>, ()> {
    let (tail, data) = tuple((
        hour1,
        tag(":"),
        minute1,
        opt(
            tuple((
                tag(":"),
                second1,
            ))
        )
    )).parse(input)?;

    let (hour, _, minute, opt_sec) = data;

    let mut second = 0;

    if let Some((_, sec)) = opt_sec {
        second = sec;
    }

    let now = Local::now().round_subsecs(0);
    let dt_opt = Local.with_ymd_and_hms(
        now.year(),
        now.month(),
        now.day(),
        hour,
        minute,
        second
    );

    let dt = extract_datetime(dt_opt)?;

    Ok((tail, dt))
}

pub fn parse_time_ampm(input: &str) -> IResult<&str, DateTime<Local>, ()> {
    let (tail, data) = tuple((
        hour1,
        opt(
            tuple((
                tag(":"),
                minute1,
                opt(
                    tuple((
                        tag(":"),
                        second1,
                    ))
                )
            ))
        ),
        space0,
        alt((
            tag("a.m."),
            tag("am"),
            tag("p.m."),
            tag("pm")
        ))
    )).parse(input)?;

    let (hour, opt_min_sec, _, ampm) = data;

    if hour > 12 {
        return Err(nom::Err::<()>::Error(()));
    }

    let mut hour = hour;

    match ampm {
        "a.m." | "am" => {
            if hour == 12 { hour = 0 }
        },
        "p.m." | "pm" => {
            if hour < 12 { hour += 12 }
        },
        _ => ()
    }

    let mut minute = 0;
    let mut second = 0;

    match opt_min_sec {
        Some((_, mins, None)) => minute = mins,
        Some((_, mins, Some((_, secs)))) => {
            minute = mins;
            second = secs;
        },
        None => ()
    };

    let now = Local::now().round_subsecs(0);
    let dt_opt = Local.with_ymd_and_hms(
        now.year(),
        now.month(),
        now.day(),
        hour,
        minute,
        second
    );

    let dt = extract_datetime(dt_opt)?;

    Ok((tail, dt))
}

pub fn parse_time_spelled(input: &str) -> IResult<&str, DateTime<Local>, ()> {
    let (tail, res) = alt((
        parse_oclock,
        parse_subminutes
    )).parse(input)?;

    Ok((tail, res))
}

fn parse_oclock(input: &str) -> IResult<&str, DateTime<Local>, ()> {
    let (tail, (hour, _)) = tuple((
        hour1,
        tag(" o'clock")
    )).parse(input)?;

    let now = Local::now().round_subsecs(0);
    let dt_opt = Local.with_ymd_and_hms(now.year(), now.month(), now.day(), hour, 0, 0);
    let dt = extract_datetime(dt_opt)?;

    Ok((tail, dt))
}

fn parse_subminutes(input: &str) -> IResult<&str, DateTime<Local>, ()> {
    let (tail, (amount, rel, hour)) = tuple((
        alt((
            tag("half "),
            tag("a quarter ")
        )),
        alt((
            tag("past "),
            tag("to ")
        )),
        hour1
    )).parse(input)?;

    let minutes = match amount {
        "half " => 30,
        "a quarter " => 15,
        _ => 0 // this will never happen
    };

    let duration = match rel {
        "past " => Duration::minutes(minutes),
        "to " => Duration::minutes(-1 * minutes),
        _ => Duration::minutes(0) // this will never happen
    };

    let now = Local::now();
    let dt_opt = Local.with_ymd_and_hms(now.year(), now.month(), now.day(), hour, 0, 0);
    let mut dt = extract_datetime(dt_opt)?;
    dt += duration;

    Ok((tail, dt))
}