From 017495113d1f33746d0a02554f48bc6fadcda794 Mon Sep 17 00:00:00 2001 From: Ian Roddis Date: Fri, 3 Dec 2021 16:42:34 -0400 Subject: [PATCH] Adding schedule --- README.md | 27 +++++++------ src/calendar.rs | 101 ++++++++++++++++++++++++++++++++++++++---------- src/main.rs | 1 + src/schedule.rs | 87 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 181 insertions(+), 35 deletions(-) create mode 100644 src/schedule.rs diff --git a/README.md b/README.md index b7a1403..7b39e3d 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,18 @@ Introduction Easier job scheduling, supporting: - Schedules in a particular timezone -- Calendars +- Calendars (defining which days are in scope) +- Schedules (defining spans of hours in a day) +- Jobs + - Given a calendar + - And a schedule + - Define a pattern of when jobs should run + - e.g. + - minute = "0,15,30,35", hour = "123" + - minute = "0", hour = "*" + - minute = "5", frequency = "15" // Start at 5 past the hour, and run every 15 minutes from then + +A job will Authorization ============= @@ -71,7 +82,7 @@ A JSON definition for a calendar looks as follows: ```json { "description": "Long description", - "dow_list": ["M","T","W","R","F"], + "dow_list": ["Mon","Tue","Wed","Thu","Fri", "Sat", "Sun"], "public": true, "exclude": [ { @@ -96,18 +107,6 @@ A JSON definition for a calendar looks as follows: } ``` -Letter codes are: - -| Day of Week | Letter | -|-------------|--------| -| Monday | M | -| Tuesday | T | -| Wednesday | W | -| Thursday | **R** | -| Friday | F | -| Saturday | S | -| Sunday | U | - Inheritance ----------- diff --git a/src/calendar.rs b/src/calendar.rs index a8e3481..a545c68 100644 --- a/src/calendar.rs +++ b/src/calendar.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use std::collections::HashSet; /* - There are a few gaping holes here, and some deficiencies: + There are a few gaping holes here, and some functional deficiencies: - Holidays are only calculated within a year. If a holiday in a prior year is bumped to the next year, it won't be considered. @@ -15,34 +15,50 @@ use std::collections::HashSet; */ #[derive(Copy, Clone, Serialize, Deserialize, Debug)] -enum AdjustmentPolicy { +pub enum AdjustmentPolicy { Prev, Next, Closest, NoAdjustment, } +impl Default for AdjustmentPolicy { + fn default() -> AdjustmentPolicy { + AdjustmentPolicy::NoAdjustment + } +} + #[derive(Clone, Serialize, Deserialize, Debug)] -enum DateSpec { +#[serde(tag = "type")] +pub enum DateSpec { SpecificDate { date: NaiveDate, + #[serde(default)] description: String, }, - MonthDay { + DayOfMonth { month: chrono::Month, day: u32, + #[serde(default)] observed: AdjustmentPolicy, + #[serde(default)] description: String, + #[serde(default)] since: Option, + #[serde(default)] until: Option, }, - MonthNthDay { + NthDayOccurance { month: Month, dow: Weekday, offset: i8, + #[serde(default)] observed: AdjustmentPolicy, + #[serde(default)] description: String, + #[serde(default)] since: Option, + #[serde(default)] until: Option, }, } @@ -54,7 +70,7 @@ impl DateSpec { match self { SpecificDate { date, .. } => Some((*date, AdjustmentPolicy::NoAdjustment)), - MonthDay { + DayOfMonth { month, day, since, @@ -73,7 +89,7 @@ impl DateSpec { )) } } - MonthNthDay { + NthDayOccurance { month, dow, offset, @@ -121,13 +137,23 @@ impl DateSpec { } } +fn default_dow_set() -> HashSet { + use Weekday::*; + HashSet::from([Mon, Tue, Wed, Thu, Fri]) +} + #[derive(Clone, Serialize, Deserialize, Default, Debug)] -struct Calendar { - description: String, - dow: HashSet, - public: bool, - excluded: Vec, - inherits: Vec, +pub struct Calendar { + #[serde(default)] + pub description: String, + #[serde(default = "default_dow_set")] + pub dow: HashSet, + #[serde(default)] + pub public: bool, + #[serde(default)] + pub exclude: Vec, + #[serde(default)] + pub inherits: Vec, } impl Calendar { @@ -200,7 +226,7 @@ impl Calendar { /// Get the set of all holidays in a given year pub fn get_holidays(&self, date: NaiveDate) -> HashSet { let holidays: Vec<(NaiveDate, AdjustmentPolicy)> = self - .excluded + .exclude .iter() .map(|x| x.resolve(date.year())) .filter(|x| x.is_some()) @@ -248,8 +274,8 @@ mod tests { description: "Test description".to_owned(), dow: HashSet::from([Mon, Tue, Wed, Thu, Fri]), public: false, - excluded: vec![ - DateSpec::MonthDay { + exclude: vec![ + DateSpec::DayOfMonth { month: December, day: 25u32, observed: AdjustmentPolicy::Next, @@ -257,7 +283,7 @@ mod tests { since: None, until: None, }, - DateSpec::MonthDay { + DateSpec::DayOfMonth { month: December, day: 26u32, observed: AdjustmentPolicy::Next, @@ -287,8 +313,8 @@ mod tests { description: "Test description".to_owned(), dow: HashSet::from([Mon, Tue, Wed, Thu, Fri]), public: false, - excluded: vec![ - DateSpec::MonthDay { + exclude: vec![ + DateSpec::DayOfMonth { month: December, day: 25u32, observed: AdjustmentPolicy::Next, @@ -296,7 +322,7 @@ mod tests { since: None, until: None, }, - DateSpec::MonthDay { + DateSpec::DayOfMonth { month: December, day: 26u32, observed: AdjustmentPolicy::Next, @@ -304,7 +330,7 @@ mod tests { since: None, until: None, }, - DateSpec::MonthDay { + DateSpec::DayOfMonth { month: January, day: 1u32, observed: AdjustmentPolicy::Next, @@ -322,4 +348,37 @@ mod tests { ); assert_eq!(myrange.len(), 20); } + + #[test] + fn test_deserialization() { + let data = r#" + { + "description": "Long description", + "dow": ["Mon","Tue","Wed","Thu","Fri", "Sat", "Sun"], + "public": true, + "exclude": [ + { + "type": "SpecificDate", + "date": "2021-01-01", + "description": "New Years Day" + }, + { + "type": "DayOfMonth", + "month": "January", + "day": 17, + "observed": "Closest", + "description": "Martin Luther King Day" + }, + { + "type": "NthDayOccurance", + "month": "January", + "dow": "Mon", + "offset": -1, + "observed": "Closest", + "description": "Final Monday Margarita Day" + } + ] + }"#; + let cal: Calendar = serde_json::from_str(data).unwrap(); + } } diff --git a/src/main.rs b/src/main.rs index b62b69e..b0914e7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ pub mod calendar; +pub mod schedule; fn main() { println!("Hello, world"); diff --git a/src/schedule.rs b/src/schedule.rs new file mode 100644 index 0000000..a972126 --- /dev/null +++ b/src/schedule.rs @@ -0,0 +1,87 @@ +use chrono::naive::{NaiveDate, NaiveTime}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Serialize, Deserialize, Debug)] +struct TimeSpan { + start: NaiveTime, + end: NaiveTime, + #[serde(default)] + description: String, +} + +impl TimeSpan { + fn intersection(&self, other: &TimeSpan) -> Option { + if self.end < other.start { + None + } else { + Some(TimeSpan { + start: if self.start < other.start { + other.start + } else { + self.start + }, + end: if self.end < other.end { + self.end + } else { + other.end + }, + description: format!( + "Intersection of {} and {}", + self.description, other.description + ), + }) + } + } +} + +impl PartialEq for TimeSpan { + fn eq(&self, other: &TimeSpan) -> bool { + self.start == other.start && self.end == other.end + } +} + +#[derive(Clone, Serialize, Deserialize, Debug)] +struct ScheduleOverride { + start_date: NaiveDate, + end_date: Option, + schedule: Vec, + + #[serde(default)] + description: String, +} + +#[derive(Clone, Serialize, Deserialize, Debug)] +struct Schedule { + default: Vec, + overrides: Vec, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_timespan_intersections() { + { + let a = TimeSpan { + start: NaiveTime::from_hms(0, 0, 0), + end: NaiveTime::from_hms(2, 0, 0), + description: "".to_owned(), + }; + + let b = TimeSpan { + start: NaiveTime::from_hms(1, 0, 0), + end: NaiveTime::from_hms(3, 0, 0), + description: "".to_owned(), + }; + + let c = TimeSpan { + start: NaiveTime::from_hms(1, 0, 0), + end: NaiveTime::from_hms(2, 0, 0), + description: "".to_owned(), + }; + + assert_eq!(a.intersection(&b), Some(c)); + } + } +}