dependencies-upgrade
This commit is contained in:
@@ -1,16 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.yml]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
144
vendor/dragonmantank/cron-expression/CHANGELOG.md
vendored
144
vendor/dragonmantank/cron-expression/CHANGELOG.md
vendored
@@ -1,5 +1,149 @@
|
||||
# Change Log
|
||||
|
||||
## [3.3.2] - 2022-09-19
|
||||
|
||||
### Added
|
||||
- N/A
|
||||
|
||||
### Changed
|
||||
- Skip some daylight savings time tests for PHP 8.1 daylight savings time weirdness (#146)
|
||||
|
||||
### Fixed
|
||||
- Changed string interpolations to work better with PHP 8.2 (#142)
|
||||
|
||||
## [3.3.1] - 2022-01-18
|
||||
|
||||
### Added
|
||||
- N/A
|
||||
|
||||
### Changed
|
||||
- N/A
|
||||
|
||||
### Fixed
|
||||
- Fixed issue when timezones had no transition, which can occur over very short timespans (#134)
|
||||
|
||||
## [3.3.0] - 2022-01-13
|
||||
|
||||
### Added
|
||||
- Added ability to register your own expression aliases (#132)
|
||||
|
||||
### Changed
|
||||
- Changed how Day of Week and Day of Month resolve when one or the other is `*` or `?`
|
||||
|
||||
### Fixed
|
||||
- PHPStan should no longer error out
|
||||
|
||||
## [3.2.4] - 2022-01-12
|
||||
|
||||
### Added
|
||||
- N/A
|
||||
|
||||
### Changed
|
||||
- Changed how Day of Week increment/decrement to help with DST changes (#131)
|
||||
|
||||
### Fixed
|
||||
- N/A
|
||||
|
||||
## [3.2.3] - 2022-01-05
|
||||
|
||||
### Added
|
||||
- N/A
|
||||
|
||||
### Changed
|
||||
- Changed how minutes and hours increment/decrement to help with DST changes (#131)
|
||||
|
||||
### Fixed
|
||||
- N/A
|
||||
|
||||
## [3.2.2] - 2022-01-05
|
||||
|
||||
### Added
|
||||
- N/A
|
||||
|
||||
### Changed
|
||||
- Marked some methods `@internal` (#124)
|
||||
|
||||
### Fixed
|
||||
- Fixed issue with small ranges and large steps that caused an error with `range()` (#88)
|
||||
- Fixed issue where wraparound logic incorrectly considered high bound on range (#89)
|
||||
|
||||
## [3.2.1] - 2022-01-04
|
||||
|
||||
### Added
|
||||
- N/A
|
||||
|
||||
### Changed
|
||||
- Added PHP 8.1 to testing (#125)
|
||||
|
||||
### Fixed
|
||||
- Allow better mixture of ranges, steps, and lists (#122)
|
||||
- Fixed return order when multiple dates are requested and inverted (#121)
|
||||
- Better handling over DST (#115)
|
||||
- Fixed PHPStan tests (#130)
|
||||
|
||||
## [3.2.0] - 2022-01-04
|
||||
|
||||
### Added
|
||||
- Added alias for `@midnight` (#117)
|
||||
|
||||
### Changed
|
||||
- Improved testing for instance of field in tests (#105)
|
||||
- Optimization for determining multiple run dates (#75)
|
||||
- `CronExpression` properties changed from private to protected (#106)
|
||||
|
||||
### Fixed
|
||||
- N/A
|
||||
|
||||
## [3.1.0] - 2020-11-24
|
||||
|
||||
### Added
|
||||
- Added `CronExpression::getParts()` method to get parts of the expression as an array (#83)
|
||||
|
||||
### Changed
|
||||
- Changed to Interfaces for some type hints (#97, #86)
|
||||
- Dropped minimum PHP version to 7.2
|
||||
- Few syntax changes for phpstan compatibility (#93)
|
||||
|
||||
### Fixed
|
||||
- N/A
|
||||
|
||||
### Deprecated
|
||||
- Deprecated `CronExpression::factory` in favor of the constructor (#56)
|
||||
- Deprecated `CronExpression::YEAR` as a formality, the functionality is already removed (#87)
|
||||
|
||||
## [3.0.1] - 2020-10-12
|
||||
### Added
|
||||
- Added support for PHP 8 (#92)
|
||||
### Changed
|
||||
- N/A
|
||||
### Fixed
|
||||
- N/A
|
||||
|
||||
## [3.0.0] - 2020-03-25
|
||||
|
||||
**MAJOR CHANGE** - In previous versions of this library, setting both a "Day of Month" and a "Day of Week" would be interpreted as an `AND` statement, not an `OR` statement. For example:
|
||||
|
||||
`30 0 1 * 1`
|
||||
|
||||
would evaluate to "Run 30 minutes after the 0 hour when the Day Of Month is 1 AND a Monday" instead of "Run 30 minutes after the 0 hour on Day Of Month 1 OR a Monday", where the latter is more inline with most cron systems. This means that if your cron expression has both of these fields set, you may see your expression fire more often starting with v3.0.0.
|
||||
|
||||
### Added
|
||||
- Additional docblocks for IDE and documentation
|
||||
- Added phpstan as a development dependency
|
||||
- Added a `Cron\FieldFactoryInterface` to make migrations easier (#38)
|
||||
### Changed
|
||||
- Changed some DI testing during TravisCI runs
|
||||
- `\Cron\CronExpression::determineTimezone()` now checks for `\DateTimeInterface` instead of just `\DateTime`
|
||||
- Errors with fields now report a more human-understandable error and are 1-based instead of 0-based
|
||||
- Better support for `\DateTimeImmutable` across the library by typehinting for `\DateTimeInterface` now
|
||||
- Literals should now be less case-sensative across the board
|
||||
- Changed logic for when both a Day of Week and a Day of Month are supplied to now be an OR statement, not an AND
|
||||
### Fixed
|
||||
- Fixed infinite loop when determining last day of week from literals
|
||||
- Fixed bug where single number ranges were allowed (ex: `1/10`)
|
||||
- Fixed nullable FieldFactory in CronExpression where no factory could be supplied
|
||||
- Fixed issue where logic for dropping seconds to 0 could lead to a timezone change
|
||||
|
||||
## [2.3.1] - 2020-10-12
|
||||
### Added
|
||||
- Added support for PHP 8 (#92)
|
||||
|
||||
23
vendor/dragonmantank/cron-expression/README.md
vendored
23
vendor/dragonmantank/cron-expression/README.md
vendored
@@ -1,7 +1,7 @@
|
||||
PHP Cron Expression Parser
|
||||
==========================
|
||||
|
||||
[](https://packagist.org/packages/dragonmantank/cron-expression) [](https://packagist.org/packages/dragonmantank/cron-expression) [](http://travis-ci.org/dragonmantank/cron-expression)
|
||||
[](https://packagist.org/packages/dragonmantank/cron-expression) [](https://packagist.org/packages/dragonmantank/cron-expression) [](http://travis-ci.org/dragonmantank/cron-expression) [](https://github.styleci.io/repos/103715337)
|
||||
|
||||
The PHP cron expression parser can parse a CRON expression, determine if it is
|
||||
due to run, calculate the next run date of the expression, and calculate the previous
|
||||
@@ -32,21 +32,21 @@ Usage
|
||||
require_once '/vendor/autoload.php';
|
||||
|
||||
// Works with predefined scheduling definitions
|
||||
$cron = Cron\CronExpression::factory('@daily');
|
||||
$cron = new Cron\CronExpression('@daily');
|
||||
$cron->isDue();
|
||||
echo $cron->getNextRunDate()->format('Y-m-d H:i:s');
|
||||
echo $cron->getPreviousRunDate()->format('Y-m-d H:i:s');
|
||||
|
||||
// Works with complex expressions
|
||||
$cron = Cron\CronExpression::factory('3-59/15 6-12 */15 1 2-5');
|
||||
$cron = new Cron\CronExpression('3-59/15 6-12 */15 1 2-5');
|
||||
echo $cron->getNextRunDate()->format('Y-m-d H:i:s');
|
||||
|
||||
// Calculate a run date two iterations into the future
|
||||
$cron = Cron\CronExpression::factory('@daily');
|
||||
$cron = new Cron\CronExpression('@daily');
|
||||
echo $cron->getNextRunDate(null, 2)->format('Y-m-d H:i:s');
|
||||
|
||||
// Calculate a run date relative to a specific time
|
||||
$cron = Cron\CronExpression::factory('@monthly');
|
||||
$cron = new Cron\CronExpression('@monthly');
|
||||
echo $cron->getNextRunDate('2010-01-12 00:00:00')->format('Y-m-d H:i:s');
|
||||
```
|
||||
|
||||
@@ -65,14 +65,23 @@ A CRON expression is a string representing the schedule for a particular command
|
||||
| +-------------------- hour (0 - 23)
|
||||
+------------------------- min (0 - 59)
|
||||
|
||||
This library also supports a few macros:
|
||||
|
||||
* `@yearly`, `@annually` - Run once a year, midnight, Jan. 1 - `0 0 1 1 *`
|
||||
* `@monthly` - Run once a month, midnight, first of month - `0 0 1 * *`
|
||||
* `@weekly` - Run once a week, midnight on Sun - `0 0 * * 0`
|
||||
* `@daily`, `@midnight` - Run once a day, midnight - `0 0 * * *`
|
||||
* `@hourly` - Run once an hour, first minute - `0 * * * *`
|
||||
|
||||
Requirements
|
||||
============
|
||||
|
||||
- PHP 7.0+
|
||||
- PHP 7.2+
|
||||
- PHPUnit is required to run the unit tests
|
||||
- Composer is required to run the unit tests
|
||||
|
||||
Projects that Use cron-expression
|
||||
=================================
|
||||
* Part of the [Laravel Framework](https://github.com/laravel/framework/)
|
||||
* Available as a [Symfony Bundle - setono/cron-expression-bundle](https://github.com/Setono/CronExpressionBundle)
|
||||
* Available as a [Symfony Bundle - setono/cron-expression-bundle](https://github.com/Setono/CronExpressionBundle)
|
||||
* Framework agnostic, PHP-based job scheduler - [Crunz](https://github.com/lavary/crunz)
|
||||
|
||||
@@ -5,11 +5,6 @@
|
||||
"keywords": ["cron", "schedule"],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Michael Dowling",
|
||||
"email": "mtdowling@gmail.com",
|
||||
"homepage": "https://github.com/mtdowling"
|
||||
},
|
||||
{
|
||||
"name": "Chris Tankersley",
|
||||
"email": "chris@ctankersley.com",
|
||||
@@ -17,10 +12,14 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.0|^8.0"
|
||||
"php": "^7.2|^8.0",
|
||||
"webmozart/assert": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^6.4|^7.0|^8.0|^9.0"
|
||||
"phpstan/phpstan": "^1.0",
|
||||
"phpunit/phpunit": "^7.0|^8.0|^9.0",
|
||||
"phpstan/phpstan-webmozart-assert": "^1.0",
|
||||
"phpstan/extension-installer": "^1.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
@@ -29,12 +28,20 @@
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Tests\\": "tests/Cron/"
|
||||
"Cron\\Tests\\": "tests/Cron/"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.3-dev"
|
||||
"replace": {
|
||||
"mtdowling/cron-expression": "^1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"phpstan": "./vendor/bin/phpstan analyze",
|
||||
"test": "phpunit"
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"ocramius/package-versions": true,
|
||||
"phpstan/extension-installer": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
15
vendor/dragonmantank/cron-expression/phpstan.neon
vendored
Normal file
15
vendor/dragonmantank/cron-expression/phpstan.neon
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
parameters:
|
||||
checkMissingIterableValueType: false
|
||||
|
||||
ignoreErrors:
|
||||
- '#Call to an undefined method DateTimeInterface::add\(\)#'
|
||||
- '#Call to an undefined method DateTimeInterface::modify\(\)#'
|
||||
- '#Call to an undefined method DateTimeInterface::setDate\(\)#'
|
||||
- '#Call to an undefined method DateTimeInterface::setTime\(\)#'
|
||||
- '#Call to an undefined method DateTimeInterface::setTimezone\(\)#'
|
||||
- '#Call to an undefined method DateTimeInterface::sub\(\)#'
|
||||
|
||||
level: max
|
||||
|
||||
paths:
|
||||
- src/
|
||||
@@ -1,33 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cron;
|
||||
|
||||
use DateTimeInterface;
|
||||
|
||||
/**
|
||||
* Abstract CRON expression field
|
||||
* Abstract CRON expression field.
|
||||
*/
|
||||
abstract class AbstractField implements FieldInterface
|
||||
{
|
||||
/**
|
||||
* Full range of values that are allowed for this field type
|
||||
* Full range of values that are allowed for this field type.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fullRange = [];
|
||||
|
||||
/**
|
||||
* Literal values we need to convert to integers
|
||||
* Literal values we need to convert to integers.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $literals = [];
|
||||
|
||||
/**
|
||||
* Start value of the full range
|
||||
* @var integer
|
||||
* Start value of the full range.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $rangeStart;
|
||||
|
||||
/**
|
||||
* End value of the full range
|
||||
* @var integer
|
||||
* End value of the full range.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $rangeEnd;
|
||||
|
||||
@@ -40,98 +48,107 @@ abstract class AbstractField implements FieldInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if a field is satisfied by a value
|
||||
* Check to see if a field is satisfied by a value.
|
||||
*
|
||||
* @param string $dateValue Date value to check
|
||||
* @param string $value Value to test
|
||||
* @internal
|
||||
* @param int $dateValue Date value to check
|
||||
* @param string $value Value to test
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSatisfied($dateValue, $value)
|
||||
public function isSatisfied(int $dateValue, string $value): bool
|
||||
{
|
||||
if ($this->isIncrementsOfRanges($value)) {
|
||||
return $this->isInIncrementsOfRanges($dateValue, $value);
|
||||
} elseif ($this->isRange($value)) {
|
||||
}
|
||||
|
||||
if ($this->isRange($value)) {
|
||||
return $this->isInRange($dateValue, $value);
|
||||
}
|
||||
|
||||
return $value == '*' || $dateValue == $value;
|
||||
return '*' === $value || $dateValue === (int) $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a value is a range
|
||||
* Check if a value is a range.
|
||||
*
|
||||
* @internal
|
||||
* @param string $value Value to test
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isRange($value)
|
||||
public function isRange(string $value): bool
|
||||
{
|
||||
return strpos($value, '-') !== false;
|
||||
return false !== strpos($value, '-');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a value is an increments of ranges
|
||||
* Check if a value is an increments of ranges.
|
||||
*
|
||||
* @internal
|
||||
* @param string $value Value to test
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isIncrementsOfRanges($value)
|
||||
public function isIncrementsOfRanges(string $value): bool
|
||||
{
|
||||
return strpos($value, '/') !== false;
|
||||
return false !== strpos($value, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if a value is within a range
|
||||
* Test if a value is within a range.
|
||||
*
|
||||
* @param string $dateValue Set date value
|
||||
* @param string $value Value to test
|
||||
* @internal
|
||||
* @param int $dateValue Set date value
|
||||
* @param string $value Value to test
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isInRange($dateValue, $value)
|
||||
public function isInRange(int $dateValue, $value): bool
|
||||
{
|
||||
$parts = array_map(function($value) {
|
||||
$parts = array_map(
|
||||
function ($value) {
|
||||
$value = trim($value);
|
||||
$value = $this->convertLiterals($value);
|
||||
return $value;
|
||||
|
||||
return $this->convertLiterals($value);
|
||||
},
|
||||
explode('-', $value, 2)
|
||||
);
|
||||
|
||||
|
||||
return $dateValue >= $parts[0] && $dateValue <= $parts[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if a value is within an increments of ranges (offset[-to]/step size)
|
||||
* Test if a value is within an increments of ranges (offset[-to]/step size).
|
||||
*
|
||||
* @param string $dateValue Set date value
|
||||
* @param string $value Value to test
|
||||
* @internal
|
||||
* @param int $dateValue Set date value
|
||||
* @param string $value Value to test
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isInIncrementsOfRanges($dateValue, $value)
|
||||
public function isInIncrementsOfRanges(int $dateValue, string $value): bool
|
||||
{
|
||||
$chunks = array_map('trim', explode('/', $value, 2));
|
||||
$range = $chunks[0];
|
||||
$step = isset($chunks[1]) ? $chunks[1] : 0;
|
||||
$step = $chunks[1] ?? 0;
|
||||
|
||||
// No step or 0 steps aren't cool
|
||||
if (is_null($step) || '0' === $step || 0 === $step) {
|
||||
/** @phpstan-ignore-next-line */
|
||||
if (null === $step || '0' === $step || 0 === $step) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Expand the * to a full range
|
||||
if ('*' == $range) {
|
||||
if ('*' === $range) {
|
||||
$range = $this->rangeStart . '-' . $this->rangeEnd;
|
||||
}
|
||||
|
||||
// Generate the requested small range
|
||||
$rangeChunks = explode('-', $range, 2);
|
||||
$rangeStart = $rangeChunks[0];
|
||||
$rangeEnd = isset($rangeChunks[1]) ? $rangeChunks[1] : $rangeStart;
|
||||
$rangeStart = (int) $rangeChunks[0];
|
||||
$rangeEnd = $rangeChunks[1] ?? $rangeStart;
|
||||
$rangeEnd = (int) $rangeEnd;
|
||||
|
||||
if ($rangeStart < $this->rangeStart || $rangeStart > $this->rangeEnd || $rangeStart > $rangeEnd) {
|
||||
throw new \OutOfRangeException('Invalid range start requested');
|
||||
@@ -141,82 +158,93 @@ abstract class AbstractField implements FieldInterface
|
||||
throw new \OutOfRangeException('Invalid range end requested');
|
||||
}
|
||||
|
||||
// Steps larger than the range need to wrap around and be handled slightly differently than smaller steps
|
||||
if ($step >= $this->rangeEnd) {
|
||||
$thisRange = [$this->fullRange[$step % count($this->fullRange)]];
|
||||
// Steps larger than the range need to wrap around and be handled
|
||||
// slightly differently than smaller steps
|
||||
|
||||
// UPDATE - This is actually false. The C implementation will allow a
|
||||
// larger step as valid syntax, it never wraps around. It will stop
|
||||
// once it hits the end. Unfortunately this means in future versions
|
||||
// we will not wrap around. However, because the logic exists today
|
||||
// per the above documentation, fixing the bug from #89
|
||||
if ($step > $this->rangeEnd) {
|
||||
$thisRange = [$this->fullRange[$step % \count($this->fullRange)]];
|
||||
} else {
|
||||
$thisRange = range($rangeStart, $rangeEnd, $step);
|
||||
if ($step > ($rangeEnd - $rangeStart)) {
|
||||
$thisRange[$rangeStart] = (int) $rangeStart;
|
||||
} else {
|
||||
$thisRange = range($rangeStart, $rangeEnd, (int) $step);
|
||||
}
|
||||
}
|
||||
|
||||
return in_array($dateValue, $thisRange);
|
||||
return \in_array($dateValue, $thisRange, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a range of values for the given cron expression
|
||||
* Returns a range of values for the given cron expression.
|
||||
*
|
||||
* @param string $expression The expression to evaluate
|
||||
* @param int $max Maximum offset for range
|
||||
* @param int $max Maximum offset for range
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getRangeForExpression($expression, $max)
|
||||
public function getRangeForExpression(string $expression, int $max): array
|
||||
{
|
||||
$values = array();
|
||||
$values = [];
|
||||
$expression = $this->convertLiterals($expression);
|
||||
|
||||
if (strpos($expression, ',') !== false) {
|
||||
if (false !== strpos($expression, ',')) {
|
||||
$ranges = explode(',', $expression);
|
||||
$values = [];
|
||||
foreach ($ranges as $range) {
|
||||
$expanded = $this->getRangeForExpression($range, $this->rangeEnd);
|
||||
$values = array_merge($values, $expanded);
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
if ($this->isRange($expression) || $this->isIncrementsOfRanges($expression)) {
|
||||
if (!$this->isIncrementsOfRanges($expression)) {
|
||||
list ($offset, $to) = explode('-', $expression);
|
||||
[$offset, $to] = explode('-', $expression);
|
||||
$offset = $this->convertLiterals($offset);
|
||||
$to = $this->convertLiterals($to);
|
||||
$stepSize = 1;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
$range = array_map('trim', explode('/', $expression, 2));
|
||||
$stepSize = isset($range[1]) ? $range[1] : 0;
|
||||
$stepSize = $range[1] ?? 0;
|
||||
$range = $range[0];
|
||||
$range = explode('-', $range, 2);
|
||||
$offset = $range[0];
|
||||
$to = isset($range[1]) ? $range[1] : $max;
|
||||
$to = $range[1] ?? $max;
|
||||
}
|
||||
$offset = $offset == '*' ? $this->rangeStart : $offset;
|
||||
$offset = '*' === $offset ? $this->rangeStart : $offset;
|
||||
if ($stepSize >= $this->rangeEnd) {
|
||||
$values = [$this->fullRange[$stepSize % count($this->fullRange)]];
|
||||
$values = [$this->fullRange[$stepSize % \count($this->fullRange)]];
|
||||
} else {
|
||||
for ($i = $offset; $i <= $to; $i += $stepSize) {
|
||||
$values[] = (int)$i;
|
||||
$values[] = (int) $i;
|
||||
}
|
||||
}
|
||||
sort($values);
|
||||
}
|
||||
else {
|
||||
$values = array($expression);
|
||||
} else {
|
||||
$values = [$expression];
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert literal
|
||||
* Convert literal.
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function convertLiterals($value)
|
||||
protected function convertLiterals(string $value): string
|
||||
{
|
||||
if (count($this->literals)) {
|
||||
$key = array_search($value, $this->literals);
|
||||
if ($key !== false) {
|
||||
if (\count($this->literals)) {
|
||||
$key = array_search(strtoupper($value), $this->literals, true);
|
||||
if (false !== $key) {
|
||||
return (string) $key;
|
||||
}
|
||||
}
|
||||
@@ -225,12 +253,13 @@ abstract class AbstractField implements FieldInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if a value is valid for the field
|
||||
* Checks to see if a value is valid for the field.
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function validate($value)
|
||||
public function validate(string $value): bool
|
||||
{
|
||||
$value = $this->convertLiterals($value);
|
||||
|
||||
@@ -239,22 +268,29 @@ abstract class AbstractField implements FieldInterface
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strpos($value, '/') !== false) {
|
||||
list($range, $step) = explode('/', $value);
|
||||
return $this->validate($range) && filter_var($step, FILTER_VALIDATE_INT);
|
||||
}
|
||||
|
||||
// Validate each chunk of a list individually
|
||||
if (strpos($value, ',') !== false) {
|
||||
if (false !== strpos($value, ',')) {
|
||||
foreach (explode(',', $value) as $listItem) {
|
||||
if (!$this->validate($listItem)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strpos($value, '-') !== false) {
|
||||
if (false !== strpos($value, '/')) {
|
||||
[$range, $step] = explode('/', $value);
|
||||
|
||||
// Don't allow numeric ranges
|
||||
if (is_numeric($range)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->validate($range) && filter_var($step, FILTER_VALIDATE_INT);
|
||||
}
|
||||
|
||||
if (false !== strpos($value, '-')) {
|
||||
if (substr_count($value, '-') > 1) {
|
||||
return false;
|
||||
}
|
||||
@@ -263,7 +299,7 @@ abstract class AbstractField implements FieldInterface
|
||||
$chunks[0] = $this->convertLiterals($chunks[0]);
|
||||
$chunks[1] = $this->convertLiterals($chunks[1]);
|
||||
|
||||
if ('*' == $chunks[0] || '*' == $chunks[1]) {
|
||||
if ('*' === $chunks[0] || '*' === $chunks[1]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -274,13 +310,37 @@ abstract class AbstractField implements FieldInterface
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_float($value) || strpos($value, '.') !== false) {
|
||||
if (false !== strpos($value, '.')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We should have a numeric by now, so coerce this into an integer
|
||||
$value = (int) $value;
|
||||
|
||||
return in_array($value, $this->fullRange, true);
|
||||
return \in_array($value, $this->fullRange, true);
|
||||
}
|
||||
|
||||
protected function timezoneSafeModify(DateTimeInterface $dt, string $modification): DateTimeInterface
|
||||
{
|
||||
$timezone = $dt->getTimezone();
|
||||
$dt = $dt->setTimezone(new \DateTimeZone("UTC"));
|
||||
$dt = $dt->modify($modification);
|
||||
$dt = $dt->setTimezone($timezone);
|
||||
return $dt;
|
||||
}
|
||||
|
||||
protected function setTimeHour(DateTimeInterface $date, bool $invert, int $originalTimestamp): DateTimeInterface
|
||||
{
|
||||
$date = $date->setTime((int)$date->format('H'), ($invert ? 59 : 0));
|
||||
|
||||
// setTime caused the offset to change, moving time in the wrong direction
|
||||
$actualTimestamp = $date->format('U');
|
||||
if ((! $invert) && ($actualTimestamp <= $originalTimestamp)) {
|
||||
$date = $this->timezoneSafeModify($date, "+1 hour");
|
||||
} elseif ($invert && ($actualTimestamp >= $originalTimestamp)) {
|
||||
$date = $this->timezoneSafeModify($date, "-1 hour");
|
||||
}
|
||||
|
||||
return $date;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cron;
|
||||
|
||||
use DateTime;
|
||||
@@ -8,7 +10,9 @@ use DateTimeInterface;
|
||||
use DateTimeZone;
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
use RuntimeException;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
/**
|
||||
* CRON expression parser that can determine whether or not a CRON expression is
|
||||
@@ -20,83 +24,147 @@ use RuntimeException;
|
||||
* minute [0-59], hour [0-23], day of month, month [1-12|JAN-DEC], day of week
|
||||
* [1-7|MON-SUN], and an optional year.
|
||||
*
|
||||
* @link http://en.wikipedia.org/wiki/Cron
|
||||
* @see http://en.wikipedia.org/wiki/Cron
|
||||
*/
|
||||
class CronExpression
|
||||
{
|
||||
const MINUTE = 0;
|
||||
const HOUR = 1;
|
||||
const DAY = 2;
|
||||
const MONTH = 3;
|
||||
const WEEKDAY = 4;
|
||||
const YEAR = 5;
|
||||
public const MINUTE = 0;
|
||||
public const HOUR = 1;
|
||||
public const DAY = 2;
|
||||
public const MONTH = 3;
|
||||
public const WEEKDAY = 4;
|
||||
|
||||
/** @deprecated */
|
||||
public const YEAR = 5;
|
||||
|
||||
public const MAPPINGS = [
|
||||
'@yearly' => '0 0 1 1 *',
|
||||
'@annually' => '0 0 1 1 *',
|
||||
'@monthly' => '0 0 1 * *',
|
||||
'@weekly' => '0 0 * * 0',
|
||||
'@daily' => '0 0 * * *',
|
||||
'@midnight' => '0 0 * * *',
|
||||
'@hourly' => '0 * * * *',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array CRON expression parts
|
||||
*/
|
||||
private $cronParts;
|
||||
protected $cronParts;
|
||||
|
||||
/**
|
||||
* @var FieldFactory CRON field factory
|
||||
* @var FieldFactoryInterface CRON field factory
|
||||
*/
|
||||
private $fieldFactory;
|
||||
protected $fieldFactory;
|
||||
|
||||
/**
|
||||
* @var int Max iteration count when searching for next run date
|
||||
*/
|
||||
private $maxIterationCount = 1000;
|
||||
protected $maxIterationCount = 1000;
|
||||
|
||||
/**
|
||||
* @var array Order in which to test of cron parts
|
||||
*/
|
||||
private static $order = array(self::YEAR, self::MONTH, self::DAY, self::WEEKDAY, self::HOUR, self::MINUTE);
|
||||
protected static $order = [
|
||||
self::YEAR,
|
||||
self::MONTH,
|
||||
self::DAY,
|
||||
self::WEEKDAY,
|
||||
self::HOUR,
|
||||
self::MINUTE,
|
||||
];
|
||||
|
||||
/**
|
||||
* Factory method to create a new CronExpression.
|
||||
*
|
||||
* @param string $expression The CRON expression to create. There are
|
||||
* several special predefined values which can be used to substitute the
|
||||
* CRON expression:
|
||||
*
|
||||
* `@yearly`, `@annually` - Run once a year, midnight, Jan. 1 - 0 0 1 1 *
|
||||
* `@monthly` - Run once a month, midnight, first of month - 0 0 1 * *
|
||||
* `@weekly` - Run once a week, midnight on Sun - 0 0 * * 0
|
||||
* `@daily` - Run once a day, midnight - 0 0 * * *
|
||||
* `@hourly` - Run once an hour, first minute - 0 * * * *
|
||||
* @param FieldFactory|null $fieldFactory Field factory to use
|
||||
*
|
||||
* @return CronExpression
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public static function factory($expression, FieldFactory $fieldFactory = null)
|
||||
{
|
||||
$mappings = array(
|
||||
'@yearly' => '0 0 1 1 *',
|
||||
'@annually' => '0 0 1 1 *',
|
||||
'@monthly' => '0 0 1 * *',
|
||||
'@weekly' => '0 0 * * 0',
|
||||
'@daily' => '0 0 * * *',
|
||||
'@hourly' => '0 * * * *'
|
||||
);
|
||||
private static $registeredAliases = self::MAPPINGS;
|
||||
|
||||
if (isset($mappings[$expression])) {
|
||||
$expression = $mappings[$expression];
|
||||
/**
|
||||
* Registered a user defined CRON Expression Alias.
|
||||
*
|
||||
* @throws LogicException If the expression or the alias name are invalid
|
||||
* or if the alias is already registered.
|
||||
*/
|
||||
public static function registerAlias(string $alias, string $expression): void
|
||||
{
|
||||
try {
|
||||
new self($expression);
|
||||
} catch (InvalidArgumentException $exception) {
|
||||
throw new LogicException("The expression `$expression` is invalid", 0, $exception);
|
||||
}
|
||||
|
||||
return new static($expression, $fieldFactory ?: new FieldFactory());
|
||||
$shortcut = strtolower($alias);
|
||||
if (1 !== preg_match('/^@\w+$/', $shortcut)) {
|
||||
throw new LogicException("The alias `$alias` is invalid. It must start with an `@` character and contain alphanumeric (letters, numbers, regardless of case) plus underscore (_).");
|
||||
}
|
||||
|
||||
if (isset(self::$registeredAliases[$shortcut])) {
|
||||
throw new LogicException("The alias `$alias` is already registered.");
|
||||
}
|
||||
|
||||
self::$registeredAliases[$shortcut] = $expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregistered a user defined CRON Expression Alias.
|
||||
*
|
||||
* @throws LogicException If the user tries to unregister a built-in alias
|
||||
*/
|
||||
public static function unregisterAlias(string $alias): bool
|
||||
{
|
||||
$shortcut = strtolower($alias);
|
||||
if (isset(self::MAPPINGS[$shortcut])) {
|
||||
throw new LogicException("The alias `$alias` is a built-in alias; it can not be unregistered.");
|
||||
}
|
||||
|
||||
if (!isset(self::$registeredAliases[$shortcut])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
unset(self::$registeredAliases[$shortcut]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether a CRON Expression alias is registered.
|
||||
*/
|
||||
public static function supportsAlias(string $alias): bool
|
||||
{
|
||||
return isset(self::$registeredAliases[strtolower($alias)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all registered aliases as an associated array where the aliases are the key
|
||||
* and their associated expressions are the values.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public static function getAliases(): array
|
||||
{
|
||||
return self::$registeredAliases;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since version 3.0.2, use __construct instead.
|
||||
*/
|
||||
public static function factory(string $expression, FieldFactoryInterface $fieldFactory = null): CronExpression
|
||||
{
|
||||
/** @phpstan-ignore-next-line */
|
||||
return new static($expression, $fieldFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a CronExpression.
|
||||
*
|
||||
* @param string $expression The CRON expression to validate.
|
||||
* @param string $expression the CRON expression to validate
|
||||
*
|
||||
* @return bool True if a valid CRON expression was passed. False if not.
|
||||
* @see \Cron\CronExpression::factory
|
||||
*/
|
||||
public static function isValidExpression($expression)
|
||||
public static function isValidExpression(string $expression): bool
|
||||
{
|
||||
try {
|
||||
self::factory($expression);
|
||||
new CronExpression($expression);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
return false;
|
||||
}
|
||||
@@ -105,29 +173,36 @@ class CronExpression
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a CRON expression
|
||||
* Parse a CRON expression.
|
||||
*
|
||||
* @param string $expression CRON expression (e.g. '8 * * * *')
|
||||
* @param FieldFactory|null $fieldFactory Factory to create cron fields
|
||||
* @param string $expression CRON expression (e.g. '8 * * * *')
|
||||
* @param null|FieldFactoryInterface $fieldFactory Factory to create cron fields
|
||||
*/
|
||||
public function __construct($expression, FieldFactory $fieldFactory = null)
|
||||
public function __construct(string $expression, FieldFactoryInterface $fieldFactory = null)
|
||||
{
|
||||
$this->fieldFactory = $fieldFactory;
|
||||
$shortcut = strtolower($expression);
|
||||
$expression = self::$registeredAliases[$shortcut] ?? $expression;
|
||||
|
||||
$this->fieldFactory = $fieldFactory ?: new FieldFactory();
|
||||
$this->setExpression($expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set or change the CRON expression
|
||||
* Set or change the CRON expression.
|
||||
*
|
||||
* @param string $value CRON expression (e.g. 8 * * * *)
|
||||
*
|
||||
* @return CronExpression
|
||||
* @throws \InvalidArgumentException if not a valid CRON expression
|
||||
*
|
||||
* @return CronExpression
|
||||
*/
|
||||
public function setExpression($value)
|
||||
public function setExpression(string $value): CronExpression
|
||||
{
|
||||
$this->cronParts = preg_split('/\s/', $value, -1, PREG_SPLIT_NO_EMPTY);
|
||||
if (count($this->cronParts) < 5) {
|
||||
$split = preg_split('/\s/', $value, -1, PREG_SPLIT_NO_EMPTY);
|
||||
Assert::isArray($split);
|
||||
|
||||
$this->cronParts = $split;
|
||||
if (\count($this->cronParts) < 5) {
|
||||
throw new InvalidArgumentException(
|
||||
$value . ' is not a valid CRON expression'
|
||||
);
|
||||
@@ -141,15 +216,16 @@ class CronExpression
|
||||
}
|
||||
|
||||
/**
|
||||
* Set part of the CRON expression
|
||||
* Set part of the CRON expression.
|
||||
*
|
||||
* @param int $position The position of the CRON expression to set
|
||||
* @param string $value The value to set
|
||||
* @param int $position The position of the CRON expression to set
|
||||
* @param string $value The value to set
|
||||
*
|
||||
* @throws \InvalidArgumentException if the value is not valid for the part
|
||||
*
|
||||
* @return CronExpression
|
||||
* @throws \InvalidArgumentException if the value is not valid for the part
|
||||
*/
|
||||
public function setPart($position, $value)
|
||||
public function setPart(int $position, string $value): CronExpression
|
||||
{
|
||||
if (!$this->fieldFactory->getField($position)->validate($value)) {
|
||||
throw new InvalidArgumentException(
|
||||
@@ -163,13 +239,13 @@ class CronExpression
|
||||
}
|
||||
|
||||
/**
|
||||
* Set max iteration count for searching next run dates
|
||||
* Set max iteration count for searching next run dates.
|
||||
*
|
||||
* @param int $maxIterationCount Max iteration count when searching for next run date
|
||||
*
|
||||
* @return CronExpression
|
||||
*/
|
||||
public function setMaxIterationCount($maxIterationCount)
|
||||
public function setMaxIterationCount(int $maxIterationCount): CronExpression
|
||||
{
|
||||
$this->maxIterationCount = $maxIterationCount;
|
||||
|
||||
@@ -191,16 +267,18 @@ class CronExpression
|
||||
* it matches the cron expression.
|
||||
* @param null|string $timeZone TimeZone to use instead of the system default
|
||||
*
|
||||
* @return \DateTime
|
||||
* @throws \RuntimeException on too many iterations
|
||||
* @throws \Exception
|
||||
*
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getNextRunDate($currentTime = 'now', $nth = 0, $allowCurrentDate = false, $timeZone = null)
|
||||
public function getNextRunDate($currentTime = 'now', int $nth = 0, bool $allowCurrentDate = false, $timeZone = null): DateTime
|
||||
{
|
||||
return $this->getRunDate($currentTime, $nth, false, $allowCurrentDate, $timeZone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a previous run date relative to the current date or a specific date
|
||||
* Get a previous run date relative to the current date or a specific date.
|
||||
*
|
||||
* @param string|\DateTimeInterface $currentTime Relative calculation date
|
||||
* @param int $nth Number of matches to skip before returning
|
||||
@@ -208,69 +286,104 @@ class CronExpression
|
||||
* current date if it matches the cron expression
|
||||
* @param null|string $timeZone TimeZone to use instead of the system default
|
||||
*
|
||||
* @return \DateTime
|
||||
* @throws \RuntimeException on too many iterations
|
||||
* @throws \Exception
|
||||
*
|
||||
* @return \DateTime
|
||||
*
|
||||
* @see \Cron\CronExpression::getNextRunDate
|
||||
*/
|
||||
public function getPreviousRunDate($currentTime = 'now', $nth = 0, $allowCurrentDate = false, $timeZone = null)
|
||||
public function getPreviousRunDate($currentTime = 'now', int $nth = 0, bool $allowCurrentDate = false, $timeZone = null): DateTime
|
||||
{
|
||||
return $this->getRunDate($currentTime, $nth, true, $allowCurrentDate, $timeZone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get multiple run dates starting at the current date or a specific date
|
||||
* Get multiple run dates starting at the current date or a specific date.
|
||||
*
|
||||
* @param int $total Set the total number of dates to calculate
|
||||
* @param string|\DateTimeInterface $currentTime Relative calculation date
|
||||
* @param bool $invert Set to TRUE to retrieve previous dates
|
||||
* @param bool $allowCurrentDate Set to TRUE to return the
|
||||
* current date if it matches the cron expression
|
||||
* @param null|string $timeZone TimeZone to use instead of the system default
|
||||
* @param int $total Set the total number of dates to calculate
|
||||
* @param string|\DateTimeInterface|null $currentTime Relative calculation date
|
||||
* @param bool $invert Set to TRUE to retrieve previous dates
|
||||
* @param bool $allowCurrentDate Set to TRUE to return the
|
||||
* current date if it matches the cron expression
|
||||
* @param null|string $timeZone TimeZone to use instead of the system default
|
||||
*
|
||||
* @return \DateTime[] Returns an array of run dates
|
||||
*/
|
||||
public function getMultipleRunDates($total, $currentTime = 'now', $invert = false, $allowCurrentDate = false, $timeZone = null)
|
||||
public function getMultipleRunDates(int $total, $currentTime = 'now', bool $invert = false, bool $allowCurrentDate = false, $timeZone = null): array
|
||||
{
|
||||
$matches = array();
|
||||
for ($i = 0; $i < max(0, $total); $i++) {
|
||||
$timeZone = $this->determineTimeZone($currentTime, $timeZone);
|
||||
|
||||
if ('now' === $currentTime) {
|
||||
$currentTime = new DateTime();
|
||||
} elseif ($currentTime instanceof DateTime) {
|
||||
$currentTime = clone $currentTime;
|
||||
} elseif ($currentTime instanceof DateTimeImmutable) {
|
||||
$currentTime = DateTime::createFromFormat('U', $currentTime->format('U'));
|
||||
} elseif (\is_string($currentTime)) {
|
||||
$currentTime = new DateTime($currentTime);
|
||||
}
|
||||
|
||||
Assert::isInstanceOf($currentTime, DateTime::class);
|
||||
$currentTime->setTimezone(new DateTimeZone($timeZone));
|
||||
|
||||
$matches = [];
|
||||
for ($i = 0; $i < $total; ++$i) {
|
||||
try {
|
||||
$matches[] = $this->getRunDate($currentTime, $i, $invert, $allowCurrentDate, $timeZone);
|
||||
$result = $this->getRunDate($currentTime, 0, $invert, $allowCurrentDate, $timeZone);
|
||||
} catch (RuntimeException $e) {
|
||||
break;
|
||||
}
|
||||
|
||||
$allowCurrentDate = false;
|
||||
$currentTime = clone $result;
|
||||
$matches[] = $result;
|
||||
}
|
||||
|
||||
return $matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all or part of the CRON expression
|
||||
* Get all or part of the CRON expression.
|
||||
*
|
||||
* @param string $part Specify the part to retrieve or NULL to get the full
|
||||
* cron schedule string.
|
||||
* @param int|string|null $part specify the part to retrieve or NULL to get the full
|
||||
* cron schedule string
|
||||
*
|
||||
* @return string|null Returns the CRON expression, a part of the
|
||||
* @return null|string Returns the CRON expression, a part of the
|
||||
* CRON expression, or NULL if the part was specified but not found
|
||||
*/
|
||||
public function getExpression($part = null)
|
||||
public function getExpression($part = null): ?string
|
||||
{
|
||||
if (null === $part) {
|
||||
return implode(' ', $this->cronParts);
|
||||
} elseif (array_key_exists($part, $this->cronParts)) {
|
||||
}
|
||||
|
||||
if (array_key_exists($part, $this->cronParts)) {
|
||||
return $this->cronParts[$part];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the parts of the cron expression as an array.
|
||||
*
|
||||
* @return string[]
|
||||
* The array of parts that make up this expression.
|
||||
*/
|
||||
public function getParts()
|
||||
{
|
||||
return $this->cronParts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to output the full expression.
|
||||
*
|
||||
* @return string Full CRON expression
|
||||
*/
|
||||
public function __toString()
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->getExpression();
|
||||
return (string) $this->getExpression();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -283,23 +396,25 @@ class CronExpression
|
||||
*
|
||||
* @return bool Returns TRUE if the cron is due to run or FALSE if not
|
||||
*/
|
||||
public function isDue($currentTime = 'now', $timeZone = null)
|
||||
public function isDue($currentTime = 'now', $timeZone = null): bool
|
||||
{
|
||||
$timeZone = $this->determineTimeZone($currentTime, $timeZone);
|
||||
|
||||
if ('now' === $currentTime) {
|
||||
$currentTime = new DateTime();
|
||||
} elseif ($currentTime instanceof DateTime) {
|
||||
//
|
||||
$currentTime = clone $currentTime;
|
||||
} elseif ($currentTime instanceof DateTimeImmutable) {
|
||||
$currentTime = DateTime::createFromFormat('U', $currentTime->format('U'));
|
||||
} else {
|
||||
} elseif (\is_string($currentTime)) {
|
||||
$currentTime = new DateTime($currentTime);
|
||||
}
|
||||
$currentTime->setTimeZone(new DateTimeZone($timeZone));
|
||||
|
||||
Assert::isInstanceOf($currentTime, DateTime::class);
|
||||
$currentTime->setTimezone(new DateTimeZone($timeZone));
|
||||
|
||||
// drop the seconds to 0
|
||||
$currentTime = DateTime::createFromFormat('Y-m-d H:i', $currentTime->format('Y-m-d H:i'));
|
||||
$currentTime->setTime((int) $currentTime->format('H'), (int) $currentTime->format('i'), 0);
|
||||
|
||||
try {
|
||||
return $this->getNextRunDate($currentTime, 0, true)->getTimestamp() === $currentTime->getTimestamp();
|
||||
@@ -309,19 +424,21 @@ class CronExpression
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next or previous run date of the expression relative to a date
|
||||
* Get the next or previous run date of the expression relative to a date.
|
||||
*
|
||||
* @param string|\DateTimeInterface $currentTime Relative calculation date
|
||||
* @param int $nth Number of matches to skip before returning
|
||||
* @param bool $invert Set to TRUE to go backwards in time
|
||||
* @param bool $allowCurrentDate Set to TRUE to return the
|
||||
* current date if it matches the cron expression
|
||||
* @param string|null $timeZone TimeZone to use instead of the system default
|
||||
* @param string|\DateTimeInterface|null $currentTime Relative calculation date
|
||||
* @param int $nth Number of matches to skip before returning
|
||||
* @param bool $invert Set to TRUE to go backwards in time
|
||||
* @param bool $allowCurrentDate Set to TRUE to return the
|
||||
* current date if it matches the cron expression
|
||||
* @param string|null $timeZone TimeZone to use instead of the system default
|
||||
*
|
||||
* @throws \RuntimeException on too many iterations
|
||||
* @throws Exception
|
||||
*
|
||||
* @return \DateTime
|
||||
* @throws \RuntimeException on too many iterations
|
||||
*/
|
||||
protected function getRunDate($currentTime = null, $nth = 0, $invert = false, $allowCurrentDate = false, $timeZone = null)
|
||||
protected function getRunDate($currentTime = null, int $nth = 0, bool $invert = false, bool $allowCurrentDate = false, $timeZone = null): DateTime
|
||||
{
|
||||
$timeZone = $this->determineTimeZone($currentTime, $timeZone);
|
||||
|
||||
@@ -329,18 +446,26 @@ class CronExpression
|
||||
$currentDate = clone $currentTime;
|
||||
} elseif ($currentTime instanceof DateTimeImmutable) {
|
||||
$currentDate = DateTime::createFromFormat('U', $currentTime->format('U'));
|
||||
} elseif (\is_string($currentTime)) {
|
||||
$currentDate = new DateTime($currentTime);
|
||||
} else {
|
||||
$currentDate = new DateTime($currentTime ?: 'now');
|
||||
$currentDate = new DateTime('now');
|
||||
}
|
||||
|
||||
$currentDate->setTimeZone(new DateTimeZone($timeZone));
|
||||
$currentDate->setTime($currentDate->format('H'), $currentDate->format('i'), 0);
|
||||
Assert::isInstanceOf($currentDate, DateTime::class);
|
||||
$currentDate->setTimezone(new DateTimeZone($timeZone));
|
||||
// Workaround for setTime causing an offset change: https://bugs.php.net/bug.php?id=81074
|
||||
$currentDate = DateTime::createFromFormat("!Y-m-d H:iO", $currentDate->format("Y-m-d H:iP"), $currentDate->getTimezone());
|
||||
if ($currentDate === false) {
|
||||
throw new \RuntimeException('Unable to create date from format');
|
||||
}
|
||||
$currentDate->setTimezone(new DateTimeZone($timeZone));
|
||||
|
||||
$nextRun = clone $currentDate;
|
||||
$nth = (int) $nth;
|
||||
|
||||
// We don't have to satisfy * or null fields
|
||||
$parts = array();
|
||||
$fields = array();
|
||||
$parts = [];
|
||||
$fields = [];
|
||||
foreach (self::$order as $position) {
|
||||
$part = $this->getExpression($position);
|
||||
if (null === $part || '*' === $part) {
|
||||
@@ -350,20 +475,49 @@ class CronExpression
|
||||
$fields[$position] = $this->fieldFactory->getField($position);
|
||||
}
|
||||
|
||||
// Set a hard limit to bail on an impossible date
|
||||
for ($i = 0; $i < $this->maxIterationCount; $i++) {
|
||||
if (isset($parts[self::DAY]) && isset($parts[self::WEEKDAY])) {
|
||||
$domExpression = sprintf('%s %s %s %s *', $this->getExpression(0), $this->getExpression(1), $this->getExpression(2), $this->getExpression(3));
|
||||
$dowExpression = sprintf('%s %s * %s %s', $this->getExpression(0), $this->getExpression(1), $this->getExpression(3), $this->getExpression(4));
|
||||
|
||||
$domExpression = new self($domExpression);
|
||||
$dowExpression = new self($dowExpression);
|
||||
|
||||
$domRunDates = $domExpression->getMultipleRunDates($nth + 1, $currentTime, $invert, $allowCurrentDate, $timeZone);
|
||||
$dowRunDates = $dowExpression->getMultipleRunDates($nth + 1, $currentTime, $invert, $allowCurrentDate, $timeZone);
|
||||
|
||||
if ($parts[self::DAY] === '?' || $parts[self::DAY] === '*') {
|
||||
$domRunDates = [];
|
||||
}
|
||||
|
||||
if ($parts[self::WEEKDAY] === '?' || $parts[self::WEEKDAY] === '*') {
|
||||
$dowRunDates = [];
|
||||
}
|
||||
|
||||
$combined = array_merge($domRunDates, $dowRunDates);
|
||||
usort($combined, function ($a, $b) {
|
||||
return $a->format('Y-m-d H:i:s') <=> $b->format('Y-m-d H:i:s');
|
||||
});
|
||||
if ($invert) {
|
||||
$combined = array_reverse($combined);
|
||||
}
|
||||
|
||||
return $combined[$nth];
|
||||
}
|
||||
|
||||
// Set a hard limit to bail on an impossible date
|
||||
for ($i = 0; $i < $this->maxIterationCount; ++$i) {
|
||||
foreach ($parts as $position => $part) {
|
||||
$satisfied = false;
|
||||
// Get the field object used to validate this part
|
||||
$field = $fields[$position];
|
||||
// Check if this is singular or a list
|
||||
if (strpos($part, ',') === false) {
|
||||
$satisfied = $field->isSatisfiedBy($nextRun, $part);
|
||||
if (false === strpos($part, ',')) {
|
||||
$satisfied = $field->isSatisfiedBy($nextRun, $part, $invert);
|
||||
} else {
|
||||
foreach (array_map('trim', explode(',', $part)) as $listPart) {
|
||||
if ($field->isSatisfiedBy($nextRun, $listPart)) {
|
||||
if ($field->isSatisfiedBy($nextRun, $listPart, $invert)) {
|
||||
$satisfied = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -372,13 +526,14 @@ class CronExpression
|
||||
// If the field is not satisfied, then start over
|
||||
if (!$satisfied) {
|
||||
$field->increment($nextRun, $invert, $part);
|
||||
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip this match if needed
|
||||
if ((!$allowCurrentDate && $nextRun == $currentDate) || --$nth > -1) {
|
||||
$this->fieldFactory->getField(0)->increment($nextRun, $invert, isset($parts[0]) ? $parts[0] : null);
|
||||
$this->fieldFactory->getField(self::MINUTE)->increment($nextRun, $invert, $parts[self::MINUTE] ?? null);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -393,19 +548,19 @@ class CronExpression
|
||||
/**
|
||||
* Workout what timeZone should be used.
|
||||
*
|
||||
* @param string|\DateTimeInterface $currentTime Relative calculation date
|
||||
* @param string|null $timeZone TimeZone to use instead of the system default
|
||||
* @param string|\DateTimeInterface|null $currentTime Relative calculation date
|
||||
* @param string|null $timeZone TimeZone to use instead of the system default
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function determineTimeZone($currentTime, $timeZone)
|
||||
protected function determineTimeZone($currentTime, ?string $timeZone): string
|
||||
{
|
||||
if (! is_null($timeZone)) {
|
||||
if (null !== $timeZone) {
|
||||
return $timeZone;
|
||||
}
|
||||
|
||||
if ($currentTime instanceOf DateTimeInterface) {
|
||||
return $currentTime->getTimeZone()->getName();
|
||||
if ($currentTime instanceof DateTimeInterface) {
|
||||
return $currentTime->getTimezone()->getName();
|
||||
}
|
||||
|
||||
return date_default_timezone_get();
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cron;
|
||||
|
||||
use DateTime;
|
||||
use DateTimeInterface;
|
||||
|
||||
/**
|
||||
* Day of month field. Allows: * , / - ? L W
|
||||
* Day of month field. Allows: * , / - ? L W.
|
||||
*
|
||||
* 'L' stands for "last" and specifies the last day of the month.
|
||||
*
|
||||
@@ -26,28 +28,33 @@ use DateTimeInterface;
|
||||
class DayOfMonthField extends AbstractField
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $rangeStart = 1;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $rangeEnd = 31;
|
||||
|
||||
/**
|
||||
* Get the nearest day of the week for a given day in a month
|
||||
* Get the nearest day of the week for a given day in a month.
|
||||
*
|
||||
* @param int $currentYear Current year
|
||||
* @param int $currentYear Current year
|
||||
* @param int $currentMonth Current month
|
||||
* @param int $targetDay Target day of the month
|
||||
* @param int $targetDay Target day of the month
|
||||
*
|
||||
* @return \DateTime Returns the nearest date
|
||||
* @return \DateTime|null Returns the nearest date
|
||||
*/
|
||||
private static function getNearestWeekday($currentYear, $currentMonth, $targetDay)
|
||||
private static function getNearestWeekday(int $currentYear, int $currentMonth, int $targetDay): ?DateTime
|
||||
{
|
||||
$tday = str_pad($targetDay, 2, '0', STR_PAD_LEFT);
|
||||
$target = DateTime::createFromFormat('Y-m-d', "$currentYear-$currentMonth-$tday");
|
||||
$tday = str_pad((string) $targetDay, 2, '0', STR_PAD_LEFT);
|
||||
$target = DateTime::createFromFormat('Y-m-d', "{$currentYear}-{$currentMonth}-{$tday}");
|
||||
|
||||
if ($target === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$currentWeekday = (int) $target->format('N');
|
||||
|
||||
if ($currentWeekday < 6) {
|
||||
@@ -55,81 +62,93 @@ class DayOfMonthField extends AbstractField
|
||||
}
|
||||
|
||||
$lastDayOfMonth = $target->format('t');
|
||||
|
||||
foreach (array(-1, 1, -2, 2) as $i) {
|
||||
foreach ([-1, 1, -2, 2] as $i) {
|
||||
$adjusted = $targetDay + $i;
|
||||
if ($adjusted > 0 && $adjusted <= $lastDayOfMonth) {
|
||||
$target->setDate($currentYear, $currentMonth, $adjusted);
|
||||
if ($target->format('N') < 6 && $target->format('m') == $currentMonth) {
|
||||
|
||||
if ((int) $target->format('N') < 6 && (int) $target->format('m') === $currentMonth) {
|
||||
return $target;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isSatisfiedBy(DateTimeInterface $date, $value)
|
||||
public function isSatisfiedBy(DateTimeInterface $date, $value, bool $invert): bool
|
||||
{
|
||||
// ? states that the field value is to be skipped
|
||||
if ($value == '?') {
|
||||
if ('?' === $value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$fieldValue = $date->format('d');
|
||||
|
||||
// Check to see if this is the last day of the month
|
||||
if ($value == 'L') {
|
||||
return $fieldValue == $date->format('t');
|
||||
if ('L' === $value) {
|
||||
return $fieldValue === $date->format('t');
|
||||
}
|
||||
|
||||
// Check to see if this is the nearest weekday to a particular value
|
||||
if (strpos($value, 'W')) {
|
||||
if ($wPosition = strpos($value, 'W')) {
|
||||
// Parse the target day
|
||||
$targetDay = substr($value, 0, strpos($value, 'W'));
|
||||
$targetDay = (int) substr($value, 0, $wPosition);
|
||||
// Find out if the current day is the nearest day of the week
|
||||
return $date->format('j') == self::getNearestWeekday(
|
||||
$date->format('Y'),
|
||||
$date->format('m'),
|
||||
$nearest = self::getNearestWeekday(
|
||||
(int) $date->format('Y'),
|
||||
(int) $date->format('m'),
|
||||
$targetDay
|
||||
)->format('j');
|
||||
);
|
||||
if ($nearest) {
|
||||
return $date->format('j') === $nearest->format('j');
|
||||
}
|
||||
|
||||
throw new \RuntimeException('Unable to return nearest weekday');
|
||||
}
|
||||
|
||||
return $this->isSatisfied($date->format('d'), $value);
|
||||
return $this->isSatisfied((int) $date->format('d'), $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* @param \DateTime|\DateTimeImmutable &$date
|
||||
* @param \DateTime|\DateTimeImmutable $date
|
||||
*/
|
||||
public function increment(DateTimeInterface &$date, $invert = false)
|
||||
public function increment(DateTimeInterface &$date, $invert = false, $parts = null): FieldInterface
|
||||
{
|
||||
if ($invert) {
|
||||
$date = $date->modify('previous day')->setTime(23, 59);
|
||||
if (! $invert) {
|
||||
$date = $date->add(new \DateInterval('P1D'));
|
||||
$date = $date->setTime(0, 0);
|
||||
} else {
|
||||
$date = $date->modify('next day')->setTime(0, 0);
|
||||
$date = $date->sub(new \DateInterval('P1D'));
|
||||
$date = $date->setTime(23, 59);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validate($value)
|
||||
public function validate(string $value): bool
|
||||
{
|
||||
$basicChecks = parent::validate($value);
|
||||
|
||||
// Validate that a list don't have W or L
|
||||
if (strpos($value, ',') !== false && (strpos($value, 'W') !== false || strpos($value, 'L') !== false)) {
|
||||
if (false !== strpos($value, ',') && (false !== strpos($value, 'W') || false !== strpos($value, 'L'))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$basicChecks) {
|
||||
if ('?' === $value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($value === 'L') {
|
||||
if ('L' === $value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cron;
|
||||
|
||||
use DateTime;
|
||||
use DateTimeInterface;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Day of week field. Allows: * / , - ? L #
|
||||
* Day of week field. Allows: * / , - ? L #.
|
||||
*
|
||||
* Days of the week can be represented as a number 0-7 (0|7 = Sunday)
|
||||
* or as a three letter string: SUN, MON, TUE, WED, THU, FRI, SAT.
|
||||
@@ -22,12 +23,12 @@ use InvalidArgumentException;
|
||||
class DayOfWeekField extends AbstractField
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $rangeStart = 0;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $rangeEnd = 7;
|
||||
|
||||
@@ -37,7 +38,7 @@ class DayOfWeekField extends AbstractField
|
||||
protected $nthRange;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $literals = [1 => 'MON', 2 => 'TUE', 3 => 'WED', 4 => 'THU', 5 => 'FRI', 6 => 'SAT', 7 => 'SUN'];
|
||||
|
||||
@@ -52,42 +53,33 @@ class DayOfWeekField extends AbstractField
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* @param \DateTime|\DateTimeImmutable $date
|
||||
*/
|
||||
public function isSatisfiedBy(DateTimeInterface $date, $value)
|
||||
public function isSatisfiedBy(DateTimeInterface $date, $value, bool $invert): bool
|
||||
{
|
||||
if ($value == '?') {
|
||||
if ('?' === $value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Convert text day of the week values to integers
|
||||
$value = $this->convertLiterals($value);
|
||||
|
||||
$currentYear = $date->format('Y');
|
||||
$currentMonth = $date->format('m');
|
||||
$lastDayOfMonth = $date->format('t');
|
||||
$currentYear = (int) $date->format('Y');
|
||||
$currentMonth = (int) $date->format('m');
|
||||
$lastDayOfMonth = (int) $date->format('t');
|
||||
|
||||
// Find out if this is the last specific weekday of the month
|
||||
if (strpos($value, 'L')) {
|
||||
$weekday = (int) $this->convertLiterals(substr($value, 0, strpos($value, 'L')));
|
||||
if ($lPosition = strpos($value, 'L')) {
|
||||
$weekday = $this->convertLiterals(substr($value, 0, $lPosition));
|
||||
$weekday %= 7;
|
||||
|
||||
$tdate = clone $date;
|
||||
$tdate = $tdate->setDate($currentYear, $currentMonth, $lastDayOfMonth);
|
||||
while ($tdate->format('w') != $weekday) {
|
||||
$tdateClone = new DateTime();
|
||||
$tdate = $tdateClone
|
||||
->setTimezone($tdate->getTimezone())
|
||||
->setDate($currentYear, $currentMonth, --$lastDayOfMonth);
|
||||
}
|
||||
|
||||
return $date->format('j') == $lastDayOfMonth;
|
||||
$daysInMonth = (int) $date->format('t');
|
||||
$remainingDaysInMonth = $daysInMonth - (int) $date->format('d');
|
||||
return (($weekday === (int) $date->format('w')) && ($remainingDaysInMonth < 7));
|
||||
}
|
||||
|
||||
// Handle # hash tokens
|
||||
if (strpos($value, '#')) {
|
||||
list($weekday, $nth) = explode('#', $value);
|
||||
[$weekday, $nth] = explode('#', $value);
|
||||
|
||||
if (!is_numeric($nth)) {
|
||||
throw new InvalidArgumentException("Hashed weekdays must be numeric, {$nth} given");
|
||||
@@ -96,23 +88,23 @@ class DayOfWeekField extends AbstractField
|
||||
}
|
||||
|
||||
// 0 and 7 are both Sunday, however 7 matches date('N') format ISO-8601
|
||||
if ($weekday === '0') {
|
||||
if ('0' === $weekday) {
|
||||
$weekday = 7;
|
||||
}
|
||||
|
||||
$weekday = $this->convertLiterals($weekday);
|
||||
$weekday = (int) $this->convertLiterals((string) $weekday);
|
||||
|
||||
// Validate the hash fields
|
||||
if ($weekday < 0 || $weekday > 7) {
|
||||
throw new InvalidArgumentException("Weekday must be a value between 0 and 7. {$weekday} given");
|
||||
}
|
||||
|
||||
if (!in_array($nth, $this->nthRange)) {
|
||||
if (!\in_array($nth, $this->nthRange, true)) {
|
||||
throw new InvalidArgumentException("There are never more than 5 or less than 1 of a given weekday in a month, {$nth} given");
|
||||
}
|
||||
|
||||
// The current weekday must match the targeted weekday to proceed
|
||||
if ($date->format('N') != $weekday) {
|
||||
if ((int) $date->format('N') !== $weekday) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -121,7 +113,7 @@ class DayOfWeekField extends AbstractField
|
||||
$dayCount = 0;
|
||||
$currentDay = 1;
|
||||
while ($currentDay < $lastDayOfMonth + 1) {
|
||||
if ($tdate->format('N') == $weekday) {
|
||||
if ((int) $tdate->format('N') === $weekday) {
|
||||
if (++$dayCount >= $nth) {
|
||||
break;
|
||||
}
|
||||
@@ -129,57 +121,63 @@ class DayOfWeekField extends AbstractField
|
||||
$tdate = $tdate->setDate($currentYear, $currentMonth, ++$currentDay);
|
||||
}
|
||||
|
||||
return $date->format('j') == $currentDay;
|
||||
return (int) $date->format('j') === $currentDay;
|
||||
}
|
||||
|
||||
// Handle day of the week values
|
||||
if (strpos($value, '-')) {
|
||||
if (false !== strpos($value, '-')) {
|
||||
$parts = explode('-', $value);
|
||||
if ($parts[0] == '7') {
|
||||
$parts[0] = '0';
|
||||
} elseif ($parts[1] == '0') {
|
||||
$parts[1] = '7';
|
||||
if ('7' === $parts[0]) {
|
||||
$parts[0] = 0;
|
||||
} elseif ('0' === $parts[1]) {
|
||||
$parts[1] = 7;
|
||||
}
|
||||
$value = implode('-', $parts);
|
||||
}
|
||||
|
||||
// Test to see which Sunday to use -- 0 == 7 == Sunday
|
||||
$format = in_array(7, str_split($value)) ? 'N' : 'w';
|
||||
$fieldValue = $date->format($format);
|
||||
$format = \in_array(7, array_map(function ($value) {
|
||||
return (int) $value;
|
||||
}, str_split($value)), true) ? 'N' : 'w';
|
||||
$fieldValue = (int) $date->format($format);
|
||||
|
||||
return $this->isSatisfied($fieldValue, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* @param \DateTime|\DateTimeImmutable &$date
|
||||
*/
|
||||
public function increment(DateTimeInterface &$date, $invert = false)
|
||||
public function increment(DateTimeInterface &$date, $invert = false, $parts = null): FieldInterface
|
||||
{
|
||||
if ($invert) {
|
||||
$date = $date->modify('-1 day')->setTime(23, 59, 0);
|
||||
if (! $invert) {
|
||||
$date = $date->add(new \DateInterval('P1D'));
|
||||
$date = $date->setTime(0, 0);
|
||||
} else {
|
||||
$date = $date->modify('+1 day')->setTime(0, 0, 0);
|
||||
$date = $date->sub(new \DateInterval('P1D'));
|
||||
$date = $date->setTime(23, 59);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validate($value)
|
||||
public function validate(string $value): bool
|
||||
{
|
||||
$basicChecks = parent::validate($value);
|
||||
|
||||
if (!$basicChecks) {
|
||||
if ('?' === $value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle the # value
|
||||
if (strpos($value, '#') !== false) {
|
||||
if (false !== strpos($value, '#')) {
|
||||
$chunks = explode('#', $value);
|
||||
$chunks[0] = $this->convertLiterals($chunks[0]);
|
||||
|
||||
if (parent::validate($chunks[0]) && is_numeric($chunks[1]) && in_array($chunks[1], $this->nthRange)) {
|
||||
if (parent::validate($chunks[0]) && is_numeric($chunks[1]) && \in_array((int) $chunks[1], $this->nthRange, true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,54 +1,52 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cron;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* CRON field factory implementing a flyweight factory
|
||||
* @link http://en.wikipedia.org/wiki/Cron
|
||||
* CRON field factory implementing a flyweight factory.
|
||||
*
|
||||
* @see http://en.wikipedia.org/wiki/Cron
|
||||
*/
|
||||
class FieldFactory
|
||||
class FieldFactory implements FieldFactoryInterface
|
||||
{
|
||||
/**
|
||||
* @var array Cache of instantiated fields
|
||||
*/
|
||||
private $fields = array();
|
||||
private $fields = [];
|
||||
|
||||
/**
|
||||
* Get an instance of a field object for a cron expression position
|
||||
* Get an instance of a field object for a cron expression position.
|
||||
*
|
||||
* @param int $position CRON expression position value to retrieve
|
||||
*
|
||||
* @return FieldInterface
|
||||
* @throws InvalidArgumentException if a position is not valid
|
||||
*/
|
||||
public function getField($position)
|
||||
public function getField(int $position): FieldInterface
|
||||
{
|
||||
if (!isset($this->fields[$position])) {
|
||||
switch ($position) {
|
||||
case 0:
|
||||
$this->fields[$position] = new MinutesField();
|
||||
break;
|
||||
case 1:
|
||||
$this->fields[$position] = new HoursField();
|
||||
break;
|
||||
case 2:
|
||||
$this->fields[$position] = new DayOfMonthField();
|
||||
break;
|
||||
case 3:
|
||||
$this->fields[$position] = new MonthField();
|
||||
break;
|
||||
case 4:
|
||||
$this->fields[$position] = new DayOfWeekField();
|
||||
break;
|
||||
default:
|
||||
throw new InvalidArgumentException(
|
||||
($position + 1) . ' is not a valid position'
|
||||
);
|
||||
}
|
||||
return $this->fields[$position] ?? $this->fields[$position] = $this->instantiateField($position);
|
||||
}
|
||||
|
||||
private function instantiateField(int $position): FieldInterface
|
||||
{
|
||||
switch ($position) {
|
||||
case CronExpression::MINUTE:
|
||||
return new MinutesField();
|
||||
case CronExpression::HOUR:
|
||||
return new HoursField();
|
||||
case CronExpression::DAY:
|
||||
return new DayOfMonthField();
|
||||
case CronExpression::MONTH:
|
||||
return new MonthField();
|
||||
case CronExpression::WEEKDAY:
|
||||
return new DayOfWeekField();
|
||||
}
|
||||
|
||||
return $this->fields[$position];
|
||||
throw new InvalidArgumentException(
|
||||
($position + 1) . ' is not a valid position'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
8
vendor/dragonmantank/cron-expression/src/Cron/FieldFactoryInterface.php
vendored
Normal file
8
vendor/dragonmantank/cron-expression/src/Cron/FieldFactoryInterface.php
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Cron;
|
||||
|
||||
interface FieldFactoryInterface
|
||||
{
|
||||
public function getField(int $position): FieldInterface;
|
||||
}
|
||||
@@ -1,41 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cron;
|
||||
|
||||
use DateTimeInterface;
|
||||
|
||||
/**
|
||||
* CRON field interface
|
||||
* CRON field interface.
|
||||
*/
|
||||
interface FieldInterface
|
||||
{
|
||||
/**
|
||||
* Check if the respective value of a DateTime field satisfies a CRON exp
|
||||
* Check if the respective value of a DateTime field satisfies a CRON exp.
|
||||
*
|
||||
* @internal
|
||||
* @param DateTimeInterface $date DateTime object to check
|
||||
* @param string $value CRON expression to test against
|
||||
*
|
||||
* @return bool Returns TRUE if satisfied, FALSE otherwise
|
||||
*/
|
||||
public function isSatisfiedBy(DateTimeInterface $date, $value);
|
||||
public function isSatisfiedBy(DateTimeInterface $date, $value, bool $invert): bool;
|
||||
|
||||
/**
|
||||
* When a CRON expression is not satisfied, this method is used to increment
|
||||
* or decrement a DateTime object by the unit of the cron field
|
||||
* or decrement a DateTime object by the unit of the cron field.
|
||||
*
|
||||
* @param DateTimeInterface &$date DateTime object to change
|
||||
* @param bool $invert (optional) Set to TRUE to decrement
|
||||
* @internal
|
||||
* @param DateTimeInterface $date DateTime object to change
|
||||
* @param bool $invert (optional) Set to TRUE to decrement
|
||||
* @param string|null $parts (optional) Set parts to use
|
||||
*
|
||||
* @return FieldInterface
|
||||
*/
|
||||
public function increment(DateTimeInterface &$date, $invert = false);
|
||||
public function increment(DateTimeInterface &$date, $invert = false, $parts = null): FieldInterface;
|
||||
|
||||
/**
|
||||
* Validates a CRON expression for a given field
|
||||
* Validates a CRON expression for a given field.
|
||||
*
|
||||
* @param string $value CRON expression value to validate
|
||||
*
|
||||
* @return bool Returns TRUE if valid, FALSE otherwise
|
||||
*/
|
||||
public function validate($value);
|
||||
public function validate(string $value): bool;
|
||||
}
|
||||
|
||||
@@ -1,83 +1,210 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cron;
|
||||
|
||||
use DateTimeInterface;
|
||||
use DateTimeZone;
|
||||
|
||||
/**
|
||||
* Hours field. Allows: * , / -
|
||||
* Hours field. Allows: * , / -.
|
||||
*/
|
||||
class HoursField extends AbstractField
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $rangeStart = 0;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $rangeEnd = 23;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @var array|null Transitions returned by DateTimeZone::getTransitions()
|
||||
*/
|
||||
public function isSatisfiedBy(DateTimeInterface $date, $value)
|
||||
protected $transitions = [];
|
||||
|
||||
/**
|
||||
* @var int|null Timestamp of the start of the transitions range
|
||||
*/
|
||||
protected $transitionsStart = null;
|
||||
|
||||
/**
|
||||
* @var int|null Timestamp of the end of the transitions range
|
||||
*/
|
||||
protected $transitionsEnd = null;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isSatisfiedBy(DateTimeInterface $date, $value, bool $invert): bool
|
||||
{
|
||||
if ($value == '?') {
|
||||
return true;
|
||||
$checkValue = (int) $date->format('H');
|
||||
$retval = $this->isSatisfied($checkValue, $value);
|
||||
if ($retval) {
|
||||
return $retval;
|
||||
}
|
||||
|
||||
return $this->isSatisfied($date->format('H'), $value);
|
||||
// Are we on the edge of a transition
|
||||
$lastTransition = $this->getPastTransition($date);
|
||||
if (($lastTransition !== null) && ($lastTransition["ts"] > ((int) $date->format('U') - 3600))) {
|
||||
$dtLastOffset = clone $date;
|
||||
$this->timezoneSafeModify($dtLastOffset, "-1 hour");
|
||||
$lastOffset = $dtLastOffset->getOffset();
|
||||
|
||||
$dtNextOffset = clone $date;
|
||||
$this->timezoneSafeModify($dtNextOffset, "+1 hour");
|
||||
$nextOffset = $dtNextOffset->getOffset();
|
||||
|
||||
$offsetChange = $nextOffset - $lastOffset;
|
||||
if ($offsetChange >= 3600) {
|
||||
$checkValue -= 1;
|
||||
return $this->isSatisfied($checkValue, $value);
|
||||
}
|
||||
if ((! $invert) && ($offsetChange <= -3600)) {
|
||||
$checkValue += 1;
|
||||
return $this->isSatisfied($checkValue, $value);
|
||||
}
|
||||
}
|
||||
|
||||
return $retval;
|
||||
}
|
||||
|
||||
public function getPastTransition(DateTimeInterface $date): ?array
|
||||
{
|
||||
$currentTimestamp = (int) $date->format('U');
|
||||
if (
|
||||
($this->transitions === null)
|
||||
|| ($this->transitionsStart < ($currentTimestamp + 86400))
|
||||
|| ($this->transitionsEnd > ($currentTimestamp - 86400))
|
||||
) {
|
||||
// We start a day before current time so we can differentiate between the first transition entry
|
||||
// and a change that happens now
|
||||
$dtLimitStart = clone $date;
|
||||
$dtLimitStart = $dtLimitStart->modify("-12 months");
|
||||
$dtLimitEnd = clone $date;
|
||||
$dtLimitEnd = $dtLimitEnd->modify('+12 months');
|
||||
|
||||
$this->transitions = $date->getTimezone()->getTransitions(
|
||||
$dtLimitStart->getTimestamp(),
|
||||
$dtLimitEnd->getTimestamp()
|
||||
);
|
||||
if (empty($this->transitions)) {
|
||||
return null;
|
||||
}
|
||||
$this->transitionsStart = $dtLimitStart->getTimestamp();
|
||||
$this->transitionsEnd = $dtLimitEnd->getTimestamp();
|
||||
}
|
||||
|
||||
$nextTransition = null;
|
||||
foreach ($this->transitions as $transition) {
|
||||
if ($transition["ts"] > $currentTimestamp) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (($nextTransition !== null) && ($transition["ts"] < $nextTransition["ts"])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$nextTransition = $transition;
|
||||
}
|
||||
|
||||
return ($nextTransition ?? null);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param \DateTime|\DateTimeImmutable &$date
|
||||
* @param string|null $parts
|
||||
*/
|
||||
public function increment(DateTimeInterface &$date, $invert = false, $parts = null)
|
||||
public function increment(DateTimeInterface &$date, $invert = false, $parts = null): FieldInterface
|
||||
{
|
||||
$originalTimestamp = (int) $date->format('U');
|
||||
|
||||
// Change timezone to UTC temporarily. This will
|
||||
// allow us to go back or forwards and hour even
|
||||
// if DST will be changed between the hours.
|
||||
if (is_null($parts) || $parts == '*') {
|
||||
$timezone = $date->getTimezone();
|
||||
$date = $date->setTimezone(new DateTimeZone('UTC'));
|
||||
$date = $date->modify(($invert ? '-' : '+') . '1 hour');
|
||||
$date = $date->setTimezone($timezone);
|
||||
if (null === $parts || '*' === $parts) {
|
||||
if ($invert) {
|
||||
$date = $date->sub(new \DateInterval('PT1H'));
|
||||
} else {
|
||||
$date = $date->add(new \DateInterval('PT1H'));
|
||||
}
|
||||
|
||||
$date = $date->setTime($date->format('H'), $invert ? 59 : 0);
|
||||
$date = $this->setTimeHour($date, $invert, $originalTimestamp);
|
||||
return $this;
|
||||
}
|
||||
|
||||
$parts = strpos($parts, ',') !== false ? explode(',', $parts) : array($parts);
|
||||
$hours = array();
|
||||
$parts = false !== strpos($parts, ',') ? explode(',', $parts) : [$parts];
|
||||
$hours = [];
|
||||
foreach ($parts as $part) {
|
||||
$hours = array_merge($hours, $this->getRangeForExpression($part, 23));
|
||||
}
|
||||
|
||||
$current_hour = $date->format('H');
|
||||
$position = $invert ? count($hours) - 1 : 0;
|
||||
if (count($hours) > 1) {
|
||||
for ($i = 0; $i < count($hours) - 1; $i++) {
|
||||
$current_hour = (int) $date->format('H');
|
||||
$position = $invert ? \count($hours) - 1 : 0;
|
||||
$countHours = \count($hours);
|
||||
if ($countHours > 1) {
|
||||
for ($i = 0; $i < $countHours - 1; ++$i) {
|
||||
if ((!$invert && $current_hour >= $hours[$i] && $current_hour < $hours[$i + 1]) ||
|
||||
($invert && $current_hour > $hours[$i] && $current_hour <= $hours[$i + 1])) {
|
||||
$position = $invert ? $i : $i + 1;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$hour = $hours[$position];
|
||||
if ((!$invert && $date->format('H') >= $hour) || ($invert && $date->format('H') <= $hour)) {
|
||||
$date = $date->modify(($invert ? '-' : '+') . '1 day');
|
||||
$date = $date->setTime($invert ? 23 : 0, $invert ? 59 : 0);
|
||||
$target = (int) $hours[$position];
|
||||
$originalHour = (int)$date->format('H');
|
||||
|
||||
$originalDay = (int)$date->format('d');
|
||||
$previousOffset = $date->getOffset();
|
||||
|
||||
if (! $invert) {
|
||||
if ($originalHour >= $target) {
|
||||
$distance = 24 - $originalHour;
|
||||
$date = $this->timezoneSafeModify($date, "+{$distance} hours");
|
||||
|
||||
$actualDay = (int)$date->format('d');
|
||||
$actualHour = (int)$date->format('H');
|
||||
if (($actualDay !== ($originalDay + 1)) && ($actualHour !== 0)) {
|
||||
$offsetChange = ($previousOffset - $date->getOffset());
|
||||
$date = $this->timezoneSafeModify($date, "+{$offsetChange} seconds");
|
||||
}
|
||||
|
||||
$originalHour = (int)$date->format('H');
|
||||
}
|
||||
|
||||
$distance = $target - $originalHour;
|
||||
$date = $this->timezoneSafeModify($date, "+{$distance} hours");
|
||||
} else {
|
||||
if ($originalHour <= $target) {
|
||||
$distance = ($originalHour + 1);
|
||||
$date = $this->timezoneSafeModify($date, "-" . $distance . " hours");
|
||||
|
||||
$actualDay = (int)$date->format('d');
|
||||
$actualHour = (int)$date->format('H');
|
||||
if (($actualDay !== ($originalDay - 1)) && ($actualHour !== 23)) {
|
||||
$offsetChange = ($previousOffset - $date->getOffset());
|
||||
$date = $this->timezoneSafeModify($date, "+{$offsetChange} seconds");
|
||||
}
|
||||
|
||||
$originalHour = (int)$date->format('H');
|
||||
}
|
||||
|
||||
$distance = $originalHour - $target;
|
||||
$date = $this->timezoneSafeModify($date, "-{$distance} hours");
|
||||
}
|
||||
else {
|
||||
$date = $date->setTime($hour, $invert ? 59 : 0);
|
||||
|
||||
$date = $this->setTimeHour($date, $invert, $originalTimestamp);
|
||||
|
||||
$actualHour = (int)$date->format('H');
|
||||
if ($invert && ($actualHour === ($target - 1) || (($actualHour === 23) && ($target === 0)))) {
|
||||
$date = $this->timezoneSafeModify($date, "+1 hour");
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
||||
@@ -1,73 +1,94 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cron;
|
||||
|
||||
use DateTimeInterface;
|
||||
|
||||
/**
|
||||
* Minutes field. Allows: * , / -
|
||||
* Minutes field. Allows: * , / -.
|
||||
*/
|
||||
class MinutesField extends AbstractField
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $rangeStart = 0;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $rangeEnd = 59;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isSatisfiedBy(DateTimeInterface $date, $value)
|
||||
public function isSatisfiedBy(DateTimeInterface $date, $value, bool $invert):bool
|
||||
{
|
||||
if ($value == '?') {
|
||||
if ($value === '?') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->isSatisfied($date->format('i'), $value);
|
||||
return $this->isSatisfied((int)$date->format('i'), $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @param \DateTime|\DateTimeImmutable &$date
|
||||
* @param string|null $parts
|
||||
*/
|
||||
public function increment(DateTimeInterface &$date, $invert = false, $parts = null)
|
||||
public function increment(DateTimeInterface &$date, $invert = false, $parts = null): FieldInterface
|
||||
{
|
||||
if (is_null($parts)) {
|
||||
$date = $date->modify(($invert ? '-' : '+') . '1 minute');
|
||||
$date = $this->timezoneSafeModify($date, ($invert ? "-" : "+") ."1 minute");
|
||||
return $this;
|
||||
}
|
||||
|
||||
$parts = strpos($parts, ',') !== false ? explode(',', $parts) : array($parts);
|
||||
$minutes = array();
|
||||
$current_minute = (int) $date->format('i');
|
||||
|
||||
$parts = false !== strpos($parts, ',') ? explode(',', $parts) : [$parts];
|
||||
$minutes = [];
|
||||
foreach ($parts as $part) {
|
||||
$minutes = array_merge($minutes, $this->getRangeForExpression($part, 59));
|
||||
}
|
||||
|
||||
$current_minute = $date->format('i');
|
||||
$position = $invert ? count($minutes) - 1 : 0;
|
||||
if (count($minutes) > 1) {
|
||||
for ($i = 0; $i < count($minutes) - 1; $i++) {
|
||||
$position = $invert ? \count($minutes) - 1 : 0;
|
||||
if (\count($minutes) > 1) {
|
||||
for ($i = 0; $i < \count($minutes) - 1; ++$i) {
|
||||
if ((!$invert && $current_minute >= $minutes[$i] && $current_minute < $minutes[$i + 1]) ||
|
||||
($invert && $current_minute > $minutes[$i] && $current_minute <= $minutes[$i + 1])) {
|
||||
$position = $invert ? $i : $i + 1;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((!$invert && $current_minute >= $minutes[$position]) || ($invert && $current_minute <= $minutes[$position])) {
|
||||
$date = $date->modify(($invert ? '-' : '+') . '1 hour');
|
||||
$date = $date->setTime($date->format('H'), $invert ? 59 : 0);
|
||||
}
|
||||
else {
|
||||
$date = $date->setTime($date->format('H'), $minutes[$position]);
|
||||
$target = (int) $minutes[$position];
|
||||
$originalMinute = (int) $date->format("i");
|
||||
|
||||
if (! $invert) {
|
||||
if ($originalMinute >= $target) {
|
||||
$distance = 60 - $originalMinute;
|
||||
$date = $this->timezoneSafeModify($date, "+{$distance} minutes");
|
||||
|
||||
$originalMinute = (int) $date->format("i");
|
||||
}
|
||||
|
||||
$distance = $target - $originalMinute;
|
||||
$date = $this->timezoneSafeModify($date, "+{$distance} minutes");
|
||||
} else {
|
||||
if ($originalMinute <= $target) {
|
||||
$distance = ($originalMinute + 1);
|
||||
$date = $this->timezoneSafeModify($date, "-{$distance} minutes");
|
||||
|
||||
$originalMinute = (int) $date->format("i");
|
||||
}
|
||||
|
||||
$distance = $originalMinute - $target;
|
||||
$date = $this->timezoneSafeModify($date, "-{$distance} minutes");
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
||||
@@ -1,59 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cron;
|
||||
|
||||
use DateTimeInterface;
|
||||
|
||||
/**
|
||||
* Month field. Allows: * , / -
|
||||
* Month field. Allows: * , / -.
|
||||
*/
|
||||
class MonthField extends AbstractField
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $rangeStart = 1;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $rangeEnd = 12;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $literals = [1 => 'JAN', 2 => 'FEB', 3 => 'MAR', 4 => 'APR', 5 => 'MAY', 6 => 'JUN', 7 => 'JUL',
|
||||
8 => 'AUG', 9 => 'SEP', 10 => 'OCT', 11 => 'NOV', 12 => 'DEC'];
|
||||
8 => 'AUG', 9 => 'SEP', 10 => 'OCT', 11 => 'NOV', 12 => 'DEC', ];
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isSatisfiedBy(DateTimeInterface $date, $value)
|
||||
public function isSatisfiedBy(DateTimeInterface $date, $value, bool $invert): bool
|
||||
{
|
||||
if ($value == '?') {
|
||||
if ($value === '?') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$value = $this->convertLiterals($value);
|
||||
|
||||
return $this->isSatisfied($date->format('m'), $value);
|
||||
return $this->isSatisfied((int) $date->format('m'), $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* @param \DateTime|\DateTimeImmutable &$date
|
||||
* @param \DateTime|\DateTimeImmutable $date
|
||||
*/
|
||||
public function increment(DateTimeInterface &$date, $invert = false)
|
||||
public function increment(DateTimeInterface &$date, $invert = false, $parts = null): FieldInterface
|
||||
{
|
||||
if ($invert) {
|
||||
$date = $date->modify('last day of previous month')->setTime(23, 59);
|
||||
if (! $invert) {
|
||||
$date = $date->modify('first day of next month');
|
||||
$date = $date->setTime(0, 0);
|
||||
} else {
|
||||
$date = $date->modify('first day of next month')->setTime(0, 0);
|
||||
$date = $date->modify('last day of previous month');
|
||||
$date = $date->setTime(23, 59);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Cron\Tests;
|
||||
|
||||
use Cron\DayOfWeekField;
|
||||
use Cron\HoursField;
|
||||
use Cron\MinutesField;
|
||||
use Cron\MonthField;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @author Michael Dowling <mtdowling@gmail.com>
|
||||
*/
|
||||
class AbstractFieldTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @covers \Cron\AbstractField::isRange
|
||||
*/
|
||||
public function testTestsIfRange()
|
||||
{
|
||||
$f = new DayOfWeekField();
|
||||
$this->assertTrue($f->isRange('1-2'));
|
||||
$this->assertFalse($f->isRange('2'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\AbstractField::isIncrementsOfRanges
|
||||
*/
|
||||
public function testTestsIfIncrementsOfRanges()
|
||||
{
|
||||
$f = new DayOfWeekField();
|
||||
$this->assertFalse($f->isIncrementsOfRanges('1-2'));
|
||||
$this->assertTrue($f->isIncrementsOfRanges('1/2'));
|
||||
$this->assertTrue($f->isIncrementsOfRanges('*/2'));
|
||||
$this->assertTrue($f->isIncrementsOfRanges('3-12/2'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\AbstractField::isInRange
|
||||
*/
|
||||
public function testTestsIfInRange()
|
||||
{
|
||||
$f = new DayOfWeekField();
|
||||
$this->assertTrue($f->isInRange('1', '1-2'));
|
||||
$this->assertTrue($f->isInRange('2', '1-2'));
|
||||
$this->assertTrue($f->isInRange('5', '4-12'));
|
||||
$this->assertFalse($f->isInRange('3', '4-12'));
|
||||
$this->assertFalse($f->isInRange('13', '4-12'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\AbstractField::isInIncrementsOfRanges
|
||||
*/
|
||||
public function testTestsIfInIncrementsOfRangesOnZeroStartRange()
|
||||
{
|
||||
$f = new MinutesField();
|
||||
$this->assertTrue($f->isInIncrementsOfRanges('3', '3-59/2'));
|
||||
$this->assertTrue($f->isInIncrementsOfRanges('13', '3-59/2'));
|
||||
$this->assertTrue($f->isInIncrementsOfRanges('15', '3-59/2'));
|
||||
$this->assertTrue($f->isInIncrementsOfRanges('14', '*/2'));
|
||||
$this->assertFalse($f->isInIncrementsOfRanges('2', '3-59/13'));
|
||||
$this->assertFalse($f->isInIncrementsOfRanges('14', '*/13'));
|
||||
$this->assertFalse($f->isInIncrementsOfRanges('14', '3-59/2'));
|
||||
$this->assertFalse($f->isInIncrementsOfRanges('3', '2-59'));
|
||||
$this->assertFalse($f->isInIncrementsOfRanges('3', '2'));
|
||||
$this->assertFalse($f->isInIncrementsOfRanges('3', '*'));
|
||||
$this->assertFalse($f->isInIncrementsOfRanges('0', '*/0'));
|
||||
$this->assertFalse($f->isInIncrementsOfRanges('1', '*/0'));
|
||||
|
||||
$this->assertTrue($f->isInIncrementsOfRanges('4', '4/1'));
|
||||
$this->assertFalse($f->isInIncrementsOfRanges('14', '4/1'));
|
||||
$this->assertFalse($f->isInIncrementsOfRanges('34', '4/1'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\AbstractField::isInIncrementsOfRanges
|
||||
*/
|
||||
public function testTestsIfInIncrementsOfRangesOnOneStartRange()
|
||||
{
|
||||
$f = new MonthField();
|
||||
$this->assertTrue($f->isInIncrementsOfRanges('3', '3-12/2'));
|
||||
$this->assertFalse($f->isInIncrementsOfRanges('13', '3-12/2'));
|
||||
$this->assertFalse($f->isInIncrementsOfRanges('15', '3-12/2'));
|
||||
$this->assertTrue($f->isInIncrementsOfRanges('3', '*/2'));
|
||||
$this->assertFalse($f->isInIncrementsOfRanges('3', '*/3'));
|
||||
$this->assertTrue($f->isInIncrementsOfRanges('7', '*/3'));
|
||||
$this->assertFalse($f->isInIncrementsOfRanges('14', '3-12/2'));
|
||||
$this->assertFalse($f->isInIncrementsOfRanges('3', '2-12'));
|
||||
$this->assertFalse($f->isInIncrementsOfRanges('3', '2'));
|
||||
$this->assertFalse($f->isInIncrementsOfRanges('3', '*'));
|
||||
$this->assertFalse($f->isInIncrementsOfRanges('0', '*/0'));
|
||||
$this->assertFalse($f->isInIncrementsOfRanges('1', '*/0'));
|
||||
|
||||
$this->assertTrue($f->isInIncrementsOfRanges('4', '4/1'));
|
||||
$this->assertFalse($f->isInIncrementsOfRanges('14', '4/1'));
|
||||
$this->assertFalse($f->isInIncrementsOfRanges('34', '4/1'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\AbstractField::isSatisfied
|
||||
*/
|
||||
public function testTestsIfSatisfied()
|
||||
{
|
||||
$f = new DayOfWeekField();
|
||||
$this->assertTrue($f->isSatisfied('12', '3-13'));
|
||||
$this->assertFalse($f->isSatisfied('15', '3-7/2'));
|
||||
$this->assertTrue($f->isSatisfied('12', '*'));
|
||||
$this->assertTrue($f->isSatisfied('12', '12'));
|
||||
$this->assertFalse($f->isSatisfied('12', '3-11'));
|
||||
$this->assertFalse($f->isSatisfied('12', '3-7/2'));
|
||||
$this->assertFalse($f->isSatisfied('12', '11'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows ranges and lists to coexist in the same expression
|
||||
*
|
||||
* @see https://github.com/dragonmantank/cron-expression/issues/5
|
||||
*/
|
||||
public function testAllowRangesAndLists()
|
||||
{
|
||||
$expression = '5-7,11-13';
|
||||
$f = new HoursField();
|
||||
$this->assertTrue($f->validate($expression));
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that various types of ranges expand out properly
|
||||
*
|
||||
* @see https://github.com/dragonmantank/cron-expression/issues/5
|
||||
*/
|
||||
public function testGetRangeForExpressionExpandsCorrectly()
|
||||
{
|
||||
$f = new HoursField();
|
||||
$this->assertSame([5, 6, 7, 11, 12, 13], $f->getRangeForExpression('5-7,11-13', 23));
|
||||
$this->assertSame(['5', '6', '7', '11', '12', '13'], $f->getRangeForExpression('5,6,7,11,12,13', 23));
|
||||
$this->assertSame([0, 6, 12, 18], $f->getRangeForExpression('*/6', 23));
|
||||
$this->assertSame([5, 11], $f->getRangeForExpression('5-13/6', 23));
|
||||
}
|
||||
}
|
||||
@@ -1,589 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Cron\Tests;
|
||||
|
||||
use Cron\CronExpression;
|
||||
use Cron\MonthField;
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeZone;
|
||||
use InvalidArgumentException;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @author Michael Dowling <mtdowling@gmail.com>
|
||||
*/
|
||||
class CronExpressionTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @covers \Cron\CronExpression::factory
|
||||
*/
|
||||
public function testFactoryRecognizesTemplates()
|
||||
{
|
||||
$this->assertSame('0 0 1 1 *', CronExpression::factory('@annually')->getExpression());
|
||||
$this->assertSame('0 0 1 1 *', CronExpression::factory('@yearly')->getExpression());
|
||||
$this->assertSame('0 0 * * 0', CronExpression::factory('@weekly')->getExpression());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\CronExpression::__construct
|
||||
* @covers \Cron\CronExpression::getExpression
|
||||
* @covers \Cron\CronExpression::__toString
|
||||
*/
|
||||
public function testParsesCronSchedule()
|
||||
{
|
||||
// '2010-09-10 12:00:00'
|
||||
$cron = CronExpression::factory('1 2-4 * 4,5,6 */3');
|
||||
$this->assertSame('1', $cron->getExpression(CronExpression::MINUTE));
|
||||
$this->assertSame('2-4', $cron->getExpression(CronExpression::HOUR));
|
||||
$this->assertSame('*', $cron->getExpression(CronExpression::DAY));
|
||||
$this->assertSame('4,5,6', $cron->getExpression(CronExpression::MONTH));
|
||||
$this->assertSame('*/3', $cron->getExpression(CronExpression::WEEKDAY));
|
||||
$this->assertSame('1 2-4 * 4,5,6 */3', $cron->getExpression());
|
||||
$this->assertSame('1 2-4 * 4,5,6 */3', (string) $cron);
|
||||
$this->assertNull($cron->getExpression('foo'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\CronExpression::__construct
|
||||
* @covers \Cron\CronExpression::getExpression
|
||||
* @covers \Cron\CronExpression::__toString
|
||||
*/
|
||||
public function testParsesCronScheduleThrowsAnException()
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('Invalid CRON field value A at position 0');
|
||||
|
||||
CronExpression::factory('A 1 2 3 4');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\CronExpression::__construct
|
||||
* @covers \Cron\CronExpression::getExpression
|
||||
* @dataProvider scheduleWithDifferentSeparatorsProvider
|
||||
*/
|
||||
public function testParsesCronScheduleWithAnySpaceCharsAsSeparators($schedule, array $expected)
|
||||
{
|
||||
$cron = CronExpression::factory($schedule);
|
||||
$this->assertSame($expected[0], $cron->getExpression(CronExpression::MINUTE));
|
||||
$this->assertSame($expected[1], $cron->getExpression(CronExpression::HOUR));
|
||||
$this->assertSame($expected[2], $cron->getExpression(CronExpression::DAY));
|
||||
$this->assertSame($expected[3], $cron->getExpression(CronExpression::MONTH));
|
||||
$this->assertSame($expected[4], $cron->getExpression(CronExpression::WEEKDAY));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testParsesCronScheduleWithAnySpaceCharsAsSeparators
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function scheduleWithDifferentSeparatorsProvider()
|
||||
{
|
||||
return array(
|
||||
array("*\t*\t*\t*\t*\t", array('*', '*', '*', '*', '*', '*')),
|
||||
array("* * * * * ", array('*', '*', '*', '*', '*', '*')),
|
||||
array("* \t * \t * \t * \t * \t", array('*', '*', '*', '*', '*', '*')),
|
||||
array("*\t \t*\t \t*\t \t*\t \t*\t \t", array('*', '*', '*', '*', '*', '*')),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\CronExpression::__construct
|
||||
* @covers \Cron\CronExpression::setExpression
|
||||
* @covers \Cron\CronExpression::setPart
|
||||
*/
|
||||
public function testInvalidCronsWillFail()
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
// Only four values
|
||||
$cron = CronExpression::factory('* * * 1');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\CronExpression::setPart
|
||||
*/
|
||||
public function testInvalidPartsWillFail()
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
// Only four values
|
||||
$cron = CronExpression::factory('* * * * *');
|
||||
$cron->setPart(1, 'abc');
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for cron schedule
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function scheduleProvider()
|
||||
{
|
||||
return array(
|
||||
array('*/2 */2 * * *', '2015-08-10 21:47:27', '2015-08-10 22:00:00', false),
|
||||
array('* * * * *', '2015-08-10 21:50:37', '2015-08-10 21:50:00', true),
|
||||
array('* 20,21,22 * * *', '2015-08-10 21:50:00', '2015-08-10 21:50:00', true),
|
||||
// Handles CSV values
|
||||
array('* 20,22 * * *', '2015-08-10 21:50:00', '2015-08-10 22:00:00', false),
|
||||
// CSV values can be complex
|
||||
array('7-9 * */9 * *', '2015-08-10 22:02:33', '2015-08-10 22:07:00', false),
|
||||
// 15th minute, of the second hour, every 15 days, in January, every Friday
|
||||
array('1 * * * 7', '2015-08-10 21:47:27', '2015-08-16 00:01:00', false),
|
||||
// Test with exact times
|
||||
array('47 21 * * *', strtotime('2015-08-10 21:47:30'), '2015-08-10 21:47:00', true),
|
||||
// Test Day of the week (issue #1)
|
||||
// According cron implementation, 0|7 = sunday, 1 => monday, etc
|
||||
array('* * * * 0', strtotime('2011-06-15 23:09:00'), '2011-06-19 00:00:00', false),
|
||||
array('* * * * 7', strtotime('2011-06-15 23:09:00'), '2011-06-19 00:00:00', false),
|
||||
array('* * * * 1', strtotime('2011-06-15 23:09:00'), '2011-06-20 00:00:00', false),
|
||||
// Should return the sunday date as 7 equals 0
|
||||
array('0 0 * * MON,SUN', strtotime('2011-06-15 23:09:00'), '2011-06-19 00:00:00', false),
|
||||
array('0 0 * * 1,7', strtotime('2011-06-15 23:09:00'), '2011-06-19 00:00:00', false),
|
||||
array('0 0 * * 0-4', strtotime('2011-06-15 23:09:00'), '2011-06-16 00:00:00', false),
|
||||
array('0 0 * * 7-4', strtotime('2011-06-15 23:09:00'), '2011-06-16 00:00:00', false),
|
||||
array('0 0 * * 4-7', strtotime('2011-06-15 23:09:00'), '2011-06-16 00:00:00', false),
|
||||
array('0 0 * * 7-3', strtotime('2011-06-15 23:09:00'), '2011-06-19 00:00:00', false),
|
||||
array('0 0 * * 3-7', strtotime('2011-06-15 23:09:00'), '2011-06-16 00:00:00', false),
|
||||
array('0 0 * * 3-7', strtotime('2011-06-18 23:09:00'), '2011-06-19 00:00:00', false),
|
||||
// Test lists of values and ranges (Abhoryo)
|
||||
array('0 0 * * 2-7', strtotime('2011-06-20 23:09:00'), '2011-06-21 00:00:00', false),
|
||||
array('0 0 * * 2-7', strtotime('2011-06-18 23:09:00'), '2011-06-19 00:00:00', false),
|
||||
array('0 0 * * 4-7', strtotime('2011-07-19 00:00:00'), '2011-07-21 00:00:00', false),
|
||||
// Test increments of ranges
|
||||
array('0-12/4 * * * *', strtotime('2011-06-20 12:04:00'), '2011-06-20 12:04:00', true),
|
||||
array('4-59/2 * * * *', strtotime('2011-06-20 12:04:00'), '2011-06-20 12:04:00', true),
|
||||
array('4-59/2 * * * *', strtotime('2011-06-20 12:06:00'), '2011-06-20 12:06:00', true),
|
||||
array('4-59/3 * * * *', strtotime('2011-06-20 12:06:00'), '2011-06-20 12:07:00', false),
|
||||
// Test Day of the Week and the Day of the Month (issue #1)
|
||||
array('0 0 1 1 0', strtotime('2011-06-15 23:09:00'), '2012-01-01 00:00:00', false),
|
||||
array('0 0 1 JAN 0', strtotime('2011-06-15 23:09:00'), '2012-01-01 00:00:00', false),
|
||||
array('0 0 1 * 0', strtotime('2011-06-15 23:09:00'), '2012-01-01 00:00:00', false),
|
||||
// Test the W day of the week modifier for day of the month field
|
||||
array('0 0 2W * *', strtotime('2011-07-01 00:00:00'), '2011-07-01 00:00:00', true),
|
||||
array('0 0 1W * *', strtotime('2011-05-01 00:00:00'), '2011-05-02 00:00:00', false),
|
||||
array('0 0 1W * *', strtotime('2011-07-01 00:00:00'), '2011-07-01 00:00:00', true),
|
||||
array('0 0 3W * *', strtotime('2011-07-01 00:00:00'), '2011-07-04 00:00:00', false),
|
||||
array('0 0 16W * *', strtotime('2011-07-01 00:00:00'), '2011-07-15 00:00:00', false),
|
||||
array('0 0 28W * *', strtotime('2011-07-01 00:00:00'), '2011-07-28 00:00:00', false),
|
||||
array('0 0 30W * *', strtotime('2011-07-01 00:00:00'), '2011-07-29 00:00:00', false),
|
||||
array('0 0 31W * *', strtotime('2011-07-01 00:00:00'), '2011-07-29 00:00:00', false),
|
||||
// Test the last weekday of a month
|
||||
array('* * * * 5L', strtotime('2011-07-01 00:00:00'), '2011-07-29 00:00:00', false),
|
||||
array('* * * * 6L', strtotime('2011-07-01 00:00:00'), '2011-07-30 00:00:00', false),
|
||||
array('* * * * 7L', strtotime('2011-07-01 00:00:00'), '2011-07-31 00:00:00', false),
|
||||
array('* * * * 1L', strtotime('2011-07-24 00:00:00'), '2011-07-25 00:00:00', false),
|
||||
array('* * * 1 5L', strtotime('2011-12-25 00:00:00'), '2012-01-27 00:00:00', false),
|
||||
// Test the hash symbol for the nth weekday of a given month
|
||||
array('* * * * 5#2', strtotime('2011-07-01 00:00:00'), '2011-07-08 00:00:00', false),
|
||||
array('* * * * 5#1', strtotime('2011-07-01 00:00:00'), '2011-07-01 00:00:00', true),
|
||||
array('* * * * 3#4', strtotime('2011-07-01 00:00:00'), '2011-07-27 00:00:00', false),
|
||||
|
||||
// Issue #7, documented example failed
|
||||
['3-59/15 6-12 */15 1 2-5', strtotime('2017-01-08 00:00:00'), '2017-01-31 06:03:00', false],
|
||||
|
||||
// https://github.com/laravel/framework/commit/07d160ac3cc9764d5b429734ffce4fa311385403
|
||||
['* * * * MON-FRI', strtotime('2017-01-08 00:00:00'), strtotime('2017-01-09 00:00:00'), false],
|
||||
['* * * * TUE', strtotime('2017-01-08 00:00:00'), strtotime('2017-01-10 00:00:00'), false],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\CronExpression::isDue
|
||||
* @covers \Cron\CronExpression::getNextRunDate
|
||||
* @covers \Cron\DayOfMonthField
|
||||
* @covers \Cron\DayOfWeekField
|
||||
* @covers \Cron\MinutesField
|
||||
* @covers \Cron\HoursField
|
||||
* @covers \Cron\MonthField
|
||||
* @covers \Cron\CronExpression::getRunDate
|
||||
* @dataProvider scheduleProvider
|
||||
*/
|
||||
public function testDeterminesIfCronIsDue($schedule, $relativeTime, $nextRun, $isDue)
|
||||
{
|
||||
$relativeTimeString = is_int($relativeTime) ? date('Y-m-d H:i:s', $relativeTime) : $relativeTime;
|
||||
|
||||
// Test next run date
|
||||
$cron = CronExpression::factory($schedule);
|
||||
if (is_string($relativeTime)) {
|
||||
$relativeTime = new DateTime($relativeTime);
|
||||
} elseif (is_int($relativeTime)) {
|
||||
$relativeTime = date('Y-m-d H:i:s', $relativeTime);
|
||||
}
|
||||
|
||||
if (is_string($nextRun)) {
|
||||
$nextRunDate = new DateTime($nextRun);
|
||||
} elseif (is_int($nextRun)) {
|
||||
$nextRunDate = new DateTime();
|
||||
$nextRunDate->setTimestamp($nextRun);
|
||||
}
|
||||
$this->assertSame($isDue, $cron->isDue($relativeTime));
|
||||
$next = $cron->getNextRunDate($relativeTime, 0, true);
|
||||
|
||||
$this->assertEquals($nextRunDate, $next);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\CronExpression::isDue
|
||||
*/
|
||||
public function testIsDueHandlesDifferentDates()
|
||||
{
|
||||
$cron = CronExpression::factory('* * * * *');
|
||||
$this->assertTrue($cron->isDue());
|
||||
$this->assertTrue($cron->isDue('now'));
|
||||
$this->assertTrue($cron->isDue(new DateTime('now')));
|
||||
$this->assertTrue($cron->isDue(date('Y-m-d H:i')));
|
||||
$this->assertTrue($cron->isDue(new DateTimeImmutable('now')));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\CronExpression::isDue
|
||||
*/
|
||||
public function testIsDueHandlesDifferentDefaultTimezones()
|
||||
{
|
||||
$originalTimezone = date_default_timezone_get();
|
||||
$cron = CronExpression::factory('0 15 * * 3'); //Wednesday at 15:00
|
||||
$date = '2014-01-01 15:00'; //Wednesday
|
||||
|
||||
date_default_timezone_set('UTC');
|
||||
$this->assertTrue($cron->isDue(new DateTime($date), 'UTC'));
|
||||
$this->assertFalse($cron->isDue(new DateTime($date), 'Europe/Amsterdam'));
|
||||
$this->assertFalse($cron->isDue(new DateTime($date), 'Asia/Tokyo'));
|
||||
|
||||
date_default_timezone_set('Europe/Amsterdam');
|
||||
$this->assertFalse($cron->isDue(new DateTime($date), 'UTC'));
|
||||
$this->assertTrue($cron->isDue(new DateTime($date), 'Europe/Amsterdam'));
|
||||
$this->assertFalse($cron->isDue(new DateTime($date), 'Asia/Tokyo'));
|
||||
|
||||
date_default_timezone_set('Asia/Tokyo');
|
||||
$this->assertFalse($cron->isDue(new DateTime($date), 'UTC'));
|
||||
$this->assertFalse($cron->isDue(new DateTime($date), 'Europe/Amsterdam'));
|
||||
$this->assertTrue($cron->isDue(new DateTime($date), 'Asia/Tokyo'));
|
||||
|
||||
date_default_timezone_set($originalTimezone);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\CronExpression::isDue
|
||||
*/
|
||||
public function testIsDueHandlesDifferentSuppliedTimezones()
|
||||
{
|
||||
$cron = CronExpression::factory('0 15 * * 3'); //Wednesday at 15:00
|
||||
$date = '2014-01-01 15:00'; //Wednesday
|
||||
|
||||
$this->assertTrue($cron->isDue(new DateTime($date, new DateTimeZone('UTC')), 'UTC'));
|
||||
$this->assertFalse($cron->isDue(new DateTime($date, new DateTimeZone('UTC')), 'Europe/Amsterdam'));
|
||||
$this->assertFalse($cron->isDue(new DateTime($date, new DateTimeZone('UTC')), 'Asia/Tokyo'));
|
||||
|
||||
$this->assertFalse($cron->isDue(new DateTime($date, new DateTimeZone('Europe/Amsterdam')), 'UTC'));
|
||||
$this->assertTrue($cron->isDue(new DateTime($date, new DateTimeZone('Europe/Amsterdam')), 'Europe/Amsterdam'));
|
||||
$this->assertFalse($cron->isDue(new DateTime($date, new DateTimeZone('Europe/Amsterdam')), 'Asia/Tokyo'));
|
||||
|
||||
$this->assertFalse($cron->isDue(new DateTime($date, new DateTimeZone('Asia/Tokyo')), 'UTC'));
|
||||
$this->assertFalse($cron->isDue(new DateTime($date, new DateTimeZone('Asia/Tokyo')), 'Europe/Amsterdam'));
|
||||
$this->assertTrue($cron->isDue(new DateTime($date, new DateTimeZone('Asia/Tokyo')), 'Asia/Tokyo'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\CronExpression::isDue
|
||||
*/
|
||||
public function testIsDueHandlesDifferentTimezonesAsArgument()
|
||||
{
|
||||
$cron = CronExpression::factory('0 15 * * 3'); //Wednesday at 15:00
|
||||
$date = '2014-01-01 15:00'; //Wednesday
|
||||
$utc = new \DateTimeZone('UTC');
|
||||
$amsterdam = new \DateTimeZone('Europe/Amsterdam');
|
||||
$tokyo = new \DateTimeZone('Asia/Tokyo');
|
||||
$this->assertTrue($cron->isDue(new DateTime($date, $utc), 'UTC'));
|
||||
$this->assertFalse($cron->isDue(new DateTime($date, $amsterdam), 'UTC'));
|
||||
$this->assertFalse($cron->isDue(new DateTime($date, $tokyo), 'UTC'));
|
||||
$this->assertFalse($cron->isDue(new DateTime($date, $utc), 'Europe/Amsterdam'));
|
||||
$this->assertTrue($cron->isDue(new DateTime($date, $amsterdam), 'Europe/Amsterdam'));
|
||||
$this->assertFalse($cron->isDue(new DateTime($date, $tokyo), 'Europe/Amsterdam'));
|
||||
$this->assertFalse($cron->isDue(new DateTime($date, $utc), 'Asia/Tokyo'));
|
||||
$this->assertFalse($cron->isDue(new DateTime($date, $amsterdam), 'Asia/Tokyo'));
|
||||
$this->assertTrue($cron->isDue(new DateTime($date, $tokyo), 'Asia/Tokyo'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\CronExpression::isDue
|
||||
*/
|
||||
public function testRecognisesTimezonesAsPartOfDateTime()
|
||||
{
|
||||
$cron = CronExpression::factory("0 7 * * *");
|
||||
$tzCron = "America/New_York";
|
||||
$tzServer = new \DateTimeZone("Europe/London");
|
||||
|
||||
$dtCurrent = \DateTime::createFromFormat("!Y-m-d H:i:s", "2017-10-17 10:00:00", $tzServer);
|
||||
$dtPrev = $cron->getPreviousRunDate($dtCurrent, 0, true, $tzCron);
|
||||
$this->assertEquals('1508151600 : 2017-10-16T07:00:00-04:00 : America/New_York', $dtPrev->format("U \: c \: e"));
|
||||
|
||||
$dtCurrent = \DateTimeImmutable::createFromFormat("!Y-m-d H:i:s", "2017-10-17 10:00:00", $tzServer);
|
||||
$dtPrev = $cron->getPreviousRunDate($dtCurrent, 0, true, $tzCron);
|
||||
$this->assertEquals('1508151600 : 2017-10-16T07:00:00-04:00 : America/New_York', $dtPrev->format("U \: c \: e"));
|
||||
|
||||
$dtCurrent = \DateTimeImmutable::createFromFormat("!Y-m-d H:i:s", "2017-10-17 10:00:00", $tzServer);
|
||||
$dtPrev = $cron->getPreviousRunDate($dtCurrent->format("c"), 0, true, $tzCron);
|
||||
$this->assertEquals('1508151600 : 2017-10-16T07:00:00-04:00 : America/New_York', $dtPrev->format("U \: c \: e"));
|
||||
|
||||
$dtCurrent = \DateTimeImmutable::createFromFormat("!Y-m-d H:i:s", "2017-10-17 10:00:00", $tzServer);
|
||||
$dtPrev = $cron->getPreviousRunDate($dtCurrent->format("\@U"), 0, true, $tzCron);
|
||||
$this->assertEquals('1508151600 : 2017-10-16T07:00:00-04:00 : America/New_York', $dtPrev->format("U \: c \: e"));
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @covers \Cron\CronExpression::getPreviousRunDate
|
||||
*/
|
||||
public function testCanGetPreviousRunDates()
|
||||
{
|
||||
$cron = CronExpression::factory('* * * * *');
|
||||
$next = $cron->getNextRunDate('now');
|
||||
$two = $cron->getNextRunDate('now', 1);
|
||||
$this->assertEquals($next, $cron->getPreviousRunDate($two));
|
||||
|
||||
$cron = CronExpression::factory('* */2 * * *');
|
||||
$next = $cron->getNextRunDate('now');
|
||||
$two = $cron->getNextRunDate('now', 1);
|
||||
$this->assertEquals($next, $cron->getPreviousRunDate($two));
|
||||
|
||||
$cron = CronExpression::factory('* * * */2 *');
|
||||
$next = $cron->getNextRunDate('now');
|
||||
$two = $cron->getNextRunDate('now', 1);
|
||||
$this->assertEquals($next, $cron->getPreviousRunDate($two));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\CronExpression::getMultipleRunDates
|
||||
*/
|
||||
public function testProvidesMultipleRunDates()
|
||||
{
|
||||
$cron = CronExpression::factory('*/2 * * * *');
|
||||
$this->assertEquals(array(
|
||||
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')
|
||||
), $cron->getMultipleRunDates(4, '2008-11-09 00:00:00', false, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\CronExpression::getMultipleRunDates
|
||||
* @covers \Cron\CronExpression::setMaxIterationCount
|
||||
*/
|
||||
public function testProvidesMultipleRunDatesForTheFarFuture() {
|
||||
// Fails with the default 1000 iteration limit
|
||||
$cron = CronExpression::factory('0 0 12 1 *');
|
||||
$cron->setMaxIterationCount(2000);
|
||||
$this->assertEquals(array(
|
||||
new DateTime('2016-01-12 00:00:00'),
|
||||
new DateTime('2017-01-12 00:00:00'),
|
||||
new DateTime('2018-01-12 00:00:00'),
|
||||
new DateTime('2019-01-12 00:00:00'),
|
||||
new DateTime('2020-01-12 00:00:00'),
|
||||
new DateTime('2021-01-12 00:00:00'),
|
||||
new DateTime('2022-01-12 00:00:00'),
|
||||
new DateTime('2023-01-12 00:00:00'),
|
||||
new DateTime('2024-01-12 00:00:00'),
|
||||
), $cron->getMultipleRunDates(9, '2015-04-28 00:00:00', false, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\CronExpression
|
||||
*/
|
||||
public function testCanIterateOverNextRuns()
|
||||
{
|
||||
$cron = CronExpression::factory('@weekly');
|
||||
$nextRun = $cron->getNextRunDate("2008-11-09 08:00:00");
|
||||
$this->assertEquals($nextRun, new DateTime("2008-11-16 00:00:00"));
|
||||
|
||||
// true is cast to 1
|
||||
$nextRun = $cron->getNextRunDate("2008-11-09 00:00:00", true, true);
|
||||
$this->assertEquals($nextRun, new DateTime("2008-11-16 00:00:00"));
|
||||
|
||||
// You can iterate over them
|
||||
$nextRun = $cron->getNextRunDate($cron->getNextRunDate("2008-11-09 00:00:00", 1, true), 1, true);
|
||||
$this->assertEquals($nextRun, new DateTime("2008-11-23 00:00:00"));
|
||||
|
||||
// You can skip more than one
|
||||
$nextRun = $cron->getNextRunDate("2008-11-09 00:00:00", 2, true);
|
||||
$this->assertEquals($nextRun, new DateTime("2008-11-23 00:00:00"));
|
||||
$nextRun = $cron->getNextRunDate("2008-11-09 00:00:00", 3, true);
|
||||
$this->assertEquals($nextRun, new DateTime("2008-11-30 00:00:00"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\CronExpression::getRunDate
|
||||
*/
|
||||
public function testGetRunDateHandlesDifferentDates()
|
||||
{
|
||||
$cron = CronExpression::factory('@weekly');
|
||||
$date = new DateTime("2019-03-10 00:00:00");
|
||||
$this->assertEquals($date, $cron->getNextRunDate("2019-03-03 08:00:00"));
|
||||
$this->assertEquals($date, $cron->getNextRunDate(new DateTime("2019-03-03 08:00:00")));
|
||||
$this->assertEquals($date, $cron->getNextRunDate(new DateTimeImmutable("2019-03-03 08:00:00")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\CronExpression::getRunDate
|
||||
*/
|
||||
public function testSkipsCurrentDateByDefault()
|
||||
{
|
||||
$cron = CronExpression::factory('* * * * *');
|
||||
$current = new DateTime('now');
|
||||
$next = $cron->getNextRunDate($current);
|
||||
$nextPrev = $cron->getPreviousRunDate($next);
|
||||
$this->assertSame($current->format('Y-m-d H:i:00'), $nextPrev->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\CronExpression::getRunDate
|
||||
* @ticket 7
|
||||
*/
|
||||
public function testStripsForSeconds()
|
||||
{
|
||||
$cron = CronExpression::factory('* * * * *');
|
||||
$current = new DateTime('2011-09-27 10:10:54');
|
||||
$this->assertSame('2011-09-27 10:11:00', $cron->getNextRunDate($current)->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\CronExpression::getRunDate
|
||||
*/
|
||||
public function testFixesPhpBugInDateIntervalMonth()
|
||||
{
|
||||
$cron = CronExpression::factory('0 0 27 JAN *');
|
||||
$this->assertSame('2011-01-27 00:00:00', $cron->getPreviousRunDate('2011-08-22 00:00:00')->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
public function testIssue29()
|
||||
{
|
||||
$cron = CronExpression::factory('@weekly');
|
||||
$this->assertSame(
|
||||
'2013-03-10 00:00:00',
|
||||
$cron->getPreviousRunDate('2013-03-17 00:00:00')->format('Y-m-d H:i:s')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/mtdowling/cron-expression/issues/20
|
||||
*/
|
||||
public function testIssue20() {
|
||||
$e = CronExpression::factory('* * * * MON#1');
|
||||
$this->assertTrue($e->isDue(new DateTime('2014-04-07 00:00:00')));
|
||||
$this->assertFalse($e->isDue(new DateTime('2014-04-14 00:00:00')));
|
||||
$this->assertFalse($e->isDue(new DateTime('2014-04-21 00:00:00')));
|
||||
|
||||
$e = CronExpression::factory('* * * * SAT#2');
|
||||
$this->assertFalse($e->isDue(new DateTime('2014-04-05 00:00:00')));
|
||||
$this->assertTrue($e->isDue(new DateTime('2014-04-12 00:00:00')));
|
||||
$this->assertFalse($e->isDue(new DateTime('2014-04-19 00:00:00')));
|
||||
|
||||
$e = CronExpression::factory('* * * * SUN#3');
|
||||
$this->assertFalse($e->isDue(new DateTime('2014-04-13 00:00:00')));
|
||||
$this->assertTrue($e->isDue(new DateTime('2014-04-20 00:00:00')));
|
||||
$this->assertFalse($e->isDue(new DateTime('2014-04-27 00:00:00')));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\CronExpression::getRunDate
|
||||
*/
|
||||
public function testKeepOriginalTime()
|
||||
{
|
||||
$now = new \DateTime;
|
||||
$strNow = $now->format(DateTime::ISO8601);
|
||||
$cron = CronExpression::factory('0 0 * * *');
|
||||
$cron->getPreviousRunDate($now);
|
||||
$this->assertSame($strNow, $now->format(DateTime::ISO8601));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\CronExpression::__construct
|
||||
* @covers \Cron\CronExpression::factory
|
||||
* @covers \Cron\CronExpression::isValidExpression
|
||||
* @covers \Cron\CronExpression::setExpression
|
||||
* @covers \Cron\CronExpression::setPart
|
||||
*/
|
||||
public function testValidationWorks()
|
||||
{
|
||||
// Invalid. Only four values
|
||||
$this->assertFalse(CronExpression::isValidExpression('* * * 1'));
|
||||
// Valid
|
||||
$this->assertTrue(CronExpression::isValidExpression('* * * * 1'));
|
||||
|
||||
// Issue #156, 13 is an invalid month
|
||||
$this->assertFalse(CronExpression::isValidExpression("* * * 13 * "));
|
||||
|
||||
// Issue #155, 90 is an invalid second
|
||||
$this->assertFalse(CronExpression::isValidExpression('90 * * * *'));
|
||||
|
||||
// Issue #154, 24 is an invalid hour
|
||||
$this->assertFalse(CronExpression::isValidExpression("0 24 1 12 0"));
|
||||
|
||||
// Issue #125, this is just all sorts of wrong
|
||||
$this->assertFalse(CronExpression::isValidExpression('990 14 * * mon-fri0345345'));
|
||||
|
||||
// see https://github.com/dragonmantank/cron-expression/issues/5
|
||||
$this->assertTrue(CronExpression::isValidExpression('2,17,35,47 5-7,11-13 * * *'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that 00 is considered a valid value for 0-based fields
|
||||
* cronie allows numbers with a leading 0, so adding support for this as well
|
||||
*
|
||||
* @see https://github.com/dragonmantank/cron-expression/issues/12
|
||||
*/
|
||||
public function testDoubleZeroIsValid()
|
||||
{
|
||||
$this->assertTrue(CronExpression::isValidExpression('00 * * * *'));
|
||||
$this->assertTrue(CronExpression::isValidExpression('01 * * * *'));
|
||||
$this->assertTrue(CronExpression::isValidExpression('* 00 * * *'));
|
||||
$this->assertTrue(CronExpression::isValidExpression('* 01 * * *'));
|
||||
|
||||
$e = CronExpression::factory('00 * * * *');
|
||||
$this->assertTrue($e->isDue(new DateTime('2014-04-07 00:00:00')));
|
||||
$e = CronExpression::factory('01 * * * *');
|
||||
$this->assertTrue($e->isDue(new DateTime('2014-04-07 00:01:00')));
|
||||
|
||||
$e = CronExpression::factory('* 00 * * *');
|
||||
$this->assertTrue($e->isDue(new DateTime('2014-04-07 00:00:00')));
|
||||
$e = CronExpression::factory('* 01 * * *');
|
||||
$this->assertTrue($e->isDue(new DateTime('2014-04-07 01:00:00')));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ranges with large steps should "wrap around" to the appropriate value
|
||||
* cronie allows for steps that are larger than the range of a field, with it wrapping around like a ring buffer. We
|
||||
* should do the same.
|
||||
*
|
||||
* @see https://github.com/dragonmantank/cron-expression/issues/6
|
||||
*/
|
||||
public function testRangesWrapAroundWithLargeSteps()
|
||||
{
|
||||
$f = new MonthField();
|
||||
$this->assertTrue($f->validate('*/123'));
|
||||
$this->assertSame([4], $f->getRangeForExpression('*/123', 12));
|
||||
|
||||
$e = CronExpression::factory('* * * */123 *');
|
||||
$this->assertTrue($e->isDue(new DateTime('2014-04-07 00:00:00')));
|
||||
|
||||
$nextRunDate = $e->getNextRunDate(new DateTime('2014-04-07 00:00:00'));
|
||||
$this->assertSame('2014-04-07 00:01:00', $nextRunDate->format('Y-m-d H:i:s'));
|
||||
|
||||
$nextRunDate = $e->getNextRunDate(new DateTime('2014-05-07 00:00:00'));
|
||||
$this->assertSame('2015-04-01 00:00:00', $nextRunDate->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
/**
|
||||
* When there is an issue with a field, we should report the human readable position
|
||||
*
|
||||
* @see https://github.com/dragonmantank/cron-expression/issues/29
|
||||
*/
|
||||
public function testFieldPositionIsHumanAdjusted()
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage("6 is not a valid position");
|
||||
$e = CronExpression::factory('0 * * * * ? *');
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Cron\Tests;
|
||||
|
||||
use Cron\DayOfMonthField;
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @author Michael Dowling <mtdowling@gmail.com>
|
||||
*/
|
||||
class DayOfMonthFieldTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @covers \Cron\DayOfMonthField::validate
|
||||
*/
|
||||
public function testValidatesField()
|
||||
{
|
||||
$f = new DayOfMonthField();
|
||||
$this->assertTrue($f->validate('1'));
|
||||
$this->assertTrue($f->validate('*'));
|
||||
$this->assertTrue($f->validate('L'));
|
||||
$this->assertTrue($f->validate('5W'));
|
||||
$this->assertTrue($f->validate('01'));
|
||||
$this->assertFalse($f->validate('5W,L'));
|
||||
$this->assertFalse($f->validate('1.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\DayOfMonthField::isSatisfiedBy
|
||||
*/
|
||||
public function testChecksIfSatisfied()
|
||||
{
|
||||
$f = new DayOfMonthField();
|
||||
$this->assertTrue($f->isSatisfiedBy(new DateTime(), '?'));
|
||||
$this->assertTrue($f->isSatisfiedBy(new DateTimeImmutable(), '?'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\DayOfMonthField::increment
|
||||
*/
|
||||
public function testIncrementsDate()
|
||||
{
|
||||
$d = new DateTime('2011-03-15 11:15:00');
|
||||
$f = new DayOfMonthField();
|
||||
$f->increment($d);
|
||||
$this->assertSame('2011-03-16 00:00:00', $d->format('Y-m-d H:i:s'));
|
||||
|
||||
$d = new DateTime('2011-03-15 11:15:00');
|
||||
$f->increment($d, true);
|
||||
$this->assertSame('2011-03-14 23:59:00', $d->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\DayOfMonthField::increment
|
||||
*/
|
||||
public function testIncrementsDateTimeImmutable()
|
||||
{
|
||||
$d = new DateTimeImmutable('2011-03-15 11:15:00');
|
||||
$f = new DayOfMonthField();
|
||||
$f->increment($d);
|
||||
$this->assertSame('2011-03-16 00:00:00', $d->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Day of the month cannot accept a 0 value, it must be between 1 and 31
|
||||
* See Github issue #120
|
||||
*
|
||||
* @since 2017-01-22
|
||||
*/
|
||||
public function testDoesNotAccept0Date()
|
||||
{
|
||||
$f = new DayOfMonthField();
|
||||
$this->assertFalse($f->validate(0));
|
||||
}
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Cron\Tests;
|
||||
|
||||
use Cron\DayOfWeekField;
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @author Michael Dowling <mtdowling@gmail.com>
|
||||
*/
|
||||
class DayOfWeekFieldTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @covers \Cron\DayOfWeekField::validate
|
||||
*/
|
||||
public function testValidatesField()
|
||||
{
|
||||
$f = new DayOfWeekField();
|
||||
$this->assertTrue($f->validate('1'));
|
||||
$this->assertTrue($f->validate('01'));
|
||||
$this->assertTrue($f->validate('00'));
|
||||
$this->assertTrue($f->validate('*'));
|
||||
$this->assertFalse($f->validate('*/3,1,1-12'));
|
||||
$this->assertTrue($f->validate('SUN-2'));
|
||||
$this->assertFalse($f->validate('1.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\DayOfWeekField::isSatisfiedBy
|
||||
*/
|
||||
public function testChecksIfSatisfied()
|
||||
{
|
||||
$f = new DayOfWeekField();
|
||||
$this->assertTrue($f->isSatisfiedBy(new DateTime(), '?'));
|
||||
$this->assertTrue($f->isSatisfiedBy(new DateTimeImmutable(), '?'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\DayOfWeekField::increment
|
||||
*/
|
||||
public function testIncrementsDate()
|
||||
{
|
||||
$d = new DateTime('2011-03-15 11:15:00');
|
||||
$f = new DayOfWeekField();
|
||||
$f->increment($d);
|
||||
$this->assertSame('2011-03-16 00:00:00', $d->format('Y-m-d H:i:s'));
|
||||
|
||||
$d = new DateTime('2011-03-15 11:15:00');
|
||||
$f->increment($d, true);
|
||||
$this->assertSame('2011-03-14 23:59:00', $d->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\DayOfWeekField::increment
|
||||
*/
|
||||
public function testIncrementsDateTimeImmutable()
|
||||
{
|
||||
$d = new DateTimeImmutable('2011-03-15 11:15:00');
|
||||
$f = new DayOfWeekField();
|
||||
$f->increment($d);
|
||||
$this->assertSame('2011-03-16 00:00:00', $d->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\DayOfWeekField::isSatisfiedBy
|
||||
*/
|
||||
public function testValidatesHashValueWeekday()
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('Weekday must be a value between 0 and 7. 12 given');
|
||||
|
||||
$f = new DayOfWeekField();
|
||||
$this->assertTrue($f->isSatisfiedBy(new DateTime(), '12#1'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\DayOfWeekField::isSatisfiedBy
|
||||
*/
|
||||
public function testValidatesHashValueNth()
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('There are never more than 5 or less than 1 of a given weekday in a month');
|
||||
|
||||
$f = new DayOfWeekField();
|
||||
$this->assertTrue($f->isSatisfiedBy(new DateTime(), '3#6'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\DayOfWeekField::validate
|
||||
*/
|
||||
public function testValidateWeekendHash()
|
||||
{
|
||||
$f = new DayOfWeekField();
|
||||
$this->assertTrue($f->validate('MON#1'));
|
||||
$this->assertTrue($f->validate('TUE#2'));
|
||||
$this->assertTrue($f->validate('WED#3'));
|
||||
$this->assertTrue($f->validate('THU#4'));
|
||||
$this->assertTrue($f->validate('FRI#5'));
|
||||
$this->assertTrue($f->validate('SAT#1'));
|
||||
$this->assertTrue($f->validate('SUN#3'));
|
||||
$this->assertTrue($f->validate('MON#1,MON#3'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\DayOfWeekField::isSatisfiedBy
|
||||
*/
|
||||
public function testHandlesZeroAndSevenDayOfTheWeekValues()
|
||||
{
|
||||
$f = new DayOfWeekField();
|
||||
$this->assertTrue($f->isSatisfiedBy(new DateTime('2011-09-04 00:00:00'), '0-2'));
|
||||
$this->assertTrue($f->isSatisfiedBy(new DateTime('2011-09-04 00:00:00'), '6-0'));
|
||||
|
||||
$this->assertTrue($f->isSatisfiedBy(new DateTime('2014-04-20 00:00:00'), 'SUN'));
|
||||
$this->assertTrue($f->isSatisfiedBy(new DateTime('2014-04-20 00:00:00'), 'SUN#3'));
|
||||
$this->assertTrue($f->isSatisfiedBy(new DateTime('2014-04-20 00:00:00'), '0#3'));
|
||||
$this->assertTrue($f->isSatisfiedBy(new DateTime('2014-04-20 00:00:00'), '7#3'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\DayOfWeekField::isSatisfiedBy
|
||||
*/
|
||||
public function testHandlesLastWeekdayOfTheMonth()
|
||||
{
|
||||
$f = new DayOfWeekField();
|
||||
$this->assertTrue($f->isSatisfiedBy(new DateTime('2018-12-28 00:00:00'), 'FRIL'));
|
||||
$this->assertTrue($f->isSatisfiedBy(new DateTime('2018-12-28 00:00:00'), '5L'));
|
||||
$this->assertFalse($f->isSatisfiedBy(new DateTime('2018-12-21 00:00:00'), 'FRIL'));
|
||||
$this->assertFalse($f->isSatisfiedBy(new DateTime('2018-12-21 00:00:00'), '5L'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/mtdowling/cron-expression/issues/47
|
||||
*/
|
||||
public function testIssue47() {
|
||||
$f = new DayOfWeekField();
|
||||
$this->assertFalse($f->validate('mon,'));
|
||||
$this->assertFalse($f->validate('mon-'));
|
||||
$this->assertFalse($f->validate('*/2,'));
|
||||
$this->assertFalse($f->validate('-mon'));
|
||||
$this->assertFalse($f->validate(',1'));
|
||||
$this->assertFalse($f->validate('*-'));
|
||||
$this->assertFalse($f->validate(',-'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/laravel/framework/commit/07d160ac3cc9764d5b429734ffce4fa311385403
|
||||
*/
|
||||
public function testLiteralsExpandProperly()
|
||||
{
|
||||
$f = new DayOfWeekField();
|
||||
$this->assertTrue($f->validate('MON-FRI'));
|
||||
$this->assertSame([1,2,3,4,5], $f->getRangeForExpression('MON-FRI', 7));
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Cron\Tests;
|
||||
|
||||
use Cron\FieldFactory;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @author Michael Dowling <mtdowling@gmail.com>
|
||||
*/
|
||||
class FieldFactoryTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @covers \Cron\FieldFactory::getField
|
||||
*/
|
||||
public function testRetrievesFieldInstances()
|
||||
{
|
||||
$mappings = array(
|
||||
0 => 'Cron\MinutesField',
|
||||
1 => 'Cron\HoursField',
|
||||
2 => 'Cron\DayOfMonthField',
|
||||
3 => 'Cron\MonthField',
|
||||
4 => 'Cron\DayOfWeekField',
|
||||
);
|
||||
|
||||
$f = new FieldFactory();
|
||||
|
||||
foreach ($mappings as $position => $class) {
|
||||
$this->assertSame($class, get_class($f->getField($position)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\FieldFactory::getField
|
||||
*/
|
||||
public function testValidatesFieldPosition()
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
$f = new FieldFactory();
|
||||
$f->getField(-1);
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Cron\Tests;
|
||||
|
||||
use Cron\HoursField;
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @author Michael Dowling <mtdowling@gmail.com>
|
||||
*/
|
||||
class HoursFieldTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @covers \Cron\HoursField::validate
|
||||
*/
|
||||
public function testValidatesField()
|
||||
{
|
||||
$f = new HoursField();
|
||||
$this->assertTrue($f->validate('1'));
|
||||
$this->assertTrue($f->validate('00'));
|
||||
$this->assertTrue($f->validate('01'));
|
||||
$this->assertTrue($f->validate('*'));
|
||||
$this->assertFalse($f->validate('*/3,1,1-12'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\HoursField::isSatisfiedBy
|
||||
*/
|
||||
public function testChecksIfSatisfied()
|
||||
{
|
||||
$f = new HoursField();
|
||||
$this->assertTrue($f->isSatisfiedBy(new DateTime(), '?'));
|
||||
$this->assertTrue($f->isSatisfiedBy(new DateTimeImmutable(), '?'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\HoursField::increment
|
||||
*/
|
||||
public function testIncrementsDate()
|
||||
{
|
||||
$d = new DateTime('2011-03-15 11:15:00');
|
||||
$f = new HoursField();
|
||||
$f->increment($d);
|
||||
$this->assertSame('2011-03-15 12:00:00', $d->format('Y-m-d H:i:s'));
|
||||
|
||||
$d->setTime(11, 15, 0);
|
||||
$f->increment($d, true);
|
||||
$this->assertSame('2011-03-15 10:59:00', $d->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\HoursField::increment
|
||||
*/
|
||||
public function testIncrementsDateTimeImmutable()
|
||||
{
|
||||
$d = new DateTimeImmutable('2011-03-15 11:15:00');
|
||||
$f = new HoursField();
|
||||
$f->increment($d);
|
||||
$this->assertSame('2011-03-15 12:00:00', $d->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\HoursField::increment
|
||||
*/
|
||||
public function testIncrementsDateWithThirtyMinuteOffsetTimezone()
|
||||
{
|
||||
$tz = date_default_timezone_get();
|
||||
date_default_timezone_set('America/St_Johns');
|
||||
$d = new DateTime('2011-03-15 11:15:00');
|
||||
$f = new HoursField();
|
||||
$f->increment($d);
|
||||
$this->assertSame('2011-03-15 12:00:00', $d->format('Y-m-d H:i:s'));
|
||||
|
||||
$d->setTime(11, 15, 0);
|
||||
$f->increment($d, true);
|
||||
$this->assertSame('2011-03-15 10:59:00', $d->format('Y-m-d H:i:s'));
|
||||
date_default_timezone_set($tz);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\HoursField::increment
|
||||
*/
|
||||
public function testIncrementDateWithFifteenMinuteOffsetTimezone()
|
||||
{
|
||||
$tz = date_default_timezone_get();
|
||||
date_default_timezone_set('Asia/Kathmandu');
|
||||
$d = new DateTime('2011-03-15 11:15:00');
|
||||
$f = new HoursField();
|
||||
$f->increment($d);
|
||||
$this->assertSame('2011-03-15 12:00:00', $d->format('Y-m-d H:i:s'));
|
||||
|
||||
$d->setTime(11, 15, 0);
|
||||
$f->increment($d, true);
|
||||
$this->assertSame('2011-03-15 10:59:00', $d->format('Y-m-d H:i:s'));
|
||||
date_default_timezone_set($tz);
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Cron\Tests;
|
||||
|
||||
use Cron\MinutesField;
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @author Michael Dowling <mtdowling@gmail.com>
|
||||
*/
|
||||
class MinutesFieldTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @covers \Cron\MinutesField::validate
|
||||
*/
|
||||
public function testValidatesField()
|
||||
{
|
||||
$f = new MinutesField();
|
||||
$this->assertTrue($f->validate('1'));
|
||||
$this->assertTrue($f->validate('*'));
|
||||
$this->assertFalse($f->validate('*/3,1,1-12'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\MinutesField::isSatisfiedBy
|
||||
*/
|
||||
public function testChecksIfSatisfied()
|
||||
{
|
||||
$f = new MinutesField();
|
||||
$this->assertTrue($f->isSatisfiedBy(new DateTime(), '?'));
|
||||
$this->assertTrue($f->isSatisfiedBy(new DateTimeImmutable(), '?'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\MinutesField::increment
|
||||
*/
|
||||
public function testIncrementsDate()
|
||||
{
|
||||
$d = new DateTime('2011-03-15 11:15:00');
|
||||
$f = new MinutesField();
|
||||
$f->increment($d);
|
||||
$this->assertSame('2011-03-15 11:16:00', $d->format('Y-m-d H:i:s'));
|
||||
$f->increment($d, true);
|
||||
$this->assertSame('2011-03-15 11:15:00', $d->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\MinutesField::increment
|
||||
*/
|
||||
public function testIncrementsDateTimeImmutable()
|
||||
{
|
||||
$d = new DateTimeImmutable('2011-03-15 11:15:00');
|
||||
$f = new MinutesField();
|
||||
$f->increment($d);
|
||||
$this->assertSame('2011-03-15 11:16:00', $d->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Various bad syntaxes that are reported to work, but shouldn't.
|
||||
*
|
||||
* @author Chris Tankersley
|
||||
* @since 2017-08-18
|
||||
*/
|
||||
public function testBadSyntaxesShouldNotValidate()
|
||||
{
|
||||
$f = new MinutesField();
|
||||
$this->assertFalse($f->validate('*-1'));
|
||||
$this->assertFalse($f->validate('1-2-3'));
|
||||
$this->assertFalse($f->validate('-1'));
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Cron\Tests;
|
||||
|
||||
use Cron\MonthField;
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @author Michael Dowling <mtdowling@gmail.com>
|
||||
*/
|
||||
class MonthFieldTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @covers \Cron\MonthField::validate
|
||||
*/
|
||||
public function testValidatesField()
|
||||
{
|
||||
$f = new MonthField();
|
||||
$this->assertTrue($f->validate('12'));
|
||||
$this->assertTrue($f->validate('*'));
|
||||
$this->assertFalse($f->validate('*/10,2,1-12'));
|
||||
$this->assertFalse($f->validate('1.fix-regexp'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\MonthField::isSatisfiedBy
|
||||
*/
|
||||
public function testChecksIfSatisfied()
|
||||
{
|
||||
$f = new MonthField();
|
||||
$this->assertTrue($f->isSatisfiedBy(new DateTime(), '?'));
|
||||
$this->assertTrue($f->isSatisfiedBy(new DateTimeImmutable(), '?'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\MonthField::increment
|
||||
*/
|
||||
public function testIncrementsDate()
|
||||
{
|
||||
$d = new DateTime('2011-03-15 11:15:00');
|
||||
$f = new MonthField();
|
||||
$f->increment($d);
|
||||
$this->assertSame('2011-04-01 00:00:00', $d->format('Y-m-d H:i:s'));
|
||||
|
||||
$d = new DateTime('2011-03-15 11:15:00');
|
||||
$f->increment($d, true);
|
||||
$this->assertSame('2011-02-28 23:59:00', $d->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\MonthField::increment
|
||||
*/
|
||||
public function testIncrementsDateTimeImmutable()
|
||||
{
|
||||
$d = new DateTimeImmutable('2011-03-15 11:15:00');
|
||||
$f = new MonthField();
|
||||
$f->increment($d);
|
||||
$this->assertSame('2011-04-01 00:00:00', $d->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\MonthField::increment
|
||||
*/
|
||||
public function testIncrementsDateWithThirtyMinuteTimezone()
|
||||
{
|
||||
$tz = date_default_timezone_get();
|
||||
date_default_timezone_set('America/St_Johns');
|
||||
$d = new DateTime('2011-03-31 11:59:59');
|
||||
$f = new MonthField();
|
||||
$f->increment($d);
|
||||
$this->assertSame('2011-04-01 00:00:00', $d->format('Y-m-d H:i:s'));
|
||||
|
||||
$d = new DateTime('2011-03-15 11:15:00');
|
||||
$f->increment($d, true);
|
||||
$this->assertSame('2011-02-28 23:59:00', $d->format('Y-m-d H:i:s'));
|
||||
date_default_timezone_set($tz);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @covers \Cron\MonthField::increment
|
||||
*/
|
||||
public function testIncrementsYearAsNeeded()
|
||||
{
|
||||
$f = new MonthField();
|
||||
$d = new DateTime('2011-12-15 00:00:00');
|
||||
$f->increment($d);
|
||||
$this->assertSame('2012-01-01 00:00:00', $d->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Cron\MonthField::increment
|
||||
*/
|
||||
public function testDecrementsYearAsNeeded()
|
||||
{
|
||||
$f = new MonthField();
|
||||
$d = new DateTime('2011-01-15 00:00:00');
|
||||
$f->increment($d, true);
|
||||
$this->assertSame('2010-12-31 23:59:00', $d->format('Y-m-d H:i:s'));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user