PluginProbe
Import Social Events / 1.8.5
Import Social Events v1.8.5
1.9.1 1.9.0 trunk 1.0.0 1.0.1 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.2.0 1.3.0 1.4.0 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.6.0 1.6.1 1.6.10 1.6.11 1.6.12 All 61 releases
import-facebook-events / includes / lib / icalcreator / src / Util / RecurFactory2.php

RecurFactory2.php in Import Social Events 1.8.5, at includes/lib/icalcreator/src/Util/RecurFactory2.php

1,523 lines 54.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * iCalcreator, the PHP class package managing iCal (rfc2445/rfc5445) calendar information.
4 *
5 * copyright (c) 2007-2021 Kjell-Inge Gustafsson, kigkonsult, All rights reserved
6 * Link https://kigkonsult.se
7 * Package iCalcreator
8 * Version 2.30
9 * License Subject matter of licence is the software iCalcreator.
10 * The above copyright, link, package and version notices,
11 * this licence notice and the invariant [rfc5545] PRODID result use
12 * as implemented and invoked in iCalcreator shall be included in
13 * all copies or substantial portions of the iCalcreator.
14 *
15 * iCalcreator is free software: you can redistribute it and/or modify
16 * it under the terms of the GNU Lesser General Public License as published
17 * by the Free Software Foundation, either version 3 of the License,
18 * or (at your option) any later version.
19 *
20 * iCalcreator is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU Lesser General Public License for more details.
24 *
25 * You should have received a copy of the GNU Lesser General Public License
26 * along with iCalcreator. If not, see <https://www.gnu.org/licenses/>.
27 *
28 * This file is a part of iCalcreator.
29 */
30 namespace Kigkonsult\Icalcreator\Util;
31
32 use DateTime;
33 use Exception;
34 use Kigkonsult\Icalcreator\Vcalendar;
35 use LogicException;
36
37 use function array_flip;
38 use function array_keys;
39 use function array_merge;
40 use function array_values;
41 use function array_unique;
42 use function checkdate;
43 use function count;
44 use function ctype_digit;
45 use function end;
46 use function explode;
47 use function in_array;
48 use function is_array;
49 use function reset;
50 use function sort;
51 use function sprintf;
52 use function substr;
53 use function var_export;
54
55 /**
56 * iCalcreator 'newer' recur support class
57 *
58 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
59 * @since 2.27.28 - 2020-09-10
60 */
61 class RecurFactory2
62 {
63 /*
64 * @var string DateTime format keys
65 */
66 private static $YMD = 'Ymd';
67 private static $LCD = 'd'; // day NN
68 private static $LCJ = 'j'; // day [N]N
69 private static $LCM = 'm'; // month
70 private static $LCT = 't'; // number of days in month
71 private static $UCY = 'Y'; // year NNNN
72 private static $LCW = 'w'; // day of week number
73 private static $UCW = 'W'; // week number
74
75 /*
76 * @var string DateTime nodify string
77 */
78 private static $FMTX = '%d days';
79
80 /**
81 * @var string
82 */
83 private static $COMMA = ',';
84
85 /*
86 * @var array troublesome simple recurs keys
87 */
88 private static $RECURBYX = [
89 Vcalendar::BYSECOND,
90 Vcalendar::BYMINUTE,
91 Vcalendar::BYHOUR,
92 Vcalendar::BYMONTHDAY,
93 Vcalendar::BYYEARDAY,
94 Vcalendar::BYWEEKNO,
95 Vcalendar::BYSETPOS,
96 Vcalendar::WKST
97 ];
98
99 /**
100 * Asserts recur
101 *
102 * @param array $recur
103 * @throws LogicException
104 * @since 2.27.26 - 2020-09-10
105 */
106 public static function assertRecur( array $recur )
107 {
108 $recurDisp = var_export( $recur, true );
109 static $ERR1TXT = '#1 The FREQ rule part MUST be specified in the recurrence rule.';
110 if( ! isset( $recur[Vcalendar::FREQ] )) {
111 throw new LogicException( $ERR1TXT . $recurDisp );
112 }
113 static $ERR2TXT = '#2 NO BYDAY days : ';
114 static $ERR3TXT = '#3 Unkown BYDAY day : ';
115 $cntDays = 0;
116 if( isset( $recur[Vcalendar::BYDAY] )) {
117 foreach( $recur[Vcalendar::BYDAY] as $BYDAYx => $BYDAYv ) {
118 if(((int) $BYDAYx === $BYDAYx ) && is_array( $BYDAYv )) {
119 foreach( $BYDAYv as $BYDAYx2 => $BYDAYv2 ) {
120 if( Vcalendar::DAY === $BYDAYx2 ) {
121 if( ! in_array( $BYDAYv2, RecurFactory::$DAYNAMES )) {
122 throw new LogicException( $ERR3TXT . $recurDisp );
123 }
124 $cntDays += 1;
125 }
126 } // end foreach
127 $dayN = $pos = false;
128 continue;
129 } // end if
130 elseif(( Vcalendar::DAY === $BYDAYx )) {
131 if( ! in_array( $BYDAYv, RecurFactory::$DAYNAMES )) {
132 throw new LogicException( $ERR3TXT . var_export( $recur, true ));
133 }
134 $cntDays += 1;
135 }
136 } // end foreach
137 if( empty( $cntDays )) {
138 throw new LogicException( $ERR2TXT . var_export( $recur, true ));
139 }
140 } // end if BYDAY
141 static $ERR4TXT =
142 '#3 The BYDAY rule part MUST NOT ' .
143 'be specified with a numeric value ' .
144 'when the FREQ rule part is not set to MONTHLY or YEARLY. ';
145 static $FREQ1 = [ Vcalendar::YEARLY, Vcalendar::MONTHLY ];
146 if( isset( $recur[Vcalendar::BYDAY] ) &&
147 ! in_array( $recur[Vcalendar::FREQ], $FREQ1 ) &&
148 self::hasRecurByDaysWithRelativeWeekdays( $recur[Vcalendar::BYDAY] )) {
149 throw new LogicException( $ERR4TXT . $recurDisp );
150 } // end if
151 static $ERR5TXT =
152 '#4 The BYDAY rule part MUST NOT ' .
153 'be specified with a numeric value ' .
154 'with the FREQ rule part set to YEARLY ' .
155 'when the BYWEEKNO rule part is specified. ';
156 if( isset( $recur[Vcalendar::BYDAY] ) &&
157 ( $recur[Vcalendar::FREQ] == Vcalendar::YEARLY ) &&
158 isset( $recur[Vcalendar::BYWEEKNO] ) &&
159 self::hasRecurByDaysWithRelativeWeekdays( $recur[Vcalendar::BYDAY] )) {
160 throw new LogicException( $ERR5TXT . $recurDisp );
161 } // end if
162 static $ERR6TXT =
163 '#5 The BYMONTHDAY rule part MUST NOT be specified ' .
164 'when the FREQ rule part is set to WEEKLY. ';
165 if(( $recur[Vcalendar::FREQ] == Vcalendar::WEEKLY ) &&
166 isset( $recur[Vcalendar::BYMONTHDAY] )) {
167 throw new LogicException( $ERR6TXT . $recurDisp );
168 } // end if
169 static $ERR7TXT =
170 '#6 The BYYEARDAY rule part MUST NOT be specified ' .
171 'when the FREQ rule part is set to DAILY, WEEKLY, or MONTHLY. ';
172 static $FREQ4 = [ Vcalendar::DAILY, Vcalendar::WEEKLY, Vcalendar::MONTHLY ];
173 if( isset( $recur[Vcalendar::BYYEARDAY] ) &&
174 in_array( $recur[Vcalendar::FREQ], $FREQ4 )) {
175 throw new LogicException( $ERR7TXT . $recurDisp );
176 } // end if
177 static $ERR8TXT =
178 '#7 The BYWEEKNO rule part MUST NOT be used ' .
179 'when the FREQ rule part is set to anything other than YEARLY.';
180 if( isset( $recur[Vcalendar::BYWEEKNO] ) &&
181 ( $recur[Vcalendar::FREQ] != Vcalendar::YEARLY )) {
182 throw new LogicException( $ERR8TXT . $recurDisp );
183 } // end if
184 }
185
186 /*
187 * Return Bool true if it is an simpler DAILY recur
188 *
189 * Recur UNTIL/COUNT/INTERVAL opt (INTERVAL default 1)
190 * Recur BYDAYs opt, only fixed weekdays ex. 'TH', not '-1TH'
191 * Recur BYMONTH opt
192 * Recur BYMONTHDAY opt
193 *
194 * "The BYDAY rule part MUST NOT be specified with a numeric value when the FREQ rule part
195 * is not set to MONTHLY or YEARLY."
196 *
197 * @param array $recur
198 * @return bool
199 * @since 2.27.22 - 2020-08-18
200 */
201 public static function isRecurDaily1( array $recur )
202 {
203 static $ACCEPT = [ Vcalendar::BYMONTHDAY, Vcalendar::WKST ];
204 if( Vcalendar::DAILY != $recur[Vcalendar::FREQ ] ) {
205 return false;
206 }
207 foreach( self::$RECURBYX as $byX ) {
208 if( in_array( $byX, $ACCEPT )) {
209 continue;
210 }
211 if( isset( $recur[$byX])) {
212 return false;
213 }
214 } // end foreach
215 if( ! isset( $recur[Vcalendar::BYDAY] )) {
216 return true;
217 }
218 if( empty( self::getRecurByDaysWithNoRelativeWeekdays(
219 $recur[Vcalendar::BYDAY] )
220 )) {
221 return false;
222 }
223 return true;
224 }
225
226 /*
227 * Return Bool true if it is an simple DAILY recur
228 *
229 * Recur UNTIL/COUNT/INTERVAL opt (INTERVAL default 1)
230 * Recur BYDAYs opt, only fixed weekdays ex. 'TH', not '-1TH'
231 * Recur BYMONTH opt
232 * Recur BYMONTHDAY opt
233 * BYSETPOS, only if BYMONTH or BYMONTHDAY exists
234 *
235 * "The BYDAY rule part MUST NOT be specified with a numeric value when the FREQ rule part
236 * is not set to MONTHLY or YEARLY."
237 *
238 * @param array $recur
239 * @return bool
240 * @since 2.27.24 - 2020-08-27
241 */
242 public static function isRecurDaily2( array $recur )
243 {
244 if( Vcalendar::DAILY != $recur[Vcalendar::FREQ ] ) {
245 return false;
246 }
247 if( isset( $recur[Vcalendar::BYSETPOS] ) &&
248 ( isset( $recur[Vcalendar::BYMONTH] ) ||
249 isset( $recur[Vcalendar::BYMONTHDAY] ))) {
250 unset( $recur[Vcalendar::BYSETPOS] );
251 return self::isRecurDaily1( $recur );
252 }
253 return false;
254 }
255
256 /*
257 * Return Bool true if it is an simple WEEKLY recur without BYDAYs
258 *
259 * Recur UNTIL/COUNT/INTERVAL opt (INTERVAL default 1)
260 * Recur BYMONTH opt.
261 *
262 * @param array $recur
263 * @return bool
264 * @since 2.27.22 - 2020-08-18
265 */
266 public static function isRecurWeekly1( array $recur )
267 {
268 if( Vcalendar::WEEKLY != $recur[Vcalendar::FREQ ] ) {
269 return false;
270 }
271 if( isset( $recur[Vcalendar::BYDAY] )) {
272 return false;
273 }
274 foreach( self::$RECURBYX as $byX ) {
275 if( isset( $recur[$byX])) {
276 return false;
277 }
278 }
279 return true;
280 }
281
282 /*
283 * Return Bool true if it is an simple WEEKLY recur with BYDAYs
284 *
285 * Recur UNTIL/COUNT/INTERVAL opt (INTERVAL default 1)
286 * Recur BYDAYs required, only fixed weekdays ex. 'TH', not '-1TH'
287 * Recur BYMONTH opt.
288 *
289 * "The BYDAY rule part MUST NOT be specified with a numeric value when the FREQ rule part
290 * is not set to MONTHLY or YEARLY."
291 *
292 * @param array $recur
293 * @return bool
294 * @since 2.27.22 - 2020-08-18
295 */
296 public static function isRecurWeekly2( array $recur )
297 {
298 if( Vcalendar::WEEKLY != $recur[Vcalendar::FREQ ] ) {
299 return false;
300 }
301 if( ! isset( $recur[Vcalendar::BYDAY] )) {
302 return false;
303 }
304 if( empty( self::getRecurByDaysWithNoRelativeWeekdays(
305 $recur[Vcalendar::BYDAY]
306 ))) {
307 return false;
308 }
309 foreach( self::$RECURBYX as $byX ) {
310 if( isset( $recur[$byX])) {
311 return false;
312 }
313 }
314 return true;
315 }
316
317 /*
318 * Return Bool true if it is an simple MONTHLY recur with opt BYDAY/BYMONTHDAY
319 *
320 * Recur UNTIL/COUNT/INTERVAL opt (INTERVAL default 1)
321 * Recur BYDAYs opt, only fixed Weekdays ex. 'TH', not '-1TH'
322 * Recur BYMONTH opt
323 * Recur BYMONTHDAY opt
324 * Recur BYSETPOS if BYMONTHDAY exists
325 *
326 * @param array $recur
327 * @return bool
328 * @since 2.27.22 - 2020-08-18
329 */
330 public static function isRecurMonthly1( array $recur )
331 {
332 static $ACCEPT = [ Vcalendar::BYMONTHDAY, Vcalendar::BYSETPOS, Vcalendar::WKST ];
333 if( Vcalendar::MONTHLY != $recur[Vcalendar::FREQ ] ) {
334 return false;
335 }
336 if( isset( $recur[Vcalendar::BYDAY] ) &&
337 self::hasRecurByDaysWithRelativeWeekdays( $recur[Vcalendar::BYDAY] )) {
338 return false;
339 }
340 if( isset( $recur[Vcalendar::BYSETPOS] ) &&
341 ! isset( $recur[Vcalendar::BYMONTHDAY] )) {
342 return false;
343 }
344 foreach( self::$RECURBYX as $byX ) {
345 if( in_array( $byX, $ACCEPT )) {
346 continue;
347 }
348 if( isset( $recur[$byX])) {
349 return false;
350 }
351 }
352 return true;
353 }
354
355 /*
356 * Return Bool true if it is an simple MONTHLY recur with only BYDAYs
357 *
358 * Recur UNTIL/COUNT/INTERVAL opt (INTERVAL default 1)
359 * Recur BYDAYs required
360 * Recur BYMONTH opt
361 * Recur BYSETPOS opt
362 *
363 * @param array $recur
364 * @param array $recur
365 * @return bool
366 * @since 2.27.22 - 2020-08-18
367 */
368 public static function isRecurMonthly2( array $recur )
369 {
370 static $ACCEPT = [ Vcalendar::BYSETPOS, Vcalendar::WKST ];
371 if( Vcalendar::MONTHLY != $recur[Vcalendar::FREQ ] ) {
372 return false;
373 }
374 if( ! isset( $recur[Vcalendar::BYDAY] )) {
375 return false;
376 }
377 foreach( self::$RECURBYX as $byX ) {
378 if( in_array( $byX, $ACCEPT )) {
379 continue;
380 }
381 if( isset( $recur[$byX])) {
382 return false;
383 }
384 }
385 return true;
386 }
387
388 /*
389 * Return Bool true if it is an simple YEARLY recur with BYMONTH/BYMONTHDAY but without BYDAYs
390 *
391 * Recur UNTIL/COUNT/INTERVAL opt (INTERVAL default 1)
392 * Recur BYMONTH opt
393 * Recur BYMONTHDAY opt
394 *
395 * @param array $recur
396 * @return bool
397 * @since 2.27.22 - 2020-08-18
398 */
399 public static function isRecurYearly1( array $recur )
400 {
401 static $ACCEPT = [ Vcalendar::BYMONTHDAY, Vcalendar::WKST ];
402 if( Vcalendar::YEARLY != $recur[Vcalendar::FREQ ] ) {
403 return false;
404 }
405 if( isset( $recur[Vcalendar::BYDAY] )) {
406 return false;
407 }
408 foreach( self::$RECURBYX as $byX ) {
409 if( in_array( $byX, $ACCEPT )) {
410 continue;
411 }
412 if( isset( $recur[$byX])) {
413 return false;
414 }
415 }
416 return true;
417 }
418
419 /*
420 * Return Bool true if it is an YEARLY recur with BYMONTH/BYDAY
421 *
422 * Recur UNTIL/COUNT/INTERVAL opt (INTERVAL default 1)
423 * Recur BYMONTH required
424 * Recur BYDAY required
425 * Recur BYSETPOS opt
426 *
427 * @param array $recur
428 * @return bool
429 * @since 2.29.24 - 2020-08-29
430 */
431 public static function isRecurYearly2( array $recur )
432 {
433 static $ACCEPT = [ Vcalendar::BYSETPOS, Vcalendar::WKST ];
434 if( Vcalendar::YEARLY != $recur[Vcalendar::FREQ ] ) {
435 return false;
436 }
437 if( ! isset( $recur[Vcalendar::BYDAY] ) ||
438 ! isset( $recur[Vcalendar::BYMONTH] )) {
439 return false;
440 }
441 foreach( self::$RECURBYX as $byX ) {
442 if( in_array( $byX, $ACCEPT )) {
443 continue;
444 }
445 if( isset( $recur[$byX])) {
446 return false;
447 }
448 }
449 return true;
450 }
451
452 /**
453 * Return initiated base values for recur_x_Simple
454 *
455 * If missing endDate/UNTIL, stopDate is set to (const) EXTENDYEAR year from startdate (emergency break)
456 *
457 * @param array $recur
458 * @param mixed $wDateIn
459 * @param mixed $fcnStartIn
460 * @param mixed $fcnEndIn
461 * @return array
462 * @throws Exception
463 * @since 2.29.2 - 2019-03-03
464 */
465 private static function getRecurSimpleBase(
466 array $recur,
467 $wDateIn,
468 $fcnStartIn,
469 $fcnEndIn
470 ) {
471 static $MOD = ' years';
472 $wDate = self::dateToDateTime( $wDateIn );
473 $wDateYmd = $wDate->format( self::$YMD );
474 $fcnStart = self::dateToDateTime( $fcnStartIn );
475 $fcnStartYmd = $fcnStart->format( self::$YMD );
476 if( empty( $fcnEndIn )) {
477 $base = ( $wDateYmd > $fcnStartYmd ) ? clone $wDate : clone $fcnStart;
478 $base->modify( RecurFactory::EXTENDYEAR . $MOD ); // max??
479 }
480 else {
481 $base = self::dateToDateTime( $fcnEndIn );
482 }
483 $endYmd = $base->format( self::$YMD );
484 if( isset( $recur[Vcalendar::UNTIL] )) {
485 $untilYmd = $recur[Vcalendar::UNTIL]->format( self::$YMD );
486 if( $untilYmd < $endYmd ) {
487 $endYmd = $untilYmd;
488 }
489 }
490 return [
491 $wDate,
492 $wDateYmd,
493 $fcnStartYmd,
494 $endYmd
495 ];
496 }
497
498 /**
499 * Return array dates based on a simple DAILY recur pattern
500 *
501 * Recur INTERVAL required (or set to 1)
502 * Recur BYDAYs opt, only fixed weekdays ex. 'TH', not '-1TH'
503 * Recur BYMONTH opt
504 * Recur BYMONTHDAY opt
505 * If missing endDate/UNTIL, stopDate is set to (const) EXTENDYEAR year from startdate (emergency break)
506 *
507 * "The BYDAY rule part MUST NOT be specified with a numeric value when the FREQ rule part
508 * is not set to MONTHLY or YEARLY."
509 *
510 * @param array $recur pattern for recurrency (only value part, params ignored)
511 * @param mixed $wDateIn component start date, string / array / (datetime) obj
512 * @param mixed $fcnStartIn start date, string / array / (datetime) obj
513 * @param mixed $fcnEndIn end date, string / array / (datetime) obj
514 * @return array array([Ymd] => bool)
515 * @throws Exception
516 * @since 2.27.16 - 2019-03-03
517 */
518 public static function recurDaily1(
519 array $recur,
520 $wDateIn,
521 $fcnStartIn,
522 $fcnEndIn = false
523 ) {
524 list( $wDate, $wDateYmd, $fcnStartYmd, $endYmd ) =
525 self::getRecurSimpleBase( $recur, $wDateIn, $fcnStartIn, $fcnEndIn );
526 if( $wDateYmd > $endYmd ) {
527 return [];
528 }
529 $plusXdays = sprintf( self::$FMTX, $recur[Vcalendar::INTERVAL] );
530 $count = self::getCount( $recur );
531 $result = $byDayList = $byMonthList = $byMonthDayList = $monthDays = $bspList = [];
532 $hasByMonth = $hasByMonthDays = false;
533 $byMonthList = self::getRecurByMonth( $recur, $hasByMonth );
534 $byMonthDayList = self::getRecurByMonthDay( $recur, $hasByMonthDays );
535 $byDayList = ( isset( $recur[Vcalendar::BYDAY] )) // number for day in week
536 ? self::getRecurByDaysWithNoRelativeWeekdays( $recur[Vcalendar::BYDAY] )
537 : [];
538 $wDate = $wDate->modify( $plusXdays );
539 $x = 1;
540 $bck1Month = $bck2Month = null;
541 while( $x < $count ) {
542 $Ymd = $wDate->format( self::$YMD );
543 if( $endYmd < $Ymd ) {
544 break; // leave while
545 }
546 $currMonth = (int) $wDate->format( 'm' );
547 if( $hasByMonth ) {
548 if( $bck1Month != $currMonth ) {
549 // go forward to next 'BYMONTH'
550 while( ! in_array(
551 (int) $wDate->format( self::$LCM ),
552 $byMonthList
553 )) {
554 $wDate = $wDate->modify( $plusXdays );
555 $currMonth = (int) $wDate->format( self::$LCM );
556 } // end while
557 } // end if ( $bck1Month != $currMonth )
558 $bck1Month = $currMonth;
559 $bck2Month = null;
560 $Ymd = $wDate->format( self::$YMD );
561 } // end if $hasByMonth
562 if( $Ymd <= $fcnStartYmd ) {
563 continue;
564 }
565 if( $hasByMonthDays && ( $bck2Month != $currMonth )) {
566 $bck2Month = $currMonth;
567 $monthDays = self::getMonthDaysFromByMonthDayList( // day numbers in month
568 (int) $wDate->format( self::$LCT ),
569 $byMonthDayList
570 );
571 } // end if
572 if( self::inList( $wDate->format( self::$LCD ), $monthDays ) &&
573 self::inList( $wDate->format( self::$LCW ), $byDayList )) { // number for day in week
574 $result[$Ymd] = true;
575 $x += 1;
576 } // end if
577 $wDate = $wDate->modify( $plusXdays );
578 } // end while
579 return $result;
580 }
581
582 /**
583 * Return array dates based on a DAILY/BYSETPOS recur pattern
584 *
585 * Recur INTERVAL required (or set to 1)
586 * Recur BYDAYs opt, only fixed weekdays ex. 'TH', not '-1TH'
587 * Recur BYMONTH opt
588 * Recur BYMONTHDAY opt
589 * BYSETPOS, only if BYMONTH or BYMONTHDAY exists
590 * If missing endDate/UNTIL, stopDate is set to (const) EXTENDYEAR year from startdate (emergency break)
591 *
592 * "The BYDAY rule part MUST NOT be specified with a numeric value when the FREQ rule part
593 * is not set to MONTHLY or YEARLY."
594 *
595 * @param array $recur pattern for recurrency (only value part, params ignored)
596 * @param mixed $wDateIn component start date, string / array / (datetime) obj
597 * @param mixed $fcnStartIn start date, string / array / (datetime) obj
598 * @param mixed $fcnEndIn end date, string / array / (datetime) obj
599 * @return array array([Ymd] => bool)
600 * @throws Exception
601 * @since 2.27.16 - 2019-03-03
602 */
603 public static function recurDaily2(
604 array $recur,
605 $wDateIn,
606 $fcnStartIn,
607 $fcnEndIn = false
608 ) {
609 list( $wDate, $wDateYmd, $fcnStartYmd, $endYmd ) =
610 self::getRecurSimpleBase( $recur, $wDateIn, $fcnStartIn, $fcnEndIn );
611 if( $wDateYmd > $endYmd ) {
612 return [];
613 }
614 $count = self::getCount( $recur );
615 unset( $recur[Vcalendar::COUNT] );
616 self::hasSetByPos( $recur );
617 $bySetPos = $recur[Vcalendar::BYSETPOS];
618 unset( $recur[Vcalendar::BYSETPOS] );
619 $year = (int) $wDate->format( self::$UCY );
620 $month = (int) $wDate->format( self::$LCM );
621 $wDate->setDate( $year, $month, 1 );
622 $wDateIn2 = clone $wDate;
623 $fcnStartIn2 = $wDateIn2->format( self::$YMD );
624 $yearEnd = (int) substr( $endYmd, 0, 4 );
625 $monthEnd = (int) substr( $endYmd, 4, 2 );
626 $dayEnd = (int) substr( $endYmd, 6, 2 );
627 $wDate->setDate( $yearEnd, $monthEnd, $dayEnd );
628 $fcnEndIn2 = $wDate->setDate( $yearEnd, $monthEnd, (int) $wDate->format( self::$LCT ));
629 $result1 = self::recurDaily1( $recur, $wDateIn2, $fcnStartIn2, $fcnEndIn2 );
630 $recurLimits = [ $count, $bySetPos, $wDateYmd, $endYmd ];
631 $result = $bspList = [];
632 $currMonth = $month;
633 $x = 1;
634 foreach( array_keys( $result1 ) as $Ymd ) {
635 $month = (int) substr( $Ymd, 4, 2 );
636 if( $currMonth != $month ) {
637 self::bySetPosResultAppend($result, $x, $bspList, $recurLimits );
638 if(( $x >= $count ) || ( $endYmd < $Ymd )) {
639 break; // leave foreach !
640 }
641 $bspList = [];
642 $currMonth = $month;
643 }
644 $bspList[$Ymd] = true;
645 } // end foreach
646 return $result;
647 }
648
649 /**
650 * Return array dates based on a simple WEEKLY recur pattern without BYDAYSs
651 *
652 * Recur INTERVAL required (or set to 1)
653 * Recur BYMONTH opt.
654 * If missing endDate/UNTIL, stopDate is set to (const) EXTENDYEAR year from startdate (emergency break)
655 *
656 * @param array $recur pattern for recurrency (only value part, params ignored)
657 * @param mixed $wDateIn component start date, string / array / (datetime) obj
658 * @param mixed $fcnStartIn start date, string / array / (datetime) obj
659 * @param mixed $fcnEndIn end date, string / array / (datetime) obj
660 * @return array array([Ymd] => bool)
661 * @throws Exception
662 * @since 2.27.16 - 2019-03-03
663 */
664 public static function recurWeekly1(
665 array $recur,
666 $wDateIn,
667 $fcnStartIn,
668 $fcnEndIn = false
669 ) {
670 list( $wDate, $wDateYmd, $fcnStartYmd, $endYmd ) =
671 self::getRecurSimpleBase( $recur, $wDateIn, $fcnStartIn, $fcnEndIn );
672 if( $wDateYmd > $endYmd ) {
673 return [];
674 }
675 $result = [];
676 $x = 1;
677 $count = self::getCount( $recur );
678 $hasByMonth = false;
679 $byMonthList = self::getRecurByMonth( $recur, $hasByMonth );
680 $modifyX = sprintf( self::$FMTX, ( $recur[Vcalendar::INTERVAL] * 7 ));
681 while( $x < $count ) {
682 $wDate = $wDate->modify( $modifyX );
683 $Ymd = $wDate->format( self::$YMD );
684 if( $endYmd < $Ymd ) {
685 break;
686 }
687 if( $Ymd <= $fcnStartYmd ) {
688 continue;
689 }
690 if( self::inList( $wDate->format( self::$LCM ), $byMonthList )) {
691 $result[$Ymd] = true;
692 $x += 1;
693 }
694 } // end while
695 return $result;
696 }
697
698 /**
699 * Return array dates based on a simple WEEKLY recur pattern with BYDAYs
700 *
701 * Recur INTERVAL required (or set to 1)
702 * Recur BYDAYS required, no positioned ones only ex 'WE', not '-1WE'
703 * Recur BYMONTH opt.
704 * If missing endDate/UNTIL, stopDate is set to (const) EXTENDYEAR year from startdate (emergency break)
705 *
706 * @param array $recur pattern for recurrency (only value part, params ignored)
707 * @param mixed $wDateIn component start date, string / array / (datetime) obj
708 * @param mixed $fcnStartIn start date, string / array / (datetime) obj
709 * @param mixed $fcnEndIn end date, string / array / (datetime) obj
710 * @return array array([Ymd] => bool)
711 * @throws Exception
712 * @since 2.27.28 - 2029-09-10
713 */
714 public static function recurWeekly2(
715 array $recur,
716 $wDateIn,
717 $fcnStartIn,
718 $fcnEndIn = false
719 ) {
720 static $MINUS1DAY = '-1 day';
721 list( $wDate, $wDateYmd, $fcnStartYmd, $endYmd ) =
722 self::getRecurSimpleBase( $recur, $wDateIn, $fcnStartIn, $fcnEndIn );
723 if( $wDateYmd > $endYmd ) {
724 return [];
725 }
726 $result = [];
727 $x = 1;
728 $count = self::getCount( $recur );
729 $byDayList = self::getRecurByDaysWithNoRelativeWeekdays(
730 $recur[Vcalendar::BYDAY] // number(s) for day in week
731 );
732 $hasByMonth = false;
733 $byMonthList = self::getRecurByMonth( $recur, $hasByMonth );
734 $modify1 = sprintf( self::$FMTX, 1 );
735 $targetWeekNo = (int) $wDate->format( self::$UCW );
736 // go back to first day of week or first day in month
737 while(( 1 != $wDate->format( self::$LCW )) &&
738 ( 1 != $wDate->format( self::$LCJ ))) {
739 $wDate = $wDate->modify( $MINUS1DAY );
740 } // end while
741 while( $x < $count ) {
742 $currWeekNo = (int) $wDate->format( self::$UCW );
743 $Ymd = $wDate->format( self::$YMD );
744 switch( true ) {
745 case( $Ymd <= $fcnStartYmd ) :
746 $wDate = $wDate->modify( $modify1 );
747 break;
748 case( $endYmd < $Ymd ) :
749 break 2; // leave while !
750 case( $currWeekNo == $targetWeekNo ) :
751 if( self::inList( $wDate->format( self::$LCM ), $byMonthList )) {
752 if( self::inList( $wDate->format( self::$LCW ), $byDayList )) {
753 $result[$Ymd] = true;
754 $x += 1;
755 }
756 }
757 $wDate = $wDate->modify( $modify1 );
758 break;
759 default :
760 // now is the first day of next week
761 if( 1 < $recur[Vcalendar::INTERVAL] ) {
762 // advance interval weeks
763 $dayNo = ( 7 * ( $recur[Vcalendar::INTERVAL] - 1 ));
764 $modifyX = sprintf( self::$FMTX,$dayNo );
765 $wDate = $wDate->modify( $modifyX );
766 }
767 $targetWeekNo = (int) $wDate->format( self::$UCW );
768 break;
769 } // end switch
770 } // end while
771 return $result;
772 }
773
774 /**
775 * Return array dates based on a simple MONTHLY recur pattern
776 *
777 * Recur INTERVAL required (or set to 1)
778 * Recur BYMONTH opt
779 * Recur BYMONTHDAY opt
780 * Recur BYDAYSs opt but no positioned ones only ex 'WE', not '-1WE'
781 * Recur BYSETPOS if BYMONTHDAY exists
782 * If missing endDate/UNTIL, stopDate is set to (const) EXTENDYEAR year from startdate (emergency break)
783 *
784 * @param array $recur pattern for recurrency (only value part, params ignored)
785 * @param mixed $wDateIn component start date, string / array / (datetime) obj
786 * @param mixed $fcnStartIn start date, string / array / (datetime) obj
787 * @param mixed $fcnEndIn end date, string / array / (datetime) obj
788 * @return array array([Ymd] => bool)
789 * @throws Exception
790 * @since 2.29.24 - 2020-08-29
791 */
792 public static function recurMonthly1(
793 array $recur,
794 $wDateIn,
795 $fcnStartIn,
796 $fcnEndIn = false
797 ) {
798 list( $wDate, $wDateYmd, $fcnStartYmd, $endYmd ) =
799 self::getRecurSimpleBase( $recur, $wDateIn, $fcnStartIn, $fcnEndIn );
800 if( $wDateYmd > $endYmd ) {
801 return [];
802 }
803 $firstYmd = $wDateYmd;
804 $hasBSP = self::hasSetByPos( $recur );
805 $count = self::getCount( $recur );
806 $result = $monthDays = $byDayList = $bspList = [];
807 $hasByMonthDays = $hasByMonth = $hasByDay = false;
808 $byMonthList = self::getRecurByMonth( $recur, $hasByMonth );
809 $byMonthDayList = self::getRecurByMonthDay( $recur, $hasByMonthDays );
810 if( isset( $recur[Vcalendar::BYDAY] )) {
811 $hasByDay = true;
812 $byDayList = self::getRecurByDaysWithNoRelativeWeekdays(
813 $recur[Vcalendar::BYDAY] // number(s) for day in week
814 );
815 }
816 $year = (int) $wDate->format( self::$UCY );
817 $month = (int) $wDate->format( self::$LCM );
818 $recurLimits = [];
819 $currMonth = -1;
820 if( $hasByMonthDays || $hasByDay ) {
821 if( $hasByMonthDays ) {
822 $monthDays = self::getMonthDaysFromByMonthDayList(
823 (int)$wDate->format( self::$LCT ), // day numbers in month
824 $byMonthDayList
825 );
826 if( $hasBSP ) {
827 $recurLimits = [ $count, $recur[Vcalendar::BYSETPOS], $wDateYmd, $endYmd ];
828 }
829 }
830 $day = 1;
831 $currMonth = $month;
832 } // end if
833 else {
834 $day = (int) $wDate->format( self::$LCJ );
835 }
836 $plusXmonth = $recur[Vcalendar::INTERVAL] . Util::$SP0 . RecurFactory::$LCMONTH;
837 $x = 1;
838 while( $x <= $count ) {
839 if( $month !== $currMonth ) {
840 if( $hasByMonthDays && $hasBSP ) { // has BySetPos !!
841 self::bySetPosResultAppend($result, $x, $bspList, $recurLimits );
842 $Ymd = sprintf( RecurFactory::$YMDs, $year, $month, $day );
843 if( $endYmd < $Ymd ) { // leave while !
844 break;
845 }
846 $bspList = [];
847 } // end if
848 $wDate->setDate( $year, $month, 1 )->modify( $plusXmonth );
849 $year = (int) $wDate->format( self::$UCY );
850 $month = (int) $wDate->format( self::$LCM );
851 if( ! self::inList( $month, $byMonthList )) {
852 $currMonth = -1;
853 continue;
854 }
855 $currMonth = $month;
856 if( $hasByMonthDays ) {
857 $day = 1;
858 $monthDays = self::getMonthDaysFromByMonthDayList(
859 (int) $wDate->setDate( $year, $month, $day )->format( self::$LCT ),
860 $byMonthDayList
861 );
862 }
863 if( $hasByDay ) {
864 $day = 1;
865 }
866 } // end if( $month != $currMonth )
867 else {
868 ++$day;
869 }
870 if( ! checkdate( $month, $day, $year )) {
871 $currMonth = -1;
872 continue;
873 }
874 $Ymd = sprintf( RecurFactory::$YMDs, $year, $month, $day );
875 $dayNo = (int) $wDate->setDate( $year, $month, $day )->format( self::$LCW );
876 switch( true ) {
877 case ( $hasByMonthDays && $hasBSP ) : // has BySetPos !!
878 if( self::inList( $day, $monthDays ) &&
879 self::inList( $dayNo, $byDayList )) {
880 $bspList[$Ymd] = true;
881 }
882 break;
883 case ( $endYmd < $Ymd ) :
884 break 2; // leave while !!
885 case ( $Ymd <= $firstYmd ); // accept all but first
886 break;
887 case ( self::inList( $day, $monthDays )) :
888 if( self::inList( $dayNo, $byDayList )) { // empty or hit
889 ++$x;
890 $result[$Ymd] = true;
891 if( $x >= $count ) {
892 break 2; // leave while !!
893 }
894 } // end if
895 if( ! $hasByMonthDays && ! $hasByDay ) {
896 $currMonth = -1;
897 }
898 break;
899 } // end switch
900 } // end while
901 return $result;
902 }
903
904 /**
905 * Return array dates based on a MONTHLY/YEARLY BYDAYSs recur pattern without BYMONTHDAY
906 *
907 * Recur INTERVAL required (or set to 1)
908 * Recur BYDAY required
909 * Recur BYMONTH MONTHLY opt, YEARLY required
910 * Recur BYSETPOS opt
911 * If missing endDate/UNTIL, stopDate is set to (const) EXTENDYEAR year from startdate (emergency break)
912 *
913 * "Each BYDAY value can also be preceded by a positive (+n) or
914 * negative (-n) integer, indicates the nth
915 * occurrence of a specific day within the MONTHLY or YEARLY "RRULE".
916 *
917 * "The numeric value in a BYDAY rule part with the FREQ rule part set to YEARLY corresponds
918 * to an offset within the month when the BYMONTH rule part is present"
919 *
920 * @param array $recur pattern for recurrency (only value part, params ignored)
921 * @param mixed $wDateIn component start date, string / array / (datetime) obj
922 * @param mixed $fcnStartIn start date, string / array / (datetime) obj
923 * @param mixed $fcnEndIn end date, string / array / (datetime) obj
924 * @return array array([Ymd] => bool)
925 * @throws Exception
926 * @since 2.29.24 - 2020-08-29
927 */
928 public static function recurMonthlyYearly3(
929 array $recur,
930 $wDateIn,
931 $fcnStartIn,
932 $fcnEndIn = false
933 ) {
934 list( $wDate, $wDateYmd, $fcnStartYmd, $endYmd ) =
935 self::getRecurSimpleBase( $recur, $wDateIn, $fcnStartIn, $fcnEndIn );
936 if( $wDateYmd > $endYmd ) {
937 return [];
938 }
939 $isYearly = isset( $recur[Vcalendar::YEARLY] );
940 $hasBSP = self::hasSetByPos( $recur );
941 $count = self::getCount( $recur );
942 $result = $bspList = [];
943 $year = (int) $wDate->format( self::$UCY );
944 $month = (int) $wDate->format( self::$LCM );
945 $hasByMonth = false;
946 $byMonthList = self::getRecurByMonth( $recur, $hasByMonth );
947 if( $hasByMonth ) {
948 while( ! self::inList( $month, $byMonthList )) {
949 $wDate->modify( 1 . Util::$SP0 . RecurFactory::$LCMONTH );
950 $year = (int) $wDate->format( self::$UCY );
951 $month = (int) $wDate->format( self::$LCM );
952 } // end while
953 } // end if
954 $day = 1;
955 $modifier = $isYearly
956 ? $recur[Vcalendar::INTERVAL] . Util::$SP0 . RecurFactory::$LCYEAR
957 : $recur[Vcalendar::INTERVAL] . Util::$SP0 . RecurFactory::$LCMONTH;
958 $weekDaysInMonth = self::getRecurByDaysInMonth(
959 $recur[Vcalendar::BYDAY],
960 $year,
961 $month
962 );
963 $recurLimits = [];
964 if( $hasBSP ) {
965 $recurLimits = [ $count, $recur[Vcalendar::BYSETPOS], $wDateYmd, $endYmd ];
966 $wDate->setDate( $year, $month, 1 ); //
967 $year = (int) $wDate->format( self::$UCY );
968 $month = (int) $wDate->format( self::$LCM );
969 $day = (int) $wDate->format( self::$LCJ );
970 }
971 $currMonth = $month;
972 $currYear = $year;
973 $x = ( isset( $recur[Vcalendar::BYDAY] ) &&
974 self::hasRecurByDaysWithRelativeWeekdays( $recur[Vcalendar::BYDAY] )) ? 0 : 1;
975 while( $x < $count ) {
976 if( ! checkdate( $month, $day, $year )) {
977 $currMonth = -1;
978 if( $isYearly && ( 12 === $month )) {
979 $currYear = -1;
980 }
981 }
982 if(( $month !== $currMonth ) || ( $isYearly && ( $year !== $currYear ))) {
983 $day = 1;
984 if( $isYearly && ( $year !== $currYear )) {
985 $wDate->setDate( $year, 1, $day )->modify( $modifier );
986 $year = (int) $wDate->format( self::$UCY );
987 $month = 1;
988 }
989 else {
990 $wDate->setDate( $year, $month, $day )->modify( $modifier );
991 $year = (int) $wDate->format( self::$UCY );
992 $month = (int) $wDate->format( self::$LCM );
993 }
994 if( $hasBSP ) {
995 self::bySetPosResultAppend( $result, $x, $bspList, $recurLimits );
996 $Ymd = sprintf( RecurFactory::$YMDs, $year, $month, $day );
997 if( $endYmd < $Ymd ) { // leave while !
998 break;
999 }
1000 $bspList = [];
1001 } // end if
1002 if( ! self::inList( $month, $byMonthList )) {
1003 $currMonth = -1;
1004 if( $isYearly && ( 12 === $month )) {
1005 $currYear = -1;
1006 }
1007 continue;
1008 }
1009 $currYear = $year;
1010 $currMonth = $month;
1011 $weekDaysInMonth = self::getRecurByDaysInMonth(
1012 $recur[Vcalendar::BYDAY],
1013 $year,
1014 $month
1015 );
1016 } // end if
1017 $Ymd = sprintf( RecurFactory::$YMDs, $year, $month, $day );
1018 switch( true ) {
1019 case $hasBSP : // has BySetPos !!
1020 if( self::inList( $day, $weekDaysInMonth )) {
1021 $bspList[$Ymd] = true;
1022 }
1023 break;
1024 case ( $endYmd < $Ymd ) :
1025 break 2; // leave while !
1026 case ( $Ymd <= $fcnStartYmd ) :
1027 break;
1028 case self::inList( $day, $weekDaysInMonth ) :
1029 $result[$Ymd] = true;
1030 ++$x;
1031 if( $x >= $count ) {
1032 break 2; // leave while !
1033 }
1034 break;
1035 } // end switch
1036 // count day up
1037 ++$day;
1038 } // end while
1039 return $result;
1040 }
1041
1042 /**
1043 * Append result from bspList in conjunction with x/count, bySetPos, start/endYmd
1044 *
1045 * @param array $result
1046 * @param int $x
1047 * @param array $bspList
1048 * @param array $recurLimits [ count, bySetPos, wDateYmd, endYmd ]
1049 * @return void
1050 */
1051 private static function bySetPosResultAppend(
1052 array & $result,
1053 & $x,
1054 array $bspList,
1055 array $recurLimits
1056 ) {
1057 if( empty( $bspList )) {
1058 return;
1059 }
1060 $bspKeys = array_keys( $bspList );
1061 // order bspList items up
1062 $ix = 1;
1063 foreach( $bspKeys as $Ymd ) {
1064 $bspList[$Ymd] = [ $ix++ ];
1065 }
1066 // order bspList items down
1067 $ix = -1;
1068 foreach( array_reverse( $bspKeys ) as $Ymd ) {
1069 $bspList[$Ymd][1] = $ix--;
1070 }
1071 // match up/down items in bspList with each bySetPos item
1072 $temp = [];
1073 foreach( $bspList as $Ymd => $ydmOrder ) {
1074 if( in_array( $ydmOrder[0], $recurLimits[1] )) {
1075 $temp[] = $Ymd;
1076 }
1077 elseif( in_array( $ydmOrder[1], $recurLimits[1] )) {
1078 $temp[] = $Ymd;
1079 }
1080 }
1081 // if match, update result if Ymd within startYmd - endYmd
1082 foreach( $temp as $Ymd ) {
1083 if(( $recurLimits[2] < $Ymd) && ( $Ymd <= $recurLimits[3] )) {
1084 $result[$Ymd] = true;
1085 $x += 1;
1086 if( $x >= $recurLimits[0] ) { // count
1087 break;
1088 }
1089 }
1090 } // end foreach
1091 }
1092
1093 /**
1094 * Return array dates based on a simple YEARLY recur pattern without BYDAYSs
1095 *
1096 * Recur INTERVAL required
1097 * Recur BYMONTH opt,
1098 * Recur BYMONTHDAY opt
1099 * If missing endDate/UNTIL, stopDate is set to (const) EXTENDYEAR year from startdate (emergency break)
1100 *
1101 * @param array $recur pattern for recurrency (only value part, params ignored)
1102 * @param mixed $wDateIn component start date, string / array / (datetime) obj
1103 * @param mixed $fcnStartIn start date, string / array / (datetime) obj
1104 * @param mixed $fcnEndIn end date, string / array / (datetime) obj
1105 * @return array array([Ymd] => bool)
1106 * @throws Exception
1107 * @since 2.29.21 - 2020-01-31
1108 */
1109 public static function recurYearly1(
1110 array $recur,
1111 $wDateIn,
1112 $fcnStartIn,
1113 $fcnEndIn = false
1114 ) {
1115 list( $wDate, $wDateYmd, $fcnStartYmd, $endYmd ) =
1116 self::getRecurSimpleBase( $recur, $wDateIn, $fcnStartIn, $fcnEndIn );
1117 if( $wDateYmd > $endYmd ) {
1118 return [];
1119 }
1120 $startYmd = ( $wDateYmd > $fcnStartYmd ) ? $wDateYmd : $fcnStartYmd;
1121 $result = [];
1122 $x = 1;
1123 $count = self::getCount( $recur );
1124 $hasByMonthDays = $hasByMonth = false;
1125 $byMonthList = self::getRecurByMonth( $recur, $hasByMonth );
1126 $byMonthDayList = self::getRecurByMonthDay( $recur, $hasByMonthDays );
1127 $currYear = $year = (int) $wDate->format( self::$UCY );
1128 $month = (int) $wDate->format( self::$LCM );
1129 $day = (int) $wDate->format( self::$LCJ );
1130 $currMonth = $firstMonth = $lastMonth = $month;
1131 if( $hasByMonth || $hasByMonthDays ) {
1132 $firstMonth = reset( $byMonthList );
1133 $lastMonth = end( $byMonthList );
1134 }
1135 else {
1136 $currMonth = -1;
1137 $currYear = -1;
1138 }
1139 $isLastMonth = false;
1140 while(( $x < $count ) && ( $endYmd >= $wDate->format( self::$YMD ))) {
1141 if( $year !== $currYear ) {
1142 $year += $recur[Vcalendar::INTERVAL];
1143 $currYear = $year;
1144 if( $hasByMonth ) {
1145 $month = 1;
1146 $currMonth = -1;
1147 }
1148 $wDate->setDate( $year, $month, $day );
1149 } // end if currYear
1150 if( $hasByMonth && ( $month != $currMonth )) {
1151 $continue2 = false;
1152 switch( true ) {
1153 case( ! self::inList( $month, $byMonthList )) : // set first month
1154 $currMonth = $month = $firstMonth;
1155 break;
1156 case( $isLastMonth ) : // set first month
1157 $currMonth = $month = $firstMonth;
1158 break;
1159 case( 1 === count( $byMonthList )) : // step
1160 $currYear = -1;
1161 $isLastMonth = true;
1162 continue 2; // i.e. cont. while
1163 default : // next month in list
1164 $nextKey = array_keys( $byMonthList, $month )[0] + 1;
1165 if( isset( $byMonthList[$nextKey] )) {
1166 $currMonth = $month = $byMonthList[$nextKey];
1167 break;
1168 }
1169 $currYear = -1;
1170 $isLastMonth = true;
1171 continue 2; // i.e. cont. while
1172 } // end switch
1173 $isLastMonth = ( $month === $lastMonth );
1174 $wDate->setDate( $year, $month, $day );
1175 } // end if month != currMonth
1176 $Ymd = $wDate->format( self::$YMD );
1177 if( $endYmd < $Ymd ) {
1178 break; // leave while !!
1179 }
1180 if( $hasByMonthDays ) {
1181 foreach( self::getMonthDaysFromByMonthDayList(
1182 (int) $wDate->format( self::$LCT ),
1183 $byMonthDayList
1184 ) as $monthDay ) {
1185 $Ymd = sprintf( RecurFactory::$YMDs, $year, $month, $monthDay );
1186 if( $Ymd <= $fcnStartYmd ) {
1187 continue;
1188 }
1189 if( $endYmd < $Ymd ) {
1190 break 2; // leave while !!
1191 }
1192 $result[$Ymd] = true;
1193 ++$x;
1194 if( $x >= $count ) {
1195 break 2;
1196 }
1197 } // end foreach
1198 } // end if $hasByMonthDays
1199 elseif( $Ymd > $startYmd ) {
1200 $result[$Ymd] = true;
1201 ++$x;
1202 }
1203 if( $hasByMonth ) {
1204 $currMonth = -1;
1205 if( $isLastMonth ) {
1206 $currYear = -1;
1207 }
1208 }
1209 else {
1210 $currYear = -1;
1211 }
1212 } // end while
1213 return $result;
1214 }
1215
1216 /**
1217 * Return count occurrences (if found) or PHP_INT_MAX
1218 *
1219 * @param array $recur
1220 * @return int
1221 */
1222 private static function getCount( array $recur )
1223 {
1224 return ( isset( $recur[Vcalendar::COUNT] ))
1225 ? (int) $recur[Vcalendar::COUNT]
1226 : PHP_INT_MAX;
1227 }
1228
1229 /*
1230 * Return bool true if byXxxList is empty or needle found in byXxxList
1231 *
1232 * @param int $needle
1233 * @param array $byXxxList
1234 * @return bool
1235 * @since 2.27.16 - 2019-03-04
1236 */
1237 private static function inList( $needle, array $byXxxList )
1238 {
1239 if( empty( $byXxxList )) {
1240 return true;
1241 }
1242 return in_array( $needle, $byXxxList );
1243 }
1244
1245 /**
1246 * Return bool true if recur setByPos exists, assure array
1247 *
1248 * @param array $recur
1249 * @return bool
1250 */
1251 private static function hasSetByPos( array & $recur )
1252 {
1253 if( ! isset( $recur[Vcalendar::BYSETPOS] )) {
1254 return false;
1255 }
1256 if( ! is_array( $recur[Vcalendar::BYSETPOS] )) {
1257 $recur[Vcalendar::BYSETPOS] =
1258 explode( self::$COMMA, (string) $recur[Vcalendar::BYSETPOS] );
1259 }
1260 sort( $recur[Vcalendar::BYSETPOS] );
1261 return true;
1262 }
1263
1264 /*
1265 * Return array, recur BYMONTH (sorted month numbers)
1266 *
1267 * @param array $recur
1268 * @param bool $hasMonth
1269 * @return array
1270 * @since 2.29.11 - 2019-08-30
1271 */
1272 private static function getRecurByMonth( array $recur, & $hasByMonth = false )
1273 {
1274 $byMonthList = [];
1275 if( isset( $recur[Vcalendar::BYMONTH] )) {
1276 $byMonthList = is_array( $recur[Vcalendar::BYMONTH] )
1277 ? $recur[Vcalendar::BYMONTH]
1278 : [ $recur[Vcalendar::BYMONTH] ];
1279 sort( $byMonthList, SORT_NUMERIC );
1280 $hasByMonth = true;
1281 }
1282 return $byMonthList;
1283 }
1284
1285 /*
1286 * Return array BYMONTHDAY i.e. sorted day numbers in month
1287 *
1288 * @param array $recur
1289 * @param bool $hasMonthDays
1290 * @return array
1291 * @since 2.29.11 - 2019-08-30
1292 */
1293 private static function getRecurByMonthDay( array $recur, & $hasByMonthDays = false )
1294 {
1295 $byMonthDayList = [];
1296 if( isset( $recur[Vcalendar::BYMONTHDAY] )) {
1297 $byMonthDayList = is_array( $recur[Vcalendar::BYMONTHDAY] )
1298 ? $recur[Vcalendar::BYMONTHDAY]
1299 : [ $recur[Vcalendar::BYMONTHDAY] ];
1300 sort( $byMonthDayList, SORT_NUMERIC );
1301 $hasByMonthDays = true;
1302 }
1303 return $byMonthDayList;
1304 }
1305
1306 /*
1307 * Return array list of monthdays from byMonthDayList
1308 *
1309 * Fix also negative days, days before month end, conv to month day no
1310 *
1311 * @param int $daysInMonth
1312 * @param array $byMonthDayList
1313 * @return array
1314 * @since 2.27.16 - 2019-03-06
1315 */
1316 public static function getMonthDaysFromByMonthDayList(
1317 $daysInMonth,
1318 array $byMonthDayList
1319 ) {
1320 $list = [];
1321 foreach( $byMonthDayList as $byMonthDay ) {
1322 $list[] = ( 0 < $byMonthDay )
1323 ? $byMonthDay
1324 : ( $daysInMonth + 1 + $byMonthDay );
1325 }
1326 $list = array_values( array_unique( $list ));
1327 sort( $list, SORT_NUMERIC );
1328 return $list;
1329 }
1330
1331 /*
1332 * Return recur BYDAYs but the relative part of weekday(s) skipped ( ex '-1TH' to 'TH')
1333 *
1334 * @param array $recurByDay
1335 * @return array
1336 * @since 2.27.16 - 2019-03-03
1337 */
1338 private static function getRecurByDaysWithNoRelativeWeekdays( array $recurByDay )
1339 {
1340 $dayArr = array_flip( RecurFactory::$DAYNAMES );
1341 $list = [];
1342 foreach( $recurByDay as $BYDAYx => $BYDAYv ) {
1343 if( ctype_digit((string) $BYDAYx ) && is_array( $BYDAYv )) {
1344 foreach( $BYDAYv as $BYDAYx2 => $BYDAYv2 ) {
1345 if( Vcalendar::DAY == $BYDAYx2 ) {
1346 $list[] = $dayArr[$BYDAYv2];
1347 }
1348 }
1349 } // end if
1350 elseif( Vcalendar::DAY == $BYDAYx ) {
1351 $list[] = $dayArr[$BYDAYv];
1352 }
1353 } // end foreach
1354 return $list;
1355 }
1356
1357 /*
1358 * Return bool true if recur BYDAYs has relative weekday(s) ( ex '-1 TH' )
1359 *
1360 * @param array $recurByDay
1361 * @return array
1362 * @since 2.27.16 - 2019-03-03
1363 */
1364 private static function hasRecurByDaysWithRelativeWeekdays( array $recurByDay )
1365 {
1366 if( empty( $recurByDay )) {
1367 return false;
1368 }
1369 foreach( $recurByDay as $BYDAYx => $BYDAYv ) {
1370 if(((int) $BYDAYx === $BYDAYx ) && is_array( $BYDAYv )) {
1371 // multi ByDay recur
1372 foreach( $BYDAYv as $BYDAYx2 => $BYDAYv2 ) {
1373 if( Vcalendar::DAY === $BYDAYx2 ) {
1374 continue;
1375 }
1376 else {
1377 return true;
1378 }
1379 } // end foreach
1380 continue;
1381 } // end if
1382 // single ByDay recur
1383 elseif( Vcalendar::DAY === $BYDAYx ) {
1384 continue;
1385 }
1386 else {
1387 return true;
1388 }
1389 } // end foreach
1390 return false;
1391 }
1392
1393 /*
1394 * Return recur BYDAYs for spec. year/month, also '-1MO'-type BYDAYs
1395 *
1396 * @param array $recurByDay
1397 * @param int $year
1398 * @param int $month
1399 * @return array
1400 * @since 2.27.16 - 2019-03-03
1401 */
1402 public static function getRecurByDaysInMonth( array $recurByDay, $year, $month )
1403 {
1404 static $wFmt1 = '%d-%02d-%02d';
1405 static $wFmt2 = '+1 day';
1406 $wDay = 1;
1407 $wDate = new DateTime( sprintf( $wFmt1, $year, $month, $wDay ));
1408 // get days in month
1409 $daysInMonth = (int) $wDate->format( self::$LCT );
1410 $monthDays = [];
1411 $dayPos = [];
1412 // get monthDay as weekDay and occurence pos from start
1413 while( $wDay <= $daysInMonth ) {
1414 $weekDayNo = $wDate->format( self::$LCW );
1415 $dayName = RecurFactory::$DAYNAMES[$weekDayNo];
1416 $monthDays[$wDay] = [ $dayName, 0, 0 ];
1417 $dayPos[$dayName] = isset( $dayPos[$dayName] ) ? ( $dayPos[$dayName] + 1 ) : 1;
1418 $monthDays[$wDay][1] = $dayPos[$dayName];
1419 $wDay += 1;
1420 $wDate->modify( $wFmt2 );
1421 } // end while
1422 // get occurence pos from end
1423 $dayPos = [];
1424 for( $wDay = $daysInMonth; $wDay > 0; $wDay-- ) {
1425 $dayName = $monthDays[$wDay][0];
1426 $dayPos[$dayName] = isset( $dayPos[$dayName] ) ? ( $dayPos[$dayName] - 1 ) : -1;
1427 $monthDays[$wDay][2] = $dayPos[$dayName];
1428 } // end for
1429 $result = [];
1430 $dayN = $pos = false;
1431 foreach( $recurByDay as $BYDAYx => $BYDAYv ) {
1432 if(((int) $BYDAYx === $BYDAYx ) && is_array( $BYDAYv )) {
1433 // multi ByDay recur
1434 foreach( $BYDAYv as $BYDAYx2 => $BYDAYv2 ) {
1435 if( Vcalendar::DAY === $BYDAYx2 ) {
1436 $dayN = $BYDAYv2;
1437 }
1438 else {
1439 $pos = (int) $BYDAYv2;
1440 }
1441 } // end foreach
1442 if( ! empty( $dayN )) {
1443 foreach(self::getMonthDaysFromByDay( $monthDays, $pos, $dayN ) as $day ) {
1444 $result[] = $day;
1445 }
1446 }
1447 $dayN = $pos = false;
1448 continue;
1449 } // end if
1450 // single ByDay recur
1451 elseif( Vcalendar::DAY === $BYDAYx ) {
1452 $dayN = $BYDAYv;
1453 }
1454 else {
1455 $pos = (int) $BYDAYv;
1456 }
1457 } // end foreach
1458 // single ByDay recur
1459 if( ! empty( $dayN )) {
1460 foreach(self::getMonthDaysFromByDay( $monthDays, $pos, $dayN ) as $day ) {
1461 $result[] = $day;
1462 }
1463 }
1464 sort( $result, SORT_NUMERIC );
1465 return $result;
1466 }
1467
1468 /**
1469 * Return (array) dayNo hits, found in monthDays
1470 *
1471 * @param array $monthDays with element dayN, posFromStart, posFromEnd
1472 * @param bool|int $pos
1473 * @param string $dayN weekday name abbr
1474 * @return array dayNo hits in month
1475 */
1476 private static function getMonthDaysFromByDay( array $monthDays, $pos, $dayN ) {
1477 $list = [];
1478 foreach( $monthDays as $dayNo => $dayData ) {
1479 if( $dayN != $dayData[0] ) {
1480 continue;
1481 }
1482 if( empty( $pos )) {
1483 $list[] = $dayNo;
1484 continue;
1485 }
1486 if(( $dayData[1] == $pos ) || // positive, posFromStart
1487 ( $dayData[2] == $pos )) { // negative, posFromEnd
1488 $list[] = $dayNo;
1489 }
1490 } // end foreach
1491 return $list;
1492 }
1493
1494 /*
1495 * Return DateTime
1496 *
1497 * @param DateTime|string $input
1498 * @return DateTime
1499 * @throws Exception
1500 * @throws InvalidArgumentException
1501 * @since 2.27.16 - 2019-03-03
1502 */
1503 private static function dateToDateTime( $input )
1504 {
1505 if( $input instanceof DateTime ) {
1506 return clone $input;
1507 }
1508 list( $dateStr, $timezonePart ) =
1509 DateTimeFactory::splitIntoDateStrAndTimezone( $input );
1510 try {
1511 $output = DateTimeFactory::getDateTimeWithTimezoneFromString(
1512 $dateStr,
1513 $timezonePart,
1514 Vcalendar::UTC
1515 );
1516 }
1517 catch( Exception $e ) {
1518 throw $e;
1519 }
1520 return $output;
1521 }
1522 }
1523