1use winnow::{
2 Parser,
3 ascii::{Caseless, multispace0, multispace1},
4 combinator::{alt, delimited, opt, preceded},
5};
6
7use crate::{
8 DayReference, DayTime, Direction, LanguageParser, Meridiem, RelativeTime, StandardDate, Time,
9 TimeExpression, TimeUnit, Weekday, WeekdayModifier, common,
10};
11
12pub struct EnglishParser;
13
14impl EnglishParser {
15 fn parse_number(input: &mut &str) -> winnow::Result<i64> {
16 alt((
17 common::parse_digit_number,
18 alt((
19 Caseless("an").value(1),
20 Caseless("a").value(1),
21 Caseless("one").value(1),
22 )),
23 Caseless("two").value(2),
24 Caseless("three").value(3),
25 Caseless("four").value(4),
26 Caseless("five").value(5),
27 Caseless("six").value(6),
28 Caseless("seven").value(7),
29 Caseless("eight").value(8),
30 Caseless("nine").value(9),
31 Caseless("ten").value(10),
32 ))
33 .parse_next(input)
34 }
35
36 fn parse_time_unit(input: &mut &str) -> winnow::Result<TimeUnit> {
37 alt((
38 alt((
39 Caseless("seconds").value(TimeUnit::Second),
40 Caseless("second").value(TimeUnit::Second),
41 Caseless("secs").value(TimeUnit::Second),
42 Caseless("sec").value(TimeUnit::Second),
43 Caseless("s").value(TimeUnit::Second),
44 )),
45 alt((
46 Caseless("minutes").value(TimeUnit::Minute),
47 Caseless("minute").value(TimeUnit::Minute),
48 Caseless("mins").value(TimeUnit::Minute),
49 Caseless("min").value(TimeUnit::Minute),
50 )),
51 alt((
52 Caseless("hours").value(TimeUnit::Hour),
53 Caseless("hour").value(TimeUnit::Hour),
54 Caseless("hrs").value(TimeUnit::Hour),
55 Caseless("hr").value(TimeUnit::Hour),
56 Caseless("h").value(TimeUnit::Hour),
57 )),
58 alt((
59 Caseless("days").value(TimeUnit::Day),
60 Caseless("day").value(TimeUnit::Day),
61 Caseless("d").value(TimeUnit::Day),
62 )),
63 alt((
64 Caseless("weeks").value(TimeUnit::Week),
65 Caseless("week").value(TimeUnit::Week),
66 Caseless("wks").value(TimeUnit::Week),
67 Caseless("wk").value(TimeUnit::Week),
68 Caseless("w").value(TimeUnit::Week),
69 )),
70 alt((
71 Caseless("months").value(TimeUnit::Month),
72 Caseless("month").value(TimeUnit::Month),
73 Caseless("mos").value(TimeUnit::Month),
74 Caseless("mo").value(TimeUnit::Month),
75 )),
76 alt((
77 Caseless("years").value(TimeUnit::Year),
78 Caseless("year").value(TimeUnit::Year),
79 Caseless("yrs").value(TimeUnit::Year),
80 Caseless("yr").value(TimeUnit::Year),
81 Caseless("y").value(TimeUnit::Year),
82 )),
83 Caseless("m").value(TimeUnit::Minute),
85 ))
86 .parse_next(input)
87 }
88
89 fn parse_relative_past(input: &mut &str) -> winnow::Result<TimeExpression> {
90 (
91 Self::parse_number,
92 multispace1,
93 Self::parse_time_unit,
94 multispace1,
95 Caseless("ago"),
96 )
97 .map(|(amount, _, unit, _, _)| {
98 TimeExpression::Relative(RelativeTime {
99 amount,
100 unit,
101 direction: Direction::Past,
102 })
103 })
104 .parse_next(input)
105 }
106
107 fn parse_relative_future(input: &mut &str) -> winnow::Result<TimeExpression> {
108 preceded(
109 (Caseless("in"), multispace1),
110 (Self::parse_number, multispace1, Self::parse_time_unit),
111 )
112 .map(|(amount, _, unit)| {
113 TimeExpression::Relative(RelativeTime {
114 amount,
115 unit,
116 direction: Direction::Future,
117 })
118 })
119 .parse_next(input)
120 }
121
122 fn parse_now(input: &mut &str) -> winnow::Result<TimeExpression> {
123 Caseless("now").value(TimeExpression::Now).parse_next(input)
124 }
125
126 fn parse_iso_datetime(input: &mut &str) -> winnow::Result<TimeExpression> {
127 common::parse_iso_datetime(input)
128 }
129
130 fn parse_weekday(input: &mut &str) -> winnow::Result<Weekday> {
131 alt((
132 alt((
133 Caseless("monday").value(Weekday::Monday),
134 Caseless("mon").value(Weekday::Monday),
135 )),
136 alt((
137 Caseless("tuesday").value(Weekday::Tuesday),
138 Caseless("tue").value(Weekday::Tuesday),
139 )),
140 alt((
141 Caseless("wednesday").value(Weekday::Wednesday),
142 Caseless("wed").value(Weekday::Wednesday),
143 )),
144 alt((
145 Caseless("thursday").value(Weekday::Thursday),
146 Caseless("thu").value(Weekday::Thursday),
147 )),
148 alt((
149 Caseless("friday").value(Weekday::Friday),
150 Caseless("fri").value(Weekday::Friday),
151 )),
152 alt((
153 Caseless("saturday").value(Weekday::Saturday),
154 Caseless("sat").value(Weekday::Saturday),
155 )),
156 alt((
157 Caseless("sunday").value(Weekday::Sunday),
158 Caseless("sun").value(Weekday::Sunday),
159 )),
160 ))
161 .parse_next(input)
162 }
163
164 fn parse_day_shortcuts(input: &mut &str) -> winnow::Result<DayReference> {
165 alt((
166 Caseless("today").value(DayReference::Today),
167 Caseless("yesterday").value(DayReference::Yesterday),
168 Caseless("tomorrow").value(DayReference::Tomorrow),
169 ))
170 .parse_next(input)
171 }
172
173 fn parse_weekday_modifier(input: &mut &str) -> winnow::Result<WeekdayModifier> {
174 alt((
175 Caseless("last").value(WeekdayModifier::Last),
176 Caseless("next").value(WeekdayModifier::Next),
177 ))
178 .parse_next(input)
179 }
180
181 fn parse_modified_weekday(input: &mut &str) -> winnow::Result<DayReference> {
182 (
183 Self::parse_weekday_modifier,
184 multispace1,
185 Self::parse_weekday,
186 )
187 .map(|(modifier, _, day)| DayReference::Weekday {
188 day,
189 modifier: Some(modifier),
190 })
191 .parse_next(input)
192 }
193
194 fn parse_simple_weekday(input: &mut &str) -> winnow::Result<DayReference> {
195 Self::parse_weekday
196 .map(|day| DayReference::Weekday {
197 day,
198 modifier: None,
199 })
200 .parse_next(input)
201 }
202
203 fn parse_day_reference(input: &mut &str) -> winnow::Result<TimeExpression> {
204 alt((
205 Self::parse_day_shortcuts,
206 Self::parse_modified_weekday,
207 Self::parse_simple_weekday,
208 ))
209 .map(TimeExpression::Day)
210 .parse_next(input)
211 }
212
213 fn parse_meridiem(input: &mut &str) -> winnow::Result<Meridiem> {
214 alt((
215 alt((
216 Caseless("am").value(Meridiem::AM),
217 Caseless("a.m.").value(Meridiem::AM),
218 )),
219 alt((
220 Caseless("pm").value(Meridiem::PM),
221 Caseless("p.m.").value(Meridiem::PM),
222 )),
223 ))
224 .parse_next(input)
225 }
226
227 fn parse_time_digits(input: &mut &str) -> winnow::Result<(u8, u8, u8, Option<Meridiem>)> {
228 let hour = common::parse_two_digit_number(input)?;
229 ':'.parse_next(input)?;
230 let minute = common::parse_two_digit_number(input)?;
231 let second = opt(preceded(':', common::parse_two_digit_number))
232 .parse_next(input)?
233 .unwrap_or(0);
234 let meridiem = opt(preceded(multispace1, Self::parse_meridiem)).parse_next(input)?;
235
236 Ok((hour, minute, second, meridiem))
237 }
238
239 fn parse_time(input: &mut &str) -> winnow::Result<TimeExpression> {
240 Self::parse_time_digits
241 .map(|(hour, minute, second, meridiem)| {
242 TimeExpression::Time(Time {
243 hour,
244 minute,
245 second,
246 meridiem,
247 })
248 })
249 .parse_next(input)
250 }
251
252 fn parse_day_at_time(input: &mut &str) -> winnow::Result<TimeExpression> {
253 (
254 alt((
255 Self::parse_day_shortcuts,
256 Self::parse_modified_weekday,
257 Self::parse_simple_weekday,
258 )),
259 preceded(
260 multispace1,
261 preceded(
262 Caseless("at"),
263 preceded(multispace1, Self::parse_time_digits),
264 ),
265 ),
266 )
267 .map(|(day, (hour, minute, second, meridiem))| {
268 TimeExpression::DayTime(DayTime {
269 day,
270 time: Time {
271 hour,
272 minute,
273 second,
274 meridiem,
275 },
276 })
277 })
278 .parse_next(input)
279 }
280
281 fn parse_date_format(input: &mut &str) -> winnow::Result<TimeExpression> {
282 alt((
283 (
285 common::parse_four_digit_number,
286 '-',
287 common::parse_two_digit_number,
288 '-',
289 common::parse_two_digit_number,
290 )
291 .map(|(year, _, month, _, day)| {
292 TimeExpression::Date(StandardDate { day, month, year })
293 }),
294 (
296 common::parse_two_digit_number,
297 alt(('/', '-')),
298 common::parse_two_digit_number,
299 alt(('/', '-')),
300 common::parse_four_digit_number,
301 )
302 .map(|(day, _, month, _, year)| {
303 TimeExpression::Date(StandardDate { day, month, year })
304 }),
305 ))
306 .parse_next(input)
307 }
308}
309
310impl LanguageParser for EnglishParser {
311 fn parse<'a>(
312 &self,
313 input: &'a str,
314 ) -> Result<TimeExpression, winnow::error::ParseError<&'a str, winnow::error::ContextError>>
315 {
316 delimited(
317 multispace0,
318 alt((
319 Self::parse_iso_datetime,
320 Self::parse_date_format,
321 Self::parse_day_at_time,
322 Self::parse_now,
323 Self::parse_day_reference,
324 Self::parse_time,
325 Self::parse_relative_past,
326 Self::parse_relative_future,
327 )),
328 multispace0,
329 )
330 .parse(input)
331 }
332}