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