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 Sep 1, 2023
1 parent 2737b4a commit 83189e0
Show file tree
Hide file tree
Showing 4 changed files with 191 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"
78 changes: 77 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ 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,
Timelike,
};

use parse_relative_time::parse_relative_time;

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

// parse weekday
if let Some(weekday) = parse_weekday::parse_weekday(s.as_ref()) {
let mut beginning_of_day = date
.with_hour(0)
.unwrap()
.with_minute(0)
.unwrap()
.with_second(0)
.unwrap()
.with_nanosecond(0)
.unwrap();

while beginning_of_day.weekday() != weekday {
beginning_of_day += Duration::days(1);
}

let dt = DateTime::<FixedOffset>::from(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 Expand Up @@ -353,6 +379,56 @@ mod tests {
}
}

#[cfg(test)]
mod weekday {
use chrono::{DateTime, Local, TimeZone};

use crate::parse_datetime_at_date;

fn get_formatted_date(date: DateTime<Local>, weekday: &str) -> String {
let result = parse_datetime_at_date(date, weekday).unwrap();

return result.format("%F %T %f").to_string();
}
#[test]
fn test_weekday() {
// add some constant hours and minutes and seconds to check its reset
let date = Local.with_ymd_and_hms(2023, 02, 28, 10, 12, 3).unwrap();

// 2023-2-28 is tuesday
assert_eq!(
get_formatted_date(date, "tuesday"),
"2023-02-28 00:00:00 000000000"
);

// 2023-3-01 is wednesday
assert_eq!(
get_formatted_date(date, "wed"),
"2023-03-01 00:00:00 000000000"
);

assert_eq!(
get_formatted_date(date, "thu"),
"2023-03-02 00:00:00 000000000"
);

assert_eq!(
get_formatted_date(date, "fri"),
"2023-03-03 00:00:00 000000000"
);

assert_eq!(
get_formatted_date(date, "sat"),
"2023-03-04 00:00:00 000000000"
);

assert_eq!(
get_formatted_date(date, "sun"),
"2023-03-05 00:00:00 000000000"
);
}
}

#[cfg(test)]
mod timestamp {
use crate::parse_datetime;
Expand Down
96 changes: 96 additions & 0 deletions src/parse_weekday.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
use chrono::Weekday;
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::combinator::value;
use nom::{self, IResult};

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

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

match parse_result {
Ok((_, weekday)) => Some(weekday),
Err(_) => None,
}
}

#[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),
("wednes", Wed),
("wednesday", Wed),
("thu", Thu),
("thursday", Thu),
("fri", Fri),
("friday", Fri),
("sat", Sat),
("saturday", Sat),
("sun", Sun),
("sunday", Sun),
];

for (name, weekday) in days {
assert_eq!(parse_weekday(name), Some(weekday));
assert_eq!(parse_weekday(&format!(" {}", name)), Some(weekday));
assert_eq!(parse_weekday(&format!(" {} ", name)), Some(weekday));
assert_eq!(parse_weekday(&format!("{} ", name)), Some(weekday));

let (left, right) = name.split_at(1);
let (test_str1, test_str2) = (
format!("{}{}", left.to_uppercase(), right.to_lowercase()),
format!("{}{}", left.to_lowercase(), right.to_uppercase()),
);

assert_eq!(parse_weekday(&test_str1), Some(weekday));
assert_eq!(parse_weekday(&test_str2), Some(weekday));
}
}

#[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_none());
}
}
}

0 comments on commit 83189e0

Please sign in to comment.