Mercurial > hg > rc1
comparison vendor/sabre/vobject/lib/Recur/RRuleIterator.php @ 7:430dbd5346f7
vendor sabre as distributed
author | Charlie Root |
---|---|
date | Sat, 13 Jan 2018 09:06:10 -0500 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
6:cec75ba50afc | 7:430dbd5346f7 |
---|---|
1 <?php | |
2 | |
3 namespace Sabre\VObject\Recur; | |
4 | |
5 use DateTime; | |
6 use InvalidArgumentException; | |
7 use Iterator; | |
8 use Sabre\VObject\DateTimeParser; | |
9 use Sabre\VObject\Property; | |
10 | |
11 | |
12 /** | |
13 * RRuleParser | |
14 * | |
15 * This class receives an RRULE string, and allows you to iterate to get a list | |
16 * of dates in that recurrence. | |
17 * | |
18 * For instance, passing: FREQ=DAILY;LIMIT=5 will cause the iterator to contain | |
19 * 5 items, one for each day. | |
20 * | |
21 * @copyright Copyright (C) 2011-2015 fruux GmbH (https://fruux.com/). | |
22 * @author Evert Pot (http://evertpot.com/) | |
23 * @license http://sabre.io/license/ Modified BSD License | |
24 */ | |
25 class RRuleIterator implements Iterator { | |
26 | |
27 /** | |
28 * Creates the Iterator | |
29 * | |
30 * @param string|array $rrule | |
31 * @param DateTime $start | |
32 */ | |
33 public function __construct($rrule, DateTime $start) { | |
34 | |
35 $this->startDate = $start; | |
36 $this->parseRRule($rrule); | |
37 $this->currentDate = clone $this->startDate; | |
38 | |
39 } | |
40 | |
41 /* Implementation of the Iterator interface {{{ */ | |
42 | |
43 public function current() { | |
44 | |
45 if (!$this->valid()) return null; | |
46 return clone $this->currentDate; | |
47 | |
48 } | |
49 | |
50 /** | |
51 * Returns the current item number | |
52 * | |
53 * @return int | |
54 */ | |
55 public function key() { | |
56 | |
57 return $this->counter; | |
58 | |
59 } | |
60 | |
61 /** | |
62 * Returns whether the current item is a valid item for the recurrence | |
63 * iterator. This will return false if we've gone beyond the UNTIL or COUNT | |
64 * statements. | |
65 * | |
66 * @return bool | |
67 */ | |
68 public function valid() { | |
69 | |
70 if (!is_null($this->count)) { | |
71 return $this->counter < $this->count; | |
72 } | |
73 return is_null($this->until) || $this->currentDate <= $this->until; | |
74 | |
75 } | |
76 | |
77 /** | |
78 * Resets the iterator | |
79 * | |
80 * @return void | |
81 */ | |
82 public function rewind() { | |
83 | |
84 $this->currentDate = clone $this->startDate; | |
85 $this->counter = 0; | |
86 | |
87 } | |
88 | |
89 /** | |
90 * Goes on to the next iteration | |
91 * | |
92 * @return void | |
93 */ | |
94 public function next() { | |
95 | |
96 $previousStamp = $this->currentDate->getTimeStamp(); | |
97 | |
98 // Otherwise, we find the next event in the normal RRULE | |
99 // sequence. | |
100 switch($this->frequency) { | |
101 | |
102 case 'hourly' : | |
103 $this->nextHourly(); | |
104 break; | |
105 | |
106 case 'daily' : | |
107 $this->nextDaily(); | |
108 break; | |
109 | |
110 case 'weekly' : | |
111 $this->nextWeekly(); | |
112 break; | |
113 | |
114 case 'monthly' : | |
115 $this->nextMonthly(); | |
116 break; | |
117 | |
118 case 'yearly' : | |
119 $this->nextYearly(); | |
120 break; | |
121 | |
122 } | |
123 $this->counter++; | |
124 | |
125 } | |
126 | |
127 /* End of Iterator implementation }}} */ | |
128 | |
129 /** | |
130 * Returns true if this recurring event never ends. | |
131 * | |
132 * @return bool | |
133 */ | |
134 public function isInfinite() { | |
135 | |
136 return !$this->count && !$this->until; | |
137 | |
138 } | |
139 | |
140 /** | |
141 * This method allows you to quickly go to the next occurrence after the | |
142 * specified date. | |
143 * | |
144 * @param DateTime $dt | |
145 * @return void | |
146 */ | |
147 public function fastForward(\DateTime $dt) { | |
148 | |
149 while($this->valid() && $this->currentDate < $dt ) { | |
150 $this->next(); | |
151 } | |
152 | |
153 } | |
154 | |
155 /** | |
156 * The reference start date/time for the rrule. | |
157 * | |
158 * All calculations are based on this initial date. | |
159 * | |
160 * @var DateTime | |
161 */ | |
162 protected $startDate; | |
163 | |
164 /** | |
165 * The date of the current iteration. You can get this by calling | |
166 * ->current(). | |
167 * | |
168 * @var DateTime | |
169 */ | |
170 protected $currentDate; | |
171 | |
172 /** | |
173 * Frequency is one of: secondly, minutely, hourly, daily, weekly, monthly, | |
174 * yearly. | |
175 * | |
176 * @var string | |
177 */ | |
178 protected $frequency; | |
179 | |
180 /** | |
181 * The number of recurrences, or 'null' if infinitely recurring. | |
182 * | |
183 * @var int | |
184 */ | |
185 protected $count; | |
186 | |
187 /** | |
188 * The interval. | |
189 * | |
190 * If for example frequency is set to daily, interval = 2 would mean every | |
191 * 2 days. | |
192 * | |
193 * @var int | |
194 */ | |
195 protected $interval = 1; | |
196 | |
197 /** | |
198 * The last instance of this recurrence, inclusively | |
199 * | |
200 * @var \DateTime|null | |
201 */ | |
202 protected $until; | |
203 | |
204 /** | |
205 * Which seconds to recur. | |
206 * | |
207 * This is an array of integers (between 0 and 60) | |
208 * | |
209 * @var array | |
210 */ | |
211 protected $bySecond; | |
212 | |
213 /** | |
214 * Which minutes to recur | |
215 * | |
216 * This is an array of integers (between 0 and 59) | |
217 * | |
218 * @var array | |
219 */ | |
220 protected $byMinute; | |
221 | |
222 /** | |
223 * Which hours to recur | |
224 * | |
225 * This is an array of integers (between 0 and 23) | |
226 * | |
227 * @var array | |
228 */ | |
229 protected $byHour; | |
230 | |
231 /** | |
232 * The current item in the list. | |
233 * | |
234 * You can get this number with the key() method. | |
235 * | |
236 * @var int | |
237 */ | |
238 protected $counter = 0; | |
239 | |
240 /** | |
241 * Which weekdays to recur. | |
242 * | |
243 * This is an array of weekdays | |
244 * | |
245 * This may also be preceeded by a positive or negative integer. If present, | |
246 * this indicates the nth occurrence of a specific day within the monthly or | |
247 * yearly rrule. For instance, -2TU indicates the second-last tuesday of | |
248 * the month, or year. | |
249 * | |
250 * @var array | |
251 */ | |
252 protected $byDay; | |
253 | |
254 /** | |
255 * Which days of the month to recur | |
256 * | |
257 * This is an array of days of the months (1-31). The value can also be | |
258 * negative. -5 for instance means the 5th last day of the month. | |
259 * | |
260 * @var array | |
261 */ | |
262 protected $byMonthDay; | |
263 | |
264 /** | |
265 * Which days of the year to recur. | |
266 * | |
267 * This is an array with days of the year (1 to 366). The values can also | |
268 * be negative. For instance, -1 will always represent the last day of the | |
269 * year. (December 31st). | |
270 * | |
271 * @var array | |
272 */ | |
273 protected $byYearDay; | |
274 | |
275 /** | |
276 * Which week numbers to recur. | |
277 * | |
278 * This is an array of integers from 1 to 53. The values can also be | |
279 * negative. -1 will always refer to the last week of the year. | |
280 * | |
281 * @var array | |
282 */ | |
283 protected $byWeekNo; | |
284 | |
285 /** | |
286 * Which months to recur. | |
287 * | |
288 * This is an array of integers from 1 to 12. | |
289 * | |
290 * @var array | |
291 */ | |
292 protected $byMonth; | |
293 | |
294 /** | |
295 * Which items in an existing st to recur. | |
296 * | |
297 * These numbers work together with an existing by* rule. It specifies | |
298 * exactly which items of the existing by-rule to filter. | |
299 * | |
300 * Valid values are 1 to 366 and -1 to -366. As an example, this can be | |
301 * used to recur the last workday of the month. | |
302 * | |
303 * This would be done by setting frequency to 'monthly', byDay to | |
304 * 'MO,TU,WE,TH,FR' and bySetPos to -1. | |
305 * | |
306 * @var array | |
307 */ | |
308 protected $bySetPos; | |
309 | |
310 /** | |
311 * When the week starts. | |
312 * | |
313 * @var string | |
314 */ | |
315 protected $weekStart = 'MO'; | |
316 | |
317 /* Functions that advance the iterator {{{ */ | |
318 | |
319 /** | |
320 * Does the processing for advancing the iterator for hourly frequency. | |
321 * | |
322 * @return void | |
323 */ | |
324 protected function nextHourly() { | |
325 | |
326 $this->currentDate->modify('+' . $this->interval . ' hours'); | |
327 | |
328 } | |
329 | |
330 /** | |
331 * Does the processing for advancing the iterator for daily frequency. | |
332 * | |
333 * @return void | |
334 */ | |
335 protected function nextDaily() { | |
336 | |
337 if (!$this->byHour && !$this->byDay) { | |
338 $this->currentDate->modify('+' . $this->interval . ' days'); | |
339 return; | |
340 } | |
341 | |
342 if (isset($this->byHour)) { | |
343 $recurrenceHours = $this->getHours(); | |
344 } | |
345 | |
346 if (isset($this->byDay)) { | |
347 $recurrenceDays = $this->getDays(); | |
348 } | |
349 | |
350 if (isset($this->byMonth)) { | |
351 $recurrenceMonths = $this->getMonths(); | |
352 } | |
353 | |
354 do { | |
355 if ($this->byHour) { | |
356 if ($this->currentDate->format('G') == '23') { | |
357 // to obey the interval rule | |
358 $this->currentDate->modify('+' . $this->interval-1 . ' days'); | |
359 } | |
360 | |
361 $this->currentDate->modify('+1 hours'); | |
362 | |
363 } else { | |
364 $this->currentDate->modify('+' . $this->interval . ' days'); | |
365 | |
366 } | |
367 | |
368 // Current month of the year | |
369 $currentMonth = $this->currentDate->format('n'); | |
370 | |
371 // Current day of the week | |
372 $currentDay = $this->currentDate->format('w'); | |
373 | |
374 // Current hour of the day | |
375 $currentHour = $this->currentDate->format('G'); | |
376 | |
377 } while ( | |
378 ($this->byDay && !in_array($currentDay, $recurrenceDays)) || | |
379 ($this->byHour && !in_array($currentHour, $recurrenceHours)) || | |
380 ($this->byMonth && !in_array($currentMonth, $recurrenceMonths)) | |
381 ); | |
382 | |
383 } | |
384 | |
385 /** | |
386 * Does the processing for advancing the iterator for weekly frequency. | |
387 * | |
388 * @return void | |
389 */ | |
390 protected function nextWeekly() { | |
391 | |
392 if (!$this->byHour && !$this->byDay) { | |
393 $this->currentDate->modify('+' . $this->interval . ' weeks'); | |
394 return; | |
395 } | |
396 | |
397 if ($this->byHour) { | |
398 $recurrenceHours = $this->getHours(); | |
399 } | |
400 | |
401 if ($this->byDay) { | |
402 $recurrenceDays = $this->getDays(); | |
403 } | |
404 | |
405 // First day of the week: | |
406 $firstDay = $this->dayMap[$this->weekStart]; | |
407 | |
408 do { | |
409 | |
410 if ($this->byHour) { | |
411 $this->currentDate->modify('+1 hours'); | |
412 } else { | |
413 $this->currentDate->modify('+1 days'); | |
414 } | |
415 | |
416 // Current day of the week | |
417 $currentDay = (int) $this->currentDate->format('w'); | |
418 | |
419 // Current hour of the day | |
420 $currentHour = (int) $this->currentDate->format('G'); | |
421 | |
422 // We need to roll over to the next week | |
423 if ($currentDay === $firstDay && (!$this->byHour || $currentHour == '0')) { | |
424 $this->currentDate->modify('+' . $this->interval-1 . ' weeks'); | |
425 | |
426 // We need to go to the first day of this week, but only if we | |
427 // are not already on this first day of this week. | |
428 if($this->currentDate->format('w') != $firstDay) { | |
429 $this->currentDate->modify('last ' . $this->dayNames[$this->dayMap[$this->weekStart]]); | |
430 } | |
431 } | |
432 | |
433 // We have a match | |
434 } while (($this->byDay && !in_array($currentDay, $recurrenceDays)) || ($this->byHour && !in_array($currentHour, $recurrenceHours))); | |
435 } | |
436 | |
437 /** | |
438 * Does the processing for advancing the iterator for monthly frequency. | |
439 * | |
440 * @return void | |
441 */ | |
442 protected function nextMonthly() { | |
443 | |
444 $currentDayOfMonth = $this->currentDate->format('j'); | |
445 if (!$this->byMonthDay && !$this->byDay) { | |
446 | |
447 // If the current day is higher than the 28th, rollover can | |
448 // occur to the next month. We Must skip these invalid | |
449 // entries. | |
450 if ($currentDayOfMonth < 29) { | |
451 $this->currentDate->modify('+' . $this->interval . ' months'); | |
452 } else { | |
453 $increase = 0; | |
454 do { | |
455 $increase++; | |
456 $tempDate = clone $this->currentDate; | |
457 $tempDate->modify('+ ' . ($this->interval*$increase) . ' months'); | |
458 } while ($tempDate->format('j') != $currentDayOfMonth); | |
459 $this->currentDate = $tempDate; | |
460 } | |
461 return; | |
462 } | |
463 | |
464 while(true) { | |
465 | |
466 $occurrences = $this->getMonthlyOccurrences(); | |
467 | |
468 foreach($occurrences as $occurrence) { | |
469 | |
470 // The first occurrence thats higher than the current | |
471 // day of the month wins. | |
472 if ($occurrence > $currentDayOfMonth) { | |
473 break 2; | |
474 } | |
475 | |
476 } | |
477 | |
478 // If we made it all the way here, it means there were no | |
479 // valid occurrences, and we need to advance to the next | |
480 // month. | |
481 // | |
482 // This line does not currently work in hhvm. Temporary workaround | |
483 // follows: | |
484 // $this->currentDate->modify('first day of this month'); | |
485 $this->currentDate = new \DateTime($this->currentDate->format('Y-m-1 H:i:s'), $this->currentDate->getTimezone()); | |
486 // end of workaround | |
487 $this->currentDate->modify('+ ' . $this->interval . ' months'); | |
488 | |
489 // This goes to 0 because we need to start counting at the | |
490 // beginning. | |
491 $currentDayOfMonth = 0; | |
492 | |
493 } | |
494 | |
495 $this->currentDate->setDate($this->currentDate->format('Y'), $this->currentDate->format('n'), $occurrence); | |
496 | |
497 } | |
498 | |
499 /** | |
500 * Does the processing for advancing the iterator for yearly frequency. | |
501 * | |
502 * @return void | |
503 */ | |
504 protected function nextYearly() { | |
505 | |
506 $currentMonth = $this->currentDate->format('n'); | |
507 $currentYear = $this->currentDate->format('Y'); | |
508 $currentDayOfMonth = $this->currentDate->format('j'); | |
509 | |
510 // No sub-rules, so we just advance by year | |
511 if (!$this->byMonth) { | |
512 | |
513 // Unless it was a leap day! | |
514 if ($currentMonth==2 && $currentDayOfMonth==29) { | |
515 | |
516 $counter = 0; | |
517 do { | |
518 $counter++; | |
519 // Here we increase the year count by the interval, until | |
520 // we hit a date that's also in a leap year. | |
521 // | |
522 // We could just find the next interval that's dividable by | |
523 // 4, but that would ignore the rule that there's no leap | |
524 // year every year that's dividable by a 100, but not by | |
525 // 400. (1800, 1900, 2100). So we just rely on the datetime | |
526 // functions instead. | |
527 $nextDate = clone $this->currentDate; | |
528 $nextDate->modify('+ ' . ($this->interval*$counter) . ' years'); | |
529 } while ($nextDate->format('n')!=2); | |
530 $this->currentDate = $nextDate; | |
531 | |
532 return; | |
533 | |
534 } | |
535 | |
536 // The easiest form | |
537 $this->currentDate->modify('+' . $this->interval . ' years'); | |
538 return; | |
539 | |
540 } | |
541 | |
542 $currentMonth = $this->currentDate->format('n'); | |
543 $currentYear = $this->currentDate->format('Y'); | |
544 $currentDayOfMonth = $this->currentDate->format('j'); | |
545 | |
546 $advancedToNewMonth = false; | |
547 | |
548 // If we got a byDay or getMonthDay filter, we must first expand | |
549 // further. | |
550 if ($this->byDay || $this->byMonthDay) { | |
551 | |
552 while(true) { | |
553 | |
554 $occurrences = $this->getMonthlyOccurrences(); | |
555 | |
556 foreach($occurrences as $occurrence) { | |
557 | |
558 // The first occurrence that's higher than the current | |
559 // day of the month wins. | |
560 // If we advanced to the next month or year, the first | |
561 // occurrence is always correct. | |
562 if ($occurrence > $currentDayOfMonth || $advancedToNewMonth) { | |
563 break 2; | |
564 } | |
565 | |
566 } | |
567 | |
568 // If we made it here, it means we need to advance to | |
569 // the next month or year. | |
570 $currentDayOfMonth = 1; | |
571 $advancedToNewMonth = true; | |
572 do { | |
573 | |
574 $currentMonth++; | |
575 if ($currentMonth>12) { | |
576 $currentYear+=$this->interval; | |
577 $currentMonth = 1; | |
578 } | |
579 } while (!in_array($currentMonth, $this->byMonth)); | |
580 | |
581 $this->currentDate->setDate($currentYear, $currentMonth, $currentDayOfMonth); | |
582 | |
583 } | |
584 | |
585 // If we made it here, it means we got a valid occurrence | |
586 $this->currentDate->setDate($currentYear, $currentMonth, $occurrence); | |
587 return; | |
588 | |
589 } else { | |
590 | |
591 // These are the 'byMonth' rules, if there are no byDay or | |
592 // byMonthDay sub-rules. | |
593 do { | |
594 | |
595 $currentMonth++; | |
596 if ($currentMonth>12) { | |
597 $currentYear+=$this->interval; | |
598 $currentMonth = 1; | |
599 } | |
600 } while (!in_array($currentMonth, $this->byMonth)); | |
601 $this->currentDate->setDate($currentYear, $currentMonth, $currentDayOfMonth); | |
602 | |
603 return; | |
604 | |
605 } | |
606 | |
607 } | |
608 | |
609 /* }}} */ | |
610 | |
611 /** | |
612 * This method receives a string from an RRULE property, and populates this | |
613 * class with all the values. | |
614 * | |
615 * @param string|array $rrule | |
616 * @return void | |
617 */ | |
618 protected function parseRRule($rrule) { | |
619 | |
620 if (is_string($rrule)) { | |
621 $rrule = Property\ICalendar\Recur::stringToArray($rrule); | |
622 } | |
623 | |
624 foreach($rrule as $key=>$value) { | |
625 | |
626 $key = strtoupper($key); | |
627 switch($key) { | |
628 | |
629 case 'FREQ' : | |
630 $value = strtolower($value); | |
631 if (!in_array( | |
632 $value, | |
633 array('secondly','minutely','hourly','daily','weekly','monthly','yearly') | |
634 )) { | |
635 throw new InvalidArgumentException('Unknown value for FREQ=' . strtoupper($value)); | |
636 } | |
637 $this->frequency = $value; | |
638 break; | |
639 | |
640 case 'UNTIL' : | |
641 $this->until = DateTimeParser::parse($value, $this->startDate->getTimezone()); | |
642 | |
643 // In some cases events are generated with an UNTIL= | |
644 // parameter before the actual start of the event. | |
645 // | |
646 // Not sure why this is happening. We assume that the | |
647 // intention was that the event only recurs once. | |
648 // | |
649 // So we are modifying the parameter so our code doesn't | |
650 // break. | |
651 if($this->until < $this->startDate) { | |
652 $this->until = $this->startDate; | |
653 } | |
654 break; | |
655 | |
656 case 'INTERVAL' : | |
657 // No break | |
658 | |
659 case 'COUNT' : | |
660 $val = (int)$value; | |
661 if ($val < 1) { | |
662 throw new \InvalidArgumentException(strtoupper($key) . ' in RRULE must be a positive integer!'); | |
663 } | |
664 $key = strtolower($key); | |
665 $this->$key = $val; | |
666 break; | |
667 | |
668 case 'BYSECOND' : | |
669 $this->bySecond = (array)$value; | |
670 break; | |
671 | |
672 case 'BYMINUTE' : | |
673 $this->byMinute = (array)$value; | |
674 break; | |
675 | |
676 case 'BYHOUR' : | |
677 $this->byHour = (array)$value; | |
678 break; | |
679 | |
680 case 'BYDAY' : | |
681 $value = (array)$value; | |
682 foreach($value as $part) { | |
683 if (!preg_match('#^ (-|\+)? ([1-5])? (MO|TU|WE|TH|FR|SA|SU) $# xi', $part)) { | |
684 throw new \InvalidArgumentException('Invalid part in BYDAY clause: ' . $part); | |
685 } | |
686 } | |
687 $this->byDay = $value; | |
688 break; | |
689 | |
690 case 'BYMONTHDAY' : | |
691 $this->byMonthDay = (array)$value; | |
692 break; | |
693 | |
694 case 'BYYEARDAY' : | |
695 $this->byYearDay = (array)$value; | |
696 break; | |
697 | |
698 case 'BYWEEKNO' : | |
699 $this->byWeekNo = (array)$value; | |
700 break; | |
701 | |
702 case 'BYMONTH' : | |
703 $this->byMonth = (array)$value; | |
704 break; | |
705 | |
706 case 'BYSETPOS' : | |
707 $this->bySetPos = (array)$value; | |
708 break; | |
709 | |
710 case 'WKST' : | |
711 $this->weekStart = strtoupper($value); | |
712 break; | |
713 | |
714 default: | |
715 throw new \InvalidArgumentException('Not supported: ' . strtoupper($key)); | |
716 | |
717 } | |
718 | |
719 } | |
720 | |
721 } | |
722 | |
723 /** | |
724 * Mappings between the day number and english day name. | |
725 * | |
726 * @var array | |
727 */ | |
728 protected $dayNames = array( | |
729 0 => 'Sunday', | |
730 1 => 'Monday', | |
731 2 => 'Tuesday', | |
732 3 => 'Wednesday', | |
733 4 => 'Thursday', | |
734 5 => 'Friday', | |
735 6 => 'Saturday', | |
736 ); | |
737 | |
738 /** | |
739 * Returns all the occurrences for a monthly frequency with a 'byDay' or | |
740 * 'byMonthDay' expansion for the current month. | |
741 * | |
742 * The returned list is an array of integers with the day of month (1-31). | |
743 * | |
744 * @return array | |
745 */ | |
746 protected function getMonthlyOccurrences() { | |
747 | |
748 $startDate = clone $this->currentDate; | |
749 | |
750 $byDayResults = array(); | |
751 | |
752 // Our strategy is to simply go through the byDays, advance the date to | |
753 // that point and add it to the results. | |
754 if ($this->byDay) foreach($this->byDay as $day) { | |
755 | |
756 $dayName = $this->dayNames[$this->dayMap[substr($day,-2)]]; | |
757 | |
758 | |
759 // Dayname will be something like 'wednesday'. Now we need to find | |
760 // all wednesdays in this month. | |
761 $dayHits = array(); | |
762 | |
763 // workaround for missing 'first day of the month' support in hhvm | |
764 $checkDate = new \DateTime($startDate->format('Y-m-1')); | |
765 // workaround modify always advancing the date even if the current day is a $dayName in hhvm | |
766 if ($checkDate->format('l') !== $dayName) { | |
767 $checkDate->modify($dayName); | |
768 } | |
769 | |
770 do { | |
771 $dayHits[] = $checkDate->format('j'); | |
772 $checkDate->modify('next ' . $dayName); | |
773 } while ($checkDate->format('n') === $startDate->format('n')); | |
774 | |
775 // So now we have 'all wednesdays' for month. It is however | |
776 // possible that the user only really wanted the 1st, 2nd or last | |
777 // wednesday. | |
778 if (strlen($day)>2) { | |
779 $offset = (int)substr($day,0,-2); | |
780 | |
781 if ($offset>0) { | |
782 // It is possible that the day does not exist, such as a | |
783 // 5th or 6th wednesday of the month. | |
784 if (isset($dayHits[$offset-1])) { | |
785 $byDayResults[] = $dayHits[$offset-1]; | |
786 } | |
787 } else { | |
788 | |
789 // if it was negative we count from the end of the array | |
790 $byDayResults[] = $dayHits[count($dayHits) + $offset]; | |
791 } | |
792 } else { | |
793 // There was no counter (first, second, last wednesdays), so we | |
794 // just need to add the all to the list). | |
795 $byDayResults = array_merge($byDayResults, $dayHits); | |
796 | |
797 } | |
798 | |
799 } | |
800 | |
801 $byMonthDayResults = array(); | |
802 if ($this->byMonthDay) foreach($this->byMonthDay as $monthDay) { | |
803 | |
804 // Removing values that are out of range for this month | |
805 if ($monthDay > $startDate->format('t') || | |
806 $monthDay < 0-$startDate->format('t')) { | |
807 continue; | |
808 } | |
809 if ($monthDay>0) { | |
810 $byMonthDayResults[] = $monthDay; | |
811 } else { | |
812 // Negative values | |
813 $byMonthDayResults[] = $startDate->format('t') + 1 + $monthDay; | |
814 } | |
815 } | |
816 | |
817 // If there was just byDay or just byMonthDay, they just specify our | |
818 // (almost) final list. If both were provided, then byDay limits the | |
819 // list. | |
820 if ($this->byMonthDay && $this->byDay) { | |
821 $result = array_intersect($byMonthDayResults, $byDayResults); | |
822 } elseif ($this->byMonthDay) { | |
823 $result = $byMonthDayResults; | |
824 } else { | |
825 $result = $byDayResults; | |
826 } | |
827 $result = array_unique($result); | |
828 sort($result, SORT_NUMERIC); | |
829 | |
830 // The last thing that needs checking is the BYSETPOS. If it's set, it | |
831 // means only certain items in the set survive the filter. | |
832 if (!$this->bySetPos) { | |
833 return $result; | |
834 } | |
835 | |
836 $filteredResult = array(); | |
837 foreach($this->bySetPos as $setPos) { | |
838 | |
839 if ($setPos<0) { | |
840 $setPos = count($result)-($setPos+1); | |
841 } | |
842 if (isset($result[$setPos-1])) { | |
843 $filteredResult[] = $result[$setPos-1]; | |
844 } | |
845 } | |
846 | |
847 sort($filteredResult, SORT_NUMERIC); | |
848 return $filteredResult; | |
849 | |
850 } | |
851 | |
852 /** | |
853 * Simple mapping from iCalendar day names to day numbers | |
854 * | |
855 * @var array | |
856 */ | |
857 protected $dayMap = array( | |
858 'SU' => 0, | |
859 'MO' => 1, | |
860 'TU' => 2, | |
861 'WE' => 3, | |
862 'TH' => 4, | |
863 'FR' => 5, | |
864 'SA' => 6, | |
865 ); | |
866 | |
867 protected function getHours() | |
868 { | |
869 $recurrenceHours = array(); | |
870 foreach($this->byHour as $byHour) { | |
871 $recurrenceHours[] = $byHour; | |
872 } | |
873 | |
874 return $recurrenceHours; | |
875 } | |
876 | |
877 protected function getDays() { | |
878 | |
879 $recurrenceDays = array(); | |
880 foreach($this->byDay as $byDay) { | |
881 | |
882 // The day may be preceeded with a positive (+n) or | |
883 // negative (-n) integer. However, this does not make | |
884 // sense in 'weekly' so we ignore it here. | |
885 $recurrenceDays[] = $this->dayMap[substr($byDay,-2)]; | |
886 | |
887 } | |
888 | |
889 return $recurrenceDays; | |
890 } | |
891 | |
892 protected function getMonths() { | |
893 | |
894 $recurrenceMonths = array(); | |
895 foreach($this->byMonth as $byMonth) { | |
896 $recurrenceMonths[] = $byMonth; | |
897 } | |
898 | |
899 return $recurrenceMonths; | |
900 } | |
901 } |