mirror of
https://github.com/rlanvin/php-rrule.git
synced 2025-02-21 10:54:14 +01:00
Work in progress
This commit is contained in:
parent
1535174243
commit
8a20641693
37
LICENSE
37
LICENSE
@ -18,4 +18,39 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
|
|
||||||
|
-----------------------
|
||||||
|
Based on Python's dateutil.
|
||||||
|
|
||||||
|
dateutil - Extensions to the standard Python datetime module.
|
||||||
|
|
||||||
|
Copyright (c) 2003-2011 - Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||||
|
Copyright (c) 2012-2014 - Tomi Pieviläinen <tomi.pievilainen@iki.fi>
|
||||||
|
Copyright (c) 2014 - Yaron de Leeuw <me@jarondl.net>
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
* Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@ -6,7 +6,7 @@
|
|||||||
"homepage": "https://github.com/rlanvin/php-rrule",
|
"homepage": "https://github.com/rlanvin/php-rrule",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=5.3.0"
|
"php": ">=5.4.0"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"classmap": ["src/"]
|
"classmap": ["src/"]
|
||||||
|
649
src/RRule.php
649
src/RRule.php
@ -4,12 +4,21 @@
|
|||||||
* Implementation of RRULE as defined by RFC 5545.
|
* Implementation of RRULE as defined by RFC 5545.
|
||||||
*
|
*
|
||||||
* Heavily based on dateutil/rrule.py
|
* Heavily based on dateutil/rrule.py
|
||||||
|
*
|
||||||
|
* Some useful terms to understand the algorithms and variables naming:
|
||||||
|
*
|
||||||
|
* yearday = day of the year, from 0 to 365 (on leap years) - date('z')
|
||||||
|
* weekday = day of the week (ISO-8601), from 1 (MO) to 7 (SU) - date('N')
|
||||||
|
* monthday = day of the month, from 1 to 31
|
||||||
|
* wkst = week start, the weekday (1 to 7) which is the first day of week.
|
||||||
|
* Default is Monday (1). In some countries it's Sunday (7).
|
||||||
|
* weekno = number of the week in the year (ISO-8601)
|
||||||
|
*
|
||||||
|
* CAREFUL with that bug: https://bugs.php.net/bug.php?id=62476
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace RRule;
|
namespace RRule;
|
||||||
|
|
||||||
define(__NAMESPACE__.'\MAX_YEAR',date('Y', PHP_INT_MAX));
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
@ -66,19 +75,35 @@ function is_leap_year($year)
|
|||||||
*/
|
*/
|
||||||
class RRule implements \Iterator, \ArrayAccess
|
class RRule implements \Iterator, \ArrayAccess
|
||||||
{
|
{
|
||||||
// frequencies
|
const SECONDLY = 7;
|
||||||
public static $frequencies = ['SECONDLY','MINUTELY','HOURLY','DAILY','WEEKLY','MONTHLY','YEARLY'];
|
const MINUTELY = 6;
|
||||||
|
const HOURLY = 5;
|
||||||
|
const DAILY = 4;
|
||||||
|
const WEEKLY = 3;
|
||||||
|
const MONTHLY = 2;
|
||||||
|
const YEARLY = 1;
|
||||||
|
|
||||||
const SECONDLY = 0;
|
// frequency names
|
||||||
const MINUTELY = 1;
|
public static $frequencies = [
|
||||||
const HOURLY = 2;
|
'SECONDLY' => self::SECONDLY,
|
||||||
const DAILY = 3;
|
'MINUTELY' => self::MINUTELY,
|
||||||
const WEEKLY = 4;
|
'HOURLY' => self::HOURLY,
|
||||||
const MONTHLY = 5;
|
'DAILY' => self::DAILY,
|
||||||
const YEARLY = 6;
|
'WEEKLY' => self::WEEKLY,
|
||||||
|
'MONTHLY' => self::MONTHLY,
|
||||||
|
'YEARLY' => self::YEARLY
|
||||||
|
];
|
||||||
|
|
||||||
// weekdays numbered from 1 (ISO-8601 or date('N'))
|
// weekdays numbered from 1 (ISO-8601 or date('N'))
|
||||||
public static $week_days = ['MO' => 1,'TU' => 2,'WE' => 3,'TH' => 4,'FR' => 5,'SA' => 6,'SU' => 7];
|
public static $week_days = [
|
||||||
|
'MO' => 1,
|
||||||
|
'TU' => 2,
|
||||||
|
'WE' => 3,
|
||||||
|
'TH' => 4,
|
||||||
|
'FR' => 5,
|
||||||
|
'SA' => 6,
|
||||||
|
'SU' => 7
|
||||||
|
];
|
||||||
|
|
||||||
// original rule
|
// original rule
|
||||||
protected $rule = array(
|
protected $rule = array(
|
||||||
@ -101,7 +126,6 @@ class RRule implements \Iterator, \ArrayAccess
|
|||||||
|
|
||||||
// parsed and validated values
|
// parsed and validated values
|
||||||
protected $dtstart = null;
|
protected $dtstart = null;
|
||||||
protected $dtstart_ts = null;
|
|
||||||
protected $freq = null;
|
protected $freq = null;
|
||||||
protected $until = null;
|
protected $until = null;
|
||||||
protected $count = null;
|
protected $count = null;
|
||||||
@ -118,6 +142,7 @@ class RRule implements \Iterator, \ArrayAccess
|
|||||||
protected $bymonth = null;
|
protected $bymonth = null;
|
||||||
protected $bysetpos = null;
|
protected $bysetpos = null;
|
||||||
protected $wkst = null;
|
protected $wkst = null;
|
||||||
|
protected $timeset = null;
|
||||||
|
|
||||||
// Public interface
|
// Public interface
|
||||||
|
|
||||||
@ -126,10 +151,15 @@ class RRule implements \Iterator, \ArrayAccess
|
|||||||
*/
|
*/
|
||||||
public function __construct(array $parts)
|
public function __construct(array $parts)
|
||||||
{
|
{
|
||||||
|
$parts = array_change_key_case($parts, CASE_UPPER);
|
||||||
|
|
||||||
// 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('Unsupported parameter(s): '.implode(',',array_keys($unsupported)));
|
throw new \InvalidArgumentException(
|
||||||
|
'Unsupported parameter(s): '
|
||||||
|
.implode(',',array_keys($unsupported))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$parts = array_merge($this->rule, $parts);
|
$parts = array_merge($this->rule, $parts);
|
||||||
@ -138,62 +168,75 @@ class RRule implements \Iterator, \ArrayAccess
|
|||||||
// 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('The WKST rule part must be one of the following: '.implode(', ',array_keys(self::$week_days)));
|
throw new \InvalidArgumentException(
|
||||||
|
'The WKST rule part must be one of the following: '
|
||||||
|
.implode(', ',array_keys(self::$week_days))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
$this->wkst = self::$week_days[$parts['WKST']];
|
$this->wkst = self::$week_days[$parts['WKST']];
|
||||||
|
|
||||||
// FREQ
|
// FREQ
|
||||||
$parts['FREQ'] = strtoupper($parts['FREQ']);
|
$parts['FREQ'] = strtoupper($parts['FREQ']);
|
||||||
if ( ! in_array($parts['FREQ'], self::$frequencies) ) {
|
if ( (is_int($parts['FREQ']) && ($parts['FREQ'] < self::SECONDLY || $parts['FREQ'] > self::YEARLY))
|
||||||
throw new \InvalidArgumentException('The FREQ rule part must be one of the following: '.implode(', ',self::$frequencies));
|
|| ! array_key_exists($parts['FREQ'], self::$frequencies) ) {
|
||||||
|
throw new \InvalidArgumentException(
|
||||||
|
'The FREQ rule part must be one of the following: '
|
||||||
|
.implode(', ',array_keys(self::$frequencies))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
$this->freq = $parts['FREQ'];
|
$this->freq = self::$frequencies[$parts['FREQ']];
|
||||||
|
|
||||||
// INTERVAL
|
// INTERVAL
|
||||||
$parts['INTERVAL'] = (int) $parts['INTERVAL'];
|
$parts['INTERVAL'] = (int) $parts['INTERVAL'];
|
||||||
if ( $parts['INTERVAL'] < 1 ) {
|
if ( $parts['INTERVAL'] < 1 ) {
|
||||||
throw new \InvalidArgumentException('The INTERVAL rule part must be a positive integer (> 0)');
|
throw new \InvalidArgumentException(
|
||||||
|
'The INTERVAL rule part must be a positive integer (> 0)'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
$this->interval = (int) $parts['INTERVAL'];
|
$this->interval = (int) $parts['INTERVAL'];
|
||||||
|
|
||||||
// DTSTART
|
// DTSTART
|
||||||
if ( not_empty($parts['DTSTART']) ) {
|
if ( not_empty($parts['DTSTART']) ) {
|
||||||
if ( is_string($parts['DTSTART']) ) {
|
if ( $parts['DTSTART'] instanceof \DateTime ) {
|
||||||
$this->dtstart = $parts['DTSTART'];
|
$this->dtstart = $parts['DTSTART'];
|
||||||
$this->dtstart_ts = strtotime($parts['DTSTART']);
|
|
||||||
}
|
}
|
||||||
elseif ( $parts['DTSTART'] instanceof DateTime ) {
|
else {
|
||||||
$this->dtstart = $parts['DTSTART']->format('Y-m-d');
|
try {
|
||||||
$this->dtstart_ts = $parts['DTSTART']->getTimestamp();
|
if ( is_integer($parts['DTSTART']) ) {
|
||||||
}
|
$this->dtstart = \DateTime::createFromFormat('U',$parts['DTSTART']);
|
||||||
elseif ( is_integer($parts['DTSTART']) ) {
|
}
|
||||||
$this->dtstart = date('Y-m-d',$parts['DTSTART']);
|
else {
|
||||||
$this->dtstart_ts = $parts['DTSTART'];
|
$this->dtstart = new \DateTime($parts['DTSTART']);
|
||||||
}
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
if ( ! $this->dtstart_ts ) {
|
throw new \InvalidArgumentException(
|
||||||
throw new \InvalidArgumentException('Cannot parse DTSTART - must be a valid date, timestamp or DateTime object');
|
'Failed to parse DTSTART ; it must be a valid date, timestamp or \DateTime object'
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$this->dtstart = date('Y-m-d');
|
$this->dtstart = new \DateTime();
|
||||||
$this->dtstart_ts = strtotime($this->dtstart);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UNTIL (optional)
|
// UNTIL (optional)
|
||||||
if ( not_empty($parts['UNTIL']) ) {
|
if ( not_empty($parts['UNTIL']) ) {
|
||||||
if ( is_string($parts['UNTIL']) ) {
|
if ( $parts['UNTIL'] instanceof \DateTime ) {
|
||||||
$this->until = $parts['UNTIL'];
|
$this->until = $parts['UNTIL'];
|
||||||
}
|
}
|
||||||
elseif ( $parts['UNTIL'] instanceof DateTime ) {
|
else {
|
||||||
$this->until = $parts['UNTIL']->format('Y-m-d');
|
try {
|
||||||
}
|
if ( is_integer($parts['UNTIL']) ) {
|
||||||
elseif ( is_integer($parts['UNTIL']) ) {
|
$this->until = \DateTime::createFromFormat('U',$parts['UNTIL']);
|
||||||
$this->until = date('Y-m-d',$parts['UNTIL']);
|
}
|
||||||
}
|
else {
|
||||||
|
$this->until = new \DateTime($parts['UNTIL']);
|
||||||
if ( ! strtotime($this->until) ) {
|
}
|
||||||
throw new \InvalidArgumentException('Cannot parse UNTIL - must be a valid date, timestamp or DateTime object');
|
} catch (\Exception $e) {
|
||||||
|
throw new \InvalidArgumentException(
|
||||||
|
'Failed to parse UNTIL ; it must be a valid date, timestamp or \DateTime object'
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,64 +252,21 @@ class RRule implements \Iterator, \ArrayAccess
|
|||||||
// 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 'YEARLY':
|
case self::YEARLY:
|
||||||
if ( ! not_empty($parts['BYMONTH']) ) {
|
if ( ! not_empty($parts['BYMONTH']) ) {
|
||||||
$parts['BYMONTH'] = [date('m',$this->dtstart_ts)];
|
$parts['BYMONTH'] = [(int) $this->dtstart->format('m')];
|
||||||
}
|
}
|
||||||
$parts['BYMONTHDAY'] = [date('j', $this->dtstart_ts)];
|
$parts['BYMONTHDAY'] = [(int) $this->dtstart->format('j')];
|
||||||
break;
|
break;
|
||||||
case 'MONTHLY':
|
case self::MONTHLY:
|
||||||
$parts['BYMONTHDAY'] = [date('j',$this->dtstart_ts)];
|
$parts['BYMONTHDAY'] = [(int) $this->dtstart->format('j')];
|
||||||
break;
|
break;
|
||||||
case 'WEEKLY':
|
case self::WEEKLY:
|
||||||
$parts['BYDAY'] = [array_search(date('N', $this->dtstart_ts), self::$week_days)];
|
$parts['BYDAY'] = [array_search($this->dtstart->format('N'), self::$week_days)];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BYSECOND
|
|
||||||
if ( not_empty($parts['BYSECOND']) ) {
|
|
||||||
if ( ! is_array($parts['BYSECOND']) ) {
|
|
||||||
$parts['BYSECOND'] = explode(',',$parts['BYSECOND']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->bysecond = [];
|
|
||||||
foreach ( $parts['BYSECOND'] as $value ) {
|
|
||||||
if ( $value < 0 || $value > 60 ) {
|
|
||||||
throw new \InvalidArgumentException('Invalid BYSECOND value: '.$value);
|
|
||||||
}
|
|
||||||
$this->bysecond[] = (int) $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( not_empty($parts['BYMINUTE']) ) {
|
|
||||||
if ( ! is_array($parts['BYMINUTE']) ) {
|
|
||||||
$parts['BYMINUTE'] = explode(',',$parts['BYMINUTE']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->byminute = [];
|
|
||||||
foreach ( $parts['BYMINUTE'] as $value ) {
|
|
||||||
if ( $value < 0 || $value > 59 ) {
|
|
||||||
throw new \InvalidArgumentException('Invalid BYMINUTE value: '.$value);
|
|
||||||
}
|
|
||||||
$this->byminute[] = (int) $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( not_empty($parts['BYHOUR']) ) {
|
|
||||||
if ( ! is_array($parts['BYHOUR']) ) {
|
|
||||||
$parts['BYHOUR'] = explode(',',$parts['BYHOUR']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->byhour = [];
|
|
||||||
foreach ( $parts['BYHOUR'] as $value ) {
|
|
||||||
if ( $value < 0 || $value > 23 ) {
|
|
||||||
throw new \InvalidArgumentException('Invalid BYHOUR value: '.$value);
|
|
||||||
}
|
|
||||||
$this->byhour[] = (int) $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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']) ) {
|
||||||
@ -275,10 +275,12 @@ class RRule implements \Iterator, \ArrayAccess
|
|||||||
$this->byweekday = [];
|
$this->byweekday = [];
|
||||||
$this->byweekday_relative = [];
|
$this->byweekday_relative = [];
|
||||||
foreach ( $parts['BYDAY'] as $value ) {
|
foreach ( $parts['BYDAY'] as $value ) {
|
||||||
|
$value = trim($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_relative[] = [self::$week_days[$matches[2]], (int)$matches[1]];
|
$this->byweekday_relative[] = [self::$week_days[$matches[2]], (int)$matches[1]];
|
||||||
}
|
}
|
||||||
@ -287,12 +289,12 @@ class RRule implements \Iterator, \ArrayAccess
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! empty($this->weekday_relative) ) {
|
if ( ! empty($this->byweekday_relative) ) {
|
||||||
if ( $this->freq !== 'MONTHLY' && $this->freq !== '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 == '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.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -303,7 +305,7 @@ class RRule implements \Iterator, \ArrayAccess
|
|||||||
// 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 == '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.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -327,7 +329,7 @@ class RRule implements \Iterator, \ArrayAccess
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ( not_empty($parts['BYYEARDAY']) ) {
|
if ( not_empty($parts['BYYEARDAY']) ) {
|
||||||
if ( $this->freq == 'DAILY' || $this->freq == 'WEEKLY' || $this->freq == '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.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -347,7 +349,7 @@ class RRule implements \Iterator, \ArrayAccess
|
|||||||
|
|
||||||
// BYWEEKNO
|
// BYWEEKNO
|
||||||
if ( not_empty($parts['BYWEEKNO']) ) {
|
if ( not_empty($parts['BYWEEKNO']) ) {
|
||||||
if ( $this->freq !== '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.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -398,6 +400,89 @@ class RRule implements \Iterator, \ArrayAccess
|
|||||||
$this->bysetpos[] = (int) $value;
|
$this->bysetpos[] = (int) $value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// now for the time options
|
||||||
|
// this gets more complicated
|
||||||
|
|
||||||
|
if ( not_empty($parts['BYHOUR']) ) {
|
||||||
|
if ( ! is_array($parts['BYHOUR']) ) {
|
||||||
|
$parts['BYHOUR'] = explode(',',$parts['BYHOUR']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->byhour = [];
|
||||||
|
foreach ( $parts['BYHOUR'] as $value ) {
|
||||||
|
if ( $value < 0 || $value > 23 ) {
|
||||||
|
throw new \InvalidArgumentException('Invalid BYHOUR value: '.$value);
|
||||||
|
}
|
||||||
|
$this->byhour[] = (int) $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $this->freq === self::HOURLY ) {
|
||||||
|
// do something (__construct_byset) ?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseif ( $this->freq < self::HOURLY ) {
|
||||||
|
$this->byhour = [(int) $this->dtstart->format('G')];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( not_empty($parts['BYMINUTE']) ) {
|
||||||
|
if ( ! is_array($parts['BYMINUTE']) ) {
|
||||||
|
$parts['BYMINUTE'] = explode(',',$parts['BYMINUTE']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->byminute = [];
|
||||||
|
foreach ( $parts['BYMINUTE'] as $value ) {
|
||||||
|
if ( $value < 0 || $value > 59 ) {
|
||||||
|
throw new \InvalidArgumentException('Invalid BYMINUTE value: '.$value);
|
||||||
|
}
|
||||||
|
$this->byminute[] = (int) $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $this->freq == self::MINUTELY ) {
|
||||||
|
// do something
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseif ( $this->freq < self::MINUTELY ) {
|
||||||
|
$this->byminute = [(int) $this->dtstart->format('i')];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( not_empty($parts['BYSECOND']) ) {
|
||||||
|
if ( ! is_array($parts['BYSECOND']) ) {
|
||||||
|
$parts['BYSECOND'] = explode(',',$parts['BYSECOND']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->bysecond = [];
|
||||||
|
foreach ( $parts['BYSECOND'] as $value ) {
|
||||||
|
if ( $value < 0 || $value > 60 ) {
|
||||||
|
throw new \InvalidArgumentException('Invalid BYSECOND value: '.$value);
|
||||||
|
}
|
||||||
|
$this->bysecond[] = (int) $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $this->freq == self::SECONDLY ) {
|
||||||
|
// do something
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseif ( $this->freq < self::SECONDLY ) {
|
||||||
|
$this->bysecond = [(int) $this->dtstart->format('s')];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $this->freq < self::HOURLY ) {
|
||||||
|
// for frequencies DAILY, WEEKLY, MONTHLY AND YEARLY, we build
|
||||||
|
// an array of every time of the day at which there should be an
|
||||||
|
// occurence - default, if no BYHOUR/BYMINUTE/BYSECOND are provided
|
||||||
|
// is only one time, and it's the DTSTART time.
|
||||||
|
$this->timeset = array();
|
||||||
|
foreach ( $this->byhour as $hour ) {
|
||||||
|
foreach ( $this->byminute as $minute ) {
|
||||||
|
foreach ( $this->bysecond as $second ) {
|
||||||
|
// fixme another format?
|
||||||
|
$this->timeset[] = [$hour,$minute,$second];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort($this->timeset);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getOccurrences()
|
public function getOccurrences()
|
||||||
@ -498,15 +583,15 @@ class RRule implements \Iterator, \ArrayAccess
|
|||||||
protected function getDaySet($year, $month, $day, array $masks)
|
protected function getDaySet($year, $month, $day, array $masks)
|
||||||
{
|
{
|
||||||
switch ( $this->freq ) {
|
switch ( $this->freq ) {
|
||||||
case 'YEARLY':
|
case self::YEARLY:
|
||||||
return range(0,$masks['year_len']-1);
|
return range(0,$masks['year_len']-1);
|
||||||
|
|
||||||
case 'MONTHLY':
|
case self::MONTHLY:
|
||||||
$start = $masks['month_to_last_day'][$month-1];
|
$start = $masks['last_day_of_month'][$month-1];
|
||||||
$stop = $masks['month_to_last_day'][$month];
|
$stop = $masks['last_day_of_month'][$month];
|
||||||
return range($start, $stop - 1);
|
return range($start, $stop - 1);
|
||||||
|
|
||||||
case 'WEEKLY':
|
case self::WEEKLY:
|
||||||
// on first iteration, the first week will not be complete
|
// on first iteration, the first week will not be complete
|
||||||
// we don't backtrack to the first day of the week, to avoid
|
// we don't backtrack to the first day of the week, to avoid
|
||||||
// crossing year boundary in reverse (i.e. if the week started
|
// crossing year boundary in reverse (i.e. if the week started
|
||||||
@ -518,16 +603,16 @@ class RRule implements \Iterator, \ArrayAccess
|
|||||||
for ( $j = 0; $j < 7; $j++ ) {
|
for ( $j = 0; $j < 7; $j++ ) {
|
||||||
$set[] = $i;
|
$set[] = $i;
|
||||||
$i += 1;
|
$i += 1;
|
||||||
if ( $masks['doy_to_weekday'][$i] == $this->wkst ) {
|
if ( $masks['yearday_to_weekday'][$i] == $this->wkst ) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $set;
|
return $set;
|
||||||
|
|
||||||
case 'DAILY':
|
case self::DAILY:
|
||||||
case 'HOURLY':
|
case self::HOURLY:
|
||||||
case 'MINUTELY':
|
case self::MINUTELY:
|
||||||
case 'SECONDLY':
|
case self::SECONDLY:
|
||||||
$n = (int) date('z', mktime(0,0,0,$month,$day,$year));
|
$n = (int) date('z', mktime(0,0,0,$month,$day,$year));
|
||||||
return [$n];
|
return [$n];
|
||||||
}
|
}
|
||||||
@ -536,42 +621,43 @@ class RRule implements \Iterator, \ArrayAccess
|
|||||||
/**
|
/**
|
||||||
* Some serious magic is happening here.
|
* Some serious magic is happening here.
|
||||||
*/
|
*/
|
||||||
protected function buildWeekdayMasks($year, $month, $day, array & $masks)
|
protected function buildNthWeekdayMask($year, $month, $day, array & $masks)
|
||||||
{
|
{
|
||||||
$masks['doy_to_weekday'] = array_slice(self::$WEEKDAY_MASK, date('N', mktime(0,0,0,1,1,$year))-1);
|
$masks['yearday_is_in_weekday_relative'] = array();
|
||||||
$masks['doy_to_weekday_relative'] = array();
|
|
||||||
|
|
||||||
if ( $this->byweekday_relative ) {
|
if ( $this->byweekday_relative ) {
|
||||||
$ranges = array();
|
$ranges = array();
|
||||||
if ( $this->freq == 'YEARLY' ) {
|
if ( $this->freq == self::YEARLY ) {
|
||||||
if ( $this->bymonth ) {
|
if ( $this->bymonth ) {
|
||||||
foreach ( $this->bymonth as $bymonth ) {
|
foreach ( $this->bymonth as $bymonth ) {
|
||||||
$ranges[] = [$masks['month_to_last_day'][$bymonth-1], $masks['month_to_last_day'][$bymonth]];
|
$ranges[] = [$masks['last_day_of_month'][$bymonth-1], $masks['last_day_of_month'][$bymonth]];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$ranges = [[0,$masks['year_len']-1]];
|
$ranges = [[0,$masks['year_len']-1]];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
elseif ( $this->freq == 'MONTHLY') {
|
elseif ( $this->freq == self::MONTHLY ) {
|
||||||
$ranges[] = [$masks['month_to_last_day'][$month-1], $masks['month_to_last_day'][$month]];
|
$ranges[] = [$masks['last_day_of_month'][$month-1], $masks['last_day_of_month'][$month]];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( $ranges ) {
|
if ( $ranges ) {
|
||||||
|
// Weekly frequency won't get here, so we may not
|
||||||
|
// 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_relative as $tmp ) {
|
foreach ( $this->byweekday_relative 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['doy_to_weekday'][$i] - $weekday, 7);
|
$i = $i - pymod($masks['yearday_to_weekday'][$i] - $weekday, 7);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$i = $first + ($nth - 1) * 7;
|
$i = $first + ($nth - 1) * 7;
|
||||||
$i = $i + (7 - $masks['doy_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['doy_to_weekday_relative'][$i] = 1;
|
$masks['yearday_is_in_weekday_relative'][$i] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -579,6 +665,116 @@ class RRule implements \Iterator, \ArrayAccess
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* More magic
|
||||||
|
*/
|
||||||
|
protected function buildWeeknoMask($year, $month, $day, & $masks)
|
||||||
|
{
|
||||||
|
$masks['yearday_is_in_weekno'] = array();
|
||||||
|
|
||||||
|
// calculate the index of the first wkst day of the year
|
||||||
|
// 0 means the first day of the year is the wkst day (e.g. wkst is Monday and Jan 1st is a Monday)
|
||||||
|
// 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)
|
||||||
|
$first_wkst = (7 - $masks['weekday_of_1st_yearday'] + $this->wkst) % 7;
|
||||||
|
if( $first_wkst >= 4 ) {
|
||||||
|
$first_wkst_offset = 0;
|
||||||
|
// 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'] + pymod($masks['weekday_of_1st_yearday'] - $this->wkst,7);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$first_wkst_offset = $first_wkst;
|
||||||
|
// Number of days in the year, minus the days we left in last year.
|
||||||
|
$nb_days = $masks['year_len'] - $first_wkst;
|
||||||
|
}
|
||||||
|
$nb_weeks = (int) ($nb_days / 7) + (int) (($nb_days % 7) / 4);
|
||||||
|
|
||||||
|
// alright now we now when the first week starts
|
||||||
|
// and the number of weeks of the year
|
||||||
|
// so we can generate a map of every yearday that are in the weeks
|
||||||
|
// specified in byweekno
|
||||||
|
foreach ( $this->byweekno as $n ) {
|
||||||
|
if ( $n < 0 ) {
|
||||||
|
$n = $n + $nb_weeks + 1;
|
||||||
|
}
|
||||||
|
if ( $n <= 0 || $n > $nb_weeks ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ( $n > 1 ) {
|
||||||
|
$i = $first_wkst_offset + ($n - 1) * 7;
|
||||||
|
if ( $first_wkst_offset != $first_wkst ) {
|
||||||
|
// if week #1 started the previous year
|
||||||
|
// realign the start of the week
|
||||||
|
$i = $i - (7 - $first_wkst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$i = $first_wkst_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
for ( $j = 0; $j < 7; $j++ ) {
|
||||||
|
$masks['yearday_is_in_weekno'][$i] = true;
|
||||||
|
$i = $i + 1;
|
||||||
|
if ( $masks['yearday_to_weekday'][$i] == $this->wkst ) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// days of next year.
|
||||||
|
if ( in_array(1, $this->byweekno) ) {
|
||||||
|
// Check week number 1 of next year as well
|
||||||
|
// TODO: Check -numweeks for next year.
|
||||||
|
$i = $first_wkst_offset + $nb_weeks * 7;
|
||||||
|
if ( $first_wkst_offset != $first_wkst ) {
|
||||||
|
$i = $i - (7 - $first_wkst);
|
||||||
|
}
|
||||||
|
if ( $i < $masks['year_len'] ) {
|
||||||
|
// If week starts in next year, we don't care about it.
|
||||||
|
for ( $j = 0; $j < 7; $j++ ) {
|
||||||
|
$masks['yearday_is_in_weekno'][$i] = true;
|
||||||
|
$i += 1;
|
||||||
|
if ( $masks['yearday_to_weekday'][$i] == $this->wkst ) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $first_wkst_offset ) {
|
||||||
|
// Check last week number of last year as well.
|
||||||
|
// 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
|
||||||
|
// days from last year's last week number in this year.
|
||||||
|
if ( ! in_array(-1, $this->byweekno) ) {
|
||||||
|
$weekday_of_1st_yearday = date('N', mktime(0,0,0,1,1,$year-1));
|
||||||
|
$first_wkst_offset_last_year = (7 - $weekday_of_1st_yearday + $this->wkst) % 7;
|
||||||
|
$last_year_len = 365 + is_leap_year($year - 1);
|
||||||
|
if ( $first_wkst_offset_last_year >= 4) {
|
||||||
|
$first_wkst_offset_last_year = 0;
|
||||||
|
$nb_weeks_last_year = 52 + (int) ((($last_year_len + ($weekday_of_1st_yearday - $this->wkst) % 7) % 7) / 4);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$nb_weeks_last_year = 52 + (int) ((($masks['year_len'] - $first_wkst_offset) % 7) /4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$nb_weeks_last_year = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( in_array($nb_weeks_last_year, $this->byweekno) ) {
|
||||||
|
for ( $i = 0; $i < $first_wkst_offset; $i++ ) {
|
||||||
|
$masks['yearday_is_in_weekno'][$i] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the main method, where all of the logic happens.
|
* This is the main method, where all of the logic happens.
|
||||||
*
|
*
|
||||||
@ -589,32 +785,54 @@ class RRule implements \Iterator, \ArrayAccess
|
|||||||
// these are the static variables, i.e. the variables that persists
|
// these are the static variables, i.e. the variables that persists
|
||||||
// at every call of the method (to emulate a generator)
|
// at every call of the method (to emulate a generator)
|
||||||
static $year = null, $month = null, $day = null;
|
static $year = null, $month = null, $day = null;
|
||||||
static $current_set = null;
|
static $hour = null, $minute = null, $second = null;
|
||||||
|
static $current_set = null, $masks = null, $timeset = null;
|
||||||
static $total = 0;
|
static $total = 0;
|
||||||
|
|
||||||
if ( $reset ) {
|
if ( $reset ) {
|
||||||
$year = $month = $day = null;
|
$year = $month = $day = null;
|
||||||
$current_set = null;
|
$hour = $minute = $second = null;
|
||||||
|
$current_set = $masks = $timeset = null;
|
||||||
$total = 0;
|
$total = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// stop once $total has reached COUNT
|
// stop once $total has reached COUNT
|
||||||
if ( $this->count && $total >= $this->count ) {
|
if ( $this->count && $total >= $this->count ) {
|
||||||
// echo "\tTotal = $total ; COUNT = ".$this->count." stopping iteration\n";
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( $year == null ) {
|
if ( $year == null ) {
|
||||||
// difference from python here
|
if ( $this->freq === self::WEEKLY ) {
|
||||||
if ( $this->freq == '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
|
// simply loop by adding +7 days. The Python lib does some
|
||||||
$tmp = strtotime($this->dtstart);
|
// calculation magic at the end of the loop (when incrementing)
|
||||||
$tmp = strtotime('-'.pymod(date('N', $tmp) - $this->wkst,7).'days', $tmp);
|
// to realign on first pass.
|
||||||
list($year,$month,$day) = explode('-',date('Y-m-d',$tmp));
|
$tmp = $this->dtstart->modify('-'.pymod($this->dtstart->format('N') - $this->wkst,7).'days');
|
||||||
|
list($year,$month,$day) = explode('-',$tmp->format('Y-n-j'));
|
||||||
|
unset($tmp);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
list($year,$month,$day) = explode('-',$this->dtstart);
|
list($year,$month,$day) = explode('-',$this->dtstart->format('Y-n-j'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo, not sure when this should be rebuilt
|
||||||
|
// and not sure what this does anyway
|
||||||
|
if ( $timeset == null ) {
|
||||||
|
if ( $this->freq < self::HOURLY ) { // daily, weekly, monthly or yearly
|
||||||
|
$timeset = $this->timeset;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (
|
||||||
|
($this->freq >= self::HOURLY && $this->byhour && ! in_array($hour, $this->byhour))
|
||||||
|
|| ($this->freq >= self::MINUTELY && $this->byminute && ! in_array($minute, $this->byminute))
|
||||||
|
|| ($this->freq >= self::SECONDLY && $this->bysecond && ! in_array($second, $this->bysecond))
|
||||||
|
) {
|
||||||
|
$timeset = array();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$timeset = $this->getTimeSet($hour, $minute, $second);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -626,83 +844,89 @@ class RRule implements \Iterator, \ArrayAccess
|
|||||||
// 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
|
||||||
$masks = [];
|
if ( empty($masks) || $masks['year'] != $year || $masks['month'] != $month ) {
|
||||||
$masks['leap_year'] = is_leap_year($year);
|
$masks = array('year' => '','month'=>'');
|
||||||
$masks['year_len'] = 365 + (int) $masks['leap_year'];
|
// only if year has changed
|
||||||
$masks['next_year_len'] = 365 + is_leap_year($year + 1);
|
if ( $masks['year'] != $year ) {
|
||||||
if ( $masks['leap_year'] ) {
|
$masks['leap_year'] = is_leap_year($year);
|
||||||
$masks['doy_to_month'] = self::$MONTH_MASK_366;
|
$masks['year_len'] = 365 + (int) $masks['leap_year'];
|
||||||
$masks['doy_to_monthday'] = self::$MONTHDAY_MASK_366;
|
$masks['next_year_len'] = 365 + is_leap_year($year + 1);
|
||||||
$masks['doy_to_monthday_negative'] = self::$NEGATIVE_MONTHDAY_MASK_366;
|
$masks['weekday_of_1st_yearday'] = date('N', mktime(0,0,0,1,1,$year));
|
||||||
$masks['month_to_last_day'] = self::$LAST_DAY_OF_MONTH_366;
|
$masks['yearday_to_weekday'] = array_slice(self::$WEEKDAY_MASK, $masks['weekday_of_1st_yearday']-1);
|
||||||
|
if ( $masks['leap_year'] ) {
|
||||||
|
$masks['yearday_to_month'] = self::$MONTH_MASK_366;
|
||||||
|
$masks['yearday_to_monthday'] = self::$MONTHDAY_MASK_366;
|
||||||
|
$masks['yearday_to_monthday_negative'] = self::$NEGATIVE_MONTHDAY_MASK_366;
|
||||||
|
$masks['last_day_of_month'] = self::$LAST_DAY_OF_MONTH_366;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$masks['yearday_to_month'] = self::$MONTH_MASK;
|
||||||
|
$masks['yearday_to_monthday'] = self::$MONTHDAY_MASK;
|
||||||
|
$masks['yearday_to_monthday_negative'] = self::$NEGATIVE_MONTHDAY_MASK;
|
||||||
|
$masks['last_day_of_month'] = self::$LAST_DAY_OF_MONTH;
|
||||||
|
}
|
||||||
|
if ( $this->byweekno ) {
|
||||||
|
$this->buildWeeknoMask($year, $month, $day, $masks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// everytime month or year changes
|
||||||
|
if ( $this->byweekday_relative ) {
|
||||||
|
$this->buildNthWeekdayMask($year, $month, $day, $masks);
|
||||||
|
}
|
||||||
|
$masks['year'] = $year;
|
||||||
|
$masks['month'] = $month;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
$masks['doy_to_month'] = self::$MONTH_MASK;
|
|
||||||
$masks['doy_to_monthday'] = self::$MONTHDAY_MASK;
|
|
||||||
$masks['doy_to_monthday_negative'] = self::$NEGATIVE_MONTHDAY_MASK;
|
|
||||||
$masks['month_to_last_day'] = self::$LAST_DAY_OF_MONTH;
|
|
||||||
}
|
|
||||||
$this->buildWeekdayMasks($year, $month, $day, $masks);
|
|
||||||
|
|
||||||
|
// calculate the current set
|
||||||
$current_set = $this->getDaySet($year, $month, $day, $masks);
|
$current_set = $this->getDaySet($year, $month, $day, $masks);
|
||||||
// echo"\tWorking with set=".json_encode($current_set)."\n";
|
// echo"\tWorking with $year-$month-$day set=".json_encode($current_set)."\n";
|
||||||
|
// print_r(json_encode($masks));
|
||||||
// echo "\tdoy_to_weekday = ".json_encode($masks['doy_to_weekday'])."\n";
|
|
||||||
// echo "\tdoy_to_weekday_relative = ".json_encode($masks['doy_to_weekday_relative'])."\n";
|
|
||||||
// fgets(STDIN);
|
// fgets(STDIN);
|
||||||
|
|
||||||
$filtered_set = array();
|
$filtered_set = array();
|
||||||
|
|
||||||
// If multiple BYxxx rule parts are specified, then after evaluating the
|
foreach ( $current_set as $yearday ) {
|
||||||
// specified FREQ and INTERVAL rule parts, the BYxxx rule parts are
|
if ( $this->bymonth && ! in_array($masks['yearday_to_month'][$yearday], $this->bymonth) ) {
|
||||||
// applied to the current set of evaluated occurrences in the following
|
continue;
|
||||||
// order: BYMONTH, BYWEEKNO, BYYEARDAY, BYMONTHDAY, BYDAY, BYHOUR,
|
}
|
||||||
// BYMINUTE, BYSECOND and BYSETPOS; then COUNT and UNTIL are evaluated.
|
|
||||||
|
|
||||||
// filter out (if needed)
|
if ( $this->byweekno && ! isset($masks['yearday_is_in_weekno'][$yearday]) ) {
|
||||||
foreach ( $current_set as $day_of_year ) {
|
|
||||||
// echo "\t DAY OF YEAR ",$day_of_year,"\n";
|
|
||||||
// echo "\t month=",$masks['doy_to_month'][$day_of_year],"\n";
|
|
||||||
// echo "\t monthday=",$doy_to_monthday[$day_of_year],"\n";
|
|
||||||
// echo "\t -monthday=",$doy_to_monthday_negative[$day_of_year],"\n";
|
|
||||||
// echo "\t weekday=",$doy_to_weekday[$day_of_year],"\n";
|
|
||||||
// fgets(STDIN);
|
|
||||||
if ( $this->bymonth && ! in_array($masks['doy_to_month'][$day_of_year], $this->bymonth) ) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if ( ($this->bymonthday || $this->bymonthday_negative)
|
|
||||||
&& ! in_array($masks['doy_to_monthday'][$day_of_year], $this->bymonthday)
|
|
||||||
&& ! in_array($masks['doy_to_monthday_negative'][$day_of_year], $this->bymonthday_negative) ) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if ( $this->byweekday && ! in_array($masks['doy_to_weekday'][$day_of_year], $this->byweekday) ) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if ( $this->byweekday_relative && ! isset($masks['doy_to_weekday_relative'][$day_of_year]) ) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( $this->byyearday ) {
|
if ( $this->byyearday ) {
|
||||||
if ( $day_of_year < $masks['year_len'] ) {
|
if ( $yearday < $masks['year_len'] ) {
|
||||||
if ( ! in_array($day_of_year + 1, $this->byyearday) && ! in_array(- $masks['year_len'] + $day_of_year,$this->byyearday) ) {
|
if ( ! in_array($yearday + 1, $this->byyearday) && ! in_array(- $masks['year_len'] + $yearday,$this->byyearday) ) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else { // if ( ($day_of_year >= $masks['year_len']
|
else { // if ( ($yearday >= $masks['year_len']
|
||||||
if ( ! in_array($day_of_year + 1 - $masks['year_len'], $this->byyearday) && ! in_array(- $masks['next_year_len'] + $day_of_year - $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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$filtered_set[] = $day_of_year;
|
if ( ($this->bymonthday || $this->bymonthday_negative)
|
||||||
|
&& ! in_array($masks['yearday_to_monthday'][$yearday], $this->bymonthday)
|
||||||
|
&& ! in_array($masks['yearday_to_monthday_negative'][$yearday], $this->bymonthday_negative) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $this->byweekday && ! in_array($masks['yearday_to_weekday'][$yearday], $this->byweekday) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $this->byweekday_relative && ! isset($masks['yearday_is_in_weekday_relative'][$yearday]) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$filtered_set[] = $yearday;
|
||||||
}
|
}
|
||||||
// echo "\tFiltered set (before BYSETPOS)=".json_encode($filtered_set)."\n";
|
// echo "\tFiltered set (before BYSETPOS)=".json_encode($filtered_set)."\n";
|
||||||
|
|
||||||
$current_set = $filtered_set;
|
$current_set = $filtered_set;
|
||||||
|
|
||||||
// Note: if one day we decide to support time this will have to be
|
// XXX this needs to be applied after expanding the timeset
|
||||||
// moved/rewritten to expand time *before* applying BYSETPOS
|
|
||||||
if ( $this->bysetpos ) {
|
if ( $this->bysetpos ) {
|
||||||
$filtered_set = [];
|
$filtered_set = [];
|
||||||
$n = sizeof($current_set);
|
$n = sizeof($current_set);
|
||||||
@ -726,30 +950,37 @@ class RRule implements \Iterator, \ArrayAccess
|
|||||||
// 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
|
||||||
while ( ($day_of_year = current($current_set)) !== false ) {
|
while ( ($yearday = current($current_set)) !== false ) {
|
||||||
$occurrence = date('Y-m-d', mktime(0, 0, 0, 1, ($day_of_year + 1), $year));
|
// $occurrence = date('Y-m-d', mktime(0, 0, 0, 1, ($yearday + 1), $year));
|
||||||
|
// echo "\t occurrence (mktime) = ", $occurrence,"\n";
|
||||||
|
$occurrence = \DateTime::createFromFormat('Y z', "$year $yearday");
|
||||||
|
// echo "\t occurrence (before time) =", $occurrence->format('r'),"\n";
|
||||||
|
while ( ($time = current($timeset)) !== false ) {
|
||||||
|
$occurrence->setTime($time[0], $time[1], $time[2]);
|
||||||
|
// consider end conditions
|
||||||
|
if ( $this->until && $occurrence > $this->until ) {
|
||||||
|
// $this->length = $total (?)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// consider end conditions
|
next($timeset);
|
||||||
if ( $this->until && $occurrence > $this->until ) {
|
if ( $occurrence >= $this->dtstart ) { // ignore occurences before DTSTART
|
||||||
// $this->length = $total (?)
|
$total += 1;
|
||||||
return null;
|
return $occurrence; // yield
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
reset($timeset);
|
||||||
next($current_set);
|
next($current_set);
|
||||||
if ( $occurrence >= $this->dtstart ) { // ignore occurences before DTSTART
|
|
||||||
$total += 1;
|
|
||||||
return $occurrence; // yield
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. we reset the loop to the next interval
|
// 3. we reset the loop to the next interval
|
||||||
$current_set = null; // reset the loop
|
$current_set = null; // reset the loop
|
||||||
switch ( $this->freq ) {
|
switch ( $this->freq ) {
|
||||||
case 'YEARLY':
|
case self::YEARLY:
|
||||||
// we do not care about $month or $day not existing, they are not used in yearly frequency
|
// we do not care about $month or $day not existing, they are not used in yearly frequency
|
||||||
$year = $year + $this->interval;
|
$year = $year + $this->interval;
|
||||||
break;
|
break;
|
||||||
case 'MONTHLY':
|
case self::MONTHLY:
|
||||||
// we do not care about the day of the month not existing, it is not used in monthly frequency
|
// we do not care about the day of the month not existing, it is not used in monthly frequency
|
||||||
$month = $month + $this->interval;
|
$month = $month + $this->interval;
|
||||||
if ( $month > 12 ) {
|
if ( $month > 12 ) {
|
||||||
@ -763,22 +994,20 @@ class RRule implements \Iterator, \ArrayAccess
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'WEEKLY':
|
case self::WEEKLY:
|
||||||
// here we take a little shortcut from the Python version, by using date/time methods
|
// here we take a little shortcut from the Python version, by using date/time methods
|
||||||
list($year,$month,$day) = explode('-',date('Y-m-d',strtotime('+'.($this->interval*7).'day', mktime(0,0,0,$month,$day,$year))));
|
// list($year,$month,$day) = explode('-',date('Y-m-d',strtotime('+'.($this->interval*7).'day', mktime(0,0,0,$month,$day,$year))));
|
||||||
|
list($year,$month,$day) = explode('-',(new \DateTime("$year-$month-$day"))->modify('+'.($this->interval*7).'day')->format('Y-n-j'));
|
||||||
break;
|
break;
|
||||||
case 'DAILY':
|
case self::DAILY:
|
||||||
// here we take a little shortcut from the Python version, by using date/time methods
|
// here we take a little shortcut from the Python version, by using date/time methods
|
||||||
list($year,$month,$day) = explode('-',date('Y-m-d',strtotime('+'.$this->interval.'day', mktime(0,0,0,$month,$day,$year))));
|
// list($year,$month,$day) = explode('-',date('Y-m-d',strtotime('+'.$this->interval.'day', mktime(0,0,0,$month,$day,$year))));
|
||||||
|
list($year,$month,$day) = explode('-',(new \DateTime("$year-$month-$day"))->modify('+'.$this->interval.'day')->format('Y-n-j'));
|
||||||
break;
|
break;
|
||||||
case 'HOURLY':
|
case self::HOURLY:
|
||||||
case 'MINUTELY':
|
case self::MINUTELY:
|
||||||
case 'SECONDLY':
|
case self::SECONDLY:
|
||||||
throw new LogicException('Unimplemented');
|
throw new \InvalidArgumentException('Unimplemented frequency');
|
||||||
}
|
|
||||||
// prevent overflow (especially on 32 bits system)
|
|
||||||
if ( $year >= MAX_YEAR ) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,109 +4,53 @@ use RRule\RRule;
|
|||||||
|
|
||||||
class RRuleTest extends PHPUnit_Framework_TestCase
|
class RRuleTest extends PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
public function testMissingParameter()
|
public function invalidRules()
|
||||||
{
|
|
||||||
$this->setExpectedException('InvalidArgumentException');
|
|
||||||
new RRule([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testUnsupportedParameter()
|
|
||||||
{
|
|
||||||
$this->setExpectedException('InvalidArgumentException');
|
|
||||||
new RRule([
|
|
||||||
'FREQ' => 'DAILY',
|
|
||||||
'FOO' => 'BAR'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function validByMonth()
|
|
||||||
{
|
{
|
||||||
return array(
|
return array(
|
||||||
['1'],
|
array([]),
|
||||||
['1,2'],
|
array(['FREQ' => 'foobar']),
|
||||||
[[1,2]]
|
array(['FREQ' => 'DAILY', 'INTERVAL' => -1]),
|
||||||
);
|
array(['FREQ' => 'DAILY', 'UNTIL' => 'foobar']),
|
||||||
}
|
array(['FREQ' => 'DAILY', 'COUNT' => -1]),
|
||||||
/**
|
|
||||||
* @dataProvider validByMonth
|
|
||||||
*/
|
|
||||||
public function testValidByMonth($bymonth)
|
|
||||||
{
|
|
||||||
new RRule([
|
|
||||||
'FREQ' => 'DAILY',
|
|
||||||
'BYMONTH' => $bymonth
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function invalidByMonth()
|
// The BYDAY rule part MUST NOT be specified with a numeric value
|
||||||
{
|
// when the FREQ rule part is not set to MONTHLY or YEARLY.
|
||||||
return array(
|
array(['FREQ' => 'DAILY', 'BYDAY' => ['1MO']]),
|
||||||
[0],
|
array(['FREQ' => 'WEEKLY', 'BYDAY' => ['1MO']]),
|
||||||
['-1'],
|
// The BYDAY rule part MUST NOT be specified with a numeric value
|
||||||
[-1],
|
// with the FREQ rule part set to YEARLY when the BYWEEKNO rule part is specified.
|
||||||
[13]
|
array(['FREQ' => 'YEARLY', 'BYDAY' => ['1MO'], 'BYWEEKNO' => 20]),
|
||||||
|
|
||||||
|
array(['FREQ' => 'DAILY', 'BYMONTHDAY' => 0]),
|
||||||
|
array(['FREQ' => 'DAILY', 'BYMONTHDAY' => 32]),
|
||||||
|
array(['FREQ' => 'DAILY', 'BYMONTHDAY' => -32]),
|
||||||
|
// The BYMONTHDAY rule part MUST NOT be specified when the FREQ rule
|
||||||
|
// part is set to WEEKLY.
|
||||||
|
array(['FREQ' => 'WEEKLY', 'BYMONTHDAY' => 1]),
|
||||||
|
|
||||||
|
array(['FREQ' => 'YEARLY', 'BYYEARDAY' => 0]),
|
||||||
|
array(['FREQ' => 'YEARLY', 'BYYEARDAY' => 367]),
|
||||||
|
// The BYYEARDAY rule part MUST NOT be specified when the FREQ
|
||||||
|
// rule part is set to DAILY, WEEKLY, or MONTHLY.
|
||||||
|
array(['FREQ' => 'DAILY', 'BYYEARDAY' => 1]),
|
||||||
|
array(['FREQ' => 'WEEKLY', 'BYYEARDAY' => 1]),
|
||||||
|
array(['FREQ' => 'MONTHLY', 'BYYEARDAY' => 1]),
|
||||||
|
|
||||||
|
// BYSETPOS rule part MUST only be used in conjunction with another
|
||||||
|
// BYxxx rule part.
|
||||||
|
array(['FREQ' => 'DAILY', 'BYSETPOS' => -1]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dataProvider invalidByMonth
|
* @dataProvider invalidRules
|
||||||
* @expectedException InvalidArgumentException
|
* @expectedException InvalidArgumentException
|
||||||
* @depends testValidByMonth
|
|
||||||
*/
|
*/
|
||||||
public function testInvalidByMonth($bymonth)
|
public function testInvalidRules($rule)
|
||||||
{
|
{
|
||||||
new RRule([
|
new RRule($rule);
|
||||||
'FREQ' => 'DAILY',
|
|
||||||
'BYMONTH' => $bymonth
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function validByDay()
|
|
||||||
{
|
|
||||||
return array(
|
|
||||||
['MO'],
|
|
||||||
['1MO'],
|
|
||||||
['+1MO'],
|
|
||||||
['-1MO'],
|
|
||||||
['53MO'],
|
|
||||||
['53MO']
|
|
||||||
);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @dataProvider validByDay
|
|
||||||
*/
|
|
||||||
public function testValidByDay($byday)
|
|
||||||
{
|
|
||||||
new RRule([
|
|
||||||
'FREQ' => 'DAILY',
|
|
||||||
'BYDAY' => $byday
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function invalidByDay()
|
|
||||||
{
|
|
||||||
return array(
|
|
||||||
[0],
|
|
||||||
['54MO'],
|
|
||||||
['-54MO']
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dataProvider invalidByDay
|
|
||||||
* @expectedException InvalidArgumentException
|
|
||||||
* @depends testValidByDay
|
|
||||||
*/
|
|
||||||
public function testInvalidByDay($byday)
|
|
||||||
{
|
|
||||||
new RRule([
|
|
||||||
'FREQ' => 'DAILY',
|
|
||||||
'BYDAY' => $byday
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function testIsLeapYear()
|
public function testIsLeapYear()
|
||||||
{
|
{
|
||||||
$this->assertFalse(\RRule\is_leap_year(1700));
|
$this->assertFalse(\RRule\is_leap_year(1700));
|
||||||
@ -115,48 +59,48 @@ class RRuleTest extends PHPUnit_Framework_TestCase
|
|||||||
$this->assertTrue(\RRule\is_leap_year(2000));
|
$this->assertTrue(\RRule\is_leap_year(2000));
|
||||||
}
|
}
|
||||||
|
|
||||||
// datetime\(([0-9]+), ([0-9]+), ([0-9]+)[ ,0-9\)]+
|
// date_create\(([0-9]+), ([0-9]+), ([0-9]+)[ ,0-9\)]+
|
||||||
|
|
||||||
public function yearlyRules()
|
public function yearlyRules()
|
||||||
{
|
{
|
||||||
return array(
|
return array(
|
||||||
array([],['1997-09-02','1998-09-02','1999-09-02']),
|
array([],[date_create('1997-09-02'),date_create('1998-09-02'), date_create('1999-09-02')]),
|
||||||
array(['INTERVAL' => 2], ['1997-09-02','1999-09-02','2001-09-02']),
|
array(['INTERVAL' => 2], [date_create('1997-09-02'),date_create('1999-09-02'),date_create('2001-09-02')]),
|
||||||
array(['BYMONTH' => [1,3]], ['1998-01-02','1998-03-02','1999-01-02']),
|
array(['DTSTART' => '2000-02-29'], [date_create('2000-02-29'),date_create('2004-02-29'),date_create('2008-02-29')]),
|
||||||
array(['BYMONTHDAY' => [1,3]], ['1997-09-03','1997-10-01','1997-10-03']),
|
array(['BYMONTH' => [1,3]], [date_create('1998-01-02'),date_create('1998-03-02'),date_create('1999-01-02')]),
|
||||||
array(['BYMONTH' => [1,3], 'BYMONTHDAY' => [5,7]], ['1998-01-05','1998-01-07','1998-03-05']),
|
array(['BYMONTHDAY' => [1,3]], [date_create('1997-09-03'),date_create('1997-10-01'),date_create('1997-10-03')]),
|
||||||
array(['BYDAY' => ['TU','TH']], ['1997-09-02','1997-09-04','1997-09-09']),
|
array(['BYMONTH' => [1,3], 'BYMONTHDAY' => [5,7]], [date_create('1998-01-05'),date_create('1998-01-07'),date_create('1998-03-05')]),
|
||||||
array(['BYDAY' => ['SU']], ['1997-09-07','1997-09-14','1997-09-21']),
|
array(['BYDAY' => ['TU','TH']], [date_create('1997-09-02'),date_create('1997-09-04'),date_create('1997-09-09')]),
|
||||||
array(['BYDAY' => ['1TU','-1TH']], ['1997-12-25','1998-01-06','1998-12-31']),
|
array(['BYDAY' => ['SU']], [date_create('1997-09-07'),date_create('1997-09-14'),date_create('1997-09-21')]),
|
||||||
array(['BYDAY' => ['3TU','-3TH']], ['1997-12-11','1998-01-20','1998-12-17']),
|
array(['BYDAY' => ['1TU','-1TH']], [date_create('1997-12-25'),date_create('1998-01-06'),date_create('1998-12-31')]),
|
||||||
array(['BYMONTH' => [1,3], 'BYDAY' => ['TU','TH']], ['1998-01-01','1998-01-06','1998-01-08']),
|
array(['BYDAY' => ['3TU','-3TH']], [date_create('1997-12-11'),date_create('1998-01-20'),date_create('1998-12-17')]),
|
||||||
array(['BYMONTH' => [1,3], 'BYDAY' => ['1TU','-1TH']], ['1998-01-06','1998-01-29','1998-03-03']),
|
array(['BYMONTH' => [1,3], 'BYDAY' => ['TU','TH']], [date_create('1998-01-01'),date_create('1998-01-06'),date_create('1998-01-08')]),
|
||||||
|
array(['BYMONTH' => [1,3], 'BYDAY' => ['1TU','-1TH']], [date_create('1998-01-06'),date_create('1998-01-29'),date_create('1998-03-03')]),
|
||||||
// This is interesting because the TH(-3) ends up before the TU(3).
|
// This is interesting because the TH(-3) ends up before the TU(3).
|
||||||
array(['BYMONTH' => [1,3], 'BYDAY' => ['3TU','-3TH']], ['1998-01-15','1998-01-20','1998-03-12']),
|
array(['BYMONTH' => [1,3], 'BYDAY' => ['3TU','-3TH']], [date_create('1998-01-15'),date_create('1998-01-20'),date_create('1998-03-12')]),
|
||||||
array(['BYMONTHDAY' => [1,3], 'BYDAY' => ['TU','TH']], ['1998-01-01','1998-02-03','1998-03-03']),
|
array(['BYMONTHDAY' => [1,3], 'BYDAY' => ['TU','TH']], [date_create('1998-01-01'),date_create('1998-02-03'),date_create('1998-03-03')]),
|
||||||
array(['BYMONTHDAY' => [1,3], 'BYDAY' => ['TU','TH'], 'BYMONTH' => [1,3]], ['1998-01-01','1998-03-03','2001-03-01']),
|
array(['BYMONTHDAY' => [1,3], 'BYDAY' => ['TU','TH'], 'BYMONTH' => [1,3]], [date_create('1998-01-01'),date_create('1998-03-03'),date_create('2001-03-01')]),
|
||||||
array(['BYYEARDAY' => [1,100,200,365], 'COUNT' => 4], ['1997-12-31','1998-01-01','1998-04-10', '1998-07-19']),
|
array(['BYYEARDAY' => [1,100,200,365], 'COUNT' => 4], [date_create('1997-12-31'),date_create('1998-01-01'),date_create('1998-04-10'), date_create('1998-07-19')]),
|
||||||
array(['BYYEARDAY' => [-365, -266, -166, -1], 'COUNT' => 4], ['1997-12-31','1998-01-01','1998-04-10', '1998-07-19']),
|
array(['BYYEARDAY' => [-365, -266, -166, -1], 'COUNT' => 4], [date_create('1997-12-31'),date_create('1998-01-01'),date_create('1998-04-10'), date_create('1998-07-19')]),
|
||||||
array(['BYYEARDAY' => [1,100,200,365], 'BYMONTH' => [4,7], 'COUNT' => 4], ['1998-04-10','1998-07-19','1999-04-10', '1999-07-19']),
|
array(['BYYEARDAY' => [1,100,200,365], 'BYMONTH' => [4,7], 'COUNT' => 4], [date_create('1998-04-10'),date_create('1998-07-19'),date_create('1999-04-10'), date_create('1999-07-19')]),
|
||||||
array(['BYYEARDAY' => [-365, -266, -166, -1], 'BYMONTH' => [4,7], 'COUNT' => 4], ['1998-04-10','1998-07-19','1999-04-10', '1999-07-19']),
|
array(['BYYEARDAY' => [-365, -266, -166, -1], 'BYMONTH' => [4,7], 'COUNT' => 4], [date_create('1998-04-10'),date_create('1998-07-19'),date_create('1999-04-10'), date_create('1999-07-19')]),
|
||||||
// array(['BYWEEKNO' => 20],['1998-5-11','1998-5-12','1998-5-13']),
|
array(['BYWEEKNO' => 20],[date_create('1998-05-11'),date_create('1998-05-12'),date_create('1998-05-13')]),
|
||||||
// // That's a nice one. The first days of week number one may be in the last year.
|
// That's a nice one. The first days of week number one may be in the last year.
|
||||||
// array(['BYWEEKNO' => 1, 'BYDAY' => 'MO'], ['1997-12-29', '1999-01-04', '2000-01-03']),
|
array(['BYWEEKNO' => 1, 'BYDAY' => 'MO'], [date_create('1997-12-29'), date_create('1999-01-04'), date_create('2000-01-03')]),
|
||||||
// // Another nice test. The last days of week number 52/53 may be in the next year.
|
// Another nice test. The last days of week number 52/53 may be in the next year.
|
||||||
// array(['BYWEEKNO' => 52, 'BYDAY' => 'SU'], ['1997-12-28', '1998-12-27', '2000-01-02']),
|
array(['BYWEEKNO' => 52, 'BYDAY' => 'SU'], [date_create('1997-12-28'), date_create('1998-12-27'), date_create('2000-01-02')]),
|
||||||
// array(['BYWEEKNO' => -1, 'BYDAY' => 'SU'], ['1997-12-28', '1999-01-03', '2000-01-02']),
|
array(['BYWEEKNO' => -1, 'BYDAY' => 'SU'], [date_create('1997-12-28'), date_create('1999-01-03'), date_create('2000-01-02')]),
|
||||||
// array(['BYWEEKNO' => 53, 'BYDAY' => 'MO'], ['1998-12-28', '2004-12-27', '2009-12-28']),
|
array(['BYWEEKNO' => 53, 'BYDAY' => 'MO'], [date_create('1998-12-28'), date_create('2004-12-27'), date_create('2009-12-28')]),
|
||||||
|
|
||||||
// FIXME (time part missing)
|
// FIXME (time part missing)
|
||||||
// array(['BYHOUR' => [6, 18]], ['1997-09-02','1998-09-02','1998-09-02']),
|
// array(['BYHOUR' => [6, 18]], [date_create('1997-09-02'),date_create('1998-09-02'),date_create('1998-09-02')]),
|
||||||
// array(['BYMINUTE'=> [6, 18]], ['1997-9-2', '1997-9-2', '1998-9-2']),
|
// array(['BYMINUTE'=> [6, 18]], ['1997-09-02', '1997-09-02', '1998-09-02']),
|
||||||
// array(['BYSECOND' => [6, 18]], ['1997-9-2', '1997-9-2', '1998-9-2']),
|
// array(['BYSECOND' => [6, 18]], ['1997-09-02', '1997-09-02', '1998-09-02']),
|
||||||
// array(['BYHOUR' => [6, 18], 'BYMINUTE' => [6, 18]], ['1997-9-2','1997-9-2','1998-9-2']),
|
// array(['BYHOUR' => [6, 18], 'BYMINUTE' => [6, 18]], ['1997-09-02','1997-09-02','1998-09-02']),
|
||||||
// array(['BYHOUR' => [6, 18], 'BYSECOND' => [6, 18]], ['1997-9-2','1997-9-2','1998-9-2']),
|
// array(['BYHOUR' => [6, 18], 'BYSECOND' => [6, 18]], ['1997-09-02','1997-09-02','1998-09-02']),
|
||||||
// array(['BYMINUTE' => [6, 18], 'BYSECOND' => [6, 18]], ['1997-9-2','1997-9-2','1997-9-2']),
|
// array(['BYMINUTE' => [6, 18], 'BYSECOND' => [6, 18]], ['1997-09-02','1997-09-02','1997-09-02']),
|
||||||
// array(['BYHOUR'=>[6, 18],'BYMINUTE'=>[6, 18],'BYSECOND'=>[6, 18]],['1997-9-2','1997-9-2','1997-9-2']),
|
// array(['BYHOUR'=>[6, 18],'BYMINUTE'=>[6, 18],'BYSECOND'=>[6, 18]],['1997-09-02','1997-09-02','1997-09-02']),
|
||||||
// array(['BYMONTHDAY'=>15,'BYHOUR'=>[6, 18],'BYSETPOS'=>[3, -3],['1997-11-15','1998-2-15','1998-11-15'])
|
// array(['BYMONTHDAY'=>15,'BYHOUR'=>[6, 18],'BYSETPOS'=>[3, -3],[date_create('1997-11-15'),date_create('1998-02-15'),date_create('1998-11-15')])
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,7 +121,31 @@ class RRuleTest extends PHPUnit_Framework_TestCase
|
|||||||
public function monthlyRules()
|
public function monthlyRules()
|
||||||
{
|
{
|
||||||
return array(
|
return array(
|
||||||
|
array([],[date_create('1997-09-02'),date_create('1997-10-02'),date_create('1997-11-02')]),
|
||||||
|
array(['INTERVAL'=>2],[date_create('1997-09-02'),date_create('1997-11-02'),date_create('1998-01-02')]),
|
||||||
|
array(['INTERVAL'=>18],[date_create('1997-09-02'),date_create('1999-03-02'),date_create('2000-09-02')]),
|
||||||
|
array(['BYMONTH' => [1, 3]],[date_create('1998-01-02'),date_create('1998-03-02'),date_create('1999-01-02')]),
|
||||||
|
array(['BYMONTHDAY' => [1, 3]],[date_create('1997-09-03'),date_create('1997-10-01'),date_create('1997-10-03')]),
|
||||||
|
array(['BYMONTHDAY' => [5, 7], 'BYMONTH' => [1, 3]], [date_create('1998-01-05'), date_create('1998-01-07'), date_create('1998-03-05')]),
|
||||||
|
array(['BYDAY' => ['TU', 'TH']], [date_create('1997-09-02'),date_create('1997-09-04'),date_create('1997-09-09')]),
|
||||||
|
// Third Monday of the month
|
||||||
|
array(['BYDAY' => '3MO'],[date_create('1997-09-15'),date_create('1997-10-20'),date_create('1997-11-17')]),
|
||||||
|
array(['BYDAY' => '1TU,-1TH'],[date_create('1997-09-02'),date_create('1997-09-25'),date_create('1997-10-07')]),
|
||||||
|
array(['BYDAY' => '3TU,-3TH'],[date_create('1997-09-11'),date_create('1997-09-16'),date_create('1997-10-16')]),
|
||||||
|
array(['BYDAY' => 'TU,TH', 'BYMONTH' => [1, 3]],[date_create('1998-01-01'),date_create('1998-01-06'),date_create('1998-01-08')]),
|
||||||
|
array(['BYMONTH' => [1, 3], 'BYDAY' => '1TU, -1TH'],[date_create('1998-01-06'),date_create('1998-01-29'),date_create('1998-03-03')]),
|
||||||
|
array(['BYMONTH' => [1, 3], 'BYDAY' => '3TU, -3TH'],[date_create('1998-01-15'),date_create('1998-01-20'),date_create('1998-03-12')]),
|
||||||
|
array(['BYMONTHDAY' => [1, 3], 'BYDAY' => ['TU', 'TH']], [date_create('1998-01-01'),date_create('1998-02-03'),date_create('1998-03-03')]),
|
||||||
|
array(['BYMONTH' => [1, 3], 'BYMONTHDAY' => [1, 3], 'BYDAY' => ['TU', 'TH']],[date_create('1998-01-01'),date_create('1998-03-03'),date_create('2001-03-01')]),
|
||||||
|
|
||||||
|
// array(['BYHOUR'=> [6, 18],['1997-09-02',date_create('1997-10-02'),date_create('1997-10-02')]),
|
||||||
|
// array(['BYMINUTE'=> [6, 18],['1997-09-02','1997-09-02',date_create('1997-10-02')]),
|
||||||
|
// array(['BYSECOND' => [6, 18],['1997-09-02','1997-09-02',date_create('1997-10-02')]),
|
||||||
|
// array(['BYHOUR'=>[6, 18],'BYMINUTE'=>[6, 18]],['1997-09-02','1997-09-02',date_create('1997-10-02')]),
|
||||||
|
// array(['BYHOUR'=>[6, 18],'BYSECOND'=>[6, 18]],['1997-09-02','1997-09-02',date_create('1997-10-02')]),
|
||||||
|
// array(['BYMINUTE'=>[6, 18],'BYSECOND'=>[6, 18]],['1997-09-02','1997-09-02','1997-09-02']),
|
||||||
|
// array(['BYHOUR'=>[6, 18],'BYMINUTE'=>[6, 18],'BYSECOND'=>[6, 18]],['1997-09-02','1997-09-02','1997-09-02']),
|
||||||
|
// array(['BYMONTHDAY'=>[13, 17],'BYHOUR'=>[6, 18],'BYSETPOS'=>[3, -3]],[date_create('1997-09-13'),date_create('1997-09-17'),date_create('1997-10-13')])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,4 +161,158 @@ class RRuleTest extends PHPUnit_Framework_TestCase
|
|||||||
], $rule));
|
], $rule));
|
||||||
$this->assertEquals($occurrences, $rule->getOccurrences());
|
$this->assertEquals($occurrences, $rule->getOccurrences());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function weeklyRules()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array([],[date_create('1997-09-02'), date_create('1997-09-09'), date_create('1997-09-16')]),
|
||||||
|
array(['interval'=>2],[date_create('1997-09-02'),date_create('1997-09-16'),date_create('1997-09-30')]),
|
||||||
|
array(['interval'=>20],[date_create('1997-09-02'),date_create('1998-01-20'),date_create('1998-06-09')]),
|
||||||
|
array(['bymonth'=>[1, 3]],[date_create('1998-01-06'),date_create('1998-01-13'),date_create('1998-01-20')]),
|
||||||
|
array(['byday'=> ['TU', 'TH']],[date_create('1997-09-02'), date_create('1997-09-04'), date_create('1997-09-09')]),
|
||||||
|
|
||||||
|
# This test is interesting, because it crosses the year
|
||||||
|
# boundary in a weekly period to find day '1' as a
|
||||||
|
# valid recurrence.
|
||||||
|
array(['bymonth'=>[1, 3],'byday'=>['TU', 'TH']],[date_create('1998-01-01'), date_create('1998-01-06'), date_create('1998-01-08')]),
|
||||||
|
|
||||||
|
// array(['byhour'=>[6, 18]],[date_create('1997-09-02'),date_create('1997-09-09'),date_create('1997-09-09')]),
|
||||||
|
// array(['byminute'=>[6, 18]],[date_create('1997-09-02'),date_create('1997-09-02'),date_create('1997-09-09')]),
|
||||||
|
// array(['bysecond'=> [6, 18]],[date_create('1997-09-02'),date_create('1997-09-02'),date_create('1997-09-09')]),
|
||||||
|
// array(['byhour'=> [6, 18],'byminute'=>[6, 18]],[date_create('1997-09-02'),date_create('1997-09-02'),date_create('1997-09-09')]),
|
||||||
|
// array(['byhour'=>[6, 18],'bysecond'=>[6, 18]],[date_create('1997-09-02'),date_create('1997-09-02'),date_create('1997-09-09')]),
|
||||||
|
// array(['byminute'=>[6, 18],'bysecond'=>[6, 18]],[date_create('1997-09-02'),date_create('1997-09-02'),date_create('1997-09-02')]),
|
||||||
|
// array(['byhour'=>[6, 18],'byminute'=>[6, 18],'bysecond'=>[6, 18]],[date_create('1997-09-02'),date_create('1997-09-02'),date_create('1997-09-02')]),
|
||||||
|
// array(['byday'=>['TU', 'TH'],'byhour'=>[6, 18],'bysetpos'=>[3, -3]],[date_create('1997-09-02'),date_create('1997-09-04'),date_create('1997-09-09')])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @dataProvider weeklyRules
|
||||||
|
*/
|
||||||
|
public function testWeekly($rule, $occurrences)
|
||||||
|
{
|
||||||
|
$rule = new RRule(array_merge([
|
||||||
|
'FREQ' => 'WEEKLY',
|
||||||
|
'COUNT' => 3,
|
||||||
|
'DTSTART' => '1997-09-02'
|
||||||
|
], $rule));
|
||||||
|
$this->assertEquals($occurrences, $rule->getOccurrences());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dailyRules()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array([], [date_create('1997-09-02'),date_create('1997-09-03'),date_create('1997-09-04')]),
|
||||||
|
array(['interval'=>2],[date_create('1997-09-02'), date_create('1997-09-04'), date_create('1997-09-06')]),
|
||||||
|
array(['interval'=>92],[date_create('1997-09-02'), date_create('1997-12-03'), date_create('1998-03-05')]),
|
||||||
|
array(['bymonth'=>[1, 3]],[date_create('1998-01-01'), date_create('1998-01-02'), date_create('1998-01-03')]),
|
||||||
|
array(['bymonthday'=>[1, 3]],[date_create('1997-09-03'), date_create('1997-10-01'), date_create('1997-10-03')]),
|
||||||
|
array(['bymonth'=>[1, 3],'bymonthday'=>[5, 7]],[date_create('1998-01-05'), date_create('1998-01-07'), date_create('1998-03-05')]),
|
||||||
|
array(['byday'=>['TU', 'TH']],[date_create('1997-09-02'), date_create('1997-09-04'), date_create('1997-09-09')]),
|
||||||
|
array(['bymonth'=> [1, 3], 'byday'=> ['TU', 'TH']],[date_create('1998-01-01'), date_create('1998-01-06'), date_create('1998-01-08')]),
|
||||||
|
array(['bymonthday'=> [1, 3], 'byday'=>['TU', 'TH']],[date_create('1998-01-01'), date_create('1998-02-03'), date_create('1998-03-03')]),
|
||||||
|
array(['bymonth'=>[1, 3],'bymonthday'=>[1, 3],'byday'=>['TU', 'TH']],[date_create('1998-01-01'), date_create('1998-03-03'), date_create('2001-03-01')]),
|
||||||
|
// array(['count'=>4,'byyearday'=>[1, 100, 200, 365]],[date_create('1997-12-31'), date_create('1998-01-01'), date_create('1998-04-10'), date_create('1998-07-19')]),
|
||||||
|
// array(['count'=>4,'byyearday'=>[-365, -266, -166, -1]],[date_create('1997-12-31'), date_create('1998-01-01'), date_create('1998-04-10'), date_create('1998-07-19')]),
|
||||||
|
// array(['count'=>4, 'bymonth'=>[1, 7],'byyearday'=>[1, 100, 200, 365]],[date_create('1998-01-01'),date_create('1998-07-19'),date_create('1999-01-01'),date_create('1999-07-19')]),
|
||||||
|
// array(['count'=>4, 'bymonth' => [1, 7], 'byyearday' => [-365, -266, -166, -1]],[date_create('1998-01-01'), date_create('1998-07-19'), date_create('1999-01-01'), date_create('1999-07-19')]),
|
||||||
|
// array(['byweekno' => 20], [date_create('1998-05-11'), date_create('1998-05-12'), date_create('1998-05-13')]),
|
||||||
|
// array(['byweekno' => 1, 'byday' => 'MO'],[date_create('1997-12-29'),date_create('1999-01-04'),date_create('2000-01-03')]),
|
||||||
|
// array(['byweekno' => 52, 'byday' => 'SU'], [date_create('1997-12-28'), date_create('1998-12-27'), date_create('2000-01-02')]),
|
||||||
|
// array(['byweekno' => -1, 'byday' => 'SU'],[date_create('1997-12-28'),date_create('1999-01-03'),date_create('2000-01-02')]),
|
||||||
|
// array(['byweekno'=>53,'byday'=>'MO'],[date_create('1998-12-28'), date_create('2004-12-27'), date_create('2009-12-28')])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @dataProvider dailyRules
|
||||||
|
*/
|
||||||
|
public function testDaily($rule, $occurrences)
|
||||||
|
{
|
||||||
|
$rule = new RRule(array_merge([
|
||||||
|
'FREQ' => 'DAILY',
|
||||||
|
'COUNT' => 3,
|
||||||
|
'DTSTART' => '1997-09-02'
|
||||||
|
], $rule));
|
||||||
|
$this->assertEquals($occurrences, $rule->getOccurrences());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// def testDailyByHour(self):
|
||||||
|
// self.assertEqual(list(rrule(DAILY,
|
||||||
|
|
||||||
|
// byhour=(6, 18),
|
||||||
|
|
||||||
|
// [date_create('1997-09-02'),
|
||||||
|
// date_create('1997-09-03'),
|
||||||
|
// date_create('1997-09-03')])
|
||||||
|
|
||||||
|
// def testDailyByMinute(self):
|
||||||
|
// self.assertEqual(list(rrule(DAILY,
|
||||||
|
|
||||||
|
// byminute=(6, 18),
|
||||||
|
|
||||||
|
// [date_create('1997-09-02'),
|
||||||
|
// date_create('1997-09-02'),
|
||||||
|
// date_create('1997-09-03')])
|
||||||
|
|
||||||
|
// def testDailyBySecond(self):
|
||||||
|
// self.assertEqual(list(rrule(DAILY,
|
||||||
|
|
||||||
|
// bysecond=(6, 18),
|
||||||
|
|
||||||
|
// [date_create('1997-09-02'),
|
||||||
|
// date_create('1997-09-02'),
|
||||||
|
// date_create('1997-09-03')])
|
||||||
|
|
||||||
|
// def testDailyByHourAndMinute(self):
|
||||||
|
// self.assertEqual(list(rrule(DAILY,
|
||||||
|
|
||||||
|
// byhour=(6, 18),
|
||||||
|
// byminute=(6, 18),
|
||||||
|
|
||||||
|
// [date_create('1997-09-02'),
|
||||||
|
// date_create('1997-09-02'),
|
||||||
|
// date_create('1997-09-03')])
|
||||||
|
|
||||||
|
// def testDailyByHourAndSecond(self):
|
||||||
|
// self.assertEqual(list(rrule(DAILY,
|
||||||
|
|
||||||
|
// byhour=(6, 18),
|
||||||
|
// bysecond=(6, 18),
|
||||||
|
|
||||||
|
// [date_create('1997-09-02'),
|
||||||
|
// date_create('1997-09-02'),
|
||||||
|
// date_create('1997-09-03')])
|
||||||
|
|
||||||
|
// def testDailyByMinuteAndSecond(self):
|
||||||
|
// self.assertEqual(list(rrule(DAILY,
|
||||||
|
|
||||||
|
// byminute=(6, 18),
|
||||||
|
// bysecond=(6, 18),
|
||||||
|
|
||||||
|
// [date_create('1997-09-02'),
|
||||||
|
// date_create('1997-09-02'),
|
||||||
|
// date_create('1997-09-02')])
|
||||||
|
|
||||||
|
// def testDailyByHourAndMinuteAndSecond(self):
|
||||||
|
// self.assertEqual(list(rrule(DAILY,
|
||||||
|
|
||||||
|
// byhour=(6, 18),
|
||||||
|
// byminute=(6, 18),
|
||||||
|
// bysecond=(6, 18),
|
||||||
|
|
||||||
|
// [date_create('1997-09-02'),
|
||||||
|
// date_create('1997-09-02'),
|
||||||
|
// date_create('1997-09-02')])
|
||||||
|
|
||||||
|
// def testDailyBySetPos(self):
|
||||||
|
// self.assertEqual(list(rrule(DAILY,
|
||||||
|
|
||||||
|
// byhour=(6, 18),
|
||||||
|
// byminute=(15, 45),
|
||||||
|
// bysetpos=(3, -3),
|
||||||
|
|
||||||
|
// [date_create('1997-09-02'),
|
||||||
|
// date_create('1997-09-03'),
|
||||||
|
// date_create('1997-09-03')])
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user