RRule\RRule::occursAt PHP Method

occursAt() public method

This method will attempt to determine the result programmatically. However depending on the BYXXX rule parts that have been set, it might not always be possible. As a last resort, this method will loop through all occurrences until $date. This will incurr some performance penalty.
public occursAt ( mixed $date ) : boolean
$date mixed
return boolean
    public function occursAt($date)
    {
        $date = self::parseDate($date);
        // convert timezone to dtstart timezone for comparison
        $date->setTimezone($this->dtstart->getTimezone());
        if (in_array($date, $this->cache)) {
            // in the cache (whether cache is complete or not)
            return true;
        } elseif ($this->total !== null) {
            // cache complete and not in cache
            return false;
        }
        // let's start with the obvious
        if ($date < $this->dtstart || $this->until && $date > $this->until) {
            return false;
        }
        // now the BYXXX rules (expect BYSETPOS)
        if ($this->byhour && !in_array($date->format('G'), $this->byhour)) {
            return false;
        }
        if ($this->byminute && !in_array((int) $date->format('i'), $this->byminute)) {
            return false;
        }
        if ($this->bysecond && !in_array((int) $date->format('s'), $this->bysecond)) {
            return false;
        }
        // we need some more variables before we continue
        list($year, $month, $day, $yearday, $weekday) = explode(' ', $date->format('Y n j z N'));
        $masks = array();
        $masks['weekday_of_1st_yearday'] = date_create($year . '-01-01 00:00:00')->format('N');
        $masks['yearday_to_weekday'] = array_slice(self::$WEEKDAY_MASK, $masks['weekday_of_1st_yearday'] - 1);
        if (is_leap_year($year)) {
            $masks['year_len'] = 366;
            $masks['last_day_of_month'] = self::$LAST_DAY_OF_MONTH_366;
        } else {
            $masks['year_len'] = 365;
            $masks['last_day_of_month'] = self::$LAST_DAY_OF_MONTH;
        }
        $month_len = $masks['last_day_of_month'][$month] - $masks['last_day_of_month'][$month - 1];
        if ($this->bymonth && !in_array($month, $this->bymonth)) {
            return false;
        }
        if ($this->bymonthday || $this->bymonthday_negative) {
            $monthday_negative = -1 * ($month_len - $day + 1);
            if (!in_array($day, $this->bymonthday) && !in_array($monthday_negative, $this->bymonthday_negative)) {
                return false;
            }
        }
        if ($this->byyearday) {
            // caution here, yearday starts from 0 !
            $yearday_negative = -1 * ($masks['year_len'] - $yearday);
            if (!in_array($yearday + 1, $this->byyearday) && !in_array($yearday_negative, $this->byyearday)) {
                return false;
            }
        }
        if ($this->byweekday || $this->byweekday_nth) {
            // we need to summon some magic here
            $this->buildNthWeekdayMask($year, $month, $day, $masks);
            if (!in_array($weekday, $this->byweekday) && !isset($masks['yearday_is_nth_weekday'][$yearday])) {
                return false;
            }
        }
        if ($this->byweekno) {
            // more magic
            $this->buildWeeknoMask($year, $month, $day, $masks);
            if (!isset($masks['yearday_is_in_weekno'][$yearday])) {
                return false;
            }
        }
        // so now we have exhausted all the BYXXX rules (exept bysetpos),
        // we still need to consider frequency and interval
        list($start_year, $start_month, $start_day) = explode('-', $this->dtstart->format('Y-m-d'));
        switch ($this->freq) {
            case self::YEARLY:
                if (($year - $start_year) % $this->interval !== 0) {
                    return false;
                }
                break;
            case self::MONTHLY:
                // we need to count the number of months elapsed
                $diff = 12 - $start_month + 12 * ($year - $start_year - 1) + $month;
                if ($diff % $this->interval !== 0) {
                    return false;
                }
                break;
            case self::WEEKLY:
                // count nb of days and divide by 7 to get number of weeks
                // we add some days to align dtstart with wkst
                $diff = $date->diff($this->dtstart);
                $diff = (int) (($diff->days + pymod($this->dtstart->format('N') - $this->wkst, 7)) / 7);
                if ($diff % $this->interval !== 0) {
                    return false;
                }
                break;
            case self::DAILY:
                // count nb of days
                $diff = $date->diff($this->dtstart);
                if ($diff->days % $this->interval !== 0) {
                    return false;
                }
                break;
                // XXX: I'm not sure the 3 formulas below take the DST into account...
            // XXX: I'm not sure the 3 formulas below take the DST into account...
            case self::HOURLY:
                $diff = $date->diff($this->dtstart);
                $diff = $diff->h + $diff->days * 24;
                if ($diff % $this->interval !== 0) {
                    return false;
                }
                break;
            case self::MINUTELY:
                $diff = $date->diff($this->dtstart);
                $diff = $diff->i + $diff->h * 60 + $diff->days * 1440;
                if ($diff % $this->interval !== 0) {
                    return false;
                }
                break;
            case self::SECONDLY:
                $diff = $date->diff($this->dtstart);
                // XXX does not account for leap second (should it?)
                $diff = $diff->s + $diff->i * 60 + $diff->h * 3600 + $diff->days * 86400;
                if ($diff % $this->interval !== 0) {
                    return false;
                }
                break;
                throw new \Exception('Unimplemented frequency');
        }
        // now we are left with 2 rules BYSETPOS and COUNT
        //
        // - I think BYSETPOS *could* be determined without loooping by considering
        // the current set, calculating all the occurrences of the current set
        // and determining the position of $date in the result set.
        // However I'm not convinced it's worth it.
        //
        // - I don't see any way to determine COUNT programmatically, because occurrences
        // might sometimes be dropped (e.g. a 29 Feb on a normal year, or during
        // the switch to DST) and not counted in the final set
        if (!$this->count && !$this->bysetpos) {
            return true;
        }
        // so... as a fallback we have to loop
        foreach ($this as $occurrence) {
            if ($occurrence == $date) {
                return true;
                // lucky you!
            }
            if ($occurrence > $date) {
                break;
            }
        }
        // we ended the loop without finding
        return false;
    }

Usage Example

Esempio n. 1
0
 public function testOccursAtTakeTimezoneIntoAccount()
 {
     $rrule = new RRule(array('freq' => 'daily', 'count' => 365, 'dtstart' => date_create('2015-07-01 09:00:00')));
     $this->assertTrue($rrule->occursAt('2015-07-02 09:00:00'), 'When timezone is not specified, it takes the default timezone');
     $rrule = new RRule(array('freq' => 'daily', 'count' => 365, 'dtstart' => date_create('2015-07-01 09:00:00', new DateTimeZone('Australia/Sydney'))));
     $this->assertTrue($rrule->occursAt(date_create('2015-07-02 09:00:00', new DateTimeZone('Australia/Sydney'))));
     $this->assertTrue($rrule->occursAt(date_create('2015-07-01 23:00:00', new DateTimeZone('UTC'))), 'Timezone is converted for comparison (cached)');
     $rrule->clearCache();
     $this->assertTrue($rrule->occursAt(date_create('2015-07-01 23:00:00', new DateTimeZone('UTC'))), 'Timezone is converted for comparison (uncached)');
     $rrule->clearCache();
     $this->assertFalse($rrule->occursAt('2015-07-02 09:00:00'), 'When passed a string, default timezone is used for creating the DateTime');
     $rrule->clearCache();
     $this->assertTrue($rrule->occursAt('Wed, 01 Jul 2015 09:00:00 +1000'), 'When passed a string with timezone, timezone is kept (uncached)');
     $this->assertTrue($rrule->occursAt('Wed, 01 Jul 2015 09:00:00 +1000'), 'When passed a string with timezone, timezone is kept (cached)');
     $rrule->clearCache();
     $this->assertTrue($rrule->occursAt('2015-07-01T09:00:00+10:00'), 'When passed a string with timezone, timezone is kept (uncached)');
     $this->assertTrue($rrule->occursAt('2015-07-01T09:00:00+10:00'), 'When passed a string with timezone, timezone is kept (cached)');
     // test with DST
     $rrule = new RRule(array('freq' => 'daily', 'count' => 365, 'dtstart' => date_create('2015-07-01 09:00:00', new DateTimeZone('Europe/Helsinki'))));
     $this->assertTrue($rrule->occursAt(date_create('2015-07-02 09:00:00', new DateTimeZone('Europe/Helsinki'))));
     $this->assertTrue($rrule->occursAt(date_create('2015-07-02 06:00:00', new DateTimeZone('UTC'))), 'During summer time, Europe/Helsinki is UTC+3 (cached)');
     $rrule->clearCache();
     $this->assertTrue($rrule->occursAt(date_create('2015-07-02 06:00:00', new DateTimeZone('UTC'))), 'During summer time, Europe/Helsinki is UTC+3 (uncached)');
     $this->assertTrue($rrule->occursAt(date_create('2015-12-02 09:00:00', new DateTimeZone('Europe/Helsinki'))));
     $this->assertTrue($rrule->occursAt(date_create('2015-12-02 07:00:00', new DateTimeZone('UTC'))), 'During winter time, Europe/Helsinki is UTC+2 (cached)');
     $rrule->clearCache();
     $this->assertTrue($rrule->occursAt(date_create('2015-12-02 07:00:00', new DateTimeZone('UTC'))), 'During winter time, Europe/Helsinki is UTC+2 (uncached)');
 }
All Usage Examples Of RRule\RRule::occursAt