TTimeScheduler::getDaysArray()   F
last analyzed

Complexity

Conditions 37
Paths 1380

Size

Total Lines 86
Code Lines 62

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 37
eloc 62
c 1
b 0
f 0
nc 1380
nop 2
dl 0
loc 86
rs 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * TTimeScheduler class file.
5
 *
6
 * @author Brad Anderson <[email protected]>
7
 * @link https://github.com/pradosoft/prado
8
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
9
 */
10
11
namespace Prado\Util\Cron;
12
13
use Prado\Exceptions\TInvalidDataValueException;
14
15
/**
16
 * TTimeScheduler class.
17
 *
18
 * This class keeps track of Scheduling event times.  It
19
 * specifies a schedule and the dates and times to trigger
20
 *
21
 * cron setup
22
 *     '*     *         *                *              *         *'
23
 * (minute) (hour) (day of month) (month of year) (day of week) (year)
24
 *  (0-59) (0-23)  (1-31)    (1-12|Jan,Feb,-,Dec) (0-6|Sun-Sat) (20xx)
25
 *
26
 * '*' (star) is any time.
27
 * adding '/(\d)' is every \d moments.
28
 * Day of Month can add '(\d)W' after the day to signify closest weekday.
29
 * Day of Month can add 'L' for last day, or 'L-2' for last day minus two.
30
 * Day of Week can add '(\d)L' after the day of week for last day of month of that type.
31
 * Day of Week can add "#(\d)" after the day to signify which week the day is from.
32
 *
33
 * Generally avoid 01:00:00 to 2:59:59 as they may not trigger or double
34
 * trigger during daylight savings time changes.
35
 *
36
 * The Months of the Year supports English, German, Spanish, French, Italian,
37
 * Russian, Hindi, and Arabic and their abbreviations.  The Days of the Week supports English, German,
38
 * Spanish, French, Italian, Russian, and Hindi and their abbreviations.
39
 *
40
 * There are schedule shortcuts:
41
 *     '@yearly' => '0 0 1 1 ?'
42
 *     '@annually' => '0 0 1 1 ?'
43
 *     '@monthly' => '0 0 1 * ?'
44
 *     '@weekly' => '0 0 ? * 0'
45
 *     '@midnight' => '0 0 * * ?'
46
 *     '@daily' => '0 0 * * ?'
47
 *     '@hourly' => '0 * * * ?'
48
 *
49
 * An efficient one-off task at a specified unix time can be scheduled with
50
 * '@(\d)' where \d is the unix time in the local php instance time zone.
51
 * This minimizes costly validation, parsing, and nextTriggerTime processing.
52
 * This allows for a higher cron task throughput to handle more cron tasks.
53
 *
54
 * e.g. '@1668165071' for 2022-11-11 11:11:11 and will trigger after the
55
 * specified time at '12 11 11 11 ? 2022'.
56
 *
57
 * @author Brad Anderson <[email protected]>
58
 * @since 4.2.0
59
 * @see https://crontab.guru For more info on Crontab Schedule Expressions.
60
 */
61
class TTimeScheduler extends \Prado\TComponent
62
{
63
	public const YEAR_MIN = 1970;
64
65
	public const YEAR_MAX = 2099;
66
67
	/** the minute attributes of the schedule */
68
	protected const MINUTE = 0;
69
70
	/** the hour attributes of the schedule */
71
	protected const HOUR = 1;
72
73
	/** the day of month attributes of the schedule */
74
	protected const DAY_OF_MONTH = 2;
75
76
	/** the month of year attributes of the schedule */
77
	protected const MONTH_OF_YEAR = 3;
78
79
	/** the day of week attributes of the schedule */
80
	protected const DAY_OF_WEEK = 4;
81
82
	/** the year attributes of the schedule */
83
	protected const YEAR = 5;
84
85
	/** The cron schedule */
86
	private $_schedule;
87
88
	/** efficient one off trigger time */
89
	protected $_triggerTime;
90
91
	/** the parsed attributes of the schedule */
92
	private $_attr = [];
93
94
	private static $_intervals = [
95
		'@yearly' => '0 0 1 1 ?',
96
		'@annually' => '0 0 1 1 ?',
97
		'@monthly' => '0 0 1 * ?',
98
		'@weekly' => '0 0 ? * 0',
99
		'@midnight' => '0 0 * * ?',
100
		'@daily' => '0 0 * * ?',
101
		'@hourly' => '0 * * * ?',
102
		];
103
	private static $_keywords = [
104
		self::MONTH_OF_YEAR => [
105
			//English, German, Spanish, French, Italian, Russian, Hindi, Arabic
106
					1 => '(?:january|januar|enero|janvier|gennaio|jan|ene|janv|genn|Январь|Янв|जनवरी|يناير)',
107
					2 => '(?:february|februar|febrero|février|febbraio|feb|févr|febbr|Февраль|Фев|फरवरी|فبراير)',
108
					3 => '(?:march|maerz|märz|marzo|mars|mar|mae|mär|Март|Мар|मार्च|مارس)',
109
					4 => '(?:april|abril|avril|aprile|apr|abr|Апрель|Апр|अप्रैल|إبريل|أبريل)',
110
					5 => '(?:may|mai|mayo|maggio|magg|Май|मई|مايو)',
111
					6 => '(?:june|juni|junio|juin|giugno|jun|Июнь|Июн|जून|يونيه|يونيو)',
112
					7 => '(?:july|juli|julio|juillet|luglio|jul|juil|Июль|Июл|जुलाई|يوليه|يوليو)',
113
					8 => '(?:august|agosto|août|aug|ago|ag|Август|Авг|अगस्त|أغسطس)',
114
					9 => '(?:september|septiembre|septembre|settembre|sep|sept|sett|Сентябрь|Сен|सितम्बर|سبتمبر)',
115
					10 => '(?:october|oktober|octubre|octobre|ottobre|okt|oct|ott|Октябрь|Окт|अक्टूबर|أكتوبر)',
116
					11 => '(?:november|noviembre|novembre|nov|Ноябрь|Ноя|नवम्बर|نوفمبر)',
117
					12 => '(?:december|dezember|diciembre|décembre|dicembre|dec|dez|déc|dic|Декабрь|Дек|दिसम्बर|ديسمبر)',
118
				],
119
		self::DAY_OF_WEEK => [ //no Arabic as those are just numbered days of the week
120
					0 => '(?:sunday|sonntag|sun|son|su|so|domingo|do|d|dimanche|dim|domenica|dom|Воскресенье|Вск|Вс|रविवार|रवि)',
121
					1 => '(?:monday|montag|mon|mo|lune|lu|l|lundi|lun|lunedì|Понедельник|Пнд|Пн|सोमवार|सोम)',
122
					2 => '(?:tuesday|dienstag|die|tue|tu|di|martes|ma|m|k|mardi|mar|martedì|Вторник|Втр|Вт|मंगलवार|मंगल)',
123
					3 => '(?:wednesday|mittwoch|mit|wed|we|mi|miércoles|x|mercredi|mer|mercoledì|me|Среда|Сре|Ср|बुधवार|बुध)',
124
					4 => '(?:thursday|donnerstag|don|thu|th|do|jueves|ju|j|jeudi|jeu|giovedì|gio|gi|Четверг|Чтв|Чт|गुरुवार|गुरु)',
125
					5 => '(?:friday|freitag|fre|fri|fr|viernes|vi|v|vendredi|ven|venerdì|ve|Пятница|Птн|Пт|शुक्रवार|शुक्र)',
126
					6 => '(?:saturday|samstag|sam|sat|sa|sábado|s|samedi|sabato|sab|Суббота|Сбт|Сб|शनिवार|शनि)',
127
				],
128
		];
129
130
	/** validation is computed only once */
131
	private static $_validatorCache;
132
133
	/**
134
	 * @return string This returns the cron schedule
135
	 */
136
	public function getSchedule()
137
	{
138
		return $this->_schedule;
139
	}
140
141
	/**
142
	 * Parses a Cron Time Tag
143
	 * @param mixed $schedule
144
	 */
145
	public function setSchedule($schedule)
146
	{
147
		if ($schedule === '') {
148
			$schedule = '* * * * * *';
149
		} elseif ($schedule === null) {
150
			$this->_schedule = $schedule;
151
			return;
152
		}
153
		$schedule = trim($schedule);
154
		$this->_schedule = $schedule;
155
		$this->_attr = [];
156
		if (strlen($schedule) > 1 && $schedule[0] == '@') {
157
			if (is_numeric($triggerTime = substr($schedule, 1))) {
158
				$this->_triggerTime = (int) $triggerTime;
159
				return;
160
			}
161
		}
162
163
		if (self::$_validatorCache) {
164
			$minuteValidator = self::$_validatorCache['m'];
165
			$hourValidator = self::$_validatorCache['h'];
166
			$domValidator = self::$_validatorCache['dom'];
167
			$monthValidator = self::$_validatorCache['mo'];
168
			$dowValidator = self::$_validatorCache['dow'];
169
			$yearValidator = self::$_validatorCache['y'];
170
			$fullValidator = self::$_validatorCache['f'];
171
		} else {
172
			$minute = '(?:[0-9]|[1-5][0-9])';
173
			$minuteStar = '\*(?:\/(?:[1-9]|[1-5][0-9]))?';
174
			$minuteRegex = $minute . '(?:\-(?:[1-9]|[1-5][0-9]))?(?:\/(?:[1-9]|[1-5][0-9]))?';
175
			$hour = '(?:[0-9]|1[0-9]|2[0-3])';
176
			$hourStar = '\*(?:\/(?:[1-9]|1[0-9]|2[0-3]))?';
177
			$hourRegex = $hour . '(?:\-(?:[1-9]|1[0-9]|2[0-3]))?(?:\/(?:[1-9]|1[0-9]|2[0-3]))?';
178
			$dom = '(?:(?:[1-9]|[12][0-9]|3[01])W?)';
179
			$domWOW = '(?:[1-9]|[12][0-9]|3[01])';
180
			$domStar = '\*(?:\/(?:[1-9]|[12][0-9]|3[01]))?';
181
			$domRegex = '(?:' . $dom . '(?:\-' . $dom . ')?(?:\/' . $domWOW . ')?|' . '(?:L(?:\-[1-5])?)' . ')';
182
			$month = '(?:[1-9]|1[012]|' .
183
				self::$_keywords[self::MONTH_OF_YEAR][1] . '|' .
184
				self::$_keywords[self::MONTH_OF_YEAR][2] . '|' .
185
				self::$_keywords[self::MONTH_OF_YEAR][3] . '|' .
186
				self::$_keywords[self::MONTH_OF_YEAR][4] . '|' .
187
				self::$_keywords[self::MONTH_OF_YEAR][5] . '|' .
188
				self::$_keywords[self::MONTH_OF_YEAR][6] . '|' .
189
				self::$_keywords[self::MONTH_OF_YEAR][7] . '|' .
190
				self::$_keywords[self::MONTH_OF_YEAR][8] . '|' .
191
				self::$_keywords[self::MONTH_OF_YEAR][9] . '|' .
192
				self::$_keywords[self::MONTH_OF_YEAR][10] . '|' .
193
				self::$_keywords[self::MONTH_OF_YEAR][11] . '|' .
194
				self::$_keywords[self::MONTH_OF_YEAR][12] . ')';
195
			$monthStar = '\*(?:\/(?:[1-9]|1[012]))?';
196
			$monthRegex = $month . '(?:\-' . $month . ')?(?:\/(?:[1-9]|1[012]))?';
197
			$dow = '(?:[0-6]|' .
198
				self::$_keywords[self::DAY_OF_WEEK][0] . '|' .
199
				self::$_keywords[self::DAY_OF_WEEK][1] . '|' .
200
				self::$_keywords[self::DAY_OF_WEEK][2] . '|' .
201
				self::$_keywords[self::DAY_OF_WEEK][3] . '|' .
202
				self::$_keywords[self::DAY_OF_WEEK][4] . '|' .
203
				self::$_keywords[self::DAY_OF_WEEK][5] . '|' .
204
				self::$_keywords[self::DAY_OF_WEEK][6] . ')';
205
			$dowStar = '\*(?:\/[0-6])?';
206
			$dowRegex = '(?:[0-6]L|' . $dow . '(?:(?:\-' . $dow . ')?(?:\/[0-6])?|#[1-5])?)';
207
			$year = '(?:19[7-9][0-9]|20[0-9][0-9])';
208
			$yearStar = '\*(?:\/[0-9]?[0-9])?';
209
			$yearRegex = $year . '(?:\-' . $year . ')?(?:\/[0-9]?[0-9])?';
210
			$fullValidator = '/^(?:(@(?:annually|yearly|monthly|weekly|daily|hourly))|' .
211
				'(?#minute)((?:' . $minuteStar . '|' . $minuteRegex . ')(?:\,(?:' . $minuteStar . '|' . $minuteRegex . '))*)[\s]+' .
212
				'(?#hour)((?:' . $hourStar . '|' . $hourRegex . ')(?:\,(?:' . $hourStar . '|' . $hourRegex . '))*)[\s]+' .
213
				'(?#DoM)(\?|(?:(?:' . $domStar . '|' . $domRegex . ')(?:,(?:' . $domStar . '|' . $domRegex . '))*))[\s]+' .
214
				'(?#month)((?:' . $monthStar . '|' . $monthRegex . ')(?:\,(?:' . $monthStar . '|' . $monthRegex . '))*)[\s]+' .
215
				'(?#DoW)(\?|(?:' . $dowStar . '|' . $dowRegex . ')(?:\,(?:' . $dowStar . '|' . $dowRegex . '))*)' .
216
				'(?#year)(?:[\s]+' .
217
					'((?:' . $yearStar . '|' . $yearRegex . ')(?:\,(?:' . $yearStar . '|' . $yearRegex . '))*)' .
218
				')?' .
219
			')$/i';
220
221
			$minuteValidator = '/^(\*|' . $minute . ')(?:\-(' . $minute . '))?(?:\/(' . $minute . '))?$/i';
222
			$hourValidator = '/^(\*|' . $hour . ')(?:\-(' . $hour . '))?(?:\/(' . $hour . '))?$/i';
223
			$domValidator = '/^(\*|\?|L|' . $domWOW . ')(W)?(?:\-(' . $domWOW . ')(W)?)?(?:\/(' . $domWOW . '))?$/i';
224
			$monthValidator = '/^(\*|' . $month . ')(?:\-(' . $month . '))?(?:\/(' . $month . '))?$/i';
225
			$dowValidator = '/^(\*|\?|' . $dow . ')(L)?(?:\-(' . $dow . ')(L)?)?(?:\/(' . $dow . '))?(?:#([1-5]))?$/i';
226
			$yearValidator = '/^(\*|' . $year . ')(?:\-(' . $year . '))?(?:\/([1-9]?[0-9]))?$/i';
227
			self::$_validatorCache = [
228
					'm' => $minuteValidator,
229
					'h' => $hourValidator,
230
					'dom' => $domValidator,
231
					'mo' => $monthValidator,
232
					'dow' => $dowValidator,
233
					'y' => $yearValidator,
234
					'f' => $fullValidator,
235
				];
236
		}
237
238
		$i = 0;
0 ignored issues
show
Unused Code introduced by
The assignment to $i is dead and can be removed.
Loading history...
239
		do {
240
			if (!preg_match($fullValidator, $schedule, $matches)) {
241
				throw new TInvalidDataValueException('timescheduler_invalid_string', $schedule);
242
			}
243
			if ($matches[1]) {
244
				foreach (self::$_intervals as $interval => $intervalSchedule) {
245
					if ($interval == $matches[1]) {
246
						$schedule = $intervalSchedule;
247
					}
248
				}
249
			}
250
		} while ($matches[1]);
251
252
		$this->_attr[self::MINUTE] = [];
253
		foreach (explode(',', $matches[2]) as $match) {
254
			if (preg_match($minuteValidator, $match, $m2)) {
255
				if ($m2[1] === '*') {
256
					$data = ['min' => 0, 'end' => 59];
257
				} else {
258
					$data = ['min' => $m2[1]];
259
					$data['end'] = $m2[2] ?? $data['min'];
260
				}
261
				$data['period'] = (int) max($m2[3] ?? 1, 1);
262
				if (!($m2[2] ?? 0) && ($m2[3] ?? 0)) {
263
					$data['end'] = 59; //No end with period
264
				}
265
				$this->_attr[self::MINUTE][] = $data;
266
			} else {
267
				throw new TInvalidDataValueException('timescheduler_invalid_string', $match);
268
			}
269
		}
270
271
		$this->_attr[self::HOUR] = [];
272
		foreach (explode(',', $matches[3]) as $match) {
273
			if (preg_match($hourValidator, $match, $m2)) {
274
				if ($m2[1] === '*') {
275
					$data = ['hour' => 0, 'end' => 23];
276
				} else {
277
					$data = ['hour' => $m2[1]];
278
					$data['end'] = $m2[2] ?? $m2[1];
279
				}
280
				$data['period'] = (int) max($m2[3] ?? 1, 1);
281
				if (!($m2[2] ?? 0) && ($m2[3] ?? 0)) {
282
					$data['end'] = 23; //No end with period
283
				}
284
				$this->_attr[self::HOUR][] = $data;
285
			} else {
286
				throw new TInvalidDataValueException('timescheduler_invalid_string', $match);
287
			}
288
		}
289
290
		$this->_attr[self::DAY_OF_MONTH] = [];
291
		foreach (explode(',', $matches[4]) as $match) {
292
			if (preg_match($domValidator, $match, $m2)) {
293
				$data = ['dom' => $m2[1]]; // *, ?, \d, L
294
				$data['domWeekday'] = $m2[2] ?? null;
295
				$data['end'] = $m2[3] ?? ($m2[1] != 'L' ? $m2[1] : null);
296
				//$data['endWeekday'] = $m2[4] ?? null;
297
				$data['endWeekday'] = $m2[4] ?? (($m2[3] ?? null) ? null : $data['domWeekday']);
298
				$data['period'] = (int) max($m2[5] ?? 1, 1);
299
				if (!($m2[3] ?? 0) && ($m2[5] ?? 0)) {
300
					$data['end'] = 31; //No end with period
301
				}
302
				$this->_attr[self::DAY_OF_MONTH][] = $data;
303
			} else {
304
				throw new TInvalidDataValueException('timescheduler_invalid_string', $match);
305
			}
306
		}
307
308
		$this->_attr[self::MONTH_OF_YEAR] = [];
309
		foreach (explode(',', $matches[5]) as $match) {
310
			if (preg_match($monthValidator, $match, $m2)) {
311
				if ($m2[1] === '*') {
312
					$data = ['moy' => 1, 'end' => 12];
313
				} else {
314
					$data = ['moy' => (int) $this->translateMonthOfYear($m2[1])];
315
					$data['end'] = (isset($m2[2]) && $m2[2]) ? (int) $this->translateMonthOfYear($m2[2]) : $data['moy'];
316
				}
317
				$data['period'] = (int) max($m2[3] ?? 1, 1);
318
				if (!($m2[2] ?? 0) && ($m2[3] ?? 0)) {
319
					$data['end'] = 12; //No end with period
320
				}
321
				$this->_attr[self::MONTH_OF_YEAR][] = $data;
322
			} else {
323
				throw new TInvalidDataValueException('timescheduler_invalid_string', $match);
324
			}
325
		}
326
327
		$this->_attr[self::DAY_OF_WEEK] = [];
328
		foreach (explode(',', $matches[6]) as $match) {
329
			if (preg_match($dowValidator, $match, $m2)) {
330
				if ($m2[1] === '*' || $m2[1] === '?') {
331
					$data = ['dow' => 0, 'end' => 6];
332
				} else {
333
					$data = ['dow' => (int) $this->translateDayOfWeek($m2[1])];
334
					$data['end'] = (isset($m2[3]) && $m2[3]) ? (int) $this->translateDayOfWeek($m2[3]) : $data['dow'];
335
				}
336
				$data['lastDow'] = $m2[2] ?? null;
337
				$data['lastEnd'] = $m2[4] ?? null;
338
				$data['period'] = (int) max($m2[5] ?? 1, 1);
339
				$data['week'] = $m2[6] ?? null;
340
				if (!($m2[3] ?? 0) && ($m2[5] ?? 0)) {
341
					$data['end'] = 6; //No end with period
342
				}
343
				$this->_attr[self::DAY_OF_WEEK][] = $data;
344
			} else {
345
				throw new TInvalidDataValueException('timescheduler_invalid_string', $match);
346
			}
347
		}
348
349
		$this->_attr[self::YEAR] = [];
350
		$matches[7] ??= '*';
351
		foreach (explode(',', $matches[7]) as $match) {
352
			if (preg_match($yearValidator, $match, $m2)) {
353
				if ($m2[1] === '*') {
354
					$data = ['year' => self::YEAR_MIN];
355
					$data['end'] = self::YEAR_MAX;
356
				} else {
357
					$data = ['year' => $m2[1]];
358
					$data['end'] = $m2[2] ?? $m2[1];
359
				}
360
				$data['period'] = max($m2[3] ?? 1, 1);
361
				if (!($m2[2] ?? 0) && ($m2[3] ?? 0)) {
362
					$data['end'] = self::YEAR_MAX; //No end with period
363
				}
364
				$this->_attr[self::YEAR][] = $data;
365
			} else {
366
				throw new TInvalidDataValueException('timescheduler_invalid_string', $match);
367
			}
368
		}
369
	}
370
371
	protected function translateMonthOfYear($v)
372
	{
373
		if (is_numeric($v)) {
374
			return $v;
375
		}
376
		foreach (self::$_keywords[self::MONTH_OF_YEAR] as $moy => $regex) {
377
			if (preg_match('/^(' . $regex . ')$/i', $v)) {
378
				return $moy;
379
			}
380
		}
381
		return $v;
382
	}
383
384
	protected function translateDayOfWeek($v)
385
	{
386
		if (is_numeric($v)) {
387
			return $v;
388
		}
389
		foreach (self::$_keywords[self::DAY_OF_WEEK] as $dow => $regex) {
390
			if (preg_match('/^(' . $regex . ')$/i', $v)) {
391
				return $dow;
392
			}
393
		}
394
		return $v;
395
	}
396
397
398
	/**
399
	 * Given which minutes are valid for the parsed time stamp, this generates
400
	 * an array of valid minutes.  Each minute is available as the key to the array
401
	 * and the value contains true or false whether the minute meets the cron
402
	 * time stamp
403
	 */
404
	protected function getMinutesArray()
405
	{
406
		$ma = array_pad([], 60, null);
407
		foreach ($this->_attr[self::MINUTE] as $m) {
408
			if (is_numeric($m['min'])) {
409
				for ($i = $m['min']; $i <= $m['end'] && $i < 60; $i += $m['period']) {
410
					$ma[$i] = 1;
411
				}
412
			}
413
		}
414
		return $ma;
415
	}
416
417
418
	/**
419
	 * Given which hours are valid for the parsed time stamp, this generates
420
	 * an array of valid hours.  Each hour is available as the key to the array
421
	 * and the value contains true or false whether the hour meets the cron
422
	 * time stamp
423
	 */
424
	protected function getHoursArray()
425
	{
426
		$ha = array_pad([], 24, null);
427
		foreach ($this->_attr[self::HOUR] as $h) {
428
			if (is_numeric($h['hour'])) {
429
				for ($i = $h['hour']; $i <= $h['end'] && $i < 24; $i += $h['period']) {
430
					$ha[$i] = 1;
431
				}
432
			}
433
		}
434
		return $ha;
435
	}
436
437
438
	/**
439
	 * Returns the number of days in a given month and year, taking into account leap years.
440
	 *
441
	 * @param numeric $month: month (integers 1-12)
442
	 * @param numeric $year: year (any integer)
0 ignored issues
show
Bug introduced by
The type Prado\Util\Cron\numeric was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
443
	 */
444
	public function days_in_month($month, $year)
445
	{
446
		return $month == 2 ? ($year % 4 ? 28 : ($year % 100 ? 29 : ($year % 400 ? 28 : 29))) : (($month - 1) % 7 % 2 ? 30 : 31);
447
	}
448
449
450
	/**
451
	 * Given which days are valid for the parsed time stamp, this generates
452
	 * an array of valid days for the given month and year.  Each day is available
453
	 * as the key to the array and the value contains true or false whether the day
454
	 * meets the cron time stamp
455
	 * @param mixed $month
456
	 * @param mixed $year
457
	 */
458
	protected function getDaysArray($month, $year)
459
	{
460
		$daysinmonth = $this->days_in_month($month, $year);
461
		$domStar = false;
462
		$dowStar = false;
463
		$da = array_pad([], $daysinmonth + 1, null);
464
		unset($da[0]);
465
		$dwa = array_pad([], $daysinmonth + 1, null);
466
		unset($dwa[0]);
467
		foreach ($this->_attr[self::DAY_OF_MONTH] as $d) {
468
			if ($d['dom'] === '*' || $d['dom'] === '?') {
469
				$domStar = (($d['dom'] == '?') || ($d['period'] == 1));
470
				foreach ($da as $key => $value) {
471
					if (($key - 1) % $d['period'] == 0) {
472
						$da[$key] = 1;
473
					}
474
				}
475
			} elseif (is_numeric($d['dom'])) {
476
				$startDay = $d['dom'];
477
				if ($d['domWeekday']) {
478
					$datea = getdate(strtotime("$year-$month-$startDay"));
479
					if ($datea['wday'] == 6) {
480
						if ($startDay == 1) {
481
							$startDay = 3;
482
						} else {
483
							$startDay--;
484
						}
485
					} elseif ($datea['wday'] == 0) {
486
						if ($startDay == $daysinmonth) {
487
							$startDay = $daysinmonth - 2;
488
						} else {
489
							$startDay++;
490
						}
491
					}
492
				}
493
				$endDay = $d['end'];
494
				if ($d['endWeekday']) {
495
					$datea = getdate(strtotime("$year-$month-$endDay"));
496
					if ($datea['wday'] == 6) {
497
						if ($endDay == 1) {
498
							$endDay = 3;
499
						} else {
500
							$endDay--;
501
						}
502
					} elseif ($datea['wday'] == 0) {
503
						if ($endDay == $daysinmonth) {
504
							$endDay = $daysinmonth - 2;
505
						} else {
506
							$endDay++;
507
						}
508
					}
509
				}
510
				for ($i = $startDay; $i <= $endDay && $i <= 31; $i += $d['period']) {
511
					$da[$i] = 1;
512
				}
513
			} elseif ($d['dom'] == 'L') {
514
				$less = empty($d['end']) ? 0 : $d['end'];
515
				$da[$daysinmonth - $less] = 1;
516
			}
517
		}
518
		$firstDatea = getdate(strtotime("$year-$month-01"));
519
		foreach ($this->_attr[self::DAY_OF_WEEK] as $d) {
520
			if (is_numeric($d['dow'])) {
521
				//start at the first sunday on or before the 1st day of the month
522
				for ($i = 1 - $firstDatea['wday']; $i <= $daysinmonth; $i += 7) {
523
					for ($ii = $d['dow']; ($ii <= $d['end']) && ($ii < 7) && (($i + $ii) <= $daysinmonth); $ii += $d['period']) {
524
						$iii = $i + $ii;
525
						$w = floor(($iii + 6) / 7);
526
						$lw = floor(($daysinmonth - $iii) / 7);
527
528
						if (($iii >= 0) && ((!$d['week'] || $d['week'] == $w) && (!$d['lastDow'] || $lw == 0))) {
529
							$dwa[$iii] = 1;
530
						}
531
					}
532
				}
533
			}
534
		}
535
		if ($dowStar) {
536
			return $da;
537
		} elseif ($domStar) {
538
			return $dwa;
539
		}
540
		foreach ($da as $key => $value) {
541
			$da[$key] = $value && ($dwa[$key] ?? 0);
542
		}
543
		return $da;
544
	}
545
546
547
	/**
548
	 * Given which month are valid for the parsed time stamp, this generates
549
	 * an array of valid months.  Each month is available as the key to the array
550
	 * and the value contains true or false whether the month meets the cron
551
	 * time stamp
552
	 */
553
	protected function getMonthsArray()
554
	{
555
		$ma = array_pad([], 13, null);
556
		unset($ma[0]);
557
		foreach ($this->_attr[self::MONTH_OF_YEAR] as $m) {
558
			if (is_numeric($m['moy'])) {
559
				for ($i = $m['moy']; $i <= $m['end'] && $i <= 12; $i += $m['period']) {
560
					$ma[$i] = 1;
561
				}
562
			}
563
		}
564
		return $ma;
565
	}
566
567
568
569
	/**
570
	 * Given which years are valid for the parsed time stamp, this generates
571
	 * an array of valid years.  Each year is available as the key to the array
572
	 * and the value contains true or false whether the year meets the cron
573
	 * time stamp.  Only the previos 2 years and next 33 years are available
574
	 * @return array
575
	 */
576
	protected function getYearsArray()
577
	{
578
		$ya = [];
579
		for ($i = self::YEAR_MIN - 1; $i <= self::YEAR_MAX; $i++) {
580
			$ya['' . $i] = 0;
581
		}
582
		foreach ($this->_attr[self::YEAR] as $m) {
583
			if (is_numeric($m['year'])) {
584
				for ($i = $m['year']; $i <= $m['end']; $i += $m['period']) {
585
					$ya[$i] = 1;
586
				}
587
			}
588
		}
589
		return $ya;
590
	}
591
592
	/**
593
	 * This calculates the next trigger time for the schedule based on the $priortime
594
	 * If no parameter time is given, the current system time is used.
595
	 * @param false|numeric|string $priortime the time or date/time from which to compute the next Trigger Time
596
	 * @return numeric the unix time of the next trigger event.
597
	 */
598
	public function getNextTriggerTime($priortime = false)
599
	{
600
		if ($priortime === null || $this->_schedule === null) {
601
			return null;
602
		}
603
		if ($priortime === false) {
604
			$priortime = time();
605
		} elseif (!is_numeric($priortime)) {
606
			$priortime = strtotime($priortime);
607
		}
608
		if ($this->_triggerTime !== null) {
609
			if ($priortime >= $this->_triggerTime) {
610
				return null;
611
			}
612
			return $this->_triggerTime;
613
		}
614
615
		$lastdata = getdate($priortime);
0 ignored issues
show
Bug introduced by
It seems like $priortime can also be of type string; however, parameter $timestamp of getdate() does only seem to accept integer|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

615
		$lastdata = getdate(/** @scrutinizer ignore-type */ $priortime);
Loading history...
616
617
		$oyear = $year = $lastdata['year'];
618
		$omonth = $month = $lastdata['mon'];
619
620
		$minutes = $this->getMinutesArray();
621
		$hours = $this->getHoursArray();
622
		$days = $this->getDaysArray($month, $year);
623
		$months = $this->getMonthsArray();
624
		$years = $this->getYearsArray();
625
626
		// Do Minutes
627
		$nmin = null;
628
		$z = -1;
629
		$s = (!$hours[$lastdata['hours']] || !$days[$lastdata['mday']] || !$months[$month] || !$years[$year]) ? 0 : $lastdata['minutes'] + 1;
630
		for ($i = $s, $hextra = 0; $i != $s + $z; $i++) {
631
			if ($i > 59) {
632
				$hextra = 1;
633
				$i = 0;
634
			}
635
			if ($minutes[$i]) {
636
				$nmin = $i;
637
				break;
638
			}
639
			$z = 0;
640
		}
641
642
		// Do Hours
643
		$nhour = null;
644
		$z = -1;
645
		$s = (!$days[$lastdata['mday']] || !$months[$lastdata['mon']] || !$years[$year]) ? 0 : $lastdata['hours'];
646
		for ($i = $s + $hextra, $dextra = 0; $i != $s + $hextra + $z ; $i++) {
647
			if ($i > 23) {
648
				$dextra = 1;
649
				$i = 0;
650
			}
651
			if ($hours[$i]) {
652
				$nhour = $i;
653
				break;
654
			}
655
			$z = 0;
656
		}
657
658
		// Adjust Month/year for extra day
659
		$nday = '01';
660
		if ($dextra) {
661
			$lastdata = getdate($priortime + $dextra * 86400);
662
			if ($lastdata['mon'] != $month) {
663
				$year = $lastdata['year'];
664
				$month = $lastdata['mon'];
665
				$days = $this->getDaysArray($month, $year);
666
			}
667
		}
668
669
		// Do the Day of Month
670
		$dim = $this->days_in_month($month, $year);
671
		$zeroIndex = !$months[$lastdata['mon']] || !$years[$year];
672
		$s = ($zeroIndex) ? 1 : $lastdata['mday'];
673
		for ($i = $s; $i != $s - 1; $i++) {
674
			if ($i > $dim) {
675
				$month++;
676
				if ($month > 12) {
677
					$month = 1;
678
					$year++;
679
					if ($year > self::YEAR_MAX) {
680
						return null;
681
					}
682
				}
683
				$lastdata = getdate(strtotime("$year-$month-01"));
684
				$dim = $this->days_in_month($month, $year);
685
				$i = 1;
686
				$days = $this->getDaysArray($month, $year);
687
				$s = ($zeroIndex) ? 1 : $lastdata['mday'];
688
			}
689
			if ($days[$i]) {
690
				$nday = $i;
691
				break;
692
			}
693
		}
694
695
		// Do the Month of the Year
696
		$nmonth = null;
697
		$z = -1;
698
		$s = (!$years[$year]) ? 1 : $lastdata['mon'];
699
		for ($i = $s, $yextra = 0; $i != $s + $z; $i++) {
700
			if ($i > 12) {
701
				$yextra = 1;
702
				$year++;
703
				$i = 1;
704
			}
705
			if ($months[$i]) {
706
				$nmonth = $i;
707
				break;
708
			}
709
			$z = 0;
710
		}
711
712
		// If month or year different, recompute day of month for that month/year
713
		if ($yextra || $nmonth != $omonth) {
714
			$month = $nmonth;
715
			$dim = $this->days_in_month($month, $year);
716
			$days = $this->getDaysArray($month, $year);
717
			$zeroIndex = !$months[$omonth] || !$years[$oyear];
718
			$s = ($zeroIndex) ? 1 : $lastdata['mday'];
719
			for ($i = $s; $i != $s - 1; $i++) {
720
				if ($i > $dim) {
721
					$month++;
722
					if ($month > 12) {
723
						$month = 1;
724
						$year++;
725
						if ($year > self::YEAR_MAX) {
726
							return null;
727
						}
728
					}
729
					$lastdata = getdate(strtotime("$year-$month-02"));
730
					$dim = $this->days_in_month($month, $year);
731
					$i = 1;
732
					$days = $this->getDaysArray($month, $year);
733
					$s = ($zeroIndex) ? 1 : $lastdata['mday'];
734
				}
735
				if ($days[$i]) {
736
					$nday = $i;
737
					break;
738
				}
739
			}
740
		}
741
742
		// Do Year
743
		$nyear = null;
744
		for ($i = $lastdata['year'] + $yextra; $i <= self::YEAR_MAX; $i++) {
745
			if ($years[$i]) {
746
				$nyear = $i;
747
				break;
748
			}
749
		}
750
		if ($nyear === null) {
751
			$time = null;
752
		} else {
753
			$time = strtotime("$nyear-$nmonth-$nday $nhour:$nmin:00");
754
		}
755
		return $time;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $time also could return the type integer which is incompatible with the documented return type Prado\Util\Cron\numeric.
Loading history...
756
	}
757
}
758