]> git.proxmox.com Git - proxmox-backup.git/blame - src/tools/systemd/parse_time.rs
tools/systemd/parse_time: enable */x syntax for calendar events
[proxmox-backup.git] / src / tools / systemd / parse_time.rs
CommitLineData
e05b637c
DM
1use std::collections::HashMap;
2
547f0c97 3use anyhow::{Error};
e05b637c
DM
4use lazy_static::lazy_static;
5
6use super::time::*;
7
177a2de9 8use crate::tools::nom::{
547f0c97 9 parse_complete_line, parse_u64, parse_error, IResult,
177a2de9
DM
10};
11
e05b637c 12use nom::{
177a2de9 13 error::{context},
e05b637c 14 bytes::complete::{tag, take_while1},
547f0c97 15 combinator::{map_res, opt, recognize},
e05b637c
DM
16 sequence::{pair, preceded, tuple},
17 character::complete::{alpha1, space0, digit1},
18 multi::separated_nonempty_list,
19};
20
e05b637c
DM
21lazy_static! {
22 pub static ref TIME_SPAN_UNITS: HashMap<&'static str, f64> = {
23 let mut map = HashMap::new();
24
25 let second = 1.0;
26
27 map.insert("seconds", second);
28 map.insert("second", second);
29 map.insert("sec", second);
30 map.insert("s", second);
31
32 let msec = second / 1000.0;
33
34 map.insert("msec", msec);
35 map.insert("ms", msec);
36
37 let usec = msec / 1000.0;
38
39 map.insert("usec", usec);
40 map.insert("us", usec);
41 map.insert("µs", usec);
42
43 let nsec = usec / 1000.0;
44
45 map.insert("nsec", nsec);
46 map.insert("ns", nsec);
47
48 let minute = second * 60.0;
49
50 map.insert("minutes", minute);
51 map.insert("minute", minute);
52 map.insert("min", minute);
53 map.insert("m", minute);
54
55 let hour = minute * 60.0;
56
57 map.insert("hours", hour);
58 map.insert("hour", hour);
59 map.insert("hr", hour);
60 map.insert("h", hour);
61
62 let day = hour * 24.0 ;
63
64 map.insert("days", day);
65 map.insert("day", day);
66 map.insert("d", day);
67
68 let week = day * 7.0;
69
70 map.insert("weeks", week);
71 map.insert("week", week);
72 map.insert("w", week);
73
74 let month = 30.44 * day;
75
76 map.insert("months", month);
77 map.insert("month", month);
78 map.insert("M", month);
79
80 let year = 365.25 * day;
81
82 map.insert("years", year);
83 map.insert("year", year);
84 map.insert("y", year);
85
86 map
87 };
88}
7a314d18
DM
89fn parse_time_comp(max: usize) -> impl Fn(&str) -> IResult<&str, u32> {
90 move |i: &str| {
91 let (i, v) = map_res(recognize(digit1), str::parse)(i)?;
92 if (v as usize) >= max {
93 return Err(parse_error(i, "time value too large"));
94 }
95 Ok((i, v))
96 }
e05b637c
DM
97}
98
177a2de9 99fn parse_weekday(i: &str) -> IResult<&str, WeekDays> {
e05b637c
DM
100 let (i, text) = alpha1(i)?;
101
102 match text.to_ascii_lowercase().as_str() {
103 "monday" | "mon" => Ok((i, WeekDays::MONDAY)),
104 "tuesday" | "tue" => Ok((i, WeekDays::TUESDAY)),
105 "wednesday" | "wed" => Ok((i, WeekDays::WEDNESDAY)),
106 "thursday" | "thu" => Ok((i, WeekDays::THURSDAY)),
107 "friday" | "fri" => Ok((i, WeekDays::FRIDAY)),
108 "saturday" | "sat" => Ok((i, WeekDays::SATURDAY)),
109 "sunday" | "sun" => Ok((i, WeekDays::SUNDAY)),
110 _ => return Err(parse_error(text, "weekday")),
111 }
112}
113
114fn parse_weekdays_range(i: &str) -> IResult<&str, WeekDays> {
115 let (i, startday) = parse_weekday(i)?;
116
117 let generate_range = |start, end| {
118 let mut res = 0;
119 let mut pos = start;
120 loop {
121 res |= pos;
122 if pos >= end { break; }
123 pos = pos << 1;
124 }
125 WeekDays::from_bits(res).unwrap()
126 };
127
128 if let (i, Some((_, endday))) = opt(pair(tag(".."),parse_weekday))(i)? {
129 let start = startday.bits();
130 let end = endday.bits();
131 if start > end {
132 let set1 = generate_range(start, WeekDays::SUNDAY.bits());
133 let set2 = generate_range(WeekDays::MONDAY.bits(), end);
134 Ok((i, set1 | set2))
135 } else {
136 Ok((i, generate_range(start, end)))
137 }
138 } else {
139 Ok((i, startday))
140 }
141}
142
7a314d18
DM
143fn parse_date_time_comp(max: usize) -> impl Fn(&str) -> IResult<&str, DateTimeValue> {
144 move |i: &str| {
145 let (i, value) = parse_time_comp(max)(i)?;
e05b637c 146
7a314d18 147 if let (i, Some(end)) = opt(preceded(tag(".."), parse_time_comp(max)))(i)? {
ce7ab28c
DC
148 if value > end {
149 return Err(parse_error(i, "range start is bigger than end"));
150 }
7a314d18
DM
151 return Ok((i, DateTimeValue::Range(value, end)))
152 }
e05b637c 153
7a314d18
DM
154 if i.starts_with("/") {
155 let i = &i[1..];
156 let (i, repeat) = parse_time_comp(max)(i)?;
157 Ok((i, DateTimeValue::Repeated(value, repeat)))
158 } else {
159 Ok((i, DateTimeValue::Single(value)))
160 }
e05b637c
DM
161 }
162}
163
13bed622 164fn parse_date_time_comp_list(start: u32, max: usize) -> impl Fn(&str) -> IResult<&str, Vec<DateTimeValue>> {
7a314d18
DM
165 move |i: &str| {
166 if i.starts_with("*") {
13bed622
DC
167 let i = &i[1..];
168 if i.starts_with("/") {
169 let (n, repeat) = parse_time_comp(max)(&i[1..])?;
170 if repeat > 0 {
171 return Ok((n, vec![DateTimeValue::Repeated(start, repeat)]));
172 }
173 }
174 return Ok((i, Vec::new()));
7a314d18 175 }
e05b637c 176
7a314d18 177 separated_nonempty_list(tag(","), parse_date_time_comp(max))(i)
e05b637c 178 }
e05b637c
DM
179}
180
181fn parse_time_spec(i: &str) -> IResult<&str, (Vec<DateTimeValue>, Vec<DateTimeValue>, Vec<DateTimeValue>)> {
182
183 let (i, (hour, minute, opt_second)) = tuple((
13bed622
DC
184 parse_date_time_comp_list(0, 24),
185 preceded(tag(":"), parse_date_time_comp_list(0, 60)),
186 opt(preceded(tag(":"), parse_date_time_comp_list(0, 60))),
e05b637c
DM
187 ))(i)?;
188
189 if let Some(second) = opt_second {
190 Ok((i, (hour, minute, second)))
191 } else {
192 Ok((i, (hour, minute, vec![DateTimeValue::Single(0)])))
193 }
194}
195
44055cac
DC
196fn parse_date_spec(i: &str) -> IResult<&str, (Vec<DateTimeValue>, Vec<DateTimeValue>, Vec<DateTimeValue>)> {
197
198 // TODO: implement ~ for days (man systemd.time)
199 if let Ok((i, (year, month, day))) = tuple((
13bed622
DC
200 parse_date_time_comp_list(0, 2200), // the upper limit for systemd, stay compatible
201 preceded(tag("-"), parse_date_time_comp_list(1, 13)),
202 preceded(tag("-"), parse_date_time_comp_list(1, 32)),
44055cac
DC
203 ))(i) {
204 Ok((i, (year, month, day)))
205 } else if let Ok((i, (month, day))) = tuple((
13bed622
DC
206 parse_date_time_comp_list(1, 13),
207 preceded(tag("-"), parse_date_time_comp_list(1, 32)),
44055cac
DC
208 ))(i) {
209 Ok((i, (Vec::new(), month, day)))
210 } else {
211 Err(parse_error(i, "invalid date spec"))
212 }
213}
214
e05b637c 215pub fn parse_calendar_event(i: &str) -> Result<CalendarEvent, Error> {
547f0c97 216 parse_complete_line("calendar event", i, parse_calendar_event_incomplete)
e05b637c
DM
217}
218
219fn parse_calendar_event_incomplete(mut i: &str) -> IResult<&str, CalendarEvent> {
220
221 let mut has_dayspec = false;
222 let mut has_timespec = false;
44055cac 223 let mut has_datespec = false;
e05b637c
DM
224
225 let mut event = CalendarEvent::default();
226
227 if i.starts_with(|c: char| char::is_ascii_alphabetic(&c)) {
228
229 match i {
230 "minutely" => {
231 return Ok(("", CalendarEvent {
232 second: vec![DateTimeValue::Single(0)],
233 ..Default::default()
234 }));
235 }
236 "hourly" => {
237 return Ok(("", CalendarEvent {
238 minute: vec![DateTimeValue::Single(0)],
239 second: vec![DateTimeValue::Single(0)],
240 ..Default::default()
241 }));
242 }
243 "daily" => {
244 return Ok(("", CalendarEvent {
245 hour: vec![DateTimeValue::Single(0)],
246 minute: vec![DateTimeValue::Single(0)],
247 second: vec![DateTimeValue::Single(0)],
248 ..Default::default()
249 }));
250 }
956295ce
TL
251 "weekly" => {
252 return Ok(("", CalendarEvent {
253 hour: vec![DateTimeValue::Single(0)],
254 minute: vec![DateTimeValue::Single(0)],
255 second: vec![DateTimeValue::Single(0)],
256 days: WeekDays::MONDAY,
257 ..Default::default()
258 }));
259 }
44055cac
DC
260 "monthly" => {
261 return Ok(("", CalendarEvent {
262 hour: vec![DateTimeValue::Single(0)],
263 minute: vec![DateTimeValue::Single(0)],
264 second: vec![DateTimeValue::Single(0)],
265 day: vec![DateTimeValue::Single(1)],
266 ..Default::default()
267 }));
268 }
269 "yearly" | "annually" => {
270 return Ok(("", CalendarEvent {
271 hour: vec![DateTimeValue::Single(0)],
272 minute: vec![DateTimeValue::Single(0)],
273 second: vec![DateTimeValue::Single(0)],
274 day: vec![DateTimeValue::Single(1)],
275 month: vec![DateTimeValue::Single(1)],
276 ..Default::default()
277 }));
278 }
279 "quarterly" => {
280 return Ok(("", CalendarEvent {
281 hour: vec![DateTimeValue::Single(0)],
282 minute: vec![DateTimeValue::Single(0)],
283 second: vec![DateTimeValue::Single(0)],
284 day: vec![DateTimeValue::Single(1)],
285 month: vec![
286 DateTimeValue::Single(1),
287 DateTimeValue::Single(4),
288 DateTimeValue::Single(7),
289 DateTimeValue::Single(10),
290 ],
291 ..Default::default()
292 }));
293 }
294 "semiannually" | "semi-annually" => {
295 return Ok(("", CalendarEvent {
296 hour: vec![DateTimeValue::Single(0)],
297 minute: vec![DateTimeValue::Single(0)],
298 second: vec![DateTimeValue::Single(0)],
299 day: vec![DateTimeValue::Single(1)],
300 month: vec![
301 DateTimeValue::Single(1),
302 DateTimeValue::Single(7),
303 ],
304 ..Default::default()
305 }));
e05b637c
DM
306 }
307 _ => { /* continue */ }
308 }
309
310 let (n, range_list) = context(
311 "weekday range list",
312 separated_nonempty_list(tag(","), parse_weekdays_range)
313 )(i)?;
314
315 has_dayspec = true;
316
317 i = space0(n)?.0;
318
319 for range in range_list { event.days.insert(range); }
320 }
321
44055cac
DC
322 if let (n, Some((year, month, day))) = opt(parse_date_spec)(i)? {
323 event.year = year;
324 event.month = month;
325 event.day = day;
326 has_datespec = true;
327 i = space0(n)?.0;
328 }
e05b637c
DM
329
330 if let (n, Some((hour, minute, second))) = opt(parse_time_spec)(i)? {
331 event.hour = hour;
332 event.minute = minute;
333 event.second = second;
334 has_timespec = true;
335 i = n;
336 } else {
337 event.hour = vec![DateTimeValue::Single(0)];
338 event.minute = vec![DateTimeValue::Single(0)];
339 event.second = vec![DateTimeValue::Single(0)];
340 }
341
342 if !(has_dayspec || has_timespec || has_datespec) {
343 return Err(parse_error(i, "date or time specification"));
344 }
345
346 Ok((i, event))
347}
348
349fn parse_time_unit(i: &str) -> IResult<&str, &str> {
350 let (n, text) = take_while1(|c: char| char::is_ascii_alphabetic(&c) || c == 'µ')(i)?;
351 if TIME_SPAN_UNITS.contains_key(&text) {
352 Ok((n, text))
353 } else {
354 Err(parse_error(text, "time unit"))
355 }
356}
357
358
359pub fn parse_time_span(i: &str) -> Result<TimeSpan, Error> {
547f0c97 360 parse_complete_line("time span", i, parse_time_span_incomplete)
e05b637c
DM
361}
362
363fn parse_time_span_incomplete(mut i: &str) -> IResult<&str, TimeSpan> {
364
365 let mut ts = TimeSpan::default();
366
367 loop {
368 i = space0(i)?.0;
369 if i.is_empty() { break; }
370 let (n, num) = parse_u64(i)?;
371 i = space0(n)?.0;
372
373 if let (n, Some(unit)) = opt(parse_time_unit)(i)? {
374 i = n;
375 match unit {
376 "seconds" | "second" | "sec" | "s" => {
377 ts.seconds += num;
378 }
379 "msec" | "ms" => {
380 ts.msec += num;
381 }
382 "usec" | "us" | "µs" => {
383 ts.usec += num;
384 }
385 "nsec" | "ns" => {
386 ts.nsec += num;
387 }
388 "minutes" | "minute" | "min" | "m" => {
389 ts.minutes += num;
390 }
391 "hours" | "hour" | "hr" | "h" => {
392 ts.hours += num;
393 }
394 "days" | "day" | "d" => {
395 ts.days += num;
396 }
397 "weeks" | "week" | "w" => {
398 ts.weeks += num;
399 }
400 "months" | "month" | "M" => {
401 ts.months += num;
402 }
403 "years" | "year" | "y" => {
404 ts.years += num;
405 }
406 _ => return Err(parse_error(unit, "internal error")),
407 }
408 } else {
409 ts.seconds += num;
410 }
411 }
412
413 Ok((i, ts))
414}