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
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 (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
336fn 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
458fn 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
486fn 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 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
643fn 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
675fn 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
842enum PlusOrMinus {
845 Plus,
846 Minus,
847}