Adding schedule

This commit is contained in:
Ian Roddis
2021-12-03 16:42:34 -04:00
parent e5ea51ee26
commit 017495113d
4 changed files with 181 additions and 35 deletions

View File

@@ -4,7 +4,18 @@ Introduction
Easier job scheduling, supporting: Easier job scheduling, supporting:
- Schedules in a particular timezone - 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 Authorization
============= =============
@@ -71,7 +82,7 @@ A JSON definition for a calendar looks as follows:
```json ```json
{ {
"description": "Long description", "description": "Long description",
"dow_list": ["M","T","W","R","F"], "dow_list": ["Mon","Tue","Wed","Thu","Fri", "Sat", "Sun"],
"public": true, "public": true,
"exclude": [ "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 Inheritance
----------- -----------

View File

@@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
use std::collections::HashSet; 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 - 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. 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)] #[derive(Copy, Clone, Serialize, Deserialize, Debug)]
enum AdjustmentPolicy { pub enum AdjustmentPolicy {
Prev, Prev,
Next, Next,
Closest, Closest,
NoAdjustment, NoAdjustment,
} }
impl Default for AdjustmentPolicy {
fn default() -> AdjustmentPolicy {
AdjustmentPolicy::NoAdjustment
}
}
#[derive(Clone, Serialize, Deserialize, Debug)] #[derive(Clone, Serialize, Deserialize, Debug)]
enum DateSpec { #[serde(tag = "type")]
pub enum DateSpec {
SpecificDate { SpecificDate {
date: NaiveDate, date: NaiveDate,
#[serde(default)]
description: String, description: String,
}, },
MonthDay { DayOfMonth {
month: chrono::Month, month: chrono::Month,
day: u32, day: u32,
#[serde(default)]
observed: AdjustmentPolicy, observed: AdjustmentPolicy,
#[serde(default)]
description: String, description: String,
#[serde(default)]
since: Option<NaiveDate>, since: Option<NaiveDate>,
#[serde(default)]
until: Option<NaiveDate>, until: Option<NaiveDate>,
}, },
MonthNthDay { NthDayOccurance {
month: Month, month: Month,
dow: Weekday, dow: Weekday,
offset: i8, offset: i8,
#[serde(default)]
observed: AdjustmentPolicy, observed: AdjustmentPolicy,
#[serde(default)]
description: String, description: String,
#[serde(default)]
since: Option<NaiveDate>, since: Option<NaiveDate>,
#[serde(default)]
until: Option<NaiveDate>, until: Option<NaiveDate>,
}, },
} }
@@ -54,7 +70,7 @@ impl DateSpec {
match self { match self {
SpecificDate { date, .. } => Some((*date, AdjustmentPolicy::NoAdjustment)), SpecificDate { date, .. } => Some((*date, AdjustmentPolicy::NoAdjustment)),
MonthDay { DayOfMonth {
month, month,
day, day,
since, since,
@@ -73,7 +89,7 @@ impl DateSpec {
)) ))
} }
} }
MonthNthDay { NthDayOccurance {
month, month,
dow, dow,
offset, offset,
@@ -121,13 +137,23 @@ impl DateSpec {
} }
} }
fn default_dow_set() -> HashSet<Weekday> {
use Weekday::*;
HashSet::from([Mon, Tue, Wed, Thu, Fri])
}
#[derive(Clone, Serialize, Deserialize, Default, Debug)] #[derive(Clone, Serialize, Deserialize, Default, Debug)]
struct Calendar { pub struct Calendar {
description: String, #[serde(default)]
dow: HashSet<Weekday>, pub description: String,
public: bool, #[serde(default = "default_dow_set")]
excluded: Vec<DateSpec>, pub dow: HashSet<Weekday>,
inherits: Vec<String>, #[serde(default)]
pub public: bool,
#[serde(default)]
pub exclude: Vec<DateSpec>,
#[serde(default)]
pub inherits: Vec<String>,
} }
impl Calendar { impl Calendar {
@@ -200,7 +226,7 @@ impl Calendar {
/// Get the set of all holidays in a given year /// Get the set of all holidays in a given year
pub fn get_holidays(&self, date: NaiveDate) -> HashSet<NaiveDate> { pub fn get_holidays(&self, date: NaiveDate) -> HashSet<NaiveDate> {
let holidays: Vec<(NaiveDate, AdjustmentPolicy)> = self let holidays: Vec<(NaiveDate, AdjustmentPolicy)> = self
.excluded .exclude
.iter() .iter()
.map(|x| x.resolve(date.year())) .map(|x| x.resolve(date.year()))
.filter(|x| x.is_some()) .filter(|x| x.is_some())
@@ -248,8 +274,8 @@ mod tests {
description: "Test description".to_owned(), description: "Test description".to_owned(),
dow: HashSet::from([Mon, Tue, Wed, Thu, Fri]), dow: HashSet::from([Mon, Tue, Wed, Thu, Fri]),
public: false, public: false,
excluded: vec![ exclude: vec![
DateSpec::MonthDay { DateSpec::DayOfMonth {
month: December, month: December,
day: 25u32, day: 25u32,
observed: AdjustmentPolicy::Next, observed: AdjustmentPolicy::Next,
@@ -257,7 +283,7 @@ mod tests {
since: None, since: None,
until: None, until: None,
}, },
DateSpec::MonthDay { DateSpec::DayOfMonth {
month: December, month: December,
day: 26u32, day: 26u32,
observed: AdjustmentPolicy::Next, observed: AdjustmentPolicy::Next,
@@ -287,8 +313,8 @@ mod tests {
description: "Test description".to_owned(), description: "Test description".to_owned(),
dow: HashSet::from([Mon, Tue, Wed, Thu, Fri]), dow: HashSet::from([Mon, Tue, Wed, Thu, Fri]),
public: false, public: false,
excluded: vec![ exclude: vec![
DateSpec::MonthDay { DateSpec::DayOfMonth {
month: December, month: December,
day: 25u32, day: 25u32,
observed: AdjustmentPolicy::Next, observed: AdjustmentPolicy::Next,
@@ -296,7 +322,7 @@ mod tests {
since: None, since: None,
until: None, until: None,
}, },
DateSpec::MonthDay { DateSpec::DayOfMonth {
month: December, month: December,
day: 26u32, day: 26u32,
observed: AdjustmentPolicy::Next, observed: AdjustmentPolicy::Next,
@@ -304,7 +330,7 @@ mod tests {
since: None, since: None,
until: None, until: None,
}, },
DateSpec::MonthDay { DateSpec::DayOfMonth {
month: January, month: January,
day: 1u32, day: 1u32,
observed: AdjustmentPolicy::Next, observed: AdjustmentPolicy::Next,
@@ -322,4 +348,37 @@ mod tests {
); );
assert_eq!(myrange.len(), 20); 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();
}
} }

View File

@@ -1,4 +1,5 @@
pub mod calendar; pub mod calendar;
pub mod schedule;
fn main() { fn main() {
println!("Hello, world"); println!("Hello, world");

87
src/schedule.rs Normal file
View File

@@ -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<TimeSpan> {
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<NaiveDate>,
schedule: Vec<TimeSpan>,
#[serde(default)]
description: String,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
struct Schedule {
default: Vec<TimeSpan>,
overrides: Vec<ScheduleOverride>,
}
#[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));
}
}
}