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    let mut repeats = None;
248
249    let (mut open_end, end) = match pairs.next() {
250        None => {
251            return Err(Error::Unsupported("point in time"));
252        }
253        Some(pair) if pair.as_rule() == Rule::timespan_plus => {
254            // TODO: opening_hours.js handles this better: it will set the
255            //       state to unknown and add a warning comment.
256            (true, ts::Time::Fixed(ExtendedTime::MIDNIGHT_24))
257        }
258        Some(pair) => (false, build_extended_time(pair)?),
259    };
260
261    if let Some(pair_repetition) = pairs.next() {
262        match pair_repetition.as_rule() {
263            Rule::timespan_plus => open_end = true,
264            Rule::minute => repeats = Some(build_minute(pair_repetition)),
265            Rule::hour_minutes => repeats = Some(build_hour_minutes_as_duration(pair_repetition)),
266            other => return Err(unexpected_token(other, Rule::timespan)),
267        }
268    }
269
270    assert!(pairs.next().is_none());
271    Ok(ts::TimeSpan { range: start..end, repeats, open_end })
272}
273
274fn build_time(pair: Pair<Rule>) -> Result<ts::Time> {
275    assert_eq!(pair.as_rule(), Rule::time);
276    let inner = pair.into_inner().next().expect("empty time");
277
278    Ok(match inner.as_rule() {
279        Rule::hour_minutes => ts::Time::Fixed(build_hour_minutes(inner)?),
280        Rule::variable_time => ts::Time::Variable(build_variable_time(inner)?),
281        other => unexpected_token(other, Rule::time),
282    })
283}
284
285fn build_extended_time(pair: Pair<Rule>) -> Result<ts::Time> {
286    assert_eq!(pair.as_rule(), Rule::extended_time);
287    let inner = pair.into_inner().next().expect("empty extended time");
288
289    Ok(match inner.as_rule() {
290        Rule::extended_hour_minutes => ts::Time::Fixed(build_extended_hour_minutes(inner)?),
291        Rule::variable_time => ts::Time::Variable(build_variable_time(inner)?),
292        other => unexpected_token(other, Rule::extended_time),
293    })
294}
295
296fn build_variable_time(pair: Pair<Rule>) -> Result<ts::VariableTime> {
297    assert_eq!(pair.as_rule(), Rule::variable_time);
298    let mut pairs = pair.into_inner();
299
300    let event = build_event(pairs.next().expect("empty variable time"));
301
302    let offset = {
303        if pairs.peek().is_some() {
304            let sign = build_plus_or_minus(pairs.next().unwrap());
305
306            let mins: i16 = build_hour_minutes(pairs.next().expect("missing hour minutes"))?
307                .mins_from_midnight()
308                .try_into()
309                .expect("offset overflow");
310
311            match sign {
312                PlusOrMinus::Plus => mins,
313                PlusOrMinus::Minus => -mins,
314            }
315        } else {
316            0
317        }
318    };
319
320    Ok(ts::VariableTime { event, offset })
321}
322
323fn build_event(pair: Pair<Rule>) -> ts::TimeEvent {
324    assert_eq!(pair.as_rule(), Rule::event);
325
326    match pair.into_inner().next().expect("empty event").as_rule() {
327        Rule::dawn => ts::TimeEvent::Dawn,
328        Rule::sunrise => ts::TimeEvent::Sunrise,
329        Rule::sunset => ts::TimeEvent::Sunset,
330        Rule::dusk => ts::TimeEvent::Dusk,
331        other => unexpected_token(other, Rule::event),
332    }
333}
334
335// ---
336// --- WeekDay selector
337// ---
338
339fn build_weekday_selector(pair: Pair<Rule>) -> Result<Vec<ds::WeekDayRange>> {
340    assert_eq!(pair.as_rule(), Rule::weekday_selector);
341
342    pair.into_inner()
343        .flat_map(|pair| match pair.as_rule() {
344            Rule::weekday_sequence => pair.into_inner().map(build_weekday_range as fn(_) -> _),
345            Rule::holiday_sequence => pair.into_inner().map(build_holiday as _),
346            other => unexpected_token(other, Rule::weekday_sequence),
347        })
348        .collect()
349}
350
351fn build_weekday_range(pair: Pair<Rule>) -> Result<ds::WeekDayRange> {
352    assert_eq!(pair.as_rule(), Rule::weekday_range);
353    let mut pairs = pair.into_inner();
354
355    let start = build_wday(pairs.next().expect("empty weekday range"));
356
357    let end = {
358        if pairs.peek().map(|x| x.as_rule()) == Some(Rule::wday) {
359            build_wday(pairs.next().unwrap())
360        } else {
361            start
362        }
363    };
364
365    let mut nth_from_start = [false; 5];
366    let mut nth_from_end = [false; 5];
367
368    while pairs.peek().map(|x| x.as_rule()) == Some(Rule::nth_entry) {
369        let (sign, indices) = build_nth_entry(pairs.next().unwrap())?;
370
371        let nth_array = match sign {
372            Sign::Neg => &mut nth_from_end,
373            Sign::Pos => &mut nth_from_start,
374        };
375
376        for i in indices {
377            nth_array[usize::from(i - 1)] = true;
378        }
379    }
380
381    if !nth_from_start.contains(&true) && !nth_from_end.contains(&true) {
382        nth_from_start = [true; 5];
383        nth_from_end = [true; 5];
384    }
385
386    let offset = {
387        if let Some(pair) = pairs.next() {
388            build_day_offset(pair)?
389        } else {
390            0
391        }
392    };
393
394    Ok(ds::WeekDayRange::Fixed {
395        range: start..=end,
396        offset,
397        nth_from_start,
398        nth_from_end,
399    })
400}
401
402fn build_holiday(pair: Pair<Rule>) -> Result<ds::WeekDayRange> {
403    assert_eq!(pair.as_rule(), Rule::holiday);
404    let mut pairs = pair.into_inner();
405
406    let kind = match pairs.next().expect("empty holiday").as_rule() {
407        Rule::public_holiday => ds::HolidayKind::Public,
408        Rule::school_holiday => ds::HolidayKind::School,
409        other => unexpected_token(other, Rule::holiday),
410    };
411
412    let offset = pairs.next().map(build_day_offset).unwrap_or(Ok(0))?;
413    Ok(ds::WeekDayRange::Holiday { kind, offset })
414}
415
416fn build_nth_entry(pair: Pair<Rule>) -> Result<(Sign, RangeInclusive<u8>)> {
417    assert_eq!(pair.as_rule(), Rule::nth_entry);
418    let mut pairs = pair.into_inner();
419
420    let sign = {
421        if pairs.peek().map(|x| x.as_rule()) == Some(Rule::nth_minus) {
422            pairs.next();
423            Sign::Neg
424        } else {
425            Sign::Pos
426        }
427    };
428
429    let start = build_nth(pairs.next().expect("empty nth entry"));
430    let end = pairs.next().map(build_nth).unwrap_or(start);
431    Ok((sign, start..=end))
432}
433
434fn build_nth(pair: Pair<Rule>) -> u8 {
435    assert_eq!(pair.as_rule(), Rule::nth);
436    pair.as_str().parse().expect("invalid nth format")
437}
438
439fn build_day_offset(pair: Pair<Rule>) -> Result<i64> {
440    assert_eq!(pair.as_rule(), Rule::day_offset);
441    let mut pairs = pair.into_inner();
442
443    let sign = build_plus_or_minus(pairs.next().expect("empty day offset"));
444    let val_abs = build_positive_number(pairs.next().expect("missing value"))?;
445
446    let val_abs: i64 = val_abs.try_into().map_err(|_| Error::Overflow {
447        value: format!("{}", val_abs),
448        expected: "an integer in [-2**63, 2**63[".to_string(),
449    })?;
450
451    Ok(match sign {
452        PlusOrMinus::Plus => val_abs,
453        PlusOrMinus::Minus => -val_abs,
454    })
455}
456
457// ---
458// --- Week selector
459// ---
460
461fn build_week_selector(pair: Pair<Rule>) -> Result<Vec<ds::WeekRange>> {
462    assert_eq!(pair.as_rule(), Rule::week_selector);
463    pair.into_inner().map(build_week).collect()
464}
465
466fn build_week(pair: Pair<Rule>) -> Result<ds::WeekRange> {
467    assert_eq!(pair.as_rule(), Rule::week);
468    let mut rules = pair.into_inner();
469
470    let start = build_weeknum(rules.next().expect("empty weeknum range"));
471    let end = rules.next().map(build_weeknum);
472
473    let step = rules.next().map(build_positive_number).transpose()?;
474    let step = step.unwrap_or(1).try_into().map_err(|_| Error::Overflow {
475        value: format!("{}", step.unwrap()),
476        expected: "an integer in [0, 255]".to_string(),
477    })?;
478
479    Ok(ds::WeekRange {
480        range: WeekNum(start)..=WeekNum(end.unwrap_or(start)),
481        step,
482    })
483}
484
485// ---
486// --- Month selector
487// ---
488
489fn build_monthday_selector(pair: Pair<Rule>) -> Result<Vec<ds::MonthdayRange>> {
490    assert_eq!(pair.as_rule(), Rule::monthday_selector);
491    pair.into_inner().map(build_monthday_range).collect()
492}
493
494fn build_monthday_range(pair: Pair<Rule>) -> Result<ds::MonthdayRange> {
495    assert_eq!(pair.as_rule(), Rule::monthday_range);
496    let mut pairs = pair.into_inner();
497
498    let year = {
499        if pairs.peek().map(|x| x.as_rule()) == Some(Rule::year) {
500            Some(build_year(pairs.next().unwrap()))
501        } else {
502            None
503        }
504    };
505
506    match pairs.peek().expect("empty monthday range").as_rule() {
507        Rule::month => {
508            let start = build_month(pairs.next().unwrap());
509            let end = pairs.next().map(build_month).unwrap_or(start);
510
511            Ok(ds::MonthdayRange::Month { year, range: start..=end })
512        }
513        Rule::date_from => {
514            let start = build_date_from(pairs.next().unwrap());
515
516            let start_offset = {
517                if pairs.peek().map(|x| x.as_rule()) == Some(Rule::date_offset) {
518                    build_date_offset(pairs.next().unwrap())?
519                } else {
520                    ds::DateOffset::default()
521                }
522            };
523
524            let end = match pairs.peek().map(|x| x.as_rule()) {
525                Some(Rule::date_to) => build_date_to(pairs.next().unwrap(), start)?,
526                Some(Rule::monthday_range_plus) => {
527                    pairs.next();
528
529                    if start.has_year() {
530                        ds::Date::ymd(31, ds::Month::December, 9999)
531                    } else {
532                        ds::Date::md(31, ds::Month::December)
533                    }
534                }
535                None => {
536                    return Ok(ds::MonthdayRange::Date {
537                        start: (start, start_offset),
538                        end: (start, start_offset),
539                    });
540                }
541                Some(other) => unexpected_token(other, Rule::monthday_range),
542            };
543
544            let end_offset = pairs
545                .next()
546                .map(build_date_offset)
547                .unwrap_or_else(|| Ok(Default::default()))?;
548
549            Ok(ds::MonthdayRange::Date {
550                start: (start, start_offset),
551                end: (end, end_offset),
552            })
553        }
554        other => unexpected_token(other, Rule::monthday_range),
555    }
556}
557
558fn build_date_offset(pair: Pair<Rule>) -> Result<ds::DateOffset> {
559    assert_eq!(pair.as_rule(), Rule::date_offset);
560    let mut pairs = pair.into_inner();
561
562    let wday_offset = {
563        if pairs.peek().map(|x| x.as_rule()) == Some(Rule::plus_or_minus) {
564            let sign = build_plus_or_minus(pairs.next().unwrap());
565            let wday = build_wday(pairs.next().expect("missing wday after sign"));
566
567            match sign {
568                PlusOrMinus::Plus => ds::WeekDayOffset::Next(wday),
569                PlusOrMinus::Minus => ds::WeekDayOffset::Prev(wday),
570            }
571        } else {
572            ds::WeekDayOffset::None
573        }
574    };
575
576    let day_offset = pairs.next().map(build_day_offset).unwrap_or(Ok(0))?;
577
578    Ok(ds::DateOffset { wday_offset, day_offset })
579}
580
581fn build_date_from(pair: Pair<Rule>) -> ds::Date {
582    assert_eq!(pair.as_rule(), Rule::date_from);
583    let mut pairs = pair.into_inner();
584
585    let year = {
586        if pairs.peek().map(|x| x.as_rule()) == Some(Rule::year) {
587            Some(build_year(pairs.next().unwrap()))
588        } else {
589            None
590        }
591    };
592
593    match pairs.peek().expect("empty date (from)").as_rule() {
594        Rule::variable_date => {
595            #[cfg(feature = "log")]
596            WARN_EASTER.call_once(|| log::warn!("Easter is not supported yet"));
597            ds::Date::Easter { year }
598        }
599        Rule::month => ds::Date::Fixed {
600            year,
601            month: build_month(pairs.next().expect("missing month")),
602            day: build_daynum(pairs.next().expect("missing day")),
603        },
604        other => unexpected_token(other, Rule::date_from),
605    }
606}
607
608fn build_date_to(pair: Pair<Rule>, from: ds::Date) -> Result<ds::Date> {
609    assert_eq!(pair.as_rule(), Rule::date_to);
610    let pair = pair.into_inner().next().expect("empty date (to)");
611
612    Ok(match pair.as_rule() {
613        Rule::date_from => build_date_from(pair),
614        Rule::daynum => {
615            let daynum = build_daynum(pair);
616
617            match from {
618                ds::Date::Easter { .. } => {
619                    // NOTE: this is actually not a specified constraint, but it is quite confusing
620                    //       that this is allowed
621                    return Err(Error::Unsupported("Easter followed by a day number"));
622                }
623                ds::Date::Fixed { mut year, mut month, day } => {
624                    if day > daynum {
625                        month = month.next();
626
627                        if month == ds::Month::January {
628                            if let Some(x) = year.as_mut() {
629                                *x += 1
630                            }
631                        }
632                    }
633
634                    ds::Date::Fixed { year, month, day: daynum }
635                }
636            }
637        }
638        other => unexpected_token(other, Rule::date_to),
639    })
640}
641
642// ---
643// --- Year selector
644// ---
645
646fn build_year_selector(pair: Pair<Rule>) -> Result<Vec<ds::YearRange>> {
647    assert_eq!(pair.as_rule(), Rule::year_selector);
648    pair.into_inner().map(build_year_range).collect()
649}
650
651fn build_year_range(pair: Pair<Rule>) -> Result<ds::YearRange> {
652    assert_eq!(pair.as_rule(), Rule::year_range);
653    let mut rules = pair.into_inner();
654
655    let start = build_year(rules.next().expect("empty year range"));
656    let end = rules.next().map(|pair| match pair.as_rule() {
657        Rule::year => build_year(pair),
658        Rule::year_range_plus => 9999,
659        other => unexpected_token(other, Rule::year_range),
660    });
661
662    let step = rules.next().map(build_positive_number).transpose()?;
663    let step = step.unwrap_or(1).try_into().map_err(|_| Error::Overflow {
664        value: format!("{}", step.unwrap()),
665        expected: "an integer in [0, 2**16[".to_string(),
666    })?;
667
668    Ok(ds::YearRange {
669        range: Year(start)..=Year(end.unwrap_or(start)),
670        step,
671    })
672}
673
674// ---
675// --- Basic elements
676// ---
677
678fn build_plus_or_minus(pair: Pair<Rule>) -> PlusOrMinus {
679    assert_eq!(pair.as_rule(), Rule::plus_or_minus);
680    let pair = pair.into_inner().next().expect("empty plus or minus");
681
682    match pair.as_rule() {
683        Rule::plus => PlusOrMinus::Plus,
684        Rule::minus => PlusOrMinus::Minus,
685        other => unexpected_token(other, Rule::plus_or_minus),
686    }
687}
688
689fn build_minute(pair: Pair<Rule>) -> Duration {
690    assert_eq!(pair.as_rule(), Rule::minute);
691    let minutes = pair.as_str().parse().expect("invalid minute");
692    Duration::minutes(minutes)
693}
694
695fn build_hour_minutes(pair: Pair<Rule>) -> Result<ExtendedTime> {
696    assert_eq!(pair.as_rule(), Rule::hour_minutes);
697    let mut pairs = pair.into_inner();
698
699    let Some(hour_rule) = pairs.next() else {
700        return Ok(ExtendedTime::MIDNIGHT_24);
701    };
702
703    let hour = hour_rule.as_str().parse().expect("invalid hour");
704
705    let minutes = pairs
706        .next()
707        .expect("missing minutes")
708        .as_str()
709        .parse()
710        .expect("invalid minutes");
711
712    ExtendedTime::new(hour, minutes).ok_or(Error::InvalidExtendTime { hour, minutes })
713}
714
715fn build_extended_hour_minutes(pair: Pair<Rule>) -> Result<ExtendedTime> {
716    assert_eq!(pair.as_rule(), Rule::extended_hour_minutes);
717    let mut pairs = pair.into_inner();
718
719    let hour = pairs
720        .next()
721        .expect("missing hour")
722        .as_str()
723        .parse()
724        .expect("invalid hour");
725
726    let minutes = pairs
727        .next()
728        .expect("missing minutes")
729        .as_str()
730        .parse()
731        .expect("invalid minutes");
732
733    ExtendedTime::new(hour, minutes).ok_or(Error::InvalidExtendTime { hour, minutes })
734}
735
736fn build_hour_minutes_as_duration(pair: Pair<Rule>) -> Duration {
737    assert_eq!(pair.as_rule(), Rule::hour_minutes);
738    let mut pairs = pair.into_inner();
739
740    let hour = pairs
741        .next()
742        .expect("missing hour")
743        .as_str()
744        .parse()
745        .expect("invalid hour");
746
747    let minutes = pairs
748        .next()
749        .expect("missing minutes")
750        .as_str()
751        .parse()
752        .expect("invalid minutes");
753
754    Duration::hours(hour) + Duration::minutes(minutes)
755}
756
757fn build_wday(pair: Pair<Rule>) -> ds::Weekday {
758    assert_eq!(pair.as_rule(), Rule::wday);
759    let pair = pair.into_inner().next().expect("empty week day");
760
761    match pair.as_rule() {
762        Rule::sunday => ds::Weekday::Sun,
763        Rule::monday => ds::Weekday::Mon,
764        Rule::tuesday => ds::Weekday::Tue,
765        Rule::wednesday => ds::Weekday::Wed,
766        Rule::thursday => ds::Weekday::Thu,
767        Rule::friday => ds::Weekday::Fri,
768        Rule::saturday => ds::Weekday::Sat,
769        other => unexpected_token(other, Rule::wday),
770    }
771}
772
773fn build_daynum(pair: Pair<Rule>) -> u8 {
774    assert_eq!(pair.as_rule(), Rule::daynum);
775    let daynum = pair.as_str().parse().expect("invalid month format");
776
777    if daynum == 0 {
778        #[cfg(feature = "log")]
779        log::warn!("Found day number 0 in opening hours: specify the 1st or 31st instead.");
780        return 1;
781    }
782
783    if daynum > 31 {
784        #[cfg(feature = "log")]
785        log::warn!("Found day number {daynum} in opening hours");
786        return 31;
787    }
788
789    daynum
790}
791
792fn build_weeknum(pair: Pair<Rule>) -> u8 {
793    assert_eq!(pair.as_rule(), Rule::weeknum);
794    pair.as_str().parse().expect("invalid weeknum format")
795}
796
797fn build_month(pair: Pair<Rule>) -> ds::Month {
798    assert_eq!(pair.as_rule(), Rule::month);
799    let pair = pair.into_inner().next().expect("empty month");
800
801    match pair.as_rule() {
802        Rule::january => ds::Month::January,
803        Rule::february => ds::Month::February,
804        Rule::march => ds::Month::March,
805        Rule::april => ds::Month::April,
806        Rule::may => ds::Month::May,
807        Rule::june => ds::Month::June,
808        Rule::july => ds::Month::July,
809        Rule::august => ds::Month::August,
810        Rule::september => ds::Month::September,
811        Rule::october => ds::Month::October,
812        Rule::november => ds::Month::November,
813        Rule::december => ds::Month::December,
814        other => unexpected_token(other, Rule::month),
815    }
816}
817
818fn build_year(pair: Pair<Rule>) -> u16 {
819    assert_eq!(pair.as_rule(), Rule::year);
820    pair.as_str().parse().expect("invalid year format")
821}
822
823fn build_positive_number(pair: Pair<Rule>) -> Result<u64> {
824    assert_eq!(pair.as_rule(), Rule::positive_number);
825    pair.as_str().parse().map_err(|_| Error::Overflow {
826        value: pair.as_str().to_string(),
827        expected: "a number between 0 and 2**64".to_string(),
828    })
829}
830
831fn build_comment(pair: Pair<Rule>) -> String {
832    assert_eq!(pair.as_rule(), Rule::comment);
833    build_comment_inner(pair.into_inner().next().expect("empty comment"))
834}
835
836fn build_comment_inner(pair: Pair<Rule>) -> String {
837    assert_eq!(pair.as_rule(), Rule::comment_inner);
838    pair.as_str().to_string()
839}
840
841// Mics
842
843enum PlusOrMinus {
844    Plus,
845    Minus,
846}