diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a40710d2..975ab2f56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,14 @@ ChangeLog ========= -4.0.0-alpha2 (2015-??-??) +4.0.0-alpha3 (????-??-??) +------------------------- + +* #258: Support for expanding events that use `RDATE`. (@jabdoa2) +* #258: Correctly support TZID for events that use `RDATE`. (@jabdoa2) + + +4.0.0-alpha2 (2015-09-04) ------------------------- * Updated windows timezone file to support new mexican timezone. @@ -9,6 +16,10 @@ ChangeLog * #250: `isInTimeRange()` now considers the timezone for floating dates and times. (@armin-hackmann) * Added a duplicate vcard merging tool for the command line. +* #253: `isInTimeRange()` now correctly handles events that throw the + `NoInstancesException` exception. (@migrax, @DominikTo) +* #254: The parser threw an `E_NOTICE` for certain invalid objects. It now + correctly throws a `ParseException`. 4.0.0-alpha1 (2015-07-17) @@ -46,6 +57,13 @@ ChangeLog and `IntegerValue` to allow PHP 7 compatibility. +3.4.7 (2015-09-04) +------------------ + +* #253: Handle `isInTimeRange` for recurring events that have 0 valid + instances. (@DominikTo, @migrax). + + 3.4.6 (2015-08-06) ------------------ diff --git a/lib/BirthdayCalendarGenerator.php b/lib/BirthdayCalendarGenerator.php index 05df5091c..f93030e91 100644 --- a/lib/BirthdayCalendarGenerator.php +++ b/lib/BirthdayCalendarGenerator.php @@ -120,6 +120,13 @@ function getResult() { continue; } + // We've seen clients (ez-vcard) putting "BDAY:" properties + // without a value into vCards. If we come across those, we'll + // skip them. + if (empty($object->BDAY->getValue())) { + continue; + } + // We're always converting to vCard 4.0 so we can rely on the // VCardConverter handling the X-APPLE-OMIT-YEAR property for us. $object = $object->convert(Document::VCARD40); diff --git a/lib/Component/VCalendar.php b/lib/Component/VCalendar.php index d1cd30833..d39c31e52 100644 --- a/lib/Component/VCalendar.php +++ b/lib/Component/VCalendar.php @@ -310,7 +310,7 @@ function expand(DateTimeInterface $start, DateTimeInterface $end, DateTimeZone $ throw new \LogicException('Event did not have a UID!'); } - if (isset($vevent->{'RECURRENCE-ID'}) || isset($vevent->RRULE)) { + if (isset($vevent->{'RECURRENCE-ID'}) || isset($vevent->RRULE) || isset($vevent->{'RDATE'})) { if (isset($recurringEvents[$uid])) { $recurringEvents[$uid][] = $vevent; } else { diff --git a/lib/Component/VEvent.php b/lib/Component/VEvent.php index 3b3c69bec..ddc4dab08 100644 --- a/lib/Component/VEvent.php +++ b/lib/Component/VEvent.php @@ -5,6 +5,7 @@ use DateTimeInterface; use Sabre\VObject; use Sabre\VObject\Recur\EventIterator; +use Sabre\VObject\Recur\NoInstancesException; /** * VEvent component. @@ -32,7 +33,19 @@ class VEvent extends VObject\Component { function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end) { if ($this->RRULE) { - $it = new EventIterator($this, null, $start->getTimezone()); + + try { + + $it = new EventIterator($this, null, $start->getTimezone()); + + } catch (NoInstancesException $e) { + + // If we've catched this exception, there are no instances + // for the event that fall into the specified time-range. + return false; + + } + $it->fastForward($start); // We fast-forwarded to a spot where the end-time of the diff --git a/lib/FreeBusyData.php b/lib/FreeBusyData.php index 487a0b342..00e791218 100644 --- a/lib/FreeBusyData.php +++ b/lib/FreeBusyData.php @@ -92,7 +92,7 @@ function add($start, $end, $type) { if ($this->data[$insertStartIndex - 1]['start'] === $start) { // The old item starts at the exact same point as the new item. $insertStartIndex--; - } + } // Now we know where to insert the item, we need to know where it // starts overlapping with items on the tail end. We need to start diff --git a/lib/Parser/MimeDir.php b/lib/Parser/MimeDir.php index 986708f68..a98a0f059 100644 --- a/lib/Parser/MimeDir.php +++ b/lib/Parser/MimeDir.php @@ -344,6 +344,9 @@ protected function readProperty($line) { $value = $this->unescapeParam($value); + if (is_null($lastParam)) { + throw new ParseException('Invalid Mimedir file. Line starting at ' . $this->startLine . ' did not follow iCalendar/vCard conventions'); + } if (is_null($property['parameters'][$lastParam])) { $property['parameters'][$lastParam] = $value; } elseif (is_array($property['parameters'][$lastParam])) { diff --git a/lib/Property/ICalendar/Recur.php b/lib/Property/ICalendar/Recur.php index b75d78643..a802aa6a0 100644 --- a/lib/Property/ICalendar/Recur.php +++ b/lib/Property/ICalendar/Recur.php @@ -51,6 +51,9 @@ function setValue($value) { if (strpos($v, ',') !== false) { $v = explode(',', $v); } + if (strcmp($k, 'until') === 0) { + $v = strtr($v, [':' => '', '-' => '']); + } } else { $v = array_map('strtoupper', $v); } @@ -91,7 +94,6 @@ function getValue() { * Sets a multi-valued property. * * @param array $parts - * * @return void */ function setParts(array $parts) { diff --git a/lib/Recur/RDateIterator.php b/lib/Recur/RDateIterator.php index 985c2d75c..18bda5a29 100644 --- a/lib/Recur/RDateIterator.php +++ b/lib/Recur/RDateIterator.php @@ -91,7 +91,8 @@ function next() { $this->currentDate = DateTimeParser::parse( - $this->dates[$this->counter - 1] + $this->dates[$this->counter - 1], + $this->startDate->getTimezone() ); } diff --git a/lib/Version.php b/lib/Version.php index e14f5fb7f..ea5778271 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,6 +14,6 @@ class Version { /** * Full version number. */ - const VERSION = '4.0.0-alpha2'; + const VERSION = '4.0.0-alpha3'; } diff --git a/tests/VObject/BirthdayCalendarGeneratorTest.php b/tests/VObject/BirthdayCalendarGeneratorTest.php index a28484a28..1fc11803f 100644 --- a/tests/VObject/BirthdayCalendarGeneratorTest.php +++ b/tests/VObject/BirthdayCalendarGeneratorTest.php @@ -414,6 +414,35 @@ function testVcardStringWithValidBirthdayLocalized() { } + function testVcardStringWithEmptyBirthdayProperty() { + + $generator = new BirthdayCalendarGenerator(); + $input = <<setObjects($input); + $output = $generator->getResult(); + + $this->assertVObjEquals( + $expected, + $output + ); + + } + /** * @expectedException \Sabre\VObject\ParseException */ diff --git a/tests/VObject/Component/VEventTest.php b/tests/VObject/Component/VEventTest.php index 1bf1ceefc..2ef6ea1c6 100644 --- a/tests/VObject/Component/VEventTest.php +++ b/tests/VObject/Component/VEventTest.php @@ -71,6 +71,16 @@ function timeRangeTestData() { $tests[] = [$vevent7, new \DateTime('2012-02-01 15:00:00'), new \DateTime('2012-02-02'), true]; // The timezone of timerange in question should also be considered. $tests[] = [$vevent7, new \DateTime('2012-02-02 00:00:00', new \DateTimeZone('Europe/Berlin')), new \DateTime('2012-02-03 00:00:00', new \DateTimeZone('Europe/Berlin')), false]; + + // Added this test to check recurring events that have no instances. + $vevent8 = clone $vevent; + $vevent8->DTSTART = '20130329T140000'; + $vevent8->DTEND = '20130329T153000'; + $vevent8->RRULE = ['FREQ' => 'WEEKLY', 'BYDAY' => ['FR'], 'UNTIL' => '20130412T115959Z']; + $vevent8->add('EXDATE', '20130405T140000'); + $vevent8->add('EXDATE', '20130329T140000'); + $tests[] = [$vevent8, new \DateTime('2013-03-01'), new \DateTime('2013-04-01'), false]; + return $tests; } diff --git a/tests/VObject/Issue259Test.php b/tests/VObject/Issue259Test.php new file mode 100644 index 000000000..4a73be560 --- /dev/null +++ b/tests/VObject/Issue259Test.php @@ -0,0 +1,21 @@ +setInput($jcalWithUntil); + + $vcalendar = $parser->parse(); + $eventAsArray = $vcalendar->select('VEVENT'); + $event = reset($eventAsArray); + $rruleAsArray = $event->select('RRULE'); + $rrule = reset($rruleAsArray); + $this->assertNotNull($rrule); + $this->assertEquals($rrule->getValue(), 'FREQ=MONTHLY;UNTIL=20160101T220000Z'); + } + +} diff --git a/tests/VObject/IssueUndefinedIndexTest.php b/tests/VObject/IssueUndefinedIndexTest.php new file mode 100644 index 000000000..9b6292e1d --- /dev/null +++ b/tests/VObject/IssueUndefinedIndexTest.php @@ -0,0 +1,30 @@ +assertInstanceOf('Sabre\\VObject\\Component\\VCard', $vcard); + + } + +} diff --git a/tests/VObject/Recur/EventIterator/HandleRDateExpandTest.php b/tests/VObject/Recur/EventIterator/HandleRDateExpandTest.php new file mode 100644 index 000000000..027fc5571 --- /dev/null +++ b/tests/VObject/Recur/EventIterator/HandleRDateExpandTest.php @@ -0,0 +1,59 @@ +assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + + $vcal->expand(new DateTime('2015-01-01'), new DateTime('2015-12-01')); + + $result = iterator_to_array($vcal->vevent); + + $this->assertEquals(5, count($result)); + + $utc = new DateTimeZone('UTC'); + $expected = array( + new DateTimeImmutable("2015-10-12", $utc), + new DateTimeImmutable("2015-10-15", $utc), + new DateTimeImmutable("2015-10-17", $utc), + new DateTimeImmutable("2015-10-18", $utc), + new DateTimeImmutable("2015-10-20", $utc), + ); + + $result = array_map(function($ev){return $ev->dtstart->getDateTime();}, $result); + $this->assertEquals($expected, $result); + + } + +} diff --git a/tests/VObject/Recur/RDateIteratorTest.php b/tests/VObject/Recur/RDateIteratorTest.php index 2c9c7f1bf..7abf0e004 100644 --- a/tests/VObject/Recur/RDateIteratorTest.php +++ b/tests/VObject/Recur/RDateIteratorTest.php @@ -27,6 +27,30 @@ function testSimple() { } + function testTimezone() { + + $tz = new DateTimeZone('Europe/Berlin'); + $it = new RDateIterator('20140901T000000,20141001T000000', new DateTimeImmutable('2014-08-01 00:00:00', $tz)); + + $expected = [ + new DateTimeImmutable('2014-08-01 00:00:00', $tz), + new DateTimeImmutable('2014-09-01 00:00:00', $tz), + new DateTimeImmutable('2014-10-01 00:00:00', $tz), + ]; + + $result = iterator_to_array($it); + + $this->assertEquals( + $expected, + iterator_to_array($it) + ); + + + $this->assertFalse($it->isInfinite()); + + } + + function testFastForward() { $utc = new DateTimeZone('UTC');