Skip to content

getNextRunDate skips the next midnight when called before a spring DST transition #220

Description

@punjabik

Description

getNextRunDate() skips an entire valid day for a daily midnight expression when the calculation starts on the spring DST transition day before the offset changes.

This appears related to #154 and #202, which were addressed by #203, but the midnight case below still fails on v3.6.0.

The scheduled local time itself is not inside the missing DST hour.

Reproduction

<?php

declare(strict_types=1);

require __DIR__ . '/vendor/autoload.php';

use Cron\CronExpression;

$timezone = new DateTimeZone('Europe/Prague');
$current = new DateTimeImmutable(
    '2026-03-29 00:30:00',
    $timezone,
);

$next = (new CronExpression('0 0 * * *'))->getNextRunDate(
    $current,
    timeZone: $timezone->getName(),
);

echo $current->format('Y-m-d H:i:s P e') . PHP_EOL;
echo $next->format('Y-m-d H:i:s P e') . PHP_EOL;

Actual result

2026-03-29 00:30:00 +01:00 Europe/Prague
2026-03-31 00:00:00 +02:00 Europe/Prague

Expected result

2026-03-30 00:00:00 +02:00 Europe/Prague

The next valid occurrence after 2026-03-29 00:30:00 is midnight at the start of March 30. March 30 must not be skipped.

Failing PHPUnit test

public function testNextMidnightIsNotSkippedAcrossSpringDstTransition(): void
{
    $timezone = new DateTimeZone('Europe/Prague');
    $current = new DateTimeImmutable(
        '2026-03-29 00:30:00',
        $timezone,
    );

    $next = (new CronExpression('0 0 * * *'))->getNextRunDate(
        $current,
        timeZone: $timezone->getName(),
    );

    self::assertSame(
        '2026-03-30 00:00:00 +02:00 Europe/Prague',
        $next->format('Y-m-d H:i:s P e'),
    );
}

Environment

  • dragonmantank/cron-expression: v3.6.0
  • PHP: 8.3.6
  • Timezone: Europe/Prague
  • PHP timezone database: 0.system
  • Also reproducible on current master: d425a24

Relation to PR #203

PR #203 fixes the reproductions from #154 and #202 on v3.6.0. I verified that those original cases now work.

This reproduction is slightly different: the target hour is midnight, before the DST turnover hour, and the calculation begins on the transition day before the offset changes. In this case, the first midnight after the transition is still skipped.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions