Expression::getDues()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 1
c 1
b 0
f 1
dl 0
loc 3
rs 10
cc 1
nc 1
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the PHP-CRON-EXPR package.
7
 *
8
 * (c) Jitendra Adhikari <[email protected]>
9
 *     <https://github.com/adhocore>
10
 *
11
 * Licensed under MIT license.
12
 */
13
14
namespace Ahc\Cron;
15
16
/**
17
 * Cron Expression Parser.
18
 *
19
 * This class checks if a cron expression is due to run on given timestamp (or default now).
20
 * Acknowledgement: The initial idea came from {@link http://stackoverflow.com/a/5727346}.
21
 *
22
 * @author Jitendra Adhikari <[email protected]>
23
 */
24
class Expression
25
{
26
    /** @var Expression */
27
    protected static $instance;
28
29
    /** @var SegmentChecker */
30
    protected $checker;
31
32
    protected static $expressions = [
33
        '@yearly'    => '0 0 1 1 *',
34
        '@annually'  => '0 0 1 1 *',
35
        '@monthly'   => '0 0 1 * *',
36
        '@weekly'    => '0 0 * * 0',
37
        '@daily'     => '0 0 * * *',
38
        '@hourly'    => '0 * * * *',
39
        '@always'    => '* * * * *',
40
        '@5minutes'  => '*/5 * * * *',
41
        '@10minutes' => '*/10 * * * *',
42
        '@15minutes' => '*/15 * * * *',
43
        '@30minutes' => '0,30 * * * *',
44
    ];
45
46
    protected static $literals = [
47
        'sun' => 0,
48
        'mon' => 1,
49
        'tue' => 2,
50
        'wed' => 3,
51
        'thu' => 4,
52
        'fri' => 5,
53
        'sat' => 6,
54
        'jan' => 1,
55
        'feb' => 2,
56
        'mar' => 3,
57
        'apr' => 4,
58
        'may' => 5,
59
        'jun' => 6,
60
        'jul' => 7,
61
        'aug' => 8,
62
        'sep' => 9,
63
        'oct' => 10,
64
        'nov' => 11,
65
        'dec' => 12,
66
    ];
67
68
    public function __construct(SegmentChecker $checker = null)
69
    {
70
        $this->checker = $checker ?: new SegmentChecker;
71
72
        if (null === static::$instance) {
73
            static::$instance = $this;
74
        }
75
    }
76
77
    public static function instance(): self
78
    {
79
        if (null === static::$instance) {
80
            static::$instance = new static;
81
        }
82
83
        return static::$instance;
84
    }
85
86
    /**
87
     * Parse cron expression to decide if it can be run on given time (or default now).
88
     *
89
     * @param string $expr The cron expression.
90
     * @param mixed  $time The timestamp to validate the cron expr against. Defaults to now.
91
     *
92
     * @return bool
93
     */
94
    public static function isDue(string $expr, $time = null): bool
95
    {
96
        return static::instance()->isCronDue($expr, $time);
97
    }
98
99
    /**
100
     * Filter only the jobs that are due.
101
     *
102
     * @param array $jobs Jobs with cron exprs. [job1 => cron-expr1, job2 => cron-expr2, ...]
103
     * @param mixed $time The timestamp to validate the cron expr against. Defaults to now.
104
     *
105
     * @return array Due job names: [job1name, ...];
106
     */
107
    public static function getDues(array $jobs, $time = null): array
108
    {
109
        return static::instance()->filter($jobs, $time);
110
    }
111
112
    /**
113
     * Instance call.
114
     *
115
     * Parse cron expression to decide if it can be run on given time (or default now).
116
     *
117
     * @param string $expr The cron expression.
118
     * @param mixed  $time The timestamp to validate the cron expr against. Defaults to now.
119
     *
120
     * @return bool
121
     */
122
    public function isCronDue(string $expr, $time = null): bool
123
    {
124
        $this->checker->setReference(new ReferenceTime($time));
125
126
        foreach (\explode(' ', $this->normalizeExpr($expr)) as $pos => $segment) {
127
            if ($segment === '*' || $segment === '?') {
128
                continue;
129
            }
130
131
            if (!$this->checker->checkDue($segment, $pos)) {
132
                return false;
133
            }
134
        }
135
136
        return true;
137
    }
138
139
    /**
140
     * Filter only the jobs that are due.
141
     *
142
     * @param array $jobs Jobs with cron exprs. [job1 => cron-expr1, job2 => cron-expr2, ...]
143
     * @param mixed $time The timestamp to validate the cron expr against. Defaults to now.
144
     *
145
     * @return array Due job names: [job1name, ...];
146
     */
147
    public function filter(array $jobs, $time = null): array
148
    {
149
        $dues = $cache = [];
150
151
        foreach ($jobs as $name => $expr) {
152
            $expr = $this->normalizeExpr($expr);
153
154
            if (!isset($cache[$expr])) {
155
                $cache[$expr] = $this->isCronDue($expr, $time);
156
            }
157
158
            if ($cache[$expr]) {
159
                $dues[] = $name;
160
            }
161
        }
162
163
        return $dues;
164
    }
165
166
    protected function normalizeExpr(string $expr): string
167
    {
168
        $expr = \trim($expr);
169
170
        if (isset(static::$expressions[$expr])) {
171
            return static::$expressions[$expr];
172
        }
173
174
        $expr  = \preg_replace('~\s+~', ' ', $expr);
175
        $count = \substr_count($expr, ' ');
176
177
        if ($count < 4 || $count > 5) {
178
            throw new \UnexpectedValueException(
179
                'Cron $expr should have 5 or 6 segments delimited by space'
180
            );
181
        }
182
183
        return \str_ireplace(\array_keys(static::$literals), \array_values(static::$literals), $expr);
184
    }
185
}
186