HangfireIO / Cronos

A fully-featured .NET library for working with Cron expressions. Built with time zones in mind and intuitively handles daylight saving time transitions
MIT License
974 stars 114 forks source link

Wrong value reported on `GetNextOccurrence` function #30

Closed ermirbeqiraj closed 3 years ago

ermirbeqiraj commented 3 years ago

Wrong value reported for next occurrence, ignores remaining time for this current date.

Consider the following:

    var strExpression = "25 23 */3 * *";
    var cronExpression = Cronos.CronExpression.Parse(strExpression, Cronos.CronFormat.Standard);
    var nextOccurrence = cronExpression.GetNextOccurrence(DateTime.UtcNow);

    var now = DateTime.UtcNow; // 2020-09-30 16:32:00
    var nextFireShouldBe = new DateTime(now.Year, now.Month, now.Day, 23, 25, 0);

    //nextOccurrence    => 2020-10-01 23:25:00
    //nextFireShouldBe  => 2020-09-30 23:25:00

Next occurrence should be on same date, but is pushed for tomorrow instead

celluj34 commented 3 years ago

Try using the inclusive parameter:

var nextOccurrence = _cronExpression.GetNextOccurrence(DateTime.UtcNow, true);

ermirbeqiraj commented 3 years ago

Tried that actually, it doesn't makes any difference :/

odinserj commented 3 years ago

Unfortunately this is expected and this is how other cron parsers work, for example NCrontab. The thing is that day of month field start with number 1 (not with 0 as hour and minute fields), so */3 expression will yield the following sequence:

1
4
7
10
13
16
19
22
25
28
31
ermirbeqiraj commented 3 years ago

Ok, that's a reasonable response, however, shouldn't it start within the following day? since the first occurrence is not consumed yet? I bumped into this from scheduling a job in ActiveMQ, the same cron expression in there was current day (30-09...) but the first occurrence from Cronos was the next day

odinserj commented 3 years ago

I've also spawned a new droplet on Digital Ocean to test this cron expression against the original Cron utility. I've used Ubuntu 20.04.01 LTS, disabled time synchronization with the timedatectl set-ntp 0 command and added the following entry to the local crontab file:

25 23 */3 * * date >> /root/sample2.txt

The I set time to Sep 30, 2020 using the date --set "30 Sep 2020 23:24:00" command and waited a minute – nothing happened. Then I set time to Oct 1, 2020 using the date --set "1 Oct 2020 23:24:00" command and waited a minute, and the file appeared with the following contents:

Thu Oct  1 23:25:01 UTC 2020

To avoid any problems with Cron itself, I've set time back to Sep 30, 2020 23:00:00 (5 minutes before the required time) with date --set "30 Sep 2020 23:20:00" command, but that file was still the same, so original Cron utility does not think that Sep 30 is a great time to run a command with 25 23 */3 * * expression, and so Cronos shouldn't do this either.

Thu Oct  1 23:25:01 UTC 2020

The thing is that there are a lot of implementation of cron parsers, and unfortunately they may be different in some corner cases. I was trying to implement Cronos as closely to the original Cron program as possible, digging into its source code to know the details, to avoid implementing yet another standard and avoid cases like this.

Here's the full log of an experiment:

root@ubuntu-s-1vcpu-1gb-fra1-01:~# crontab -l
# Edit this file to introduce tasks to be run by cron.
#
# Each task to run has to be defined through a single line
# indicating with different fields when the task will be run
# and what command to run for the task
#
# To define the time you can provide concrete values for
# minute (m), hour (h), day of month (dom), month (mon),
# and day of week (dow) or use '*' in these fields (for 'any').
#
# Notice that tasks will be started based on the cron's system
# daemon's notion of time and timezones.
#
# Output of the crontab jobs (including errors) is sent through
# email to the user the crontab file belongs to (unless redirected).
#
# For example, you can run a backup of all your user accounts
# at 5 a.m every week with:
# 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/
#
# For more information see the manual pages of crontab(5) and cron(8)
#
# m h  dom mon dow   command
25 23 */3 * * date >> /root/sample2.txt
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date --set "30 Sep 2020 23:24:00"
Wed Sep 30 23:24:00 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Thu Oct  1 09:08:38 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Thu Oct  1 09:08:43 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# ls
snap
root@ubuntu-s-1vcpu-1gb-fra1-01:~# timedatectl set-ntp 0

root@ubuntu-s-1vcpu-1gb-fra1-01:~#
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date --set "30 Sep 2020 23:24:00"
Wed Sep 30 23:24:00 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:24:02 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:24:03 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# ls
snap
root@ubuntu-s-1vcpu-1gb-fra1-01:~# ls
snap
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:24:05 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:24:10 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# ls
snap
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:24:14 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# ls
snap
root@ubuntu-s-1vcpu-1gb-fra1-01:~# ls
snap
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:24:25 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:24:27 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:24:28 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:24:29 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:24:30 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:24:31 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:24:32 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:24:33 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:24:34 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:24:35 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:24:35 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:24:36 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:24:55 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# ls
snap
root@ubuntu-s-1vcpu-1gb-fra1-01:~# ls
snap
root@ubuntu-s-1vcpu-1gb-fra1-01:~# ls
snap
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:25:02 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# ls
snap
root@ubuntu-s-1vcpu-1gb-fra1-01:~# ls
snap
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:25:23 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:25:40 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# ls
snap
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:25:43 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:25:50 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# ls
snap
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:25:54 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:26:01 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# ls
snap
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date --set "1 Oct 2020 23:24:00"
Thu Oct  1 23:24:00 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Thu Oct  1 23:24:04 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# ls
snap
root@ubuntu-s-1vcpu-1gb-fra1-01:~# ls
snap
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Thu Oct  1 23:24:22 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Thu Oct  1 23:24:40 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# ls
snap
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Thu Oct  1 23:24:52 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# ls
snap
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Thu Oct  1 23:25:00 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# ls
sample2.txt  snap
root@ubuntu-s-1vcpu-1gb-fra1-01:~# cat sample2.txt
Thu Oct  1 23:25:01 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# ls
sample2.txt  snap
root@ubuntu-s-1vcpu-1gb-fra1-01:~# cat sample2.txt
Thu Oct  1 23:25:01 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# crontab -e
No modification made
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date --set "30 Sep 2020 23:20:00"
Wed Sep 30 23:20:00 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# cat sample2.txt
Thu Oct  1 23:25:01 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:20:55 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:23:27 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:23:59 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:24:01 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# cat sample2.txt
Thu Oct  1 23:25:01 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:24:16 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:24:25 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:24:30 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:24:37 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:24:47 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:24:51 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:24:55 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:24:59 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:25:04 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# cat sample2.txt
Thu Oct  1 23:25:01 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:25:08 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# cat sample2.txt
Thu Oct  1 23:25:01 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:25:23 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# cat sample2.txt
Thu Oct  1 23:25:01 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# date
Wed Sep 30 23:25:28 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~# cat sample2.txt
Thu Oct  1 23:25:01 UTC 2020
root@ubuntu-s-1vcpu-1gb-fra1-01:~#

So everything is working the same way as in the original cron program, and problem relates to ActiveMQ.