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.
  • Loading branch information
philolo1 committed Aug 23, 2023
1 parent b615eff commit f8e60b4
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 3 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
target/
fuzz/corpus
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
// Expose parse_datetime
pub mod parse_datetime;

mod parse_weekday;

use chrono::{Duration, Local, NaiveDate, Utc};
use regex::{Error as RegexError, Regex};
use std::error::Error;
Expand Down
45 changes: 42 additions & 3 deletions src/parse_datetime.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

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

use crate::ParseDurationError;
use crate::{parse_weekday, ParseDurationError};

/// Formats that parse input can take.
/// Taken from `touch` coreutils
Expand Down Expand Up @@ -90,6 +92,24 @@ pub fn from_str<S: AsRef<str> + Clone>(s: S) -> Result<DateTime<FixedOffset>, Pa
}
}

// 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(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 @@ -255,13 +275,32 @@ mod tests {
];

for offset in offsets {
let time = Utc.timestamp(offset, 0);
let time = Utc.timestamp_opt(offset, 0).unwrap();
let dt = from_str(format!("@{}", offset));
assert_eq!(dt.unwrap(), time);
}
}
}

mod weekday {
use chrono::Timelike;

use crate::parse_datetime::from_str;

#[test]
fn test_weekday() {
let days = ["mon", "tues", "wed", "thu", "fri", "sat", "sun"];
for day in days {
let el = from_str(day);
assert!(el.is_ok());
let el = el.unwrap();
assert_eq!(el.hour(), 0);
assert_eq!(el.minute(), 0);
assert_eq!(el.second(), 0);
}
}
}

/// Used to test example code presented in the README.
mod readme_test {
use crate::parse_datetime::from_str;
Expand Down
80 changes: 80 additions & 0 deletions src/parse_weekday.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use chrono::{ParseWeekdayError, Weekday};

const WEEKDAY_CONVERSION_MAP: [(&str, &str); 3] =
[("tues", "tue"), ("thurs", "thu"), ("thus", "thu")];

pub(crate) fn parse_weekday(s: &str) -> Result<Weekday, ParseWeekdayError> {
let mut s = s.trim().to_lowercase();
for (before, after) in WEEKDAY_CONVERSION_MAP {
if s == before {
s = String::from(after);
}
}

s.trim().parse::<Weekday>()
}

#[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 f8e60b4

Please sign in to comment.