dragonmantank / cron-expression

CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due
MIT License
4.57k stars 124 forks source link

"0 0 1-7,14-21 * Wed" the getNextRunDate is failing. #138

Closed delphimat closed 1 year ago

delphimat commented 2 years ago

The purpose of this CRON expression is to run every 2 weeks with a specific day. ( on my example i have Wednesday )

Today we are the 18 may 2022.

cronExpressionWeekly = "0 0 1-7,14-21 * Wed"

(new CronExpression($cronExpressionWeekly))->getNextRunDate('now')->format('d.m.Y')

the result return 2022-05-19 00:00:00 which is a Thursday. the cron expression is not respected at all.

I believe the issue is about the range. 1-7,14-21

richdynamix commented 2 years ago

I am also seeing this error 30 18 21-31 * */6 This Cron should run the first Saturday of the month at 18:30. https://crontab.guru/#30_18_21-31_*_*/6 But the NextRunDate calculates as date: 2022-07-21 18:30:00.0 Europe/London (+01:00) which is also a Thursday.

Is there any workaround @dragonmantank ?

peterlaws commented 2 years ago
$cron = new Cron\CronExpression('10 0 * */3 *');
echo $cron->getNextRunDate('now')->format('Y-m-d H:i:s');

always give the next day (2022-10-08 00:10:00), but */3 is every 3 months.

If I specify a day of month, then it does say January, but if I change the month to any other (/4, /6), it gives a random month, someimes January, sometimes Novmber this year.

dragonmantank commented 1 year ago

tl;dr - Due to the POSIX standard, when Day of Month and Day of Week are both specified they are evaluated as an OR, not an AND. This leads to unexpected results.


Part of this is because of the way Day of Month and Day of Week work when combined. cronie, which is the implementation this most closely follows, treats both values as an OR, not an AND.

Given the following expression:

0 0 1-7,14-21 * Wed

This will evaluate as "Any Day of Month in [1,2,3,4,5,6,7,14,15,16,17,18,19,20,21] OR a Wednesday". For example, "2023-08-02" is valid because it works with both constraints, and "2023-08-03" is valid because it works with the Day of Month constraint. "2023-08-09" would be valid because it is a Wednesday, but "2023-08-10" would be invalid because it is not in the Day of Month range or a Wednesday.

Why is this an OR comparison instead of an AND?

Because that's the POSIX Standard. Granted, no one really knows the original reasoning, but for compatibility I followed the POSIX standard.

dragonmantank commented 1 year ago

always give the next day (2022-10-08 00:10:00), but */3 is every 3 months.

As another important distinction, because once again cron doesn't always do what we expect, X/X is a step call. It evaluates to "Given this range, start here and step X times to get the next value." It isn't technicaly "every three months" as in like a calendar quarter.

*/3 evaluates as 1-12/3, so will start at 1 (January). Then step 3 to April (1+3=4, so April). Then step 3 to July (4+3=7). The important part is that it starts at the beginning of the range, not steps before determining a starting point.

So if we expand out '10 0 * */3 *', that evaluates as "The 10th minute of the 0th hour, every day of the month for months 1,4,7,10 on every day of the week". January dates would be fully valid. If you want a truly quarterly/financial date run, you are better off setting the month to 3,6,9,12 instead of a step call.