Skip to content

Commit fe29267

Browse files
committed
Support weekdays in parse_datetime
This commit resolves issue #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.
1 parent 2737b4a commit fe29267

File tree

4 files changed

+185
-1
lines changed

4 files changed

+185
-1
lines changed

Cargo.lock

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ readme = "README.md"
1010
[dependencies]
1111
regex = "1.9"
1212
chrono = { version="0.4", default-features=false, features=["std", "alloc", "clock"] }
13+
nom = "7.1.3"

src/lib.rs

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ use std::fmt::{self, Display};
1515
// Expose parse_datetime
1616
mod parse_relative_time;
1717

18-
use chrono::{DateTime, FixedOffset, Local, LocalResult, NaiveDateTime, TimeZone};
18+
mod parse_weekday;
19+
20+
use chrono::{
21+
DateTime, Datelike, Duration, FixedOffset, Local, LocalResult, NaiveDateTime, TimeZone,
22+
};
1923

2024
use parse_relative_time::parse_relative_time;
2125

@@ -168,6 +172,22 @@ pub fn parse_datetime_at_date<S: AsRef<str> + Clone>(
168172
}
169173
}
170174

175+
// parse weekday
176+
if let Ok(weekday) = parse_weekday::parse_weekday(s.as_ref()) {
177+
let mut beginning_of_day = Local.with_ymd_and_hms(date.year(), date.month(), date.day(), 0, 0, 0).unwrap();
178+
179+
loop {
180+
if beginning_of_day.weekday() == weekday {
181+
break;
182+
}
183+
beginning_of_day += Duration::days(1);
184+
}
185+
186+
if let Ok(dt) = naive_dt_to_fixed_offset(date, beginning_of_day.naive_local()) {
187+
return Ok(dt);
188+
}
189+
}
190+
171191
// Parse epoch seconds
172192
if s.as_ref().bytes().next() == Some(b'@') {
173193
if let Ok(parsed) = NaiveDateTime::parse_from_str(&s.as_ref()[1..], "%s") {
@@ -353,6 +373,35 @@ mod tests {
353373
}
354374
}
355375

376+
#[cfg(test)]
377+
mod weekday {
378+
use crate::parse_datetime;
379+
#[test]
380+
fn test_weekday() {
381+
let weekdays = vec![
382+
"mon",
383+
"monday" ,
384+
"tue",
385+
"tues",
386+
"tuesday",
387+
"wed",
388+
"wednesday",
389+
"thu",
390+
"thursday",
391+
"fri",
392+
"friday",
393+
"sat",
394+
"saturday",
395+
"sun",
396+
"sunday",
397+
];
398+
399+
for weekday in weekdays {
400+
assert_eq!(parse_datetime(weekday).is_ok(), true);
401+
}
402+
}
403+
}
404+
356405
#[cfg(test)]
357406
mod timestamp {
358407
use crate::parse_datetime;

src/parse_weekday.rs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
use chrono::Weekday;
2+
use nom::branch::alt;
3+
use nom::bytes::complete::tag_no_case;
4+
use nom::combinator::map;
5+
use nom::{self, Finish, IResult};
6+
7+
pub(crate) fn parse_weekday(s: &str) -> Result<Weekday, &str> {
8+
let s = s.trim();
9+
10+
let parse_result: IResult<&str, Weekday> = nom::combinator::all_consuming(alt((
11+
map(alt((tag_no_case("monday"), tag_no_case("mon"))), |_| {
12+
Weekday::Mon
13+
}),
14+
map(
15+
alt((
16+
tag_no_case("tuesday"),
17+
tag_no_case("tues"),
18+
tag_no_case("tue"),
19+
)),
20+
|_| Weekday::Tue,
21+
),
22+
map(alt((tag_no_case("wednesday"), tag_no_case("wed"))), |_| {
23+
Weekday::Wed
24+
}),
25+
map(
26+
alt((
27+
tag_no_case("thursday"),
28+
tag_no_case("thurs"),
29+
tag_no_case("thur"),
30+
tag_no_case("thu"),
31+
)),
32+
|_| Weekday::Thu,
33+
),
34+
map(alt((tag_no_case("friday"), tag_no_case("fri"))), |_| {
35+
Weekday::Fri
36+
}),
37+
map(alt((tag_no_case("saturday"), tag_no_case("sat"))), |_| {
38+
Weekday::Sat
39+
}),
40+
map(alt((tag_no_case("sunday"), tag_no_case("sun"))), |_| {
41+
Weekday::Sun
42+
}),
43+
)))(s);
44+
45+
let parse_result = parse_result.finish();
46+
47+
match parse_result {
48+
Ok((_, weekday)) => Ok(weekday),
49+
Err(res) => Err(res.input),
50+
}
51+
}
52+
53+
#[cfg(test)]
54+
mod tests {
55+
56+
use chrono::Weekday::*;
57+
58+
use crate::parse_weekday::parse_weekday;
59+
60+
#[test]
61+
fn test_valid_weekdays() {
62+
let days = [
63+
("mon", Mon),
64+
("monday", Mon),
65+
("tue", Tue),
66+
("tues", Tue),
67+
("tuesday", Tue),
68+
("wed", Wed),
69+
("wednesday", Wed),
70+
("thu", Thu),
71+
("thursday", Thu),
72+
("fri", Fri),
73+
("friday", Fri),
74+
("sat", Sat),
75+
("saturday", Sat),
76+
("sun", Sun),
77+
("sunday", Sun),
78+
];
79+
80+
for day in days.iter() {
81+
let mut test_strings = vec![];
82+
test_strings.push(String::from(day.0));
83+
test_strings.push(format!(" {}", day.0));
84+
test_strings.push(format!(" {} ", day.0));
85+
test_strings.push(format!("{} ", day.0));
86+
test_strings.push(format!(" {}", day.0.to_uppercase()));
87+
88+
let (left, right) = day.0.split_at(1);
89+
test_strings.push(format!("{}{}", left.to_uppercase(), right.to_lowercase()));
90+
test_strings.push(format!("{}{}", left.to_lowercase(), right.to_uppercase()));
91+
92+
for str in test_strings.iter() {
93+
assert!(parse_weekday(str).is_ok());
94+
assert_eq!(parse_weekday(str).unwrap(), day.1);
95+
}
96+
}
97+
}
98+
99+
#[test]
100+
fn test_invalid_weekdays() {
101+
let days = [
102+
"mond",
103+
"tuesda",
104+
"we",
105+
"th",
106+
"fr",
107+
"sa",
108+
"su",
109+
"garbageday",
110+
"tomorrow",
111+
"yesterday",
112+
];
113+
for day in days {
114+
assert!(parse_weekday(day).is_err());
115+
}
116+
}
117+
}

0 commit comments

Comments
 (0)