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