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 f072913
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 1 deletion.
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"
53 changes: 52 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 mut beginning_of_day = Local
.with_ymd_and_hms(date.year(), date.month(), date.day(), 0, 0, 0)
.unwrap();

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.naive_local()) {
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 Expand Up @@ -353,6 +375,35 @@ mod tests {
}
}

#[cfg(test)]
mod weekday {
use crate::parse_datetime;
#[test]
fn test_weekday() {
let weekdays = vec![
"mon",
"monday",
"tue",
"tues",
"tuesday",
"wed",
"wednesday",
"thu",
"thursday",
"fri",
"friday",
"sat",
"saturday",
"sun",
"sunday",
];

for weekday in weekdays {
assert_eq!(parse_datetime(weekday).is_ok(), true);
}
}
}

#[cfg(test)]
mod timestamp {
use crate::parse_datetime;
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 f072913

Please sign in to comment.