1use chrono::offset::{LocalResult, TimeZone};
2use chrono::{DateTime, Datelike, Timelike, Utc};
3use std::fmt::{Display, Formatter, Result as FmtResult};
4use std::ops::Bound::{Included, Unbounded};
5
6#[cfg(feature = "serde")]
7use core::fmt;
8#[cfg(feature = "serde")]
9use serde::{
10 de::{self, Visitor},
11 Deserialize, Serialize, Serializer,
12};
13
14use crate::ordinal::*;
15use crate::queries::*;
16use crate::time_unit::*;
17
18impl From<Schedule> for String {
19 fn from(schedule: Schedule) -> String {
20 schedule.source
21 }
22}
23
24#[derive(Clone, Debug, Eq)]
25pub struct Schedule {
26 source: String,
27 fields: ScheduleFields,
28}
29
30impl Schedule {
31 pub(crate) fn new(source: String, fields: ScheduleFields) -> Schedule {
32 Schedule { source, fields }
33 }
34
35 fn next_after<Z>(&self, after: &DateTime<Z>) -> LocalResult<DateTime<Z>>
36 where
37 Z: TimeZone,
38 {
39 let mut query = NextAfterQuery::from(after);
40 for year in self
41 .fields
42 .years
43 .ordinals()
44 .range((Included(query.year_lower_bound()), Unbounded))
45 .cloned()
46 {
47 if year > after.year() as u32 {
49 query.reset_month();
50 query.reset_day_of_month();
51 }
52 let month_start = query.month_lower_bound();
53 if !self.fields.months.ordinals().contains(&month_start) {
54 query.reset_month();
55 }
56 let month_range = (Included(month_start), Included(Months::inclusive_max()));
57 for month in self.fields.months.ordinals().range(month_range).cloned() {
58 let day_of_month_start = query.day_of_month_lower_bound();
59 if !self
60 .fields
61 .days_of_month
62 .ordinals()
63 .contains(&day_of_month_start)
64 {
65 query.reset_day_of_month();
66 }
67 let day_of_month_end = days_in_month(month, year);
68 let day_of_month_range = (
69 Included(day_of_month_start.min(day_of_month_end)),
70 Included(day_of_month_end),
71 );
72
73 'day_loop: for day_of_month in self
74 .fields
75 .days_of_month
76 .ordinals()
77 .range(day_of_month_range)
78 .cloned()
79 {
80 let hour_start = query.hour_lower_bound();
81 if !self.fields.hours.ordinals().contains(&hour_start) {
82 query.reset_hour();
83 }
84 let hour_range = (Included(hour_start), Included(Hours::inclusive_max()));
85
86 for hour in self.fields.hours.ordinals().range(hour_range).cloned() {
87 let minute_start = query.minute_lower_bound();
88 if !self.fields.minutes.ordinals().contains(&minute_start) {
89 query.reset_minute();
90 }
91 let minute_range =
92 (Included(minute_start), Included(Minutes::inclusive_max()));
93
94 for minute in self.fields.minutes.ordinals().range(minute_range).cloned() {
95 let second_start = query.second_lower_bound();
96 if !self.fields.seconds.ordinals().contains(&second_start) {
97 query.reset_second();
98 }
99 let second_range =
100 (Included(second_start), Included(Seconds::inclusive_max()));
101
102 for second in
103 self.fields.seconds.ordinals().range(second_range).cloned()
104 {
105 let timezone = after.timezone();
106 let candidate = match timezone.with_ymd_and_hms(
107 year as i32,
108 month,
109 day_of_month,
110 hour,
111 minute,
112 second,
113 ) {
114 LocalResult::None => continue,
115 candidate => candidate,
116 };
117 if !self.fields.days_of_week.ordinals().contains(
118 &candidate
119 .clone()
120 .latest()
121 .unwrap()
122 .weekday()
123 .number_from_sunday(),
124 ) {
125 continue 'day_loop;
126 }
127 return candidate;
128 }
129 query.reset_minute();
130 } query.reset_hour();
132 } query.reset_day_of_month();
134 } query.reset_month();
136 } }
138
139 LocalResult::None
141 }
142
143 fn prev_from<Z>(&self, before: &DateTime<Z>) -> LocalResult<DateTime<Z>>
144 where
145 Z: TimeZone,
146 {
147 let mut query = PrevFromQuery::from(before);
148 for year in self
149 .fields
150 .years
151 .ordinals()
152 .range((Unbounded, Included(query.year_upper_bound())))
153 .rev()
154 .cloned()
155 {
156 let month_start = query.month_upper_bound();
157
158 if !self.fields.months.ordinals().contains(&month_start) {
159 query.reset_month();
160 }
161 let month_range = (Included(Months::inclusive_min()), Included(month_start));
162
163 for month in self
164 .fields
165 .months
166 .ordinals()
167 .range(month_range)
168 .rev()
169 .cloned()
170 {
171 let day_of_month_end = query.day_of_month_upper_bound();
172 if !self
173 .fields
174 .days_of_month
175 .ordinals()
176 .contains(&day_of_month_end)
177 {
178 query.reset_day_of_month();
179 }
180
181 let day_of_month_end = days_in_month(month, year).min(day_of_month_end);
182
183 let day_of_month_range = (
184 Included(DaysOfMonth::inclusive_min()),
185 Included(day_of_month_end),
186 );
187
188 'day_loop: for day_of_month in self
189 .fields
190 .days_of_month
191 .ordinals()
192 .range(day_of_month_range)
193 .rev()
194 .cloned()
195 {
196 let hour_start = query.hour_upper_bound();
197 if !self.fields.hours.ordinals().contains(&hour_start) {
198 query.reset_hour();
199 }
200 let hour_range = (Included(Hours::inclusive_min()), Included(hour_start));
201
202 for hour in self
203 .fields
204 .hours
205 .ordinals()
206 .range(hour_range)
207 .rev()
208 .cloned()
209 {
210 let minute_start = query.minute_upper_bound();
211 if !self.fields.minutes.ordinals().contains(&minute_start) {
212 query.reset_minute();
213 }
214 let minute_range =
215 (Included(Minutes::inclusive_min()), Included(minute_start));
216
217 for minute in self
218 .fields
219 .minutes
220 .ordinals()
221 .range(minute_range)
222 .rev()
223 .cloned()
224 {
225 let second_start = query.second_upper_bound();
226 if !self.fields.seconds.ordinals().contains(&second_start) {
227 query.reset_second();
228 }
229 let second_range =
230 (Included(Seconds::inclusive_min()), Included(second_start));
231
232 for second in self
233 .fields
234 .seconds
235 .ordinals()
236 .range(second_range)
237 .rev()
238 .cloned()
239 {
240 let timezone = before.timezone();
241 let candidate = match timezone.with_ymd_and_hms(
242 year as i32,
243 month,
244 day_of_month,
245 hour,
246 minute,
247 second,
248 ) {
249 LocalResult::None => continue,
250 some => some,
251 };
252 if !self.fields.days_of_week.ordinals().contains(
253 &candidate
254 .clone()
255 .latest()
256 .unwrap()
257 .weekday()
258 .number_from_sunday(),
259 ) {
260 continue 'day_loop;
261 }
262 return candidate;
263 }
264 query.reset_minute();
265 } query.reset_hour();
267 } query.reset_day_of_month();
269 } query.reset_month();
271 } }
273
274 LocalResult::None
276 }
277
278 pub fn upcoming<Z>(&self, timezone: Z) -> ScheduleIterator<'_, Z>
281 where
282 Z: TimeZone,
283 {
284 self.after(&timezone.from_utc_datetime(&Utc::now().naive_utc()))
285 }
286
287 pub fn upcoming_owned<Z: TimeZone>(&self, timezone: Z) -> OwnedScheduleIterator<Z> {
289 self.after_owned(timezone.from_utc_datetime(&Utc::now().naive_utc()))
290 }
291
292 pub fn after<Z>(&self, after: &DateTime<Z>) -> ScheduleIterator<'_, Z>
294 where
295 Z: TimeZone,
296 {
297 ScheduleIterator::new(self, after)
298 }
299
300 pub fn after_owned<Z: TimeZone>(&self, after: DateTime<Z>) -> OwnedScheduleIterator<Z> {
302 OwnedScheduleIterator::new(self.clone(), after)
303 }
304
305 pub fn includes<Z>(&self, date_time: DateTime<Z>) -> bool
306 where
307 Z: TimeZone,
308 {
309 self.fields.years.includes(date_time.year() as Ordinal)
310 && self.fields.months.includes(date_time.month() as Ordinal)
311 && self
312 .fields
313 .days_of_week
314 .includes(date_time.weekday().number_from_sunday())
315 && self
316 .fields
317 .days_of_month
318 .includes(date_time.day() as Ordinal)
319 && self.fields.hours.includes(date_time.hour() as Ordinal)
320 && self.fields.minutes.includes(date_time.minute() as Ordinal)
321 && self.fields.seconds.includes(date_time.second() as Ordinal)
322 }
323
324 pub fn years(&self) -> &impl TimeUnitSpec {
326 &self.fields.years
327 }
328
329 pub fn months(&self) -> &impl TimeUnitSpec {
331 &self.fields.months
332 }
333
334 pub fn days_of_month(&self) -> &impl TimeUnitSpec {
336 &self.fields.days_of_month
337 }
338
339 pub fn days_of_week(&self) -> &impl TimeUnitSpec {
341 &self.fields.days_of_week
342 }
343
344 pub fn hours(&self) -> &impl TimeUnitSpec {
346 &self.fields.hours
347 }
348
349 pub fn minutes(&self) -> &impl TimeUnitSpec {
351 &self.fields.minutes
352 }
353
354 pub fn seconds(&self) -> &impl TimeUnitSpec {
356 &self.fields.seconds
357 }
358
359 pub fn timeunitspec_eq(&self, other: &Schedule) -> bool {
360 self.fields == other.fields
361 }
362
363 pub fn source(&self) -> &str {
365 &self.source
366 }
367}
368
369impl Display for Schedule {
370 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
371 write!(f, "{}", self.source)
372 }
373}
374
375impl PartialEq for Schedule {
376 fn eq(&self, other: &Schedule) -> bool {
377 self.source == other.source
378 }
379}
380
381#[derive(Clone, Debug, PartialEq, Eq)]
382pub struct ScheduleFields {
383 years: Years,
384 days_of_week: DaysOfWeek,
385 months: Months,
386 days_of_month: DaysOfMonth,
387 hours: Hours,
388 minutes: Minutes,
389 seconds: Seconds,
390}
391
392impl ScheduleFields {
393 pub(crate) fn new(
394 seconds: Seconds,
395 minutes: Minutes,
396 hours: Hours,
397 days_of_month: DaysOfMonth,
398 months: Months,
399 days_of_week: DaysOfWeek,
400 years: Years,
401 ) -> ScheduleFields {
402 ScheduleFields {
403 years,
404 days_of_week,
405 months,
406 days_of_month,
407 hours,
408 minutes,
409 seconds,
410 }
411 }
412}
413
414pub struct ScheduleIterator<'a, Z>
415where
416 Z: TimeZone,
417{
418 schedule: &'a Schedule,
419 previous_datetime: Option<DateTime<Z>>,
420 later_datetime: Option<DateTime<Z>>,
421 earlier_datetime: Option<DateTime<Z>>,
422}
423impl<'a, Z> ScheduleIterator<'a, Z>
426where
427 Z: TimeZone,
428{
429 fn new(schedule: &'a Schedule, starting_datetime: &DateTime<Z>) -> Self {
430 ScheduleIterator {
431 schedule,
432 previous_datetime: Some(starting_datetime.clone()),
433 later_datetime: None,
434 earlier_datetime: None,
435 }
436 }
437}
438
439impl<Z> Iterator for ScheduleIterator<'_, Z>
440where
441 Z: TimeZone,
442{
443 type Item = DateTime<Z>;
444
445 fn next(&mut self) -> Option<DateTime<Z>> {
446 let previous = self.previous_datetime.take()?;
447
448 if let Some(later) = self.later_datetime.take() {
449 self.previous_datetime = Some(later.clone());
450 Some(later)
451 } else {
452 match self.schedule.next_after(&previous) {
453 LocalResult::Single(next) => {
454 self.previous_datetime = Some(next.clone());
455 Some(next)
456 }
457 LocalResult::Ambiguous(earlier, later) => {
458 self.previous_datetime = Some(earlier.clone());
459 self.later_datetime = Some(later);
460 Some(earlier)
461 }
462 LocalResult::None => None,
463 }
464 }
465 }
466}
467
468impl<Z> DoubleEndedIterator for ScheduleIterator<'_, Z>
469where
470 Z: TimeZone,
471{
472 fn next_back(&mut self) -> Option<Self::Item> {
473 let previous = self.previous_datetime.take()?;
474
475 if let Some(earlier) = self.earlier_datetime.take() {
476 self.previous_datetime = Some(earlier.clone());
477 Some(earlier)
478 } else {
479 match self.schedule.prev_from(&previous) {
480 LocalResult::Single(prev) => {
481 self.previous_datetime = Some(prev.clone());
482 Some(prev)
483 }
484 LocalResult::Ambiguous(earlier, later) => {
485 self.previous_datetime = Some(later.clone());
486 self.earlier_datetime = Some(earlier);
487 Some(later)
488 }
489 LocalResult::None => None,
490 }
491 }
492 }
493}
494
495pub struct OwnedScheduleIterator<Z>
497where
498 Z: TimeZone,
499{
500 schedule: Schedule,
501 previous_datetime: Option<DateTime<Z>>,
502 later_datetime: Option<DateTime<Z>>,
507 earlier_datetime: Option<DateTime<Z>>,
508}
509
510impl<Z> OwnedScheduleIterator<Z>
511where
512 Z: TimeZone,
513{
514 pub fn new(schedule: Schedule, starting_datetime: DateTime<Z>) -> Self {
515 Self {
516 schedule,
517 previous_datetime: Some(starting_datetime),
518 later_datetime: None,
519 earlier_datetime: None,
520 }
521 }
522}
523
524impl<Z> Iterator for OwnedScheduleIterator<Z>
525where
526 Z: TimeZone,
527{
528 type Item = DateTime<Z>;
529
530 fn next(&mut self) -> Option<DateTime<Z>> {
531 let previous = self.previous_datetime.take()?;
532
533 if let Some(later) = self.later_datetime.take() {
534 self.previous_datetime = Some(later.clone());
535 Some(later)
536 } else {
537 match self.schedule.next_after(&previous) {
538 LocalResult::Single(next) => {
539 self.previous_datetime = Some(next.clone());
540 Some(next)
541 }
542 LocalResult::Ambiguous(earlier, later) => {
547 self.previous_datetime = Some(earlier.clone());
548 self.later_datetime = Some(later);
549 Some(earlier)
550 }
551 LocalResult::None => None,
552 }
553 }
554 }
555}
556
557impl<Z: TimeZone> DoubleEndedIterator for OwnedScheduleIterator<Z> {
558 fn next_back(&mut self) -> Option<Self::Item> {
559 let previous = self.previous_datetime.take()?;
560
561 if let Some(earlier) = self.earlier_datetime.take() {
562 self.previous_datetime = Some(earlier.clone());
563 Some(earlier)
564 } else {
565 match self.schedule.prev_from(&previous) {
566 LocalResult::Single(prev) => {
567 self.previous_datetime = Some(prev.clone());
568 Some(prev)
569 }
570 LocalResult::Ambiguous(earlier, later) => {
575 self.previous_datetime = Some(later.clone());
576 self.earlier_datetime = Some(earlier);
577 Some(later)
578 }
579 LocalResult::None => None,
580 }
581 }
582 }
583}
584
585fn is_leap_year(year: Ordinal) -> bool {
586 let by_four = year % 4 == 0;
587 let by_hundred = year % 100 == 0;
588 let by_four_hundred = year % 400 == 0;
589 by_four && ((!by_hundred) || by_four_hundred)
590}
591
592fn days_in_month(month: Ordinal, year: Ordinal) -> u32 {
593 let is_leap_year = is_leap_year(year);
594 match month {
595 9 | 4 | 6 | 11 => 30,
596 2 if is_leap_year => 29,
597 2 => 28,
598 _ => 31,
599 }
600}
601
602#[cfg(feature = "serde")]
603struct ScheduleVisitor;
604
605#[cfg(feature = "serde")]
606impl Visitor<'_> for ScheduleVisitor {
607 type Value = Schedule;
608
609 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
610 formatter.write_str("a valid cron expression")
611 }
612
613 fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
618 where
619 E: de::Error,
620 {
621 Schedule::try_from(v).map_err(de::Error::custom)
622 }
623
624 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
631 where
632 E: de::Error,
633 {
634 Schedule::try_from(v).map_err(de::Error::custom)
635 }
636}
637
638#[cfg(feature = "serde")]
639impl Serialize for Schedule {
640 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
641 where
642 S: Serializer,
643 {
644 serializer.serialize_str(self.source())
645 }
646}
647
648#[cfg(feature = "serde")]
649impl<'de> Deserialize<'de> for Schedule {
650 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
651 where
652 D: serde::Deserializer<'de>,
653 {
654 deserializer.deserialize_string(ScheduleVisitor)
664 }
665}
666
667#[cfg(test)]
668mod test {
669 use chrono::Duration;
670 #[cfg(feature = "serde")]
671 use serde_test::{assert_tokens, Token};
672
673 use super::*;
674 use std::str::FromStr;
675
676 #[cfg(feature = "serde")]
677 #[test]
678 fn test_ser_de_schedule_tokens() {
679 let schedule = Schedule::from_str("* * * * * * *").expect("valid format");
680 assert_tokens(&schedule, &[Token::String("* * * * * * *")])
681 }
682
683 #[cfg(feature = "serde")]
684 #[test]
685 fn test_invalid_ser_de_schedule_tokens() {
686 use serde_test::assert_de_tokens_error;
687
688 assert_de_tokens_error::<Schedule>(
689 &[Token::String(
690 "definitively an invalid value for a cron schedule!",
691 )],
692 "definitively an invalid value for a cron schedule!\n\
693 ^\n\
694 The 'Seconds' field does not support using names. 'definitively' specified.",
695 );
696 }
697
698 #[cfg(feature = "serde")]
699 #[test]
700 fn test_ser_de_schedule_shorthand() {
701 let serialized = postcard::to_stdvec(&Schedule::try_from("@hourly").expect("valid format"))
702 .expect("serializable schedule");
703
704 let schedule: Schedule =
705 postcard::from_bytes(&serialized).expect("deserializable schedule");
706
707 let starting_date = Utc.with_ymd_and_hms(2017, 2, 25, 22, 29, 36).unwrap();
708 assert!([
709 Utc.with_ymd_and_hms(2017, 2, 25, 23, 0, 0).unwrap(),
710 Utc.with_ymd_and_hms(2017, 2, 26, 0, 0, 0).unwrap(),
711 Utc.with_ymd_and_hms(2017, 2, 26, 1, 0, 0).unwrap(),
712 ]
713 .into_iter()
714 .eq(schedule.after(&starting_date).take(3)));
715 }
716
717 #[cfg(feature = "serde")]
718 #[test]
719 fn test_ser_de_schedule_period_values_range() {
720 let serialized =
721 postcard::to_stdvec(&Schedule::try_from("0 0 0 1-31/10 * ?").expect("valid format"))
722 .expect("serializable schedule");
723
724 let schedule: Schedule =
725 postcard::from_bytes(&serialized).expect("deserializable schedule");
726
727 let starting_date = Utc.with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap();
728 assert!([
729 Utc.with_ymd_and_hms(2020, 1, 11, 0, 0, 0).unwrap(),
730 Utc.with_ymd_and_hms(2020, 1, 21, 0, 0, 0).unwrap(),
731 Utc.with_ymd_and_hms(2020, 1, 31, 0, 0, 0).unwrap(),
732 Utc.with_ymd_and_hms(2020, 2, 1, 0, 0, 0).unwrap(),
733 Utc.with_ymd_and_hms(2020, 2, 11, 0, 0, 0).unwrap(),
734 Utc.with_ymd_and_hms(2020, 2, 21, 0, 0, 0).unwrap(),
735 Utc.with_ymd_and_hms(2020, 3, 1, 0, 0, 0).unwrap(),
736 ]
737 .into_iter()
738 .eq(schedule.after(&starting_date).take(7)));
739 }
740
741 #[test]
742 fn test_next_and_prev_from() {
743 let expression = "0 5,13,40-42 17 1 Jan *";
744 let schedule = Schedule::from_str(expression).unwrap();
745
746 let next = schedule.next_after(&Utc::now());
747 println!("NEXT AFTER for {} {:?}", expression, next);
748 assert!(next.single().is_some());
749
750 let next2 = schedule.next_after(&next.unwrap());
751 println!("NEXT2 AFTER for {} {:?}", expression, next2);
752 assert!(next2.single().is_some());
753
754 let prev = schedule.prev_from(&next2.unwrap());
755 println!("PREV FROM for {} {:?}", expression, prev);
756 assert!(prev.single().is_some());
757 assert_eq!(prev, next);
758
759 let prev2 = schedule.prev_from(&(next2.unwrap() + Duration::nanoseconds(100)));
760 println!("PREV2 FROM for {} {:?}", expression, prev2);
761 assert!(prev2.single().is_some());
762 assert_eq!(prev2, next2);
763 }
764
765 #[test]
766 fn test_next_after_past_date_next_year() {
767 let starting_point = Utc.with_ymd_and_hms(2021, 10, 27, 0, 0, 0).unwrap();
769
770 let expression = "0 5 17 1 6 ? 2022".to_string();
773 let schedule = Schedule::from_str(&expression).unwrap();
774 let next = schedule.next_after(&starting_point);
775 println!("NEXT AFTER for {} {:?}", expression, next);
776 assert!(next.single().is_some());
777 }
778
779 #[test]
780 fn test_prev_from() {
781 let expression = "0 5,13,40-42 17 1 Jan *";
782 let schedule = Schedule::from_str(expression).unwrap();
783 let prev = schedule.prev_from(&Utc::now());
784 println!("PREV FROM for {} {:?}", expression, prev);
785 assert!(prev.single().is_some());
786 }
787
788 #[test]
789 fn test_next_after() {
790 let expression = "0 5,13,40-42 17 1 Jan *";
791 let schedule = Schedule::from_str(expression).unwrap();
792 let next = schedule.next_after(&Utc::now());
793 println!("NEXT AFTER for {} {:?}", expression, next);
794 assert!(next.single().is_some());
795 }
796
797 #[test]
798 fn test_upcoming_utc() {
799 let expression = "0 0,30 0,6,12,18 1,15 Jan-March Thurs";
800 let schedule = Schedule::from_str(expression).unwrap();
801 let mut upcoming = schedule.upcoming(Utc);
802 let next1 = upcoming.next();
803 assert!(next1.is_some());
804 let next2 = upcoming.next();
805 assert!(next2.is_some());
806 let next3 = upcoming.next();
807 assert!(next3.is_some());
808 println!("Upcoming 1 for {} {:?}", expression, next1);
809 println!("Upcoming 2 for {} {:?}", expression, next2);
810 println!("Upcoming 3 for {} {:?}", expression, next3);
811 }
812
813 #[test]
814 fn test_upcoming_utc_owned() {
815 let expression = "0 0,30 0,6,12,18 1,15 Jan-March Thurs";
816 let schedule = Schedule::from_str(expression).unwrap();
817 let mut upcoming = schedule.upcoming_owned(Utc);
818 let next1 = upcoming.next();
819 assert!(next1.is_some());
820 let next2 = upcoming.next();
821 assert!(next2.is_some());
822 let next3 = upcoming.next();
823 assert!(next3.is_some());
824 println!("Upcoming 1 for {} {:?}", expression, next1);
825 println!("Upcoming 2 for {} {:?}", expression, next2);
826 println!("Upcoming 3 for {} {:?}", expression, next3);
827 }
828
829 #[test]
830 fn test_upcoming_rev_utc() {
831 let expression = "0 0,30 0,6,12,18 1,15 Jan-March Thurs";
832 let schedule = Schedule::from_str(expression).unwrap();
833 let mut upcoming = schedule.upcoming(Utc).rev();
834 let prev1 = upcoming.next();
835 assert!(prev1.is_some());
836 let prev2 = upcoming.next();
837 assert!(prev2.is_some());
838 let prev3 = upcoming.next();
839 assert!(prev3.is_some());
840 println!("Prev Upcoming 1 for {} {:?}", expression, prev1);
841 println!("Prev Upcoming 2 for {} {:?}", expression, prev2);
842 println!("Prev Upcoming 3 for {} {:?}", expression, prev3);
843 }
844
845 #[test]
846 fn test_upcoming_rev_utc_owned() {
847 let expression = "0 0,30 0,6,12,18 1,15 Jan-March Thurs";
848 let schedule = Schedule::from_str(expression).unwrap();
849 let mut upcoming = schedule.upcoming_owned(Utc).rev();
850 let prev1 = upcoming.next();
851 assert!(prev1.is_some());
852 let prev2 = upcoming.next();
853 assert!(prev2.is_some());
854 let prev3 = upcoming.next();
855 assert!(prev3.is_some());
856 println!("Prev Upcoming 1 for {} {:?}", expression, prev1);
857 println!("Prev Upcoming 2 for {} {:?}", expression, prev2);
858 println!("Prev Upcoming 3 for {} {:?}", expression, prev3);
859 }
860
861 #[test]
862 fn test_upcoming_local() {
863 use chrono::Local;
864 let expression = "0 0,30 0,6,12,18 1,15 Jan-March Thurs";
865 let schedule = Schedule::from_str(expression).unwrap();
866 let mut upcoming = schedule.upcoming(Local);
867 let next1 = upcoming.next();
868 assert!(next1.is_some());
869 let next2 = upcoming.next();
870 assert!(next2.is_some());
871 let next3 = upcoming.next();
872 assert!(next3.is_some());
873 println!("Upcoming 1 for {} {:?}", expression, next1);
874 println!("Upcoming 2 for {} {:?}", expression, next2);
875 println!("Upcoming 3 for {} {:?}", expression, next3);
876 }
877
878 #[test]
879 fn test_schedule_to_string() {
880 let expression = "* 1,2,3 * * * *";
881 let schedule: Schedule = Schedule::from_str(expression).unwrap();
882 let result = String::from(schedule);
883 assert_eq!(expression, result);
884 }
885
886 #[test]
887 fn test_display_schedule() {
888 use std::fmt::Write;
889 let expression = "@monthly";
890 let schedule = Schedule::from_str(expression).unwrap();
891 let mut result = String::new();
892 write!(result, "{}", schedule).unwrap();
893 assert_eq!(expression, result);
894 }
895
896 #[test]
897 fn test_valid_from_str() {
898 let schedule = Schedule::from_str("0 0,30 0,6,12,18 1,15 Jan-March Thurs");
899 schedule.unwrap();
900 }
901
902 #[test]
903 fn test_invalid_from_str() {
904 let schedule = Schedule::from_str("cheesecake 0,30 0,6,12,18 1,15 Jan-March Thurs");
905 assert!(schedule.is_err());
906 }
907
908 #[test]
909 fn test_no_panic_on_nonexistent_time_after() {
910 use chrono::offset::TimeZone;
911 use chrono_tz::Tz;
912
913 let schedule_tz: Tz = "Europe/London".parse().unwrap();
914 let dt = schedule_tz
915 .with_ymd_and_hms(2019, 10, 27, 0, 3, 29)
916 .unwrap()
917 .checked_add_signed(chrono::Duration::hours(1)) .unwrap();
919 let schedule = Schedule::from_str("* * * * * Sat,Sun *").unwrap();
920 let next = schedule.after(&dt).next().unwrap();
921 assert!(next > dt); }
923
924 #[test]
925 fn test_no_panic_on_nonexistent_time_before() {
926 use chrono::offset::TimeZone;
927 use chrono_tz::Tz;
928
929 let schedule_tz: Tz = "Europe/London".parse().unwrap();
930 let dt = schedule_tz
931 .with_ymd_and_hms(2019, 10, 27, 0, 3, 29)
932 .unwrap()
933 .checked_add_signed(chrono::Duration::hours(1)) .unwrap();
935 let schedule = Schedule::from_str("* * * * * Sat,Sun *").unwrap();
936 let prev = schedule.after(&dt).nth_back(1).unwrap();
937 assert!(prev < dt); }
939
940 #[test]
941 fn test_no_panic_on_leap_day_time_after() {
942 let dt = chrono::DateTime::parse_from_rfc3339("2024-02-29T10:00:00.000+08:00").unwrap();
943 let schedule = Schedule::from_str("0 0 0 * * * 2100").unwrap();
944 let next = schedule.after(&dt).next().unwrap();
945 assert!(next > dt); }
947
948 #[test]
949 fn test_time_unit_spec_equality() {
950 let schedule_1 = Schedule::from_str("@weekly").unwrap();
951 let schedule_2 = Schedule::from_str("0 0 0 * * 1 *").unwrap();
952 let schedule_3 = Schedule::from_str("0 0 0 * * 1-7 *").unwrap();
953 let schedule_4 = Schedule::from_str("0 0 0 * * * *").unwrap();
954 assert_ne!(schedule_1, schedule_2);
955 assert!(schedule_1.timeunitspec_eq(&schedule_2));
956 assert!(schedule_3.timeunitspec_eq(&schedule_4));
957 }
958
959 #[test]
960 fn test_dst_ambiguous_time_after() {
961 use chrono_tz::Tz;
962
963 let schedule_tz: Tz = "America/Chicago".parse().unwrap();
964 let dt = schedule_tz
965 .with_ymd_and_hms(2022, 11, 5, 23, 30, 0)
966 .unwrap();
967 let schedule = Schedule::from_str("0 0 * * * * *").unwrap();
968 let times = schedule
969 .after(&dt)
970 .map(|x| x.to_string())
971 .take(5)
972 .collect::<Vec<_>>();
973 let expected_times = [
974 "2022-11-06 00:00:00 CDT".to_string(),
975 "2022-11-06 01:00:00 CDT".to_string(),
976 "2022-11-06 01:00:00 CST".to_string(), "2022-11-06 02:00:00 CST".to_string(),
978 "2022-11-06 03:00:00 CST".to_string(),
979 ];
980
981 assert_eq!(times.as_slice(), expected_times.as_slice());
982 }
983
984 #[test]
985 fn test_dst_ambiguous_time_before() {
986 use chrono_tz::Tz;
987
988 let schedule_tz: Tz = "America/Chicago".parse().unwrap();
989 let dt = schedule_tz.with_ymd_and_hms(2022, 11, 6, 3, 30, 0).unwrap();
990 let schedule = Schedule::from_str("0 0 * * * * *").unwrap();
991 let times = schedule
992 .after(&dt)
993 .map(|x| x.to_string())
994 .rev()
995 .take(5)
996 .collect::<Vec<_>>();
997 let expected_times = [
998 "2022-11-06 03:00:00 CST".to_string(),
999 "2022-11-06 02:00:00 CST".to_string(),
1000 "2022-11-06 01:00:00 CST".to_string(),
1001 "2022-11-06 01:00:00 CDT".to_string(), "2022-11-06 00:00:00 CDT".to_string(),
1003 ];
1004
1005 assert_eq!(times.as_slice(), expected_times.as_slice());
1006 }
1007}