dragonmantank / cron-expression

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

getNextRunDate() skips March for some years #176

Open tochix opened 6 months ago

tochix commented 6 months ago

It seems that when the last day of the month cron expression(59 23 L * *) is used, the getNextRunDate method skips March for some years and returns April instead. I was thinking this had to do with 2024 being a leap year, but when I ran test cases for other years, it turned out to not be only for leap years.

/**
 * @dataProvider dateProvider
 */
public function testLastDayOfMonthCronExpression(string $eventDate, string $expectedCronDate)
{
    $expression = '59 23 L * *';
    $cron = new CronExpression($expression);
    $timeZone = new DateTimeZone('Europe/Helsinki');

    $expectedRunDate = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $expectedCronDate, $timeZone);
    $providedDate = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $eventDate, $timeZone);

    $nextRunDate = $cron->getNextRunDate($providedDate, 0, true, $timeZone->getName());
    $this->assertEquals($expectedRunDate, $nextRunDate);
}

public function dateProvider()
{
    return [
        '2024 March' => ['2024-03-20 12:13:14', '2024-03-31 23:59:00',],
        '2023 March' => ['2023-03-30 12:13:14', '2023-03-31 23:59:00',],
        '2022 March' => ['2022-03-30 12:13:14', '2022-03-31 23:59:00',],
        '2021 March' => ['2021-03-30 12:13:14', '2021-03-31 23:59:00',],
        '2020 March' => ['2020-03-30 12:13:14', '2020-03-31 23:59:00',],
        '2019 March' => ['2019-03-30 12:13:14', '2019-03-31 23:59:00',],
        '2018 March' => ['2018-03-20 12:13:14', '2018-03-31 23:59:00',],
        '2017 March' => ['2017-03-30 12:13:14', '2017-03-31 23:59:00',],
        '2016 March' => ['2016-03-30 12:13:14', '2016-03-31 23:59:00',],
        '2015 March' => ['2015-03-30 12:13:14', '2015-03-31 23:59:00',],
        '2014 March' => ['2014-03-30 12:13:14', '2014-03-31 23:59:00',],
        '2013 March' => ['2013-03-30 12:13:14', '2013-03-31 23:59:00',],
        '2012 March' => ['2012-03-20 12:13:14', '2012-03-31 23:59:00',],
        '2011 March' => ['2011-03-30 12:13:14', '2011-03-31 23:59:00',],
        '2010 March' => ['2010-03-30 12:13:14', '2010-03-31 23:59:00',],
        '2009 March' => ['2009-03-30 12:13:14', '2009-03-31 23:59:00',],
        '2008 March' => ['2008-03-30 12:13:14', '2008-03-31 23:59:00',],
        '2007 March' => ['2007-03-30 12:13:14', '2007-03-31 23:59:00',],
        '2006 March' => ['2006-03-30 12:13:14', '2006-03-31 23:59:00',],
        '2005 March' => ['2005-03-30 12:13:14', '2005-03-31 23:59:00',],
        '2004 March' => ['2004-03-30 12:13:14', '2004-03-31 23:59:00',],
        '2003 March' => ['2003-03-30 12:13:14', '2003-03-31 23:59:00',],
        '2002 March' => ['2002-03-30 12:13:14', '2002-03-31 23:59:00',],
        '2001 March' => ['2001-03-30 12:13:14', '2001-03-31 23:59:00',],
        '2000 March' => ['2000-03-30 12:13:14', '2000-03-31 23:59:00',],
    ];
}

results in:

Failed asserting that two DateTime objects are equal.
Expected :2024-03-31T23:59:00.000000+0300
Actual   :2024-04-30T23:59:00.000000+0300

Failed asserting that two DateTime objects are equal.
Expected :2019-03-31T23:59:00.000000+0300
Actual   :2019-04-30T23:59:00.000000+0300

Failed asserting that two DateTime objects are equal.
Expected :2013-03-31T23:59:00.000000+0300
Actual   :2013-04-30T23:59:00.000000+0300

Failed asserting that two DateTime objects are equal.
Expected :2002-03-31T23:59:00.000000+0300
Actual   :2002-04-30T23:59:00.000000+0300

So instead of returning last day of March, it returns last day of April for the years 2024, 2019, 2013 and 2002.

dragonmantank commented 5 months ago

I believe I have a fix for this, it is due to how time math was being handled. Before I release a fix, could I get your thoughts on https://github.com/dragonmantank/cron-expression/discussions/181 as it will impact the overall solution?