recurring/pattern/daily.rs
1use crate::pattern::Interval;
2use crate::{DateTimeRange, Error, Pattern, private};
3use jiff::{
4 ToSpan,
5 civil::{DateTime, Time},
6};
7
8/// A recurrence pattern for daily events which may also include fixed times of the day.
9///
10/// # Example
11///
12/// ```
13/// use recurring::pattern::Daily;
14/// use jiff::civil::time;
15///
16/// let every_two_days_at_twelve = Daily::new(1).at(time(12, 0, 0, 0));
17/// ```
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct Daily {
20 interval: Interval,
21 at: Option<Time>,
22}
23
24impl Daily {
25 /// Creates a new `Daily` from an interval of days.
26 ///
27 /// The fallible version of this method is [`Daily::try_new`].
28 ///
29 /// # Panics
30 ///
31 /// Panics if `interval` is negative or zero.
32 ///
33 /// # Example
34 ///
35 /// ```
36 /// use recurring::pattern::Daily;
37 ///
38 /// let every_two_days = Daily::new(2);
39 /// ```
40 pub fn new<I: ToSpan>(interval: I) -> Daily {
41 Daily {
42 interval: Interval::new(interval.days()),
43 at: None,
44 }
45 }
46
47 /// Creates a new `Daily` from an interval of days.
48 ///
49 /// The panicking version of this method is [`Daily::new`].
50 ///
51 /// # Errors
52 ///
53 /// Returns an `Error` if `interval` is negative or zero.
54 ///
55 /// # Example
56 ///
57 /// ```
58 /// use recurring::pattern::Daily;
59 ///
60 /// assert!(Daily::try_new(1).is_ok());
61 /// assert!(Daily::try_new(0).is_err());
62 /// assert!(Daily::try_new(-1).is_err());
63 /// ```
64 pub fn try_new<I: ToSpan>(interval: I) -> Result<Daily, Error> {
65 Ok(Daily {
66 interval: Interval::try_new(interval.days())?,
67 at: None,
68 })
69 }
70
71 /// Sets the exact time of day for the daily recurrence.
72 ///
73 /// # Example
74 ///
75 /// ```
76 /// use jiff::civil::time;
77 /// use recurring::pattern::daily;
78 ///
79 /// let every_day_at_twelve = daily(1).at(time(12, 0, 0, 0));
80 ///
81 /// let every_day_at_midnight_and_twelve = every_day_at_twelve.at(time(0, 0, 0, 0));
82 /// ```
83 #[must_use]
84 pub fn at<T: Into<Time>>(mut self, time: T) -> Daily {
85 self.at = Some(time.into());
86 self
87 }
88
89 fn range_adjusted<F>(&self, f: F, instant: DateTime, range: DateTimeRange) -> Option<DateTime>
90 where
91 F: FnOnce(&Interval, DateTime, DateTimeRange) -> Option<DateTime>,
92 {
93 let Some(time) = self.at else {
94 return f(&self.interval, instant, range);
95 };
96
97 let start = if range.start.time() <= time {
98 range.start
99 } else {
100 self.interval.next_after(range.start, range)?
101 };
102
103 let start = start.with().time(time).build().ok()?;
104 let range = DateTimeRange::from(start..range.end);
105
106 f(&self.interval, instant, range)
107 }
108}
109
110impl Pattern for Daily {
111 fn next_after(&self, instant: DateTime, range: DateTimeRange) -> Option<DateTime> {
112 self.range_adjusted(Interval::next_after, instant, range)
113 }
114
115 fn previous_before(&self, instant: DateTime, range: DateTimeRange) -> Option<DateTime> {
116 self.range_adjusted(Interval::previous_before, instant, range)
117 }
118
119 fn closest_to(&self, instant: DateTime, range: DateTimeRange) -> Option<DateTime> {
120 self.range_adjusted(Interval::closest_to, instant, range)
121 }
122}
123
124impl private::Sealed for Daily {}