Skip to main content

opening_hours/
context.rs

1use std::fmt::Debug;
2use std::sync::Arc;
3
4use compact_calendar::CompactCalendar;
5use opening_hours_syntax::rules::day::HolidayKind;
6
7use crate::localization::{Localize, NoLocation};
8
9// --
10// -- Holidays
11// --
12
13/// Pairs a set of public holidays with a set of school holidays.
14#[derive(Clone, Default, Debug, Hash, PartialEq, Eq)]
15pub struct ContextHolidays {
16    pub(crate) public: Arc<CompactCalendar>,
17    pub(crate) school: Arc<CompactCalendar>,
18}
19
20impl ContextHolidays {
21    /// Create a new holidays context from sets of public and school holidays.
22    pub fn new(public: Arc<CompactCalendar>, school: Arc<CompactCalendar>) -> Self {
23        Self { public, school }
24    }
25
26    /// Get the set of public holidays attached to this context.
27    pub fn get_public(&self) -> &CompactCalendar {
28        &self.public
29    }
30
31    /// Get the set of school holidays attached to this context.
32    pub fn get_school(&self) -> &CompactCalendar {
33        &self.school
34    }
35
36    /// Get the set of holidays corresponding to given kind.
37    pub fn get_for_kind(&self, kind: HolidayKind) -> &CompactCalendar {
38        match kind {
39            HolidayKind::Public => &self.public,
40            HolidayKind::School => &self.school,
41        }
42    }
43}
44
45/// All the context attached to a parsed OpeningHours expression and that can
46/// alter its evaluation semantics.
47#[derive(Clone, Debug, Hash, PartialEq, Eq)]
48pub struct Context<L = NoLocation> {
49    /// A calendar use for evaluation of public and private holidays.
50    pub holidays: ContextHolidays,
51    /// A calendar of holidays where it is unknown whether it applies to
52    /// current location or not. Typicaly, this can include regional holidays
53    /// in a countext where only the country is known.
54    pub holidays_unknown: ContextHolidays,
55    /// Specify locality of the place attached to the expression: from
56    /// timezone to coordinates.
57    pub locale: L,
58    /// As an approximation, consider that any interval bigger that this size
59    /// is infinite. This can be enabled if you need better performance and
60    /// you don't care if a shop is open in more than a year.
61    pub approx_bound_interval_size: Option<chrono::TimeDelta>,
62}
63
64impl<L> Context<L> {
65    /// Attach a new holidays component to this context.
66    pub fn with_holidays(self, holidays: ContextHolidays) -> Self {
67        Self { holidays, ..self }
68    }
69
70    /// Attach a new unknown holidays component to this context.
71    pub fn with_holidays_unknown(self, holidays_unknown: ContextHolidays) -> Self {
72        Self { holidays_unknown, ..self }
73    }
74
75    /// Attach a new locale component to this context.
76    pub fn with_locale<L2: Localize>(self, locale: L2) -> Context<L2> {
77        Context {
78            holidays: self.holidays,
79            holidays_unknown: self.holidays_unknown,
80            locale,
81            approx_bound_interval_size: None,
82        }
83    }
84
85    /// Enables appromiation of long intervals.
86    pub fn approx_bound_interval_size(self, max_size: chrono::TimeDelta) -> Self {
87        Self { approx_bound_interval_size: Some(max_size), ..self }
88    }
89}
90
91#[cfg(feature = "auto-timezone")]
92impl Context<crate::localization::TzLocation<chrono_tz::Tz>> {
93    /// Create a context with given coordinates and try to infer a timezone and
94    /// a local holiday calendar.
95    ///
96    /// ```
97    /// use opening_hours::Context;
98    /// use opening_hours::localization::{Coordinates, Country, TzLocation};
99    ///
100    /// let coords = Coordinates::new(48.8535, 2.34839).unwrap();
101    ///
102    /// assert_eq!(
103    ///     Context::from_coords(coords),
104    ///     Context::default()
105    ///         .with_holidays(Country::FR.holidays())
106    ///         .with_locale(TzLocation::from_coords(coords)),
107    /// );
108    /// ```
109    #[cfg(feature = "auto-country")]
110    pub fn from_coords(coords: crate::localization::Coordinates) -> Self {
111        use crate::localization::Country;
112
113        let locale = crate::localization::TzLocation::from_coords(coords);
114        let country_res = Country::try_from_coords(coords);
115        let holidays = country_res.map(Country::holidays).unwrap_or_default();
116
117        let holidays_unknown = country_res
118            .map(Country::holidays_regional)
119            .unwrap_or_default();
120
121        Self {
122            holidays,
123            holidays_unknown,
124            locale,
125            approx_bound_interval_size: None,
126        }
127    }
128}
129
130impl Default for Context<NoLocation> {
131    fn default() -> Self {
132        Self {
133            holidays: Default::default(),
134            holidays_unknown: Default::default(),
135            locale: NoLocation,
136            approx_bound_interval_size: None,
137        }
138    }
139}