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 if let Some(second) =
87 self.fields.seconds.ordinals().range(second_range).next()
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 if let Some(second) = self
215 .fields
216 .seconds
217 .ordinals()
218 .range(second_range)
219 .rev()
220 .cloned()
221 .next()
222 {
223 let timezone = before.timezone();
224 let candidate = timezone
225 .with_ymd_and_hms(
226 year as i32,
227 month,
228 day_of_month,
229 hour,
230 minute,
231 second,
232 )
233 .unwrap();
234 if !self
235 .fields
236 .days_of_week
237 .ordinals()
238 .contains(&candidate.weekday().number_from_sunday())
239 {
240 continue 'day_loop;
241 }
242 return Some(candidate);
243 }
244 query.reset_minute();
245 } query.reset_hour();
247 } query.reset_day_of_month();
249 } query.reset_month();
251 } }
253
254 None
256 }
257
258 pub fn after<Z>(&self, after: &DateTime<Z>) -> ScheduleIterator<'_, Z>
269 where
270 Z: TimeZone,
271 {
272 ScheduleIterator::new(self, after)
273 }
274
275 pub fn includes<Z>(&self, date_time: DateTime<Z>) -> bool
276 where
277 Z: TimeZone,
278 {
279 self.fields.years.includes(date_time.year() as Ordinal)
280 && self.fields.months.includes(date_time.month() as Ordinal)
281 && self
282 .fields
283 .days_of_week
284 .includes(date_time.weekday().number_from_sunday())
285 && self
286 .fields
287 .days_of_month
288 .includes(date_time.day() as Ordinal)
289 && self.fields.hours.includes(date_time.hour() as Ordinal)
290 && self.fields.minutes.includes(date_time.minute() as Ordinal)
291 && self.fields.seconds.includes(date_time.second() as Ordinal)
292 }
293
294 pub fn years(&self) -> &impl TimeUnitSpec {
297 &self.fields.years
298 }
299
300 pub fn months(&self) -> &impl TimeUnitSpec {
303 &self.fields.months
304 }
305
306 pub fn days_of_month(&self) -> &impl TimeUnitSpec {
309 &self.fields.days_of_month
310 }
311
312 pub fn days_of_week(&self) -> &impl TimeUnitSpec {
315 &self.fields.days_of_week
316 }
317
318 pub fn hours(&self) -> &impl TimeUnitSpec {
321 &self.fields.hours
322 }
323
324 pub fn minutes(&self) -> &impl TimeUnitSpec {
327 &self.fields.minutes
328 }
329
330 pub fn seconds(&self) -> &impl TimeUnitSpec {
333 &self.fields.seconds
334 }
335
336 pub fn timeunitspec_eq(&self, other: &Schedule) -> bool {
337 self.fields == other.fields
338 }
339}
340
341impl Display for Schedule {
342 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
343 write!(f, "{}", self.source)
344 }
345}
346
347impl PartialEq for Schedule {
348 fn eq(&self, other: &Schedule) -> bool {
349 self.source == other.source
350 }
351}
352
353#[derive(Clone, Debug, PartialEq, Eq)]
354pub struct ScheduleFields {
355 years: Years,
356 days_of_week: DaysOfWeek,
357 months: Months,
358 days_of_month: DaysOfMonth,
359 hours: Hours,
360 minutes: Minutes,
361 seconds: Seconds,
362}
363
364impl ScheduleFields {
365 pub(crate) fn new(
366 seconds: Seconds,
367 minutes: Minutes,
368 hours: Hours,
369 days_of_month: DaysOfMonth,
370 months: Months,
371 days_of_week: DaysOfWeek,
372 years: Years,
373 ) -> ScheduleFields {
374 ScheduleFields {
375 years,
376 days_of_week,
377 months,
378 days_of_month,
379 hours,
380 minutes,
381 seconds,
382 }
383 }
384}
385
386pub struct ScheduleIterator<'a, Z>
387where
388 Z: TimeZone,
389{
390 is_done: bool,
391 schedule: &'a Schedule,
392 previous_datetime: DateTime<Z>,
393}
394impl<'a, Z> ScheduleIterator<'a, Z>
397where
398 Z: TimeZone,
399{
400 fn new(schedule: &'a Schedule, starting_datetime: &DateTime<Z>) -> ScheduleIterator<'a, Z> {
401 ScheduleIterator {
402 is_done: false,
403 schedule,
404 previous_datetime: starting_datetime.clone(),
405 }
406 }
407}
408
409impl<'a, Z> Iterator for ScheduleIterator<'a, Z>
410where
411 Z: TimeZone,
412{
413 type Item = DateTime<Z>;
414
415 fn next(&mut self) -> Option<DateTime<Z>> {
416 if self.is_done {
417 return None;
418 }
419 if let Some(next_datetime) = self.schedule.next_after(&self.previous_datetime) {
420 self.previous_datetime = next_datetime.clone();
421 Some(next_datetime)
422 } else {
423 self.is_done = true;
424 None
425 }
426 }
427}
428
429impl<'a, Z> DoubleEndedIterator for ScheduleIterator<'a, Z>
430where
431 Z: TimeZone,
432{
433 fn next_back(&mut self) -> Option<Self::Item> {
434 if self.is_done {
435 return None;
436 }
437
438 if let Some(prev_datetime) = self.schedule.prev_before(&self.previous_datetime) {
439 self.previous_datetime = prev_datetime.clone();
440 Some(prev_datetime)
441 } else {
442 self.is_done = true;
443 None
444 }
445 }
446}
447
448fn is_leap_year(year: Ordinal) -> bool {
449 let by_four = year % 4 == 0;
450 let by_hundred = year % 100 == 0;
451 let by_four_hundred = year % 400 == 0;
452 by_four && ((!by_hundred) || by_four_hundred)
453}
454
455fn days_in_month(month: Ordinal, year: Ordinal) -> u32 {
456 let is_leap_year = is_leap_year(year);
457 match month {
458 9 | 4 | 6 | 11 => 30,
459 2 if is_leap_year => 29,
460 2 => 28,
461 _ => 31,
462 }
463}
464
465#[cfg(test)]
466mod test {
467 use super::*;
468 use std::str::FromStr;
469
470 #[test]
557 fn test_schedule_to_string() {
558 let expression = "* 1,2,3 * * * *";
559 let schedule: Schedule = Schedule::from_str(expression).unwrap();
560 let result = String::from(schedule);
561 assert_eq!(expression, result);
562 }
563
564 #[test]
565 fn test_display_schedule() {
566 use std::fmt::Write;
567 let expression = "@monthly";
568 let schedule = Schedule::from_str(expression).unwrap();
569 let mut result = String::new();
570 write!(result, "{}", schedule).unwrap();
571 assert_eq!(expression, result);
572 }
573
574 #[test]
575 fn test_valid_from_str() {
576 let schedule = Schedule::from_str("0 0,30 0,6,12,18 1,15 Jan-March Thurs");
577 schedule.unwrap();
578 }
579
580 #[test]
581 fn test_invalid_from_str() {
582 let schedule = Schedule::from_str("cheesecake 0,30 0,6,12,18 1,15 Jan-March Thurs");
583 assert!(schedule.is_err());
584 }
585
586 #[test]
587 fn test_time_unit_spec_equality() {
588 let schedule_1 = Schedule::from_str("@weekly").unwrap();
589 let schedule_2 = Schedule::from_str("0 0 0 * * 1 *").unwrap();
590 let schedule_3 = Schedule::from_str("0 0 0 * * 1-7 *").unwrap();
591 let schedule_4 = Schedule::from_str("0 0 0 * * * *").unwrap();
592 assert_ne!(schedule_1, schedule_2);
593 assert!(schedule_1.timeunitspec_eq(&schedule_2));
594 assert!(schedule_3.timeunitspec_eq(&schedule_4));
595 }
596}