1use chrono::offset::TimeZone;
2use chrono::{DateTime, Datelike, Timelike};
3use std::fmt::{Display, Formatter, Result as FmtResult};
4use std::ops::Bound::{Included, Unbounded};
5
6use crate::ordinal::*;
7use crate::queries::*;
8use crate::time_unit::*;
9
10impl From<Schedule> for String {
11 fn from(schedule: Schedule) -> String {
12 schedule.source
13 }
14}
15
16#[derive(Clone, Debug, Eq)]
17pub struct Schedule {
18 source: String,
19 fields: ScheduleFields,
20}
21
22impl Schedule {
23 pub(crate) fn new(source: String, fields: ScheduleFields) -> Schedule {
24 Schedule { source, fields }
25 }
26
27 pub fn next_after<Z>(&self, after: &DateTime<Z>) -> Option<DateTime<Z>>
28 where
29 Z: TimeZone,
30 {
31 let mut query = NextAfterQuery::from(after);
32 for year in self
33 .fields
34 .years
35 .ordinals()
36 .range((Included(query.year_lower_bound()), Unbounded))
37 .cloned()
38 {
39 let month_start = query.month_lower_bound();
40 if !self.fields.months.ordinals().contains(&month_start) {
41 query.reset_month();
42 }
43 let month_range = (Included(month_start), Included(Months::inclusive_max()));
44 for month in self.fields.months.ordinals().range(month_range).cloned() {
45 let day_of_month_start = query.day_of_month_lower_bound();
46 if !self
47 .fields
48 .days_of_month
49 .ordinals()
50 .contains(&day_of_month_start)
51 {
52 query.reset_day_of_month();
53 }
54 let day_of_month_end = days_in_month(month, year);
55 let day_of_month_range = (Included(day_of_month_start), Included(day_of_month_end));
56
57 'day_loop: for day_of_month in self
58 .fields
59 .days_of_month
60 .ordinals()
61 .range(day_of_month_range)
62 .cloned()
63 {
64 let hour_start = query.hour_lower_bound();
65 if !self.fields.hours.ordinals().contains(&hour_start) {
66 query.reset_hour();
67 }
68 let hour_range = (Included(hour_start), Included(Hours::inclusive_max()));
69
70 for hour in self.fields.hours.ordinals().range(hour_range).cloned() {
71 let minute_start = query.minute_lower_bound();
72 if !self.fields.minutes.ordinals().contains(&minute_start) {
73 query.reset_minute();
74 }
75 let minute_range =
76 (Included(minute_start), Included(Minutes::inclusive_max()));
77
78 for minute in self.fields.minutes.ordinals().range(minute_range).cloned() {
79 let second_start = query.second_lower_bound();
80 if !self.fields.seconds.ordinals().contains(&second_start) {
81 query.reset_second();
82 }
83 let second_range =
84 (Included(second_start), Included(Seconds::inclusive_max()));
85
86 for second in
87 self.fields.seconds.ordinals().range(second_range).cloned()
88 {
89 let timezone = after.timezone();
90 let candidate = timezone
91 .with_ymd_and_hms(
92 year as i32,
93 month,
94 day_of_month,
95 hour,
96 minute,
97 second,
98 )
99 .unwrap();
100 if !self
101 .fields
102 .days_of_week
103 .ordinals()
104 .contains(&candidate.weekday().number_from_sunday())
105 {
106 continue 'day_loop;
107 }
108 return Some(candidate);
109 }
110 query.reset_minute();
111 } query.reset_hour();
113 } query.reset_day_of_month();
115 } query.reset_month();
117 } }
119
120 None
122 }
123
124 pub fn prev_before<Z>(&self, before: &DateTime<Z>) -> Option<DateTime<Z>>
125 where
126 Z: TimeZone,
127 {
128 let mut query = PrevFromQuery::from(before);
129 for year in self
130 .fields
131 .years
132 .ordinals()
133 .range((Unbounded, Included(query.year_upper_bound())))
134 .rev()
135 .cloned()
136 {
137 let month_start = query.month_upper_bound();
138
139 if !self.fields.months.ordinals().contains(&month_start) {
140 query.reset_month();
141 }
142 let month_range = (Included(Months::inclusive_min()), Included(month_start));
143
144 for month in self
145 .fields
146 .months
147 .ordinals()
148 .range(month_range)
149 .rev()
150 .cloned()
151 {
152 let day_of_month_end = query.day_of_month_upper_bound();
153 if !self
154 .fields
155 .days_of_month
156 .ordinals()
157 .contains(&day_of_month_end)
158 {
159 query.reset_day_of_month();
160 }
161
162 let day_of_month_end = days_in_month(month, year).min(day_of_month_end);
163
164 let day_of_month_range = (
165 Included(DaysOfMonth::inclusive_min()),
166 Included(day_of_month_end),
167 );
168
169 'day_loop: for day_of_month in self
170 .fields
171 .days_of_month
172 .ordinals()
173 .range(day_of_month_range)
174 .rev()
175 .cloned()
176 {
177 let hour_start = query.hour_upper_bound();
178 if !self.fields.hours.ordinals().contains(&hour_start) {
179 query.reset_hour();
180 }
181 let hour_range = (Included(Hours::inclusive_min()), Included(hour_start));
182
183 for hour in self
184 .fields
185 .hours
186 .ordinals()
187 .range(hour_range)
188 .rev()
189 .cloned()
190 {
191 let minute_start = query.minute_upper_bound();
192 if !self.fields.minutes.ordinals().contains(&minute_start) {
193 query.reset_minute();
194 }
195 let minute_range =
196 (Included(Minutes::inclusive_min()), Included(minute_start));
197
198 for minute in self
199 .fields
200 .minutes
201 .ordinals()
202 .range(minute_range)
203 .rev()
204 .cloned()
205 {
206 let second_start = query.second_upper_bound();
207 if !self.fields.seconds.ordinals().contains(&second_start) {
208 query.reset_second();
209 }
210 let second_range =
211 (Included(Seconds::inclusive_min()), Included(second_start));
212
213 for second in self
214 .fields
215 .seconds
216 .ordinals()
217 .range(second_range)
218 .rev()
219 .cloned()
220 {
221 let timezone = before.timezone();
222 let candidate = timezone
223 .with_ymd_and_hms(
224 year as i32,
225 month,
226 day_of_month,
227 hour,
228 minute,
229 second,
230 )
231 .unwrap();
232 if !self
233 .fields
234 .days_of_week
235 .ordinals()
236 .contains(&candidate.weekday().number_from_sunday())
237 {
238 continue 'day_loop;
239 }
240 return Some(candidate);
241 }
242 query.reset_minute();
243 } query.reset_hour();
245 } query.reset_day_of_month();
247 } query.reset_month();
249 } }
251
252 None
254 }
255
256 pub fn after<Z>(&self, after: &DateTime<Z>) -> ScheduleIterator<'_, Z>
267 where
268 Z: TimeZone,
269 {
270 ScheduleIterator::new(self, after)
271 }
272
273 pub fn includes<Z>(&self, date_time: DateTime<Z>) -> bool
274 where
275 Z: TimeZone,
276 {
277 self.fields.years.includes(date_time.year() as Ordinal)
278 && self.fields.months.includes(date_time.month() as Ordinal)
279 && self
280 .fields
281 .days_of_week
282 .includes(date_time.weekday().number_from_sunday())
283 && self
284 .fields
285 .days_of_month
286 .includes(date_time.day() as Ordinal)
287 && self.fields.hours.includes(date_time.hour() as Ordinal)
288 && self.fields.minutes.includes(date_time.minute() as Ordinal)
289 && self.fields.seconds.includes(date_time.second() as Ordinal)
290 }
291
292 pub fn years(&self) -> &impl TimeUnitSpec {
295 &self.fields.years
296 }
297
298 pub fn months(&self) -> &impl TimeUnitSpec {
301 &self.fields.months
302 }
303
304 pub fn days_of_month(&self) -> &impl TimeUnitSpec {
307 &self.fields.days_of_month
308 }
309
310 pub fn days_of_week(&self) -> &impl TimeUnitSpec {
313 &self.fields.days_of_week
314 }
315
316 pub fn hours(&self) -> &impl TimeUnitSpec {
319 &self.fields.hours
320 }
321
322 pub fn minutes(&self) -> &impl TimeUnitSpec {
325 &self.fields.minutes
326 }
327
328 pub fn seconds(&self) -> &impl TimeUnitSpec {
331 &self.fields.seconds
332 }
333
334 pub fn timeunitspec_eq(&self, other: &Schedule) -> bool {
335 self.fields == other.fields
336 }
337}
338
339impl Display for Schedule {
340 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
341 write!(f, "{}", self.source)
342 }
343}
344
345impl PartialEq for Schedule {
346 fn eq(&self, other: &Schedule) -> bool {
347 self.source == other.source
348 }
349}
350
351#[derive(Clone, Debug, PartialEq, Eq)]
352pub struct ScheduleFields {
353 years: Years,
354 days_of_week: DaysOfWeek,
355 months: Months,
356 days_of_month: DaysOfMonth,
357 hours: Hours,
358 minutes: Minutes,
359 seconds: Seconds,
360}
361
362impl ScheduleFields {
363 pub(crate) fn new(
364 seconds: Seconds,
365 minutes: Minutes,
366 hours: Hours,
367 days_of_month: DaysOfMonth,
368 months: Months,
369 days_of_week: DaysOfWeek,
370 years: Years,
371 ) -> ScheduleFields {
372 ScheduleFields {
373 years,
374 days_of_week,
375 months,
376 days_of_month,
377 hours,
378 minutes,
379 seconds,
380 }
381 }
382}
383
384pub struct ScheduleIterator<'a, Z>
385where
386 Z: TimeZone,
387{
388 is_done: bool,
389 schedule: &'a Schedule,
390 previous_datetime: DateTime<Z>,
391}
392impl<'a, Z> ScheduleIterator<'a, Z>
395where
396 Z: TimeZone,
397{
398 fn new(schedule: &'a Schedule, starting_datetime: &DateTime<Z>) -> ScheduleIterator<'a, Z> {
399 ScheduleIterator {
400 is_done: false,
401 schedule,
402 previous_datetime: starting_datetime.clone(),
403 }
404 }
405}
406
407impl<'a, Z> Iterator for ScheduleIterator<'a, Z>
408where
409 Z: TimeZone,
410{
411 type Item = DateTime<Z>;
412
413 fn next(&mut self) -> Option<DateTime<Z>> {
414 if self.is_done {
415 return None;
416 }
417 if let Some(next_datetime) = self.schedule.next_after(&self.previous_datetime) {
418 self.previous_datetime = next_datetime.clone();
419 Some(next_datetime)
420 } else {
421 self.is_done = true;
422 None
423 }
424 }
425}
426
427impl<'a, Z> DoubleEndedIterator for ScheduleIterator<'a, Z>
428where
429 Z: TimeZone,
430{
431 fn next_back(&mut self) -> Option<Self::Item> {
432 if self.is_done {
433 return None;
434 }
435
436 if let Some(prev_datetime) = self.schedule.prev_before(&self.previous_datetime) {
437 self.previous_datetime = prev_datetime.clone();
438 Some(prev_datetime)
439 } else {
440 self.is_done = true;
441 None
442 }
443 }
444}
445
446fn is_leap_year(year: Ordinal) -> bool {
447 let by_four = year % 4 == 0;
448 let by_hundred = year % 100 == 0;
449 let by_four_hundred = year % 400 == 0;
450 by_four && ((!by_hundred) || by_four_hundred)
451}
452
453fn days_in_month(month: Ordinal, year: Ordinal) -> u32 {
454 let is_leap_year = is_leap_year(year);
455 match month {
456 9 | 4 | 6 | 11 => 30,
457 2 if is_leap_year => 29,
458 2 => 28,
459 _ => 31,
460 }
461}
462
463#[cfg(test)]
464mod test {
465 use super::*;
466 use std::str::FromStr;
467
468 #[test]
555 fn test_schedule_to_string() {
556 let expression = "* 1,2,3 * * * *";
557 let schedule: Schedule = Schedule::from_str(expression).unwrap();
558 let result = String::from(schedule);
559 assert_eq!(expression, result);
560 }
561
562 #[test]
563 fn test_display_schedule() {
564 use std::fmt::Write;
565 let expression = "@monthly";
566 let schedule = Schedule::from_str(expression).unwrap();
567 let mut result = String::new();
568 write!(result, "{}", schedule).unwrap();
569 assert_eq!(expression, result);
570 }
571
572 #[test]
573 fn test_valid_from_str() {
574 let schedule = Schedule::from_str("0 0,30 0,6,12,18 1,15 Jan-March Thurs");
575 schedule.unwrap();
576 }
577
578 #[test]
579 fn test_invalid_from_str() {
580 let schedule = Schedule::from_str("cheesecake 0,30 0,6,12,18 1,15 Jan-March Thurs");
581 assert!(schedule.is_err());
582 }
583
584 #[test]
585 fn test_time_unit_spec_equality() {
586 let schedule_1 = Schedule::from_str("@weekly").unwrap();
587 let schedule_2 = Schedule::from_str("0 0 0 * * 1 *").unwrap();
588 let schedule_3 = Schedule::from_str("0 0 0 * * 1-7 *").unwrap();
589 let schedule_4 = Schedule::from_str("0 0 0 * * * *").unwrap();
590 assert_ne!(schedule_1, schedule_2);
591 assert!(schedule_1.timeunitspec_eq(&schedule_2));
592 assert!(schedule_3.timeunitspec_eq(&schedule_4));
593 }
594}