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