rusty_cron/
lib.rs

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        //After that combine and calculate closest execution time delay
62        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(); // I have faith in myself, this won't fail
230                        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(); // I have faith in myself, this won't fail
240                                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(); // I have faith in myself, this won't fail
249                                        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}