diff --git a/README.md b/README.md index 7d2695c..83ea1da 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ use Orisai\CronExpressionExplainer\DefaultCronExpressionExplainer; $explainer = new DefaultCronExpressionExplainer(); $explainer->explain('* * * * *'); // At every minute. +$explainer->explain('*/30 * * * *'); // At every 30th minute. $explainer->explain('@daily'); // At 00:00. $explainer->explain('* * 1 * 1'); // At every minute on day-of-month 1 and on every Monday. $explainer->explain('0 22 * 12 *'); // At 22:00 in December. diff --git a/docs/README.md b/docs/README.md index e013541..f26181d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -26,6 +26,7 @@ use Orisai\CronExpressionExplainer\DefaultCronExpressionExplainer; $explainer = new DefaultCronExpressionExplainer(); $explainer->explain('* * * * *'); // At every minute. +$explainer->explain('*/30 * * * *'); // At every 30th minute. $explainer->explain('@daily'); // At 00:00. $explainer->explain('* * 1 * 1'); // At every minute on day-of-month 1 and on every Monday. $explainer->explain('0 22 * 12 *'); // At 22:00 in December. diff --git a/src/Interpreter/BasePartInterpreter.php b/src/Interpreter/BasePartInterpreter.php index ce30160..9cfae16 100644 --- a/src/Interpreter/BasePartInterpreter.php +++ b/src/Interpreter/BasePartInterpreter.php @@ -39,18 +39,26 @@ public function explainPart(Part $part, bool $renderName = true): string } if ($part instanceof StepPart) { + $range = $part->getRange(); $step = $part->getStep(); // Range with step === 1 is the same as range without step if ($step === 1) { - return $this->explainPart($part->getRange()); + return $this->explainPart($range); + } + + if ($range instanceof ValuePart && $range->getValue() === '*') { + return 'every ' + . $step + . $this->getNumberExtension($step) + . " {$this->getInStepName()}"; } return 'every ' . $step . $this->getNumberExtension($step) . " {$this->getInStepName()} " - . $this->explainPart($part->getRange(), false); + . $this->explainPart($range, false); } if ($part instanceof RangePart) { diff --git a/src/Part/PartParser.php b/src/Part/PartParser.php index c9f654e..6dae02e 100644 --- a/src/Part/PartParser.php +++ b/src/Part/PartParser.php @@ -31,13 +31,15 @@ private function parseUnlistedPart(string $part, BasePartInterpreter $interprete { if (str_contains($part, '/')) { $stepParts = explode('/', $part, 2); + $step = (int) $stepParts[1]; assert((string) $step === $stepParts[1]); - return new StepPart( - $this->parseRangePart($stepParts[0], $interpreter), - $step, - ); + $range = str_contains($stepParts[0], '-') + ? $this->parseRangePart($stepParts[0], $interpreter) + : $this->parseValuePart($stepParts[0], $interpreter); + + return new StepPart($range, $step); } if (str_contains($part, '-')) { diff --git a/src/Part/StepPart.php b/src/Part/StepPart.php index bfe7f4a..7abfea4 100644 --- a/src/Part/StepPart.php +++ b/src/Part/StepPart.php @@ -5,17 +5,24 @@ final class StepPart implements Part { - private RangePart $range; + /** @var RangePart|ValuePart */ + private Part $range; private int $step; - public function __construct(RangePart $range, int $step) + /** + * @param RangePart|ValuePart $range + */ + public function __construct(Part $range, int $step) { $this->range = $range; $this->step = $step; } - public function getRange(): RangePart + /** + * @return RangePart|ValuePart + */ + public function getRange(): Part { return $this->range; } diff --git a/tests/Unit/DefaultCronExpressionExplainerTest.php b/tests/Unit/DefaultCronExpressionExplainerTest.php index 2ec20d6..b3dd0eb 100644 --- a/tests/Unit/DefaultCronExpressionExplainerTest.php +++ b/tests/Unit/DefaultCronExpressionExplainerTest.php @@ -76,6 +76,11 @@ public function provideExplain(): Generator 'At every minute from 1 through 15 and from 31 through 45.', ]; + yield [ + '*/30 * * * *', + 'At every 30th minute.', + ]; + yield [ '1-30/1 * * * *', 'At every minute from 1 through 30.', @@ -173,6 +178,11 @@ public function provideExplain(): Generator 'At every minute past every hour from 1 through 5 and from 11 through 15.', ]; + yield [ + '* */2 * * *', + 'At every minute past every 2nd hour.', + ]; + yield [ '* 1-10/1 * * *', 'At every minute past every hour from 1 through 10.', @@ -297,6 +307,11 @@ public function provideExplain(): Generator 'At every minute on every day-of-month from 1 through 5 and from 11 through 15.', ]; + yield [ + '* * */2 * *', + 'At every minute on every 2nd day-of-month.', + ]; + yield [ '* * 1-10/1 * *', 'At every minute on every day-of-month from 1 through 10.', @@ -469,6 +484,11 @@ public function provideExplain(): Generator 'At every minute on every day-of-week from Sunday through Tuesday and from Thursday through Saturday.', ]; + yield [ + '* * * * */2', + 'At every minute on every 2nd day-of-week.', + ]; + yield [ '* * * * 2-4/1', 'At every minute on every day-of-week from Tuesday through Thursday.', @@ -671,6 +691,11 @@ public function provideExplain(): Generator 'At every minute in every month from January through March and from May through July.', ]; + yield [ + '* * * */2 *', + 'At every minute in every 2nd month.', + ]; + yield [ '* * * 2-4/1 *', 'At every minute in every month from February through April.',