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
26enum 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
42fn 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
113fn 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
149fn 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
234fn 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 (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
335fn 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
457fn 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
485fn 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 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
642fn 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
674fn 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
841enum PlusOrMinus {
844 Plus,
845 Minus,
846}