Skip to content

Commit

Permalink
Support weekdays in parse_datetime
Browse files Browse the repository at this point in the history
This commit resolves issue uutils#23.

Adds parse_weekday function that uses chrono weekday parser with a map for edge cases.
Adds tests cases to make sure it works correctly.
Use nom for parsing.
  • Loading branch information
philolo1 committed Aug 29, 2023
1 parent 2737b4a commit 2cfe2fd
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 1 deletion.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
target/
fuzz/corpus
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ readme = "README.md"
[dependencies]
regex = "1.9"
chrono = { version="0.4", default-features=false, features=["std", "alloc", "clock"] }
nom = "7.1.3"
24 changes: 23 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ use std::fmt::{self, Display};
// Expose parse_datetime
mod parse_relative_time;

use chrono::{DateTime, FixedOffset, Local, LocalResult, NaiveDateTime, TimeZone};
mod parse_weekday;

use chrono::{
DateTime, Datelike, Duration, FixedOffset, Local, LocalResult, NaiveDateTime, TimeZone,
};

use parse_relative_time::parse_relative_time;

Expand Down Expand Up @@ -168,6 +172,24 @@ pub fn parse_datetime_at_date<S: AsRef<str> + Clone>(
}
}

// parse weekday
if let Ok(weekday) = parse_weekday::parse_weekday(s.as_ref()) {
let now = Local::now().naive_local();
let beginning_of_day = Local.with_ymd_and_hms(now.year(), now.month(), now.day(), 0, 0, 0);
let mut beginning_of_day = beginning_of_day.unwrap().naive_local();

loop {
if beginning_of_day.weekday() == weekday {
break;
}
beginning_of_day += Duration::days(1);
}

if let Ok(dt) = naive_dt_to_fixed_offset(date, beginning_of_day) {
return Ok(dt);
}
}

// Parse epoch seconds
if s.as_ref().bytes().next() == Some(b'@') {
if let Ok(parsed) = NaiveDateTime::parse_from_str(&s.as_ref()[1..], "%s") {
Expand Down
117 changes: 117 additions & 0 deletions src/parse_weekday.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
use chrono::Weekday;
use nom::branch::alt;
use nom::bytes::complete::tag_no_case;
use nom::combinator::map;
use nom::{self, Finish, IResult};

pub(crate) fn parse_weekday(s: &str) -> Result<Weekday, &str> {
let s = s.trim();

let parse_result: IResult<&str, Weekday> = nom::combinator::all_consuming(alt((
map(alt((tag_no_case("monday"), tag_no_case("mon"))), |_| {
Weekday::Mon
}),
map(
alt((
tag_no_case("tuesday"),
tag_no_case("tues"),
tag_no_case("tue"),
)),
|_| Weekday::Tue,
),
map(alt((tag_no_case("wednesday"), tag_no_case("wed"))), |_| {
Weekday::Wed
}),
map(
alt((
tag_no_case("thursday"),
tag_no_case("thurs"),
tag_no_case("thur"),
tag_no_case("thu"),
)),
|_| Weekday::Thu,
),
map(alt((tag_no_case("friday"), tag_no_case("fri"))), |_| {
Weekday::Fri
}),
map(alt((tag_no_case("saturday"), tag_no_case("sat"))), |_| {
Weekday::Sat
}),
map(alt((tag_no_case("sunday"), tag_no_case("sun"))), |_| {
Weekday::Sun
}),
)))(s);

let parse_result = parse_result.finish();

match parse_result {
Ok((_, weekday)) => Ok(weekday),
Err(res) => Err(res.input),
}
}

#[cfg(test)]
mod tests {

use chrono::Weekday::*;

use crate::parse_weekday::parse_weekday;

#[test]
fn test_valid_weekdays() {
let days = [
("mon", Mon),
("monday", Mon),
("tue", Tue),
("tues", Tue),
("tuesday", Tue),
("wed", Wed),
("wednesday", Wed),
("thu", Thu),
("thursday", Thu),
("fri", Fri),
("friday", Fri),
("sat", Sat),
("saturday", Sat),
("sun", Sun),
("sunday", Sun),
];

for day in days.iter() {
let mut test_strings = vec![];
test_strings.push(String::from(day.0));
test_strings.push(format!(" {}", day.0));
test_strings.push(format!(" {} ", day.0));
test_strings.push(format!("{} ", day.0));
test_strings.push(format!(" {}", day.0.to_uppercase()));

let (left, right) = day.0.split_at(1);
test_strings.push(format!("{}{}", left.to_uppercase(), right.to_lowercase()));
test_strings.push(format!("{}{}", left.to_lowercase(), right.to_uppercase()));

for str in test_strings.iter() {
assert!(parse_weekday(str).is_ok());
assert_eq!(parse_weekday(str).unwrap(), day.1);
}
}
}

#[test]
fn test_invalid_weekdays() {
let days = [
"mond",
"tuesda",
"we",
"th",
"fr",
"sa",
"su",
"garbageday",
"tomorrow",
"yesterday",
];
for day in days {
assert!(parse_weekday(day).is_err());
}
}
}

0 comments on commit 2cfe2fd

Please sign in to comment.