Skip to content

Commit c724203

Browse files
committed
feat: everything
1 parent dbbd294 commit c724203

File tree

7 files changed

+441
-0
lines changed

7 files changed

+441
-0
lines changed

.editorconfig

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
; http://editorconfig.org
2+
;
3+
; Sublime: https://github.com/sindresorhus/editorconfig-sublime
4+
; Phpstorm: https://plugins.jetbrains.com/plugin/7294-editorconfig
5+
6+
root = true
7+
8+
[*]
9+
indent_style = space
10+
indent_size = 2
11+
end_of_line = lf
12+
charset = utf-8
13+
trim_trailing_whitespace = true
14+
insert_final_newline = true
15+
16+
[{*.md,*.php,composer.json,composer.lock}]
17+
indent_size = 4

.travis.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
language: php
2+
3+
php:
4+
- 5.4
5+
- 5.5
6+
- 5.6
7+
- 7.0
8+
- 7.1
9+
10+
install:
11+
- composer install --prefer-dist
12+
13+
script:
14+
- vendor/bin/phpunit --coverage-text

composer.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "adhocore/cron-expr",
3+
"description": "Ultra lightweight Cron Expression parser for PHP",
4+
"type": "library",
5+
"keywords": [
6+
"cron", "cron-expression", "cron-parser", "cron-expr"
7+
],
8+
"license": "MIT",
9+
"authors": [
10+
{
11+
"name": "Jitendra Adhikari",
12+
"email": "[email protected]"
13+
}
14+
],
15+
"autoload": {
16+
"psr-4": {
17+
"Ahc\\Cron\\": "src/"
18+
}
19+
},
20+
"autoload-dev": {
21+
"psr-4": {
22+
"Ahc\\Cron\\Test\\": "tests/"
23+
}
24+
},
25+
"require": {
26+
},
27+
"require-dev": {
28+
"phpunit/phpunit": "^5.7.0"
29+
}
30+
}

phpunit.xml.dist

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<phpunit backupGlobals="false"
4+
backupStaticAttributes="false"
5+
colors="true"
6+
convertErrorsToExceptions="true"
7+
convertNoticesToExceptions="true"
8+
convertWarningsToExceptions="true"
9+
processIsolation="false"
10+
stopOnFailure="false"
11+
syntaxCheck="false"
12+
bootstrap="vendor/autoload.php"
13+
>
14+
<testsuites>
15+
<testsuite name="Cron Expr Suite">
16+
<directory>./tests/</directory>
17+
</testsuite>
18+
</testsuites>
19+
<filter>
20+
<whitelist processUncoveredFilesFromWhitelist="true">
21+
<directory suffix=".php">./src</directory>
22+
</whitelist>
23+
</filter>
24+
</phpunit>

readme.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
## adhcore/cron-expr [![build status](https://travis-ci.org/adhocore/cron-expr.svg?branch=master)](https://travis-ci.org/adhocore/cron-expr)
2+
3+
- Lightweight Cron expression parser library for PHP.
4+
5+
## Installation
6+
```bash
7+
composer require adhocore/cron-expr
8+
```
9+
10+
## Usage
11+
```php
12+
use Ahc\Cron\Expression;
13+
14+
Expression::isDue('@always');
15+
Expression::isDue('@hourly', '2015-01-01 00:00:00');
16+
Expression::isDue('*/20 * * * *', new DateTime);
17+
Expression::isDue('5-34/4 * * * *', time());
18+
```
19+
20+
### Real Abbreviations
21+
You can use real abbreviations for month and week days. eg: `JAN`, `dec`, `fri`, `SUN`
22+
23+
24+
### Tags
25+
Following tags are available and they are converted to real cron expressions before parsing:
26+
27+
- *@yearly* or *@annually* - every year
28+
- *@monthly* - every month
29+
- *@daily* - every day
30+
- *@weekly* - every week
31+
- *@hourly* - every hour
32+
- *@5minuts* - every 5 minutes
33+
- *@10minuts* - every 10 minutes
34+
- *@15minuts* - every 15 minutes
35+
- *@30minuts* - every 30 minutes
36+
- *@always* - every minute
37+
38+
### Modifiers
39+
Following modifiers supported
40+
41+
- *Day of Month / 3rd segment:*
42+
- `L` stands for last day of month (eg: `L` could mean 29th for February in leap year)
43+
- `W` stands for closest week day (eg: `10W` is closest week days (MON-FRI) to 10th date)
44+
- *Day of Week / 5th segment:*
45+
- `L` stands for last weekday of month (eg: `2L` is last monday)
46+
- `#` stands for nth day of week in the month (eg: `1#2` is second sunday)

src/Expression.php

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
<?php
2+
3+
namespace Ahc\Cron;
4+
5+
/**
6+
* Cron Expression Parser.
7+
*
8+
* This class checks if a cron expression is due to run on given timestamp (or default now).
9+
* Acknowledgement: The initial idea came from {@link http://stackoverflow.com/a/5727346}.
10+
*
11+
* @author Jitendra Adhikari <[email protected]>
12+
*/
13+
class Expression
14+
{
15+
protected static $expressions = [
16+
'@yearly' => '0 0 1 1 *',
17+
'@annually' => '0 0 1 1 *',
18+
'@monthly' => '0 0 1 * *',
19+
'@weekly' => '0 0 * * 0',
20+
'@daily' => '0 0 * * *',
21+
'@hourly' => '0 * * * *',
22+
'@always' => '* * * * *',
23+
'@5minutes' => '*/5 * * * *',
24+
'@10minutes' => '*/10 * * * *',
25+
'@15minutes' => '*/15 * * * *',
26+
'@30minutes' => '0,30 * * * *',
27+
];
28+
29+
protected static $literals = [
30+
'sun' => 0,
31+
'mon' => 1,
32+
'tue' => 2,
33+
'wed' => 3,
34+
'thu' => 4,
35+
'fri' => 5,
36+
'sat' => 6,
37+
'jan' => 1,
38+
'feb' => 2,
39+
'mar' => 3,
40+
'apr' => 4,
41+
'may' => 5,
42+
'jun' => 6,
43+
'jul' => 7,
44+
'aug' => 8,
45+
'sep' => 9,
46+
'oct' => 10,
47+
'nov' => 11,
48+
'dec' => 12,
49+
];
50+
51+
/**
52+
* Parse cron expression to decide if it can be run on given time (or default now).
53+
*
54+
* @param string $expr The cron expression.
55+
* @param int $time The timestamp to validate the cron expr against. Defaults to now.
56+
*
57+
* @return bool
58+
*/
59+
public static function isDue($expr, $time = null)
60+
{
61+
if (isset(static::$expressions[$expr])) {
62+
$expr = static::$expressions[$expr];
63+
}
64+
65+
if ('' === trim($expr, '?* ')) {
66+
return true;
67+
}
68+
69+
if (empty($time)) {
70+
$time = time();
71+
} elseif (is_string($time)) {
72+
$time = strtotime($time);
73+
} elseif ($time instanceof \DateTime) {
74+
$time = $time->getTimestamp();
75+
}
76+
77+
$expr = explode(' ', str_ireplace(array_keys(static::$literals), array_values(static::$literals), $expr));
78+
if (count($expr) < 5 || count($expr) > 6) {
79+
throw new \UnexpectedValueException('Cron $expr should have 5 or 6 segments delimited by space');
80+
}
81+
82+
$time = array_map('intval', explode(' ', date('i G j n w Y t d m N', $time)));
83+
84+
foreach ($expr as $pos => $value) {
85+
if ($value === '*' || $value === '?') {
86+
continue;
87+
}
88+
89+
$isDue = true;
90+
$value = explode(',', trim($value));
91+
92+
foreach ($value as $offset) {
93+
if (strpos($offset, '*/') !== false || strpos($offset, '0/') !== false) {
94+
$parts = explode('/', $offset, 2);
95+
$isDue = $time[$pos] % $parts[1] === 0;
96+
} elseif (strpos($offset, '/') !== false) {
97+
$parts = explode('/', $offset, 2);
98+
$subparts = explode('-', $parts[0], 2) + [1 => $time[$pos]];
99+
$isDue = $subparts[0] <= $time[$pos] && $time[$pos] <= $subparts[1] && $parts[1]
100+
? in_array($time[$pos], range($subparts[0], $subparts[1], $parts[1]))
101+
: false;
102+
} elseif (strpos($offset, '-') !== false) {
103+
$parts = explode('-', $offset);
104+
$isDue = $parts[0] <= $time[$pos] && $time[$pos] <= $parts[1];
105+
} elseif (is_numeric($offset)) {
106+
$isDue = $time[$pos] == $offset;
107+
} elseif (strpbrk($offset, 'LCW#')) {
108+
$isDue = static::checkModifier($offset, $pos, $time);
109+
}
110+
111+
if ($isDue) {
112+
break;
113+
}
114+
}
115+
116+
if (!$isDue) {
117+
return false;
118+
}
119+
}
120+
121+
return true;
122+
}
123+
124+
/**
125+
* Check if modifiers [L C W #] are satisfied.
126+
*
127+
* @internal
128+
*
129+
* @param string $value
130+
* @param int $pos
131+
* @param int $time
132+
*
133+
* @return bool
134+
*/
135+
protected static function checkModifier($value, $pos, $time)
136+
{
137+
$month = $time[8] < 10 ? '0' . $time[8] : $time[8];
138+
139+
// Day of month.
140+
if ($pos === 2) {
141+
if ($value == 'L') {
142+
return $time[2] == $time[6];
143+
}
144+
145+
if ($pos = strpos($value, 'W')) {
146+
$value = substr($value, 0, $pos);
147+
148+
foreach ([0, -1, 1, -2, 2] as $i) {
149+
$incr = $value + $i;
150+
if ($incr > 0 && $incr <= $time[6]) {
151+
if ($incr < 10) {
152+
$incr = '0' . $incr;
153+
}
154+
155+
$parts = explode(' ', date('N m j', strtotime("$time[5]-$month-$incr")));
156+
if ($parts[0] < 6 && $parts[1] == $month) {
157+
return $time[2] == $parts[2];
158+
}
159+
}
160+
}
161+
}
162+
}
163+
164+
// Day of week.
165+
if ($pos === 4) {
166+
if ($pos = strpos($value, 'L')) {
167+
$value = explode('L', str_replace('7L', '0L', $value));
168+
$decr = $time[6];
169+
for ($i = 0; $i < 7; $i++) {
170+
$decr -= $i;
171+
if (date('w', strtotime("$time[5]-$month-$decr")) == $value[0]) {
172+
return $time[2] == $decr;
173+
}
174+
}
175+
176+
return false;
177+
}
178+
179+
if (strpos($value, '#')) {
180+
$value = explode('#', str_replace('0#', '7#', $value));
181+
182+
if ($value[0] < 0 || $value[0] > 7 || $value[1] < 1 || $value[1] > 5 || $time[9] != $value[0]) {
183+
return false;
184+
}
185+
186+
return intval($time[7] / 7) == $value[1] - 1;
187+
}
188+
}
189+
190+
throw new \UnexpectedValueException(sprintf('Invalid modifier value %s for segment #%d', $value, $pos));
191+
}
192+
}

0 commit comments

Comments
 (0)