[pbs-devel] [PATCH proxmox-backup 10/10] tools/systemd/time: enable dates for calendarevents
Dominik Csapak
d.csapak at proxmox.com
Thu Sep 3 13:40:00 CEST 2020
this implements parsing and calculating calendarevents that have a
basic date component (year-mon-day) with the usual syntax options
(*, ranges, lists)
and some special events:
monthly
yearly/annually (like systemd)
quarterly
semiannually,semi-annually (like systemd)
includes some regression tests
the ~ syntax for days (the last x days of the month) is not yet
implemented
Signed-off-by: Dominik Csapak <d.csapak at proxmox.com>
---
src/tools/systemd/parse_time.rs | 77 +++++++++++++++++++++++++++++++--
src/tools/systemd/time.rs | 76 +++++++++++++++++++++++++++++++-
2 files changed, 147 insertions(+), 6 deletions(-)
diff --git a/src/tools/systemd/parse_time.rs b/src/tools/systemd/parse_time.rs
index 810a9883..578e253b 100644
--- a/src/tools/systemd/parse_time.rs
+++ b/src/tools/systemd/parse_time.rs
@@ -186,6 +186,25 @@ fn parse_time_spec(i: &str) -> IResult<&str, (Vec<DateTimeValue>, Vec<DateTimeVa
}
}
+fn parse_date_spec(i: &str) -> IResult<&str, (Vec<DateTimeValue>, Vec<DateTimeValue>, Vec<DateTimeValue>)> {
+
+ // TODO: implement ~ for days (man systemd.time)
+ if let Ok((i, (year, month, day))) = tuple((
+ parse_date_time_comp_list(2200), // the upper limit for systemd, stay compatible
+ preceded(tag("-"), parse_date_time_comp_list(13)),
+ preceded(tag("-"), parse_date_time_comp_list(32)),
+ ))(i) {
+ Ok((i, (year, month, day)))
+ } else if let Ok((i, (month, day))) = tuple((
+ parse_date_time_comp_list(13),
+ preceded(tag("-"), parse_date_time_comp_list(32)),
+ ))(i) {
+ Ok((i, (Vec::new(), month, day)))
+ } else {
+ Err(parse_error(i, "invalid date spec"))
+ }
+}
+
pub fn parse_calendar_event(i: &str) -> Result<CalendarEvent, Error> {
parse_complete_line("calendar event", i, parse_calendar_event_incomplete)
}
@@ -194,7 +213,7 @@ fn parse_calendar_event_incomplete(mut i: &str) -> IResult<&str, CalendarEvent>
let mut has_dayspec = false;
let mut has_timespec = false;
- let has_datespec = false;
+ let mut has_datespec = false;
let mut event = CalendarEvent::default();
@@ -231,8 +250,52 @@ fn parse_calendar_event_incomplete(mut i: &str) -> IResult<&str, CalendarEvent>
..Default::default()
}));
}
- "monthly" | "yearly" | "quarterly" | "semiannually" => {
- return Err(parse_error(i, "unimplemented date or time specification"));
+ "monthly" => {
+ return Ok(("", CalendarEvent {
+ hour: vec![DateTimeValue::Single(0)],
+ minute: vec![DateTimeValue::Single(0)],
+ second: vec![DateTimeValue::Single(0)],
+ day: vec![DateTimeValue::Single(1)],
+ ..Default::default()
+ }));
+ }
+ "yearly" | "annually" => {
+ return Ok(("", CalendarEvent {
+ hour: vec![DateTimeValue::Single(0)],
+ minute: vec![DateTimeValue::Single(0)],
+ second: vec![DateTimeValue::Single(0)],
+ day: vec![DateTimeValue::Single(1)],
+ month: vec![DateTimeValue::Single(1)],
+ ..Default::default()
+ }));
+ }
+ "quarterly" => {
+ return Ok(("", CalendarEvent {
+ hour: vec![DateTimeValue::Single(0)],
+ minute: vec![DateTimeValue::Single(0)],
+ second: vec![DateTimeValue::Single(0)],
+ day: vec![DateTimeValue::Single(1)],
+ month: vec![
+ DateTimeValue::Single(1),
+ DateTimeValue::Single(4),
+ DateTimeValue::Single(7),
+ DateTimeValue::Single(10),
+ ],
+ ..Default::default()
+ }));
+ }
+ "semiannually" | "semi-annually" => {
+ return Ok(("", CalendarEvent {
+ hour: vec![DateTimeValue::Single(0)],
+ minute: vec![DateTimeValue::Single(0)],
+ second: vec![DateTimeValue::Single(0)],
+ day: vec![DateTimeValue::Single(1)],
+ month: vec![
+ DateTimeValue::Single(1),
+ DateTimeValue::Single(7),
+ ],
+ ..Default::default()
+ }));
}
_ => { /* continue */ }
}
@@ -249,7 +312,13 @@ fn parse_calendar_event_incomplete(mut i: &str) -> IResult<&str, CalendarEvent>
for range in range_list { event.days.insert(range); }
}
- // todo: support date specs
+ if let (n, Some((year, month, day))) = opt(parse_date_spec)(i)? {
+ event.year = year;
+ event.month = month;
+ event.day = day;
+ has_datespec = true;
+ i = space0(n)?.0;
+ }
if let (n, Some((hour, minute, second))) = opt(parse_time_spec)(i)? {
event.hour = hour;
diff --git a/src/tools/systemd/time.rs b/src/tools/systemd/time.rs
index 6ce881e7..4993a30c 100644
--- a/src/tools/systemd/time.rs
+++ b/src/tools/systemd/time.rs
@@ -101,14 +101,12 @@ pub struct CalendarEvent {
pub minute: Vec<DateTimeValue>,
/// the hour(s) this event should trigger
pub hour: Vec<DateTimeValue>,
-/* FIXME: TODO
/// the day(s) in a month this event should trigger
pub day: Vec<DateTimeValue>,
/// the month(s) in a year this event should trigger
pub month: Vec<DateTimeValue>,
/// the years(s) this event should trigger
pub year: Vec<DateTimeValue>,
-*/
}
#[derive(Default)]
@@ -173,6 +171,51 @@ pub fn compute_next_event(
count += 1;
}
+ if !event.year.is_empty() && t.changes.contains(TMChanges::YEAR) {
+ let year = t.year();
+ if DateTimeValue::list_contains(&event.year, year) {
+ t.changes.remove(TMChanges::YEAR);
+ } else {
+ if let Some(n) = DateTimeValue::find_next(&event.year, year) {
+ t.add_years(n - year)?;
+ continue;
+ } else {
+ // if we have no valid year, we cannot find a correct timestamp
+ return Ok(None);
+ }
+ }
+ }
+
+ if !event.month.is_empty() && t.changes.contains(TMChanges::MON) {
+ let month = t.month();
+ if DateTimeValue::list_contains(&event.month, month) {
+ t.changes.remove(TMChanges::MON);
+ } else {
+ if let Some(n) = DateTimeValue::find_next(&event.month, month) {
+ t.add_months(n - month)?;
+ } else {
+ // if we could not find valid month, retry next year
+ t.add_years(1)?;
+ }
+ continue;
+ }
+ }
+
+ if !event.day.is_empty() && t.changes.contains(TMChanges::MDAY) {
+ let day = t.day();
+ if DateTimeValue::list_contains(&event.day, day) {
+ t.changes.remove(TMChanges::MDAY);
+ } else {
+ if let Some(n) = DateTimeValue::find_next(&event.day, day) {
+ t.add_days(n - day)?;
+ } else {
+ // if we could not find valid mday, retry next month
+ t.add_months(1)?;
+ }
+ continue;
+ }
+ }
+
if !all_days && t.changes.contains(TMChanges::WDAY) { // match day first
let day_num = t.day_num();
let day = WeekDays::from_bits(1<<day_num).unwrap();
@@ -294,6 +337,18 @@ mod test {
Ok(expect)
};
+ let test_never = |v: &'static str, last: i64| -> Result<(), Error> {
+ let event = match parse_calendar_event(v) {
+ Ok(event) => event,
+ Err(err) => bail!("parsing '{}' failed - {}", v, err),
+ };
+
+ match compute_next_event(&event, last, true)? {
+ None => Ok(()),
+ Some(next) => bail!("compute next for '{}' succeeded, but expected fail - result {}", v, next),
+ }
+ };
+
const MIN: i64 = 60;
const HOUR: i64 = 3600;
const DAY: i64 = 3600*24;
@@ -361,6 +416,23 @@ mod test {
n = test_value("1:0", n, THURSDAY_00_00 + i*DAY + HOUR)?;
}
+ // test date functionality
+
+ test_value("2020-07-31", 0, JUL_31_2020)?;
+ test_value("02-28", 0, (31+27)*DAY)?;
+ test_value("02-29", 0, 2*365*DAY + (31+28)*DAY)?; // 1972-02-29
+ test_value("1965/5-01-01", -1, THURSDAY_00_00)?;
+ test_value("2020-7..9-2/2", JUL_31_2020, JUL_31_2020 + 2*DAY)?;
+ test_value("2020,2021-12-31", JUL_31_2020, DEC_31_2020)?;
+
+ test_value("monthly", 0, 31*DAY)?;
+ test_value("quarterly", 0, (31+28+31)*DAY)?;
+ test_value("semiannually", 0, (31+28+31+30+31+30)*DAY)?;
+ test_value("yearly", 0, (365)*DAY)?;
+
+ test_never("2021-02-29", 0)?;
+ test_never("02-30", 0)?;
+
Ok(())
}
--
2.20.1
More information about the pbs-devel
mailing list