@@ -58,28 +58,7 @@ class Expression
5858 */
5959 public static function isDue ($ expr , $ time = null )
6060 {
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 )));
61+ list ($ expr , $ time ) = static ::process ($ expr , $ time );
8362
8463 foreach ($ expr as $ pos => $ value ) {
8564 if ($ value === '* ' || $ value === '? ' ) {
@@ -90,22 +69,12 @@ public static function isDue($expr, $time = null)
9069 $ value = explode (', ' , trim ($ value ));
9170
9271 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 );
72+ $ isDue = static ::isOffsetDue ($ offset , $ pos , $ time );
73+
74+ if (null === $ isDue ) {
75+ throw new \UnexpectedValueException (
76+ sprintf ('Invalid offset value %s for segment #%d ' , $ offset , $ pos )
77+ );
10978 }
11079
11180 if ($ isDue ) {
@@ -121,72 +90,172 @@ public static function isDue($expr, $time = null)
12190 return true ;
12291 }
12392
93+ /**
94+ * Process and prepare input.
95+ *
96+ * @param string $expr
97+ * @param mixed $time
98+ *
99+ * @return array
100+ */
101+ protected static function process ($ expr , $ time )
102+ {
103+ if (isset (static ::$ expressions [$ expr ])) {
104+ $ expr = static ::$ expressions [$ expr ];
105+ }
106+
107+ $ expr = str_ireplace (array_keys (static ::$ literals ), array_values (static ::$ literals ), $ expr );
108+ $ expr = explode (' ' , $ expr );
109+
110+ if (count ($ expr ) < 5 || count ($ expr ) > 6 ) {
111+ throw new \UnexpectedValueException (
112+ 'Cron $expr should have 5 or 6 segments delimited by space '
113+ );
114+ }
115+
116+ if (empty ($ time )) {
117+ $ time = time ();
118+ } elseif (is_string ($ time )) {
119+ $ time = strtotime ($ time );
120+ } elseif ($ time instanceof \DateTime) {
121+ $ time = $ time ->getTimestamp ();
122+ }
123+
124+ $ time = array_map ('intval ' , explode (' ' , date ('i G j n w Y t d m N ' , $ time )));
125+
126+ return [$ expr , $ time ];
127+ }
128+
129+ /**
130+ * Check if a given offset at a position is due with respect to given time.
131+ *
132+ * @param string $offset
133+ * @param int $pos
134+ * @param array $time
135+ *
136+ * @return bool|null
137+ */
138+ protected static function isOffsetDue ($ offset , $ pos , $ time )
139+ {
140+ if (strpos ($ offset , '*/ ' ) !== false || strpos ($ offset , '0/ ' ) !== false ) {
141+ $ parts = explode ('/ ' , $ offset , 2 );
142+
143+ return $ time [$ pos ] % $ parts [1 ] === 0 ;
144+ }
145+
146+ if (strpos ($ offset , '/ ' ) !== false ) {
147+ $ parts = explode ('/ ' , $ offset , 2 );
148+ $ subparts = explode ('- ' , $ parts [0 ], 2 ) + [1 => $ time [$ pos ]];
149+
150+ return ($ subparts [0 ] <= $ time [$ pos ] && $ time [$ pos ] <= $ subparts [1 ] && $ parts [1 ])
151+ ? in_array ($ time [$ pos ], range ($ subparts [0 ], $ subparts [1 ], $ parts [1 ]))
152+ : false ;
153+ }
154+
155+ if (strpos ($ offset , '- ' ) !== false ) {
156+ $ parts = explode ('- ' , $ offset );
157+
158+ return $ parts [0 ] <= $ time [$ pos ] && $ time [$ pos ] <= $ parts [1 ];
159+ }
160+
161+ if (is_numeric ($ offset )) {
162+ return $ time [$ pos ] == $ offset ;
163+ }
164+
165+ if (($ pos === 2 || $ pos === 4 ) && strpbrk ($ offset , 'LCW# ' )) {
166+ return $ pos === 4
167+ ? static ::checkWeekDay ($ offset , $ time )
168+ : static ::checkMonthDay ($ offset , $ time );
169+ }
170+ }
171+
124172 /**
125173 * Check if modifiers [L C W #] are satisfied.
126174 *
127175 * @internal
128176 *
129177 * @param string $value
130- * @param int $pos
131178 * @param int $time
132179 *
133- * @return bool
180+ * @return bool|null
134181 */
135- protected static function checkModifier ($ value, $ pos , $ time )
182+ protected static function checkMonthDay ($ value , $ time )
136183 {
137184 $ month = $ time [8 ] < 10 ? '0 ' . $ time [8 ] : $ time [8 ];
138185
139- // Day of month.
140- if ($ pos === 2 ) {
141- if ($ value == 'L ' ) {
142- return $ time [2 ] == $ time [6 ];
143- }
186+ if ($ value == 'L ' ) {
187+ return $ time [2 ] == $ time [6 ];
188+ }
144189
145- if ($ pos = strpos ($ value , 'W ' )) {
146- $ value = substr ($ value , 0 , $ pos );
190+ if ($ pos = strpos ($ value , 'W ' )) {
191+ $ value = substr ($ value , 0 , $ pos );
147192
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- }
193+ foreach ([0 , -1 , 1 , -2 , 2 ] as $ i ) {
194+ $ incr = $ value + $ i ;
195+ if ($ incr > 0 && $ incr <= $ time [6 ]) {
196+ if ($ incr < 10 ) {
197+ $ incr = '0 ' . $ incr ;
198+ }
154199
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- }
200+ $ parts = explode (' ' , date ('N m j ' , strtotime ("$ time [5 ]- $ month- $ incr " )));
201+ if ($ parts [0 ] < 6 && $ parts [1 ] == $ month ) {
202+ return $ time [2 ] == $ parts [2 ];
159203 }
160204 }
161205 }
162206 }
207+ }
163208
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- }
209+ /**
210+ * Check if modifiers [L C W #] are satisfied.
211+ *
212+ * @internal
213+ *
214+ * @param string $value
215+ * @param int $time
216+ *
217+ * @return bool|null
218+ */
219+ protected static function checkWeekDay ($ value , $ time )
220+ {
221+ $ month = $ time [8 ] < 10 ? '0 ' . $ time [8 ] : $ time [8 ];
175222
176- return false ;
223+ if ($ pos = strpos ($ value , 'L ' )) {
224+ $ value = explode ('L ' , str_replace ('7L ' , '0L ' , $ value ));
225+ $ decr = $ time [6 ];
226+ for ($ i = 0 ; $ i < 7 ; $ i ++) {
227+ $ decr -= $ i ;
228+ if (date ('w ' , strtotime ("$ time [5 ]- $ month- $ decr " )) == $ value [0 ]) {
229+ return $ time [2 ] == $ decr ;
230+ }
177231 }
178232
179- if ( strpos ( $ value , ' # ' )) {
180- $ value = explode ( ' # ' , str_replace ( ' 0# ' , ' 7# ' , $ value ));
233+ return false ;
234+ }
181235
182- if ($ value [0 ] < 0 || $ value [0 ] > 7 || $ value [1 ] < 1 || $ value [1 ] > 5 || $ time [9 ] != $ value [0 ]) {
183- return false ;
184- }
236+ if (strpos ($ value , '# ' )) {
237+ $ value = explode ('# ' , str_replace ('0# ' , '7# ' , $ value ));
185238
186- return intval ($ time [7 ] / 7 ) == $ value [1 ] - 1 ;
239+ if ($ value [0 ] < 0 || $ value [0 ] > 7 || $ value [1 ] < 1 || $ value [1 ] > 5 || $ time [9 ] != $ value [0 ]) {
240+ return false ;
187241 }
242+
243+ return intval ($ time [7 ] / 7 ) == $ value [1 ] - 1 ;
188244 }
245+ }
189246
190- throw new \UnexpectedValueException (sprintf ('Invalid modifier value %s for segment #%d ' , $ value , $ pos ));
247+ /**
248+ * Instance call.
249+ *
250+ * Parse cron expression to decide if it can be run on given time (or default now).
251+ *
252+ * @param string $expr The cron expression.
253+ * @param int $time The timestamp to validate the cron expr against. Defaults to now.
254+ *
255+ * @return bool
256+ */
257+ public function isCronDue ($ expr , $ time = null )
258+ {
259+ return static ::isDue ($ expr , $ time );
191260 }
192261}
0 commit comments