1 use std
::convert
::TryInto
;
6 character
::complete
::space0
,
9 multi
::separated_nonempty_list
,
10 sequence
::{preceded, terminated, tuple}
,
13 use crate::date_time_value
::DateTimeValue
;
14 use crate::parse_helpers
::{parse_complete_line, parse_error, parse_time_comp, IResult}
;
15 use crate::{parse_weekdays_range, TmEditor, WeekDays}
;
17 /// Calendar events may be used to refer to one or more points in time in a
18 /// single expression. They are designed after the systemd.time Calendar Events
19 /// specification, but are not guaranteed to be 100% compatible.
20 #[derive(Default, Clone, Debug)]
21 pub struct CalendarEvent
{
22 /// the days in a week this event should trigger
23 pub(crate) days
: WeekDays
,
24 /// the second(s) this event should trigger
25 pub(crate) second
: Vec
<DateTimeValue
>, // todo: support float values
26 /// the minute(s) this event should trigger
27 pub(crate) minute
: Vec
<DateTimeValue
>,
28 /// the hour(s) this event should trigger
29 pub(crate) hour
: Vec
<DateTimeValue
>,
30 /// the day(s) in a month this event should trigger
31 pub(crate) day
: Vec
<DateTimeValue
>,
32 /// the month(s) in a year this event should trigger
33 pub(crate) month
: Vec
<DateTimeValue
>,
34 /// the years(s) this event should trigger
35 pub(crate) year
: Vec
<DateTimeValue
>,
39 /// Computes the next timestamp after `last`. If `utc` is false, the local
40 /// timezone will be used for the calculation.
41 pub fn compute_next_event(&self, last
: i64, utc
: bool
) -> Result
<Option
<i64>, Error
> {
42 let last
= last
+ 1; // at least one second later
44 let all_days
= self.days
.is_empty() || self.days
.is_all();
46 let mut t
= TmEditor
::with_epoch(last
, utc
)?
;
51 // cancel after 1000 loops
58 if !self.year
.is_empty() {
59 let year
: u32 = t
.year().try_into()?
;
60 if !DateTimeValue
::list_contains(&self.year
, year
) {
61 if let Some(n
) = DateTimeValue
::find_next(&self.year
, year
) {
62 t
.add_years((n
- year
).try_into()?
)?
;
65 // if we have no valid year, we cannot find a correct timestamp
71 if !self.month
.is_empty() {
72 let month
: u32 = t
.month().try_into()?
;
73 if !DateTimeValue
::list_contains(&self.month
, month
) {
74 if let Some(n
) = DateTimeValue
::find_next(&self.month
, month
) {
75 t
.add_months((n
- month
).try_into()?
)?
;
77 // if we could not find valid month, retry next year
84 if !self.day
.is_empty() {
85 let day
: u32 = t
.day().try_into()?
;
86 if !DateTimeValue
::list_contains(&self.day
, day
) {
87 if let Some(n
) = DateTimeValue
::find_next(&self.day
, day
) {
88 t
.add_days((n
- day
).try_into()?
)?
;
90 // if we could not find valid mday, retry next month
99 let day_num
: u32 = t
.day_num().try_into()?
;
100 let day
= WeekDays
::from_bits(1 << day_num
).unwrap();
101 if !self.days
.contains(day
) {
102 if let Some(n
) = ((day_num
+ 1)..7)
103 .find(|d
| self.days
.contains(WeekDays
::from_bits(1 << d
).unwrap()))
106 t
.add_days((n
- day_num
).try_into()?
)?
;
109 t
.add_days((7 - day_num
).try_into()?
)?
;
116 if !self.hour
.is_empty() {
117 let hour
= t
.hour().try_into()?
;
118 if !DateTimeValue
::list_contains(&self.hour
, hour
) {
119 if let Some(n
) = DateTimeValue
::find_next(&self.hour
, hour
) {
121 t
.set_time(n
.try_into()?
, 0, 0)?
;
131 if !self.minute
.is_empty() {
132 let minute
= t
.min().try_into()?
;
133 if !DateTimeValue
::list_contains(&self.minute
, minute
) {
134 if let Some(n
) = DateTimeValue
::find_next(&self.minute
, minute
) {
136 t
.set_min_sec(n
.try_into()?
, 0)?
;
139 t
.set_time(t
.hour() + 1, 0, 0)?
;
146 if !self.second
.is_empty() {
147 let second
= t
.sec().try_into()?
;
148 if !DateTimeValue
::list_contains(&self.second
, second
) {
149 if let Some(n
) = DateTimeValue
::find_next(&self.second
, second
) {
151 t
.set_sec(n
.try_into()?
)?
;
154 t
.set_min_sec(t
.min() + 1, 0)?
;
160 let next
= t
.into_epoch()?
;
161 return Ok(Some(next
));
166 /// Verify the format of the [CalendarEvent]
167 pub fn verify_calendar_event(i
: &str) -> Result
<(), Error
> {
168 parse_calendar_event(i
)?
;
172 /// Compute the next event. Use [CalendarEvent::compute_next_event] instead.
173 #[deprecated="use method 'compute_next_event' of CalendarEvent instead"]
174 pub fn compute_next_event(
175 event
: &CalendarEvent
,
178 ) -> Result
<Option
<i64>, Error
> {
179 event
.compute_next_event(last
, utc
)
182 /// Parse a [CalendarEvent]
183 pub fn parse_calendar_event(i
: &str) -> Result
<CalendarEvent
, Error
> {
184 parse_complete_line("calendar event", i
, parse_calendar_event_incomplete
)
187 fn parse_calendar_event_incomplete(mut i
: &str) -> IResult
<&str, CalendarEvent
> {
188 let mut has_dayspec
= false;
189 let mut has_timespec
= false;
190 let mut has_datespec
= false;
192 let mut event
= CalendarEvent
::default();
194 if i
.starts_with(|c
: char| char::is_ascii_alphabetic(&c
)) {
200 second
: vec
![DateTimeValue
::Single(0)],
209 minute
: vec
![DateTimeValue
::Single(0)],
210 second
: vec
![DateTimeValue
::Single(0)],
219 hour
: vec
![DateTimeValue
::Single(0)],
220 minute
: vec
![DateTimeValue
::Single(0)],
221 second
: vec
![DateTimeValue
::Single(0)],
230 hour
: vec
![DateTimeValue
::Single(0)],
231 minute
: vec
![DateTimeValue
::Single(0)],
232 second
: vec
![DateTimeValue
::Single(0)],
233 days
: WeekDays
::MONDAY
,
242 hour
: vec
![DateTimeValue
::Single(0)],
243 minute
: vec
![DateTimeValue
::Single(0)],
244 second
: vec
![DateTimeValue
::Single(0)],
245 day
: vec
![DateTimeValue
::Single(1)],
250 "yearly" | "annually" => {
254 hour
: vec
![DateTimeValue
::Single(0)],
255 minute
: vec
![DateTimeValue
::Single(0)],
256 second
: vec
![DateTimeValue
::Single(0)],
257 day
: vec
![DateTimeValue
::Single(1)],
258 month
: vec
![DateTimeValue
::Single(1)],
267 hour
: vec
![DateTimeValue
::Single(0)],
268 minute
: vec
![DateTimeValue
::Single(0)],
269 second
: vec
![DateTimeValue
::Single(0)],
270 day
: vec
![DateTimeValue
::Single(1)],
272 DateTimeValue
::Single(1),
273 DateTimeValue
::Single(4),
274 DateTimeValue
::Single(7),
275 DateTimeValue
::Single(10),
281 "semiannually" | "semi-annually" => {
285 hour
: vec
![DateTimeValue
::Single(0)],
286 minute
: vec
![DateTimeValue
::Single(0)],
287 second
: vec
![DateTimeValue
::Single(0)],
288 day
: vec
![DateTimeValue
::Single(1)],
289 month
: vec
![DateTimeValue
::Single(1), DateTimeValue
::Single(7)],
294 _
=> { /* continue */ }
297 let (n
, range_list
) = context(
298 "weekday range list",
299 separated_nonempty_list(tag(","), parse_weekdays_range
),
306 for range
in range_list
{
307 event
.days
.insert(range
);
311 if let (n
, Some(date
)) = opt(parse_date_spec
)(i
)?
{
312 event
.year
= date
.year
;
313 event
.month
= date
.month
;
314 event
.day
= date
.day
;
319 if let (n
, Some(time
)) = opt(parse_time_spec
)(i
)?
{
320 event
.hour
= time
.hour
;
321 event
.minute
= time
.minute
;
322 event
.second
= time
.second
;
326 event
.hour
= vec
![DateTimeValue
::Single(0)];
327 event
.minute
= vec
![DateTimeValue
::Single(0)];
328 event
.second
= vec
![DateTimeValue
::Single(0)];
331 if !(has_dayspec
|| has_timespec
|| has_datespec
) {
332 return Err(parse_error(i
, "date or time specification"));
339 hour
: Vec
<DateTimeValue
>,
340 minute
: Vec
<DateTimeValue
>,
341 second
: Vec
<DateTimeValue
>,
345 year
: Vec
<DateTimeValue
>,
346 month
: Vec
<DateTimeValue
>,
347 day
: Vec
<DateTimeValue
>,
350 fn parse_date_time_comp(max
: usize) -> impl Fn(&str) -> IResult
<&str, DateTimeValue
> {
352 let (i
, value
) = parse_time_comp(max
)(i
)?
;
354 if let (i
, Some(end
)) = opt(preceded(tag(".."), parse_time_comp(max
)))(i
)?
{
356 return Err(parse_error(i
, "range start is bigger than end"));
358 if let Some(time
) = i
.strip_prefix('
/'
) {
359 let (time
, repeat
) = parse_time_comp(max
)(time
)?
;
360 return Ok((time
, DateTimeValue
::Repeated(value
, repeat
, Some(end
))));
362 return Ok((i
, DateTimeValue
::Range(value
, end
)));
365 if let Some(time
) = i
.strip_prefix('
/'
) {
366 let (time
, repeat
) = parse_time_comp(max
)(time
)?
;
367 Ok((time
, DateTimeValue
::Repeated(value
, repeat
, None
)))
369 Ok((i
, DateTimeValue
::Single(value
)))
374 fn parse_date_time_comp_list(
377 ) -> impl Fn(&str) -> IResult
<&str, Vec
<DateTimeValue
>> {
379 if let Some(rest
) = i
.strip_prefix('
*'
) {
380 if let Some(time
) = rest
.strip_prefix('
/'
) {
381 let (n
, repeat
) = parse_time_comp(max
)(time
)?
;
383 return Ok((n
, vec
![DateTimeValue
::Repeated(start
, repeat
, None
)]));
386 return Ok((rest
, Vec
::new()));
389 separated_nonempty_list(tag(","), parse_date_time_comp(max
))(i
)
393 fn parse_time_spec(i
: &str) -> IResult
<&str, TimeSpec
> {
394 let (i
, (opt_hour
, minute
, opt_second
)) = tuple((
395 opt(terminated(parse_date_time_comp_list(0, 24), tag(":"))),
396 parse_date_time_comp_list(0, 60),
397 opt(preceded(tag(":"), parse_date_time_comp_list(0, 60))),
400 let hour
= opt_hour
.unwrap_or_else(Vec
::new
);
401 let second
= opt_second
.unwrap_or_else(|| vec
![DateTimeValue
::Single(0)]);
413 fn parse_date_spec(i
: &str) -> IResult
<&str, DateSpec
> {
414 // TODO: implement ~ for days (man systemd.time)
415 if let Ok((i
, (year
, month
, day
))) = tuple((
416 parse_date_time_comp_list(0, 2200), // the upper limit for systemd, stay compatible
417 preceded(tag("-"), parse_date_time_comp_list(1, 13)),
418 preceded(tag("-"), parse_date_time_comp_list(1, 32)),
421 Ok((i
, DateSpec { year, month, day }
))
422 } else if let Ok((i
, (month
, day
))) = tuple((
423 parse_date_time_comp_list(1, 13),
424 preceded(tag("-"), parse_date_time_comp_list(1, 32)),
436 Err(parse_error(i
, "invalid date spec"))