[pbs-devel] [PATCH proxmox 08/14] proxmox-time: move CalendarEvent into calendar_events.rs
Dominik Csapak
d.csapak at proxmox.com
Tue Nov 30 13:12:02 CET 2021
and all relevant parsing functions as well
Signed-off-by: Dominik Csapak <d.csapak at proxmox.com>
---
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<DateTimeValue>, // todo: support float values
+ /// the minute(s) this event should trigger
+ pub(crate) minute: Vec<DateTimeValue>,
+ /// the hour(s) this event should trigger
+ pub(crate) hour: Vec<DateTimeValue>,
+ /// the day(s) in a month this event should trigger
+ pub(crate) day: Vec<DateTimeValue>,
+ /// the month(s) in a year this event should trigger
+ pub(crate) month: Vec<DateTimeValue>,
+ /// the years(s) this event should trigger
+ pub(crate) year: Vec<DateTimeValue>,
+}
+
+/// 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<Option<i64>, 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<<day_num).unwrap();
+ if !event.days.contains(day) {
+ if let Some(n) = ((day_num+1)..7)
+ .find(|d| event.days.contains(WeekDays::from_bits(1<<d).unwrap()))
+ {
+ // try next day
+ t.add_days((n - day_num).try_into()?)?;
+ } else {
+ // try next week
+ t.add_days((7 - day_num).try_into()?)?;
+ }
+ continue;
+ }
+ }
+
+ // this day
+ if !event.hour.is_empty() {
+ let hour = t.hour().try_into()?;
+ if !DateTimeValue::list_contains(&event.hour, hour) {
+ if let Some(n) = DateTimeValue::find_next(&event.hour, hour) {
+ // test next hour
+ t.set_time(n.try_into()?, 0, 0)?;
+ } else {
+ // test next day
+ t.add_days(1)?;
+ }
+ continue;
+ }
+ }
+
+ // this hour
+ if !event.minute.is_empty() {
+ let minute = t.min().try_into()?;
+ if !DateTimeValue::list_contains(&event.minute, minute) {
+ if let Some(n) = DateTimeValue::find_next(&event.minute, minute) {
+ // test next minute
+ t.set_min_sec(n.try_into()?, 0)?;
+ } else {
+ // test next hour
+ t.set_time(t.hour() + 1, 0, 0)?;
+ }
+ continue;
+ }
+ }
+
+ // this minute
+ if !event.second.is_empty() {
+ let second = t.sec().try_into()?;
+ if !DateTimeValue::list_contains(&event.second, second) {
+ if let Some(n) = DateTimeValue::find_next(&event.second, second) {
+ // test next second
+ t.set_sec(n.try_into()?)?;
+ } else {
+ // test next min
+ t.set_min_sec(t.min() + 1, 0)?;
+ }
+ continue;
+ }
+ }
+
+ let next = t.into_epoch()?;
+ return Ok(Some(next))
+ }
+}
+
+/// Parse a [CalendarEvent]
+pub fn parse_calendar_event(i: &str) -> Result<CalendarEvent, Error> {
+ 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<DateTimeValue>,
+ minute: Vec<DateTimeValue>,
+ second: Vec<DateTimeValue>,
+}
+
+struct DateSpec {
+ year: Vec<DateTimeValue>,
+ month: Vec<DateTimeValue>,
+ day: Vec<DateTimeValue>,
+}
+
+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<DateTimeValue>> {
+ 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<DateTimeValue>,
- minute: Vec<DateTimeValue>,
- second: Vec<DateTimeValue>,
-}
-
-struct DateSpec {
- year: Vec<DateTimeValue>,
- month: Vec<DateTimeValue>,
- day: Vec<DateTimeValue>,
-}
-
-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<DateTimeValue>> {
- 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<CalendarEvent, Error> {
- 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<DateTimeValue>, // todo: support float values
- /// the minute(s) this event should trigger
- pub(crate) minute: Vec<DateTimeValue>,
- /// the hour(s) this event should trigger
- pub(crate) hour: Vec<DateTimeValue>,
- /// the day(s) in a month this event should trigger
- pub(crate) day: Vec<DateTimeValue>,
- /// the month(s) in a year this event should trigger
- pub(crate) month: Vec<DateTimeValue>,
- /// the years(s) this event should trigger
- pub(crate) year: Vec<DateTimeValue>,
-}
+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<Option<i64>, 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<<day_num).unwrap();
- if !event.days.contains(day) {
- if let Some(n) = ((day_num+1)..7)
- .find(|d| event.days.contains(WeekDays::from_bits(1<<d).unwrap()))
- {
- // try next day
- t.add_days((n - day_num).try_into()?)?;
- } else {
- // try next week
- t.add_days((7 - day_num).try_into()?)?;
- }
- continue;
- }
- }
-
- // this day
- if !event.hour.is_empty() {
- let hour = t.hour().try_into()?;
- if !DateTimeValue::list_contains(&event.hour, hour) {
- if let Some(n) = DateTimeValue::find_next(&event.hour, hour) {
- // test next hour
- t.set_time(n.try_into()?, 0, 0)?;
- } else {
- // test next day
- t.add_days(1)?;
- }
- continue;
- }
- }
-
- // this hour
- if !event.minute.is_empty() {
- let minute = t.min().try_into()?;
- if !DateTimeValue::list_contains(&event.minute, minute) {
- if let Some(n) = DateTimeValue::find_next(&event.minute, minute) {
- // test next minute
- t.set_min_sec(n.try_into()?, 0)?;
- } else {
- // test next hour
- t.set_time(t.hour() + 1, 0, 0)?;
- }
- continue;
- }
- }
-
- // this minute
- if !event.second.is_empty() {
- let second = t.sec().try_into()?;
- if !DateTimeValue::list_contains(&event.second, second) {
- if let Some(n) = DateTimeValue::find_next(&event.second, second) {
- // test next second
- t.set_sec(n.try_into()?)?;
- } else {
- // test next min
- t.set_min_sec(t.min() + 1, 0)?;
- }
- continue;
- }
- }
-
- let next = t.into_epoch()?;
- return Ok(Some(next))
- }
-}
-
#[cfg(test)]
mod test {
--
2.30.2
More information about the pbs-devel
mailing list