temps_core/language/
german.rs

1use winnow::{
2    Parser,
3    ascii::Caseless,
4    combinator::{alt, delimited, opt, preceded},
5    token::take_while,
6};
7
8use crate::{
9    DayReference, DayTime, Direction, LanguageParser, RelativeTime, Result, StandardDate, Time,
10    TimeExpression, TimeUnit, Weekday, WeekdayModifier, common, error::ParseErrorExt,
11};
12
13pub struct GermanParser;
14
15impl GermanParser {
16    fn parse_number(input: &mut &str) -> winnow::Result<i64> {
17        alt((
18            common::parse_digit_number,
19            "einem".value(1),
20            "einer".value(1),
21            "einen".value(1),
22            "eine".value(1),
23            "ein".value(1),
24            "zwei".value(2),
25            "drei".value(3),
26            "vier".value(4),
27            "fünf".value(5),
28            "sechs".value(6),
29            "sieben".value(7),
30            "acht".value(8),
31            "neun".value(9),
32            "zehn".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                "Sekunden".value(TimeUnit::Second),
41                "Sekunde".value(TimeUnit::Second),
42                Caseless("sek").value(TimeUnit::Second), // Abbreviations can be case-insensitive
43            )),
44            alt((
45                "Minuten".value(TimeUnit::Minute),
46                "Minute".value(TimeUnit::Minute),
47                Caseless("min").value(TimeUnit::Minute), // Abbreviations can be case-insensitive
48            )),
49            alt((
50                "Stunden".value(TimeUnit::Hour),
51                "Stunde".value(TimeUnit::Hour),
52                Caseless("std").value(TimeUnit::Hour), // Abbreviations can be case-insensitive
53            )),
54            alt((
55                "Tagen".value(TimeUnit::Day),
56                "Tage".value(TimeUnit::Day),
57                "Tag".value(TimeUnit::Day),
58            )),
59            alt((
60                "Wochen".value(TimeUnit::Week),
61                "Woche".value(TimeUnit::Week),
62            )),
63            alt((
64                "Monaten".value(TimeUnit::Month),
65                "Monate".value(TimeUnit::Month),
66                "Monat".value(TimeUnit::Month),
67            )),
68            alt((
69                "Jahren".value(TimeUnit::Year),
70                "Jahre".value(TimeUnit::Year),
71                "Jahr".value(TimeUnit::Year),
72            )),
73        ))
74        .parse_next(input)
75    }
76
77    fn parse_relative_past(input: &mut &str) -> winnow::Result<TimeExpression> {
78        preceded(
79            "vor",
80            preceded(
81                take_while(1.., ' '),
82                (
83                    Self::parse_number,
84                    take_while(1.., ' '),
85                    Self::parse_time_unit,
86                ),
87            ),
88        )
89        .map(|(amount, _, unit)| {
90            TimeExpression::Relative(RelativeTime {
91                amount,
92                unit,
93                direction: Direction::Past,
94            })
95        })
96        .parse_next(input)
97    }
98
99    fn parse_relative_future(input: &mut &str) -> winnow::Result<TimeExpression> {
100        preceded(
101            "in",
102            preceded(
103                take_while(1.., ' '),
104                (
105                    Self::parse_number,
106                    take_while(1.., ' '),
107                    Self::parse_time_unit,
108                ),
109            ),
110        )
111        .map(|(amount, _, unit)| {
112            TimeExpression::Relative(RelativeTime {
113                amount,
114                unit,
115                direction: Direction::Future,
116            })
117        })
118        .parse_next(input)
119    }
120
121    fn parse_now(input: &mut &str) -> winnow::Result<TimeExpression> {
122        Caseless("jetzt")
123            .value(TimeExpression::Now)
124            .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                "Montag".value(Weekday::Monday),
135                Caseless("mo").value(Weekday::Monday), // Abbreviations can be case-insensitive
136            )),
137            alt((
138                "Dienstag".value(Weekday::Tuesday),
139                Caseless("di").value(Weekday::Tuesday), // Abbreviations can be case-insensitive
140            )),
141            alt((
142                "Mittwoch".value(Weekday::Wednesday),
143                Caseless("mi").value(Weekday::Wednesday), // Abbreviations can be case-insensitive
144            )),
145            alt((
146                "Donnerstag".value(Weekday::Thursday),
147                Caseless("do").value(Weekday::Thursday), // Abbreviations can be case-insensitive
148            )),
149            alt((
150                "Freitag".value(Weekday::Friday),
151                Caseless("fr").value(Weekday::Friday), // Abbreviations can be case-insensitive
152            )),
153            alt((
154                "Samstag".value(Weekday::Saturday),
155                Caseless("sa").value(Weekday::Saturday), // Abbreviations can be case-insensitive
156            )),
157            alt((
158                "Sonntag".value(Weekday::Sunday),
159                Caseless("so").value(Weekday::Sunday), // Abbreviations can be case-insensitive
160            )),
161        ))
162        .parse_next(input)
163    }
164
165    fn parse_day_shortcuts(input: &mut &str) -> winnow::Result<DayReference> {
166        alt((
167            Caseless("heute").value(DayReference::Today),
168            Caseless("gestern").value(DayReference::Yesterday),
169            Caseless("morgen").value(DayReference::Tomorrow),
170        ))
171        .parse_next(input)
172    }
173
174    fn parse_weekday_modifier(input: &mut &str) -> winnow::Result<WeekdayModifier> {
175        alt((
176            alt((
177                "letzten".value(WeekdayModifier::Last),
178                "letzte".value(WeekdayModifier::Last),
179            )),
180            alt((
181                "nächsten".value(WeekdayModifier::Next),
182                "nächste".value(WeekdayModifier::Next),
183            )),
184        ))
185        .parse_next(input)
186    }
187
188    fn parse_modified_weekday(input: &mut &str) -> winnow::Result<DayReference> {
189        (
190            Self::parse_weekday_modifier,
191            take_while(1.., ' '),
192            Self::parse_weekday,
193        )
194            .map(|(modifier, _, day)| DayReference::Weekday {
195                day,
196                modifier: Some(modifier),
197            })
198            .parse_next(input)
199    }
200
201    fn parse_simple_weekday(input: &mut &str) -> winnow::Result<DayReference> {
202        Self::parse_weekday
203            .map(|day| DayReference::Weekday {
204                day,
205                modifier: None,
206            })
207            .parse_next(input)
208    }
209
210    fn parse_day_reference(input: &mut &str) -> winnow::Result<TimeExpression> {
211        alt((
212            Self::parse_day_shortcuts,
213            Self::parse_modified_weekday,
214            Self::parse_simple_weekday,
215        ))
216        .map(TimeExpression::Day)
217        .parse_next(input)
218    }
219
220    fn parse_time_digits(input: &mut &str) -> winnow::Result<(u8, u8, u8)> {
221        let hour = common::parse_two_digit_number(input)?;
222        ':'.parse_next(input)?;
223        let minute = common::parse_two_digit_number(input)?;
224        let second = opt(preceded(':', common::parse_two_digit_number))
225            .parse_next(input)?
226            .unwrap_or(0);
227
228        Ok((hour, minute, second))
229    }
230
231    fn parse_time(input: &mut &str) -> winnow::Result<TimeExpression> {
232        (
233            Self::parse_time_digits,
234            opt(preceded(take_while(1.., ' '), Caseless("uhr"))),
235        )
236            .map(|((hour, minute, second), _)| {
237                TimeExpression::Time(Time {
238                    hour,
239                    minute,
240                    second,
241                    meridiem: None, // German typically uses 24-hour format
242                })
243            })
244            .parse_next(input)
245    }
246
247    fn parse_day_at_time(input: &mut &str) -> winnow::Result<TimeExpression> {
248        (
249            alt((
250                Self::parse_day_shortcuts,
251                Self::parse_modified_weekday,
252                Self::parse_simple_weekday,
253            )),
254            take_while(1.., ' '),
255            "um",
256            take_while(1.., ' '),
257            Self::parse_time_digits,
258            opt(preceded(take_while(1.., ' '), Caseless("uhr"))),
259        )
260            .map(|(day, _, _, _, time_digits, _)| {
261                TimeExpression::DayTime(DayTime {
262                    day,
263                    time: Time {
264                        hour: time_digits.0,
265                        minute: time_digits.1,
266                        second: time_digits.2,
267                        meridiem: None,
268                    },
269                })
270            })
271            .parse_next(input)
272    }
273
274    fn parse_date_format(input: &mut &str) -> winnow::Result<TimeExpression> {
275        // DD.MM.YYYY (German format)
276        (
277            common::parse_two_digit_number,
278            '.',
279            common::parse_two_digit_number,
280            '.',
281            common::parse_four_digit_number,
282        )
283            .map(|(day, _, month, _, year)| TimeExpression::Date(StandardDate { day, month, year }))
284            .parse_next(input)
285    }
286}
287
288impl LanguageParser for GermanParser {
289    fn parse(&self, input: &str) -> Result<TimeExpression> {
290        delimited(
291            take_while(0.., ' '),
292            alt((
293                Self::parse_iso_datetime,
294                Self::parse_date_format,
295                Self::parse_day_at_time,
296                Self::parse_now,
297                Self::parse_day_reference,
298                Self::parse_time,
299                Self::parse_relative_past,
300                Self::parse_relative_future,
301            )),
302            take_while(0.., ' '),
303        )
304        .parse(input)
305        .map_err(|e| e.to_temps_error(input))
306    }
307}