From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 21AD7828F1 for ; Tue, 30 Nov 2021 13:12:44 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id BC10326A47 for ; Tue, 30 Nov 2021 13:12:22 +0100 (CET) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [94.136.29.106]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS id 73006269C1 for ; Tue, 30 Nov 2021 13:12:14 +0100 (CET) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 4A7AE44689 for ; Tue, 30 Nov 2021 13:12:14 +0100 (CET) From: Dominik Csapak To: pbs-devel@lists.proxmox.com Date: Tue, 30 Nov 2021 13:12:02 +0100 Message-Id: <20211130121209.216846-9-d.csapak@proxmox.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20211130121209.216846-1-d.csapak@proxmox.com> References: <20211130121209.216846-1-d.csapak@proxmox.com> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.182 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [event.day, time.rs, lib.rs] Subject: [pbs-devel] [PATCH proxmox 08/14] proxmox-time: move CalendarEvent into calendar_events.rs X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 30 Nov 2021 12:12:44 -0000 and all relevant parsing functions as well Signed-off-by: Dominik Csapak --- proxmox-time/src/calendar_event.rs | 429 +++++++++++++++++++++++++++++ proxmox-time/src/lib.rs | 3 + proxmox-time/src/parse_time.rs | 220 --------------- proxmox-time/src/time.rs | 160 +---------- 4 files changed, 433 insertions(+), 379 deletions(-) create mode 100644 proxmox-time/src/calendar_event.rs diff --git a/proxmox-time/src/calendar_event.rs b/proxmox-time/src/calendar_event.rs new file mode 100644 index 0000000..1c21a84 --- /dev/null +++ b/proxmox-time/src/calendar_event.rs @@ -0,0 +1,429 @@ +use std::convert::TryInto; + +use anyhow::Error; +use nom::{ + bytes::complete::tag, + character::complete::space0, + combinator::opt, + error::context, + multi::separated_nonempty_list, + sequence::{preceded, terminated, tuple}, +}; + +use crate::date_time_value::DateTimeValue; +use crate::parse_helpers::{parse_complete_line, parse_error, parse_time_comp, IResult}; +use crate::{parse_weekdays_range, TmEditor, WeekDays}; + +/// Calendar events may be used to refer to one or more points in time in a +/// single expression. They are designed after the systemd.time Calendar Events +/// specification, but are not guaranteed to be 100% compatible. +#[derive(Default, Clone, Debug)] +pub struct CalendarEvent { + /// the days in a week this event should trigger + pub(crate) days: WeekDays, + /// the second(s) this event should trigger + pub(crate) second: Vec, // todo: support float values + /// the minute(s) this event should trigger + pub(crate) minute: Vec, + /// the hour(s) this event should trigger + pub(crate) hour: Vec, + /// the day(s) in a month this event should trigger + pub(crate) day: Vec, + /// the month(s) in a year this event should trigger + pub(crate) month: Vec, + /// the years(s) this event should trigger + pub(crate) year: Vec, +} + +/// Verify the format of the [CalendarEvent] +pub fn verify_calendar_event(i: &str) -> Result<(), Error> { + parse_calendar_event(i)?; + Ok(()) +} + +/// Compute the next event +pub fn compute_next_event( + event: &CalendarEvent, + last: i64, + utc: bool, +) -> Result, Error> { + + let last = last + 1; // at least one second later + + let all_days = event.days.is_empty() || event.days.is_all(); + + let mut t = TmEditor::with_epoch(last, utc)?; + + let mut count = 0; + + loop { + // cancel after 1000 loops + if count > 1000 { + return Ok(None); + } else { + count += 1; + } + + if !event.year.is_empty() { + let year: u32 = t.year().try_into()?; + if !DateTimeValue::list_contains(&event.year, year) { + if let Some(n) = DateTimeValue::find_next(&event.year, year) { + t.add_years((n - year).try_into()?)?; + continue; + } else { + // if we have no valid year, we cannot find a correct timestamp + return Ok(None); + } + } + } + + if !event.month.is_empty() { + let month: u32 = t.month().try_into()?; + if !DateTimeValue::list_contains(&event.month, month) { + if let Some(n) = DateTimeValue::find_next(&event.month, month) { + t.add_months((n - month).try_into()?)?; + } else { + // if we could not find valid month, retry next year + t.add_years(1)?; + } + continue; + } + } + + if !event.day.is_empty() { + let day: u32 = t.day().try_into()?; + if !DateTimeValue::list_contains(&event.day, day) { + if let Some(n) = DateTimeValue::find_next(&event.day, day) { + t.add_days((n - day).try_into()?)?; + } else { + // if we could not find valid mday, retry next month + t.add_months(1)?; + } + continue; + } + } + + if !all_days { // match day first + let day_num: u32 = t.day_num().try_into()?; + let day = WeekDays::from_bits(1< Result { + parse_complete_line("calendar event", i, parse_calendar_event_incomplete) +} + +fn parse_calendar_event_incomplete(mut i: &str) -> IResult<&str, CalendarEvent> { + let mut has_dayspec = false; + let mut has_timespec = false; + let mut has_datespec = false; + + let mut event = CalendarEvent::default(); + + if i.starts_with(|c: char| char::is_ascii_alphabetic(&c)) { + match i { + "minutely" => { + return Ok(( + "", + CalendarEvent { + second: vec![DateTimeValue::Single(0)], + ..Default::default() + }, + )); + } + "hourly" => { + return Ok(( + "", + CalendarEvent { + minute: vec![DateTimeValue::Single(0)], + second: vec![DateTimeValue::Single(0)], + ..Default::default() + }, + )); + } + "daily" => { + return Ok(( + "", + CalendarEvent { + hour: vec![DateTimeValue::Single(0)], + minute: vec![DateTimeValue::Single(0)], + second: vec![DateTimeValue::Single(0)], + ..Default::default() + }, + )); + } + "weekly" => { + return Ok(( + "", + CalendarEvent { + hour: vec![DateTimeValue::Single(0)], + minute: vec![DateTimeValue::Single(0)], + second: vec![DateTimeValue::Single(0)], + days: WeekDays::MONDAY, + ..Default::default() + }, + )); + } + "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 */ } + } + + let (n, range_list) = context( + "weekday range list", + separated_nonempty_list(tag(","), parse_weekdays_range), + )(i)?; + + has_dayspec = true; + + i = space0(n)?.0; + + for range in range_list { + event.days.insert(range); + } + } + + if let (n, Some(date)) = opt(parse_date_spec)(i)? { + event.year = date.year; + event.month = date.month; + event.day = date.day; + has_datespec = true; + i = space0(n)?.0; + } + + if let (n, Some(time)) = opt(parse_time_spec)(i)? { + event.hour = time.hour; + event.minute = time.minute; + event.second = time.second; + has_timespec = true; + i = n; + } else { + event.hour = vec![DateTimeValue::Single(0)]; + event.minute = vec![DateTimeValue::Single(0)]; + event.second = vec![DateTimeValue::Single(0)]; + } + + if !(has_dayspec || has_timespec || has_datespec) { + return Err(parse_error(i, "date or time specification")); + } + + Ok((i, event)) +} + +struct TimeSpec { + hour: Vec, + minute: Vec, + second: Vec, +} + +struct DateSpec { + year: Vec, + month: Vec, + day: Vec, +} + +fn parse_date_time_comp(max: usize) -> impl Fn(&str) -> IResult<&str, DateTimeValue> { + move |i: &str| { + let (i, value) = parse_time_comp(max)(i)?; + + if let (i, Some(end)) = opt(preceded(tag(".."), parse_time_comp(max)))(i)? { + if value > end { + return Err(parse_error(i, "range start is bigger than end")); + } + if let Some(time) = i.strip_prefix('/') { + let (time, repeat) = parse_time_comp(max)(time)?; + return Ok((time, DateTimeValue::Repeated(value, repeat, Some(end)))); + } + return Ok((i, DateTimeValue::Range(value, end))); + } + + if let Some(time) = i.strip_prefix('/') { + let (time, repeat) = parse_time_comp(max)(time)?; + Ok((time, DateTimeValue::Repeated(value, repeat, None))) + } else { + Ok((i, DateTimeValue::Single(value))) + } + } +} + +fn parse_date_time_comp_list( + start: u32, + max: usize, +) -> impl Fn(&str) -> IResult<&str, Vec> { + move |i: &str| { + if let Some(rest) = i.strip_prefix('*') { + if let Some(time) = rest.strip_prefix('/') { + let (n, repeat) = parse_time_comp(max)(time)?; + if repeat > 0 { + return Ok((n, vec![DateTimeValue::Repeated(start, repeat, None)])); + } + } + return Ok((rest, Vec::new())); + } + + separated_nonempty_list(tag(","), parse_date_time_comp(max))(i) + } +} + +fn parse_time_spec(i: &str) -> IResult<&str, TimeSpec> { + let (i, (opt_hour, minute, opt_second)) = tuple(( + opt(terminated(parse_date_time_comp_list(0, 24), tag(":"))), + parse_date_time_comp_list(0, 60), + opt(preceded(tag(":"), parse_date_time_comp_list(0, 60))), + ))(i)?; + + let hour = opt_hour.unwrap_or_else(Vec::new); + let second = opt_second.unwrap_or_else(|| vec![DateTimeValue::Single(0)]); + + Ok(( + i, + TimeSpec { + hour, + minute, + second, + }, + )) +} + +fn parse_date_spec(i: &str) -> IResult<&str, DateSpec> { + // TODO: implement ~ for days (man systemd.time) + if let Ok((i, (year, month, day))) = tuple(( + parse_date_time_comp_list(0, 2200), // the upper limit for systemd, stay compatible + preceded(tag("-"), parse_date_time_comp_list(1, 13)), + preceded(tag("-"), parse_date_time_comp_list(1, 32)), + ))(i) + { + Ok((i, DateSpec { year, month, day })) + } else if let Ok((i, (month, day))) = tuple(( + parse_date_time_comp_list(1, 13), + preceded(tag("-"), parse_date_time_comp_list(1, 32)), + ))(i) + { + Ok(( + i, + DateSpec { + year: Vec::new(), + month, + day, + }, + )) + } else { + Err(parse_error(i, "invalid date spec")) + } +} diff --git a/proxmox-time/src/lib.rs b/proxmox-time/src/lib.rs index 3d888a2..0436628 100644 --- a/proxmox-time/src/lib.rs +++ b/proxmox-time/src/lib.rs @@ -17,6 +17,9 @@ pub(crate) mod parse_helpers; pub(crate) mod date_time_value; +mod calendar_event; +pub use calendar_event::*; + mod week_days; pub use week_days::*; diff --git a/proxmox-time/src/parse_time.rs b/proxmox-time/src/parse_time.rs index adaf7ac..51c0486 100644 --- a/proxmox-time/src/parse_time.rs +++ b/proxmox-time/src/parse_time.rs @@ -88,226 +88,6 @@ lazy_static! { }; } -struct TimeSpec { - hour: Vec, - minute: Vec, - second: Vec, -} - -struct DateSpec { - year: Vec, - month: Vec, - day: Vec, -} - -fn parse_date_time_comp(max: usize) -> impl Fn(&str) -> IResult<&str, DateTimeValue> { - move |i: &str| { - let (i, value) = parse_time_comp(max)(i)?; - - if let (i, Some(end)) = opt(preceded(tag(".."), parse_time_comp(max)))(i)? { - if value > end { - return Err(parse_error(i, "range start is bigger than end")); - } - if let Some(time) = i.strip_prefix('/') { - let (time, repeat) = parse_time_comp(max)(time)?; - return Ok((time, DateTimeValue::Repeated(value, repeat, Some(end)))); - } - return Ok((i, DateTimeValue::Range(value, end))); - } - - if let Some(time) = i.strip_prefix('/') { - let (time, repeat) = parse_time_comp(max)(time)?; - Ok((time, DateTimeValue::Repeated(value, repeat, None))) - } else { - Ok((i, DateTimeValue::Single(value))) - } - } -} - -fn parse_date_time_comp_list(start: u32, max: usize) -> impl Fn(&str) -> IResult<&str, Vec> { - move |i: &str| { - if let Some(rest) = i.strip_prefix('*') { - if let Some(time) = rest.strip_prefix('/') { - let (n, repeat) = parse_time_comp(max)(time)?; - if repeat > 0 { - return Ok((n, vec![DateTimeValue::Repeated(start, repeat, None)])); - } - } - return Ok((rest, Vec::new())); - } - - separated_nonempty_list(tag(","), parse_date_time_comp(max))(i) - } -} - -fn parse_time_spec(i: &str) -> IResult<&str, TimeSpec> { - - let (i, (opt_hour, minute, opt_second)) = tuple(( - opt(terminated(parse_date_time_comp_list(0, 24), tag(":"))), - parse_date_time_comp_list(0, 60), - opt(preceded(tag(":"), parse_date_time_comp_list(0, 60))), - ))(i)?; - - let hour = opt_hour.unwrap_or_else(Vec::new); - let second = opt_second.unwrap_or_else(|| vec![DateTimeValue::Single(0)]); - - Ok((i, TimeSpec { hour, minute, second })) -} - -fn parse_date_spec(i: &str) -> IResult<&str, DateSpec> { - - // TODO: implement ~ for days (man systemd.time) - if let Ok((i, (year, month, day))) = tuple(( - parse_date_time_comp_list(0, 2200), // the upper limit for systemd, stay compatible - preceded(tag("-"), parse_date_time_comp_list(1, 13)), - preceded(tag("-"), parse_date_time_comp_list(1, 32)), - ))(i) { - Ok((i, DateSpec { year, month, day })) - } else if let Ok((i, (month, day))) = tuple(( - parse_date_time_comp_list(1, 13), - preceded(tag("-"), parse_date_time_comp_list(1, 32)), - ))(i) { - Ok((i, DateSpec { year: Vec::new(), month, day })) - } else { - Err(parse_error(i, "invalid date spec")) - } -} - -/// Parse a [CalendarEvent] -pub fn parse_calendar_event(i: &str) -> Result { - parse_complete_line("calendar event", i, parse_calendar_event_incomplete) -} - -fn parse_calendar_event_incomplete(mut i: &str) -> IResult<&str, CalendarEvent> { - - let mut has_dayspec = false; - let mut has_timespec = false; - let mut has_datespec = false; - - let mut event = CalendarEvent::default(); - - if i.starts_with(|c: char| char::is_ascii_alphabetic(&c)) { - - match i { - "minutely" => { - return Ok(("", CalendarEvent { - second: vec![DateTimeValue::Single(0)], - ..Default::default() - })); - } - "hourly" => { - return Ok(("", CalendarEvent { - minute: vec![DateTimeValue::Single(0)], - second: vec![DateTimeValue::Single(0)], - ..Default::default() - })); - } - "daily" => { - return Ok(("", CalendarEvent { - hour: vec![DateTimeValue::Single(0)], - minute: vec![DateTimeValue::Single(0)], - second: vec![DateTimeValue::Single(0)], - ..Default::default() - })); - } - "weekly" => { - return Ok(("", CalendarEvent { - hour: vec![DateTimeValue::Single(0)], - minute: vec![DateTimeValue::Single(0)], - second: vec![DateTimeValue::Single(0)], - days: WeekDays::MONDAY, - ..Default::default() - })); - } - "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 */ } - } - - let (n, range_list) = context( - "weekday range list", - separated_nonempty_list(tag(","), parse_weekdays_range) - )(i)?; - - has_dayspec = true; - - i = space0(n)?.0; - - for range in range_list { event.days.insert(range); } - } - - if let (n, Some(date)) = opt(parse_date_spec)(i)? { - event.year = date.year; - event.month = date.month; - event.day = date.day; - has_datespec = true; - i = space0(n)?.0; - } - - if let (n, Some(time)) = opt(parse_time_spec)(i)? { - event.hour = time.hour; - event.minute = time.minute; - event.second = time.second; - has_timespec = true; - i = n; - } else { - event.hour = vec![DateTimeValue::Single(0)]; - event.minute = vec![DateTimeValue::Single(0)]; - event.second = vec![DateTimeValue::Single(0)]; - } - - if !(has_dayspec || has_timespec || has_datespec) { - return Err(parse_error(i, "date or time specification")); - } - - Ok((i, event)) -} - fn parse_time_unit(i: &str) -> IResult<&str, &str> { let (n, text) = take_while1(|c: char| char::is_ascii_alphabetic(&c) || c == 'ยต')(i)?; if TIME_SPAN_UNITS.contains_key(&text) { diff --git a/proxmox-time/src/time.rs b/proxmox-time/src/time.rs index 7b9625f..1c45a09 100644 --- a/proxmox-time/src/time.rs +++ b/proxmox-time/src/time.rs @@ -1,33 +1,10 @@ -use std::convert::TryInto; - use anyhow::Error; use crate::date_time_value::DateTimeValue; use crate::TmEditor; use crate::WeekDays; -use crate::{parse_calendar_event, parse_time_span}; - -/// Calendar events may be used to refer to one or more points in time in a -/// single expression. They are designed after the systemd.time Calendar Events -/// specification, but are not guaranteed to be 100% compatible. -#[derive(Default, Clone, Debug)] -pub struct CalendarEvent { - /// the days in a week this event should trigger - pub(crate) days: WeekDays, - /// the second(s) this event should trigger - pub(crate) second: Vec, // todo: support float values - /// the minute(s) this event should trigger - pub(crate) minute: Vec, - /// the hour(s) this event should trigger - pub(crate) hour: Vec, - /// the day(s) in a month this event should trigger - pub(crate) day: Vec, - /// the month(s) in a year this event should trigger - pub(crate) month: Vec, - /// the years(s) this event should trigger - pub(crate) year: Vec, -} +use crate::{compute_next_event, parse_calendar_event, parse_time_span}; /// A time spans defines a time duration #[derive(Default, Clone, Debug)] @@ -148,141 +125,6 @@ pub fn verify_time_span(i: &str) -> Result<(), Error> { Ok(()) } -/// Verify the format of the [CalendarEvent] -pub fn verify_calendar_event(i: &str) -> Result<(), Error> { - parse_calendar_event(i)?; - Ok(()) -} - -/// Compute the next event -pub fn compute_next_event( - event: &CalendarEvent, - last: i64, - utc: bool, -) -> Result, Error> { - - let last = last + 1; // at least one second later - - let all_days = event.days.is_empty() || event.days.is_all(); - - let mut t = TmEditor::with_epoch(last, utc)?; - - let mut count = 0; - - loop { - // cancel after 1000 loops - if count > 1000 { - return Ok(None); - } else { - count += 1; - } - - if !event.year.is_empty() { - let year: u32 = t.year().try_into()?; - if !DateTimeValue::list_contains(&event.year, year) { - if let Some(n) = DateTimeValue::find_next(&event.year, year) { - t.add_years((n - year).try_into()?)?; - continue; - } else { - // if we have no valid year, we cannot find a correct timestamp - return Ok(None); - } - } - } - - if !event.month.is_empty() { - let month: u32 = t.month().try_into()?; - if !DateTimeValue::list_contains(&event.month, month) { - if let Some(n) = DateTimeValue::find_next(&event.month, month) { - t.add_months((n - month).try_into()?)?; - } else { - // if we could not find valid month, retry next year - t.add_years(1)?; - } - continue; - } - } - - if !event.day.is_empty() { - let day: u32 = t.day().try_into()?; - if !DateTimeValue::list_contains(&event.day, day) { - if let Some(n) = DateTimeValue::find_next(&event.day, day) { - t.add_days((n - day).try_into()?)?; - } else { - // if we could not find valid mday, retry next month - t.add_months(1)?; - } - continue; - } - } - - if !all_days { // match day first - let day_num: u32 = t.day_num().try_into()?; - let day = WeekDays::from_bits(1<