Adding schedule
This commit is contained in:
27
README.md
27
README.md
@@ -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
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
|||||||
101
src/calendar.rs
101
src/calendar.rs
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
87
src/schedule.rs
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user