1
0
mirror of https://github.com/rlanvin/php-rrule.git synced 2025-02-20 09:54:16 +01:00

Remove space inside parenthesis to be closer to PSR-2

This commit is contained in:
rlanvin 2019-01-14 13:09:43 +00:00
parent d7046ee3ce
commit a5bda44f59
7 changed files with 450 additions and 448 deletions

View File

@ -57,6 +57,8 @@ You will also find useful information in the [RFC 5545 section 3.3.10](https://t
Feel free to contribute! Just create a new issue or a new pull request. Feel free to contribute! Just create a new issue or a new pull request.
The coding style is (mostly) PSR-2, but with tabs.
## Note ## Note
I started this library because I wasn't happy with the existing implementations I started this library because I wasn't happy with the existing implementations

View File

@ -59,13 +59,13 @@ function pymod($a, $b)
*/ */
function is_leap_year($year) function is_leap_year($year)
{ {
if ( $year % 4 !== 0 ) { if ($year % 4 !== 0) {
return false; return false;
} }
if ( $year % 100 !== 0 ) { if ($year % 100 !== 0) {
return true; return true;
} }
if ( $year % 400 !== 0 ) { if ($year % 400 !== 0) {
return false; return false;
} }
return true; return true;
@ -197,15 +197,15 @@ class RRule implements RRuleInterface
*/ */
public function __construct($parts, $dtstart = null) public function __construct($parts, $dtstart = null)
{ {
if ( is_string($parts) ) { if (is_string($parts)) {
$parts = RfcParser::parseRRule($parts, $dtstart); $parts = RfcParser::parseRRule($parts, $dtstart);
$parts = array_change_key_case($parts, CASE_UPPER); $parts = array_change_key_case($parts, CASE_UPPER);
} }
else { else {
if ( $dtstart ) { if ($dtstart) {
throw new \InvalidArgumentException('$dtstart argument has no effect if not constructing from a string'); throw new \InvalidArgumentException('$dtstart argument has no effect if not constructing from a string');
} }
if ( is_array($parts) ) { if (is_array($parts)) {
$parts = array_change_key_case($parts, CASE_UPPER); $parts = array_change_key_case($parts, CASE_UPPER);
} }
else { else {
@ -218,7 +218,7 @@ class RRule implements RRuleInterface
// validate extra parts // validate extra parts
$unsupported = array_diff_key($parts, $this->rule); $unsupported = array_diff_key($parts, $this->rule);
if ( ! empty($unsupported) ) { if (! empty($unsupported)) {
throw new \InvalidArgumentException( throw new \InvalidArgumentException(
'Unsupported parameter(s): ' 'Unsupported parameter(s): '
.implode(',',array_keys($unsupported)) .implode(',',array_keys($unsupported))
@ -230,7 +230,7 @@ class RRule implements RRuleInterface
// WKST // WKST
$parts['WKST'] = strtoupper($parts['WKST']); $parts['WKST'] = strtoupper($parts['WKST']);
if ( ! array_key_exists($parts['WKST'], self::$week_days) ) { if (! array_key_exists($parts['WKST'], self::$week_days)) {
throw new \InvalidArgumentException( throw new \InvalidArgumentException(
'The WKST rule part must be one of the following: ' 'The WKST rule part must be one of the following: '
.implode(', ',array_keys(self::$week_days)) .implode(', ',array_keys(self::$week_days))
@ -239,8 +239,8 @@ class RRule implements RRuleInterface
$this->wkst = self::$week_days[$parts['WKST']]; $this->wkst = self::$week_days[$parts['WKST']];
// FREQ // FREQ
if ( is_integer($parts['FREQ']) ) { if (is_integer($parts['FREQ'])) {
if ( $parts['FREQ'] > self::SECONDLY || $parts['FREQ'] < self::YEARLY ) { if ($parts['FREQ'] > self::SECONDLY || $parts['FREQ'] < self::YEARLY) {
throw new \InvalidArgumentException( throw new \InvalidArgumentException(
'The FREQ rule part must be one of the following: ' 'The FREQ rule part must be one of the following: '
.implode(', ',array_keys(self::$frequencies)) .implode(', ',array_keys(self::$frequencies))
@ -250,7 +250,7 @@ class RRule implements RRuleInterface
} }
else { // string else { // string
$parts['FREQ'] = strtoupper($parts['FREQ']); $parts['FREQ'] = strtoupper($parts['FREQ']);
if ( ! array_key_exists($parts['FREQ'], self::$frequencies) ) { if (! array_key_exists($parts['FREQ'], self::$frequencies)) {
throw new \InvalidArgumentException( throw new \InvalidArgumentException(
'The FREQ rule part must be one of the following: ' 'The FREQ rule part must be one of the following: '
.implode(', ',array_keys(self::$frequencies)) .implode(', ',array_keys(self::$frequencies))
@ -260,7 +260,7 @@ class RRule implements RRuleInterface
} }
// INTERVAL // INTERVAL
if ( filter_var($parts['INTERVAL'], FILTER_VALIDATE_INT, array('options' => array('min_range' => 1))) === false ) { if (filter_var($parts['INTERVAL'], FILTER_VALIDATE_INT, array('options' => array('min_range' => 1))) === false) {
throw new \InvalidArgumentException( throw new \InvalidArgumentException(
'The INTERVAL rule part must be a positive integer (> 0)' 'The INTERVAL rule part must be a positive integer (> 0)'
); );
@ -268,7 +268,7 @@ class RRule implements RRuleInterface
$this->interval = (int) $parts['INTERVAL']; $this->interval = (int) $parts['INTERVAL'];
// DTSTART // DTSTART
if ( not_empty($parts['DTSTART']) ) { if (not_empty($parts['DTSTART'])) {
try { try {
$this->dtstart = self::parseDate($parts['DTSTART']); $this->dtstart = self::parseDate($parts['DTSTART']);
} catch (\Exception $e) { } catch (\Exception $e) {
@ -279,7 +279,7 @@ class RRule implements RRuleInterface
} }
else { else {
$this->dtstart = new \DateTime(); // for PHP 7.1+ this contains microseconds which causes many problems $this->dtstart = new \DateTime(); // for PHP 7.1+ this contains microseconds which causes many problems
if ( version_compare(PHP_VERSION, '7.1.0') >= 0 ) { if (version_compare(PHP_VERSION, '7.1.0') >= 0) {
// remove microseconds // remove microseconds
$this->dtstart->setTime( $this->dtstart->setTime(
$this->dtstart->format('H'), $this->dtstart->format('H'),
@ -291,7 +291,7 @@ class RRule implements RRuleInterface
} }
// UNTIL (optional) // UNTIL (optional)
if ( not_empty($parts['UNTIL']) ) { if (not_empty($parts['UNTIL'])) {
try { try {
$this->until = self::parseDate($parts['UNTIL']); $this->until = self::parseDate($parts['UNTIL']);
} catch (\Exception $e) { } catch (\Exception $e) {
@ -302,22 +302,22 @@ class RRule implements RRuleInterface
} }
// COUNT (optional) // COUNT (optional)
if ( not_empty($parts['COUNT']) ) { if (not_empty($parts['COUNT'])) {
if ( filter_var($parts['COUNT'], FILTER_VALIDATE_INT, array('options' => array('min_range' => 1))) === false ) { if (filter_var($parts['COUNT'], FILTER_VALIDATE_INT, array('options' => array('min_range' => 1))) === false) {
throw new \InvalidArgumentException('COUNT must be a positive integer (> 0)'); throw new \InvalidArgumentException('COUNT must be a positive integer (> 0)');
} }
$this->count = (int) $parts['COUNT']; $this->count = (int) $parts['COUNT'];
} }
if ( $this->until && $this->count ) { if ($this->until && $this->count) {
throw new \InvalidArgumentException('The UNTIL or COUNT rule parts MUST NOT occur in the same rule'); 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 // 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'])) ) { if (! (not_empty($parts['BYWEEKNO']) || not_empty($parts['BYYEARDAY']) || not_empty($parts['BYMONTHDAY']) || not_empty($parts['BYDAY']))) {
switch ( $this->freq ) { switch ($this->freq) {
case self::YEARLY: case self::YEARLY:
if ( ! not_empty($parts['BYMONTH']) ) { if (! not_empty($parts['BYMONTH'])) {
$parts['BYMONTH'] = array((int) $this->dtstart->format('m')); $parts['BYMONTH'] = array((int) $this->dtstart->format('m'));
} }
$parts['BYMONTHDAY'] = array((int) $this->dtstart->format('j')); $parts['BYMONTHDAY'] = array((int) $this->dtstart->format('j'));
@ -332,20 +332,20 @@ class RRule implements RRuleInterface
} }
// BYDAY (translated to byweekday for convenience) // BYDAY (translated to byweekday for convenience)
if ( not_empty($parts['BYDAY']) ) { if (not_empty($parts['BYDAY'])) {
if ( ! is_array($parts['BYDAY']) ) { if (! is_array($parts['BYDAY'])) {
$parts['BYDAY'] = explode(',',$parts['BYDAY']); $parts['BYDAY'] = explode(',',$parts['BYDAY']);
} }
$this->byweekday = array(); $this->byweekday = array();
$this->byweekday_nth = array(); $this->byweekday_nth = array();
foreach ( $parts['BYDAY'] as $value ) { foreach ($parts['BYDAY'] as $value) {
$value = trim(strtoupper($value)); $value = trim(strtoupper($value));
$valid = preg_match('/^([+-]?[0-9]+)?([A-Z]{2})$/', $value, $matches); $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) ) { 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); throw new \InvalidArgumentException('Invalid BYDAY value: '.$value);
} }
if ( $matches[1] ) { if ($matches[1]) {
$this->byweekday_nth[] = array(self::$week_days[$matches[2]], (int)$matches[1]); $this->byweekday_nth[] = array(self::$week_days[$matches[2]], (int)$matches[1]);
} }
else { else {
@ -353,11 +353,11 @@ class RRule implements RRuleInterface
} }
} }
if ( ! empty($this->byweekday_nth) ) { if (! empty($this->byweekday_nth)) {
if ( ! ($this->freq === self::MONTHLY || $this->freq === self::YEARLY) ) { 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.'); 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']) ) { 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.'); 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.');
} }
} }
@ -368,23 +368,23 @@ class RRule implements RRuleInterface
// example, -10 represents the tenth to the last day of the month. // example, -10 represents the tenth to the last day of the month.
// The BYMONTHDAY rule part MUST NOT be specified when the FREQ rule // The BYMONTHDAY rule part MUST NOT be specified when the FREQ rule
// part is set to WEEKLY. // part is set to WEEKLY.
if ( not_empty($parts['BYMONTHDAY']) ) { if (not_empty($parts['BYMONTHDAY'])) {
if ( $this->freq === self::WEEKLY ) { 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.'); 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']) ) { if (! is_array($parts['BYMONTHDAY'])) {
$parts['BYMONTHDAY'] = explode(',',$parts['BYMONTHDAY']); $parts['BYMONTHDAY'] = explode(',',$parts['BYMONTHDAY']);
} }
$this->bymonthday = array(); $this->bymonthday = array();
$this->bymonthday_negative = array(); $this->bymonthday_negative = array();
foreach ( $parts['BYMONTHDAY'] as $value ) { foreach ($parts['BYMONTHDAY'] as $value) {
if ( !$value || filter_var($value, FILTER_VALIDATE_INT, array('options' => array('min_range' => -31, 'max_range' => 31))) === false ) { if (!$value || filter_var($value, FILTER_VALIDATE_INT, array('options' => array('min_range' => -31, 'max_range' => 31))) === false) {
throw new \InvalidArgumentException('Invalid BYMONTHDAY value: '.$value.' (valid values are 1 to 31 or -31 to -1)'); throw new \InvalidArgumentException('Invalid BYMONTHDAY value: '.$value.' (valid values are 1 to 31 or -31 to -1)');
} }
$value = (int) $value; $value = (int) $value;
if ( $value < 0 ) { if ($value < 0) {
$this->bymonthday_negative[] = $value; $this->bymonthday_negative[] = $value;
} }
else { else {
@ -393,18 +393,18 @@ class RRule implements RRuleInterface
} }
} }
if ( not_empty($parts['BYYEARDAY']) ) { if (not_empty($parts['BYYEARDAY'])) {
if ( $this->freq === self::DAILY || $this->freq === self::WEEKLY || $this->freq === self::MONTHLY ) { 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.'); 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']) ) { if (! is_array($parts['BYYEARDAY'])) {
$parts['BYYEARDAY'] = explode(',',$parts['BYYEARDAY']); $parts['BYYEARDAY'] = explode(',',$parts['BYYEARDAY']);
} }
$this->bysetpos = array(); $this->bysetpos = array();
foreach ( $parts['BYYEARDAY'] as $value ) { foreach ($parts['BYYEARDAY'] as $value) {
if ( ! $value || filter_var($value, FILTER_VALIDATE_INT, array('options' => array('min_range' => -366, 'max_range' => 366))) === false ) { if (! $value || filter_var($value, FILTER_VALIDATE_INT, array('options' => array('min_range' => -366, 'max_range' => 366))) === false) {
throw new \InvalidArgumentException('Invalid BYSETPOS value: '.$value.' (valid values are 1 to 366 or -366 to -1)'); throw new \InvalidArgumentException('Invalid BYSETPOS value: '.$value.' (valid values are 1 to 366 or -366 to -1)');
} }
@ -413,18 +413,18 @@ class RRule implements RRuleInterface
} }
// BYWEEKNO // BYWEEKNO
if ( not_empty($parts['BYWEEKNO']) ) { if (not_empty($parts['BYWEEKNO'])) {
if ( $this->freq !== self::YEARLY ) { 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.'); 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']) ) { if (! is_array($parts['BYWEEKNO'])) {
$parts['BYWEEKNO'] = explode(',',$parts['BYWEEKNO']); $parts['BYWEEKNO'] = explode(',',$parts['BYWEEKNO']);
} }
$this->byweekno = array(); $this->byweekno = array();
foreach ( $parts['BYWEEKNO'] as $value ) { foreach ($parts['BYWEEKNO'] as $value) {
if ( ! $value || filter_var($value, FILTER_VALIDATE_INT, array('options' => array('min_range' => -53, 'max_range' => 53))) === false ) { if (! $value || filter_var($value, FILTER_VALIDATE_INT, array('options' => array('min_range' => -53, 'max_range' => 53))) === false) {
throw new \InvalidArgumentException('Invalid BYWEEKNO value: '.$value.' (valid values are 1 to 53 or -53 to -1)'); throw new \InvalidArgumentException('Invalid BYWEEKNO value: '.$value.' (valid values are 1 to 53 or -53 to -1)');
} }
$this->byweekno[] = (int) $value; $this->byweekno[] = (int) $value;
@ -433,35 +433,35 @@ class RRule implements RRuleInterface
// The BYMONTH rule part specifies a COMMA-separated list of months // The BYMONTH rule part specifies a COMMA-separated list of months
// of the year. Valid values are 1 to 12. // of the year. Valid values are 1 to 12.
if ( not_empty($parts['BYMONTH']) ) { if (not_empty($parts['BYMONTH'])) {
if ( ! is_array($parts['BYMONTH']) ) { if (! is_array($parts['BYMONTH'])) {
$parts['BYMONTH'] = explode(',',$parts['BYMONTH']); $parts['BYMONTH'] = explode(',',$parts['BYMONTH']);
} }
$this->bymonth = array(); $this->bymonth = array();
foreach ( $parts['BYMONTH'] as $value ) { foreach ($parts['BYMONTH'] as $value) {
if ( filter_var($value, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 12))) === false ) { if (filter_var($value, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 12))) === false) {
throw new \InvalidArgumentException('Invalid BYMONTH value: '.$value); throw new \InvalidArgumentException('Invalid BYMONTH value: '.$value);
} }
$this->bymonth[] = (int) $value; $this->bymonth[] = (int) $value;
} }
} }
if ( not_empty($parts['BYSETPOS']) ) { if (not_empty($parts['BYSETPOS'])) {
if ( ! (not_empty($parts['BYWEEKNO']) || not_empty($parts['BYYEARDAY']) if (! (not_empty($parts['BYWEEKNO']) || not_empty($parts['BYYEARDAY'])
|| not_empty($parts['BYMONTHDAY']) || not_empty($parts['BYDAY']) || not_empty($parts['BYMONTHDAY']) || not_empty($parts['BYDAY'])
|| not_empty($parts['BYMONTH']) || not_empty($parts['BYHOUR']) || not_empty($parts['BYMONTH']) || not_empty($parts['BYHOUR'])
|| not_empty($parts['BYMINUTE']) || not_empty($parts['BYSECOND'])) ) { || 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.'); throw new \InvalidArgumentException('The BYSETPOS rule part MUST only be used in conjunction with another BYxxx rule part.');
} }
if ( ! is_array($parts['BYSETPOS']) ) { if (! is_array($parts['BYSETPOS'])) {
$parts['BYSETPOS'] = explode(',',$parts['BYSETPOS']); $parts['BYSETPOS'] = explode(',',$parts['BYSETPOS']);
} }
$this->bysetpos = array(); $this->bysetpos = array();
foreach ( $parts['BYSETPOS'] as $value ) { foreach ($parts['BYSETPOS'] as $value) {
if ( ! $value || filter_var($value, FILTER_VALIDATE_INT, array('options' => array('min_range' => -366, 'max_range' => 366))) === false ) { if (! $value || filter_var($value, FILTER_VALIDATE_INT, array('options' => array('min_range' => -366, 'max_range' => 366))) === false) {
throw new \InvalidArgumentException('Invalid BYSETPOS value: '.$value.' (valid values are 1 to 366 or -366 to -1)'); throw new \InvalidArgumentException('Invalid BYSETPOS value: '.$value.' (valid values are 1 to 366 or -366 to -1)');
} }
@ -469,14 +469,14 @@ class RRule implements RRuleInterface
} }
} }
if ( not_empty($parts['BYHOUR']) ) { if (not_empty($parts['BYHOUR'])) {
if ( ! is_array($parts['BYHOUR']) ) { if (! is_array($parts['BYHOUR'])) {
$parts['BYHOUR'] = explode(',',$parts['BYHOUR']); $parts['BYHOUR'] = explode(',',$parts['BYHOUR']);
} }
$this->byhour = array(); $this->byhour = array();
foreach ( $parts['BYHOUR'] as $value ) { foreach ($parts['BYHOUR'] as $value) {
if ( filter_var($value, FILTER_VALIDATE_INT, array('options' => array('min_range' => 0, 'max_range' => 23))) === false ) { if (filter_var($value, FILTER_VALIDATE_INT, array('options' => array('min_range' => 0, 'max_range' => 23))) === false) {
throw new \InvalidArgumentException('Invalid BYHOUR value: '.$value); throw new \InvalidArgumentException('Invalid BYHOUR value: '.$value);
} }
$this->byhour[] = (int) $value; $this->byhour[] = (int) $value;
@ -484,59 +484,59 @@ class RRule implements RRuleInterface
sort($this->byhour); sort($this->byhour);
} }
elseif ( $this->freq < self::HOURLY ) { elseif ($this->freq < self::HOURLY) {
$this->byhour = array((int) $this->dtstart->format('G')); $this->byhour = array((int) $this->dtstart->format('G'));
} }
if ( not_empty($parts['BYMINUTE']) ) { if (not_empty($parts['BYMINUTE'])) {
if ( ! is_array($parts['BYMINUTE']) ) { if (! is_array($parts['BYMINUTE'])) {
$parts['BYMINUTE'] = explode(',',$parts['BYMINUTE']); $parts['BYMINUTE'] = explode(',',$parts['BYMINUTE']);
} }
$this->byminute = array(); $this->byminute = array();
foreach ( $parts['BYMINUTE'] as $value ) { foreach ($parts['BYMINUTE'] as $value) {
if ( filter_var($value, FILTER_VALIDATE_INT, array('options' => array('min_range' => 0, 'max_range' => 59))) === false ) { if (filter_var($value, FILTER_VALIDATE_INT, array('options' => array('min_range' => 0, 'max_range' => 59))) === false) {
throw new \InvalidArgumentException('Invalid BYMINUTE value: '.$value); throw new \InvalidArgumentException('Invalid BYMINUTE value: '.$value);
} }
$this->byminute[] = (int) $value; $this->byminute[] = (int) $value;
} }
sort($this->byminute); sort($this->byminute);
} }
elseif ( $this->freq < self::MINUTELY ) { elseif ($this->freq < self::MINUTELY) {
$this->byminute = array((int) $this->dtstart->format('i')); $this->byminute = array((int) $this->dtstart->format('i'));
} }
if ( not_empty($parts['BYSECOND']) ) { if (not_empty($parts['BYSECOND'])) {
if ( ! is_array($parts['BYSECOND']) ) { if (! is_array($parts['BYSECOND'])) {
$parts['BYSECOND'] = explode(',',$parts['BYSECOND']); $parts['BYSECOND'] = explode(',',$parts['BYSECOND']);
} }
$this->bysecond = array(); $this->bysecond = array();
foreach ( $parts['BYSECOND'] as $value ) { foreach ($parts['BYSECOND'] as $value) {
// yes, "60" is a valid value, in (very rare) cases on leap seconds // yes, "60" is a valid value, in (very rare) cases on leap seconds
// December 31, 2005 23:59:60 UTC is a valid date... // December 31, 2005 23:59:60 UTC is a valid date...
// so is 2012-06-30T23:59:60UTC // so is 2012-06-30T23:59:60UTC
if ( filter_var($value, FILTER_VALIDATE_INT, array('options' => array('min_range' => 0, 'max_range' => 60))) === false ) { if (filter_var($value, FILTER_VALIDATE_INT, array('options' => array('min_range' => 0, 'max_range' => 60))) === false) {
throw new \InvalidArgumentException('Invalid BYSECOND value: '.$value); throw new \InvalidArgumentException('Invalid BYSECOND value: '.$value);
} }
$this->bysecond[] = (int) $value; $this->bysecond[] = (int) $value;
} }
sort($this->bysecond); sort($this->bysecond);
} }
elseif ( $this->freq < self::SECONDLY ) { elseif ($this->freq < self::SECONDLY) {
$this->bysecond = array((int) $this->dtstart->format('s')); $this->bysecond = array((int) $this->dtstart->format('s'));
} }
if ( $this->freq < self::HOURLY ) { if ($this->freq < self::HOURLY) {
// for frequencies DAILY, WEEKLY, MONTHLY AND YEARLY, we can build // for frequencies DAILY, WEEKLY, MONTHLY AND YEARLY, we can build
// an array of every time of the day at which there should be an // an array of every time of the day at which there should be an
// occurrence - default, if no BYHOUR/BYMINUTE/BYSECOND are provided // occurrence - default, if no BYHOUR/BYMINUTE/BYSECOND are provided
// is only one time, and it's the DTSTART time. This is a cached version // 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 // if you will, since it'll never change at these frequencies
$this->timeset = array(); $this->timeset = array();
foreach ( $this->byhour as $hour ) { foreach ($this->byhour as $hour) {
foreach ( $this->byminute as $minute ) { foreach ($this->byminute as $minute) {
foreach ( $this->bysecond as $second ) { foreach ($this->bysecond as $second) {
$this->timeset[] = array($hour,$minute,$second); $this->timeset[] = array($hour,$minute,$second);
} }
} }
@ -574,8 +574,8 @@ class RRule implements RRuleInterface
public function rfcString($include_timezone = true) public function rfcString($include_timezone = true)
{ {
$str = ''; $str = '';
if ( $this->rule['DTSTART'] ) { if ($this->rule['DTSTART']) {
if ( ! $include_timezone ) { if (! $include_timezone) {
$str = sprintf( $str = sprintf(
"DTSTART:%s\nRRULE:", "DTSTART:%s\nRRULE:",
$this->dtstart->format('Ymd\THis') $this->dtstart->format('Ymd\THis')
@ -584,14 +584,14 @@ class RRule implements RRuleInterface
else { else {
$dtstart = clone $this->dtstart; $dtstart = clone $this->dtstart;
$timezone_name = $dtstart->getTimeZone()->getName(); $timezone_name = $dtstart->getTimeZone()->getName();
if ( strpos($timezone_name,':') !== false ) { if (strpos($timezone_name,':') !== false) {
// handle unsupported timezones like "+02:00" // handle unsupported timezones like "+02:00"
// we convert them to UTC to generate a valid string // we convert them to UTC to generate a valid string
// note: there is possibly other weird timezones out there that we should catch // note: there is possibly other weird timezones out there that we should catch
$dtstart->setTimezone(new \DateTimeZone('UTC')); $dtstart->setTimezone(new \DateTimeZone('UTC'));
$timezone_name = 'UTC'; $timezone_name = 'UTC';
} }
if ( in_array($timezone_name, array('UTC','GMT','Z')) ) { if (in_array($timezone_name, array('UTC','GMT','Z'))) {
$str = sprintf( $str = sprintf(
"DTSTART:%s\nRRULE:", "DTSTART:%s\nRRULE:",
$dtstart->format('Ymd\THis\Z') $dtstart->format('Ymd\THis\Z')
@ -608,18 +608,18 @@ class RRule implements RRuleInterface
} }
$parts = array(); $parts = array();
foreach ( $this->rule as $key => $value ) { foreach ($this->rule as $key => $value) {
if ( $key === 'DTSTART' ) { if ($key === 'DTSTART') {
continue; continue;
} }
if ( $key === 'INTERVAL' && $value == 1 ) { if ($key === 'INTERVAL' && $value == 1) {
continue; continue;
} }
if ( $key === 'WKST' && $value === 'MO') { if ($key === 'WKST' && $value === 'MO') {
continue; continue;
} }
if ( $key === 'UNTIL' && $value ) { if ($key === 'UNTIL' && $value) {
if ( ! $include_timezone ) { if (! $include_timezone) {
$tmp = clone $this->until; $tmp = clone $this->until;
// put until on the same timezone as DTSTART // put until on the same timezone as DTSTART
$tmp->setTimeZone($this->dtstart->getTimezone()); $tmp->setTimeZone($this->dtstart->getTimezone());
@ -633,14 +633,14 @@ class RRule implements RRuleInterface
} }
continue; continue;
} }
if ( $key === 'FREQ' && $value && !array_key_exists($value, static::$frequencies) ) { if ($key === 'FREQ' && $value && !array_key_exists($value, static::$frequencies)) {
$frequency_key = array_search($value, static::$frequencies); $frequency_key = array_search($value, static::$frequencies);
if ($frequency_key !== false) { if ($frequency_key !== false) {
$value = $frequency_key; $value = $frequency_key;
} }
} }
if ( $value !== NULL ) { if ($value !== NULL) {
if ( is_array($value) ) { if (is_array($value)) {
$value = implode(',',$value); $value = implode(',',$value);
} }
$parts[] = strtoupper(str_replace(' ','',"$key=$value")); $parts[] = strtoupper(str_replace(' ','',"$key=$value"));
@ -678,19 +678,19 @@ class RRule implements RRuleInterface
{ {
$class = '\RRule\RSet'; $class = '\RRule\RSet';
if ( ! $force_rset ) { if (! $force_rset) {
// try to detect if we have a RRULE or a set // try to detect if we have a RRULE or a set
$uppercased_string = strtoupper($string); $uppercased_string = strtoupper($string);
$nb_rrule = substr_count($string, 'RRULE'); $nb_rrule = substr_count($string, 'RRULE');
if ( $nb_rrule == 0 ) { if ($nb_rrule == 0) {
$class = '\RRule\RRule'; $class = '\RRule\RRule';
} }
elseif ( $nb_rrule > 1 ) { elseif ($nb_rrule > 1) {
$class = '\RRule\RSet'; $class = '\RRule\RSet';
} }
else { else {
$class = '\RRule\RRule'; $class = '\RRule\RRule';
if ( strpos($string, 'EXDATE') !== false || strpos($string, 'RDATE') !== false || strpos($string, 'EXRULE') !== false ) { if (strpos($string, 'EXDATE') !== false || strpos($string, 'RDATE') !== false || strpos($string, 'EXRULE') !== false) {
$class = '\RRule\RSet'; $class = '\RRule\RSet';
} }
} }
@ -754,28 +754,28 @@ class RRule implements RRuleInterface
// convert timezone to dtstart timezone for comparison // convert timezone to dtstart timezone for comparison
$date->setTimezone($this->dtstart->getTimezone()); $date->setTimezone($this->dtstart->getTimezone());
if ( in_array($date, $this->cache) ) { if (in_array($date, $this->cache)) {
// in the cache (whether cache is complete or not) // in the cache (whether cache is complete or not)
return true; return true;
} }
elseif ( $this->total !== null ) { elseif ($this->total !== null) {
// cache complete and not in cache // cache complete and not in cache
return false; return false;
} }
// let's start with the obvious // let's start with the obvious
if ( $date < $this->dtstart || ($this->until && $date > $this->until) ) { if ($date < $this->dtstart || ($this->until && $date > $this->until)) {
return false; return false;
} }
// now the BYXXX rules (expect BYSETPOS) // now the BYXXX rules (expect BYSETPOS)
if ( $this->byhour && ! in_array($date->format('G'), $this->byhour) ) { if ($this->byhour && ! in_array($date->format('G'), $this->byhour)) {
return false; return false;
} }
if ( $this->byminute && ! in_array((int) $date->format('i'), $this->byminute) ) { if ($this->byminute && ! in_array((int) $date->format('i'), $this->byminute)) {
return false; return false;
} }
if ( $this->bysecond && ! in_array((int) $date->format('s'), $this->bysecond) ) { if ($this->bysecond && ! in_array((int) $date->format('s'), $this->bysecond)) {
return false; return false;
} }
@ -784,7 +784,7 @@ class RRule implements RRuleInterface
$masks = array(); $masks = array();
$masks['weekday_of_1st_yearday'] = date_create($year.'-01-01 00:00:00')->format('N'); $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); $masks['yearday_to_weekday'] = array_slice(self::$WEEKDAY_MASK, $masks['weekday_of_1st_yearday']-1);
if ( is_leap_year($year) ) { if (is_leap_year($year)) {
$masks['year_len'] = 366; $masks['year_len'] = 366;
$masks['last_day_of_month'] = self::$LAST_DAY_OF_MONTH_366; $masks['last_day_of_month'] = self::$LAST_DAY_OF_MONTH_366;
} }
@ -794,40 +794,40 @@ class RRule implements RRuleInterface
} }
$month_len = $masks['last_day_of_month'][$month] - $masks['last_day_of_month'][$month-1]; $month_len = $masks['last_day_of_month'][$month] - $masks['last_day_of_month'][$month-1];
if ( $this->bymonth && ! in_array($month, $this->bymonth) ) { if ($this->bymonth && ! in_array($month, $this->bymonth)) {
return false; return false;
} }
if ( $this->bymonthday || $this->bymonthday_negative ) { if ($this->bymonthday || $this->bymonthday_negative) {
$monthday_negative = -1 * ($month_len - $day + 1); $monthday_negative = -1 * ($month_len - $day + 1);
if ( ! in_array($day, $this->bymonthday) && ! in_array($monthday_negative, $this->bymonthday_negative) ) { if (! in_array($day, $this->bymonthday) && ! in_array($monthday_negative, $this->bymonthday_negative)) {
return false; return false;
} }
} }
if ( $this->byyearday ) { if ($this->byyearday) {
// caution here, yearday starts from 0 ! // caution here, yearday starts from 0 !
$yearday_negative = -1*($masks['year_len'] - $yearday); $yearday_negative = -1*($masks['year_len'] - $yearday);
if ( ! in_array($yearday+1, $this->byyearday) && ! in_array($yearday_negative, $this->byyearday) ) { if (! in_array($yearday+1, $this->byyearday) && ! in_array($yearday_negative, $this->byyearday)) {
return false; return false;
} }
} }
if ( $this->byweekday || $this->byweekday_nth ) { if ($this->byweekday || $this->byweekday_nth) {
// we need to summon some magic here // we need to summon some magic here
$this->buildNthWeekdayMask($year, $month, $day, $masks); $this->buildNthWeekdayMask($year, $month, $day, $masks);
if ( ! in_array($weekday, $this->byweekday) && ! isset($masks['yearday_is_nth_weekday'][$yearday]) ) { if (! in_array($weekday, $this->byweekday) && ! isset($masks['yearday_is_nth_weekday'][$yearday])) {
return false; return false;
} }
} }
if ( $this->byweekno ) { if ($this->byweekno) {
// more magic // more magic
$this->buildWeeknoMask($year, $month, $day, $masks); $this->buildWeeknoMask($year, $month, $day, $masks);
if ( ! isset($masks['yearday_is_in_weekno'][$yearday]) ) { if (! isset($masks['yearday_is_in_weekno'][$yearday])) {
return false; return false;
} }
} }
@ -835,9 +835,9 @@ class RRule implements RRuleInterface
// so now we have exhausted all the BYXXX rules (exept bysetpos), // so now we have exhausted all the BYXXX rules (exept bysetpos),
// we still need to consider frequency and interval // we still need to consider frequency and interval
list ($start_year, $start_month, $start_day) = explode('-',$this->dtstart->format('Y-m-d')); list ($start_year, $start_month, $start_day) = explode('-',$this->dtstart->format('Y-m-d'));
switch ( $this->freq ) { switch ($this->freq) {
case self::YEARLY: case self::YEARLY:
if ( ($year - $start_year) % $this->interval !== 0 ) { if (($year - $start_year) % $this->interval !== 0) {
return false; return false;
} }
break; break;
@ -845,7 +845,7 @@ class RRule implements RRuleInterface
// we need to count the number of months elapsed // we need to count the number of months elapsed
$diff = (12 - $start_month) + 12*($year - $start_year - 1) + $month; $diff = (12 - $start_month) + 12*($year - $start_year - 1) + $month;
if ( ($diff % $this->interval) !== 0 ) { if (($diff % $this->interval) !== 0) {
return false; return false;
} }
break; break;
@ -854,14 +854,14 @@ class RRule implements RRuleInterface
// we add some days to align dtstart with wkst // we add some days to align dtstart with wkst
$diff = $date->diff($this->dtstart); $diff = $date->diff($this->dtstart);
$diff = (int) (($diff->days + pymod($this->dtstart->format('N') - $this->wkst,7)) / 7); $diff = (int) (($diff->days + pymod($this->dtstart->format('N') - $this->wkst,7)) / 7);
if ( $diff % $this->interval !== 0 ) { if ($diff % $this->interval !== 0) {
return false; return false;
} }
break; break;
case self::DAILY: case self::DAILY:
// count nb of days // count nb of days
$diff = $date->diff($this->dtstart); $diff = $date->diff($this->dtstart);
if ( $diff->days % $this->interval !== 0 ) { if ($diff->days % $this->interval !== 0) {
return false; return false;
} }
break; break;
@ -869,14 +869,14 @@ class RRule implements RRuleInterface
case self::HOURLY: case self::HOURLY:
$diff = $date->diff($this->dtstart); $diff = $date->diff($this->dtstart);
$diff = $diff->h + $diff->days * 24; $diff = $diff->h + $diff->days * 24;
if ( $diff % $this->interval !== 0 ) { if ($diff % $this->interval !== 0) {
return false; return false;
} }
break; break;
case self::MINUTELY: case self::MINUTELY:
$diff = $date->diff($this->dtstart); $diff = $date->diff($this->dtstart);
$diff = $diff->i + $diff->h * 60 + $diff->days * 1440; $diff = $diff->i + $diff->h * 60 + $diff->days * 1440;
if ( $diff % $this->interval !== 0 ) { if ($diff % $this->interval !== 0) {
return false; return false;
} }
break; break;
@ -884,7 +884,7 @@ class RRule implements RRuleInterface
$diff = $date->diff($this->dtstart); $diff = $date->diff($this->dtstart);
// XXX does not account for leap second (should it?) // XXX does not account for leap second (should it?)
$diff = $diff->s + $diff->i * 60 + $diff->h * 3600 + $diff->days * 86400; $diff = $diff->s + $diff->i * 60 + $diff->h * 3600 + $diff->days * 86400;
if ( $diff % $this->interval !== 0 ) { if ($diff % $this->interval !== 0) {
return false; return false;
} }
break; break;
@ -902,16 +902,16 @@ class RRule implements RRuleInterface
// might sometimes be dropped (e.g. a 29 Feb on a normal year, or during // 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 // the switch to DST) and not counted in the final set
if ( ! $this->count && ! $this->bysetpos ) { if (! $this->count && ! $this->bysetpos) {
return true; return true;
} }
// so... as a fallback we have to loop // so... as a fallback we have to loop
foreach ( $this as $occurrence ) { foreach ($this as $occurrence) {
if ( $occurrence == $date ) { if ($occurrence == $date) {
return true; // lucky you! return true; // lucky you!
} }
if ( $occurrence > $date ) { if ($occurrence > $date) {
break; break;
} }
} }
@ -988,27 +988,27 @@ class RRule implements RRuleInterface
*/ */
public function offsetGet($offset) public function offsetGet($offset)
{ {
if ( ! is_numeric($offset) || $offset < 0 || is_float($offset) ) { if (! is_numeric($offset) || $offset < 0 || is_float($offset)) {
throw new \InvalidArgumentException('Illegal offset type: '.gettype($offset)); throw new \InvalidArgumentException('Illegal offset type: '.gettype($offset));
} }
if ( isset($this->cache[$offset]) ) { if (isset($this->cache[$offset])) {
// found in cache // found in cache
return clone $this->cache[$offset]; return clone $this->cache[$offset];
} }
elseif ( $this->total !== null ) { elseif ($this->total !== null) {
// cache complete and not found in cache // cache complete and not found in cache
return null; return null;
} }
// not in cache and cache not complete, we have to loop to find it // not in cache and cache not complete, we have to loop to find it
$i = 0; $i = 0;
foreach ( $this as $occurrence ) { foreach ($this as $occurrence) {
if ( $i == $offset ) { if ($i == $offset) {
return $occurrence; return $occurrence;
} }
$i++; $i++;
if ( $i > $offset ) { if ($i > $offset) {
break; break;
} }
} }
@ -1043,12 +1043,12 @@ class RRule implements RRuleInterface
*/ */
public function count() public function count()
{ {
if ( $this->isInfinite() ) { if ($this->isInfinite()) {
throw new \LogicException('Cannot count an infinite recurrence rule.'); throw new \LogicException('Cannot count an infinite recurrence rule.');
} }
if ( $this->total === null ) { if ($this->total === null) {
foreach ( $this as $occurrence ) {} foreach ($this as $occurrence) {}
} }
return $this->total; return $this->total;
@ -1070,7 +1070,7 @@ class RRule implements RRuleInterface
*/ */
protected function getDaySet($year, $month, $day, array $masks) protected function getDaySet($year, $month, $day, array $masks)
{ {
switch ( $this->freq ) { switch ($this->freq) {
case self::YEARLY: case self::YEARLY:
return range(0,$masks['year_len']-1); return range(0,$masks['year_len']-1);
@ -1088,10 +1088,10 @@ class RRule implements RRuleInterface
$set = array(); $set = array();
$i = (int) date_create($year.'-'.$month.'-'.$day.' 00:00:00')->format('z'); $i = (int) date_create($year.'-'.$month.'-'.$day.' 00:00:00')->format('z');
$start = $i; $start = $i;
for ( $j = 0; $j < 7; $j++ ) { for ($j = 0; $j < 7; $j++) {
$set[] = $i; $set[] = $i;
$i += 1; $i += 1;
if ( $masks['yearday_to_weekday'][$i] == $this->wkst ) { if ($masks['yearday_to_weekday'][$i] == $this->wkst) {
break; break;
} }
} }
@ -1126,11 +1126,11 @@ class RRule implements RRuleInterface
{ {
$masks['yearday_is_nth_weekday'] = array(); $masks['yearday_is_nth_weekday'] = array();
if ( $this->byweekday_nth ) { if ($this->byweekday_nth) {
$ranges = array(); $ranges = array();
if ( $this->freq == self::YEARLY ) { if ($this->freq == self::YEARLY) {
if ( $this->bymonth ) { if ($this->bymonth) {
foreach ( $this->bymonth as $bymonth ) { foreach ($this->bymonth as $bymonth) {
$ranges[] = array( $ranges[] = array(
$masks['last_day_of_month'][$bymonth - 1], $masks['last_day_of_month'][$bymonth - 1],
$masks['last_day_of_month'][$bymonth] - 1 $masks['last_day_of_month'][$bymonth] - 1
@ -1141,21 +1141,21 @@ class RRule implements RRuleInterface
$ranges = array(array(0, $masks['year_len'] - 1)); $ranges = array(array(0, $masks['year_len'] - 1));
} }
} }
elseif ( $this->freq == self::MONTHLY ) { elseif ($this->freq == self::MONTHLY) {
$ranges[] = array( $ranges[] = array(
$masks['last_day_of_month'][$month - 1], $masks['last_day_of_month'][$month - 1],
$masks['last_day_of_month'][$month] - 1 $masks['last_day_of_month'][$month] - 1
); );
} }
if ( $ranges ) { if ($ranges) {
// Weekly frequency won't get here, so we may not // Weekly frequency won't get here, so we may not
// care about cross-year weekly periods. // care about cross-year weekly periods.
foreach ( $ranges as $tmp ) { foreach ($ranges as $tmp) {
list($first, $last) = $tmp; list($first, $last) = $tmp;
foreach ( $this->byweekday_nth as $tmp ) { foreach ($this->byweekday_nth as $tmp) {
list($weekday, $nth) = $tmp; list($weekday, $nth) = $tmp;
if ( $nth < 0 ) { if ($nth < 0) {
$i = $last + ($nth + 1) * 7; $i = $last + ($nth + 1) * 7;
$i = $i - pymod($masks['yearday_to_weekday'][$i] - $weekday, 7); $i = $i - pymod($masks['yearday_to_weekday'][$i] - $weekday, 7);
} }
@ -1164,7 +1164,7 @@ class RRule implements RRuleInterface
$i = $i + (7 - $masks['yearday_to_weekday'][$i] + $weekday) % 7; $i = $i + (7 - $masks['yearday_to_weekday'][$i] + $weekday) % 7;
} }
if ( $i >= $first && $i <= $last ) { if ($i >= $first && $i <= $last) {
$masks['yearday_is_nth_weekday'][$i] = true; $masks['yearday_is_nth_weekday'][$i] = true;
} }
} }
@ -1197,7 +1197,7 @@ class RRule implements RRuleInterface
// n means there is n days before the first wkst day of the year. // n means there is n days before the first wkst day of the year.
// if n >= 4, this is the first day of the year (even though it started the year before) // if n >= 4, this is the first day of the year (even though it started the year before)
$first_wkst = (7 - $masks['weekday_of_1st_yearday'] + $this->wkst) % 7; $first_wkst = (7 - $masks['weekday_of_1st_yearday'] + $this->wkst) % 7;
if( $first_wkst >= 4 ) { if($first_wkst >= 4) {
$first_wkst_offset = 0; $first_wkst_offset = 0;
// Number of days in the year, plus the days we got from last year. // Number of days in the year, plus the days we got from last year.
$nb_days = $masks['year_len'] + $masks['weekday_of_1st_yearday'] - $this->wkst; $nb_days = $masks['year_len'] + $masks['weekday_of_1st_yearday'] - $this->wkst;
@ -1214,16 +1214,16 @@ class RRule implements RRuleInterface
// and the number of weeks of the year // and the number of weeks of the year
// so we can generate a map of every yearday that are in the weeks // so we can generate a map of every yearday that are in the weeks
// specified in byweekno // specified in byweekno
foreach ( $this->byweekno as $n ) { foreach ($this->byweekno as $n) {
if ( $n < 0 ) { if ($n < 0) {
$n = $n + $nb_weeks + 1; $n = $n + $nb_weeks + 1;
} }
if ( $n <= 0 || $n > $nb_weeks ) { if ($n <= 0 || $n > $nb_weeks) {
continue; continue;
} }
if ( $n > 1 ) { if ($n > 1) {
$i = $first_wkst_offset + ($n - 1) * 7; $i = $first_wkst_offset + ($n - 1) * 7;
if ( $first_wkst_offset != $first_wkst ) { if ($first_wkst_offset != $first_wkst) {
// if week #1 started the previous year // if week #1 started the previous year
// realign the start of the week // realign the start of the week
$i = $i - (7 - $first_wkst); $i = $i - (7 - $first_wkst);
@ -1235,10 +1235,10 @@ class RRule implements RRuleInterface
// now add 7 days into the resultset, stopping either at 7 or // now add 7 days into the resultset, stopping either at 7 or
// if we reach wkst before (in the case of short first week of year) // if we reach wkst before (in the case of short first week of year)
for ( $j = 0; $j < 7; $j++ ) { for ($j = 0; $j < 7; $j++) {
$masks['yearday_is_in_weekno'][$i] = true; $masks['yearday_is_in_weekno'][$i] = true;
$i = $i + 1; $i = $i + 1;
if ( $masks['yearday_to_weekday'][$i] == $this->wkst ) { if ($masks['yearday_to_weekday'][$i] == $this->wkst) {
break; break;
} }
} }
@ -1247,35 +1247,35 @@ class RRule implements RRuleInterface
// if we asked for week #1, it's possible that the week #1 of next year // if we asked for week #1, it's possible that the week #1 of next year
// already started this year. Therefore we need to return also the matching // already started this year. Therefore we need to return also the matching
// days of next year. // days of next year.
if ( in_array(1, $this->byweekno) ) { if (in_array(1, $this->byweekno)) {
// Check week number 1 of next year as well // Check week number 1 of next year as well
// TODO: Check -numweeks for next year. // TODO: Check -numweeks for next year.
$i = $first_wkst_offset + $nb_weeks * 7; $i = $first_wkst_offset + $nb_weeks * 7;
if ( $first_wkst_offset != $first_wkst ) { if ($first_wkst_offset != $first_wkst) {
$i = $i - (7 - $first_wkst); $i = $i - (7 - $first_wkst);
} }
if ( $i < $masks['year_len'] ) { if ($i < $masks['year_len']) {
// If week starts in next year, we don't care about it. // If week starts in next year, we don't care about it.
for ( $j = 0; $j < 7; $j++ ) { for ($j = 0; $j < 7; $j++) {
$masks['yearday_is_in_weekno'][$i] = true; $masks['yearday_is_in_weekno'][$i] = true;
$i += 1; $i += 1;
if ( $masks['yearday_to_weekday'][$i] == $this->wkst ) { if ($masks['yearday_to_weekday'][$i] == $this->wkst) {
break; break;
} }
} }
} }
} }
if ( $first_wkst_offset ) { if ($first_wkst_offset) {
// Check last week number of last year as well. // Check last week number of last year as well.
// If first_wkst_offset is 0, either the year started on week start, // If first_wkst_offset is 0, either the year started on week start,
// or week number 1 got days from last year, so there are no // or week number 1 got days from last year, so there are no
// days from last year's last week number in this year. // days from last year's last week number in this year.
if ( ! in_array(-1, $this->byweekno) ) { if (! in_array(-1, $this->byweekno)) {
$weekday_of_1st_yearday = date_create(($year-1).'-01-01 00:00:00')->format('N'); $weekday_of_1st_yearday = date_create(($year-1).'-01-01 00:00:00')->format('N');
$first_wkst_offset_last_year = (7 - $weekday_of_1st_yearday + $this->wkst) % 7; $first_wkst_offset_last_year = (7 - $weekday_of_1st_yearday + $this->wkst) % 7;
$last_year_len = 365 + is_leap_year($year - 1); $last_year_len = 365 + is_leap_year($year - 1);
if ( $first_wkst_offset_last_year >= 4) { if ($first_wkst_offset_last_year >= 4) {
$first_wkst_offset_last_year = 0; $first_wkst_offset_last_year = 0;
$nb_weeks_last_year = 52 + (int) ((($last_year_len + ($weekday_of_1st_yearday - $this->wkst) % 7) % 7) / 4); $nb_weeks_last_year = 52 + (int) ((($last_year_len + ($weekday_of_1st_yearday - $this->wkst) % 7) % 7) / 4);
} }
@ -1287,8 +1287,8 @@ class RRule implements RRuleInterface
$nb_weeks_last_year = -1; $nb_weeks_last_year = -1;
} }
if ( in_array($nb_weeks_last_year, $this->byweekno) ) { if (in_array($nb_weeks_last_year, $this->byweekno)) {
for ( $i = 0; $i < $first_wkst_offset; $i++ ) { for ($i = 0; $i < $first_wkst_offset; $i++) {
$masks['yearday_is_in_weekno'][$i] = true; $masks['yearday_is_in_weekno'][$i] = true;
} }
} }
@ -1315,11 +1315,11 @@ class RRule implements RRuleInterface
*/ */
protected function getTimeSet($hour, $minute, $second) protected function getTimeSet($hour, $minute, $second)
{ {
switch ( $this->freq ) { switch ($this->freq) {
case self::HOURLY: case self::HOURLY:
$set = array(); $set = array();
foreach ( $this->byminute as $minute ) { foreach ($this->byminute as $minute) {
foreach ( $this->bysecond as $second ) { foreach ($this->bysecond as $second) {
// should we use another type? // should we use another type?
$set[] = array($hour, $minute, $second); $set[] = array($hour, $minute, $second);
} }
@ -1328,7 +1328,7 @@ class RRule implements RRuleInterface
return $set; return $set;
case self::MINUTELY: case self::MINUTELY:
$set = array(); $set = array();
foreach ( $this->bysecond as $second ) { foreach ($this->bysecond as $second) {
// should we use another type? // should we use another type?
$set[] = array($hour, $minute, $second); $set[] = array($hour, $minute, $second);
} }
@ -1397,7 +1397,7 @@ class RRule implements RRuleInterface
$dayset = null; $dayset = null;
// go through the cache first // go through the cache first
foreach ( $this->cache as $occurrence ) { foreach ($this->cache as $occurrence) {
yield clone $occurrence; // since DateTime is not immutable, avoid any problem yield clone $occurrence; // since DateTime is not immutable, avoid any problem
$total += 1; $total += 1;
@ -1405,14 +1405,14 @@ class RRule implements RRuleInterface
// if the cache as been used up completely and we now there is nothing else, // if the cache as been used up completely and we now there is nothing else,
// we can stop the generator // we can stop the generator
if ( $total === $this->total ) { if ($total === $this->total) {
return; // end generator return; // end generator
} }
if ( $occurrence ) { if ($occurrence) {
$dtstart = clone $occurrence; // since DateTime is not immutable, clone to avoid any problem $dtstart = clone $occurrence; // since DateTime is not immutable, clone to avoid any problem
// so we skip the last occurrence of the cache // so we skip the last occurrence of the cache
if ( $this->freq === self::SECONDLY ) { if ($this->freq === self::SECONDLY) {
$dtstart->modify('+'.$this->interval.'second'); $dtstart->modify('+'.$this->interval.'second');
} }
else { else {
@ -1420,11 +1420,11 @@ class RRule implements RRuleInterface
} }
} }
if ( $dtstart === null ) { if ($dtstart === null) {
$dtstart = clone $this->dtstart; $dtstart = clone $this->dtstart;
} }
if ( $this->freq === self::WEEKLY ) { if ($this->freq === self::WEEKLY) {
// we align the start date to the WKST, so we can then // we align the start date to the WKST, so we can then
// simply loop by adding +7 days. The Python lib does some // simply loop by adding +7 days. The Python lib does some
// calculation magic at the end of the loop (when incrementing) // calculation magic at the end of the loop (when incrementing)
@ -1442,7 +1442,7 @@ class RRule implements RRuleInterface
$second = (int) $second; $second = (int) $second;
// we initialize the timeset // we initialize the timeset
if ( $this->freq < self::HOURLY ) { if ($this->freq < self::HOURLY) {
// daily, weekly, monthly or yearly // daily, weekly, monthly or yearly
// we don't need to calculate a new timeset // we don't need to calculate a new timeset
$timeset = $this->timeset; $timeset = $this->timeset;
@ -1462,24 +1462,24 @@ class RRule implements RRuleInterface
} }
$max_cycles = self::$REPEAT_CYCLES[$this->freq <= self::DAILY ? $this->freq : self::DAILY]; $max_cycles = self::$REPEAT_CYCLES[$this->freq <= self::DAILY ? $this->freq : self::DAILY];
for ( $i = 0; $i < $max_cycles; $i++ ) { for ($i = 0; $i < $max_cycles; $i++) {
// 1. get an array of all days in the next interval (day, month, week, etc.) // 1. get an array of all days in the next interval (day, month, week, etc.)
// we filter out from this array all days that do not match the BYXXX conditions // we filter out from this array all days that do not match the BYXXX conditions
// to speed things up, we use days of the year (day numbers) instead of date // to speed things up, we use days of the year (day numbers) instead of date
if ( $dayset === null ) { if ($dayset === null) {
// rebuild the various masks and converters // rebuild the various masks and converters
// these arrays will allow fast date operations // these arrays will allow fast date operations
// without relying on date() methods // without relying on date() methods
if ( empty($masks) || $masks['year'] != $year || $masks['month'] != $month ) { if (empty($masks) || $masks['year'] != $year || $masks['month'] != $month) {
$masks = array('year' => '','month'=>''); $masks = array('year' => '','month'=>'');
// only if year has changed // only if year has changed
if ( $masks['year'] != $year ) { if ($masks['year'] != $year) {
$masks['leap_year'] = is_leap_year($year); $masks['leap_year'] = is_leap_year($year);
$masks['year_len'] = 365 + (int) $masks['leap_year']; $masks['year_len'] = 365 + (int) $masks['leap_year'];
$masks['next_year_len'] = 365 + is_leap_year($year + 1); $masks['next_year_len'] = 365 + is_leap_year($year + 1);
$masks['weekday_of_1st_yearday'] = date_create($year."-01-01 00:00:00")->format('N'); $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); $masks['yearday_to_weekday'] = array_slice(self::$WEEKDAY_MASK, $masks['weekday_of_1st_yearday']-1);
if ( $masks['leap_year'] ) { if ($masks['leap_year']) {
$masks['yearday_to_month'] = self::$MONTH_MASK_366; $masks['yearday_to_month'] = self::$MONTH_MASK_366;
$masks['yearday_to_monthday'] = self::$MONTHDAY_MASK_366; $masks['yearday_to_monthday'] = self::$MONTHDAY_MASK_366;
$masks['yearday_to_monthday_negative'] = self::$NEGATIVE_MONTHDAY_MASK_366; $masks['yearday_to_monthday_negative'] = self::$NEGATIVE_MONTHDAY_MASK_366;
@ -1491,12 +1491,12 @@ class RRule implements RRuleInterface
$masks['yearday_to_monthday_negative'] = self::$NEGATIVE_MONTHDAY_MASK; $masks['yearday_to_monthday_negative'] = self::$NEGATIVE_MONTHDAY_MASK;
$masks['last_day_of_month'] = self::$LAST_DAY_OF_MONTH; $masks['last_day_of_month'] = self::$LAST_DAY_OF_MONTH;
} }
if ( $this->byweekno ) { if ($this->byweekno) {
$this->buildWeeknoMask($year, $month, $day, $masks); $this->buildWeeknoMask($year, $month, $day, $masks);
} }
} }
// everytime month or year changes // everytime month or year changes
if ( $this->byweekday_nth ) { if ($this->byweekday_nth) {
$this->buildNthWeekdayMask($year, $month, $day, $masks); $this->buildNthWeekdayMask($year, $month, $day, $masks);
} }
$masks['year'] = $year; $masks['year'] = $year;
@ -1509,37 +1509,37 @@ class RRule implements RRuleInterface
$filtered_set = array(); $filtered_set = array();
// filter out the days based on the BYXXX rules // filter out the days based on the BYXXX rules
foreach ( $dayset as $yearday ) { foreach ($dayset as $yearday) {
if ( $this->bymonth && ! in_array($masks['yearday_to_month'][$yearday], $this->bymonth) ) { if ($this->bymonth && ! in_array($masks['yearday_to_month'][$yearday], $this->bymonth)) {
continue; continue;
} }
if ( $this->byweekno && ! isset($masks['yearday_is_in_weekno'][$yearday]) ) { if ($this->byweekno && ! isset($masks['yearday_is_in_weekno'][$yearday])) {
continue; continue;
} }
if ( $this->byyearday ) { if ($this->byyearday) {
if ( $yearday < $masks['year_len'] ) { if ($yearday < $masks['year_len']) {
if ( ! in_array($yearday + 1, $this->byyearday) && ! in_array(- $masks['year_len'] + $yearday,$this->byyearday) ) { if (! in_array($yearday + 1, $this->byyearday) && ! in_array(- $masks['year_len'] + $yearday,$this->byyearday)) {
continue; continue;
} }
} }
else { // if ( ($yearday >= $masks['year_len'] else { // if ( ($yearday >= $masks['year_len']
if ( ! in_array($yearday + 1 - $masks['year_len'], $this->byyearday) && ! in_array(- $masks['next_year_len'] + $yearday - $mask['year_len'], $this->byyearday) ) { if (! in_array($yearday + 1 - $masks['year_len'], $this->byyearday) && ! in_array(- $masks['next_year_len'] + $yearday - $mask['year_len'], $this->byyearday)) {
continue; continue;
} }
} }
} }
if ( ($this->bymonthday || $this->bymonthday_negative) if (($this->bymonthday || $this->bymonthday_negative)
&& ! in_array($masks['yearday_to_monthday'][$yearday], $this->bymonthday) && ! in_array($masks['yearday_to_monthday'][$yearday], $this->bymonthday)
&& ! in_array($masks['yearday_to_monthday_negative'][$yearday], $this->bymonthday_negative) ) { && ! in_array($masks['yearday_to_monthday_negative'][$yearday], $this->bymonthday_negative)) {
continue; continue;
} }
if ( ( $this->byweekday || $this->byweekday_nth ) if (($this->byweekday || $this->byweekday_nth)
&& ! in_array($masks['yearday_to_weekday'][$yearday], $this->byweekday) && ! in_array($masks['yearday_to_weekday'][$yearday], $this->byweekday)
&& ! isset($masks['yearday_is_nth_weekday'][$yearday]) ) { && ! isset($masks['yearday_is_nth_weekday'][$yearday])) {
continue; continue;
} }
@ -1552,11 +1552,11 @@ class RRule implements RRuleInterface
// so we make a special loop to return while generating // so we make a special loop to return while generating
// TODO this is not needed with a generator anymore // TODO this is not needed with a generator anymore
// we can yield directly within the loop // we can yield directly within the loop
if ( $this->bysetpos && $timeset ) { if ($this->bysetpos && $timeset) {
$filtered_set = array(); $filtered_set = array();
foreach ( $this->bysetpos as $pos ) { foreach ($this->bysetpos as $pos) {
$n = count($timeset); $n = count($timeset);
if ( $pos < 0 ) { if ($pos < 0) {
$pos = $n * count($dayset) + $pos; $pos = $n * count($dayset) + $pos;
} }
else { else {
@ -1565,12 +1565,12 @@ class RRule implements RRuleInterface
$div = (int) ($pos / $n); // daypos $div = (int) ($pos / $n); // daypos
$mod = $pos % $n; // timepos $mod = $pos % $n; // timepos
if ( isset($dayset[$div]) && isset($timeset[$mod]) ) { if (isset($dayset[$div]) && isset($timeset[$mod])) {
$yearday = $dayset[$div]; $yearday = $dayset[$div];
$time = $timeset[$mod]; $time = $timeset[$mod];
// used as array key to ensure uniqueness // used as array key to ensure uniqueness
$tmp = $year.':'.$yearday.':'.$time[0].':'.$time[1].':'.$time[2]; $tmp = $year.':'.$yearday.':'.$time[0].':'.$time[1].':'.$time[2];
if ( ! isset($filtered_set[$tmp]) ) { if (! isset($filtered_set[$tmp])) {
$occurrence = \DateTime::createFromFormat( $occurrence = \DateTime::createFromFormat(
'Y z', 'Y z',
"$year $yearday", "$year $yearday",
@ -1589,18 +1589,18 @@ class RRule implements RRuleInterface
// 2. loop, generate a valid date, and return the result (fake "yield") // 2. loop, generate a valid date, and return the result (fake "yield")
// at the same time, we check the end condition and return null if // at the same time, we check the end condition and return null if
// we need to stop // we need to stop
if ( $this->bysetpos && $timeset ) { if ($this->bysetpos && $timeset) {
// while ( ($occurrence = current($dayset)) !== false ) { // while ( ($occurrence = current($dayset)) !== false ) {
foreach ( $dayset as $occurrence ) { foreach ($dayset as $occurrence) {
// consider end conditions // consider end conditions
if ( $this->until && $occurrence > $this->until ) { if ($this->until && $occurrence > $this->until) {
$this->total = $total; // save total for count() cache $this->total = $total; // save total for count() cache
return; return;
} }
// next($dayset); // next($dayset);
if ( $occurrence >= $dtstart ) { // ignore occurrences before DTSTART if ($occurrence >= $dtstart) { // ignore occurrences before DTSTART
if ( $this->count && $total >= $this->count ) { if ($this->count && $total >= $this->count) {
$this->total = $total; $this->total = $total;
return; return;
} }
@ -1613,7 +1613,7 @@ class RRule implements RRuleInterface
else { else {
// normal loop, without BYSETPOS // normal loop, without BYSETPOS
// while ( ($yearday = current($dayset)) !== false ) { // while ( ($yearday = current($dayset)) !== false ) {
foreach ( $dayset as $yearday ) { foreach ($dayset as $yearday) {
$occurrence = \DateTime::createFromFormat( $occurrence = \DateTime::createFromFormat(
'Y z', 'Y z',
"$year $yearday", "$year $yearday",
@ -1621,17 +1621,17 @@ class RRule implements RRuleInterface
); );
// while ( ($time = current($timeset)) !== false ) { // while ( ($time = current($timeset)) !== false ) {
foreach ( $timeset as $time ) { foreach ($timeset as $time) {
$occurrence->setTime($time[0], $time[1], $time[2]); $occurrence->setTime($time[0], $time[1], $time[2]);
// consider end conditions // consider end conditions
if ( $this->until && $occurrence > $this->until ) { if ($this->until && $occurrence > $this->until) {
$this->total = $total; // save total for count() cache $this->total = $total; // save total for count() cache
return; return;
} }
// next($timeset); // next($timeset);
if ( $occurrence >= $dtstart ) { // ignore occurrences before DTSTART if ($occurrence >= $dtstart) { // ignore occurrences before DTSTART
if ( $this->count && $total >= $this->count ) { if ($this->count && $total >= $this->count) {
$this->total = $total; $this->total = $total;
return; return;
} }
@ -1647,7 +1647,7 @@ class RRule implements RRuleInterface
// 3. we reset the loop to the next interval // 3. we reset the loop to the next interval
$days_increment = 0; $days_increment = 0;
switch ( $this->freq ) { switch ($this->freq) {
case self::YEARLY: case self::YEARLY:
// we do not care about $month or $day not existing, // we do not care about $month or $day not existing,
// they are not used in yearly frequency // they are not used in yearly frequency
@ -1657,12 +1657,12 @@ class RRule implements RRuleInterface
// we do not care about the day of the month not existing // we do not care about the day of the month not existing
// it is not used in monthly frequency // it is not used in monthly frequency
$month = $month + $this->interval; $month = $month + $this->interval;
if ( $month > 12 ) { if ($month > 12) {
$div = (int) ($month / 12); $div = (int) ($month / 12);
$mod = $month % 12; $mod = $month % 12;
$month = $mod; $month = $mod;
$year = $year + $div; $year = $year + $div;
if ( $month == 0 ) { if ($month == 0) {
$month = 12; $month = 12;
$year = $year - 1; $year = $year - 1;
} }
@ -1683,7 +1683,7 @@ class RRule implements RRuleInterface
// call the DateTime method at the very end. // call the DateTime method at the very end.
case self::HOURLY: case self::HOURLY:
if ( empty($dayset) ) { if (empty($dayset)) {
// an empty set means that this day has been filtered out // an empty set means that this day has been filtered out
// by one of the BYXXX rule. So there is no need to // by one of the BYXXX rule. So there is no need to
// examine it any further, we know nothing is going to // examine it any further, we know nothing is going to
@ -1693,21 +1693,21 @@ class RRule implements RRuleInterface
} }
$found = false; $found = false;
for ( $j = 0; $j < self::$REPEAT_CYCLES[self::HOURLY]; $j++ ) { for ($j = 0; $j < self::$REPEAT_CYCLES[self::HOURLY]; $j++) {
$hour += $this->interval; $hour += $this->interval;
$div = (int) ($hour / 24); $div = (int) ($hour / 24);
$mod = $hour % 24; $mod = $hour % 24;
if ( $div ) { if ($div) {
$hour = $mod; $hour = $mod;
$days_increment += $div; $days_increment += $div;
} }
if ( ! $this->byhour || in_array($hour, $this->byhour)) { if (! $this->byhour || in_array($hour, $this->byhour)) {
$found = true; $found = true;
break; break;
} }
} }
if ( ! $found ) { if (! $found) {
$this->total = $total; // save total for count cache $this->total = $total; // save total for count cache
return; // stop the iterator return; // stop the iterator
} }
@ -1715,33 +1715,33 @@ class RRule implements RRuleInterface
$timeset = $this->getTimeSet($hour, $minute, $second); $timeset = $this->getTimeSet($hour, $minute, $second);
break; break;
case self::MINUTELY: case self::MINUTELY:
if ( empty($dayset) ) { if (empty($dayset)) {
$minute += ((int) ((1439 - ($hour*60+$minute)) / $this->interval)) * $this->interval; $minute += ((int) ((1439 - ($hour*60+$minute)) / $this->interval)) * $this->interval;
} }
$found = false; $found = false;
for ( $j = 0; $j < self::$REPEAT_CYCLES[self::MINUTELY]; $j++ ) { for ($j = 0; $j < self::$REPEAT_CYCLES[self::MINUTELY]; $j++) {
$minute += $this->interval; $minute += $this->interval;
$div = (int) ($minute / 60); $div = (int) ($minute / 60);
$mod = $minute % 60; $mod = $minute % 60;
if ( $div ) { if ($div) {
$minute = $mod; $minute = $mod;
$hour += $div; $hour += $div;
$div = (int) ($hour / 24); $div = (int) ($hour / 24);
$mod = $hour % 24; $mod = $hour % 24;
if ( $div ) { if ($div) {
$hour = $mod; $hour = $mod;
$days_increment += $div; $days_increment += $div;
} }
} }
if ( (! $this->byhour || in_array($hour, $this->byhour)) && if ((! $this->byhour || in_array($hour, $this->byhour)) &&
(! $this->byminute || in_array($minute, $this->byminute)) ) { (! $this->byminute || in_array($minute, $this->byminute))) {
$found = true; $found = true;
break; break;
} }
} }
if ( ! $found ) { if (! $found) {
$this->total = $total; // save total for count cache $this->total = $total; // save total for count cache
return; // stop the iterator return; // stop the iterator
} }
@ -1749,40 +1749,40 @@ class RRule implements RRuleInterface
$timeset = $this->getTimeSet($hour, $minute, $second); $timeset = $this->getTimeSet($hour, $minute, $second);
break; break;
case self::SECONDLY: case self::SECONDLY:
if ( empty($dayset) ) { if (empty($dayset)) {
$second += ((int) ((86399 - ($hour*3600 + $minute*60 + $second)) / $this->interval)) * $this->interval; $second += ((int) ((86399 - ($hour*3600 + $minute*60 + $second)) / $this->interval)) * $this->interval;
} }
$found = false; $found = false;
for ( $j = 0; $j < self::$REPEAT_CYCLES[self::SECONDLY]; $j++ ) { for ($j = 0; $j < self::$REPEAT_CYCLES[self::SECONDLY]; $j++) {
$second += $this->interval; $second += $this->interval;
$div = (int) ($second / 60); $div = (int) ($second / 60);
$mod = $second % 60; $mod = $second % 60;
if ( $div ) { if ($div) {
$second = $mod; $second = $mod;
$minute += $div; $minute += $div;
$div = (int) ($minute / 60); $div = (int) ($minute / 60);
$mod = $minute % 60; $mod = $minute % 60;
if ( $div ) { if ($div) {
$minute = $mod; $minute = $mod;
$hour += $div; $hour += $div;
$div = (int) ($hour / 24); $div = (int) ($hour / 24);
$mod = $hour % 24; $mod = $hour % 24;
if ( $div ) { if ($div) {
$hour = $mod; $hour = $mod;
$days_increment += $div; $days_increment += $div;
} }
} }
} }
if ( ( ! $this->byhour || in_array($hour, $this->byhour) ) if ((! $this->byhour || in_array($hour, $this->byhour))
&& ( ! $this->byminute || in_array($minute, $this->byminute) ) && (! $this->byminute || in_array($minute, $this->byminute))
&& ( ! $this->bysecond || in_array($second, $this->bysecond) ) ) { && (! $this->bysecond || in_array($second, $this->bysecond))) {
$found = true; $found = true;
break; break;
} }
} }
if ( ! $found ) { if (! $found) {
$this->total = $total; // save total for count cache $this->total = $total; // save total for count cache
return; // stop the iterator return; // stop the iterator
} }
@ -1791,7 +1791,7 @@ class RRule implements RRuleInterface
break; break;
} }
// here we take a little shortcut from the Python version, by using DateTime // here we take a little shortcut from the Python version, by using DateTime
if ( $days_increment ) { if ($days_increment) {
list($year,$month,$day) = explode('-',date_create("$year-$month-$day")->modify("+ $days_increment days")->format('Y-n-j')); list($year,$month,$day) = explode('-',date_create("$year-$month-$day")->modify("+ $days_increment days")->format('Y-n-j'));
} }
$dayset = null; // reset the loop $dayset = null; // reset the loop
@ -2005,14 +2005,14 @@ class RRule implements RRuleInterface
*/ */
static protected function i18nSelect($array, $n) static protected function i18nSelect($array, $n)
{ {
if ( ! is_array($array) ) { if (! is_array($array)) {
return $array; return $array;
} }
if ( array_key_exists($n, $array) ) { if (array_key_exists($n, $array)) {
return $array[$n]; return $array[$n];
} }
elseif ( array_key_exists('else', $array) ) { elseif (array_key_exists('else', $array)) {
return $array['else']; return $array['else'];
} }
else { else {
@ -2031,7 +2031,7 @@ class RRule implements RRuleInterface
*/ */
static protected function i18nList(array $array, $and = 'and') static protected function i18nList(array $array, $and = 'and')
{ {
if ( count($array) > 1 ) { if (count($array) > 1) {
$last = array_splice($array, -1); $last = array_splice($array, -1);
return sprintf( return sprintf(
'%s %s %s', '%s %s %s',
@ -2051,7 +2051,7 @@ class RRule implements RRuleInterface
*/ */
static protected function intlLoaded() static protected function intlLoaded()
{ {
if ( self::$intl_loaded === null ) { if (self::$intl_loaded === null) {
self::$intl_loaded = extension_loaded('intl'); self::$intl_loaded = extension_loaded('intl');
} }
return self::$intl_loaded; return self::$intl_loaded;
@ -2068,25 +2068,25 @@ class RRule implements RRuleInterface
*/ */
static protected function i18nFilesToLoad($locale, $use_intl = null) static protected function i18nFilesToLoad($locale, $use_intl = null)
{ {
if ( $use_intl === null ) { if ($use_intl === null) {
$use_intl = self::intlLoaded(); $use_intl = self::intlLoaded();
} }
$files = array(); $files = array();
if ( $use_intl ) { if ($use_intl) {
$parsed = \Locale::parseLocale($locale); $parsed = \Locale::parseLocale($locale);
$files[] = $parsed['language']; $files[] = $parsed['language'];
if ( isset($parsed['region']) ) { if (isset($parsed['region'])) {
$files[] = $parsed['language'].'_'.$parsed['region']; $files[] = $parsed['language'].'_'.$parsed['region'];
} }
} }
else { else {
if ( ! preg_match('/^([a-z]{2})(?:(?:_|-)[A-Z][a-z]+)?(?:(?:_|-)([A-Za-z]{2}))?(?:(?:_|-)[A-Z]*)?(?:\.[a-zA-Z\-0-9]*)?$/', $locale, $matches) ) { if (! preg_match('/^([a-z]{2})(?:(?:_|-)[A-Z][a-z]+)?(?:(?:_|-)([A-Za-z]{2}))?(?:(?:_|-)[A-Z]*)?(?:\.[a-zA-Z\-0-9]*)?$/', $locale, $matches)) {
throw new \InvalidArgumentException("The locale option does not look like a valid locale: $locale. For more option install the intl extension."); throw new \InvalidArgumentException("The locale option does not look like a valid locale: $locale. For more option install the intl extension.");
} }
$files[] = $matches[1]; $files[] = $matches[1];
if ( isset($matches[2]) ) { if (isset($matches[2])) {
$files[] = $matches[1].'_'.strtoupper($matches[2]); $files[] = $matches[1].'_'.strtoupper($matches[2]);
} }
} }
@ -2115,20 +2115,20 @@ class RRule implements RRuleInterface
$base_path = __DIR__.'/i18n'; $base_path = __DIR__.'/i18n';
$result = array(); $result = array();
foreach ( $files as $file ) { foreach ($files as $file) {
// if the file exists in $custom_path, it overrides the default // if the file exists in $custom_path, it overrides the default
if ( $custom_path && is_file("$custom_path/$file.php") ) { if ($custom_path && is_file("$custom_path/$file.php")) {
$path = "$custom_path/$file.php"; $path = "$custom_path/$file.php";
} }
else { else {
$path = "$base_path/$file.php"; $path = "$base_path/$file.php";
} }
if ( isset(self::$i18n[$path]) ) { if (isset(self::$i18n[$path])) {
$result = array_merge($result, self::$i18n[$path]); $result = array_merge($result, self::$i18n[$path]);
} }
elseif ( is_file($path) && is_readable($path) ) { elseif (is_file($path) && is_readable($path)) {
self::$i18n[$path] = include $path; self::$i18n[$path] = include $path;
$result = array_merge($result, self::$i18n[$path]); $result = array_merge($result, self::$i18n[$path]);
} }
@ -2137,7 +2137,7 @@ class RRule implements RRuleInterface
} }
} }
if ( empty($result) ) { if (empty($result)) {
if (!is_null($fallback)) { if (!is_null($fallback)) {
return self::i18nLoad($fallback, null, $use_intl); return self::i18nLoad($fallback, null, $use_intl);
} }
@ -2171,7 +2171,7 @@ class RRule implements RRuleInterface
*/ */
public function humanReadable(array $opt = array()) public function humanReadable(array $opt = array())
{ {
if ( ! isset($opt['use_intl']) ) { if (! isset($opt['use_intl'])) {
$opt['use_intl'] = self::intlLoaded(); $opt['use_intl'] = self::intlLoaded();
} }
@ -2187,21 +2187,21 @@ class RRule implements RRuleInterface
); );
// attempt to detect default locale // attempt to detect default locale
if ( $opt['use_intl'] ) { if ($opt['use_intl']) {
$default_opt['locale'] = \Locale::getDefault(); $default_opt['locale'] = \Locale::getDefault();
} else { } else {
$default_opt['locale'] = setlocale(LC_ALL, 0); $default_opt['locale'] = setlocale(LC_ALL, 0);
if ( $default_opt['locale'] == 'C' ) { if ($default_opt['locale'] == 'C') {
$default_opt['locale'] = 'en'; $default_opt['locale'] = 'en';
} }
} }
if ( $opt['use_intl'] ) { if ($opt['use_intl']) {
$default_opt['date_format'] = \IntlDateFormatter::SHORT; $default_opt['date_format'] = \IntlDateFormatter::SHORT;
if ( $this->freq >= self::SECONDLY || not_empty($this->rule['BYSECOND']) ) { if ($this->freq >= self::SECONDLY || not_empty($this->rule['BYSECOND'])) {
$default_opt['time_format'] = \IntlDateFormatter::LONG; $default_opt['time_format'] = \IntlDateFormatter::LONG;
} }
elseif ( $this->freq >= self::HOURLY || not_empty($this->rule['BYHOUR']) || not_empty($this->rule['BYMINUTE']) ) { elseif ($this->freq >= self::HOURLY || not_empty($this->rule['BYHOUR']) || not_empty($this->rule['BYMINUTE'])) {
$default_opt['time_format'] = \IntlDateFormatter::SHORT; $default_opt['time_format'] = \IntlDateFormatter::SHORT;
} }
else { else {
@ -2213,17 +2213,17 @@ class RRule implements RRuleInterface
$i18n = self::i18nLoad($opt['locale'], $opt['fallback'], $opt['use_intl'], $opt['custom_path']); $i18n = self::i18nLoad($opt['locale'], $opt['fallback'], $opt['use_intl'], $opt['custom_path']);
if ( $opt['date_formatter'] && ! is_callable($opt['date_formatter']) ) { if ($opt['date_formatter'] && ! is_callable($opt['date_formatter'])) {
throw new \InvalidArgumentException('The option date_formatter must callable'); throw new \InvalidArgumentException('The option date_formatter must callable');
} }
if ( ! $opt['date_formatter'] ) { if (! $opt['date_formatter']) {
if ( $opt['use_intl'] ) { if ($opt['use_intl']) {
$timezone = $this->dtstart->getTimezone()->getName(); $timezone = $this->dtstart->getTimezone()->getName();
if ( $timezone === 'Z' ) { if ($timezone === 'Z') {
$timezone = 'GMT'; // otherwise IntlDateFormatter::create fails because... reasons. $timezone = 'GMT'; // otherwise IntlDateFormatter::create fails because... reasons.
} elseif ( preg_match('/[-+]\d{2}/',$timezone) ) { } elseif (preg_match('/[-+]\d{2}/',$timezone)) {
$timezone = 'GMT'.$timezone; // otherwise IntlDateFormatter::create fails because... other reasons. $timezone = 'GMT'.$timezone; // otherwise IntlDateFormatter::create fails because... other reasons.
} }
$formatter = \IntlDateFormatter::create( $formatter = \IntlDateFormatter::create(
@ -2232,7 +2232,7 @@ class RRule implements RRuleInterface
$opt['time_format'], $opt['time_format'],
$timezone $timezone
); );
if ( ! $formatter ) { if (! $formatter) {
throw new \RuntimeException('IntlDateFormatter::create() failed. Error Code: '.intl_get_error_code().' "'. intl_get_error_message().'" (this should not happen, please open a bug report!)'); throw new \RuntimeException('IntlDateFormatter::create() failed. Error Code: '.intl_get_error_code().' "'. intl_get_error_message().'" (this should not happen, please open a bug report!)');
} }
$opt['date_formatter'] = function($date) use ($formatter) { $opt['date_formatter'] = function($date) use ($formatter) {
@ -2269,9 +2269,9 @@ class RRule implements RRuleInterface
); );
// BYXXX rules // BYXXX rules
if ( not_empty($this->rule['BYMONTH']) ) { if (not_empty($this->rule['BYMONTH'])) {
$tmp = $this->bymonth; $tmp = $this->bymonth;
foreach ( $tmp as & $value) { foreach ($tmp as & $value) {
$value = $i18n['months'][$value]; $value = $i18n['months'][$value];
} }
$parts['bymonth'] = strtr(self::i18nSelect($i18n['bymonth'], count($tmp)), array( $parts['bymonth'] = strtr(self::i18nSelect($i18n['bymonth'], count($tmp)), array(
@ -2279,10 +2279,10 @@ class RRule implements RRuleInterface
)); ));
} }
if ( not_empty($this->rule['BYWEEKNO']) ) { if (not_empty($this->rule['BYWEEKNO'])) {
// XXX negative week number are not great here // XXX negative week number are not great here
$tmp = $this->byweekno; $tmp = $this->byweekno;
foreach ( $tmp as & $value ) { foreach ($tmp as & $value) {
$value = strtr($i18n['nth_weekno'], array( $value = strtr($i18n['nth_weekno'], array(
'%{n}' => $value '%{n}' => $value
)); ));
@ -2295,9 +2295,9 @@ class RRule implements RRuleInterface
); );
} }
if ( not_empty($this->rule['BYYEARDAY']) ) { if (not_empty($this->rule['BYYEARDAY'])) {
$tmp = $this->byyearday; $tmp = $this->byyearday;
foreach ( $tmp as & $value ) { foreach ($tmp as & $value) {
$value = strtr(self::i18nSelect($i18n[$value>0?'nth_yearday':'-nth_yearday'],$value), array( $value = strtr(self::i18nSelect($i18n[$value>0?'nth_yearday':'-nth_yearday'],$value), array(
'%{n}' => abs($value) '%{n}' => abs($value)
)); ));
@ -2312,11 +2312,11 @@ class RRule implements RRuleInterface
$parts['byyearday'] = $tmp; $parts['byyearday'] = $tmp;
} }
if ( not_empty($this->rule['BYMONTHDAY']) ) { if (not_empty($this->rule['BYMONTHDAY'])) {
$parts['bymonthday'] = array(); $parts['bymonthday'] = array();
if ( $this->bymonthday ) { if ($this->bymonthday) {
$tmp = $this->bymonthday; $tmp = $this->bymonthday;
foreach ( $tmp as & $value ) { foreach ($tmp as & $value) {
$value = strtr(self::i18nSelect($i18n['nth_monthday'],$value), array( $value = strtr(self::i18nSelect($i18n['nth_monthday'],$value), array(
'%{n}' => $value '%{n}' => $value
)); ));
@ -2330,9 +2330,9 @@ class RRule implements RRuleInterface
)); ));
$parts['bymonthday'][] = $tmp; $parts['bymonthday'][] = $tmp;
} }
if ( $this->bymonthday_negative ) { if ($this->bymonthday_negative) {
$tmp = $this->bymonthday_negative; $tmp = $this->bymonthday_negative;
foreach ( $tmp as & $value ) { foreach ($tmp as & $value) {
$value = strtr(self::i18nSelect($i18n['-nth_monthday'],$value), array( $value = strtr(self::i18nSelect($i18n['-nth_monthday'],$value), array(
'%{n}' => -$value '%{n}' => -$value
)); ));
@ -2349,20 +2349,20 @@ class RRule implements RRuleInterface
$parts['bymonthday'] = implode(' '.$i18n['and'],$parts['bymonthday']); $parts['bymonthday'] = implode(' '.$i18n['and'],$parts['bymonthday']);
} }
if ( not_empty($this->rule['BYDAY']) ) { if (not_empty($this->rule['BYDAY'])) {
$parts['byweekday'] = array(); $parts['byweekday'] = array();
if ( $this->byweekday ) { if ($this->byweekday) {
$tmp = $this->byweekday; $tmp = $this->byweekday;
foreach ( $tmp as & $value ) { foreach ($tmp as & $value) {
$value = $i18n['weekdays'][$value]; $value = $i18n['weekdays'][$value];
} }
$parts['byweekday'][] = strtr(self::i18nSelect($i18n['byweekday'], count($tmp)), array( $parts['byweekday'][] = strtr(self::i18nSelect($i18n['byweekday'], count($tmp)), array(
'%{weekdays}' => self::i18nList($tmp, $i18n['and']) '%{weekdays}' => self::i18nList($tmp, $i18n['and'])
)); ));
} }
if ( $this->byweekday_nth ) { if ($this->byweekday_nth) {
$tmp = $this->byweekday_nth; $tmp = $this->byweekday_nth;
foreach ( $tmp as & $value ) { foreach ($tmp as & $value) {
list($day, $n) = $value; list($day, $n) = $value;
$value = strtr(self::i18nSelect($i18n[$n>0?'nth_weekday':'-nth_weekday'], $n), array( $value = strtr(self::i18nSelect($i18n[$n>0?'nth_weekday':'-nth_weekday'], $n), array(
'%{weekday}' => $i18n['weekdays'][$day], '%{weekday}' => $i18n['weekdays'][$day],
@ -2381,9 +2381,9 @@ class RRule implements RRuleInterface
$parts['byweekday'] = implode(' '.$i18n['and'],$parts['byweekday']); $parts['byweekday'] = implode(' '.$i18n['and'],$parts['byweekday']);
} }
if ( not_empty($this->rule['BYHOUR']) ) { if (not_empty($this->rule['BYHOUR'])) {
$tmp = $this->byhour; $tmp = $this->byhour;
foreach ( $tmp as &$value) { foreach ($tmp as &$value) {
$value = strtr($i18n['nth_hour'], array( $value = strtr($i18n['nth_hour'], array(
'%{n}' => $value '%{n}' => $value
)); ));
@ -2393,9 +2393,9 @@ class RRule implements RRuleInterface
)); ));
} }
if ( not_empty($this->rule['BYMINUTE']) ) { if (not_empty($this->rule['BYMINUTE'])) {
$tmp = $this->byminute; $tmp = $this->byminute;
foreach ( $tmp as &$value) { foreach ($tmp as &$value) {
$value = strtr($i18n['nth_minute'], array( $value = strtr($i18n['nth_minute'], array(
'%{n}' => $value '%{n}' => $value
)); ));
@ -2405,9 +2405,9 @@ class RRule implements RRuleInterface
)); ));
} }
if ( not_empty($this->rule['BYSECOND']) ) { if (not_empty($this->rule['BYSECOND'])) {
$tmp = $this->bysecond; $tmp = $this->bysecond;
foreach ( $tmp as &$value) { foreach ($tmp as &$value) {
$value = strtr($i18n['nth_second'], array( $value = strtr($i18n['nth_second'], array(
'%{n}' => $value '%{n}' => $value
)); ));
@ -2417,9 +2417,9 @@ class RRule implements RRuleInterface
)); ));
} }
if ( $this->bysetpos ) { if ($this->bysetpos) {
$tmp = $this->bysetpos; $tmp = $this->bysetpos;
foreach ( $tmp as & $value ) { foreach ($tmp as & $value) {
$value = strtr(self::i18nSelect($i18n[$value>0?'nth_setpos':'-nth_setpos'],$value), array( $value = strtr(self::i18nSelect($i18n[$value>0?'nth_setpos':'-nth_setpos'],$value), array(
'%{n}' => abs($value) '%{n}' => abs($value)
)); ));
@ -2430,7 +2430,7 @@ class RRule implements RRuleInterface
$parts['bysetpos'] = $tmp; $parts['bysetpos'] = $tmp;
} }
if ( $opt['include_start'] ) { if ($opt['include_start']) {
// from X // from X
$parts['start'] = strtr($i18n['dtstart'], array( $parts['start'] = strtr($i18n['dtstart'], array(
'%{date}' => $opt['date_formatter']($this->dtstart) '%{date}' => $opt['date_formatter']($this->dtstart)
@ -2438,18 +2438,18 @@ class RRule implements RRuleInterface
} }
// to X, or N times, or indefinitely // to X, or N times, or indefinitely
if ( $opt['include_until'] ) { if ($opt['include_until']) {
if ( ! $this->until && ! $this->count ) { if (! $this->until && ! $this->count) {
if ( $opt['explicit_infinite'] ) { if ($opt['explicit_infinite']) {
$parts['end'] = $i18n['infinite']; $parts['end'] = $i18n['infinite'];
} }
} }
elseif ( $this->until ) { elseif ($this->until) {
$parts['end'] = strtr($i18n['until'], array( $parts['end'] = strtr($i18n['until'], array(
'%{date}' => $opt['date_formatter']($this->until) '%{date}' => $opt['date_formatter']($this->until)
)); ));
} }
elseif ( $this->count ) { elseif ($this->count) {
$parts['end'] = strtr( $parts['end'] = strtr(
self::i18nSelect($i18n['count'], $this->count), self::i18nSelect($i18n['count'], $this->count),
array( array(

View File

@ -26,25 +26,25 @@ trait RRuleTrait
*/ */
public function getOccurrences($limit = null) public function getOccurrences($limit = null)
{ {
if ( ! $limit && $this->isInfinite() ) { if (! $limit && $this->isInfinite()) {
throw new \LogicException('Cannot get all occurrences of an infinite recurrence rule.'); throw new \LogicException('Cannot get all occurrences of an infinite recurrence rule.');
} }
if ( $limit !== null && $limit !== false && $limit < 0 ) { if ($limit !== null && $limit !== false && $limit < 0) {
throw new \InvalidArgumentException('$limit cannot be negative'); throw new \InvalidArgumentException('$limit cannot be negative');
} }
// cached version already computed // cached version already computed
$iterator = $this; $iterator = $this;
if ( $this->total !== null ) { if ($this->total !== null) {
$iterator = $this->cache; $iterator = $this->cache;
} }
$res = array(); $res = array();
$n = 0; $n = 0;
foreach ( $iterator as $occurrence ) { foreach ($iterator as $occurrence) {
$res[] = clone $occurrence; // we have to clone because DateTime is not immutable $res[] = clone $occurrence; // we have to clone because DateTime is not immutable
$n += 1; $n += 1;
if ( $limit && $n >= $limit ) { if ($limit && $n >= $limit) {
break; break;
} }
} }
@ -61,38 +61,38 @@ trait RRuleTrait
*/ */
public function getOccurrencesBetween($begin, $end, $limit = null) public function getOccurrencesBetween($begin, $end, $limit = null)
{ {
if ( $begin !== null ) { if ($begin !== null) {
$begin = self::parseDate($begin); $begin = self::parseDate($begin);
} }
if ( $end !== null ) { if ($end !== null) {
$end = self::parseDate($end); $end = self::parseDate($end);
} }
elseif ( ! $limit && $this->isInfinite() ) { elseif (! $limit && $this->isInfinite()) {
throw new \LogicException('Cannot get all occurrences of an infinite recurrence rule.'); throw new \LogicException('Cannot get all occurrences of an infinite recurrence rule.');
} }
if ( $limit !== null && $limit !== false && $limit < 0 ) { if ($limit !== null && $limit !== false && $limit < 0) {
throw new \InvalidArgumentException('$limit cannot be negative'); throw new \InvalidArgumentException('$limit cannot be negative');
} }
$iterator = $this; $iterator = $this;
if ( $this->total !== null ) { if ($this->total !== null) {
$iterator = $this->cache; $iterator = $this->cache;
} }
$res = array(); $res = array();
$n = 0; $n = 0;
foreach ( $iterator as $occurrence ) { foreach ($iterator as $occurrence) {
if ( $begin !== null && $occurrence < $begin ) { if ($begin !== null && $occurrence < $begin) {
continue; continue;
} }
if ( $end !== null && $occurrence > $end ) { if ($end !== null && $occurrence > $end) {
break; break;
} }
$res[] = clone $occurrence; $res[] = clone $occurrence;
$n += 1; $n += 1;
if ( $limit && $n >= $limit ) { if ($limit && $n >= $limit) {
break; break;
} }
} }
@ -101,7 +101,7 @@ trait RRuleTrait
public function getOccurrencesAfter($date, $inclusive = false, $limit = null) public function getOccurrencesAfter($date, $inclusive = false, $limit = null)
{ {
if ( $inclusive || ! $this->occursAt($date) ) { if ($inclusive || ! $this->occursAt($date)) {
return $this->getOccurrencesBetween($date, null, $limit); return $this->getOccurrencesBetween($date, null, $limit);
} }
@ -112,7 +112,7 @@ trait RRuleTrait
public function getNthOccurrenceAfter($date, $index) public function getNthOccurrenceAfter($date, $index)
{ {
if ( $index <= 0 ) { if ($index <= 0) {
throw new \InvalidArgumentException("Index must be a positive integer"); throw new \InvalidArgumentException("Index must be a positive integer");
} }
@ -126,12 +126,12 @@ trait RRuleTrait
// we need to get everything // we need to get everything
$occurrences = $this->getOccurrencesBetween(null, $date); $occurrences = $this->getOccurrencesBetween(null, $date);
if ( ! $inclusive && $this->occursAt($date) ) { if (! $inclusive && $this->occursAt($date)) {
array_pop($occurrences); array_pop($occurrences);
} }
// the limit is counted from $date // the limit is counted from $date
if ( $limit ) { if ($limit) {
$occurrences = array_slice($occurrences, -1 * $limit); $occurrences = array_slice($occurrences, -1 * $limit);
} }
@ -140,13 +140,13 @@ trait RRuleTrait
public function getNthOccurrenceBefore($date, $index) public function getNthOccurrenceBefore($date, $index)
{ {
if ( $index <= 0 ) { if ($index <= 0) {
throw new \InvalidArgumentException("Index must be a positive integer"); throw new \InvalidArgumentException("Index must be a positive integer");
} }
$occurrences = $this->getOccurrencesBefore($date, false, $index); $occurrences = $this->getOccurrencesBefore($date, false, $index);
if ( sizeof($occurrences) < $index ) { if (sizeof($occurrences) < $index) {
return null; return null;
} }
@ -155,14 +155,14 @@ trait RRuleTrait
public function getNthOccurrenceFrom($date, $index) public function getNthOccurrenceFrom($date, $index)
{ {
if ( ! is_numeric($index) ) { if (! is_numeric($index)) {
throw new \InvalidArgumentException('Malformed index (must be a numeric)'); throw new \InvalidArgumentException('Malformed index (must be a numeric)');
} }
if ( $index == 0 ) { if ($index == 0) {
return $this->occursAt($date) ? self::parseDate($date) : null; return $this->occursAt($date) ? self::parseDate($date) : null;
} }
elseif ( $index > 0 ) { elseif ($index > 0) {
return $this->getNthOccurrenceAfter($date, $index); return $this->getNthOccurrenceAfter($date, $index);
} }
else { else {
@ -180,9 +180,9 @@ trait RRuleTrait
static public function parseDate($date) static public function parseDate($date)
{ {
// DateTimeInterface is only on PHP 5.5+, and includes DateTimeImmutable // DateTimeInterface is only on PHP 5.5+, and includes DateTimeImmutable
if ( ! $date instanceof \DateTime && ! $date instanceof \DateTimeInterface ) { if (! $date instanceof \DateTime && ! $date instanceof \DateTimeInterface) {
try { try {
if ( is_integer($date) ) { if (is_integer($date)) {
$date = \DateTime::createFromFormat('U',$date); $date = \DateTime::createFromFormat('U',$date);
$date->setTimezone(new \DateTimeZone('UTC')); // default is +00:00 (see issue #15) $date->setTimezone(new \DateTimeZone('UTC')); // default is +00:00 (see issue #15)
} }

View File

@ -62,7 +62,7 @@ class RSet implements RRuleInterface
*/ */
public function __construct($string = null, $default_dtstart = null) public function __construct($string = null, $default_dtstart = null)
{ {
if ( $string && is_string($string) ) { if ($string && is_string($string)) {
$string = trim($string); $string = trim($string);
$rrules = array(); $rrules = array();
$exrules = array(); $exrules = array();
@ -72,19 +72,19 @@ class RSet implements RRuleInterface
// parse // parse
$lines = explode("\n", $string); $lines = explode("\n", $string);
foreach ( $lines as $line ) { foreach ($lines as $line) {
$line = trim($line); $line = trim($line);
if ( strpos($line,':') === false ) { if (strpos($line,':') === false) {
throw new \InvalidArgumentException('Failed to parse RFC string, line is not starting with a property name followed by ":"'); throw new \InvalidArgumentException('Failed to parse RFC string, line is not starting with a property name followed by ":"');
} }
list($property_name,$property_value) = explode(':',$line); list($property_name,$property_value) = explode(':',$line);
$tmp = explode(";",$property_name); $tmp = explode(";",$property_name);
$property_name = $tmp[0]; $property_name = $tmp[0];
switch ( strtoupper($property_name) ) { switch (strtoupper($property_name)) {
case 'DTSTART': case 'DTSTART':
if ( $default_dtstart || $dtstart !== null ) { if ($default_dtstart || $dtstart !== null) {
throw new \InvalidArgumentException('Failed to parse RFC string, multiple DTSTART found'); throw new \InvalidArgumentException('Failed to parse RFC string, multiple DTSTART found');
} }
$dtstart = $line; $dtstart = $line;
@ -105,26 +105,26 @@ class RSet implements RRuleInterface
throw new \InvalidArgumentException("Failed to parse RFC, unknown property: $property_name"); throw new \InvalidArgumentException("Failed to parse RFC, unknown property: $property_name");
} }
} }
foreach ( $rrules as $rrule ) { foreach ($rrules as $rrule) {
if ( $dtstart ) { if ($dtstart) {
$rrule = $dtstart."\n".$rrule; $rrule = $dtstart."\n".$rrule;
} }
$this->addRRule(new RRule($rrule, $default_dtstart)); $this->addRRule(new RRule($rrule, $default_dtstart));
} }
foreach ( $exrules as $rrule ) { foreach ($exrules as $rrule) {
if ( $dtstart ) { if ($dtstart) {
$rrule = $dtstart."\n".$rrule; $rrule = $dtstart."\n".$rrule;
} }
$this->addExRule(new RRule($rrule, $default_dtstart)); $this->addExRule(new RRule($rrule, $default_dtstart));
} }
foreach ( $rdates as $date ) { foreach ($rdates as $date) {
$this->addDate($date); $this->addDate($date);
} }
foreach ( $exdates as $date ) { foreach ($exdates as $date) {
$this->addExDate($date); $this->addExDate($date);
} }
} }
@ -138,10 +138,10 @@ class RSet implements RRuleInterface
*/ */
public function addRRule($rrule) public function addRRule($rrule)
{ {
if ( is_string($rrule) || is_array($rrule) ) { if (is_string($rrule) || is_array($rrule)) {
$rrule = new RRule($rrule); $rrule = new RRule($rrule);
} }
elseif ( ! $rrule instanceof RRuleInterface ) { elseif (! $rrule instanceof RRuleInterface) {
throw new \InvalidArgumentException('The rule must be a string, an array, or implement RRuleInterface'); throw new \InvalidArgumentException('The rule must be a string, an array, or implement RRuleInterface');
} }
@ -174,10 +174,10 @@ class RSet implements RRuleInterface
*/ */
public function addExRule($rrule) public function addExRule($rrule)
{ {
if ( is_string($rrule) || is_array($rrule) ) { if (is_string($rrule) || is_array($rrule)) {
$rrule = new RRule($rrule); $rrule = new RRule($rrule);
} }
elseif ( ! $rrule instanceof RRuleInterface ) { elseif (! $rrule instanceof RRuleInterface) {
throw new \InvalidArgumentException('The rule must be a string, an array or implement RRuleInterface'); throw new \InvalidArgumentException('The rule must be a string, an array or implement RRuleInterface');
} }
@ -235,7 +235,7 @@ class RSet implements RRuleInterface
$date_to_remove = RRule::parseDate($date); $date_to_remove = RRule::parseDate($date);
$index = array_search($date_to_remove, $this->rdates); $index = array_search($date_to_remove, $this->rdates);
if ( $index !== false ) { if ($index !== false) {
unset($this->rdates[$index]); unset($this->rdates[$index]);
$this->rdates = array_values($this->rdates); $this->rdates = array_values($this->rdates);
} }
@ -309,7 +309,7 @@ class RSet implements RRuleInterface
$date_to_remove = RRule::parseDate($date); $date_to_remove = RRule::parseDate($date);
$index = array_search($date_to_remove, $this->exdates); $index = array_search($date_to_remove, $this->exdates);
if ( $index !== false ) { if ($index !== false) {
unset($this->exdates[$index]); unset($this->exdates[$index]);
$this->exdates = array_values($this->exdates); $this->exdates = array_values($this->exdates);
} }
@ -388,10 +388,10 @@ class RSet implements RRuleInterface
*/ */
public function isInfinite() public function isInfinite()
{ {
if ( $this->infinite === null ) { if ($this->infinite === null) {
$this->infinite = false; $this->infinite = false;
foreach ( $this->rrules as $rrule ) { foreach ($this->rrules as $rrule) {
if ( $rrule->isInfinite() ) { if ($rrule->isInfinite()) {
$this->infinite = true; $this->infinite = true;
break; break;
} }
@ -408,22 +408,22 @@ class RSet implements RRuleInterface
*/ */
public function getOccurrences($limit = null) public function getOccurrences($limit = null)
{ {
if ( !$limit && $this->isInfinite() ) { if (!$limit && $this->isInfinite()) {
throw new \LogicException('Cannot get all occurrences of an infinite recurrence set.'); throw new \LogicException('Cannot get all occurrences of an infinite recurrence set.');
} }
// cached version already computed // cached version already computed
$iterator = $this; $iterator = $this;
if ( $this->total !== null ) { if ($this->total !== null) {
$iterator = $this->cache; $iterator = $this->cache;
} }
$res = array(); $res = array();
$n = 0; $n = 0;
foreach ( $iterator as $occurrence ) { foreach ($iterator as $occurrence) {
$res[] = clone $occurrence; // we have to clone because DateTime is not immutable $res[] = clone $occurrence; // we have to clone because DateTime is not immutable
$n += 1; $n += 1;
if ( $limit && $n >= $limit ) { if ($limit && $n >= $limit) {
break; break;
} }
} }
@ -440,26 +440,26 @@ class RSet implements RRuleInterface
{ {
$date = RRule::parseDate($date); $date = RRule::parseDate($date);
if ( in_array($date, $this->cache) ) { if (in_array($date, $this->cache)) {
// in the cache (whether cache is complete or not) // in the cache (whether cache is complete or not)
return true; return true;
} }
elseif ( $this->total !== null ) { elseif ($this->total !== null) {
// cache complete and not in cache // cache complete and not in cache
return false; return false;
} }
// test if it *should* occur (before exclusion) // test if it *should* occur (before exclusion)
$occurs = false; $occurs = false;
foreach ( $this->rdates as $rdate ) { foreach ($this->rdates as $rdate) {
if ( $rdate == $date ) { if ($rdate == $date) {
$occurs = true; $occurs = true;
break; break;
} }
} }
if ( ! $occurs ) { if (! $occurs) {
foreach ( $this->rrules as $rrule ) { foreach ($this->rrules as $rrule) {
if ( $rrule->occursAt($date) ) { if ($rrule->occursAt($date)) {
$occurs = true; $occurs = true;
break; break;
} }
@ -467,14 +467,14 @@ class RSet implements RRuleInterface
} }
// if it should occur, test if it's excluded // if it should occur, test if it's excluded
if ( $occurs ) { if ($occurs) {
foreach ( $this->exdates as $exdate ) { foreach ($this->exdates as $exdate) {
if ( $exdate == $date ) { if ($exdate == $date) {
return false; return false;
} }
} }
foreach ( $this->exrules as $exrule ) { foreach ($this->exrules as $exrule) {
if ( $exrule->occursAt($date) ) { if ($exrule->occursAt($date)) {
return false; return false;
} }
} }
@ -549,27 +549,27 @@ class RSet implements RRuleInterface
*/ */
public function offsetGet($offset) public function offsetGet($offset)
{ {
if ( ! is_numeric($offset) || $offset < 0 || is_float($offset) ) { if (! is_numeric($offset) || $offset < 0 || is_float($offset)) {
throw new \InvalidArgumentException('Illegal offset type: '.gettype($offset)); throw new \InvalidArgumentException('Illegal offset type: '.gettype($offset));
} }
if ( isset($this->cache[$offset]) ) { if (isset($this->cache[$offset])) {
// found in cache // found in cache
return clone $this->cache[$offset]; return clone $this->cache[$offset];
} }
elseif ( $this->total !== null ) { elseif ($this->total !== null) {
// cache complete and not found in cache // cache complete and not found in cache
return null; return null;
} }
// not in cache and cache not complete, we have to loop to find it // not in cache and cache not complete, we have to loop to find it
$i = 0; $i = 0;
foreach ( $this as $occurrence ) { foreach ($this as $occurrence) {
if ( $i == $offset ) { if ($i == $offset) {
return $occurrence; return $occurrence;
} }
$i++; $i++;
if ( $i > $offset ) { if ($i > $offset) {
break; break;
} }
} }
@ -603,12 +603,12 @@ class RSet implements RRuleInterface
*/ */
public function count() public function count()
{ {
if ( $this->isInfinite() ) { if ($this->isInfinite()) {
throw new \LogicException('Cannot count an infinite recurrence set.'); throw new \LogicException('Cannot count an infinite recurrence set.');
} }
if ( $this->total === null ) { if ($this->total === null) {
foreach ( $this as $occurrence ) {} foreach ($this as $occurrence) {}
} }
return $this->total; return $this->total;
@ -643,18 +643,18 @@ class RSet implements RRuleInterface
$previous_occurrence = null; $previous_occurrence = null;
$total = 0; $total = 0;
foreach ( $this->cache as $occurrence ) { foreach ($this->cache as $occurrence) {
yield clone $occurrence; // since DateTime is not immutable, avoid any problem yield clone $occurrence; // since DateTime is not immutable, avoid any problem
$total += 1; $total += 1;
} }
if ( $this->rlist_heap === null ) { if ($this->rlist_heap === null) {
// rrules + rdate // rrules + rdate
$this->rlist_heap = new \SplMinHeap(); $this->rlist_heap = new \SplMinHeap();
$this->rlist_iterator = new \MultipleIterator(\MultipleIterator::MIT_NEED_ANY); $this->rlist_iterator = new \MultipleIterator(\MultipleIterator::MIT_NEED_ANY);
$this->rlist_iterator->attachIterator(new \ArrayIterator($this->rdates)); $this->rlist_iterator->attachIterator(new \ArrayIterator($this->rdates));
foreach ( $this->rrules as $rrule ) { foreach ($this->rrules as $rrule) {
$this->rlist_iterator->attachIterator($rrule->getIterator()); $this->rlist_iterator->attachIterator($rrule->getIterator());
} }
$this->rlist_iterator->rewind(); $this->rlist_iterator->rewind();
@ -664,28 +664,28 @@ class RSet implements RRuleInterface
$this->exlist_iterator = new \MultipleIterator(\MultipleIterator::MIT_NEED_ANY); $this->exlist_iterator = new \MultipleIterator(\MultipleIterator::MIT_NEED_ANY);
$this->exlist_iterator->attachIterator(new \ArrayIterator($this->exdates)); $this->exlist_iterator->attachIterator(new \ArrayIterator($this->exdates));
foreach ( $this->exrules as $rrule ) { foreach ($this->exrules as $rrule) {
$this->exlist_iterator->attachIterator($rrule->getIterator()); $this->exlist_iterator->attachIterator($rrule->getIterator());
} }
$this->exlist_iterator->rewind(); $this->exlist_iterator->rewind();
} }
while ( true ) { while (true) {
foreach ( $this->rlist_iterator->current() as $date ) { foreach ($this->rlist_iterator->current() as $date) {
if ( $date !== null ) { if ($date !== null) {
$this->rlist_heap->insert($date); $this->rlist_heap->insert($date);
} }
} }
$this->rlist_iterator->next(); // advance the iterator for the next call $this->rlist_iterator->next(); // advance the iterator for the next call
if ( $this->rlist_heap->isEmpty() ) { if ($this->rlist_heap->isEmpty()) {
break; // exit the loop to stop the iterator break; // exit the loop to stop the iterator
} }
$occurrence = $this->rlist_heap->top(); $occurrence = $this->rlist_heap->top();
$this->rlist_heap->extract(); // remove the occurrence from the heap $this->rlist_heap->extract(); // remove the occurrence from the heap
if ( $occurrence == $previous_occurrence ) { if ($occurrence == $previous_occurrence) {
continue; // skip, was already considered continue; // skip, was already considered
} }
@ -694,24 +694,24 @@ class RSet implements RRuleInterface
// (they will be discarded), and then check if the date is the same // (they will be discarded), and then check if the date is the same
// as occurrence (in which case it is discarded) // as occurrence (in which case it is discarded)
$excluded = false; $excluded = false;
while ( true ) { while (true) {
foreach ( $this->exlist_iterator->current() as $date ) { foreach ($this->exlist_iterator->current() as $date) {
if ( $date !== null ) { if ($date !== null) {
$this->exlist_heap->insert($date); $this->exlist_heap->insert($date);
} }
} }
$this->exlist_iterator->next(); // advance the iterator for the next call $this->exlist_iterator->next(); // advance the iterator for the next call
if ( $this->exlist_heap->isEmpty() ) { if ($this->exlist_heap->isEmpty()) {
break 1; // break this loop only break 1; // break this loop only
} }
$exdate = $this->exlist_heap->top(); $exdate = $this->exlist_heap->top();
if ( $exdate < $occurrence ) { if ($exdate < $occurrence) {
$this->exlist_heap->extract(); $this->exlist_heap->extract();
continue; continue;
} }
elseif ( $exdate == $occurrence ) { elseif ($exdate == $occurrence) {
$excluded = true; $excluded = true;
break 1; break 1;
} }
@ -722,7 +722,7 @@ class RSet implements RRuleInterface
$previous_occurrence = $occurrence; $previous_occurrence = $occurrence;
if ( $excluded ) { if ($excluded) {
continue; continue;
} }

View File

@ -35,8 +35,8 @@ class RfcParser
'value' => null 'value' => null
), $default); ), $default);
if ( strpos($line,':') === false ) { if (strpos($line,':') === false) {
if ( ! $property['name'] ) { if (! $property['name']) {
throw new \InvalidArgumentException('Failed to parse RFC line, missing property name followed by ":"'); throw new \InvalidArgumentException('Failed to parse RFC line, missing property name followed by ":"');
} }
$property['value'] = $line; $property['value'] = $line;
@ -47,8 +47,8 @@ class RfcParser
$tmp = explode(';',$property['name']); $tmp = explode(';',$property['name']);
$property['name'] = $tmp[0]; $property['name'] = $tmp[0];
array_splice($tmp,0,1); array_splice($tmp,0,1);
foreach ( $tmp as $pair ) { foreach ($tmp as $pair) {
if ( strpos($pair,'=') === false ) { if (strpos($pair,'=') === false) {
throw new \InvalidArgumentException('Failed to parse RFC line, invalid property parameters: '.$pair); throw new \InvalidArgumentException('Failed to parse RFC line, invalid property parameters: '.$pair);
} }
list($key,$value) = explode('=',$pair); list($key,$value) = explode('=',$pair);
@ -79,10 +79,10 @@ class RfcParser
$nb_rrule = 0; $nb_rrule = 0;
$lines = explode("\n", $string); $lines = explode("\n", $string);
if ( $dtstart ) { if ($dtstart) {
$nb_dtstart = 1; $nb_dtstart = 1;
if ( is_string($dtstart) ) { if (is_string($dtstart)) {
if ( strlen($dtstart) == 10 ) { if (strlen($dtstart) == 10) {
$dtstart_type = 'date'; $dtstart_type = 'date';
} }
else { else {
@ -95,34 +95,34 @@ class RfcParser
$parts['DTSTART'] = RRule::parseDate($dtstart); $parts['DTSTART'] = RRule::parseDate($dtstart);
} }
foreach ( $lines as $line ) { foreach ($lines as $line) {
$property = self::parseLine($line, array( $property = self::parseLine($line, array(
'name' => sizeof($lines) > 1 ? null : 'RRULE' // allow missing property name for single-line RRULE 'name' => sizeof($lines) > 1 ? null : 'RRULE' // allow missing property name for single-line RRULE
)); ));
switch ( strtoupper($property['name']) ) { switch (strtoupper($property['name'])) {
case 'DTSTART': case 'DTSTART':
$nb_dtstart += 1; $nb_dtstart += 1;
if ( $nb_dtstart > 1 ) { if ($nb_dtstart > 1) {
throw new \InvalidArgumentException('Too many DTSTART properties (there can be only one)'); throw new \InvalidArgumentException('Too many DTSTART properties (there can be only one)');
} }
$tmp = null; $tmp = null;
$dtstart_type = 'date'; $dtstart_type = 'date';
if ( ! preg_match($rfc_date_regexp, $property['value']) ) { if (! preg_match($rfc_date_regexp, $property['value'])) {
throw new \InvalidArgumentException( throw new \InvalidArgumentException(
'Invalid DTSTART property: date or date time format incorrect' 'Invalid DTSTART property: date or date time format incorrect'
); );
} }
if ( isset($property['params']['TZID']) ) { if (isset($property['params']['TZID'])) {
// TZID must only be specified if this is a date-time (see section 3.3.4 & 3.3.5 of RFC 5545) // TZID must only be specified if this is a date-time (see section 3.3.4 & 3.3.5 of RFC 5545)
if ( strpos($property['value'], 'T') === false ) { if (strpos($property['value'], 'T') === false) {
throw new \InvalidArgumentException( throw new \InvalidArgumentException(
'Invalid DTSTART property: TZID should not be specified if there is no time component' 'Invalid DTSTART property: TZID should not be specified if there is no time component'
); );
} }
// The "TZID" property parameter MUST NOT be applied to DATE-TIME // The "TZID" property parameter MUST NOT be applied to DATE-TIME
// properties whose time values are specified in UTC. // properties whose time values are specified in UTC.
if ( strpos($property['value'], 'Z') !== false ) { if (strpos($property['value'], 'Z') !== false) {
throw new \InvalidArgumentException( throw new \InvalidArgumentException(
'Invalid DTSTART property: TZID must not be applied when time is specified in UTC' 'Invalid DTSTART property: TZID must not be applied when time is specified in UTC'
); );
@ -130,8 +130,8 @@ class RfcParser
$dtstart_type = 'tzid'; $dtstart_type = 'tzid';
$tmp = new \DateTimeZone($property['params']['TZID']); $tmp = new \DateTimeZone($property['params']['TZID']);
} }
elseif ( strpos($property['value'], 'T') !== false ) { elseif (strpos($property['value'], 'T') !== false) {
if ( strpos($property['value'], 'Z') === false ) { if (strpos($property['value'], 'Z') === false) {
$dtstart_type = 'localtime'; // no timezone $dtstart_type = 'localtime'; // no timezone
} }
else { else {
@ -143,31 +143,31 @@ class RfcParser
case 'RRULE': case 'RRULE':
case 'EXRULE': case 'EXRULE':
$nb_rrule += 1; $nb_rrule += 1;
if ( $nb_rrule > 1 ) { if ($nb_rrule > 1) {
throw new \InvalidArgumentException('Too many RRULE properties (there can be only one)'); throw new \InvalidArgumentException('Too many RRULE properties (there can be only one)');
} }
foreach ( explode(';',$property['value']) as $pair ) { foreach (explode(';',$property['value']) as $pair) {
$pair = explode('=', $pair); $pair = explode('=', $pair);
if ( ! isset($pair[1]) || isset($pair[2]) ) { if (! isset($pair[1]) || isset($pair[2])) {
throw new \InvalidArgumentException("Failed to parse RFC string, malformed RRULE property: {$property['value']}"); throw new \InvalidArgumentException("Failed to parse RFC string, malformed RRULE property: {$property['value']}");
} }
list($key, $value) = $pair; list($key, $value) = $pair;
if ( $key === 'UNTIL' ) { if ($key === 'UNTIL') {
if ( ! preg_match($rfc_date_regexp, $value) ) { if (! preg_match($rfc_date_regexp, $value)) {
throw new \InvalidArgumentException( throw new \InvalidArgumentException(
'Invalid UNTIL property: date or date time format incorrect' 'Invalid UNTIL property: date or date time format incorrect'
); );
} }
switch ( $dtstart_type ) { switch ($dtstart_type) {
case 'date': case 'date':
if ( strpos($value, 'T') !== false) { if (strpos($value, 'T') !== false) {
throw new \InvalidArgumentException( throw new \InvalidArgumentException(
'Invalid UNTIL property: The value of the UNTIL rule part MUST be a date if DTSTART is a date.' 'Invalid UNTIL property: The value of the UNTIL rule part MUST be a date if DTSTART is a date.'
); );
} }
break; break;
case 'localtime': case 'localtime':
if ( strpos($value, 'T') === false || strpos($value, 'Z') !== false ) { if (strpos($value, 'T') === false || strpos($value, 'Z') !== false) {
throw new \InvalidArgumentException( throw new \InvalidArgumentException(
'Invalid UNTIL property: if the "DTSTART" property is specified as a date with local time, then the UNTIL rule part MUST also be specified as a date with local time' 'Invalid UNTIL property: if the "DTSTART" property is specified as a date with local time, then the UNTIL rule part MUST also be specified as a date with local time'
); );
@ -175,7 +175,7 @@ class RfcParser
break; break;
case 'tzid': case 'tzid':
case 'utc': case 'utc':
if ( strpos($value, 'T') === false || strpos($value, 'Z') === false ) { if (strpos($value, 'T') === false || strpos($value, 'Z') === false) {
throw new \InvalidArgumentException( throw new \InvalidArgumentException(
'Invalid UNTIL property: if the "DTSTART" property is specified as a date with UTC time or a date with local time and time zone reference, then the UNTIL rule part MUST be specified as a date with UTC time.' 'Invalid UNTIL property: if the "DTSTART" property is specified as a date with UTC time or a date with local time and time zone reference, then the UNTIL rule part MUST be specified as a date with UTC time.'
); );
@ -185,8 +185,8 @@ class RfcParser
$value = new \DateTime($value); $value = new \DateTime($value);
} }
elseif ( $key === 'DTSTART' ) { elseif ($key === 'DTSTART') {
if ( isset($parts['DTSTART']) ) { if (isset($parts['DTSTART'])) {
throw new \InvalidArgumentException('DTSTART cannot be part of RRULE and has already been defined'); throw new \InvalidArgumentException('DTSTART cannot be part of RRULE and has already been defined');
} }
// this is an invalid rule, however we'll support it since the JS lib is broken // this is an invalid rule, however we'll support it since the JS lib is broken
@ -210,19 +210,19 @@ class RfcParser
static public function parseRDate($line) static public function parseRDate($line)
{ {
$property = self::parseLine($line); $property = self::parseLine($line);
if ( $property['name'] !== 'RDATE' ) { if ($property['name'] !== 'RDATE') {
throw new \InvalidArgumentException("Failed to parse RDATE line, this is a {$property['name']} property"); throw new \InvalidArgumentException("Failed to parse RDATE line, this is a {$property['name']} property");
} }
$period = false; $period = false;
$tz = null; $tz = null;
foreach ( $property['params'] as $name => $value ) { foreach ($property['params'] as $name => $value) {
switch ( strtoupper($name) ) { switch (strtoupper($name)) {
case 'TZID': case 'TZID':
$tz = new \DateTimeZone($value); $tz = new \DateTimeZone($value);
break; break;
case 'VALUE': case 'VALUE':
switch ( $value ) { switch ($value) {
case 'DATE': case 'DATE':
case 'DATE-TIME': case 'DATE-TIME':
break; break;
@ -240,17 +240,17 @@ class RfcParser
$dates = array(); $dates = array();
foreach ( explode(',',$property['value']) as $value ) { foreach (explode(',',$property['value']) as $value) {
if ( $period ) { if ($period) {
if ( strpos($value,'/') === false ) { if (strpos($value,'/') === false) {
throw new \InvalidArgumentException('Invalid period in RDATE'); throw new \InvalidArgumentException('Invalid period in RDATE');
} }
// period is unsupported! // period is unsupported!
trigger_error('VALUE=PERIOD is not supported and ignored', E_USER_NOTICE); trigger_error('VALUE=PERIOD is not supported and ignored', E_USER_NOTICE);
} }
else { else {
if ( strpos($value, 'Z') ) { if (strpos($value, 'Z')) {
if ( $tz !== null ) { if ($tz !== null) {
throw new \InvalidArgumentException('Invalid RDATE property: TZID must not be applied when time is specified in UTC'); throw new \InvalidArgumentException('Invalid RDATE property: TZID must not be applied when time is specified in UTC');
} }
$dates[] = new \DateTime($value); $dates[] = new \DateTime($value);
@ -271,13 +271,13 @@ class RfcParser
static public function parseExDate($line) static public function parseExDate($line)
{ {
$property = self::parseLine($line); $property = self::parseLine($line);
if ( $property['name'] !== 'EXDATE' ) { if ($property['name'] !== 'EXDATE') {
throw new \InvalidArgumentException("Failed to parse EXDATE line, this is a {$property['name']} property"); throw new \InvalidArgumentException("Failed to parse EXDATE line, this is a {$property['name']} property");
} }
$tz = null; $tz = null;
foreach ( $property['params'] as $name => $value ) { foreach ($property['params'] as $name => $value) {
switch ( strtoupper($name) ) { switch (strtoupper($name)) {
case 'VALUE': case 'VALUE':
// Ignore optional words // Ignore optional words
break; break;
@ -291,9 +291,9 @@ class RfcParser
$dates = array(); $dates = array();
foreach ( explode(',',$property['value']) as $value ) { foreach (explode(',',$property['value']) as $value) {
if ( strpos($value, 'Z') ) { if (strpos($value, 'Z')) {
if ( $tz !== null ) { if ($tz !== null) {
throw new \InvalidArgumentException('Invalid EXDATE property: TZID must not be applied when time is specified in UTC'); throw new \InvalidArgumentException('Invalid EXDATE property: TZID must not be applied when time is specified in UTC');
} }
$dates[] = new \DateTime($value); $dates[] = new \DateTime($value);

View File

@ -221,15 +221,15 @@ class RRuleTest extends TestCase
), $rule)); ), $rule));
$this->assertEquals($occurrences, $rule->getOccurrences()); $this->assertEquals($occurrences, $rule->getOccurrences());
$this->assertEquals($occurrences, $rule->getOccurrences(), 'Cached version'); $this->assertEquals($occurrences, $rule->getOccurrences(), 'Cached version');
foreach ( $occurrences as $date ) { foreach ($occurrences as $date) {
$this->assertTrue($rule->occursAt($date), $date->format('r').'in cached version'); $this->assertTrue($rule->occursAt($date), $date->format('r').'in cached version');
} }
$rule->clearCache(); $rule->clearCache();
foreach ( $occurrences as $date ) { foreach ($occurrences as $date) {
$this->assertTrue($rule->occursAt($date), $date->format('r').'in uncached version'); $this->assertTrue($rule->occursAt($date), $date->format('r').'in uncached version');
} }
$rule->clearCache(); $rule->clearCache();
for ( $i = 0; $i < count($occurrences); $i++ ) { for ($i = 0; $i < count($occurrences); $i++) {
$this->assertEquals($rule[$i], $occurrences[$i], 'array access uncached'); $this->assertEquals($rule[$i], $occurrences[$i], 'array access uncached');
} }
} }
@ -321,15 +321,15 @@ class RRuleTest extends TestCase
), $rule)); ), $rule));
$this->assertEquals($occurrences, $rule->getOccurrences()); $this->assertEquals($occurrences, $rule->getOccurrences());
$this->assertEquals($occurrences, $rule->getOccurrences(), 'Cached version'); $this->assertEquals($occurrences, $rule->getOccurrences(), 'Cached version');
foreach ( $occurrences as $date ) { foreach ($occurrences as $date) {
$this->assertTrue($rule->occursAt($date), $date->format('r').' in cached version'); $this->assertTrue($rule->occursAt($date), $date->format('r').' in cached version');
} }
$rule->clearCache(); $rule->clearCache();
foreach ( $occurrences as $date ) { foreach ($occurrences as $date) {
$this->assertTrue($rule->occursAt($date), $date->format('r').' in uncached version'); $this->assertTrue($rule->occursAt($date), $date->format('r').' in uncached version');
} }
$rule->clearCache(); $rule->clearCache();
for ( $i = 0; $i < count($occurrences); $i++ ) { for ($i = 0; $i < count($occurrences); $i++) {
$this->assertEquals($rule[$i], $occurrences[$i], 'array access uncached'); $this->assertEquals($rule[$i], $occurrences[$i], 'array access uncached');
} }
} }
@ -396,15 +396,15 @@ class RRuleTest extends TestCase
), $rule)); ), $rule));
$this->assertEquals($occurrences, $rule->getOccurrences()); $this->assertEquals($occurrences, $rule->getOccurrences());
$this->assertEquals($occurrences, $rule->getOccurrences(), 'Cached version'); $this->assertEquals($occurrences, $rule->getOccurrences(), 'Cached version');
foreach ( $occurrences as $date ) { foreach ($occurrences as $date) {
$this->assertTrue($rule->occursAt($date), $date->format('r').' in cached version'); $this->assertTrue($rule->occursAt($date), $date->format('r').' in cached version');
} }
$rule->clearCache(); $rule->clearCache();
foreach ( $occurrences as $date ) { foreach ($occurrences as $date) {
$this->assertTrue($rule->occursAt($date), $date->format('r').' in uncached version'); $this->assertTrue($rule->occursAt($date), $date->format('r').' in uncached version');
} }
$rule->clearCache(); $rule->clearCache();
for ( $i = 0; $i < count($occurrences); $i++ ) { for ($i = 0; $i < count($occurrences); $i++) {
$this->assertEquals($rule[$i], $occurrences[$i], 'array access uncached'); $this->assertEquals($rule[$i], $occurrences[$i], 'array access uncached');
} }
} }
@ -472,15 +472,15 @@ class RRuleTest extends TestCase
), $rule)); ), $rule));
$this->assertEquals($occurrences, $rule->getOccurrences()); $this->assertEquals($occurrences, $rule->getOccurrences());
$this->assertEquals($occurrences, $rule->getOccurrences(), 'Cached version'); $this->assertEquals($occurrences, $rule->getOccurrences(), 'Cached version');
foreach ( $occurrences as $date ) { foreach ($occurrences as $date) {
$this->assertTrue($rule->occursAt($date), $date->format('r').' in cached version'); $this->assertTrue($rule->occursAt($date), $date->format('r').' in cached version');
} }
$rule->clearCache(); $rule->clearCache();
foreach ( $occurrences as $date ) { foreach ($occurrences as $date) {
$this->assertTrue($rule->occursAt($date), $date->format('r').' in uncached version'); $this->assertTrue($rule->occursAt($date), $date->format('r').' in uncached version');
} }
$rule->clearCache(); $rule->clearCache();
for ( $i = 0; $i < count($occurrences); $i++ ) { for ($i = 0; $i < count($occurrences); $i++) {
$this->assertEquals($rule[$i], $occurrences[$i], 'array access uncached'); $this->assertEquals($rule[$i], $occurrences[$i], 'array access uncached');
} }
} }
@ -597,15 +597,15 @@ class RRuleTest extends TestCase
), $rule)); ), $rule));
$this->assertEquals($occurrences, $rule->getOccurrences()); $this->assertEquals($occurrences, $rule->getOccurrences());
$this->assertEquals($occurrences, $rule->getOccurrences(), 'Cached version'); $this->assertEquals($occurrences, $rule->getOccurrences(), 'Cached version');
foreach ( $occurrences as $date ) { foreach ($occurrences as $date) {
$this->assertTrue($rule->occursAt($date), $date->format('r').' in cached version'); $this->assertTrue($rule->occursAt($date), $date->format('r').' in cached version');
} }
$rule->clearCache(); $rule->clearCache();
foreach ( $occurrences as $date ) { foreach ($occurrences as $date) {
$this->assertTrue($rule->occursAt($date), $date->format('r').' in uncached version'); $this->assertTrue($rule->occursAt($date), $date->format('r').' in uncached version');
} }
$rule->clearCache(); $rule->clearCache();
for ( $i = 0; $i < count($occurrences); $i++ ) { for ($i = 0; $i < count($occurrences); $i++) {
$this->assertEquals($rule[$i], $occurrences[$i], 'array access uncached'); $this->assertEquals($rule[$i], $occurrences[$i], 'array access uncached');
} }
} }
@ -722,15 +722,15 @@ class RRuleTest extends TestCase
), $rule)); ), $rule));
$this->assertEquals($occurrences, $rule->getOccurrences()); $this->assertEquals($occurrences, $rule->getOccurrences());
$this->assertEquals($occurrences, $rule->getOccurrences(), 'Cached version'); $this->assertEquals($occurrences, $rule->getOccurrences(), 'Cached version');
foreach ( $occurrences as $date ) { foreach ($occurrences as $date) {
$this->assertTrue($rule->occursAt($date), $date->format('r').' in cached version'); $this->assertTrue($rule->occursAt($date), $date->format('r').' in cached version');
} }
$rule->clearCache(); $rule->clearCache();
foreach ( $occurrences as $date ) { foreach ($occurrences as $date) {
$this->assertTrue($rule->occursAt($date), $date->format('r').' in uncached version'); $this->assertTrue($rule->occursAt($date), $date->format('r').' in uncached version');
} }
$rule->clearCache(); $rule->clearCache();
for ( $i = 0; $i < count($occurrences); $i++ ) { for ($i = 0; $i < count($occurrences); $i++) {
$this->assertEquals($rule[$i], $occurrences[$i], 'array access uncached'); $this->assertEquals($rule[$i], $occurrences[$i], 'array access uncached');
} }
} }
@ -847,15 +847,15 @@ class RRuleTest extends TestCase
), $rule)); ), $rule));
$this->assertEquals($occurrences, $rule->getOccurrences()); $this->assertEquals($occurrences, $rule->getOccurrences());
$this->assertEquals($occurrences, $rule->getOccurrences(), 'Cached version'); $this->assertEquals($occurrences, $rule->getOccurrences(), 'Cached version');
foreach ( $occurrences as $date ) { foreach ($occurrences as $date) {
$this->assertTrue($rule->occursAt($date), $date->format('r').' in cached version'); $this->assertTrue($rule->occursAt($date), $date->format('r').' in cached version');
} }
$rule->clearCache(); $rule->clearCache();
foreach ( $occurrences as $date ) { foreach ($occurrences as $date) {
$this->assertTrue($rule->occursAt($date), $date->format('r').' in uncached version'); $this->assertTrue($rule->occursAt($date), $date->format('r').' in uncached version');
} }
$rule->clearCache(); $rule->clearCache();
for ( $i = 0; $i < count($occurrences); $i++ ) { for ($i = 0; $i < count($occurrences); $i++) {
$this->assertEquals($rule[$i], $occurrences[$i], 'array access uncached'); $this->assertEquals($rule[$i], $occurrences[$i], 'array access uncached');
} }
} }
@ -1452,15 +1452,15 @@ class RRuleTest extends TestCase
$rule = new RRule($rule); $rule = new RRule($rule);
$this->assertEquals($occurrences, $rule->getOccurrences()); $this->assertEquals($occurrences, $rule->getOccurrences());
$this->assertEquals($occurrences, $rule->getOccurrences(), 'Cached version'); $this->assertEquals($occurrences, $rule->getOccurrences(), 'Cached version');
foreach ( $occurrences as $date ) { foreach ($occurrences as $date) {
$this->assertTrue($rule->occursAt($date), $date->format('r').' in cached version'); $this->assertTrue($rule->occursAt($date), $date->format('r').' in cached version');
} }
$rule->clearCache(); $rule->clearCache();
foreach ( $occurrences as $date ) { foreach ($occurrences as $date) {
$this->assertTrue($rule->occursAt($date), $date->format('r').' in uncached version'); $this->assertTrue($rule->occursAt($date), $date->format('r').' in uncached version');
} }
$rule->clearCache(); $rule->clearCache();
for ( $i = 0; $i < count($occurrences); $i++ ) { for ($i = 0; $i < count($occurrences); $i++) {
$this->assertEquals($rule[$i], $occurrences[$i], 'array access uncached'); $this->assertEquals($rule[$i], $occurrences[$i], 'array access uncached');
} }
} }
@ -1669,15 +1669,15 @@ class RRuleTest extends TestCase
$rule = new RRule($rule); $rule = new RRule($rule);
$this->assertEquals($occurrences, $rule->getOccurrences()); $this->assertEquals($occurrences, $rule->getOccurrences());
$this->assertEquals($occurrences, $rule->getOccurrences(), 'Cached version'); $this->assertEquals($occurrences, $rule->getOccurrences(), 'Cached version');
foreach ( $occurrences as $date ) { foreach ($occurrences as $date) {
$this->assertTrue($rule->occursAt($date), $date->format('r').' in cached version'); $this->assertTrue($rule->occursAt($date), $date->format('r').' in cached version');
} }
$rule->clearCache(); $rule->clearCache();
foreach ( $occurrences as $date ) { foreach ($occurrences as $date) {
$this->assertTrue($rule->occursAt($date), $date->format('r').' in uncached version'); $this->assertTrue($rule->occursAt($date), $date->format('r').' in uncached version');
} }
$rule->clearCache(); $rule->clearCache();
for ( $i = 0; $i < count($occurrences); $i++ ) { for ($i = 0; $i < count($occurrences); $i++) {
$this->assertEquals($rule[$i], $occurrences[$i], 'array access uncached'); $this->assertEquals($rule[$i], $occurrences[$i], 'array access uncached');
} }
} }
@ -1740,7 +1740,7 @@ class RRuleTest extends TestCase
public function testNotOccurrences($rule, $not_occurences) public function testNotOccurrences($rule, $not_occurences)
{ {
$rule = new RRule($rule); $rule = new RRule($rule);
foreach ( $not_occurences as $date ) { foreach ($not_occurences as $date) {
$this->assertFalse($rule->occursAt($date), "Rule must not match $date"); $this->assertFalse($rule->occursAt($date), "Rule must not match $date");
} }
} }
@ -2058,7 +2058,7 @@ class RRuleTest extends TestCase
// as generating the string from a rule // as generating the string from a rule
$this->assertEquals($rule, new RRule($rule->rfcString())); $this->assertEquals($rule, new RRule($rule->rfcString()));
if ( $occurrences ) { if ($occurrences) {
$this->assertEquals($occurrences, $rule->getOccurrences()); $this->assertEquals($occurrences, $rule->getOccurrences());
} }
} }
@ -2125,7 +2125,7 @@ class RRuleTest extends TestCase
{ {
$rule = @ new RRule($str); $rule = @ new RRule($str);
if ( $occurrences ) { if ($occurrences) {
$this->assertEquals($occurrences, $rule->getOccurrences(), '', 1); $this->assertEquals($occurrences, $rule->getOccurrences(), '', 1);
} }
} }
@ -2570,14 +2570,14 @@ class RRuleTest extends TestCase
// iterate // iterate
$rrule->clearCache(); $rrule->clearCache();
foreach ( $rrule as $occurrence ) { foreach ($rrule as $occurrence) {
break; break;
} }
$this->assertEquals(date_create('2007-01-01'), $occurrence); $this->assertEquals(date_create('2007-01-01'), $occurrence);
$occurrence->modify('+1 day'); $occurrence->modify('+1 day');
$this->assertEquals(date_create('2007-01-01'), $rrule[0], 'No modification possible with foreach (uncached)'); $this->assertEquals(date_create('2007-01-01'), $rrule[0], 'No modification possible with foreach (uncached)');
foreach ( $rrule as $occurrence ) { foreach ($rrule as $occurrence) {
break; break;
} }
$this->assertEquals(date_create('2007-01-01'), $occurrence); $this->assertEquals(date_create('2007-01-01'), $occurrence);
@ -2745,7 +2745,7 @@ class RRuleTest extends TestCase
$method = $reflector->getMethod('i18nFilesToLoad'); $method = $reflector->getMethod('i18nFilesToLoad');
$method->setAccessible(true); $method->setAccessible(true);
if ( ! $files ) { if (! $files) {
try { try {
$method->invokeArgs(null, array($locale, true)); $method->invokeArgs(null, array($locale, true));
$this->fail('Expected InvalidArgumentException not thrown (files was '.json_encode($files).')'); $this->fail('Expected InvalidArgumentException not thrown (files was '.json_encode($files).')');
@ -2766,7 +2766,7 @@ class RRuleTest extends TestCase
$method = $reflector->getMethod('i18nFilesToLoad'); $method = $reflector->getMethod('i18nFilesToLoad');
$method->setAccessible(true); $method->setAccessible(true);
if ( ! $files ) { if (! $files) {
try { try {
$method->invokeArgs(null, array($locale, false)); $method->invokeArgs(null, array($locale, false));
$this->fail('Expected InvalidArgumentException not thrown (files was '.json_encode($files).')'); $this->fail('Expected InvalidArgumentException not thrown (files was '.json_encode($files).')');

View File

@ -473,7 +473,7 @@ class RSetTest extends TestCase
'DTSTART' => date_create('1997-09-02 09:00') 'DTSTART' => date_create('1997-09-02 09:00')
)); ));
foreach ( $rset as $occurrence ) { foreach ($rset as $occurrence) {
$this->assertEquals(date_create('1997-09-02 09:00'), $occurrence); $this->assertEquals(date_create('1997-09-02 09:00'), $occurrence);
break; break;
} }