1use chrono::prelude::*;
2
3pub struct Cron;
4impl Cron {
5 pub fn parse_time(cron_string: &str, start_date: Option<chrono::DateTime<Utc>>) -> Result<i64, String>{
6 let separated_values: Vec<&str> = cron_string.split(' ').collect();
7
8 if separated_values.len() != 5 && separated_values.len() != 6 {
9 return Err(format!("Incorrect amount of parameters in cron string, please review format"));
10 }
11
12 let seconds_list: Vec<u32>;
13 let minutes_list: Vec<u32>;
14 let hours_list: Vec<u32>;
15 let days_month_list: Vec<u32>;
16 let months_list: Vec<u32>;
17 let days_week_list: Vec<u32>;
18
19 let mut array_index = 0;
20 if separated_values.len() == 6{
21 match Self::parse_crontab_chunk(separated_values[array_index], 0, 60) {
22 Ok(n) => seconds_list = n,
23 Err(e) => return Err(e)
24 }
25 array_index += 1;
26 }
27 else{
28 seconds_list = [0].to_vec();
29 }
30
31 match Self::parse_crontab_chunk(separated_values[array_index], 0, 60) {
32 Ok(n) => minutes_list = n,
33 Err(e) => return Err(e)
34 }
35
36 array_index += 1;
37
38 match Self::parse_crontab_chunk(separated_values[array_index], 0, 24) {
39 Ok(n) => hours_list = n,
40 Err(e) => return Err(e)
41 }
42
43 array_index += 1;
44 match Self::parse_crontab_chunk(separated_values[array_index], 1, 32) {
45 Ok(n) => days_month_list = n,
46 Err(e) => return Err(e)
47 }
48
49 array_index += 1;
50 match Self::parse_crontab_chunk(separated_values[array_index], 1, 13) {
51 Ok(n) => months_list = n,
52 Err(e) => return Err(e)
53 }
54
55 array_index += 1;
56 match Self::parse_crontab_chunk(separated_values[array_index], 0, 7) {
57 Ok(n) => days_week_list = n,
58 Err(e) => return Err(e)
59 }
60
61 let start_time: DateTime<Utc>;
63
64 match start_date{
65 Some(n) => start_time = n,
66 None => start_time = chrono::Utc::now()
67 }
68
69 return Self::get_closest_execution_timer(start_time, months_list, days_month_list, days_week_list, hours_list, minutes_list, seconds_list);
70 }
71
72 fn parse_crontab_chunk(chunk: &str, sequence_start: u32, max_value: u32) -> Result<Vec<u32>, String>{
73 match chunk.parse::<u32>() {
74 Ok(n) => return Self::process_base_case(n, sequence_start, max_value),
75 Err(_) => Self::process_list_case(chunk, sequence_start, max_value),
76 }
77 }
78
79 fn process_base_case(int_val: u32, min_value: u32, max_value: u32) -> Result<Vec<u32>, String> {
80 let mut result: Vec<u32> = Vec::new();
81
82 if int_val > max_value.into() || int_val < min_value.into() {
83 return Err(format!("Found incorrect value: {int_val}"));
84 }
85
86 result.push(int_val);
87 return Ok(result);
88 }
89
90 fn process_star_case(sequence_start: u32, max_value: u32) -> Result<Vec<u32>, String> {
91 let mut result: Vec<u32> = Vec::new();
92
93 for i in sequence_start..max_value{
94 result.push(i.into());
95 }
96
97 return Ok(result);
98 }
99
100 fn process_divisor_case(chunk: &str, mut sequence_start: u32, mut max_value: u32) -> Result<Vec<u32>, String> {
101 let mut result: Vec<u32> = Vec::new();
102 let divisor: u32;
103 let chunks: Vec<&str> = chunk.split('/').collect();
104
105 if chunks[0].len() == 1 && chunks[0] == "*"
106 {
107 }
108 else
109 {
110 match chunks[0].parse::<u32>() {
111 Ok(n) => {sequence_start = n},
112 Err(_) =>
113 {
114 if chunks[0].contains('-') {
115 let range_result = Self::process_range_case(chunks[0], max_value);
116 match range_result {
117 Ok(mut n) => {
118 n.sort();
119 sequence_start = *n.first().unwrap();
120 max_value = *n.last().unwrap() + 1;
121 },
122 Err(e) => return Err(e)
123 }
124 }else {
125 return Err(format!("Malformed crontab string"))
126 }
127 },
128 }
129 }
130
131 match chunks[1].parse::<u32>() {
132 Ok(n) => {
133 if n > max_value || n <= 0{
134 return Err(format!("Malformed crontab string"));
135 }else {
136 divisor = n;
137 }
138 },
139 Err(_) => {return Err(format!("Malformed crontab string"));},
140 }
141
142 for i in (sequence_start..max_value).step_by(divisor.try_into().unwrap()){
143 result.push(i.into());
144 }
145
146 return Ok(result);
147 }
148
149 fn process_range_case( chunk: &str, mut max_value: u32) -> Result<Vec<u32>, String> {
150 let mut result: Vec<u32> = Vec::new();
151 let chunks: Vec<&str> = chunk.split('-').collect();
152 let sequence_start: u32;
153
154 match chunks[0].parse::<u32>() {
155 Ok(n) => {sequence_start = n},
156 Err(_) => {return Err(format!("Malformed crontab string"));},
157 }
158
159 match chunks[1].parse::<u32>() {
160 Ok(n) => {
161 if n > max_value || n < sequence_start {
162 return Err(format!("Malformed crontab string"));
163 }else {
164 max_value = n + 1;
165 }
166 },
167 Err(_) => {return Err(format!("Malformed crontab string"));},
168 }
169
170 for i in sequence_start..max_value{
171 result.push(i.into());
172 }
173
174 return Ok(result);
175 }
176
177 fn process_list_case(chunk: &str, sequence_start: u32, max_value: u32) -> Result<Vec<u32>, String> {
178 let mut result: Vec<u32> = Vec::new();
179 let chunks: Vec<&str> = chunk.split(',').collect();
180
181 for chunkies in chunks {
182 match chunkies.parse::<u32>() {
183 Ok(n) => {
184 if n <= max_value && n >= sequence_start {
185 result.push(n.into());
186 }else {
187 return Err(format!("Malformed crontab string"));
188 }
189 },
190 Err(_) =>
191 {
192 if chunkies.len() == 1 && chunkies == "*" {
193 return Self::process_star_case(sequence_start, max_value);
194 }else{
195 if chunkies.contains('/') {
196 let div_results = Self::process_divisor_case(chunkies, sequence_start, max_value);
197
198 match div_results{
199 Ok(n) => for div_res in n { result.push(div_res) },
200 Err(e) => return Err(e)
201 }
202 }else if chunkies.contains('-'){
203 let range_results = Self::process_range_case(chunkies, max_value);
204
205 match range_results{
206 Ok(n) => for div_res in n { result.push(div_res) },
207 Err(e) => return Err(e)
208 }
209 }else {
210 return Err("Malformed crontab string".to_owned())
211 }
212 }
213 },
214 }
215 }
216
217 return Ok(result);
218 }
219
220 fn get_closest_execution_timer(mut now: DateTime<Utc>, months_list: Vec<u32>, days_month_list: Vec<u32>, days_week_list: Vec<u32>, hours_list: Vec<u32>, minutes_list: Vec<u32>, seconds_list: Vec<u32>) -> Result<i64, String> {
221 if months_list.contains(&now.month())
222 {
223 if days_month_list.contains(&now.day()) && days_week_list.contains(&now.weekday().num_days_from_sunday())
224 {
225 for hour in &hours_list
226 {
227 if now.hour() < *hour
228 {
229 now = chrono::Utc.with_ymd_and_hms(now.year(), now.month(), now.day(), *hour, minutes_list[0], 0).unwrap(); let result = now - chrono::Utc::now();
231 return Ok(result.num_milliseconds());
232 }
233 else if now.hour() == *hour
234 {
235 for minute in &minutes_list
236 {
237 if now.minute() < *minute
238 {
239 now = chrono::Utc.with_ymd_and_hms(now.year(), now.month(), now.day(), now.hour(), *minute, seconds_list[0]).unwrap(); let result = now - chrono::Utc::now();
241 return Ok(result.num_milliseconds());
242 }
243 else if now.minute() == *minute{
244 for second in &seconds_list
245 {
246 if now.second() < *second
247 {
248 now = chrono::Utc.with_ymd_and_hms(now.year(), now.month(), now.day(), now.hour(), *minute, *second).unwrap(); let result = now - chrono::Utc::now();
250 return Ok(result.num_milliseconds());
251 }
252 }
253 }
254 }
255 }
256 }
257 }
258 now = chrono::Utc.with_ymd_and_hms(now.year(), now.month(), now.day(),0,0,0).unwrap();
259 return Self::get_closest_execution_timer(now.checked_add_days(chrono::Days::new(1)).unwrap(), months_list, days_month_list, days_week_list, hours_list, minutes_list, seconds_list);
260 }
261 now = chrono::Utc.with_ymd_and_hms(now.year(), now.month(), 1, 0, 0, 0).unwrap();
262 return Self::get_closest_execution_timer(now.checked_add_months(chrono::Months::new(1)).unwrap(), months_list, days_month_list, days_week_list, hours_list, minutes_list, seconds_list);
263 }
264}