human_chrono_parser/locales/
pt_br.rs

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(); // Tue
184
185        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(); // Tue
204
205        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(); // Tue
226
227        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(); // Tue
260
261        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)); // before "Acordo Ortográfico da Língua Portuguesa de 1990)"
363        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}