RRule\RRule::__construct PHP Method

__construct() public method

There is no setter after the class has been instanciated, because in order to validate some BYXXX parts, we need to know the value of some other parts (FREQ or other BXXX parts).
public __construct ( mixed $parts )
$parts mixed An assoc array of parts, or a RFC string.
    public function __construct($parts)
    {
        if (is_string($parts)) {
            $parts = self::parseRfcString($parts);
        } elseif (is_array($parts)) {
            $parts = array_change_key_case($parts, CASE_UPPER);
        } else {
            throw new \InvalidArgumentException(sprintf('The first argument must be a string or an array (%s provided)', gettype($parts)));
        }
        // validate extra parts
        $unsupported = array_diff_key($parts, $this->rule);
        if (!empty($unsupported)) {
            throw new \InvalidArgumentException('Unsupported parameter(s): ' . implode(',', array_keys($unsupported)));
        }
        $parts = array_merge($this->rule, $parts);
        $this->rule = $parts;
        // save original rule
        // WKST
        $parts['WKST'] = strtoupper($parts['WKST']);
        if (!array_key_exists($parts['WKST'], self::$week_days)) {
            throw new \InvalidArgumentException('The WKST rule part must be one of the following: ' . implode(', ', array_keys(self::$week_days)));
        }
        $this->wkst = self::$week_days[$parts['WKST']];
        // FREQ
        if (is_integer($parts['FREQ'])) {
            if ($parts['FREQ'] > self::SECONDLY || $parts['FREQ'] < self::YEARLY) {
                throw new \InvalidArgumentException('The FREQ rule part must be one of the following: ' . implode(', ', array_keys(self::$frequencies)));
            }
            $this->freq = $parts['FREQ'];
        } else {
            // string
            $parts['FREQ'] = strtoupper($parts['FREQ']);
            if (!array_key_exists($parts['FREQ'], self::$frequencies)) {
                throw new \InvalidArgumentException('The FREQ rule part must be one of the following: ' . implode(', ', array_keys(self::$frequencies)));
            }
            $this->freq = self::$frequencies[$parts['FREQ']];
        }
        // INTERVAL
        $parts['INTERVAL'] = (int) $parts['INTERVAL'];
        if ($parts['INTERVAL'] < 1) {
            throw new \InvalidArgumentException('The INTERVAL rule part must be a positive integer (> 0)');
        }
        $this->interval = (int) $parts['INTERVAL'];
        // DTSTART
        if (not_empty($parts['DTSTART'])) {
            try {
                $this->dtstart = self::parseDate($parts['DTSTART']);
            } catch (\Exception $e) {
                throw new \InvalidArgumentException('Failed to parse DTSTART ; it must be a valid date, timestamp or \\DateTime object');
            }
        } else {
            $this->dtstart = new \DateTime();
        }
        // UNTIL (optional)
        if (not_empty($parts['UNTIL'])) {
            try {
                $this->until = self::parseDate($parts['UNTIL']);
            } catch (\Exception $e) {
                throw new \InvalidArgumentException('Failed to parse UNTIL ; it must be a valid date, timestamp or \\DateTime object');
            }
        }
        // COUNT (optional)
        if (not_empty($parts['COUNT'])) {
            $parts['COUNT'] = (int) $parts['COUNT'];
            if ($parts['COUNT'] < 1) {
                throw new \InvalidArgumentException('COUNT must be a positive integer (> 0)');
            }
            $this->count = (int) $parts['COUNT'];
        }
        if ($this->until && $this->count) {
            throw new \InvalidArgumentException('The UNTIL or COUNT rule parts MUST NOT occur in the same rule');
        }
        // infer necessary BYXXX rules from DTSTART, if not provided
        if (!(not_empty($parts['BYWEEKNO']) || not_empty($parts['BYYEARDAY']) || not_empty($parts['BYMONTHDAY']) || not_empty($parts['BYDAY']))) {
            switch ($this->freq) {
                case self::YEARLY:
                    if (!not_empty($parts['BYMONTH'])) {
                        $parts['BYMONTH'] = array((int) $this->dtstart->format('m'));
                    }
                    $parts['BYMONTHDAY'] = array((int) $this->dtstart->format('j'));
                    break;
                case self::MONTHLY:
                    $parts['BYMONTHDAY'] = array((int) $this->dtstart->format('j'));
                    break;
                case self::WEEKLY:
                    $parts['BYDAY'] = array(array_search($this->dtstart->format('N'), self::$week_days));
                    break;
            }
        }
        // BYDAY (translated to byweekday for convenience)
        if (not_empty($parts['BYDAY'])) {
            if (!is_array($parts['BYDAY'])) {
                $parts['BYDAY'] = explode(',', $parts['BYDAY']);
            }
            $this->byweekday = array();
            $this->byweekday_nth = array();
            foreach ($parts['BYDAY'] as $value) {
                $value = trim(strtoupper($value));
                $valid = preg_match('/^([+-]?[0-9]+)?([A-Z]{2})$/', $value, $matches);
                if (!$valid || not_empty($matches[1]) && ($matches[1] == 0 || $matches[1] > 53 || $matches[1] < -53) || !array_key_exists($matches[2], self::$week_days)) {
                    throw new \InvalidArgumentException('Invalid BYDAY value: ' . $value);
                }
                if ($matches[1]) {
                    $this->byweekday_nth[] = array(self::$week_days[$matches[2]], (int) $matches[1]);
                } else {
                    $this->byweekday[] = self::$week_days[$matches[2]];
                }
            }
            if (!empty($this->byweekday_nth)) {
                if (!($this->freq === self::MONTHLY || $this->freq === self::YEARLY)) {
                    throw new \InvalidArgumentException('The BYDAY rule part MUST NOT be specified with a numeric value when the FREQ rule part is not set to MONTHLY or YEARLY.');
                }
                if ($this->freq === self::YEARLY && not_empty($parts['BYWEEKNO'])) {
                    throw new \InvalidArgumentException('The BYDAY rule part MUST NOT be specified with a numeric value with the FREQ rule part set to YEARLY when the BYWEEKNO rule part is specified.');
                }
            }
        }
        // The BYMONTHDAY rule part specifies a COMMA-separated list of days
        // of the month.  Valid values are 1 to 31 or -31 to -1.  For
        // example, -10 represents the tenth to the last day of the month.
        // The BYMONTHDAY rule part MUST NOT be specified when the FREQ rule
        // part is set to WEEKLY.
        if (not_empty($parts['BYMONTHDAY'])) {
            if ($this->freq === self::WEEKLY) {
                throw new \InvalidArgumentException('The BYMONTHDAY rule part MUST NOT be specified when the FREQ rule part is set to WEEKLY.');
            }
            if (!is_array($parts['BYMONTHDAY'])) {
                $parts['BYMONTHDAY'] = explode(',', $parts['BYMONTHDAY']);
            }
            $this->bymonthday = array();
            $this->bymonthday_negative = array();
            foreach ($parts['BYMONTHDAY'] as $value) {
                $value = (int) $value;
                if (!$value || $value < -31 || $value > 31) {
                    throw new \InvalidArgumentException('Invalid BYMONTHDAY value: ' . $value . ' (valid values are 1 to 31 or -31 to -1)');
                }
                if ($value < 0) {
                    $this->bymonthday_negative[] = $value;
                } else {
                    $this->bymonthday[] = $value;
                }
            }
        }
        if (not_empty($parts['BYYEARDAY'])) {
            if ($this->freq === self::DAILY || $this->freq === self::WEEKLY || $this->freq === self::MONTHLY) {
                throw new \InvalidArgumentException('The BYYEARDAY rule part MUST NOT be specified when the FREQ rule part is set to DAILY, WEEKLY, or MONTHLY.');
            }
            if (!is_array($parts['BYYEARDAY'])) {
                $parts['BYYEARDAY'] = explode(',', $parts['BYYEARDAY']);
            }
            $this->bysetpos = array();
            foreach ($parts['BYYEARDAY'] as $value) {
                $value = (int) $value;
                if (!$value || $value < -366 || $value > 366) {
                    throw new \InvalidArgumentException('Invalid BYSETPOS value: ' . $value . ' (valid values are 1 to 366 or -366 to -1)');
                }
                $this->byyearday[] = $value;
            }
        }
        // BYWEEKNO
        if (not_empty($parts['BYWEEKNO'])) {
            if ($this->freq !== self::YEARLY) {
                throw new \InvalidArgumentException('The BYWEEKNO rule part MUST NOT be used when the FREQ rule part is set to anything other than YEARLY.');
            }
            if (!is_array($parts['BYWEEKNO'])) {
                $parts['BYWEEKNO'] = explode(',', $parts['BYWEEKNO']);
            }
            $this->byweekno = array();
            foreach ($parts['BYWEEKNO'] as $value) {
                $value = (int) $value;
                if (!$value || $value < -53 || $value > 53) {
                    throw new \InvalidArgumentException('Invalid BYWEEKNO value: ' . $value . ' (valid values are 1 to 53 or -53 to -1)');
                }
                $this->byweekno[] = $value;
            }
        }
        // The BYMONTH rule part specifies a COMMA-separated list of months
        // of the year.  Valid values are 1 to 12.
        if (not_empty($parts['BYMONTH'])) {
            if (!is_array($parts['BYMONTH'])) {
                $parts['BYMONTH'] = explode(',', $parts['BYMONTH']);
            }
            $this->bymonth = array();
            foreach ($parts['BYMONTH'] as $value) {
                $value = (int) $value;
                if ($value < 1 || $value > 12) {
                    throw new \InvalidArgumentException('Invalid BYMONTH value: ' . $value);
                }
                $this->bymonth[] = $value;
            }
        }
        if (not_empty($parts['BYSETPOS'])) {
            if (!(not_empty($parts['BYWEEKNO']) || not_empty($parts['BYYEARDAY']) || not_empty($parts['BYMONTHDAY']) || not_empty($parts['BYDAY']) || not_empty($parts['BYMONTH']) || not_empty($parts['BYHOUR']) || not_empty($parts['BYMINUTE']) || not_empty($parts['BYSECOND']))) {
                throw new \InvalidArgumentException('The BYSETPOS rule part MUST only be used in conjunction with another BYxxx rule part.');
            }
            if (!is_array($parts['BYSETPOS'])) {
                $parts['BYSETPOS'] = explode(',', $parts['BYSETPOS']);
            }
            $this->bysetpos = array();
            foreach ($parts['BYSETPOS'] as $value) {
                $value = (int) $value;
                if (!$value || $value < -366 || $value > 366) {
                    throw new \InvalidArgumentException('Invalid BYSETPOS value: ' . $value . ' (valid values are 1 to 366 or -366 to -1)');
                }
                $this->bysetpos[] = $value;
            }
        }
        if (not_empty($parts['BYHOUR'])) {
            if (!is_array($parts['BYHOUR'])) {
                $parts['BYHOUR'] = explode(',', $parts['BYHOUR']);
            }
            $this->byhour = array();
            foreach ($parts['BYHOUR'] as $value) {
                $value = (int) $value;
                if ($value < 0 || $value > 23) {
                    throw new \InvalidArgumentException('Invalid BYHOUR value: ' . $value);
                }
                $this->byhour[] = $value;
            }
            sort($this->byhour);
        } elseif ($this->freq < self::HOURLY) {
            $this->byhour = array((int) $this->dtstart->format('G'));
        }
        if (not_empty($parts['BYMINUTE'])) {
            if (!is_array($parts['BYMINUTE'])) {
                $parts['BYMINUTE'] = explode(',', $parts['BYMINUTE']);
            }
            $this->byminute = array();
            foreach ($parts['BYMINUTE'] as $value) {
                $value = (int) $value;
                if ($value < 0 || $value > 59) {
                    throw new \InvalidArgumentException('Invalid BYMINUTE value: ' . $value);
                }
                $this->byminute[] = $value;
            }
            sort($this->byminute);
        } elseif ($this->freq < self::MINUTELY) {
            $this->byminute = array((int) $this->dtstart->format('i'));
        }
        if (not_empty($parts['BYSECOND'])) {
            if (!is_array($parts['BYSECOND'])) {
                $parts['BYSECOND'] = explode(',', $parts['BYSECOND']);
            }
            $this->bysecond = array();
            foreach ($parts['BYSECOND'] as $value) {
                $value = (int) $value;
                // yes, "60" is a valid value, in (very rare) cases on leap seconds
                //  December 31, 2005 23:59:60 UTC is a valid date...
                // so is 2012-06-30T23:59:60UTC
                if ($value < 0 || $value > 60) {
                    throw new \InvalidArgumentException('Invalid BYSECOND value: ' . $value);
                }
                $this->bysecond[] = $value;
            }
            sort($this->bysecond);
        } elseif ($this->freq < self::SECONDLY) {
            $this->bysecond = array((int) $this->dtstart->format('s'));
        }
        if ($this->freq < self::HOURLY) {
            // for frequencies DAILY, WEEKLY, MONTHLY AND YEARLY, we can build
            // an array of every time of the day at which there should be an
            // occurrence - default, if no BYHOUR/BYMINUTE/BYSECOND are provided
            // is only one time, and it's the DTSTART time. This is a cached version
            // if you will, since it'll never change at these frequencies
            $this->timeset = array();
            foreach ($this->byhour as $hour) {
                foreach ($this->byminute as $minute) {
                    foreach ($this->bysecond as $second) {
                        $this->timeset[] = array($hour, $minute, $second);
                    }
                }
            }
        }
    }