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