1use std::{
2 fmt::{Display, Formatter, Result as FmtResult},
3 ops::Bound::{Included, Unbounded},
4};
5
6use chrono::{DateTime, Datelike, Timelike, Utc, offset::TimeZone};
7
8use crate::{ordinal::*, queries::*, 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 {
25 source,
26 fields,
27 }
28 }
29
30 fn next_after<Z>(&self, after: &DateTime<Z>) -> Option<DateTime<Z>>
31 where
32 Z: TimeZone,
33 {
34 let mut query = NextAfterQuery::from(after);
35 for year in self
36 .fields
37 .years
38 .ordinals()
39 .range((Included(query.year_lower_bound()), Unbounded))
40 .cloned()
41 {
42 let month_start = query.month_lower_bound();
43 if !self.fields.months.ordinals().contains(&month_start) {
44 query.reset_month();
45 }
46 let month_range = (Included(month_start), Included(Months::inclusive_max()));
47 for month in self.fields.months.ordinals().range(month_range).cloned() {
48 let day_of_month_start = query.day_of_month_lower_bound();
49 if !self.fields.days_of_month.ordinals().contains(&day_of_month_start) {
50 query.reset_day_of_month();
51 }
52 let day_of_month_end = days_in_month(month, year);
53 let day_of_month_range =
54 (Included(day_of_month_start.min(day_of_month_end)), Included(day_of_month_end));
55
56 'day_loop: for day_of_month in self.fields.days_of_month.ordinals().range(day_of_month_range).cloned() {
57 let hour_start = query.hour_lower_bound();
58 if !self.fields.hours.ordinals().contains(&hour_start) {
59 query.reset_hour();
60 }
61 let hour_range = (Included(hour_start), Included(Hours::inclusive_max()));
62
63 for hour in self.fields.hours.ordinals().range(hour_range).cloned() {
64 let minute_start = query.minute_lower_bound();
65 if !self.fields.minutes.ordinals().contains(&minute_start) {
66 query.reset_minute();
67 }
68 let minute_range = (Included(minute_start), Included(Minutes::inclusive_max()));
69
70 for minute in self.fields.minutes.ordinals().range(minute_range).cloned() {
71 let second_start = query.second_lower_bound();
72 if !self.fields.seconds.ordinals().contains(&second_start) {
73 query.reset_second();
74 }
75 let second_range = (Included(second_start), Included(Seconds::inclusive_max()));
76
77 for second in self.fields.seconds.ordinals().range(second_range).cloned() {
78 let timezone = after.timezone();
79 let candidate = if let Some(candidate) = timezone
80 .ymd(year as i32, month, day_of_month)
81 .and_hms_opt(hour, minute, second)
82 {
83 candidate
84 } else {
85 continue;
86 };
87 if !self
88 .fields
89 .days_of_week
90 .ordinals()
91 .contains(&candidate.weekday().number_from_sunday())
92 {
93 continue 'day_loop;
94 }
95 return Some(candidate);
96 }
97 query.reset_minute();
98 } query.reset_hour();
100 } query.reset_day_of_month();
102 } query.reset_month();
104 } }
106
107 None
109 }
110
111 fn prev_from<Z>(&self, before: &DateTime<Z>) -> Option<DateTime<Z>>
112 where
113 Z: TimeZone,
114 {
115 let mut query = PrevFromQuery::from(before);
116 for year in self
117 .fields
118 .years
119 .ordinals()
120 .range((Unbounded, Included(query.year_upper_bound())))
121 .rev()
122 .cloned()
123 {
124 let month_start = query.month_upper_bound();
125
126 if !self.fields.months.ordinals().contains(&month_start) {
127 query.reset_month();
128 }
129 let month_range = (Included(Months::inclusive_min()), Included(month_start));
130
131 for month in self.fields.months.ordinals().range(month_range).rev().cloned() {
132 let day_of_month_end = query.day_of_month_upper_bound();
133 if !self.fields.days_of_month.ordinals().contains(&day_of_month_end) {
134 query.reset_day_of_month();
135 }
136
137 let day_of_month_end = days_in_month(month, year).min(day_of_month_end);
138
139 let day_of_month_range = (Included(DaysOfMonth::inclusive_min()), Included(day_of_month_end));
140
141 'day_loop: for day_of_month in self
142 .fields
143 .days_of_month
144 .ordinals()
145 .range(day_of_month_range)
146 .rev()
147 .cloned()
148 {
149 let hour_start = query.hour_upper_bound();
150 if !self.fields.hours.ordinals().contains(&hour_start) {
151 query.reset_hour();
152 }
153 let hour_range = (Included(Hours::inclusive_min()), Included(hour_start));
154
155 for hour in self.fields.hours.ordinals().range(hour_range).rev().cloned() {
156 let minute_start = query.minute_upper_bound();
157 if !self.fields.minutes.ordinals().contains(&minute_start) {
158 query.reset_minute();
159 }
160 let minute_range = (Included(Minutes::inclusive_min()), Included(minute_start));
161
162 for minute in self.fields.minutes.ordinals().range(minute_range).rev().cloned() {
163 let second_start = query.second_upper_bound();
164 if !self.fields.seconds.ordinals().contains(&second_start) {
165 query.reset_second();
166 }
167 let second_range = (Included(Seconds::inclusive_min()), Included(second_start));
168
169 for second in self.fields.seconds.ordinals().range(second_range).rev().cloned() {
170 let timezone = before.timezone();
171 let candidate = if let Some(candidate) = timezone
172 .ymd(year as i32, month, day_of_month)
173 .and_hms_opt(hour, minute, second)
174 {
175 candidate
176 } else {
177 continue;
178 };
179 if !self
180 .fields
181 .days_of_week
182 .ordinals()
183 .contains(&candidate.weekday().number_from_sunday())
184 {
185 continue 'day_loop;
186 }
187 return Some(candidate);
188 }
189 query.reset_minute();
190 } query.reset_hour();
192 } query.reset_day_of_month();
194 } query.reset_month();
196 } }
198
199 None
201 }
202
203 pub fn upcoming<Z>(&self, timezone: Z) -> ScheduleIterator<'_, Z>
206 where
207 Z: TimeZone,
208 {
209 self.after(&timezone.from_utc_datetime(&Utc::now().naive_utc()))
210 }
211
212 pub fn upcoming_owned<Z: TimeZone>(&self, timezone: Z) -> OwnedScheduleIterator<Z> {
214 self.after_owned(timezone.from_utc_datetime(&Utc::now().naive_utc()))
215 }
216
217 pub fn after<Z>(&self, after: &DateTime<Z>) -> ScheduleIterator<'_, Z>
219 where
220 Z: TimeZone,
221 {
222 ScheduleIterator::new(self, after)
223 }
224
225 pub fn after_owned<Z: TimeZone>(&self, after: DateTime<Z>) -> OwnedScheduleIterator<Z> {
227 OwnedScheduleIterator::new(self.clone(), after)
228 }
229
230 pub fn includes<Z>(&self, date_time: DateTime<Z>) -> bool
231 where
232 Z: TimeZone,
233 {
234 self.fields.years.includes(date_time.year() as Ordinal)
235 && self.fields.months.includes(date_time.month() as Ordinal)
236 && self
237 .fields
238 .days_of_week
239 .includes(date_time.weekday().number_from_sunday())
240 && self.fields.days_of_month.includes(date_time.day() as Ordinal)
241 && self.fields.hours.includes(date_time.hour() as Ordinal)
242 && self.fields.minutes.includes(date_time.minute() as Ordinal)
243 && self.fields.seconds.includes(date_time.second() as Ordinal)
244 }
245
246 pub fn years(&self) -> &impl TimeUnitSpec {
248 &self.fields.years
249 }
250
251 pub fn months(&self) -> &impl TimeUnitSpec {
253 &self.fields.months
254 }
255
256 pub fn days_of_month(&self) -> &impl TimeUnitSpec {
258 &self.fields.days_of_month
259 }
260
261 pub fn days_of_week(&self) -> &impl TimeUnitSpec {
263 &self.fields.days_of_week
264 }
265
266 pub fn hours(&self) -> &impl TimeUnitSpec {
268 &self.fields.hours
269 }
270
271 pub fn minutes(&self) -> &impl TimeUnitSpec {
273 &self.fields.minutes
274 }
275
276 pub fn seconds(&self) -> &impl TimeUnitSpec {
278 &self.fields.seconds
279 }
280
281 pub fn timeunitspec_eq(&self, other: &Schedule) -> bool {
282 self.fields == other.fields
283 }
284}
285
286impl Display for Schedule {
287 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
288 write!(f, "{}", self.source)
289 }
290}
291
292impl PartialEq for Schedule {
293 fn eq(&self, other: &Schedule) -> bool {
294 self.source == other.source
295 }
296}
297
298#[derive(Clone, Debug, PartialEq, Eq)]
299pub struct ScheduleFields {
300 years: Years,
301 days_of_week: DaysOfWeek,
302 months: Months,
303 days_of_month: DaysOfMonth,
304 hours: Hours,
305 minutes: Minutes,
306 seconds: Seconds,
307}
308
309impl ScheduleFields {
310 pub(crate) fn new(
311 seconds: Seconds,
312 minutes: Minutes,
313 hours: Hours,
314 days_of_month: DaysOfMonth,
315 months: Months,
316 days_of_week: DaysOfWeek,
317 years: Years,
318 ) -> ScheduleFields {
319 ScheduleFields {
320 years,
321 days_of_week,
322 months,
323 days_of_month,
324 hours,
325 minutes,
326 seconds,
327 }
328 }
329}
330
331pub struct ScheduleIterator<'a, Z>
332where
333 Z: TimeZone,
334{
335 schedule: &'a Schedule,
336 previous_datetime: Option<DateTime<Z>>,
337}
338impl<'a, Z> ScheduleIterator<'a, Z>
341where
342 Z: TimeZone,
343{
344 fn new(schedule: &'a Schedule, starting_datetime: &DateTime<Z>) -> Self {
345 ScheduleIterator {
346 schedule,
347 previous_datetime: Some(starting_datetime.clone()),
348 }
349 }
350}
351
352impl<'a, Z> Iterator for ScheduleIterator<'a, Z>
353where
354 Z: TimeZone,
355{
356 type Item = DateTime<Z>;
357
358 fn next(&mut self) -> Option<DateTime<Z>> {
359 let previous = self.previous_datetime.take()?;
360
361 if let Some(next) = self.schedule.next_after(&previous) {
362 self.previous_datetime = Some(next.clone());
363 Some(next)
364 } else {
365 None
366 }
367 }
368}
369
370impl<'a, Z> DoubleEndedIterator for ScheduleIterator<'a, Z>
371where
372 Z: TimeZone,
373{
374 fn next_back(&mut self) -> Option<Self::Item> {
375 let previous = self.previous_datetime.take()?;
376
377 if let Some(prev) = self.schedule.prev_from(&previous) {
378 self.previous_datetime = Some(prev.clone());
379 Some(prev)
380 } else {
381 None
382 }
383 }
384}
385
386pub struct OwnedScheduleIterator<Z>
388where
389 Z: TimeZone,
390{
391 schedule: Schedule,
392 previous_datetime: Option<DateTime<Z>>,
393}
394
395impl<Z> OwnedScheduleIterator<Z>
396where
397 Z: TimeZone,
398{
399 pub fn new(schedule: Schedule, starting_datetime: DateTime<Z>) -> Self {
400 Self {
401 schedule,
402 previous_datetime: Some(starting_datetime),
403 }
404 }
405}
406
407impl<Z> Iterator for OwnedScheduleIterator<Z>
408where
409 Z: TimeZone,
410{
411 type Item = DateTime<Z>;
412
413 fn next(&mut self) -> Option<DateTime<Z>> {
414 let previous = self.previous_datetime.take()?;
415
416 if let Some(next) = self.schedule.next_after(&previous) {
417 self.previous_datetime = Some(next.clone());
418 Some(next)
419 } else {
420 None
421 }
422 }
423}
424
425impl<Z: TimeZone> DoubleEndedIterator for OwnedScheduleIterator<Z> {
426 fn next_back(&mut self) -> Option<Self::Item> {
427 let previous = self.previous_datetime.take()?;
428
429 if let Some(prev) = self.schedule.prev_from(&previous) {
430 self.previous_datetime = Some(prev.clone());
431 Some(prev)
432 } else {
433 None
434 }
435 }
436}
437
438fn is_leap_year(year: Ordinal) -> bool {
439 let by_four = year % 4 == 0;
440 let by_hundred = year % 100 == 0;
441 let by_four_hundred = year % 400 == 0;
442 by_four && ((!by_hundred) || by_four_hundred)
443}
444
445fn days_in_month(month: Ordinal, year: Ordinal) -> u32 {
446 let is_leap_year = is_leap_year(year);
447 match month {
448 9 | 4 | 6 | 11 => 30,
449 2 if is_leap_year => 29,
450 2 => 28,
451 _ => 31,
452 }
453}
454
455#[cfg(test)]
456mod test {
457 use std::str::FromStr;
458
459 use super::*;
460
461 #[test]
462 fn test_next_and_prev_from() {
463 let expression = "0 5,13,40-42 17 1 Jan *";
464 let schedule = Schedule::from_str(expression).unwrap();
465
466 let next = schedule.next_after(&Utc::now());
467 println!("NEXT AFTER for {} {:?}", expression, next);
468 assert!(next.is_some());
469
470 let next2 = schedule.next_after(&next.unwrap());
471 println!("NEXT2 AFTER for {} {:?}", expression, next2);
472 assert!(next2.is_some());
473
474 let prev = schedule.prev_from(&next2.unwrap());
475 println!("PREV FROM for {} {:?}", expression, prev);
476 assert!(prev.is_some());
477 assert_eq!(prev, next);
478 }
479
480 #[test]
481 fn test_prev_from() {
482 let expression = "0 5,13,40-42 17 1 Jan *";
483 let schedule = Schedule::from_str(expression).unwrap();
484 let prev = schedule.prev_from(&Utc::now());
485 println!("PREV FROM for {} {:?}", expression, prev);
486 assert!(prev.is_some());
487 }
488
489 #[test]
490 fn test_next_after() {
491 let expression = "0 5,13,40-42 17 1 Jan *";
492 let schedule = Schedule::from_str(expression).unwrap();
493 let next = schedule.next_after(&Utc::now());
494 println!("NEXT AFTER for {} {:?}", expression, next);
495 assert!(next.is_some());
496 }
497
498 #[test]
499 fn test_upcoming_utc() {
500 let expression = "0 0,30 0,6,12,18 1,15 Jan-March Thurs";
501 let schedule = Schedule::from_str(expression).unwrap();
502 let mut upcoming = schedule.upcoming(Utc);
503 let next1 = upcoming.next();
504 assert!(next1.is_some());
505 let next2 = upcoming.next();
506 assert!(next2.is_some());
507 let next3 = upcoming.next();
508 assert!(next3.is_some());
509 println!("Upcoming 1 for {} {:?}", expression, next1);
510 println!("Upcoming 2 for {} {:?}", expression, next2);
511 println!("Upcoming 3 for {} {:?}", expression, next3);
512 }
513
514 #[test]
515 fn test_upcoming_utc_owned() {
516 let expression = "0 0,30 0,6,12,18 1,15 Jan-March Thurs";
517 let schedule = Schedule::from_str(expression).unwrap();
518 let mut upcoming = schedule.upcoming_owned(Utc);
519 let next1 = upcoming.next();
520 assert!(next1.is_some());
521 let next2 = upcoming.next();
522 assert!(next2.is_some());
523 let next3 = upcoming.next();
524 assert!(next3.is_some());
525 println!("Upcoming 1 for {} {:?}", expression, next1);
526 println!("Upcoming 2 for {} {:?}", expression, next2);
527 println!("Upcoming 3 for {} {:?}", expression, next3);
528 }
529
530 #[test]
531 fn test_upcoming_rev_utc() {
532 let expression = "0 0,30 0,6,12,18 1,15 Jan-March Thurs";
533 let schedule = Schedule::from_str(expression).unwrap();
534 let mut upcoming = schedule.upcoming(Utc).rev();
535 let prev1 = upcoming.next();
536 assert!(prev1.is_some());
537 let prev2 = upcoming.next();
538 assert!(prev2.is_some());
539 let prev3 = upcoming.next();
540 assert!(prev3.is_some());
541 println!("Prev Upcoming 1 for {} {:?}", expression, prev1);
542 println!("Prev Upcoming 2 for {} {:?}", expression, prev2);
543 println!("Prev Upcoming 3 for {} {:?}", expression, prev3);
544 }
545
546 #[test]
547 fn test_upcoming_rev_utc_owned() {
548 let expression = "0 0,30 0,6,12,18 1,15 Jan-March Thurs";
549 let schedule = Schedule::from_str(expression).unwrap();
550 let mut upcoming = schedule.upcoming_owned(Utc).rev();
551 let prev1 = upcoming.next();
552 assert!(prev1.is_some());
553 let prev2 = upcoming.next();
554 assert!(prev2.is_some());
555 let prev3 = upcoming.next();
556 assert!(prev3.is_some());
557 println!("Prev Upcoming 1 for {} {:?}", expression, prev1);
558 println!("Prev Upcoming 2 for {} {:?}", expression, prev2);
559 println!("Prev Upcoming 3 for {} {:?}", expression, prev3);
560 }
561
562 #[test]
563 fn test_upcoming_local() {
564 use chrono::Local;
565 let expression = "0 0,30 0,6,12,18 1,15 Jan-March Thurs";
566 let schedule = Schedule::from_str(expression).unwrap();
567 let mut upcoming = schedule.upcoming(Local);
568 let next1 = upcoming.next();
569 assert!(next1.is_some());
570 let next2 = upcoming.next();
571 assert!(next2.is_some());
572 let next3 = upcoming.next();
573 assert!(next3.is_some());
574 println!("Upcoming 1 for {} {:?}", expression, next1);
575 println!("Upcoming 2 for {} {:?}", expression, next2);
576 println!("Upcoming 3 for {} {:?}", expression, next3);
577 }
578
579 #[test]
580 fn test_schedule_to_string() {
581 let expression = "* 1,2,3 * * * *";
582 let schedule: Schedule = Schedule::from_str(expression).unwrap();
583 let result = String::from(schedule);
584 assert_eq!(expression, result);
585 }
586
587 #[test]
588 fn test_display_schedule() {
589 use std::fmt::Write;
590 let expression = "@monthly";
591 let schedule = Schedule::from_str(expression).unwrap();
592 let mut result = String::new();
593 write!(result, "{}", schedule).unwrap();
594 assert_eq!(expression, result);
595 }
596
597 #[test]
598 fn test_valid_from_str() {
599 let schedule = Schedule::from_str("0 0,30 0,6,12,18 1,15 Jan-March Thurs");
600 schedule.unwrap();
601 }
602
603 #[test]
604 fn test_invalid_from_str() {
605 let schedule = Schedule::from_str("cheesecake 0,30 0,6,12,18 1,15 Jan-March Thurs");
606 assert!(schedule.is_err());
607 }
608
609 #[test]
644 fn test_no_panic_on_leap_day_time_after() {
645 let dt = chrono::DateTime::parse_from_rfc3339("2024-02-29T10:00:00.000+08:00").unwrap();
646 let schedule = Schedule::from_str("0 0 0 * * * 2100").unwrap();
647 let next = schedule.after(&dt).next().unwrap();
648 assert!(next > dt); }
650
651 #[test]
652 fn test_time_unit_spec_equality() {
653 let schedule_1 = Schedule::from_str("@weekly").unwrap();
654 let schedule_2 = Schedule::from_str("0 0 0 * * 1 *").unwrap();
655 let schedule_3 = Schedule::from_str("0 0 0 * * 1-7 *").unwrap();
656 let schedule_4 = Schedule::from_str("0 0 0 * * * *").unwrap();
657 assert_ne!(schedule_1, schedule_2);
658 assert!(schedule_1.timeunitspec_eq(&schedule_2));
659 assert!(schedule_3.timeunitspec_eq(&schedule_4));
660 }
661}