Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions src/Cron/CronExpression.php
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,77 @@ public function getMultipleRunDates(int $total, $currentTime = 'now', bool $inve
return $matches;
}

/**
* Get multiple run dates until a specific end date.
*
* This method calculates and returns execution dates based on the cron expression
* until a given end date, with optional constraints such as a maximum number of occurrences,
* timezone adjustments, and date inclusion rules.
*
* @param string|DateTimeInterface $until The date limit for fetching occurrences.
* If a string is provided, it must be a valid date format.
* @param int $limit The maximum number of occurrences to return. If set to 0, it defaults to `$this->maxIterationCount`.
* @param string|DateTimeInterface $currentTime The reference time to start generating dates. Defaults to 'now'.
* @param bool $allowCurrentDate Whether to include the current date in the results if it matches the cron expression. Defaults to `false`.
* @param string|null $timeZone Optional. The timezone to use for date calculations.
* - If `null`, it will be determined based on `$currentTime` (if a `DateTimeInterface` is provided, its timezone will be used).
* - If `$currentTime` is a string or `null`, the system's default timezone will be used.
* @return DateTimeInterface[] An array of DateTimeInterface objects representing the matching execution dates.
*
* @throws InvalidArgumentException|Exception
*/
public function getRunDatesUntil(
$until,
$limit = 0,
$currentTime = 'now',
$allowCurrentDate = false,
$timeZone = null
): array

{
if (is_string($until)) {
try {
$until = new DateTimeImmutable($until);
} catch (Exception $e) {
throw new InvalidArgumentException("Invalid date format: $until");
}
} elseif (!$until instanceof DateTimeInterface) {
throw new InvalidArgumentException("End date must be a string or an instance of DateTimeInterface.");
}

if (!is_int($limit)) {
throw new InvalidArgumentException("Limit must be an integer.");
}

$timeZone = $this->determineTimeZone($currentTime, $timeZone);

if ($limit === 0) {
$limit = $this->maxIterationCount;
}

$dates = [];

$untilTimestamp = $until->getTimestamp();
for ($i = 0; $i < $limit; $i++) {
try {
$result = $this->getRunDate($currentTime, 0, false, $allowCurrentDate, $timeZone);
} catch (RuntimeException $e) {
break;
}

$allowCurrentDate = false;
$currentTime = clone $result;

if ($result->getTimestamp() > $untilTimestamp) {
break;
}

$dates[] = $result;
}

return $dates;
}

/**
* Get all or part of the CRON expression.
*
Expand Down
83 changes: 83 additions & 0 deletions tests/Cron/CronExpressionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,89 @@ public function testProvidesMultipleRunDatesForTheFarFuture(): void
], $cron->getMultipleRunDates(9, '2015-04-28 00:00:00', false, true));
}

/**
* @covers \Cron\CronExpression::getRunDatesUntil
*/

public function testInvalidUntilDate(): void
{
$cron = new CronExpression('* * * * *');
$this->expectException(InvalidArgumentException::class);
$cron->getRunDatesUntil('invalid until date', 1, '2008-11-09 00:00:00');
}

/**
* @covers \Cron\CronExpression::getRunDatesUntil
*/

public function testInvalidLimit(): void
{
$cron = new CronExpression('* * * * *');
$this->expectException(InvalidArgumentException::class);
$cron->getRunDatesUntil('2008-11-09 00:00:00', null, '2008-11-09 00:00:00');
}
/**
* @covers \Cron\CronExpression::getRunDatesUntil
*/
public function testGetRunDatesUntil(): void
{
$cron = new CronExpression('*/2 * * * *');

// Test with end date and limit of 4 occurrences
$until = '2008-11-09 00:06:00';
$limit = 4;
$expectedDates = [
new DateTime('2008-11-09 00:00:00'),
new DateTime('2008-11-09 00:02:00'),
new DateTime('2008-11-09 00:04:00'),
new DateTime('2008-11-09 00:06:00'),
];

// Test with allowCurrentDate set to false (default)
$result = $cron->getRunDatesUntil($until, $limit, '2008-11-09 00:00:00');
$this->assertEquals(array_slice($expectedDates, 1), $result);

// Test with allowCurrentDate set to true
$result = $cron->getRunDatesUntil($until, $limit, '2008-11-09 00:00:00', true);
$this->assertEquals($expectedDates, $result);

// Test with limit set to 0 (defaults to maxIterationCount)
$limit = 0;
$expectedDates = [
new DateTime('2008-11-09 00:00:00'),
new DateTime('2008-11-09 00:02:00'),
new DateTime('2008-11-09 00:04:00'),
new DateTime('2008-11-09 00:06:00'),
];

// Test with allowCurrentDate set to false
$result = $cron->getRunDatesUntil($until, $limit, '2008-11-09 00:00:00');
$this->assertEquals(array_slice($expectedDates, 1), $result);

// Test with allowCurrentDate set to true
$result = $cron->getRunDatesUntil($until, $limit, '2008-11-09 00:00:00', true);
$this->assertEquals($expectedDates, $result);

// Test with end date limit that exceeds the limit
$until = '2008-11-09 00:05:00';
$limit = 10; // A larger limit to ensure iteration stops properly
$expectedDates = [
new DateTime('2008-11-09 00:00:00'),
new DateTime('2008-11-09 00:02:00'),
new DateTime('2008-11-09 00:04:00'),
];

// Test with allowCurrentDate set to false
$result = $cron->getRunDatesUntil($until, $limit, '2008-11-09 00:00:00');
$this->assertEquals(array_slice($expectedDates, 1), $result);

// Test with allowCurrentDate set to true
$result = $cron->getRunDatesUntil($until, $limit, '2008-11-09 00:00:00', true);
$this->assertEquals($expectedDates, $result);
}



/**
* @covers \Cron\CronExpression
*/
Expand Down