Validator::isClosestWeekDay()   A
last analyzed

Complexity

Conditions 6
Paths 4

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 9
c 1
b 0
f 1
dl 0
loc 17
rs 9.2222
cc 6
nc 4
nop 3
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 segment validator.
18
 *
19
 * This class checks if a cron segment is valid.
20
 *
21
 * @author Jitendra Adhikari <[email protected]>
22
 */
23
class Validator
24
{
25
    /**
26
     * Check if the value is in range of given offset.
27
     *
28
     * @param int    $value
29
     * @param string $offset
30
     *
31
     * @return bool
32
     */
33
    public function inRange(int $value, string $offset): bool
34
    {
35
        $parts = \explode('-', $offset);
36
37
        return $parts[0] <= $value && $value <= $parts[1];
38
    }
39
40
    /**
41
     * Check if the value is in step of given offset.
42
     *
43
     * @param int    $value
44
     * @param string $offset
45
     *
46
     * @return bool
47
     */
48
    public function inStep(int $value, string $offset): bool
49
    {
50
        $parts = \explode('/', $offset, 2);
51
52
        if (empty($parts[1])) {
53
            return false;
54
        }
55
56
        if (\strpos($offset, '*/') !== false || \strpos($offset, '0/') !== false) {
57
            return $value % $parts[1] === 0;
58
        }
59
60
        $subparts = \explode('-', $parts[0], 2) + [1 => $value];
61
62
        return $this->inStepRange((int) $value, (int) $subparts[0], (int) $subparts[1], (int) $parts[1]);
63
    }
64
65
    /**
66
     * Check if the value falls between start and end when advanved by step.
67
     *
68
     * @param int $value
69
     * @param int $start
70
     * @param int $end
71
     * @param int $step
72
     *
73
     * @return bool
74
     */
75
    public function inStepRange(int $value, int $start, int $end, int $step): bool
76
    {
77
        if (($start + $step) > $end) {
78
            return false;
79
        }
80
81
        if ($start <= $value && $value <= $end) {
82
            return \in_array($value, \range($start, $end, $step));
83
        }
84
85
        return false;
86
    }
87
88
    /**
89
     * Check if month modifiers [L C W #] are satisfied.
90
     *
91
     * @internal
92
     *
93
     * @param string        $value
94
     * @param ReferenceTime $time
95
     *
96
     * @return bool
97
     */
98
    public function isValidMonthDay(string $value, ReferenceTime $reference): bool
99
    {
100
        if ($value == 'L') {
101
            return $reference->monthDay() == $reference->numDays();
102
        }
103
104
        if ($pos = \strpos($value, 'W')) {
105
            $value = \substr($value, 0, $pos);
106
            $month = $this->zeroPad($reference->month());
107
108
            return $this->isClosestWeekDay((int) $value, $month, $reference);
109
        }
110
111
        $this->unexpectedValue(2, $value);
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return boolean. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
112
        // @codeCoverageIgnoreStart
113
    }
114
115
    // @codeCoverageIgnoreEnd
116
117
    protected function isClosestWeekDay(int $value, string $month, ReferenceTime $reference): bool
118
    {
119
        foreach ([0, -1, 1, -2, 2] as $i) {
120
            $incr = $value + $i;
121
            if ($incr < 1 || $incr > $reference->numDays()) {
122
                continue;
123
            }
124
125
            $incr  = $this->zeroPad($incr);
126
            $parts = \explode(' ', \date('N m j', \strtotime("{$reference->year()}-$month-$incr")));
127
            if ($parts[0] < 6 && $parts[1] == $month) {
128
                return $reference->monthDay() == $parts[2];
129
            }
130
        }
131
132
        // @codeCoverageIgnoreStart
133
        return false;
134
        // @codeCoverageIgnoreEnd
135
    }
136
137
    /**
138
     * Check if week modifiers [L C W #] are satisfied.
139
     *
140
     * @internal
141
     *
142
     * @param string        $value
143
     * @param ReferenceTime $time
144
     *
145
     * @return bool
146
     */
147
    public function isValidWeekDay(string $value, ReferenceTime $reference): bool
148
    {
149
        $month = $this->zeroPad($reference->month());
150
151
        if (\strpos($value, 'L')) {
152
            return $this->isLastWeekDay($value, $month, $reference);
153
        }
154
155
        if (!\strpos($value, '#')) {
156
            $this->unexpectedValue(4, $value);
157
        }
158
159
        list($day, $nth) = \explode('#', \str_replace('0#', '7#', $value));
160
161
        if (!$this->isNthWeekDay((int) $day, (int) $nth) || $reference->weekDay1() != $day) {
162
            return false;
163
        }
164
165
        return \intval($reference->day() / 7) == $nth - 1;
166
    }
167
168
    /**
169
     * @param int    $pos
170
     * @param string $value
171
     *
172
     * @throws \UnexpectedValueException
173
     */
174
    public function unexpectedValue(int $pos, string $value)
175
    {
176
        throw new \UnexpectedValueException(
177
            \sprintf('Invalid offset value at segment #%d: %s', $pos, $value)
178
        );
179
    }
180
181
    protected function isLastWeekDay(string $value, string $month, ReferenceTime $reference): bool
182
    {
183
        $decr  = $reference->numDays();
184
        $value = \explode('L', \str_replace('7L', '0L', $value));
185
186
        for ($i = 0; $i < 7; $i++) {
187
            $decr -= $i;
188
            if (\date('w', \strtotime("{$reference->year()}-$month-$decr")) == $value[0]) {
189
                return $reference->monthDay() == $decr;
190
            }
191
        }
192
193
        return false;
194
    }
195
196
    protected function isNthWeekDay(int $day, int $nth): bool
197
    {
198
        return !($day < 0 || $day > 7 || $nth < 1 || $nth > 5);
199
    }
200
201
    protected function zeroPad($value): string
202
    {
203
        return \str_pad((string) $value, 2, '0', \STR_PAD_LEFT);
204
    }
205
}
206