opening_hours_syntax/
parser.rs

1use std::cmp::Ord;
2use std::convert::TryInto;
3use std::fmt::Debug;
4use std::hash::Hash;
5use std::ops::RangeInclusive;
6use std::sync::Arc;
7
8use chrono::Duration;
9
10use pest::iterators::Pair;
11use pest::Parser;
12
13use crate::error::{Error, Result};
14use crate::extended_time::ExtendedTime;
15use crate::rules as rl;
16use crate::rules::day::{self as ds, WeekNum, Year};
17use crate::rules::time as ts;
18
19#[cfg(feature = "log")]
20static WARN_EASTER: std::sync::Once = std::sync::Once::new();
21
22#[derive(Parser)]
23#[grammar = "grammar.pest"]
24struct OHParser;
25
26/// Just used while collecting parsed expression
27enum Sign {
28    Neg,
29    Pos,
30}
31
32pub fn parse(data: &str) -> Result<rl::OpeningHoursExpression> {
33    let opening_hours_pair = OHParser::parse(Rule::input_opening_hours, data)
34        .map_err(Error::from)?
35        .next()
36        .expect("grammar error: no opening_hours found");
37
38    let rules = build_opening_hours(opening_hours_pair)?;
39    Ok(rl::OpeningHoursExpression { rules })
40}
41
42// ---
43// --- Time domain
44// ---
45
46fn unexpected_token<T>(token: Rule, parent: Rule) -> T {
47    unreachable!("Grammar error: found `{token:?}` inside of `{parent:?}`")
48}
49
50fn build_opening_hours(pair: Pair<Rule>) -> Result<Vec<rl::RuleSequence>> {
51    assert_eq!(pair.as_rule(), Rule::opening_hours);
52    let mut pairs = pair.into_inner();
53    let mut rules = Vec::new();
54
55    while let Some(pair) = pairs.next() {
56        rules.push(match pair.as_rule() {
57            Rule::rule_sequence => build_rule_sequence(pair, rl::RuleOperator::Normal),
58            Rule::any_rule_separator => build_rule_sequence(
59                pairs.next().expect("separator not followed by any rule"),
60                build_any_rule_separator(pair),
61            ),
62            other => unexpected_token(other, Rule::opening_hours),
63        }?)
64    }
65
66    Ok(rules)
67}
68
69fn build_rule_sequence(pair: Pair<Rule>, operator: rl::RuleOperator) -> Result<rl::RuleSequence> {
70    assert_eq!(pair.as_rule(), Rule::rule_sequence);
71    let mut pairs = pair.into_inner();
72
73    let (day_selector, time_selector, extra_comment) =
74        build_selector_sequence(pairs.next().expect("grammar error: empty rule sequence"))?;
75
76    let (kind, comment) = pairs
77        .next()
78        .map(build_rules_modifier)
79        .unwrap_or((rl::RuleKind::Open, None));
80
81    let comments = comment
82        .into_iter()
83        .chain(extra_comment)
84        .map(|s| Arc::from(s.into_boxed_str()))
85        .collect::<Vec<_>>()
86        .into();
87
88    Ok(rl::RuleSequence {
89        day_selector,
90        time_selector,
91        kind,
92        operator,
93        comments,
94    })
95}
96
97fn build_any_rule_separator(pair: Pair<Rule>) -> rl::RuleOperator {
98    assert_eq!(pair.as_rule(), Rule::any_rule_separator);
99
100    match pair
101        .into_inner()
102        .next()
103        .expect("empty rule separator")
104        .as_rule()
105    {
106        Rule::normal_rule_separator => rl::RuleOperator::Normal,
107        Rule::additional_rule_separator => rl::RuleOperator::Additional,
108        Rule::fallback_rule_separator => rl::RuleOperator::Fallback,
109        other => unexpected_token(other, Rule::any_rule_separator),
110    }
111}
112
113// ---
114// --- Rule modifier
115// ---
116
117fn build_rules_modifier(pair: Pair<Rule>) -> (rl::RuleKind, Option<String>) {
118    assert_eq!(pair.as_rule(), Rule::rules_modifier);
119    let mut pairs = pair.into_inner();
120
121    let kind = {
122        if pairs.peek().expect("empty rules_modifier").as_rule() == Rule::rules_modifier_enum {
123            build_rules_modifier_enum(pairs.next().unwrap())
124        } else {
125            rl::RuleKind::Open
126        }
127    };
128
129    let comment = pairs.next().map(build_comment);
130    (kind, comment)
131}
132
133fn build_rules_modifier_enum(pair: Pair<Rule>) -> rl::RuleKind {
134    assert_eq!(pair.as_rule(), Rule::rules_modifier_enum);
135
136    let pair = pair
137        .into_inner()
138        .next()
139        .expect("grammar error: empty rules modifier enum");
140
141    match pair.as_rule() {
142        Rule::rules_modifier_enum_closed => rl::RuleKind::Closed,
143        Rule::rules_modifier_enum_open => rl::RuleKind::Open,
144        Rule::rules_modifier_enum_unknown => rl::RuleKind::Unknown,
145        other => unexpected_token(other, Rule::rules_modifier_enum),
146    }
147}
148
149// ---
150// --- Selectors
151// ---
152
153fn build_selector_sequence(
154    pair: Pair<Rule>,
155) -> Result<(ds::DaySelector, ts::TimeSelector, Option<String>)> {
156    assert_eq!(pair.as_rule(), Rule::selector_sequence);
157    let mut pairs = pair.into_inner();
158
159    if pairs.peek().map(|x| x.as_rule()).expect("empty selector") == Rule::always_open {
160        return Ok(Default::default());
161    }
162
163    let (year, monthday, week, comment) = {
164        if pairs.peek().map(|x| x.as_rule()).unwrap() == Rule::wide_range_selectors {
165            build_wide_range_selectors(pairs.next().unwrap())?
166        } else {
167            (Vec::new(), Vec::new(), Vec::new(), None)
168        }
169    };
170
171    let (weekday, time) = {
172        if let Some(pair) = pairs.next() {
173            build_small_range_selectors(pair)?
174        } else {
175            (Vec::new(), Vec::new())
176        }
177    };
178
179    Ok((
180        ds::DaySelector { year, monthday, week, weekday },
181        ts::TimeSelector::new(time),
182        comment,
183    ))
184}
185
186#[allow(clippy::type_complexity)]
187fn build_wide_range_selectors(
188    pair: Pair<Rule>,
189) -> Result<(
190    Vec<ds::YearRange>,
191    Vec<ds::MonthdayRange>,
192    Vec<ds::WeekRange>,
193    Option<String>,
194)> {
195    assert_eq!(pair.as_rule(), Rule::wide_range_selectors);
196
197    let mut year_selector = Vec::new();
198    let mut monthday_selector = Vec::new();
199    let mut week_selector = Vec::new();
200    let mut comment = None;
201
202    for pair in pair.into_inner() {
203        match pair.as_rule() {
204            Rule::year_selector => year_selector = build_year_selector(pair)?,
205            Rule::monthday_selector => monthday_selector = build_monthday_selector(pair)?,
206            Rule::week_selector => week_selector = build_week_selector(pair)?,
207            Rule::comment => comment = Some(build_comment(pair)),
208            other => unexpected_token(other, Rule::wide_range_selectors),
209        }
210    }
211
212    Ok((year_selector, monthday_selector, week_selector, comment))
213}
214
215fn build_small_range_selectors(
216    pair: Pair<Rule>,
217) -> Result<(Vec<ds::WeekDayRange>, Vec<ts::TimeSpan>)> {
218    assert_eq!(pair.as_rule(), Rule::small_range_selectors);
219
220    let mut weekday_selector = Vec::new();
221    let mut time_selector = Vec::new();
222
223    for pair in pair.into_inner() {
224        match pair.as_rule() {
225            Rule::weekday_selector => weekday_selector = build_weekday_selector(pair)?,
226            Rule::time_selector => time_selector = build_time_selector(pair)?,
227            other => unexpected_token(other, Rule::wide_range_selectors),
228        }
229    }
230
231    Ok((weekday_selector, time_selector))
232}
233
234// ---
235// --- Time selector
236// ---
237
238fn build_time_selector(pair: Pair<Rule>) -> Result<Vec<ts::TimeSpan>> {
239    assert_eq!(pair.as_rule(), Rule::time_selector);
240    pair.into_inner().map(build_timespan).collect()
241}
242
243fn build_timespan(pair: Pair<Rule>) -> Result<ts::TimeSpan> {
244    assert_eq!(pair.as_rule(), Rule::timespan);
245    let mut pairs = pair.into_inner();
246    let start = build_time(pairs.next().expect("empty timespan"))?;
247
248    let (open_end, end) = match pairs.next() {
249        None => {
250            return Err(Error::Unsupported("point in time"));
251        }
252        Some(pair) if pair.as_rule() == Rule::timespan_plus => {
253            // TODO: opening_hours.js handles this better: it will set the
254            //       state to unknown and add a warning comment.
255            (true, ts::Time::Fixed(ExtendedTime::MIDNIGHT_24))
256        }
257        Some(pair) => (false, build_extended_time(pair)?),
258    };
259
260    let (open_end, repeats) = match pairs.next().map(|x| x.as_rule()) {
261        None => (open_end, None),
262        Some(Rule::timespan_plus) => (true, None),
263        Some(Rule::minute) => (open_end, Some(build_minute(pairs.next().unwrap()))),
264        Some(Rule::hour_minutes) => (
265            open_end,
266            Some(build_hour_minutes_as_duration(pairs.next().unwrap())),
267        ),
268        Some(other) => unexpected_token(other, Rule::timespan),
269    };
270
271    assert!(pairs.next().is_none());
272    Ok(ts::TimeSpan { range: start..end, repeats, open_end })
273}
274
275fn build_time(pair: Pair<Rule>) -> Result<ts::Time> {
276    assert_eq!(pair.as_rule(), Rule::time);
277    let inner = pair.into_inner().next().expect("empty time");
278
279    Ok(match inner.as_rule() {
280        Rule::hour_minutes => ts::Time::Fixed(build_hour_minutes(inner)?),
281        Rule::variable_time => ts::Time::Variable(build_variable_time(inner)?),
282        other => unexpected_token(other, Rule::time),
283    })
284}
285
286fn build_extended_time(pair: Pair<Rule>) -> Result<ts::Time> {
287    assert_eq!(pair.as_rule(), Rule::extended_time);
288    let inner = pair.into_inner().next().expect("empty extended time");
289
290    Ok(match inner.as_rule() {
291        Rule::extended_hour_minutes => ts::Time::Fixed(build_extended_hour_minutes(inner)?),
292        Rule::variable_time => ts::Time::Variable(build_variable_time(inner)?),
293        other => unexpected_token(other, Rule::extended_time),
294    })
295}
296
297fn build_variable_time(pair: Pair<Rule>) -> Result<ts::VariableTime> {
298    assert_eq!(pair.as_rule(), Rule::variable_time);
299    let mut pairs = pair.into_inner();
300
301    let event = build_event(pairs.next().expect("empty variable time"));
302
303    let offset = {
304        if pairs.peek().is_some() {
305            let sign = build_plus_or_minus(pairs.next().unwrap());
306
307            let mins: i16 = build_hour_minutes(pairs.next().expect("missing hour minutes"))?
308                .mins_from_midnight()
309                .try_into()
310                .expect("offset overflow");
311
312            match sign {
313                PlusOrMinus::Plus => mins,
314                PlusOrMinus::Minus => -mins,
315            }
316        } else {
317            0
318        }
319    };
320
321    Ok(ts::VariableTime { event, offset })
322}
323
324fn build_event(pair: Pair<Rule>) -> ts::TimeEvent {
325    assert_eq!(pair.as_rule(), Rule::event);
326
327    match pair.into_inner().next().expect("empty event").as_rule() {
328        Rule::dawn => ts::TimeEvent::Dawn,
329        Rule::sunrise => ts::TimeEvent::Sunrise,
330        Rule::sunset => ts::TimeEvent::Sunset,
331        Rule::dusk => ts::TimeEvent::Dusk,
332        other => unexpected_token(other, Rule::event),
333    }
334}
335
336// ---
337// --- WeekDay selector
338// ---
339
340fn build_weekday_selector(pair: Pair<Rule>) -> Result<Vec<ds::WeekDayRange>> {
341    assert_eq!(pair.as_rule(), Rule::weekday_selector);
342
343    pair.into_inner()
344        .flat_map(|pair| match pair.as_rule() {
345            Rule::weekday_sequence => pair.into_inner().map(build_weekday_range as fn(_) -> _),
346            Rule::holiday_sequence => pair.into_inner().map(build_holiday as _),
347            other => unexpected_token(other, Rule::weekday_sequence),
348        })
349        .collect()
350}
351
352fn build_weekday_range(pair: Pair<Rule>) -> Result<ds::WeekDayRange> {
353    assert_eq!(pair.as_rule(), Rule::weekday_range);
354    let mut pairs = pair.into_inner();
355
356    let start = build_wday(pairs.next().expect("empty weekday range"));
357
358    let end = {
359        if pairs.peek().map(|x| x.as_rule()) == Some(Rule::wday) {
360            build_wday(pairs.next().unwrap())
361        } else {
362            start
363        }
364    };
365
366    let mut nth_from_start = [false; 5];
367    let mut nth_from_end = [false; 5];
368
369    while pairs.peek().map(|x| x.as_rule()) == Some(Rule::nth_entry) {
370        let (sign, indices) = build_nth_entry(pairs.next().unwrap())?;
371
372        let nth_array = match sign {
373            Sign::Neg => &mut nth_from_end,
374            Sign::Pos => &mut nth_from_start,
375        };
376
377        for i in indices {
378            nth_array[usize::from(i - 1)] = true;
379        }
380    }
381
382    if !nth_from_start.contains(&true) && !nth_from_end.contains(&true) {
383        nth_from_start = [true; 5];
384        nth_from_end = [true; 5];
385    }
386
387    let offset = {
388        if let Some(pair) = pairs.next() {
389            build_day_offset(pair)?
390        } else {
391            0
392        }
393    };
394
395    Ok(ds::WeekDayRange::Fixed {
396        range: start..=end,
397        offset,
398        nth_from_start,
399        nth_from_end,
400    })
401}
402
403fn build_holiday(pair: Pair<Rule>) -> Result<ds::WeekDayRange> {
404    assert_eq!(pair.as_rule(), Rule::holiday);
405    let mut pairs = pair.into_inner();
406
407    let kind = match pairs.next().expect("empty holiday").as_rule() {
408        Rule::public_holiday => ds::HolidayKind::Public,
409        Rule::school_holiday => ds::HolidayKind::School,
410        other => unexpected_token(other, Rule::holiday),
411    };
412
413    let offset = pairs.next().map(build_day_offset).unwrap_or(Ok(0))?;
414    Ok(ds::WeekDayRange::Holiday { kind, offset })
415}
416
417fn build_nth_entry(pair: Pair<Rule>) -> Result<(Sign, RangeInclusive<u8>)> {
418    assert_eq!(pair.as_rule(), Rule::nth_entry);
419    let mut pairs = pair.into_inner();
420
421    let sign = {
422        if pairs.peek().map(|x| x.as_rule()) == Some(Rule::nth_minus) {
423            pairs.next();
424            Sign::Neg
425        } else {
426            Sign::Pos
427        }
428    };
429
430    let start = build_nth(pairs.next().expect("empty nth entry"));
431    let end = pairs.next().map(build_nth).unwrap_or(start);
432    Ok((sign, start..=end))
433}
434
435fn build_nth(pair: Pair<Rule>) -> u8 {
436    assert_eq!(pair.as_rule(), Rule::nth);
437    pair.as_str().parse().expect("invalid nth format")
438}
439
440fn build_day_offset(pair: Pair<Rule>) -> Result<i64> {
441    assert_eq!(pair.as_rule(), Rule::day_offset);
442    let mut pairs = pair.into_inner();
443
444    let sign = build_plus_or_minus(pairs.next().expect("empty day offset"));
445    let val_abs = build_positive_number(pairs.next().expect("missing value"))?;
446
447    let val_abs: i64 = val_abs.try_into().map_err(|_| Error::Overflow {
448        value: format!("{}", val_abs),
449        expected: "an integer in [-2**63, 2**63[".to_string(),
450    })?;
451
452    Ok(match sign {
453        PlusOrMinus::Plus => val_abs,
454        PlusOrMinus::Minus => -val_abs,
455    })
456}
457
458// ---
459// --- Week selector
460// ---
461
462fn build_week_selector(pair: Pair<Rule>) -> Result<Vec<ds::WeekRange>> {
463    assert_eq!(pair.as_rule(), Rule::week_selector);
464    pair.into_inner().map(build_week).collect()
465}
466
467fn build_week(pair: Pair<Rule>) -> Result<ds::WeekRange> {
468    assert_eq!(pair.as_rule(), Rule::week);
469    let mut rules = pair.into_inner();
470
471    let start = build_weeknum(rules.next().expect("empty weeknum range"));
472    let end = rules.next().map(build_weeknum);
473
474    let step = rules.next().map(build_positive_number).transpose()?;
475    let step = step.unwrap_or(1).try_into().map_err(|_| Error::Overflow {
476        value: format!("{}", step.unwrap()),
477        expected: "an integer in [0, 255]".to_string(),
478    })?;
479
480    Ok(ds::WeekRange {
481        range: WeekNum(start)..=WeekNum(end.unwrap_or(start)),
482        step,
483    })
484}
485
486// ---
487// --- Month selector
488// ---
489
490fn build_monthday_selector(pair: Pair<Rule>) -> Result<Vec<ds::MonthdayRange>> {
491    assert_eq!(pair.as_rule(), Rule::monthday_selector);
492    pair.into_inner().map(build_monthday_range).collect()
493}
494
495fn build_monthday_range(pair: Pair<Rule>) -> Result<ds::MonthdayRange> {
496    assert_eq!(pair.as_rule(), Rule::monthday_range);
497    let mut pairs = pair.into_inner();
498
499    let year = {
500        if pairs.peek().map(|x| x.as_rule()) == Some(Rule::year) {
501            Some(build_year(pairs.next().unwrap()))
502        } else {
503            None
504        }
505    };
506
507    match pairs.peek().expect("empty monthday range").as_rule() {
508        Rule::month => {
509            let start = build_month(pairs.next().unwrap());
510            let end = pairs.next().map(build_month).unwrap_or(start);
511
512            Ok(ds::MonthdayRange::Month { year, range: start..=end })
513        }
514        Rule::date_from => {
515            let start = build_date_from(pairs.next().unwrap());
516
517            let start_offset = {
518                if pairs.peek().map(|x| x.as_rule()) == Some(Rule::date_offset) {
519                    build_date_offset(pairs.next().unwrap())?
520                } else {
521                    ds::DateOffset::default()
522                }
523            };
524
525            let end = match pairs.peek().map(|x| x.as_rule()) {
526                Some(Rule::date_to) => build_date_to(pairs.next().unwrap(), start)?,
527                Some(Rule::monthday_range_plus) => {
528                    pairs.next();
529
530                    if start.has_year() {
531                        ds::Date::ymd(31, ds::Month::December, 9999)
532                    } else {
533                        ds::Date::md(31, ds::Month::December)
534                    }
535                }
536                None => {
537                    return Ok(ds::MonthdayRange::Date {
538                        start: (start, start_offset),
539                        end: (start, start_offset),
540                    });
541                }
542                Some(other) => unexpected_token(other, Rule::monthday_range),
543            };
544
545            let end_offset = pairs
546                .next()
547                .map(build_date_offset)
548                .unwrap_or_else(|| Ok(Default::default()))?;
549
550            Ok(ds::MonthdayRange::Date {
551                start: (start, start_offset),
552                end: (end, end_offset),
553            })
554        }
555        other => unexpected_token(other, Rule::monthday_range),
556    }
557}
558
559fn build_date_offset(pair: Pair<Rule>) -> Result<ds::DateOffset> {
560    assert_eq!(pair.as_rule(), Rule::date_offset);
561    let mut pairs = pair.into_inner();
562
563    let wday_offset = {
564        if pairs.peek().map(|x| x.as_rule()) == Some(Rule::plus_or_minus) {
565            let sign = build_plus_or_minus(pairs.next().unwrap());
566            let wday = build_wday(pairs.next().expect("missing wday after sign"));
567
568            match sign {
569                PlusOrMinus::Plus => ds::WeekDayOffset::Next(wday),
570                PlusOrMinus::Minus => ds::WeekDayOffset::Prev(wday),
571            }
572        } else {
573            ds::WeekDayOffset::None
574        }
575    };
576
577    let day_offset = pairs.next().map(build_day_offset).unwrap_or(Ok(0))?;
578
579    Ok(ds::DateOffset { wday_offset, day_offset })
580}
581
582fn build_date_from(pair: Pair<Rule>) -> ds::Date {
583    assert_eq!(pair.as_rule(), Rule::date_from);
584    let mut pairs = pair.into_inner();
585
586    let year = {
587        if pairs.peek().map(|x| x.as_rule()) == Some(Rule::year) {
588            Some(build_year(pairs.next().unwrap()))
589        } else {
590            None
591        }
592    };
593
594    match pairs.peek().expect("empty date (from)").as_rule() {
595        Rule::variable_date => {
596            #[cfg(feature = "log")]
597            WARN_EASTER.call_once(|| log::warn!("Easter is not supported yet"));
598            ds::Date::Easter { year }
599        }
600        Rule::month => ds::Date::Fixed {
601            year,
602            month: build_month(pairs.next().expect("missing month")),
603            day: build_daynum(pairs.next().expect("missing day")),
604        },
605        other => unexpected_token(other, Rule::date_from),
606    }
607}
608
609fn build_date_to(pair: Pair<Rule>, from: ds::Date) -> Result<ds::Date> {
610    assert_eq!(pair.as_rule(), Rule::date_to);
611    let pair = pair.into_inner().next().expect("empty date (to)");
612
613    Ok(match pair.as_rule() {
614        Rule::date_from => build_date_from(pair),
615        Rule::daynum => {
616            let daynum = build_daynum(pair);
617
618            match from {
619                ds::Date::Easter { .. } => {
620                    // NOTE: this is actually not a specified constraint, but it is quite confusing
621                    //       that this is allowed
622                    return Err(Error::Unsupported("Easter followed by a day number"));
623                }
624                ds::Date::Fixed { mut year, mut month, day } => {
625                    if day > daynum {
626                        month = month.next();
627
628                        if month == ds::Month::January {
629                            if let Some(x) = year.as_mut() {
630                                *x += 1
631                            }
632                        }
633                    }
634
635                    ds::Date::Fixed { year, month, day: daynum }
636                }
637            }
638        }
639        other => unexpected_token(other, Rule::date_to),
640    })
641}
642
643// ---
644// --- Year selector
645// ---
646
647fn build_year_selector(pair: Pair<Rule>) -> Result<Vec<ds::YearRange>> {
648    assert_eq!(pair.as_rule(), Rule::year_selector);
649    pair.into_inner().map(build_year_range).collect()
650}
651
652fn build_year_range(pair: Pair<Rule>) -> Result<ds::YearRange> {
653    assert_eq!(pair.as_rule(), Rule::year_range);
654    let mut rules = pair.into_inner();
655
656    let start = build_year(rules.next().expect("empty year range"));
657    let end = rules.next().map(|pair| match pair.as_rule() {
658        Rule::year => build_year(pair),
659        Rule::year_range_plus => 9999,
660        other => unexpected_token(other, Rule::year_range),
661    });
662
663    let step = rules.next().map(build_positive_number).transpose()?;
664    let step = step.unwrap_or(1).try_into().map_err(|_| Error::Overflow {
665        value: format!("{}", step.unwrap()),
666        expected: "an integer in [0, 2**16[".to_string(),
667    })?;
668
669    Ok(ds::YearRange {
670        range: Year(start)..=Year(end.unwrap_or(start)),
671        step,
672    })
673}
674
675// ---
676// --- Basic elements
677// ---
678
679fn build_plus_or_minus(pair: Pair<Rule>) -> PlusOrMinus {
680    assert_eq!(pair.as_rule(), Rule::plus_or_minus);
681    let pair = pair.into_inner().next().expect("empty plus or minus");
682
683    match pair.as_rule() {
684        Rule::plus => PlusOrMinus::Plus,
685        Rule::minus => PlusOrMinus::Minus,
686        other => unexpected_token(other, Rule::plus_or_minus),
687    }
688}
689
690fn build_minute(pair: Pair<Rule>) -> Duration {
691    assert_eq!(pair.as_rule(), Rule::minute);
692    let minutes = pair.as_str().parse().expect("invalid minute");
693    Duration::minutes(minutes)
694}
695
696fn build_hour_minutes(pair: Pair<Rule>) -> Result<ExtendedTime> {
697    assert_eq!(pair.as_rule(), Rule::hour_minutes);
698    let mut pairs = pair.into_inner();
699
700    let Some(hour_rule) = pairs.next() else {
701        return Ok(ExtendedTime::MIDNIGHT_24);
702    };
703
704    let hour = hour_rule.as_str().parse().expect("invalid hour");
705
706    let minutes = pairs
707        .next()
708        .expect("missing minutes")
709        .as_str()
710        .parse()
711        .expect("invalid minutes");
712
713    ExtendedTime::new(hour, minutes).ok_or(Error::InvalidExtendTime { hour, minutes })
714}
715
716fn build_extended_hour_minutes(pair: Pair<Rule>) -> Result<ExtendedTime> {
717    assert_eq!(pair.as_rule(), Rule::extended_hour_minutes);
718    let mut pairs = pair.into_inner();
719
720    let hour = pairs
721        .next()
722        .expect("missing hour")
723        .as_str()
724        .parse()
725        .expect("invalid hour");
726
727    let minutes = pairs
728        .next()
729        .expect("missing minutes")
730        .as_str()
731        .parse()
732        .expect("invalid minutes");
733
734    ExtendedTime::new(hour, minutes).ok_or(Error::InvalidExtendTime { hour, minutes })
735}
736
737fn build_hour_minutes_as_duration(pair: Pair<Rule>) -> Duration {
738    assert_eq!(pair.as_rule(), Rule::hour_minutes);
739    let mut pairs = pair.into_inner();
740
741    let hour = pairs
742        .next()
743        .expect("missing hour")
744        .as_str()
745        .parse()
746        .expect("invalid hour");
747
748    let minutes = pairs
749        .next()
750        .expect("missing minutes")
751        .as_str()
752        .parse()
753        .expect("invalid minutes");
754
755    Duration::hours(hour) + Duration::minutes(minutes)
756}
757
758fn build_wday(pair: Pair<Rule>) -> ds::Weekday {
759    assert_eq!(pair.as_rule(), Rule::wday);
760    let pair = pair.into_inner().next().expect("empty week day");
761
762    match pair.as_rule() {
763        Rule::sunday => ds::Weekday::Sun,
764        Rule::monday => ds::Weekday::Mon,
765        Rule::tuesday => ds::Weekday::Tue,
766        Rule::wednesday => ds::Weekday::Wed,
767        Rule::thursday => ds::Weekday::Thu,
768        Rule::friday => ds::Weekday::Fri,
769        Rule::saturday => ds::Weekday::Sat,
770        other => unexpected_token(other, Rule::wday),
771    }
772}
773
774fn build_daynum(pair: Pair<Rule>) -> u8 {
775    assert_eq!(pair.as_rule(), Rule::daynum);
776    let daynum = pair.as_str().parse().expect("invalid month format");
777
778    if daynum == 0 {
779        #[cfg(feature = "log")]
780        log::warn!("Found day number 0 in opening hours: specify the 1st or 31st instead.");
781        return 1;
782    }
783
784    if daynum > 31 {
785        #[cfg(feature = "log")]
786        log::warn!("Found day number {daynum} in opening hours");
787        return 31;
788    }
789
790    daynum
791}
792
793fn build_weeknum(pair: Pair<Rule>) -> u8 {
794    assert_eq!(pair.as_rule(), Rule::weeknum);
795    pair.as_str().parse().expect("invalid weeknum format")
796}
797
798fn build_month(pair: Pair<Rule>) -> ds::Month {
799    assert_eq!(pair.as_rule(), Rule::month);
800    let pair = pair.into_inner().next().expect("empty month");
801
802    match pair.as_rule() {
803        Rule::january => ds::Month::January,
804        Rule::february => ds::Month::February,
805        Rule::march => ds::Month::March,
806        Rule::april => ds::Month::April,
807        Rule::may => ds::Month::May,
808        Rule::june => ds::Month::June,
809        Rule::july => ds::Month::July,
810        Rule::august => ds::Month::August,
811        Rule::september => ds::Month::September,
812        Rule::october => ds::Month::October,
813        Rule::november => ds::Month::November,
814        Rule::december => ds::Month::December,
815        other => unexpected_token(other, Rule::month),
816    }
817}
818
819fn build_year(pair: Pair<Rule>) -> u16 {
820    assert_eq!(pair.as_rule(), Rule::year);
821    pair.as_str().parse().expect("invalid year format")
822}
823
824fn build_positive_number(pair: Pair<Rule>) -> Result<u64> {
825    assert_eq!(pair.as_rule(), Rule::positive_number);
826    pair.as_str().parse().map_err(|_| Error::Overflow {
827        value: pair.as_str().to_string(),
828        expected: "a number between 0 and 2**64".to_string(),
829    })
830}
831
832fn build_comment(pair: Pair<Rule>) -> String {
833    assert_eq!(pair.as_rule(), Rule::comment);
834    build_comment_inner(pair.into_inner().next().expect("empty comment"))
835}
836
837fn build_comment_inner(pair: Pair<Rule>) -> String {
838    assert_eq!(pair.as_rule(), Rule::comment_inner);
839    pair.as_str().to_string()
840}
841
842// Mics
843
844enum PlusOrMinus {
845    Plus,
846    Minus,
847}