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