Skip to content

Commit

Permalink
Merge pull request #1 from ftcvlad/tzid-bugfixes
Browse files Browse the repository at this point in the history
RFC compliance and fixes for issue simshaun#137
  • Loading branch information
ftcvlad committed Jun 8, 2020
2 parents d8bc5d3 + efdde8f commit 3ad214f
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 27 deletions.
92 changes: 68 additions & 24 deletions src/Recurr/Rule.php
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ public function __construct($rrule = null, $startDate = null, $endDate = null, $

if (is_array($rrule)) {
$this->loadFromArray($rrule);
} else if (!empty($rrule)) {
} elseif (!empty($rrule)) {
$this->loadFromString($rrule);
}
}
Expand Down Expand Up @@ -286,35 +286,64 @@ public function loadFromString($rrule)
*/
public function parseString($rrule)
{
if (strpos($rrule, 'DTSTART:') === 0) {
$pieces = explode(':', $rrule);
//rrule here can be:
//1) DTSTART:20200607T120200
//2) DTSTART;TZID=UTC:20200607T120200
//3) RRULE:FREQ=DAILY;INTERVAL=1
//4) FREQ=DAILY;DTSTART;TZID=UTC:20200607T120200;INTERVAL=1
//5) FREQ=DAILY;DTSTART:20200607T120200;INTERVAL=1

//SOLUTION: split by ';':
//1) [DTSTART:20200607T120200]
//2) [DTSTART, TZID=UTC:20200607T120200]
//3) [RRULE:FREQ=DAILY, INTERVAL=1]
//4) [FREQ=DAILY, DTSTART, TZID=UTC:20200607T120200, INTERVAL=1]
//5) [FREQ=DAILY, DTSTART:20200607T120200, INTERVAL=1]

$fragments = explode(';', $rrule);

if (!count($fragments)) {
throw new InvalidRRule('RRULE is empty');
}

if (count($pieces) !== 2) {
throw new InvalidRRule('DSTART is not valid');
$parts = array();
foreach ($fragments as $fragment) {
if (strpos($fragment, 'RRULE:') === 0) {
$fragment = str_replace('RRULE:', '', $fragment);
}

return array('DTSTART' => $pieces[1]);
}

if (strpos($rrule, 'RRULE:') === 0) {
$rrule = str_replace('RRULE:', '', $rrule);
}
if (strpos($fragment, 'DTSTART') === 0) {
if ($fragment === 'DTSTART') {
$parts['DTSTART'] = '';//to be replaced by next token

$pieces = explode(';', $rrule);
$parts = array();
continue;
}

if (!count($pieces)) {
throw new InvalidRRule('RRULE is empty');
}
$p = explode(':', $fragment);
if (count($p) !== 2) {
throw new InvalidRRule('DTSTART is not valid');
}

$parts['DTSTART'] = $p[1];

// Split each piece of the RRULE in to KEY=>VAL
foreach ($pieces as $piece) {
if (false === strpos($piece, '=')) {
continue;
}

list($key, $val) = explode('=', $piece);
$parts[$key] = $val;
if (strpos($fragment, '=')) {
list($key, $val) = explode('=', $fragment);

if ($key === 'TZID') {
$p = explode(':', $val);

$parts['TZID'] = $p[0];
$parts['DTSTART'] = $p[1];

continue;
}

$parts[$key] = $val;
}
}

return $parts;
Expand All @@ -341,11 +370,26 @@ public function loadFromArray($parts)
$this->setFreq(self::$freqs[$parts['FREQ']]);
}

// TZID
if (isset($parts['TZID'])) {
$this->setTimezone($parts['TZID']);
}

// DTSTART
if (isset($parts['DTSTART'])) {
$this->isStartDateFromDtstart = true;
$date = new \DateTime($parts['DTSTART']);
$date = $date->setTimezone(new \DateTimeZone($this->getTimezone()));

$timezone = new \DateTimeZone($this->getTimezone());
$date = null;
if (isset($parts['TZID'])) {
//DTSTART is datetime in TZID timezone
$date = new \DateTime($parts['DTSTART'], $timezone);
} else {
//DTSTART is UTC, convert to timezone coming from constructor/startDate/default
$date = new \DateTime($parts['DTSTART']);
$date = $date->setTimezone($timezone);
}

$this->setStartDate($date);
}

Expand Down Expand Up @@ -474,12 +518,12 @@ public function getString($timezoneType=self::TZ_FLOAT)
$date = $d->format($format);
$parts[] = "DTSTART;TZID=$tzid:$date";
} else {
$parts[] = 'DTSTART='.$this->getStartDate()->format($format);
$parts[] = 'DTSTART:'.$this->getStartDate()->format($format);
}
}

// DTEND
if ($this->endDate instanceof \DateTime) {
if ($this->endDate instanceof \DateTimeInterface) {
if ($timezoneType === self::TZ_FIXED) {
$d = $this->getEndDate();
$tzid = $d->getTimezone()->getName();
Expand Down
50 changes: 47 additions & 3 deletions tests/Recurr/Test/RuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Recurr\DateInclusion;
use Recurr\Frequency;
use Recurr\Rule;
use DateTimeImmutable;

class RuleTest extends \PHPUnit_Framework_TestCase
{
Expand All @@ -17,6 +18,49 @@ public function setUp()
$this->rule = new Rule;
}

public function testCanConvertRruleBackAndForthAndGetSameResult()
{
$rrules = [
"DTSTART:20200607T120200\r\nRRULE:FREQ=DAILY;INTERVAL=1",
"DTSTART;TZID=Europe/London:20200607T120200\r\nRRULE:FREQ=DAILY;INTERVAL=1"
];

foreach ($rrules as $rrule) {
$rule = new Rule($rrule);
$rruleOne = $rule->getString(Rule::TZ_FIXED);

$rule2 = new Rule($rruleOne);
$rruleTwo = $rule2->getString(Rule::TZ_FIXED);

$this->assertSame($rruleOne, $rruleTwo);
}
}

public function testCanCreateRuleFromStringHavingTzid()
{
$rule = new Rule("DTSTART;TZID=Europe/London:20200607T120200\r\nRRULE:FREQ=DAILY;INTERVAL=1");

$this->assertEquals('EUROPE/LONDON', $rule->getTimezone());

$startDate = $rule->getStartDate();
$this->assertSame('2020-06-07 12:02:00', $startDate->format('Y-m-d H:i:s'));


$this->assertSame('EUROPE/LONDON', $startDate->getTimezone()->getName());
}

public function testCanCreateRruleIfEndIsPassedAsDateTimeImmutable()
{
$begin = new DateTimeImmutable('2012-08-01');
$end = new DateTimeImmutable('2012-08-31');
$xmas = new DateTimeImmutable('2012-12-25');

$rule = new Rule('FREQ=WEEKLY;COUNT=5', $begin, $end);
$string = $rule->getString();

$this->assertEquals('FREQ=WEEKLY;COUNT=5;DTEND=20120831T000000', $string);
}

public function testConstructAcceptableStartDate()
{
$this->rule = new Rule(null, null);
Expand Down Expand Up @@ -282,7 +326,7 @@ public function testLoadFromStringWithDtstart()
$defaultTimezone = date_default_timezone_get();
date_default_timezone_set('America/Chicago');

$string = 'FREQ=MONTHLY;DTSTART=20140222T073000';
$string = 'FREQ=MONTHLY;DTSTART:20140222T073000';

$this->rule->setTimezone('America/Los_Angeles');
$this->rule->loadFromString($string);
Expand Down Expand Up @@ -363,7 +407,7 @@ public function testGetStringWithUTC()

public function testGetStringWithDtstart()
{
$string = 'FREQ=MONTHLY;DTSTART=20140210T163045;INTERVAL=1;WKST=MO';
$string = 'FREQ=MONTHLY;DTSTART:20140210T163045;INTERVAL=1;WKST=MO';

$this->rule->loadFromString($string);

Expand Down Expand Up @@ -424,7 +468,7 @@ public function testSetStartDateAffectsStringOutput()
$this->assertEquals('FREQ=MONTHLY;COUNT=2', $this->rule->getString());

$this->rule->setStartDate(new \DateTime('2015-12-10'), true);
$this->assertEquals('FREQ=MONTHLY;COUNT=2;DTSTART=20151210T000000', $this->rule->getString());
$this->assertEquals('FREQ=MONTHLY;COUNT=2;DTSTART:20151210T000000', $this->rule->getString());

$this->rule->setStartDate(new \DateTime('2015-12-10'), false);
$this->assertEquals('FREQ=MONTHLY;COUNT=2', $this->rule->getString());
Expand Down

0 comments on commit 3ad214f

Please sign in to comment.