1use std::str::FromStr;
2
3use chrono::{Datelike, Days, Local, NaiveDate, Weekday};
4use winnow::{
5 ascii::{digit1, space1},
6 combinator::{alt, opt},
7 PResult, Parser,
8};
9
10use crate::HumanDateParser;
11
12pub struct HumanDateParserBrazillianPortuguese;
13
14impl HumanDateParserBrazillianPortuguese {
15 pub fn parse(text: &str) -> Option<NaiveDate> {
16 let now = Local::now().naive_local().date();
17 HumanDateParserBrazillianPortuguese::parse_relative(text, now)
18 }
19}
20
21impl HumanDateParser for HumanDateParserBrazillianPortuguese {
22 fn parse_relative(text: &str, now: NaiveDate) -> Option<NaiveDate> {
23 match parse(text) {
24 Ok((_, expr)) => match expr {
25 HumanDateExpr::Keyword(keyword) => match keyword {
26 HumanDateKeyword::Today => Some(now),
27 HumanDateKeyword::Tomorrow => Some(now.checked_add_days(Days::new(1)).unwrap()),
28 HumanDateKeyword::AfterTomorrow => {
29 Some(now.checked_add_days(Days::new(2)).unwrap())
30 }
31 },
32 HumanDateExpr::InNDays(n) => Some(now.checked_add_days(Days::new(n)).unwrap()),
33 HumanDateExpr::ThisWeekWeekday(weekday) => {
34 let n =
35 (7 - now.weekday().number_from_sunday() + weekday.number_from_sunday()) % 7;
36 Some(now.checked_add_days(Days::new(n.into())).unwrap())
37 }
38 HumanDateExpr::NextWeekWeekday(weekday) => {
39 let n = 7
40 + (7 - now.weekday().number_from_sunday() + weekday.number_from_sunday())
41 % 7;
42
43 Some(now.checked_add_days(Days::new(n.into())).unwrap())
44 }
45 },
46 Err(err) => {
47 eprintln!("error: {}", err);
48 None
49 }
50 }
51 }
52}
53
54#[derive(Clone)]
55enum HumanDateKeyword {
56 Today,
57 Tomorrow,
58 AfterTomorrow,
59}
60
61enum HumanDateExpr {
62 Keyword(HumanDateKeyword),
63 InNDays(u64),
64 ThisWeekWeekday(Weekday),
65 NextWeekWeekday(Weekday),
66}
67
68fn parse(input: &str) -> PResult<(&str, HumanDateExpr)> {
69 let mut parser = alt((
70 keyword.map(HumanDateExpr::Keyword),
71 in_n_days.map(HumanDateExpr::InNDays),
72 this_week_weekday.map(HumanDateExpr::ThisWeekWeekday),
73 next_week_weekday.map(HumanDateExpr::NextWeekWeekday),
74 ));
75 let (_, expr) = parser.parse_peek(input)?;
76 Ok((input, expr))
77}
78
79fn keyword(input: &mut &str) -> PResult<HumanDateKeyword> {
80 alt((
81 "hoje".value(HumanDateKeyword::Today),
82 "amanhã".value(HumanDateKeyword::Tomorrow),
83 "depois de amanhã".value(HumanDateKeyword::AfterTomorrow),
84 ))
85 .parse_next(input)
86}
87
88fn in_n_days(input: &mut &str) -> PResult<u64> {
89 let (_, n, _) = (
90 (alt(("daqui", "em")), space1),
91 number,
92 (space1, "dia", opt('s')),
93 )
94 .parse_next(input)?;
95 Ok(n)
96}
97
98fn this_week_weekday(input: &mut &str) -> PResult<Weekday> {
99 let (_, weekday) = (opt((this, space1)), weekday).parse_next(input)?;
100 Ok(weekday)
101}
102
103fn next_week_weekday(input: &mut &str) -> PResult<Weekday> {
104 let (_, _, weekday) = (next, space1, weekday).parse_next(input)?;
105 Ok(weekday)
106}
107
108fn this(input: &mut &str) -> PResult<()> {
109 alt(("esta", "essa", "esse", "este"))
110 .void()
111 .parse_next(input)
112}
113
114fn next(input: &mut &str) -> PResult<()> {
115 alt((
116 "próxima", "proxima", "próximo", "proximo", "próx.", "prox.", "próx", "prox",
117 ))
118 .void()
119 .parse_next(input)
120}
121
122fn number(input: &mut &str) -> PResult<u64> {
123 alt((
124 digit1.try_map(FromStr::from_str),
125 "dezessete".value(17),
126 "dezesseis".value(16),
127 "dezenove".value(19),
128 alt(("quatorze", "catorze")).value(14),
129 "dezoito".value(18),
130 "quinze".value(15),
131 "vinte".value(20),
132 "treze".value(13),
133 "quatro".value(4),
134 "três".value(3),
135 "onze".value(11),
136 "doze".value(12),
137 "cinco".value(5),
138 "sete".value(7),
139 "seis".value(6),
140 "oito".value(8),
141 "nove".value(9),
142 "dois".value(2),
143 "dez".value(10),
144 "um".value(1),
145 ))
146 .parse_next(input)
147}
148
149fn weekday(input: &mut &str) -> PResult<Weekday> {
150 alt((
151 alt(("segunda-feira", "segunda feira", "segunda", "seg.", "seg")).value(Weekday::Mon),
152 alt((
153 "terça-feira",
154 "terca-feira",
155 "terça feira",
156 "terca feira",
157 "terça",
158 "terca",
159 "ter.",
160 "ter",
161 ))
162 .value(Weekday::Tue),
163 alt(("quarta-feira", "quarta feira", "quarta", "qua.", "qua")).value(Weekday::Wed),
164 alt(("quinta-feira", "quinta feira", "quinta", "qui.", "qui")).value(Weekday::Thu),
165 alt(("sexta-feira", "sexta feira", "sexta", "sex.", "sex")).value(Weekday::Fri),
166 alt(("sábado", "sabado", "sáb.", "sab.", "sáb", "sab")).value(Weekday::Sat),
167 alt(("domingo", "dom.", "dom")).value(Weekday::Sun),
168 ))
169 .parse_next(input)
170}
171
172#[cfg(test)]
173mod tests {
174 use chrono::{NaiveDate, Weekday};
175 use winnow::Parser;
176
177 use super::{
178 next, number, this, weekday, HumanDateParser, HumanDateParserBrazillianPortuguese,
179 };
180
181 #[test]
182 fn text_keywords() {
183 let now = NaiveDate::from_ymd_opt(2024, 8, 13).unwrap(); assert_eq!(
186 HumanDateParserBrazillianPortuguese::parse_relative("hoje", now),
187 NaiveDate::from_ymd_opt(2024, 8, 13)
188 );
189
190 assert_eq!(
191 HumanDateParserBrazillianPortuguese::parse_relative("amanhã", now),
192 NaiveDate::from_ymd_opt(2024, 8, 14)
193 );
194
195 assert_eq!(
196 HumanDateParserBrazillianPortuguese::parse_relative("depois de amanhã", now),
197 NaiveDate::from_ymd_opt(2024, 8, 15)
198 );
199 }
200
201 #[test]
202 fn text_in_n_days() {
203 let now = NaiveDate::from_ymd_opt(2024, 8, 13).unwrap(); assert_eq!(
206 HumanDateParserBrazillianPortuguese::parse_relative("daqui 2 dias", now),
207 NaiveDate::from_ymd_opt(2024, 8, 15)
208 );
209 assert_eq!(
210 HumanDateParserBrazillianPortuguese::parse_relative("em 2 dias", now),
211 NaiveDate::from_ymd_opt(2024, 8, 15)
212 );
213 assert_eq!(
214 HumanDateParserBrazillianPortuguese::parse_relative("daqui dois dias", now),
215 NaiveDate::from_ymd_opt(2024, 8, 15)
216 );
217 assert_eq!(
218 HumanDateParserBrazillianPortuguese::parse_relative("em três dias", now),
219 NaiveDate::from_ymd_opt(2024, 8, 16)
220 );
221 }
222
223 #[test]
224 fn test_this_week_weekday() {
225 let now = NaiveDate::from_ymd_opt(2024, 8, 13).unwrap(); assert_eq!(
228 HumanDateParserBrazillianPortuguese::parse_relative("esta terça", now),
229 NaiveDate::from_ymd_opt(2024, 8, 13)
230 );
231 assert_eq!(
232 HumanDateParserBrazillianPortuguese::parse_relative("esta quarta", now),
233 NaiveDate::from_ymd_opt(2024, 8, 14)
234 );
235 assert_eq!(
236 HumanDateParserBrazillianPortuguese::parse_relative("esta quinta", now),
237 NaiveDate::from_ymd_opt(2024, 8, 15)
238 );
239 assert_eq!(
240 HumanDateParserBrazillianPortuguese::parse_relative("esta sexta", now),
241 NaiveDate::from_ymd_opt(2024, 8, 16)
242 );
243 assert_eq!(
244 HumanDateParserBrazillianPortuguese::parse_relative("este sábado", now),
245 NaiveDate::from_ymd_opt(2024, 8, 17)
246 );
247 assert_eq!(
248 HumanDateParserBrazillianPortuguese::parse_relative("este domingo", now),
249 NaiveDate::from_ymd_opt(2024, 8, 18)
250 );
251 assert_eq!(
252 HumanDateParserBrazillianPortuguese::parse_relative("esta segunda", now),
253 NaiveDate::from_ymd_opt(2024, 8, 19)
254 );
255 }
256
257 #[test]
258 fn test_next_week_weekday() {
259 let now = NaiveDate::from_ymd_opt(2024, 8, 13).unwrap(); assert_eq!(
262 HumanDateParserBrazillianPortuguese::parse_relative("próxima terça", now),
263 NaiveDate::from_ymd_opt(2024, 8, 20)
264 );
265 assert_eq!(
266 HumanDateParserBrazillianPortuguese::parse_relative("próxima quarta", now),
267 NaiveDate::from_ymd_opt(2024, 8, 21)
268 );
269 assert_eq!(
270 HumanDateParserBrazillianPortuguese::parse_relative("próxima quinta", now),
271 NaiveDate::from_ymd_opt(2024, 8, 22)
272 );
273 assert_eq!(
274 HumanDateParserBrazillianPortuguese::parse_relative("próxima sexta", now),
275 NaiveDate::from_ymd_opt(2024, 8, 23)
276 );
277 assert_eq!(
278 HumanDateParserBrazillianPortuguese::parse_relative("próximo sábado", now),
279 NaiveDate::from_ymd_opt(2024, 8, 24)
280 );
281 assert_eq!(
282 HumanDateParserBrazillianPortuguese::parse_relative("próximo domingo", now),
283 NaiveDate::from_ymd_opt(2024, 8, 25)
284 );
285 assert_eq!(
286 HumanDateParserBrazillianPortuguese::parse_relative("próxima segunda", now),
287 NaiveDate::from_ymd_opt(2024, 8, 26)
288 );
289 }
290
291 #[test]
292 fn test_weekday() {
293 assert_eq!(weekday.parse_peek("segunda-feira"), Ok(("", Weekday::Mon)));
294 assert_eq!(weekday.parse_peek("segunda feira"), Ok(("", Weekday::Mon)));
295 assert_eq!(weekday.parse_peek("seg."), Ok(("", Weekday::Mon)));
296 assert_eq!(weekday.parse_peek("seg"), Ok(("", Weekday::Mon)));
297 assert_eq!(weekday.parse_peek("terça-feira"), Ok(("", Weekday::Tue)));
298 assert_eq!(weekday.parse_peek("terca-feira"), Ok(("", Weekday::Tue)));
299 assert_eq!(weekday.parse_peek("terça feira"), Ok(("", Weekday::Tue)));
300 assert_eq!(weekday.parse_peek("terca feira"), Ok(("", Weekday::Tue)));
301 assert_eq!(weekday.parse_peek("ter."), Ok(("", Weekday::Tue)));
302 assert_eq!(weekday.parse_peek("ter"), Ok(("", Weekday::Tue)));
303 assert_eq!(weekday.parse_peek("quarta-feira"), Ok(("", Weekday::Wed)));
304 assert_eq!(weekday.parse_peek("quarta feira"), Ok(("", Weekday::Wed)));
305 assert_eq!(weekday.parse_peek("qua."), Ok(("", Weekday::Wed)));
306 assert_eq!(weekday.parse_peek("qua"), Ok(("", Weekday::Wed)));
307 assert_eq!(weekday.parse_peek("quinta-feira"), Ok(("", Weekday::Thu)));
308 assert_eq!(weekday.parse_peek("quinta feira"), Ok(("", Weekday::Thu)));
309 assert_eq!(weekday.parse_peek("qui."), Ok(("", Weekday::Thu)));
310 assert_eq!(weekday.parse_peek("qui"), Ok(("", Weekday::Thu)));
311 assert_eq!(weekday.parse_peek("sexta-feira"), Ok(("", Weekday::Fri)));
312 assert_eq!(weekday.parse_peek("sexta feira"), Ok(("", Weekday::Fri)));
313 assert_eq!(weekday.parse_peek("sex."), Ok(("", Weekday::Fri)));
314 assert_eq!(weekday.parse_peek("sex"), Ok(("", Weekday::Fri)));
315 assert_eq!(weekday.parse_peek("sábado"), Ok(("", Weekday::Sat)));
316 assert_eq!(weekday.parse_peek("sabado"), Ok(("", Weekday::Sat)));
317 assert_eq!(weekday.parse_peek("sáb."), Ok(("", Weekday::Sat)));
318 assert_eq!(weekday.parse_peek("sab."), Ok(("", Weekday::Sat)));
319 assert_eq!(weekday.parse_peek("sáb"), Ok(("", Weekday::Sat)));
320 assert_eq!(weekday.parse_peek("domingo"), Ok(("", Weekday::Sun)));
321 assert_eq!(weekday.parse_peek("dom."), Ok(("", Weekday::Sun)));
322 assert_eq!(weekday.parse_peek("dom"), Ok(("", Weekday::Sun)));
323 }
324
325 #[test]
326 fn test_this() {
327 assert_eq!(this.parse_peek("esta"), Ok(("", ())));
328 assert_eq!(this.parse_peek("essa"), Ok(("", ())));
329 assert_eq!(this.parse_peek("esse"), Ok(("", ())));
330 assert_eq!(this.parse_peek("este"), Ok(("", ())));
331 }
332
333 #[test]
334 fn test_next() {
335 assert_eq!(next.parse_peek("próxima"), Ok(("", ())));
336 assert_eq!(next.parse_peek("proxima"), Ok(("", ())));
337 assert_eq!(next.parse_peek("próximo"), Ok(("", ())));
338 assert_eq!(next.parse_peek("proximo"), Ok(("", ())));
339 assert_eq!(next.parse_peek("próx."), Ok(("", ())));
340 assert_eq!(next.parse_peek("prox."), Ok(("", ())));
341 assert_eq!(next.parse_peek("prox"), Ok(("", ())));
342 }
343
344 #[test]
345 fn test_number() {
346 assert_eq!(number(&mut "1"), Ok(1));
347 assert_eq!(number(&mut "01"), Ok(1));
348 assert_eq!(number(&mut "um"), Ok(1));
349 assert_eq!(number(&mut "dois"), Ok(2));
350 assert_eq!(number(&mut "três"), Ok(3));
351 assert_eq!(number(&mut "quatro"), Ok(4));
352 assert_eq!(number(&mut "cinco"), Ok(5));
353 assert_eq!(number(&mut "seis"), Ok(6));
354 assert_eq!(number(&mut "sete"), Ok(7));
355 assert_eq!(number(&mut "oito"), Ok(8));
356 assert_eq!(number(&mut "nove"), Ok(9));
357 assert_eq!(number(&mut "dez"), Ok(10));
358 assert_eq!(number(&mut "onze"), Ok(11));
359 assert_eq!(number(&mut "doze"), Ok(12));
360 assert_eq!(number(&mut "treze"), Ok(13));
361 assert_eq!(number(&mut "quatorze"), Ok(14));
362 assert_eq!(number(&mut "catorze"), Ok(14)); assert_eq!(number(&mut "quinze"), Ok(15));
364 assert_eq!(number(&mut "dezesseis"), Ok(16));
365 assert_eq!(number(&mut "dezessete"), Ok(17));
366 assert_eq!(number(&mut "dezoito"), Ok(18));
367 assert_eq!(number(&mut "dezenove"), Ok(19));
368 assert_eq!(number(&mut "vinte"), Ok(20));
369 }
370}