1use crate::{
2 pattern::{Pattern, PatternItem, PatternType, PatternValueType},
3 utils, CronError, Result,
4};
5use chrono::{offset::LocalResult, DateTime, Datelike, TimeDelta, TimeZone, Timelike};
6#[cfg(feature = "tz")]
7use chrono_tz::Tz;
8use std::{fmt::Display, str::FromStr};
9
10pub const MIN_YEAR: u16 = 1970;
12pub const MAX_YEAR: u16 = 2099;
14
15pub(crate) const MIN_YEAR_STR: &str = "1970";
16
17#[derive(Debug, Clone, PartialEq, Eq, Hash)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22#[cfg_attr(feature = "serde", serde(try_from = "String"))]
23#[cfg_attr(feature = "serde", serde(into = "String"))]
24pub struct Schedule {
25 year: Pattern,
26 month: Pattern,
27 dom: Pattern,
28 dow: Pattern,
29 hour: Pattern,
30 minute: Pattern,
31 second: Pattern,
32 #[cfg(feature = "tz")]
33 tz: Option<Tz>,
34}
35
36impl Schedule {
37 pub fn new(pattern: impl Into<String>) -> Result<Self> {
43 let pattern = pattern.into();
44 let mut elements: Vec<&str> = pattern.split_whitespace().collect();
45 #[cfg(feature = "tz")]
46 let mut tz = None;
47
48 #[cfg(feature = "tz")]
50 if elements.len() >= 2 {
51 let tz_elements: Vec<&str> = elements[0].split('=').collect();
52 if tz_elements.len() == 2 && tz_elements[0].to_uppercase() == "TZ" {
53 let tz_str = tz_elements[1];
54 if let Ok(tz_value) = Tz::from_str(tz_str) {
55 tz = Some(tz_value);
56 elements.remove(0);
57 } else {
58 return Err(CronError::InvalidTimeZone(tz_str.to_string()));
59 }
60 }
61 }
62
63 if elements.len() == 1 {
65 match elements[0] {
67 "@yearly" | "@annually" => elements = vec!["0", "0", "0", "1", "1", "?", "*"],
68 "@monthly" => elements = vec!["0", "0", "0", "1", "*", "?", "*"],
69 "@weekly" => elements = vec!["0", "0", "0", "?", "*", "0", "*"],
70 "@daily" | "@midnight" => elements = vec!["0", "0", "0", "*", "*", "*", "*"],
71 "@hourly" => elements = vec!["0", "0", "*", "*", "*", "*", "*"],
72 _ => return Err(CronError::InvalidCronSchedule(pattern)),
73 }
74 } else if elements.len() == 5 {
75 elements.insert(0, "0");
76 elements.insert(6, "*");
77 } else if elements.len() == 6 {
78 elements.insert(6, "*");
79 } else if elements.len() != 7 {
80 return Err(CronError::InvalidCronSchedule(pattern));
81 }
82
83 let schedule = Self {
85 second: Pattern::parse(PatternType::Seconds, elements[0])?,
86 minute: Pattern::parse(PatternType::Minutes, elements[1])?,
87 hour: Pattern::parse(PatternType::Hours, elements[2])?,
88 dom: Pattern::parse(PatternType::Doms, elements[3])?,
89 month: Pattern::parse(PatternType::Months, elements[4])?,
90 dow: Pattern::parse(PatternType::Dows, elements[5])?,
91 year: Pattern::parse(PatternType::Years, elements[6])?,
92 #[cfg(feature = "tz")]
93 tz,
94 };
95
96 match (schedule.dom.pattern(), schedule.dow.pattern()) {
98 (PatternItem::Any, PatternItem::Any) => return Err(CronError::InvalidDaysPattern(pattern)),
99 (PatternItem::All, _) | (_, PatternItem::All) | (PatternItem::Any, _) | (_, PatternItem::Any) => {}
100 (_, _) => {
101 return Err(CronError::InvalidDaysPattern(pattern));
102 }
103 }
104
105 Ok(schedule)
106 }
107
108 #[cfg(not(feature = "tz"))]
121 #[inline]
122 pub fn upcoming<T: TimeZone>(&self, current: &DateTime<T>) -> Option<DateTime<T>> {
123 self.upcoming_impl(current)
124 }
125
126 #[cfg(feature = "tz")]
128 pub fn upcoming<Tz: TimeZone>(&self, current: &DateTime<Tz>) -> Option<DateTime<Tz>> {
129 if let Some(schedule_tz) = &self.tz {
130 let current_tz = current.timezone();
131 let current = current.with_timezone(schedule_tz);
132 let result = self.upcoming_impl(¤t);
133 result.map(|dt| dt.with_timezone(¤t_tz))
134 } else {
135 self.upcoming_impl(current)
136 }
137 }
138
139 fn upcoming_impl<Tz: TimeZone>(&self, current: &DateTime<Tz>) -> Option<DateTime<Tz>> {
143 let mut current = if current.nanosecond() > 0 {
145 current
146 .with_nanosecond(0)
147 .unwrap()
148 .checked_add_signed(TimeDelta::seconds(1))
149 .unwrap()
150 } else {
151 current.clone()
152 };
153
154 let mut year = Some(current.year() as PatternValueType);
155 let mut month = Some(current.month() as PatternValueType);
156 let mut dom = Some(current.day() as PatternValueType);
157 let mut hour = Some(current.hour() as PatternValueType);
158 let mut minute = Some(current.minute() as PatternValueType);
159 let mut second = Some(current.second() as PatternValueType);
160 let mut first_iteration = true; while year.is_none()
163 || month.is_none()
164 || dom.is_none()
165 || hour.is_none()
166 || minute.is_none()
167 || second.is_none()
168 || first_iteration
169 {
170 first_iteration = false;
171
172 if year.is_none() {
174 return None;
175 } else if month.is_none() {
176 inc_year(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second)?;
177 } else if dom.is_none() {
178 inc_month(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second)?;
179 } else if hour.is_none() {
180 inc_dom(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second)?;
181 } else if minute.is_none() {
182 inc_hour(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second)?;
183 } else if second.is_none() {
184 inc_minute(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second)?;
185 }
186
187 current = match current.timezone().with_ymd_and_hms(
188 year? as i32,
189 month? as u32,
190 dom? as u32,
191 hour? as u32,
192 minute? as u32,
193 second? as u32,
194 ) {
195 LocalResult::Single(updated_current) => updated_current,
196 LocalResult::Ambiguous(earliest, _latest) => earliest,
197 LocalResult::None => {
198 minute = None;
199 continue;
200 }
201 };
202
203 year = self.year.next(&mut current);
206 if year.is_some() {
207 month = self.month.next(&mut current);
208 year = Some(current.year() as PatternValueType);
209 if month.is_some() {
210 dom = match (self.dom.pattern(), self.dow.pattern()) {
212 (PatternItem::All, PatternItem::All) => self.dom.next(&mut current),
213 (PatternItem::All, PatternItem::Any) => self.dom.next(&mut current),
214 (PatternItem::All, _) => self.dow.next(&mut current),
215 (PatternItem::Any, PatternItem::All) => self.dow.next(&mut current),
216 (PatternItem::Any, PatternItem::Any) => unreachable!(),
217 (PatternItem::Any, _) => self.dow.next(&mut current),
218 (_, PatternItem::All) => self.dom.next(&mut current),
219 (_, PatternItem::Any) => self.dom.next(&mut current),
220 (_, _) => unreachable!(),
221 };
222 year = Some(current.year() as PatternValueType);
223 month = Some(current.month() as PatternValueType);
224 if dom.is_some() {
225 hour = self.hour.next(&mut current);
226 year = Some(current.year() as PatternValueType);
227 month = Some(current.month() as PatternValueType);
228 dom = Some(current.day() as PatternValueType);
229 if hour.is_some() {
230 minute = self.minute.next(&mut current);
231 year = Some(current.year() as PatternValueType);
232 month = Some(current.month() as PatternValueType);
233 dom = Some(current.day() as PatternValueType);
234 hour = Some(current.hour() as PatternValueType);
235 if minute.is_some() {
236 second = self.second.next(&mut current);
237 year = Some(current.year() as PatternValueType);
238 month = Some(current.month() as PatternValueType);
239 dom = Some(current.day() as PatternValueType);
240 hour = Some(current.hour() as PatternValueType);
241 minute = Some(current.minute() as PatternValueType);
242 }
243 }
244 }
245 }
246 }
247 }
248
249 match current.timezone().with_ymd_and_hms(
250 year? as i32,
251 month? as u32,
252 dom? as u32,
253 hour? as u32,
254 minute? as u32,
255 second? as u32,
256 ) {
257 LocalResult::Single(current) => Some(current),
258 LocalResult::Ambiguous(earliest, _latest) => Some(earliest),
259 LocalResult::None => None,
260 }
261 }
262
263 #[inline]
265 pub fn iter<Tz: TimeZone>(&self, current: &DateTime<Tz>) -> impl Iterator<Item = DateTime<Tz>> {
266 ScheduleIterator {
267 schedule: self.clone(),
268 next: self.upcoming(current),
269 }
270 }
271
272 #[inline]
274 pub fn into_iter<Tz: TimeZone>(self, current: &DateTime<Tz>) -> impl Iterator<Item = DateTime<Tz>> {
275 let next = self.upcoming(current);
276 ScheduleIterator { schedule: self, next }
277 }
278}
279
280#[derive(Debug, Clone, PartialEq, Eq, Hash)]
282pub(crate) struct ScheduleIterator<Tz: TimeZone> {
283 pub(crate) schedule: Schedule,
284 pub(crate) next: Option<DateTime<Tz>>,
285}
286
287impl<Tz: TimeZone> Iterator for ScheduleIterator<Tz> {
288 type Item = DateTime<Tz>;
289
290 fn next(&mut self) -> Option<Self::Item> {
291 let current = self.next.take()?;
292 self.next = self
293 .schedule
294 .upcoming(¤t.clone().checked_add_signed(TimeDelta::seconds(1))?);
295 Some(current)
296 }
297}
298
299impl From<Schedule> for String {
300 fn from(value: Schedule) -> Self {
301 value.to_string()
302 }
303}
304
305impl From<&Schedule> for String {
306 fn from(value: &Schedule) -> Self {
307 value.to_string()
308 }
309}
310
311impl TryFrom<String> for Schedule {
312 type Error = CronError;
313
314 fn try_from(value: String) -> Result<Self> {
315 Self::new(value)
316 }
317}
318
319impl TryFrom<&String> for Schedule {
320 type Error = CronError;
321
322 fn try_from(value: &String) -> Result<Self> {
323 Self::new(value)
324 }
325}
326
327impl TryFrom<&str> for Schedule {
328 type Error = CronError;
329
330 fn try_from(value: &str) -> Result<Self> {
331 Self::new(value)
332 }
333}
334
335impl FromStr for Schedule {
336 type Err = CronError;
337
338 fn from_str(s: &str) -> Result<Self> {
339 Self::new(s)
340 }
341}
342
343impl Display for Schedule {
344 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
345 #[cfg(not(feature = "tz"))]
346 {
347 write!(
348 f,
349 "{} {} {} {} {} {} {}",
350 self.second, self.minute, self.hour, self.dom, self.month, self.dow, self.year
351 )
352 }
353
354 #[cfg(feature = "tz")]
355 if let Some(tz) = self.tz {
356 write!(
357 f,
358 "TZ={} {} {} {} {} {} {} {}",
359 tz, self.second, self.minute, self.hour, self.dom, self.month, self.dow, self.year
360 )
361 } else {
362 write!(
363 f,
364 "{} {} {} {} {} {} {}",
365 self.second, self.minute, self.hour, self.dom, self.month, self.dow, self.year
366 )
367 }
368 }
369}
370
371#[inline]
373fn inc_year(
374 year: &mut Option<PatternValueType>,
375 month: &mut Option<PatternValueType>,
376 dom: &mut Option<PatternValueType>,
377 hour: &mut Option<PatternValueType>,
378 minute: &mut Option<PatternValueType>,
379 second: &mut Option<PatternValueType>,
380) -> Option<PatternValueType> {
381 if (*year)? < MAX_YEAR {
382 *year = Some((*year)? + 1);
383 *month = Some(1);
384 *dom = Some(1);
385 *hour = Some(0);
386 *minute = Some(0);
387 *second = Some(0);
388
389 *year
390 } else {
391 *year = None;
392 None
393 }
394}
395
396#[inline]
398fn inc_month(
399 year: &mut Option<PatternValueType>,
400 month: &mut Option<PatternValueType>,
401 dom: &mut Option<PatternValueType>,
402 hour: &mut Option<PatternValueType>,
403 minute: &mut Option<PatternValueType>,
404 second: &mut Option<PatternValueType>,
405) -> Option<PatternValueType> {
406 if (*month)? < 12 {
407 *month = Some((*month)? + 1);
408 *dom = Some(1);
409 *hour = Some(0);
410 *minute = Some(0);
411 *second = Some(0);
412
413 *month
414 } else {
415 inc_year(year, month, dom, hour, minute, second)?;
416 *month
417 }
418}
419
420#[inline]
422fn inc_dom(
423 year: &mut Option<PatternValueType>,
424 month: &mut Option<PatternValueType>,
425 dom: &mut Option<PatternValueType>,
426 hour: &mut Option<PatternValueType>,
427 minute: &mut Option<PatternValueType>,
428 second: &mut Option<PatternValueType>,
429) -> Option<PatternValueType> {
430 if (*dom)? < utils::days_in_month((*year)?, (*month)?) {
431 *dom = Some((*dom)? + 1);
432 *hour = Some(0);
433 *minute = Some(0);
434 *second = Some(0);
435
436 *dom
437 } else {
438 inc_month(year, month, dom, hour, minute, second)?;
439 *dom
440 }
441}
442
443#[inline]
445fn inc_hour(
446 year: &mut Option<PatternValueType>,
447 month: &mut Option<PatternValueType>,
448 dom: &mut Option<PatternValueType>,
449 hour: &mut Option<PatternValueType>,
450 minute: &mut Option<PatternValueType>,
451 second: &mut Option<PatternValueType>,
452) -> Option<PatternValueType> {
453 if (*hour)? < 23 {
454 *hour = Some((*hour)? + 1);
455 *minute = Some(0);
456 *second = Some(0);
457
458 *hour
459 } else {
460 inc_dom(year, month, dom, hour, minute, second)?;
461 *hour
462 }
463}
464
465#[inline]
467fn inc_minute(
468 year: &mut Option<PatternValueType>,
469 month: &mut Option<PatternValueType>,
470 dom: &mut Option<PatternValueType>,
471 hour: &mut Option<PatternValueType>,
472 minute: &mut Option<PatternValueType>,
473 second: &mut Option<PatternValueType>,
474) -> Option<PatternValueType> {
475 if (*minute)? < 59 {
476 *minute = Some((*minute)? + 1);
477 *second = Some(0);
478
479 *minute
480 } else {
481 inc_hour(year, month, dom, hour, minute, second)?;
482 *minute
483 }
484}
485
486#[cfg(test)]
487mod tests {
488 use super::*;
489 use chrono::DateTime;
490 use rstest::rstest;
491 use rstest_reuse::{apply, template};
492 use std::time::Duration;
493
494 #[rstest]
495 #[case("* 0 0 1 1 *", "2024-01-01T00:00:21Z", "2024-01-01T00:00:21+00:00")]
496 #[case("* 0 0 1 1 *", "2024-01-01T01:00:25Z", "2025-01-01T00:00:00+00:00")]
497 #[case("*/5 * * * * *", "2024-01-01T00:00:00Z", "2024-01-01T00:00:00+00:00")]
498 #[case("*/5 * * * * *", "2024-01-01T00:00:01Z", "2024-01-01T00:00:05+00:00")]
499 #[case("0 */15 * * * *", "2024-01-01T00:00:00Z", "2024-01-01T00:00:00+00:00")]
500 #[case("0 */15 * * * *", "2024-01-01T00:01:00Z", "2024-01-01T00:15:00+00:00")]
501 #[case("0 */30 9-17 * * 1-5", "2024-01-01T09:00:00Z", "2024-01-01T09:00:00+00:00")]
502 #[case("0 */30 9-17 * * 1-5", "2024-01-01T09:15:00Z", "2024-01-01T09:30:00+00:00")]
503 #[case("0 */5 * * * *", "2024-01-01T00:01:00Z", "2024-01-01T00:05:00+00:00")]
504 #[case("0 0 */2 * * *", "2024-01-01T01:00:00Z", "2024-01-01T02:00:00+00:00")]
505 #[case("0 0 0 ? * 1-5", "2024-01-01T00:00:00Z", "2024-01-01T00:00:00+00:00")]
506 #[case("0 0 0 ? * 1-5", "2024-01-01T00:00:01Z", "2024-01-02T00:00:00+00:00")]
507 #[case("0 0 0 ? * 1-5", "2024-01-05T00:00:01Z", "2024-01-08T00:00:00+00:00")]
508 #[case("0 0 0 * * 1#1", "2024-01-01T00:00:00Z", "2024-01-01T00:00:00+00:00")]
509 #[case("0 0 0 * * 1#1", "2024-01-02T00:00:00Z", "2024-02-05T00:00:00+00:00")]
510 #[case("0 0 0 * * 5L", "2024-01-01T00:00:00Z", "2024-01-26T00:00:00+00:00")]
511 #[case("0 0 0 * * 5L", "2024-01-26T00:00:01Z", "2024-02-23T00:00:00+00:00")]
512 #[case("0 0 0 * * 5L", "2024-02-23T00:00:00Z", "2024-02-23T00:00:00+00:00")]
513 #[case("0 0 0 * * 6,0", "2024-01-01T00:00:00Z", "2024-01-06T00:00:00+00:00")]
514 #[case("0 0 0 * * 6,0", "2024-01-06T00:00:01Z", "2024-01-07T00:00:00+00:00")]
515 #[case("0 0 0 * * 6,0", "2024-01-07T00:00:00Z", "2024-01-07T00:00:00+00:00")]
516 #[case("0 0 0 * * 6,0", "2024-01-07T00:00:01Z", "2024-01-13T00:00:00+00:00")]
517 #[case("0 0 0 * * MON", "2024-01-01T00:00:00Z", "2024-01-01T00:00:00+00:00")]
518 #[case("0 0 0 * * SUN", "2024-01-01T00:00:00Z", "2024-01-07T00:00:00+00:00")]
519 #[case("0 0 0 1 */2 *", "2024-01-01T00:00:00Z", "2024-01-01T00:00:00+00:00")]
520 #[case("0 0 0 1 */2 *", "2024-02-01T00:00:00Z", "2024-03-01T00:00:00+00:00")]
521 #[case("0 0 0 1 */3 * 1999", "1999-01-01T00:00:00Z", "1999-01-01T00:00:00+00:00")]
522 #[case("0 0 0 1 */3 * 1999", "1999-02-01T00:00:00Z", "1999-04-01T00:00:00+00:00")]
523 #[case("0 0 0 1 */3 *", "2024-01-01T00:00:00Z", "2024-01-01T00:00:00+00:00")]
524 #[case("0 0 0 1 */3 *", "2024-02-01T00:00:00Z", "2024-04-01T00:00:00+00:00")]
525 #[case("0 0 0 1 1 * *", "2024-01-01T00:00:00Z", "2024-01-01T00:00:00+00:00")]
526 #[case("0 0 0 1 1 * *", "2024-01-01T00:00:01Z", "2025-01-01T00:00:00+00:00")]
527 #[case("0 0 0 1 1 * 1970", "2024-01-01T00:00:00Z", "None")]
528 #[case("0 0 0 1 1 * 1999", "1999-01-01T00:00:00Z", "1999-01-01T00:00:00+00:00")]
529 #[case("0 0 0 1 1 * 1999", "1999-01-01T00:00:01Z", "None")]
530 #[case("0 0 0 1 1 * 2024-2025", "2024-01-01T00:00:00Z", "2024-01-01T00:00:00+00:00")]
531 #[case("0 0 0 1 1 * 2024-2025", "2025-01-01T00:00:00Z", "2025-01-01T00:00:00+00:00")]
532 #[case("0 0 0 1 1 * 2024-2025", "2026-01-01T00:00:00Z", "None")]
533 #[case("0 0 0 1 1,6,12 *", "2024-01-01T00:00:00Z", "2024-01-01T00:00:00+00:00")]
534 #[case("0 0 0 1 1,6,12 *", "2024-02-01T00:00:00Z", "2024-06-01T00:00:00+00:00")]
535 #[case("0 0 0 1,15 * ?", "2024-01-01T00:00:00Z", "2024-01-01T00:00:00+00:00")]
536 #[case("0 0 0 1,15 * ?", "2024-01-01T00:00:01Z", "2024-01-15T00:00:00+00:00")]
537 #[case("0 0 0 1,15 * ?", "2024-01-15T00:00:01Z", "2024-02-01T00:00:00+00:00")]
538 #[case("0 0 0 1,15,L * ?", "2024-01-01T00:00:00Z", "2024-01-01T00:00:00+00:00")]
539 #[case("0 0 0 1,15,L * ?", "2024-01-15T00:00:01Z", "2024-01-31T00:00:00+00:00")]
540 #[case("0 0 0 1,15,L * ?", "2024-01-31T00:00:01Z", "2024-02-01T00:00:00+00:00")]
541 #[case("0 0 0 1W * *", "2024-01-01T00:00:00Z", "2024-01-01T00:00:00+00:00")]
542 #[case("0 0 0 1W * *", "2024-04-01T00:00:00Z", "2024-04-01T00:00:00+00:00")]
543 #[case("0 0 0 28-31 2 *", "2024-02-28T00:00:00Z", "2024-02-28T00:00:00+00:00")]
544 #[case("0 0 0 28-31 2 *", "2024-02-28T00:00:01Z", "2024-02-29T00:00:00+00:00")]
545 #[case("0 0 0 28-31 2 *", "2025-02-28T00:00:00Z", "2025-02-28T00:00:00+00:00")]
546 #[case("0 0 0 28-31 2 *", "2025-02-28T00:00:01Z", "2026-02-28T00:00:00+00:00")]
547 #[case("0 0 0 28,29,30,31 2 *", "2024-02-28T00:00:00Z", "2024-02-28T00:00:00+00:00")]
548 #[case("0 0 0 28,29,30,31 2 *", "2024-02-28T00:00:01Z", "2024-02-29T00:00:00+00:00")]
549 #[case("0 0 0 28,29,30,31 2 *", "2025-02-28T00:00:00Z", "2025-02-28T00:00:00+00:00")]
550 #[case("0 0 0 28,29,30,31 2 *", "2025-02-28T00:00:01Z", "2026-02-28T00:00:00+00:00")]
551 #[case("0 0 0 29 2 * 1999", "1999-01-01T00:00:00Z", "None")]
552 #[case("0 0 0 29 2 * 1999/3", "1999-01-01T00:00:00Z", "2008-02-29T00:00:00+00:00")]
553 #[case("0 0 0 29 2 *", "2024-01-01T00:00:00Z", "2024-02-29T00:00:00+00:00")]
554 #[case("0 0 0 29 2 *", "2024-03-01T00:00:00Z", "2028-02-29T00:00:00+00:00")]
555 #[case("0 0 0 29-31 2 *", "2024-02-29T00:00:00Z", "2024-02-29T00:00:00+00:00")]
556 #[case("0 0 0 29-31 2 *", "2024-02-29T00:00:01Z", "2028-02-29T00:00:00+00:00")]
557 #[case("0 0 0 29-31 2 *", "2025-02-01T00:00:00Z", "2028-02-29T00:00:00+00:00")]
558 #[case("0 0 0 29,30,31 2 *", "2024-02-29T00:00:00Z", "2024-02-29T00:00:00+00:00")]
559 #[case("0 0 0 29,30,31 2 *", "2024-02-29T00:00:01Z", "2028-02-29T00:00:00+00:00")]
560 #[case("0 0 0 29,30,31 2 *", "2025-02-01T00:00:00Z", "2028-02-29T00:00:00+00:00")]
561 #[case("0 0 0 31 */2 * 1999", "1999-01-01T00:00:00Z", "1999-01-31T00:00:00+00:00")]
562 #[case("0 0 0 31 */2 *", "2024-01-01T00:00:00Z", "2024-01-31T00:00:00+00:00")]
563 #[case("0 0 0 31 */2 *", "2024-02-01T00:00:00Z", "2024-03-31T00:00:00+00:00")]
564 #[case("0 0 0 L * * 1999", "1999-01-15T00:00:00Z", "1999-01-31T00:00:00+00:00")]
565 #[case("0 0 0 L * * 1999", "1999-02-15T00:00:00Z", "1999-02-28T00:00:00+00:00")]
566 #[case("0 0 0 L * *", "2024-01-15T00:00:00Z", "2024-01-31T00:00:00+00:00")]
567 #[case("0 0 0 L * *", "2024-02-15T00:00:00Z", "2024-02-29T00:00:00+00:00")]
568 #[case("0 0 0 L * *", "2024-04-15T00:00:00Z", "2024-04-30T00:00:00+00:00")]
569 #[case("0 0 1 1 *", "2024-01-01T00:00:00Z", "2024-01-01T00:00:00+00:00")]
570 #[case("0 0 1 1 *", "2024-01-01T00:00:01Z", "2025-01-01T00:00:00+00:00")]
571 #[case("0 0 12 ? * 2-6", "2024-01-01T00:00:00Z", "2024-01-02T12:00:00+00:00")]
572 #[case("0 0 12 ? * 2-6", "2024-01-06T12:00:00Z", "2024-01-06T12:00:00+00:00")]
573 #[case("0 0 12 ? * 2-6", "2024-01-06T12:00:01Z", "2024-01-09T12:00:00+00:00")]
574 #[case("0 0 12 * * MON-FRI 1999", "1999-01-01T00:00:00Z", "1999-01-01T12:00:00+00:00")]
575 #[case("0 0 12 * * MON-FRI 1999", "1999-01-01T12:00:01Z", "1999-01-04T12:00:00+00:00")]
576 #[case("0 0 12 * * MON-FRI", "2024-01-01T00:00:00Z", "2024-01-01T12:00:00+00:00")]
577 #[case("0 0 12 * * MON-FRI", "2024-01-06T00:00:00Z", "2024-01-08T12:00:00+00:00")]
578 #[case("0 0 12 1-7 * *", "2024-01-01T00:00:00Z", "2024-01-01T12:00:00+00:00")]
579 #[case("0 0 12 1-7 * *", "2024-01-07T12:00:00Z", "2024-01-07T12:00:00+00:00")]
580 #[case("0 0 12 1-7 * *", "2024-01-07T12:00:01Z", "2024-02-01T12:00:00+00:00")]
581 #[case("0 0 12 1,15 * ?", "2024-01-01T00:00:00Z", "2024-01-01T12:00:00+00:00")]
582 #[case("0 0 12 1,15 * ?", "2024-01-01T12:00:01Z", "2024-01-15T12:00:00+00:00")]
583 #[case("0 0 6 * * 1-5", "2024-01-01T00:00:00Z", "2024-01-01T06:00:00+00:00")]
584 #[case("0 0 6 * * 1-5", "2024-01-06T00:00:00Z", "2024-01-08T06:00:00+00:00")]
585 #[case("0 0 9 * * 1", "2024-01-01T00:00:00Z", "2024-01-01T09:00:00+00:00")]
586 #[case("0 0 9 * * 1", "2024-01-01T09:00:01Z", "2024-01-08T09:00:00+00:00")]
587 #[case("0 0 9 * * 1#1", "2024-04-12T00:00:00Z", "2024-05-06T09:00:00+00:00")]
588 #[case("0 0 9 * * 6#4", "2024-11-30T09:00:00Z", "2024-12-28T09:00:00+00:00")]
589 #[case("0 0 9-17 * * 1-5", "2024-01-01T08:00:00Z", "2024-01-01T09:00:00+00:00")]
590 #[case("0 0 9-17 * * 1-5", "2024-01-01T17:00:01Z", "2024-01-02T09:00:00+00:00")]
591 #[case("0 15,45 9-17 * * 1-5", "2024-01-01T09:00:00Z", "2024-01-01T09:15:00+00:00")]
592 #[case("0 15,45 9-17 * * 1-5", "2024-01-01T09:15:01Z", "2024-01-01T09:45:00+00:00")]
593 #[case("0 30 0 1 * *", "2024-01-01T00:00:00Z", "2024-01-01T00:30:00+00:00")]
594 #[case("30 0 0 1 * *", "2024-01-01T00:00:00Z", "2024-01-01T00:00:30+00:00")]
595 #[case("30 0 0 1 * *", "2024-01-01T00:00:30Z", "2024-01-01T00:00:30+00:00")]
596 #[case("30 0 0 1 * *", "2024-01-01T00:00:30.001Z", "2024-02-01T00:00:30+00:00")]
597 #[case("25 * * * *", "2024-01-01T00:21:21Z", "2024-01-01T00:25:00+00:00")]
598 #[case("1 2 29-31 * *", "2024-01-01T00:00:21Z", "2024-01-29T02:01:00+00:00")]
599 #[case("1 2 29-31 * *", "2024-01-31T00:00:21Z", "2024-01-31T02:01:00+00:00")]
600 #[case("1 2 29-31 * *", "2024-02-01T00:00:21Z", "2024-02-29T02:01:00+00:00")]
601 #[case("1 2 29-31 * *", "2024-03-31T00:00:21Z", "2024-03-31T02:01:00+00:00")]
602 #[case("1 2 29-31 * *", "2025-01-01T00:00:21Z", "2025-01-29T02:01:00+00:00")]
603 #[case("1 2 29-31 * *", "2025-02-01T00:00:21Z", "2025-03-29T02:01:00+00:00")]
604 #[case("1 2 29-31 * *", "2025-03-31T00:00:21Z", "2025-03-31T02:01:00+00:00")]
605 #[case("@yearly", "2025-03-31T00:00:21Z", "2026-01-01T00:00:00+00:00")]
606 #[case("@annually", "2025-03-31T00:00:21Z", "2026-01-01T00:00:00+00:00")]
607 #[case("@monthly", "2025-03-31T00:00:21Z", "2025-04-01T00:00:00+00:00")]
608 #[case("@weekly", "2025-03-31T00:00:21Z", "2025-04-06T00:00:00+00:00")]
609 #[case("@daily", "2025-03-31T00:00:21Z", "2025-04-01T00:00:00+00:00")]
610 #[case("@midnight", "2025-03-31T00:00:21Z", "2025-04-01T00:00:00+00:00")]
611 #[case("@hourly", "2025-03-31T00:00:21Z", "2025-03-31T01:00:00+00:00")]
612 #[timeout(Duration::from_secs(1))]
613 fn test_schedule_upcoming(#[case] pattern: &str, #[case] current: &str, #[case] expected: &str) {
614 let schedule = Schedule::new(pattern).unwrap();
615 let current = DateTime::parse_from_rfc3339(current).unwrap();
616 let next = schedule.upcoming(¤t);
617
618 if expected == "None" {
619 assert!(
620 next.is_none(),
621 "pattern = {pattern}, schedule = {schedule:?}, current = {current}, next = {next:?}"
622 );
623 } else {
624 assert!(
625 next.is_some(),
626 "pattern = {pattern}, schedule = {schedule:?}, current = {current}, next = {next:?}"
627 );
628
629 assert_eq!(
630 next.unwrap().to_rfc3339(),
631 expected,
632 "pattern = {pattern}, schedule = {schedule:?}, current = {current}, next = {next:?}"
633 );
634 }
635 }
636
637 #[test]
638 fn test_inc_year() {
639 let mut year = Some(2024);
640 let mut month = Some(1);
641 let mut dom = Some(1);
642 let mut hour = Some(0);
643 let mut minute = Some(0);
644 let mut second = Some(0);
645
646 assert_eq!(
647 inc_year(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
648 Some(2025)
649 );
650 assert_eq!(year, Some(2025));
651 assert_eq!(month, Some(1));
652 assert_eq!(dom, Some(1));
653 assert_eq!(hour, Some(0));
654 assert_eq!(minute, Some(0));
655 assert_eq!(second, Some(0));
656
657 year = Some(MAX_YEAR);
658 assert_eq!(
659 inc_year(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
660 None
661 );
662 assert_eq!(year, None);
663 }
664
665 #[test]
666 fn test_inc_month() {
667 let mut year = Some(2024);
668 let mut month = Some(1);
669 let mut dom = Some(1);
670 let mut hour = Some(0);
671 let mut minute = Some(0);
672 let mut second = Some(0);
673
674 assert_eq!(
675 inc_month(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
676 Some(2)
677 );
678 assert_eq!(year, Some(2024));
679 assert_eq!(month, Some(2));
680 assert_eq!(dom, Some(1));
681 assert_eq!(hour, Some(0));
682 assert_eq!(minute, Some(0));
683 assert_eq!(second, Some(0));
684
685 year = Some(2024);
686 month = Some(12);
687 assert_eq!(
688 inc_month(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
689 Some(1)
690 );
691 assert_eq!(year, Some(2025));
692 assert_eq!(month, Some(1));
693 assert_eq!(dom, Some(1));
694 assert_eq!(hour, Some(0));
695 assert_eq!(minute, Some(0));
696 assert_eq!(second, Some(0));
697
698 year = Some(MAX_YEAR);
699 month = Some(12);
700 assert_eq!(
701 inc_month(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
702 None
703 );
704 assert_eq!(year, None);
705 }
706
707 #[test]
708 fn test_inc_dom() {
709 let mut year = Some(2024);
710 let mut month = Some(1);
711 let mut dom = Some(1);
712 let mut hour = Some(0);
713 let mut minute = Some(0);
714 let mut second = Some(0);
715
716 assert_eq!(
717 inc_dom(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
718 Some(2)
719 );
720 assert_eq!(year, Some(2024));
721 assert_eq!(month, Some(1));
722 assert_eq!(dom, Some(2));
723 assert_eq!(hour, Some(0));
724 assert_eq!(minute, Some(0));
725 assert_eq!(second, Some(0));
726
727 year = Some(2024);
728 month = Some(1);
729 dom = Some(31);
730 assert_eq!(
731 inc_dom(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
732 Some(1)
733 );
734 assert_eq!(year, Some(2024));
735 assert_eq!(month, Some(2));
736 assert_eq!(dom, Some(1));
737 assert_eq!(hour, Some(0));
738 assert_eq!(minute, Some(0));
739 assert_eq!(second, Some(0));
740
741 year = Some(2024);
742 month = Some(12);
743 dom = Some(31);
744 assert_eq!(
745 inc_dom(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
746 Some(1)
747 );
748 assert_eq!(year, Some(2025));
749 assert_eq!(month, Some(1));
750 assert_eq!(dom, Some(1));
751 assert_eq!(hour, Some(0));
752 assert_eq!(minute, Some(0));
753 assert_eq!(second, Some(0));
754
755 year = Some(2024);
756 month = Some(2);
757 dom = Some(28);
758 assert_eq!(
759 inc_dom(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
760 Some(29)
761 );
762 assert_eq!(year, Some(2024));
763 assert_eq!(month, Some(2));
764 assert_eq!(dom, Some(29));
765 assert_eq!(hour, Some(0));
766 assert_eq!(minute, Some(0));
767 assert_eq!(second, Some(0));
768
769 year = Some(2025);
770 month = Some(2);
771 dom = Some(28);
772 assert_eq!(
773 inc_dom(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
774 Some(1)
775 );
776 assert_eq!(year, Some(2025));
777 assert_eq!(month, Some(3));
778 assert_eq!(dom, Some(1));
779 assert_eq!(hour, Some(0));
780 assert_eq!(minute, Some(0));
781 assert_eq!(second, Some(0));
782
783 year = Some(MAX_YEAR);
784 month = Some(12);
785 dom = Some(31);
786 assert_eq!(
787 inc_dom(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
788 None
789 );
790 assert_eq!(year, None);
791 }
792
793 #[test]
794 fn test_inc_hour() {
795 let mut year = Some(2024);
796 let mut month = Some(1);
797 let mut dom = Some(1);
798 let mut hour = Some(0);
799 let mut minute = Some(0);
800 let mut second = Some(0);
801
802 assert_eq!(
803 inc_hour(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
804 Some(1)
805 );
806 assert_eq!(year, Some(2024));
807 assert_eq!(month, Some(1));
808 assert_eq!(dom, Some(1));
809 assert_eq!(hour, Some(1));
810 assert_eq!(minute, Some(0));
811 assert_eq!(second, Some(0));
812
813 year = Some(2024);
814 month = Some(1);
815 dom = Some(1);
816 hour = Some(23);
817 assert_eq!(
818 inc_hour(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
819 Some(0)
820 );
821 assert_eq!(year, Some(2024));
822 assert_eq!(month, Some(1));
823 assert_eq!(dom, Some(2));
824 assert_eq!(hour, Some(0));
825 assert_eq!(minute, Some(0));
826 assert_eq!(second, Some(0));
827
828 year = Some(2024);
829 month = Some(12);
830 dom = Some(31);
831 hour = Some(23);
832 assert_eq!(
833 inc_hour(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
834 Some(0)
835 );
836 assert_eq!(year, Some(2025));
837 assert_eq!(month, Some(1));
838 assert_eq!(dom, Some(1));
839 assert_eq!(hour, Some(0));
840 assert_eq!(minute, Some(0));
841 assert_eq!(second, Some(0));
842
843 year = Some(MAX_YEAR);
844 month = Some(12);
845 dom = Some(31);
846 hour = Some(23);
847 assert_eq!(
848 inc_hour(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
849 None
850 );
851 assert_eq!(year, None);
852 }
853
854 #[test]
855 fn test_inc_minute() {
856 let mut year = Some(2024);
857 let mut month = Some(1);
858 let mut dom = Some(1);
859 let mut hour = Some(0);
860 let mut minute = Some(0);
861 let mut second = Some(0);
862
863 assert_eq!(
864 inc_minute(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
865 Some(1)
866 );
867 assert_eq!(year, Some(2024));
868 assert_eq!(month, Some(1));
869 assert_eq!(dom, Some(1));
870 assert_eq!(hour, Some(0));
871 assert_eq!(minute, Some(1));
872 assert_eq!(second, Some(0));
873
874 year = Some(2024);
875 month = Some(1);
876 dom = Some(1);
877 hour = Some(23);
878 minute = Some(59);
879 assert_eq!(
880 inc_minute(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
881 Some(0)
882 );
883 assert_eq!(year, Some(2024));
884 assert_eq!(month, Some(1));
885 assert_eq!(dom, Some(2));
886 assert_eq!(hour, Some(0));
887 assert_eq!(minute, Some(0));
888 assert_eq!(second, Some(0));
889
890 year = Some(2024);
891 month = Some(12);
892 dom = Some(31);
893 hour = Some(23);
894 minute = Some(59);
895 assert_eq!(
896 inc_minute(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
897 Some(0)
898 );
899 assert_eq!(year, Some(2025));
900 assert_eq!(month, Some(1));
901 assert_eq!(dom, Some(1));
902 assert_eq!(hour, Some(0));
903 assert_eq!(minute, Some(0));
904 assert_eq!(second, Some(0));
905
906 year = Some(MAX_YEAR);
907 month = Some(12);
908 dom = Some(31);
909 hour = Some(23);
910 minute = Some(59);
911 assert_eq!(
912 inc_minute(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
913 None
914 );
915 assert_eq!(year, None);
916 }
917
918 #[template]
919 #[rstest]
920 #[case("* * * * * * *", "* * * * * * *")]
921 #[case("* * * * * *", "* * * * * * *")]
922 #[case("* * * * *", "0 * * * * * *")]
923 #[case("*/5 * * * *", "0 0/5 * * * * *")]
924 #[case("0 */15 */6 * * *", "0 0/15 0/6 * * * *")]
925 #[case("0 0 ? 1 0", "0 0 0 ? 1 0 *")]
926 #[case("0 0 * * SUN", "0 0 0 * * 0 *")]
927 #[case("0 0 * 1 0", "0 0 0 * 1 0 *")]
928 #[case("0 0 1 1 ?", "0 0 0 1 1 ? *")]
929 #[case("0 0 1 1 *", "0 0 0 1 1 * *")]
930 #[case("0 0 12 * * MON", "0 0 12 * * 1 *")]
931 #[case("0 0 22 * * 1-5", "0 0 22 * * 1-5 *")]
932 #[case("0 0/5 14,18 * * *", "0 0/5 14,18 * * * *")]
933 #[case("0 15 10 ? * MON-FRI", "0 15 10 ? * 1-5 *")]
934 #[case("1,22,45 5/2 0-15 1-6/2 */6 * 2000", "1,22,45 5/2 0-15 1-6/2 1/6 * 2000")]
935 #[case("23 0-20/2 * * *", "0 23 0-20/2 * * * *")]
936 #[case("30 0 1 1 * *", "30 0 1 1 * * *")]
937 #[case("5,10,15,20 * * * *", "0 5,10,15,20 * * * * *")]
938 #[case("@yearly", "0 0 0 1 1 ? *")]
939 #[case("@annually", "0 0 0 1 1 ? *")]
940 #[case("@monthly", "0 0 0 1 * ? *")]
941 #[case("@weekly", "0 0 0 ? * 0 *")]
942 #[case("@daily", "0 0 0 * * * *")]
943 #[case("@midnight", "0 0 0 * * * *")]
944 #[case("@hourly", "0 0 * * * * *")]
945 fn valid_schedules_to_test(#[case] input: &str) {}
946
947 #[apply(valid_schedules_to_test)]
948 fn test_schedule_display_and_new(#[case] input: &str, #[case] expected: &str) {
949 assert_eq!(Schedule::new(input).unwrap().to_string(), expected);
950 }
951
952 #[apply(valid_schedules_to_test)]
953 fn test_try_from_string(#[case] input: &str, #[case] _expected: &str) {
954 let schedule1 = Schedule::new(input).unwrap();
956 let schedule2 = Schedule::try_from(input).unwrap();
957 assert_eq!(schedule1, schedule2);
958
959 let tst_string = String::from(input);
961 let schedule2 = Schedule::try_from(&tst_string).unwrap();
962 assert_eq!(schedule1, schedule2);
963
964 let schedule2 = Schedule::try_from(tst_string).unwrap();
966 assert_eq!(schedule1, schedule2);
967
968 let schedule2 = Schedule::from_str(input).unwrap();
970 assert_eq!(schedule1, schedule2);
971 }
972
973 #[rstest]
974 #[timeout(Duration::from_secs(1))]
975 fn test_schedule_iter() {
976 let schedule = Schedule::new("0 0 12 * 1 MON 2024").unwrap();
977 let mut iter = schedule.iter(&DateTime::parse_from_rfc3339("2024-01-01T00:00:00+00:00").unwrap());
978
979 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T12:00:00+00:00");
980 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-08T12:00:00+00:00");
981 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-15T12:00:00+00:00");
982 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-22T12:00:00+00:00");
983 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-29T12:00:00+00:00");
984 assert_eq!(iter.next(), None);
985 }
986
987 #[rstest]
988 #[timeout(Duration::from_secs(1))]
989 fn test_schedule_iter_every_second() {
990 let schedule = Schedule::new("* * * * * *").unwrap();
991 let mut iter = schedule.iter(&DateTime::parse_from_rfc3339("2024-01-01T00:00:01+00:00").unwrap());
992
993 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T00:00:01+00:00");
994 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T00:00:02+00:00");
995 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T00:00:03+00:00");
996 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T00:00:04+00:00");
997 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T00:00:05+00:00");
998 }
999
1000 #[rstest]
1001 #[timeout(Duration::from_secs(1))]
1002 fn test_schedule_iter_every_minute() {
1003 let schedule = Schedule::new("* * * * *").unwrap();
1004 let mut iter = schedule.iter(&DateTime::parse_from_rfc3339("2024-01-01T00:00:01+00:00").unwrap());
1005
1006 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T00:01:00+00:00");
1007 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T00:02:00+00:00");
1008 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T00:03:00+00:00");
1009 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T00:04:00+00:00");
1010 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T00:05:00+00:00");
1011 }
1012
1013 #[rstest]
1014 #[timeout(Duration::from_secs(1))]
1015 fn test_schedule_iter_every_hour() {
1016 let schedule = Schedule::new("13 * * * *").unwrap();
1017 let mut iter = schedule.iter(&DateTime::parse_from_rfc3339("2024-01-01T07:01:01+00:00").unwrap());
1018
1019 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T07:13:00+00:00");
1020 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T08:13:00+00:00");
1021 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T09:13:00+00:00");
1022 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T10:13:00+00:00");
1023 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T11:13:00+00:00");
1024 }
1025
1026 #[rstest]
1027 #[timeout(Duration::from_secs(1))]
1028 fn test_schedule_iter_every_day() {
1029 let schedule = Schedule::new("22 5 * * *").unwrap();
1030 let mut iter = schedule.iter(&DateTime::parse_from_rfc3339("2024-01-01T04:01:01+00:00").unwrap());
1031
1032 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T05:22:00+00:00");
1033 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-02T05:22:00+00:00");
1034 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-03T05:22:00+00:00");
1035 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-04T05:22:00+00:00");
1036 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-05T05:22:00+00:00");
1037 }
1038
1039 #[rstest]
1040 #[timeout(Duration::from_secs(1))]
1041 fn test_schedule_iter_every_month() {
1042 let schedule = Schedule::new("13 13 12 * *").unwrap();
1043 let mut iter = schedule.iter(&DateTime::parse_from_rfc3339("2024-01-12T13:13:01+00:00").unwrap());
1044
1045 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-02-12T13:13:00+00:00");
1046 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-03-12T13:13:00+00:00");
1047 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-04-12T13:13:00+00:00");
1048 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-05-12T13:13:00+00:00");
1049 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-06-12T13:13:00+00:00");
1050 }
1051
1052 #[rstest]
1053 #[timeout(Duration::from_secs(1))]
1054 fn test_schedule_iter_every_weekday() {
1055 let schedule = Schedule::new("13 13 ? * *").unwrap();
1056 let mut iter = schedule.iter(&DateTime::parse_from_rfc3339("2024-01-12T13:13:01+00:00").unwrap());
1057
1058 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-13T13:13:00+00:00");
1059 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-14T13:13:00+00:00");
1060 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-15T13:13:00+00:00");
1061 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-16T13:13:00+00:00");
1062 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-17T13:13:00+00:00");
1063 }
1064
1065 #[rstest]
1066 #[timeout(Duration::from_secs(1))]
1067 fn test_schedule_iter_every_year() {
1068 let schedule = Schedule::new("30 12 22 6 ?").unwrap();
1069 let mut iter = schedule.iter(&DateTime::parse_from_rfc3339("2021-01-12T13:13:01+00:00").unwrap());
1070
1071 assert_eq!(iter.next().unwrap().to_rfc3339(), "2021-06-22T12:30:00+00:00");
1072 assert_eq!(iter.next().unwrap().to_rfc3339(), "2022-06-22T12:30:00+00:00");
1073 assert_eq!(iter.next().unwrap().to_rfc3339(), "2023-06-22T12:30:00+00:00");
1074 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-06-22T12:30:00+00:00");
1075 assert_eq!(iter.next().unwrap().to_rfc3339(), "2025-06-22T12:30:00+00:00");
1076 }
1077
1078 #[rstest]
1079 #[timeout(Duration::from_secs(1))]
1080 fn test_schedule_into_iter() {
1081 let schedule = Schedule::new("0 0 12 * 1 MON 2024").unwrap();
1082 let mut iter = schedule.into_iter(&DateTime::parse_from_rfc3339("2024-01-01T00:00:00+00:00").unwrap());
1083
1084 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T12:00:00+00:00");
1085 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-08T12:00:00+00:00");
1086 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-15T12:00:00+00:00");
1087 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-22T12:00:00+00:00");
1088 assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-29T12:00:00+00:00");
1089 assert_eq!(iter.next(), None);
1090 }
1091
1092 #[template]
1093 #[rstest]
1094 #[case("* * * *")]
1095 #[case("* * ? * ?")]
1096 #[case("* * 10 * 1")]
1097 #[case("* * * * 2/2")]
1098 #[case("0 1 2 3 * * 1969")]
1099 #[case("0 0 0 ? * 6-1")]
1100 #[case("@minutely")]
1101 fn invalid_schedules_to_test(#[case] input: &str) {}
1102
1103 #[apply(invalid_schedules_to_test)]
1104 fn test_invalid_schedule_constructor(#[case] input: &str) {
1105 assert!(Schedule::new(input).is_err(), "input = {input}");
1106 }
1107
1108 #[apply(invalid_schedules_to_test)]
1109 fn test_try_from_invalid_string(#[case] input: &str) {
1110 assert!(Schedule::try_from(input).is_err(), "input = {input}");
1111 assert!(Schedule::from_str(input).is_err(), "input = {input}");
1112 }
1113
1114 #[rstest]
1115 #[case("0 0 0 29 2 ? 2024", "2024-02-28T23:59:59Z", "2024-02-29T00:00:00+00:00")]
1117 #[case("0 0 0 29 2 ? 2024-2099", "2024-03-01T23:59:59Z", "2028-02-29T00:00:00+00:00")]
1118 #[case("0 0 0 29 2 ? 2025-2099", "2024-02-01T23:59:59Z", "2028-02-29T00:00:00+00:00")]
1119 #[case("0 0 0 29 2 ? 1971/7", "1970-02-01T23:59:59Z", "1992-02-29T00:00:00+00:00")]
1120 #[case("0 0 0 29 2 ? 2024-2027", "2024-02-29T00:00:01Z", "None")]
1121 #[case("0 0 0 1 * ? 2024", "2024-01-31T23:59:59Z", "2024-02-01T00:00:00+00:00")]
1123 #[case("0 0 0 1 * ? 2024", "2024-02-28T23:59:59Z", "2024-03-01T00:00:00+00:00")]
1124 #[case("0 0 0 1 * ? 2024", "2024-02-29T23:59:59Z", "2024-03-01T00:00:00+00:00")]
1125 #[case("0 0 0 1 * ? 2024", "2024-03-31T23:59:59Z", "2024-04-01T00:00:00+00:00")]
1126 #[case("0 0 0 1 * ? 2024", "2024-04-30T23:59:59Z", "2024-05-01T00:00:00+00:00")]
1127 #[case("0 0 0 1 * ? 2024", "2024-11-30T23:59:59Z", "2024-12-01T00:00:00+00:00")]
1128 #[case("0 0 0 1 1 ? 2025", "2024-12-31T23:59:59Z", "2025-01-01T00:00:00+00:00")]
1130 #[case("0 0 0 L 2 ? 2024", "2024-02-28T23:59:59Z", "2024-02-29T00:00:00+00:00")]
1132 #[case("0 0 0 L 2 ? 2025", "2024-02-28T23:59:59Z", "2025-02-28T00:00:00+00:00")]
1133 #[case("0 0 0 ? 2 4L 2024", "2024-02-28T23:59:59Z", "2024-02-29T00:00:00+00:00")]
1135 #[case("0 0 0 15W * ? 2024", "2024-01-14T23:59:59Z", "2024-01-15T00:00:00+00:00")]
1137 #[case("0 0 0 ? * 1#3 2024", "2024-01-20T23:59:59Z", "2024-02-19T00:00:00+00:00")]
1139 #[timeout(Duration::from_secs(1))]
1140 fn test_edge_cases(#[case] pattern: &str, #[case] current: &str, #[case] expected: &str) {
1141 let schedule = Schedule::new(pattern).unwrap();
1142 let current = DateTime::parse_from_rfc3339(current).unwrap();
1143 let next = schedule.upcoming(¤t);
1144
1145 if expected == "None" {
1146 assert!(
1147 next.is_none(),
1148 "pattern = {pattern}, schedule = {schedule:?}, current = {current}, next = {next:?}"
1149 );
1150 } else {
1151 assert!(
1152 next.is_some(),
1153 "pattern = {pattern}, schedule = {schedule:?}, current = {current}, next = {next:?}"
1154 );
1155
1156 assert_eq!(
1157 next.unwrap().to_rfc3339(),
1158 expected,
1159 "pattern = {pattern}, schedule = {schedule:?}, current = {current}, next = {next:?}"
1160 );
1161 }
1162 }
1163
1164 #[apply(valid_schedules_to_test)]
1165 fn test_schedule_to_string(#[case] input: &str, #[case] expected: &str) {
1166 let schedule = Schedule::new(input).unwrap();
1167
1168 let string: String = (&schedule).into();
1169 assert_eq!(string, expected);
1170
1171 let string: String = schedule.into();
1172 assert_eq!(string, expected);
1173 }
1174
1175 #[cfg(feature = "tz")]
1176 mod tz {
1177 use super::super::*;
1178 use chrono::{Local, Utc};
1179 use rstest::rstest;
1180 use rstest_reuse::{apply, template};
1181 use std::{fmt::Debug, time::Duration};
1182
1183 #[template]
1184 #[rstest]
1185 #[case("TZ=Europe/Kyiv * * * * * * *", "TZ=Europe/Kyiv * * * * * * *")]
1186 #[case("TZ=Europe/London * * * * * *", "TZ=Europe/London * * * * * * *")]
1187 #[case("TZ=UTC * * * * *", "TZ=UTC 0 * * * * * *")]
1188 #[case("TZ=US/Pacific */5 * * * *", "TZ=US/Pacific 0 0/5 * * * * *")]
1189 #[case("TZ=EET 0 */15 */6 * * *", "TZ=EET 0 0/15 0/6 * * * *")]
1190 #[case("TZ=Asia/Tokyo @yearly", "TZ=Asia/Tokyo 0 0 0 1 1 ? *")]
1191 #[case("Tz=Asia/Tokyo @yearly", "TZ=Asia/Tokyo 0 0 0 1 1 ? *")]
1192 #[case("tz=Asia/Tokyo @yearly", "TZ=Asia/Tokyo 0 0 0 1 1 ? *")]
1193 #[case("tz=Europe/Paris @yearly", "TZ=Europe/Paris 0 0 0 1 1 ? *")]
1194 fn valid_schedules_to_test(#[case] input: &str, #[case] expected: &str) {}
1195
1196 #[apply(valid_schedules_to_test)]
1197 fn test_schedule_display_and_new(#[case] input: &str, #[case] expected: &str) {
1198 assert_eq!(Schedule::new(input).unwrap().to_string(), expected);
1199 }
1200
1201 #[apply(valid_schedules_to_test)]
1202 fn test_try_from_string(#[case] input: &str, #[case] _expected: &str) {
1203 let schedule1 = Schedule::new(input).unwrap();
1205 let schedule2 = Schedule::try_from(input).unwrap();
1206 assert_eq!(schedule1, schedule2);
1207
1208 let tst_string = String::from(input);
1210 let schedule2 = Schedule::try_from(&tst_string).unwrap();
1211 assert_eq!(schedule1, schedule2);
1212
1213 let schedule2 = Schedule::try_from(tst_string).unwrap();
1215 assert_eq!(schedule1, schedule2);
1216
1217 let schedule2 = Schedule::from_str(input).unwrap();
1219 assert_eq!(schedule1, schedule2);
1220 }
1221
1222 #[template]
1223 #[rstest]
1224 #[case("TZ * * * *")]
1225 #[case("TZ= * * ? * ?")]
1226 #[case("tz=UTC * * 10 * 1")]
1227 #[case("TZ=Aaa/Bbb * * * * 2/2")]
1228 #[case("TZ=Aaa/Bbb * * * * *")]
1229 #[case("TZ =UTC * * * * *")]
1230 #[case("TZ= UTC * * * * *")]
1231 #[case("TZ = UTC * * * * *")]
1232 #[case("TZ= 0 0 0 ? * 1-6")]
1233 #[case("tz= @hourly")]
1234 fn invalid_schedules_to_test(#[case] input: &str) {}
1235
1236 #[apply(invalid_schedules_to_test)]
1237 fn test_invalid_schedule_constructor(#[case] input: &str) {
1238 assert!(Schedule::new(input).is_err(), "input = {input}");
1239 }
1240
1241 #[apply(invalid_schedules_to_test)]
1242 fn test_try_from_invalid_string(#[case] input: &str) {
1243 assert!(Schedule::try_from(input).is_err(), "input = {input}");
1244 assert!(Schedule::from_str(input).is_err(), "input = {input}");
1245 }
1246
1247 #[rstest]
1248 #[case("TZ=Europe/Kyiv @monthly", "2025-03-31T00:00:21Z", "2025-03-31T21:00:00+00:00")]
1249 #[case("TZ=Europe/Kyiv @monthly", "2025-03-31T00:00:21+02:00", "2025-03-31T23:00:00+02:00")]
1250 #[case("TZ=Europe/Kyiv @monthly", "2025-11-30T00:00:21Z", "2025-11-30T22:00:00+00:00")]
1251 #[case("TZ=EET @hourly", "2000-03-25T23:00:01Z", "2000-03-26T00:00:00+00:00")]
1252 #[case("TZ=EET @hourly", "2000-03-26T00:00:01Z", "2000-03-26T01:00:00+00:00")]
1253 #[case("TZ=EET @hourly", "2000-03-26T01:00:01Z", "2000-03-26T02:00:00+00:00")]
1254 #[case("TZ=EET @hourly", "2000-10-28T22:00:01Z", "2000-10-28T23:00:00+00:00")]
1255 #[case("TZ=EET @hourly", "2000-10-28T23:00:01Z", "2000-10-29T00:00:00+00:00")] #[case("TZ=EET @hourly", "2000-10-29T00:00:01Z", "2000-10-29T02:00:00+00:00")] #[case("TZ=EET @hourly", "2000-10-29T01:00:01Z", "2000-10-29T02:00:00+00:00")] #[case("TZ=EET @hourly", "2000-10-29T02:00:01Z", "2000-10-29T03:00:00+00:00")]
1259 #[timeout(Duration::from_secs(1))]
1260 fn test_schedule_upcoming(#[case] pattern: &str, #[case] current: &str, #[case] expected: &str) {
1261 let schedule = Schedule::new(pattern).unwrap();
1262 let current = DateTime::parse_from_rfc3339(current).unwrap();
1263 let next = schedule.upcoming(¤t);
1264
1265 if expected == "None" {
1266 assert!(
1267 next.is_none(),
1268 "pattern = {pattern}, schedule = {schedule:?}, current = {current}, next = {next:?}"
1269 );
1270 } else {
1271 assert!(
1272 next.is_some(),
1273 "pattern = {pattern}, schedule = {schedule:?}, current = {current}, next = {next:?}"
1274 );
1275
1276 assert_eq!(
1277 next.unwrap().to_rfc3339(),
1278 expected,
1279 "pattern = {pattern}, schedule = {schedule:?}, current = {current}, next = {next:?}"
1280 );
1281 }
1282 }
1283
1284 #[rstest]
1285 #[timeout(Duration::from_secs(1))]
1286 fn test_schedule_iter() {
1287 let schedule = Schedule::new("TZ=UTC 0 0 12 * 1 MON 2024").unwrap();
1288 let mut iter = schedule.iter(&DateTime::parse_from_rfc3339("2024-01-01T00:00:00+00:00").unwrap());
1289
1290 assert_eq!(
1291 iter.next().unwrap().to_rfc3339(),
1292 "2024-01-01T12:00:00+00:00",
1293 "schedule = {schedule}"
1294 );
1295 assert_eq!(
1296 iter.next().unwrap().to_rfc3339(),
1297 "2024-01-08T12:00:00+00:00",
1298 "schedule = {schedule}"
1299 );
1300 assert_eq!(
1301 iter.next().unwrap().to_rfc3339(),
1302 "2024-01-15T12:00:00+00:00",
1303 "schedule = {schedule}"
1304 );
1305 assert_eq!(
1306 iter.next().unwrap().to_rfc3339(),
1307 "2024-01-22T12:00:00+00:00",
1308 "schedule = {schedule}"
1309 );
1310 assert_eq!(
1311 iter.next().unwrap().to_rfc3339(),
1312 "2024-01-29T12:00:00+00:00",
1313 "schedule = {schedule}"
1314 );
1315 assert_eq!(iter.next(), None);
1316 }
1317
1318 #[rstest]
1319 #[timeout(Duration::from_secs(1))]
1320 fn test_schedule_iter_every_second() {
1321 let schedule = Schedule::new("TZ=EET * * * * * *").unwrap();
1322 let mut iter = schedule.iter(&DateTime::parse_from_rfc3339("2024-01-01T00:00:01+00:00").unwrap());
1323
1324 assert_eq!(
1325 iter.next().unwrap().to_rfc3339(),
1326 "2024-01-01T00:00:01+00:00",
1327 "schedule = {schedule}"
1328 );
1329 assert_eq!(
1330 iter.next().unwrap().to_rfc3339(),
1331 "2024-01-01T00:00:02+00:00",
1332 "schedule = {schedule}"
1333 );
1334 assert_eq!(
1335 iter.next().unwrap().to_rfc3339(),
1336 "2024-01-01T00:00:03+00:00",
1337 "schedule = {schedule}"
1338 );
1339 assert_eq!(
1340 iter.next().unwrap().to_rfc3339(),
1341 "2024-01-01T00:00:04+00:00",
1342 "schedule = {schedule}"
1343 );
1344 assert_eq!(
1345 iter.next().unwrap().to_rfc3339(),
1346 "2024-01-01T00:00:05+00:00",
1347 "schedule = {schedule}"
1348 );
1349 }
1350
1351 #[rstest]
1352 #[timeout(Duration::from_secs(1))]
1353 fn test_schedule_iter_every_minute() {
1354 let schedule = Schedule::new("TZ=Europe/Kyiv * * * * *").unwrap();
1355 let mut iter = schedule.iter(&DateTime::parse_from_rfc3339("2024-01-01T00:00:01+00:00").unwrap());
1356
1357 assert_eq!(
1358 iter.next().unwrap().to_rfc3339(),
1359 "2024-01-01T00:01:00+00:00",
1360 "schedule = {schedule}"
1361 );
1362 assert_eq!(
1363 iter.next().unwrap().to_rfc3339(),
1364 "2024-01-01T00:02:00+00:00",
1365 "schedule = {schedule}"
1366 );
1367 assert_eq!(
1368 iter.next().unwrap().to_rfc3339(),
1369 "2024-01-01T00:03:00+00:00",
1370 "schedule = {schedule}"
1371 );
1372 assert_eq!(
1373 iter.next().unwrap().to_rfc3339(),
1374 "2024-01-01T00:04:00+00:00",
1375 "schedule = {schedule}"
1376 );
1377 assert_eq!(
1378 iter.next().unwrap().to_rfc3339(),
1379 "2024-01-01T00:05:00+00:00",
1380 "schedule = {schedule}"
1381 );
1382 }
1383
1384 #[rstest]
1385 #[timeout(Duration::from_secs(1))]
1386 fn test_schedule_iter_every_hour() {
1387 let schedule = Schedule::new("TZ=Canada/Eastern 13 * * * *").unwrap();
1388 let mut iter = schedule.iter(&DateTime::parse_from_rfc3339("2024-01-01T07:01:01+00:00").unwrap());
1389
1390 assert_eq!(
1391 iter.next().unwrap().to_rfc3339(),
1392 "2024-01-01T07:13:00+00:00",
1393 "schedule = {schedule}"
1394 );
1395 assert_eq!(
1396 iter.next().unwrap().to_rfc3339(),
1397 "2024-01-01T08:13:00+00:00",
1398 "schedule = {schedule}"
1399 );
1400 assert_eq!(
1401 iter.next().unwrap().to_rfc3339(),
1402 "2024-01-01T09:13:00+00:00",
1403 "schedule = {schedule}"
1404 );
1405 assert_eq!(
1406 iter.next().unwrap().to_rfc3339(),
1407 "2024-01-01T10:13:00+00:00",
1408 "schedule = {schedule}"
1409 );
1410 assert_eq!(
1411 iter.next().unwrap().to_rfc3339(),
1412 "2024-01-01T11:13:00+00:00",
1413 "schedule = {schedule}"
1414 );
1415 }
1416
1417 #[rstest]
1418 #[timeout(Duration::from_secs(1))]
1419 fn test_schedule_iter_every_day() {
1420 let schedule = Schedule::new("TZ=Asia/Tokyo 22 5 * * *").unwrap();
1421 let mut iter = schedule.iter(&DateTime::parse_from_rfc3339("2024-01-01T04:01:01+00:00").unwrap());
1422
1423 assert_eq!(
1424 iter.next().unwrap().to_rfc3339(),
1425 "2024-01-01T20:22:00+00:00",
1426 "schedule = {schedule}"
1427 );
1428 assert_eq!(
1429 iter.next().unwrap().to_rfc3339(),
1430 "2024-01-02T20:22:00+00:00",
1431 "schedule = {schedule}"
1432 );
1433 assert_eq!(
1434 iter.next().unwrap().to_rfc3339(),
1435 "2024-01-03T20:22:00+00:00",
1436 "schedule = {schedule}"
1437 );
1438 assert_eq!(
1439 iter.next().unwrap().to_rfc3339(),
1440 "2024-01-04T20:22:00+00:00",
1441 "schedule = {schedule}"
1442 );
1443 assert_eq!(
1444 iter.next().unwrap().to_rfc3339(),
1445 "2024-01-05T20:22:00+00:00",
1446 "schedule = {schedule}"
1447 );
1448 }
1449
1450 #[rstest]
1451 #[timeout(Duration::from_secs(1))]
1452 fn test_schedule_iter_every_month() {
1453 let schedule = Schedule::new("TZ=GMT 13 13 12 * *").unwrap();
1454 let mut iter = schedule.iter(&DateTime::parse_from_rfc3339("2024-01-12T13:13:01+00:00").unwrap());
1455
1456 assert_eq!(
1457 iter.next().unwrap().to_rfc3339(),
1458 "2024-02-12T13:13:00+00:00",
1459 "schedule = {schedule}"
1460 );
1461 assert_eq!(
1462 iter.next().unwrap().to_rfc3339(),
1463 "2024-03-12T13:13:00+00:00",
1464 "schedule = {schedule}"
1465 );
1466 assert_eq!(
1467 iter.next().unwrap().to_rfc3339(),
1468 "2024-04-12T13:13:00+00:00",
1469 "schedule = {schedule}"
1470 );
1471 assert_eq!(
1472 iter.next().unwrap().to_rfc3339(),
1473 "2024-05-12T13:13:00+00:00",
1474 "schedule = {schedule}"
1475 );
1476 assert_eq!(
1477 iter.next().unwrap().to_rfc3339(),
1478 "2024-06-12T13:13:00+00:00",
1479 "schedule = {schedule}"
1480 );
1481 }
1482
1483 #[rstest]
1484 #[timeout(Duration::from_secs(1))]
1485 fn test_schedule_iter_every_weekday() {
1486 let schedule = Schedule::new("TZ=Antarctica/South_Pole 13 13 ? * *").unwrap();
1487 let mut iter = schedule.iter(&DateTime::parse_from_rfc3339("2024-01-12T13:13:01+00:00").unwrap());
1488
1489 assert_eq!(
1490 iter.next().unwrap().to_rfc3339(),
1491 "2024-01-13T00:13:00+00:00",
1492 "schedule = {schedule}"
1493 );
1494 assert_eq!(
1495 iter.next().unwrap().to_rfc3339(),
1496 "2024-01-14T00:13:00+00:00",
1497 "schedule = {schedule}"
1498 );
1499 assert_eq!(
1500 iter.next().unwrap().to_rfc3339(),
1501 "2024-01-15T00:13:00+00:00",
1502 "schedule = {schedule}"
1503 );
1504 assert_eq!(
1505 iter.next().unwrap().to_rfc3339(),
1506 "2024-01-16T00:13:00+00:00",
1507 "schedule = {schedule}"
1508 );
1509 assert_eq!(
1510 iter.next().unwrap().to_rfc3339(),
1511 "2024-01-17T00:13:00+00:00",
1512 "schedule = {schedule}"
1513 );
1514 }
1515
1516 #[rstest]
1517 #[timeout(Duration::from_secs(1))]
1518 fn test_schedule_iter_every_year() {
1519 let schedule = Schedule::new("TZ=Asia/Shanghai 30 12 22 6 ?").unwrap();
1520 let mut iter = schedule.iter(&DateTime::parse_from_rfc3339("2021-01-12T13:13:01+00:00").unwrap());
1521
1522 assert_eq!(
1523 iter.next().unwrap().to_rfc3339(),
1524 "2021-06-22T04:30:00+00:00",
1525 "schedule = {schedule}"
1526 );
1527 assert_eq!(
1528 iter.next().unwrap().to_rfc3339(),
1529 "2022-06-22T04:30:00+00:00",
1530 "schedule = {schedule}"
1531 );
1532 assert_eq!(
1533 iter.next().unwrap().to_rfc3339(),
1534 "2023-06-22T04:30:00+00:00",
1535 "schedule = {schedule}"
1536 );
1537 assert_eq!(
1538 iter.next().unwrap().to_rfc3339(),
1539 "2024-06-22T04:30:00+00:00",
1540 "schedule = {schedule}"
1541 );
1542 assert_eq!(
1543 iter.next().unwrap().to_rfc3339(),
1544 "2025-06-22T04:30:00+00:00",
1545 "schedule = {schedule}"
1546 );
1547 }
1548
1549 #[rstest]
1550 #[case(Utc, Utc)]
1551 #[case(Utc, Local)]
1552 #[case(Local, Utc)]
1553 #[case(Local, Local)]
1554 #[case(chrono_tz::EET, Utc)]
1555 #[case(Utc, chrono_tz::EET)]
1556 #[case(chrono_tz::EET, Local)]
1557 #[case(Local, chrono_tz::EET)]
1558 #[case(chrono_tz::EET, chrono_tz::EET)]
1559 #[case(chrono_tz::EET, chrono_tz::Canada::Eastern)]
1560 #[case(chrono_tz::Canada::Eastern, chrono_tz::EET)]
1561 #[timeout(Duration::from_secs(3))]
1562 fn test_rough_iterator<T1: TimeZone + Debug, T2: TimeZone + Debug>(
1563 #[case] current_tz: T1,
1564 #[case] schedule_tz: T2,
1565 ) {
1566 use std::collections::BTreeSet;
1567
1568 const TAKE_ITEMS: usize = (365 + 366) * 24 + 3;
1569
1570 let current = current_tz.with_ymd_and_hms(2024, 3, 1, 0, 0, 0).unwrap();
1571
1572 let tz_str = format!("{schedule_tz:?}").to_uppercase();
1573 let tz = if tz_str == "LOCAL" {
1574 None
1575 } else if tz_str == "Z" || tz_str == "UTC" {
1576 Some(String::from("UTC"))
1577 } else {
1578 Some(format!("{schedule_tz:?}"))
1579 };
1580
1581 let schedule = if let Some(tz) = tz {
1582 Schedule::new(format!("TZ={tz} @hourly")).unwrap()
1583 } else {
1584 Schedule::new(" @hourly").unwrap()
1585 };
1586
1587 let result = schedule.iter(¤t).take(TAKE_ITEMS).collect::<Vec<_>>();
1588 assert_eq!(
1589 result.len(),
1590 TAKE_ITEMS,
1591 "current_tz={current_tz:?}, schedule_tz={schedule_tz:?}, schedule='{schedule}', last={}",
1592 result[result.len() - 1].to_rfc2822()
1593 );
1594
1595 let mut set = BTreeSet::new();
1597 set.extend(&result);
1598 assert_eq!(set.len(), result.len());
1599 }
1600 }
1601}