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