mirror of
https://github.com/rlanvin/php-rrule.git
synced 2025-02-20 09:54:16 +01:00
Refactor RFC string parsing
Add the possibility to construct a RSet from a string Ref #26
This commit is contained in:
parent
a6d01f4036
commit
67d4a5dc98
@ -2,8 +2,15 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Add `explicit_infinite` and `dtstart` options to `humanReadable` to respectivity omit "forever" and the start date from the sentence.
|
||||
- RFC parser will not accept multiple DTSTART or RRULE lines
|
||||
- RSet constructor now accepts a string to build a RSET from a RFC string [#26](https://github.com/rlanvin/php-rrule/issues/26)
|
||||
- New factory method `RRule::createFromRfcString()` to build either a RRule or a RSet from a string
|
||||
|
||||
### Fixed
|
||||
|
||||
- When creating a RRule, the RFC parser will not accept multiple DTSTART or RRULE lines
|
||||
|
||||
## [1.4.2] - 2017-03-29
|
||||
|
||||
|
158
src/RRule.php
158
src/RRule.php
@ -196,7 +196,7 @@ class RRule implements RRuleInterface
|
||||
public function __construct($parts)
|
||||
{
|
||||
if ( is_string($parts) ) {
|
||||
$parts = self::parseRfcString($parts);
|
||||
$parts = RfcParser::parseRRule($parts);
|
||||
$parts = array_change_key_case($parts, CASE_UPPER);
|
||||
}
|
||||
elseif ( is_array($parts) ) {
|
||||
@ -652,141 +652,43 @@ class RRule implements RRuleInterface
|
||||
*
|
||||
* @throws \InvalidArgumentException on error
|
||||
*/
|
||||
static protected function parseRfcString($string)
|
||||
static public function parseRfcString($string)
|
||||
{
|
||||
$string = trim($string);
|
||||
$parts = array();
|
||||
$dtstart_type = null;
|
||||
$rfc_date_regexp = '/\d{6}(T\d{6})?Z?/'; // regexp to check the date, a bit loose
|
||||
$nb_dtstart = 0;
|
||||
$nb_rrule = 0;
|
||||
$lines = explode("\n", $string);
|
||||
trigger_error('parseRfcString() is deprecated - use new RRule(), RRule::createFromRfcString() or \RRule\RfcParser::parseRRule() if necessary',E_USER_DEPRECATED);
|
||||
return RfcParser::parseRRule($sring);
|
||||
}
|
||||
|
||||
foreach ( $lines as $line ) {
|
||||
$line = trim($line);
|
||||
if ( strpos($line,':') === false ) {
|
||||
if ( sizeof($lines) > 1 ) {
|
||||
throw new \InvalidArgumentException('Failed to parse RFC string, line is not starting with "RRULE:" in a multi-line RFC string');
|
||||
}
|
||||
$property_name = 'RRULE';
|
||||
$property_value = $line;
|
||||
/**
|
||||
* Take a RFC 5545 string and returns either a RRule or a RSet.
|
||||
*
|
||||
* @param bool $force_rset Force a RSet to be returned.
|
||||
* @return RRule|RSet
|
||||
*
|
||||
* @throws \InvalidArgumentException on error
|
||||
*/
|
||||
static public function createFromRfcString($string, $force_rset = false)
|
||||
{
|
||||
$class = '\RRule\RSet';
|
||||
|
||||
if ( ! $force_rset ) {
|
||||
// try to detect if we have a RRULE or a set
|
||||
$uppercased_string = strtoupper($string);
|
||||
$nb_rrule = substr_count($string, 'RRULE');
|
||||
if ( $nb_rrule == 0 ) {
|
||||
$class = '\RRule\RRule';
|
||||
}
|
||||
elseif ( $nb_rrule > 1 ) {
|
||||
$class = '\RRule\RSet';
|
||||
}
|
||||
else {
|
||||
list($property_name,$property_value) = explode(':',$line);
|
||||
}
|
||||
$tmp = explode(';',$property_name);
|
||||
$property_name = $tmp[0];
|
||||
$property_params = array();
|
||||
array_splice($tmp,0,1);
|
||||
foreach ( $tmp as $pair ) {
|
||||
if ( strpos($pair,'=') === false ) {
|
||||
throw new \InvalidArgumentException('Failed to parse RFC string, invalid property parameters: '.$pair);
|
||||
$class = '\RRule\RRule';
|
||||
if ( strpos($string, 'EXDATE') !== false || strpos($string, 'RDATE') !== false || strpos($string, 'EXRULE') !== false ) {
|
||||
$class = '\RRule\RSet';
|
||||
}
|
||||
list($key,$value) = explode('=',$pair);
|
||||
$property_params[$key] = $value;
|
||||
}
|
||||
|
||||
switch ( strtoupper($property_name) ) {
|
||||
case 'DTSTART':
|
||||
$nb_dtstart += 1;
|
||||
if ( $nb_dtstart > 1 ) {
|
||||
throw new \InvalidArgumentException('Too many DTSTART properties (there can be only one)');
|
||||
}
|
||||
$tmp = null;
|
||||
$dtstart_type = 'date';
|
||||
if ( ! preg_match($rfc_date_regexp, $property_value) ) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Invalid DTSTART property: date or date time format incorrect'
|
||||
);
|
||||
}
|
||||
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)
|
||||
if ( strpos($property_value, 'T') === false ) {
|
||||
throw new \InvalidArgumentException(
|
||||
'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
|
||||
// properties whose time values are specified in UTC.
|
||||
if ( strpos($property_value, 'Z') !== false ) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Invalid DTSTART property: TZID must not be applied when time is specified in UTC'
|
||||
);
|
||||
}
|
||||
$dtstart_type = 'tzid';
|
||||
$tmp = new \DateTimeZone($property_params['TZID']);
|
||||
}
|
||||
elseif ( strpos($property_value, 'T') !== false ) {
|
||||
if ( strpos($property_value, 'Z') === false ) {
|
||||
$dtstart_type = 'localtime'; // no timezone
|
||||
}
|
||||
else {
|
||||
$dtstart_type = 'utc';
|
||||
}
|
||||
}
|
||||
$parts['DTSTART'] = new \DateTime($property_value, $tmp);
|
||||
break;
|
||||
case 'RRULE':
|
||||
$nb_rrule += 1;
|
||||
if ( $nb_rrule > 1 ) {
|
||||
throw new \InvalidArgumentException('Too many RRULE properties (there can be only one)');
|
||||
}
|
||||
foreach ( explode(';',$property_value) as $pair ) {
|
||||
$pair = explode('=', $pair);
|
||||
if ( ! isset($pair[1]) || isset($pair[2]) ) {
|
||||
throw new \InvalidArgumentException("Failed to parse RFC string, malformed RRULE property: $property_value");
|
||||
}
|
||||
list($key, $value) = $pair;
|
||||
if ( $key === 'UNTIL' ) {
|
||||
if ( ! preg_match($rfc_date_regexp, $value) ) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Invalid UNTIL property: date or date time format incorrect'
|
||||
);
|
||||
}
|
||||
switch ( $dtstart_type ) {
|
||||
case 'date':
|
||||
if ( strpos($value, 'T') !== false) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Invalid UNTIL property: The value of the UNTIL rule part MUST be a date if DTSTART is a date.'
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'localtime':
|
||||
if ( strpos($value, 'T') === false || strpos($value, 'Z') !== false ) {
|
||||
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'
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'tzid':
|
||||
case 'utc':
|
||||
if ( strpos($value, 'T') === false || strpos($value, 'Z') === false ) {
|
||||
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.'
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$value = new \DateTime($value);
|
||||
}
|
||||
elseif ( $key === 'DTSTART' ) {
|
||||
if ( isset($parts['DTSTART']) ) {
|
||||
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
|
||||
// see https://github.com/rlanvin/php-rrule/issues/25
|
||||
trigger_error("This string is not compliant with the RFC (DTSTART cannot be part of RRULE). It is accepted as is for compability reasons only.", E_USER_NOTICE);
|
||||
}
|
||||
$parts[$key] = $value;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new \InvalidArgumentException('Failed to parse RFC string, unsupported property: '.$property_name);
|
||||
}
|
||||
}
|
||||
|
||||
return $parts;
|
||||
return new $class($string);
|
||||
}
|
||||
|
||||
/**
|
||||
|
69
src/RSet.php
69
src/RSet.php
@ -55,10 +55,77 @@ class RSet implements RRuleInterface
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $string a RFC compliant text block
|
||||
*/
|
||||
public function __construct()
|
||||
public function __construct($string = null)
|
||||
{
|
||||
if ( $string && is_string($string) ) {
|
||||
$string = trim($string);
|
||||
$rrules = array();
|
||||
$exrules = array();
|
||||
$rdates = array();
|
||||
$exdates = array();
|
||||
$dtstart = null;
|
||||
|
||||
// parse
|
||||
$lines = explode("\n", $string);
|
||||
foreach ( $lines as $line ) {
|
||||
$line = trim($line);
|
||||
|
||||
if ( strpos($line,':') === false ) {
|
||||
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);
|
||||
$tmp = explode(";",$property_name);
|
||||
$property_name = $tmp[0];
|
||||
switch ( strtoupper($property_name) ) {
|
||||
case 'DTSTART':
|
||||
if ( $dtstart !== null ) {
|
||||
throw new \InvalidArgumentException('Failed to parse RFC string, multiple DTSTART found');
|
||||
}
|
||||
$dtstart = $line;
|
||||
break;
|
||||
case 'RRULE':
|
||||
$rrules[] = $line;
|
||||
break;
|
||||
case 'EXRULE':
|
||||
$exrules[] = $line;
|
||||
break;
|
||||
case 'RDATE':
|
||||
$rdates = array_merge($rdates, RfcParser::parseRDate($line));
|
||||
break;
|
||||
case 'EXDATE':
|
||||
$exdates = array_merge($exdates, RfcParser::parseExDate($line));
|
||||
break;
|
||||
default:
|
||||
throw new \InvalidArgumentException("Failed to parse RFC, unknown property: $property_name");
|
||||
}
|
||||
}
|
||||
foreach ( $rrules as $rrule ) {
|
||||
if ( $dtstart ) {
|
||||
$rrule = $dtstart."\n".$rrule;
|
||||
}
|
||||
|
||||
$this->addRRule($rrule);
|
||||
}
|
||||
|
||||
foreach ( $exrules as $rrule ) {
|
||||
if ( $dtstart ) {
|
||||
$rrule = $dtstart."\n".$rrule;
|
||||
}
|
||||
$this->addExRule($rrule);
|
||||
}
|
||||
|
||||
foreach ( $rdates as $date ) {
|
||||
$this->addDate($date);
|
||||
}
|
||||
|
||||
foreach ( $exdates as $date ) {
|
||||
$this->addExDate($date);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
287
src/RfcParser.php
Executable file
287
src/RfcParser.php
Executable file
@ -0,0 +1,287 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Licensed under the MIT license.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE file.
|
||||
*
|
||||
* @author Rémi Lanvin <remi@cloudconnected.fr>
|
||||
* @link https://github.com/rlanvin/php-rrule
|
||||
*/
|
||||
|
||||
namespace RRule;
|
||||
|
||||
/**
|
||||
* Collection of static methods to parse RFC strings.
|
||||
*
|
||||
* This is used internally by RRule and RSet. The methods are public, BUT this
|
||||
* isn't part of the public API of this library. Therefore there is no guarantee
|
||||
* they will not break even for a minor version release.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class RfcParser
|
||||
{
|
||||
|
||||
/**
|
||||
* High level "line".
|
||||
* Explode a line into property name, property parameters and property value
|
||||
*/
|
||||
static public function parseLine($line, array $default = array())
|
||||
{
|
||||
$line = trim($line);
|
||||
$property = array_merge([
|
||||
'name' => '',
|
||||
'params' => [],
|
||||
'value' => null
|
||||
], $default);
|
||||
|
||||
if ( strpos($line,':') === false ) {
|
||||
if ( ! $property['name'] ) {
|
||||
throw new \InvalidArgumentException('Failed to parse RFC line, missing property name followed by ":"');
|
||||
}
|
||||
$property['value'] = $line;
|
||||
}
|
||||
else {
|
||||
list($property['name'],$property['value']) = explode(':', $line);
|
||||
|
||||
$tmp = explode(';',$property['name']);
|
||||
$property['name'] = $tmp[0];
|
||||
array_splice($tmp,0,1);
|
||||
foreach ( $tmp as $pair ) {
|
||||
if ( strpos($pair,'=') === false ) {
|
||||
throw new \InvalidArgumentException('Failed to parse RFC line, invalid property parameters: '.$pair);
|
||||
}
|
||||
list($key,$value) = explode('=',$pair);
|
||||
$property['params'][$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $property;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse both DTSTART and RRULE (and EXRULE).
|
||||
*
|
||||
* It's impossible to accuractly parse a RRULE in isolation (without the DTSTART)
|
||||
* as some tests depends on DTSTART (notably the date format for UNTIL).
|
||||
*/
|
||||
static public function parseRRule($string)
|
||||
{
|
||||
$string = trim($string);
|
||||
$parts = array();
|
||||
$dtstart_type = null;
|
||||
$rfc_date_regexp = '/\d{6}(T\d{6})?Z?/'; // regexp to check the date, a bit loose
|
||||
$nb_dtstart = 0;
|
||||
$nb_rrule = 0;
|
||||
$lines = explode("\n", $string);
|
||||
|
||||
foreach ( $lines as $line ) {
|
||||
$property = self::parseLine($line, [
|
||||
'name' => sizeof($lines) > 1 ? null : 'RRULE' // allow missing property name for single-line RRULE
|
||||
]);
|
||||
|
||||
switch ( strtoupper($property['name']) ) {
|
||||
case 'DTSTART':
|
||||
$nb_dtstart += 1;
|
||||
if ( $nb_dtstart > 1 ) {
|
||||
throw new \InvalidArgumentException('Too many DTSTART properties (there can be only one)');
|
||||
}
|
||||
$tmp = null;
|
||||
$dtstart_type = 'date';
|
||||
if ( ! preg_match($rfc_date_regexp, $property['value']) ) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Invalid DTSTART property: date or date time format incorrect'
|
||||
);
|
||||
}
|
||||
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)
|
||||
if ( strpos($property['value'], 'T') === false ) {
|
||||
throw new \InvalidArgumentException(
|
||||
'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
|
||||
// properties whose time values are specified in UTC.
|
||||
if ( strpos($property['value'], 'Z') !== false ) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Invalid DTSTART property: TZID must not be applied when time is specified in UTC'
|
||||
);
|
||||
}
|
||||
$dtstart_type = 'tzid';
|
||||
$tmp = new \DateTimeZone($property['params']['TZID']);
|
||||
}
|
||||
elseif ( strpos($property['value'], 'T') !== false ) {
|
||||
if ( strpos($property['value'], 'Z') === false ) {
|
||||
$dtstart_type = 'localtime'; // no timezone
|
||||
}
|
||||
else {
|
||||
$dtstart_type = 'utc';
|
||||
}
|
||||
}
|
||||
$parts['DTSTART'] = new \DateTime($property['value'], $tmp);
|
||||
break;
|
||||
case 'RRULE':
|
||||
case 'EXRULE':
|
||||
$nb_rrule += 1;
|
||||
if ( $nb_rrule > 1 ) {
|
||||
throw new \InvalidArgumentException('Too many RRULE properties (there can be only one)');
|
||||
}
|
||||
foreach ( explode(';',$property['value']) as $pair ) {
|
||||
$pair = explode('=', $pair);
|
||||
if ( ! isset($pair[1]) || isset($pair[2]) ) {
|
||||
throw new \InvalidArgumentException("Failed to parse RFC string, malformed RRULE property: {$property['value']}");
|
||||
}
|
||||
list($key, $value) = $pair;
|
||||
if ( $key === 'UNTIL' ) {
|
||||
if ( ! preg_match($rfc_date_regexp, $value) ) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Invalid UNTIL property: date or date time format incorrect'
|
||||
);
|
||||
}
|
||||
switch ( $dtstart_type ) {
|
||||
case 'date':
|
||||
if ( strpos($value, 'T') !== false) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Invalid UNTIL property: The value of the UNTIL rule part MUST be a date if DTSTART is a date.'
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'localtime':
|
||||
if ( strpos($value, 'T') === false || strpos($value, 'Z') !== false ) {
|
||||
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'
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'tzid':
|
||||
case 'utc':
|
||||
if ( strpos($value, 'T') === false || strpos($value, 'Z') === false ) {
|
||||
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.'
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$value = new \DateTime($value);
|
||||
}
|
||||
elseif ( $key === 'DTSTART' ) {
|
||||
if ( isset($parts['DTSTART']) ) {
|
||||
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
|
||||
// see https://github.com/rlanvin/php-rrule/issues/25
|
||||
trigger_error("This string is not compliant with the RFC (DTSTART cannot be part of RRULE). It is accepted as is for compability reasons only.", E_USER_NOTICE);
|
||||
}
|
||||
$parts[$key] = $value;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new \InvalidArgumentException('Failed to parse RFC string, unsupported property: '.$property['name']);
|
||||
}
|
||||
}
|
||||
|
||||
return $parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse RDATE and return an array of DateTime
|
||||
*/
|
||||
static public function parseRDate($line)
|
||||
{
|
||||
$property = self::parseLine($line);
|
||||
if ( $property['name'] !== 'RDATE' ) {
|
||||
throw new \InvalidArgumentException("Failed to parse RDATE line, this is a {$property['name']} property");
|
||||
}
|
||||
|
||||
$period = false;
|
||||
$tz = null;
|
||||
foreach ( $property['params'] as $name => $value ) {
|
||||
switch ( strtoupper($name) ) {
|
||||
case 'TZID':
|
||||
$tz = new \DateTimeZone($value);
|
||||
break;
|
||||
case 'VALUE':
|
||||
switch ( $value ) {
|
||||
case 'DATE':
|
||||
case 'DATE-TIME':
|
||||
break;
|
||||
case 'PERIOD':
|
||||
$period = true;
|
||||
break;
|
||||
default:
|
||||
throw new \InvalidArgumentException("Unknown VALUE value for RDATE: $value, must be one of DATE-TIME, DATE or PERIOD");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new \InvalidArgumentException("Unknown property parameter: $name");
|
||||
}
|
||||
}
|
||||
|
||||
$dates = array();
|
||||
|
||||
foreach ( explode(',',$property['value']) as $value ) {
|
||||
if ( $period ) {
|
||||
if ( strpos($value,'/') === false ) {
|
||||
throw new \InvalidArgumentException('Invalid period in RDATE');
|
||||
}
|
||||
// period is unsupported!
|
||||
trigger_error('VALUE=PERIOD is not supported and ignored', E_USER_NOTICE);
|
||||
}
|
||||
else {
|
||||
if ( strpos($value, 'Z') ) {
|
||||
if ( $tz !== null ) {
|
||||
throw new \InvalidArgumentException('Invalid RDATE property: TZID must not be applied when time is specified in UTC');
|
||||
}
|
||||
$dates[] = new \DateTime($value);
|
||||
}
|
||||
else {
|
||||
$dates[] = new \DateTime($value, $tz);
|
||||
}
|
||||
// TODO should check that only dates are provided with VALUE=DATE, and so on.
|
||||
}
|
||||
}
|
||||
|
||||
return $dates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse EXDATE and return an array of DateTime
|
||||
*/
|
||||
static public function parseExDate($line)
|
||||
{
|
||||
$property = self::parseLine($line);
|
||||
if ( $property['name'] !== 'EXDATE' ) {
|
||||
throw new \InvalidArgumentException("Failed to parse EXDATE line, this is a {$property['name']} property");
|
||||
}
|
||||
|
||||
$tz = null;
|
||||
foreach ( $property['params'] as $name => $value ) {
|
||||
switch ( strtoupper($name) ) {
|
||||
case 'TZID':
|
||||
$tz = new \DateTimeZone($value);
|
||||
break;
|
||||
default:
|
||||
throw new \InvalidArgumentException("Unknown property parameter: $name");
|
||||
}
|
||||
}
|
||||
|
||||
$dates = array();
|
||||
|
||||
foreach ( explode(',',$property['value']) as $value ) {
|
||||
if ( strpos($value, 'Z') ) {
|
||||
if ( $tz !== null ) {
|
||||
throw new \InvalidArgumentException('Invalid EXDATE property: TZID must not be applied when time is specified in UTC');
|
||||
}
|
||||
$dates[] = new \DateTime($value);
|
||||
}
|
||||
else {
|
||||
$dates[] = new \DateTime($value, $tz);
|
||||
}
|
||||
}
|
||||
|
||||
return $dates;
|
||||
}
|
||||
|
||||
}
|
@ -2088,6 +2088,65 @@ class RRuleTest extends PHPUnit_Framework_TestCase
|
||||
$this->assertEquals($expected_str, $rule->rfcString());
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// RFC Factory method
|
||||
|
||||
public function rfcStringsForFactory()
|
||||
{
|
||||
return array(
|
||||
array('DTSTART;TZID=America/New_York:19970901T090000
|
||||
RRULE:FREQ=HOURLY;UNTIL=19971224T000000Z;WKST=SU;BYDAY=MO,WE,FR;BYMONTH=1;BYHOUR=1',
|
||||
'\RRule\RRule'
|
||||
),
|
||||
array('DTSTART;TZID=America/New_York:19970512T090000
|
||||
RRULE:FREQ=YEARLY;BYYEARDAY=1,-1,10,-50;BYDAY=MO',
|
||||
'\RRule\RRule'
|
||||
),
|
||||
array('DTSTART:19970512T090000Z
|
||||
RRULE:FREQ=YEARLY',
|
||||
'\RRule\RRule'
|
||||
),
|
||||
array('DTSTART:19970512T090000
|
||||
RRULE:FREQ=YEARLY',
|
||||
'\RRule\RRule'
|
||||
),
|
||||
array('DTSTART:19970512
|
||||
RRULE:FREQ=YEARLY',
|
||||
'\RRule\RRule'
|
||||
),
|
||||
// empty lines
|
||||
array("\nDTSTART:19970512\nRRULE:FREQ=YEARLY;COUNT=3\n\n",
|
||||
'\RRule\RRule'
|
||||
),
|
||||
// no DTSTART
|
||||
array("RRULE:FREQ=YEARLY;COUNT=3",
|
||||
'\RRule\RRule'
|
||||
),
|
||||
array(
|
||||
"DTSTART;TZID=America/New_York:19970901T090000\nRRULE:FREQ=DAILY\nEXRULE:FREQ=YEARLY\nEXDATE;TZID=America/New_York:19970902T090000",
|
||||
'\RRule\RSet'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider rfcStringsForFactory
|
||||
*/
|
||||
public function testCreateFromRfcString($string, $expected_class)
|
||||
{
|
||||
$object = RRule::createFromRfcString($string);
|
||||
$this->assertInstanceOf($expected_class, $object);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider rfcStringsForFactory
|
||||
*/
|
||||
public function testCreateFromRfcStringForceRSet($string)
|
||||
{
|
||||
$object = RRule::createFromRfcString($string, true);
|
||||
$this->assertInstanceOf('\RRule\RSet', $object);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Timezone
|
||||
|
||||
|
@ -426,4 +426,77 @@ class RSetTest extends PHPUnit_Framework_TestCase
|
||||
$this->assertInternalType('array', $rset->getExDates());
|
||||
$this->assertCount(0, $rset->getExDates());
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// RFC Strings
|
||||
|
||||
public function rfcStrings()
|
||||
{
|
||||
return array(
|
||||
array(
|
||||
"DTSTART;TZID=America/New_York:19970901T090000
|
||||
RRULE:FREQ=DAILY;COUNT=3
|
||||
EXDATE;TZID=America/New_York:19970902T090000",
|
||||
array(
|
||||
date_create('1997-09-01 09:00:00', new \DateTimeZone('America/New_York')),
|
||||
date_create('1997-09-03 09:00:00', new \DateTimeZone('America/New_York'))
|
||||
)
|
||||
),
|
||||
array(
|
||||
"DTSTART;TZID=America/New_York:19970901T090000
|
||||
RRULE:FREQ=DAILY;COUNT=3
|
||||
EXRULE:FREQ=DAILY;INTERVAL=2;COUNT=1
|
||||
EXDATE;TZID=America/New_York:19970903T090000
|
||||
RDATE;TZID=America/New_York:19970904T090000",
|
||||
array(
|
||||
date_create('1997-09-02 09:00:00', new \DateTimeZone('America/New_York')),
|
||||
date_create('1997-09-04 09:00:00', new \DateTimeZone('America/New_York'))
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider rfcStrings
|
||||
*/
|
||||
public function testParseRfcString($string, $occurrences)
|
||||
{
|
||||
$object = new RSet($string);
|
||||
$this->assertEquals($occurrences, $object->getOccurrences());
|
||||
}
|
||||
|
||||
public function quirkyRfcStrings()
|
||||
{
|
||||
return array(
|
||||
array(
|
||||
'RRULE:FREQ=MONTHLY;DTSTART=20170201T010000Z;UNTIL=20170228T030000Z;BYDAY=TU
|
||||
RDATE:20170222T010000Z
|
||||
EXDATE:20170221T010000Z',
|
||||
array(
|
||||
date_create('20170207T010000Z'),
|
||||
date_create('20170214T010000Z'),
|
||||
date_create('20170222T010000Z'),
|
||||
date_create('20170228T010000Z')
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider quirkyRfcStrings
|
||||
* @expectedException PHPUnit_Framework_Error_Notice
|
||||
*/
|
||||
public function testParseQuirkyRfcStringNotice($string, $occurrences)
|
||||
{
|
||||
$object = new RSet($string);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider quirkyRfcStrings
|
||||
*/
|
||||
public function testParseQuirkyRfcString($string, $occurrences)
|
||||
{
|
||||
$object = @ new RSet($string);
|
||||
$this->assertEquals($occurrences, $object->getOccurrences());
|
||||
}
|
||||
}
|
89
tests/RfcParserTest.php
Executable file
89
tests/RfcParserTest.php
Executable file
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
use RRule\RfcParser;
|
||||
|
||||
class RfcParserTest extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function rfcLines()
|
||||
{
|
||||
return array(
|
||||
array(
|
||||
'RDATE;TZID=America/New_York:19970714T083000',
|
||||
array(),
|
||||
array('name' => 'RDATE', 'params' => array('TZID' => 'America/New_York'), 'value' => '19970714T083000')
|
||||
),
|
||||
array(
|
||||
'RRULE:FREQ=YEARLY;UNTIL=20170202',
|
||||
array(),
|
||||
array('name' => 'RRULE', 'params' => array(), 'value' => 'FREQ=YEARLY;UNTIL=20170202')
|
||||
),
|
||||
array(
|
||||
'DTSTART=20160202T000000Z;FREQ=DAILY;UNTIL=20160205T000000Z',
|
||||
array('name' => 'RRULE'),
|
||||
array('name' => 'RRULE', 'params' => array(), 'value' => 'DTSTART=20160202T000000Z;FREQ=DAILY;UNTIL=20160205T000000Z')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider rfcLines
|
||||
*/
|
||||
public function testParseLine($line, $default, $expected)
|
||||
{
|
||||
$this->assertEquals($expected, RfcParser::parseLine($line, $default));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// RDATE
|
||||
|
||||
public function rdateLines()
|
||||
{
|
||||
return array(
|
||||
array('RDATE:19970714T123000Z',
|
||||
array(date_create('19970714T123000Z'))
|
||||
),
|
||||
array('RDATE;TZID=America/New_York:19970714T083000',
|
||||
array(date_create('19970714T083000',new \DateTimeZone('America/New_York')))
|
||||
),
|
||||
// array('RDATE;VALUE=PERIOD:19960403T020000Z/19960403T040000Z,19960404T010000Z/PT3H',
|
||||
// array()
|
||||
// ),
|
||||
array('RDATE;VALUE=DATE:19970101,19970120',
|
||||
array(date_create('19970101'),date_create('19970120'))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider rdateLines
|
||||
*/
|
||||
public function testParseRDate($string, $expected)
|
||||
{
|
||||
$dates = RfcParser::parseRDate($string);
|
||||
$this->assertEquals($dates, $expected);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// EXDATE
|
||||
|
||||
public function exdateLines()
|
||||
{
|
||||
return array(
|
||||
array('EXDATE:19960402T010000Z,19960403T010000Z,19960404T010000Z',
|
||||
array(date_create('19960402T010000Z'),date_create('19960403T010000Z'),date_create('19960404T010000Z'))
|
||||
),
|
||||
array('EXDATE;TZID=America/New_York:19970714T083000',
|
||||
array(date_create('19970714T083000',new \DateTimeZone('America/New_York')))
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider exdateLines
|
||||
*/
|
||||
public function testParseExDate($string, $expected)
|
||||
{
|
||||
$dates = RfcParser::parseExDate($string);
|
||||
$this->assertEquals($dates, $expected);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user