1use chrono::{Datelike, DateTime, Local, Timelike, TimeZone};
2
3use time_unit::{Hour, Minute, Month, MonthDay, TimeUnit, WeekDay};
4
5use crate::error::CronError;
6use crate::error::CronErrorKind::{InvalidSyntax, InvalidTimeUnit};
7
8mod time_unit;
9mod error;
10
11#[derive(Debug)]
12pub struct CronExpr {
13 minute: Minute,
14 hour: Hour,
15 day_of_month: MonthDay,
16 month: Month,
17 day_of_week: WeekDay
18}
19
20#[cfg(feature = "serialize")]
21impl<'de> serde::Deserialize<'de> for CronExpr {
22 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> {
23 use std::fmt;
24
25 struct Visitor {}
26
27 impl<'de> serde::de::Visitor<'de> for Visitor {
28 type Value = CronExpr;
29
30 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
31 write!(f, "string")
32 }
33
34 fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E> where
35 E: serde::de::Error, {
36 CronExpr::parse(v).map_err(|err| E::custom(format!("{}", err)))
37 }
38
39 fn visit_string<E>(self, v: String) -> Result<Self::Value, E> where
40 E: serde::de::Error, {
41 CronExpr::parse(&v).map_err(|err| E::custom(format!("{}", err)))
42 }
43 }
44
45 deserializer.deserialize_string(Visitor {})
46 }
47}
48
49fn next_month<T: TimeZone>(dt: &DateTime<T>) -> DateTime<T> {
50 dt.with_month(dt.month() + 1)
51 .or(dt.with_year(dt.year() + 1)
52 .and_then(|d| d.with_month0(0)))
53 .and_then(|d| d.with_day0(0))
54 .and_then(|d| d.with_hour(0))
55 .and_then(|d| d.with_minute(0))
56 .and_then(|d| d.with_second(0))
57 .and_then(|d| d.with_nanosecond(0))
58 .unwrap()
59}
60
61fn next_day<T: TimeZone>(dt: &DateTime<T>) -> DateTime<T> {
62 dt.with_day(dt.day() + 1)
63 .and_then(|d| d.with_hour(0))
64 .and_then(|d| d.with_minute(0))
65 .and_then(|d| d.with_second(0))
66 .and_then(|d| d.with_nanosecond(0))
67 .unwrap_or(next_month(dt))
68}
69
70fn next_hour<T: TimeZone>(dt: &DateTime<T>) -> DateTime<T> {
71 dt.with_hour(dt.hour() + 1)
72 .and_then(|d| d.with_minute(0))
73 .and_then(|d| d.with_second(0))
74 .and_then(|d| d.with_nanosecond(0))
75 .unwrap_or(next_day(dt))
76}
77
78fn next_minute<T: TimeZone>(dt: &DateTime<T>) -> DateTime<T> {
79 dt.with_minute(dt.minute() + 1)
80 .and_then(|d| d.with_second(0))
81 .and_then(|d| d.with_nanosecond(0))
82 .unwrap_or(next_hour(dt))
83}
84
85impl CronExpr {
86 pub fn parse(input: &str) -> Result<CronExpr, CronError> {
87 let split: Vec<&str> = input.split(" ").collect();
88
89 if split.len() != 5 {
90 return Err(InvalidSyntax.err(input))
91 }
92
93 fn parse_time_unit<T: TimeUnit>(input: &str) -> Result<T, CronError> {
94 T::parse(input).map_err(|err| InvalidTimeUnit(T::kind(), err).err(input))
95 }
96
97 Ok(CronExpr {
98 minute: parse_time_unit(split[0])?,
99 hour: parse_time_unit(split[1])?,
100 day_of_month: parse_time_unit(split[2])?,
101 month: parse_time_unit(split[3])?,
102 day_of_week: parse_time_unit(split[4])?,
103 })
104 }
105
106 pub fn next<T: TimeZone>(&self, after: &DateTime<T>) -> DateTime<T> {
107 let mut next = next_minute(after);
108 loop {
109 if !self.month.value().includes(next.month()) {
110 next = next_month(&next);
111 continue;
112 }
113
114 if !((!self.day_of_month.value().is_wildcard() && self.day_of_month.value().includes(next.day())) ||
115 (!self.day_of_week.value().is_wildcard() && self.day_of_week.value().includes(next.weekday().number_from_monday())) ||
116 (self.day_of_month.value().is_wildcard() && self.day_of_week.value().is_wildcard())) {
117 next = next_day(&next);
118 continue;
119 }
120
121 if !self.hour.value().includes(next.hour()) {
122 next = next_hour(&next);
123 continue;
124 }
125
126 if !self.minute.value().includes(next.minute()) {
127 next = next_minute(&next);
128 continue;
129 }
130
131 break;
132 }
133
134 return next;
135 }
136
137 pub fn next_after_now(&self) -> DateTime<Local> {
138 self.next(&Local::now())
139 }
140}