zslayton / cron

A cron expression parser in Rust
Apache License 2.0
376 stars 69 forks source link

Bug in time zone change #87

Open Deisss opened 3 years ago

Deisss commented 3 years ago

Here is a small code to reproduce, note that I dont know precisely if the bug is in this project or chrono_tz...

Cargo.toml:

[dependencies]
cron = "0.9.0"
chrono = "0.4.19"
chrono-tz = "0.5.3"

Code:

extern crate cron;
extern crate chrono;
extern crate chrono_tz;

use cron::Schedule;
use chrono::{ DateTime, TimeZone };
use std::str::FromStr;

pub fn next_ticks<Z>(schedule: &str, from_time: &DateTime<Z>, take: usize) -> Vec<DateTime::<Z>> where Z: TimeZone {
    //               sec  min   hour   day of month   month   day of week   year
    // let expression = "0   30   9,12,15     1,15       May-Aug  Mon,Wed,Fri  2018/2";
    match Schedule::from_str(schedule) {
        Ok(schedule) => schedule.after(from_time).take(take).collect::<Vec<DateTime::<Z>>>(),
        Err(_e) => Vec::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use chrono::SecondsFormat;
    use chrono_tz::Europe::Paris;

    #[test]
    fn test_time_zone_change_up() {
        // During the night from the 28 to 29 march 2020, France has changed its time from 2am to 3am
        // So when we reached "2020-03-29T02:00:00 Europe/Paris" we advanced to "2020-03-29T03:00:00 Europe/Paris"
        let results = next_ticks("0 * * * * * *", &Paris.ymd(2020, 3, 29).and_hms(1, 55, 0), 10);
        assert_eq!(results.len(), 10);
        assert_eq!(results[0].to_rfc3339_opts(SecondsFormat::Millis, false), "2020-03-29T01:56:00.000+01:00");
        assert_eq!(results[1].to_rfc3339_opts(SecondsFormat::Millis, false), "2020-03-29T01:57:00.000+01:00");
        assert_eq!(results[2].to_rfc3339_opts(SecondsFormat::Millis, false), "2020-03-29T01:58:00.000+01:00");
        assert_eq!(results[3].to_rfc3339_opts(SecondsFormat::Millis, false), "2020-03-29T01:59:00.000+01:00");
        // Time zone shift here
        assert_eq!(results[4].to_rfc3339_opts(SecondsFormat::Millis, false), "2020-03-29T03:00:00.000+02:00");
        assert_eq!(results[5].to_rfc3339_opts(SecondsFormat::Millis, false), "2020-03-29T03:01:00.000+02:00");
        assert_eq!(results[6].to_rfc3339_opts(SecondsFormat::Millis, false), "2020-03-29T03:02:00.000+02:00");
        assert_eq!(results[7].to_rfc3339_opts(SecondsFormat::Millis, false), "2020-03-29T03:03:00.000+02:00");
        assert_eq!(results[8].to_rfc3339_opts(SecondsFormat::Millis, false), "2020-03-29T03:04:00.000+02:00");
        assert_eq!(results[9].to_rfc3339_opts(SecondsFormat::Millis, false), "2020-03-29T03:05:00.000+02:00");
    }

    #[test]
    fn test_time_zone_change_down() {
        // During the night from the 24 to 25 october 2020, France has changed its time from 3am to 2am
        // So when we reached "2020-10-25T03:00:00 Europe/Paris" we went back to "2020-10-25T02:00:00 Europe/Paris"
        let results = next_ticks("0 * * * * * *", &Paris.ymd(2020, 10, 25).and_hms(2, 55, 0), 1);
        assert_eq!(results.len(), 10);
        assert_eq!(results[0].to_rfc3339_opts(SecondsFormat::Millis, false), "2020-10-25T02:56:00.000+02:00");
    }
}

The first works, the second test_time_zone_change_down panic with "thread 'tests::test_time_zone_change_down' panicked at 'invalid time', /Users/<>/.cargo/registry/src/github.com-1ecc6299db9ec823/chrono-0.4.19/src/date.rs:83:42"

maxcountryman commented 1 month ago

@Deisss in case this is still relevant for you and you're open to switching from chrono, my jiff fork appears to pass your test cases: https://github.com/maxcountryman/jiff-cron/pull/1.