Skip to content

Commit

Permalink
Merge pull request #652 from gharlan/recurrence-id-timezone
Browse files Browse the repository at this point in the history
ITip\Broker: handle timezones in replies to exception events
  • Loading branch information
phil-davis committed May 9, 2024
2 parents 8a2dab4 + bee4fa7 commit d4bf35b
Show file tree
Hide file tree
Showing 2 changed files with 197 additions and 3 deletions.
13 changes: 10 additions & 3 deletions lib/ITip/Broker.php
Expand Up @@ -333,7 +333,10 @@ protected function processMessageReply(Message $itipMessage, ?VCalendar $existin

// Finding all the instances the attendee replied to.
foreach ($itipMessage->message->VEVENT as $vevent) {
$recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master';
// Use the Unix timestamp returned by getTimestamp as a unique identifier for the recurrence.
// The Unix timestamp will be the same for an event, even if the reply from the attendee
// used a different format/timezone to express the event date-time.
$recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimestamp() : 'master';
$attendee = $vevent->ATTENDEE;
$instances[$recurId] = $attendee['PARTSTAT']->getValue();
if (isset($vevent->{'REQUEST-STATUS'})) {
Expand All @@ -346,7 +349,8 @@ protected function processMessageReply(Message $itipMessage, ?VCalendar $existin
// all the instances where we have a reply for.
$masterObject = null;
foreach ($existingObject->VEVENT as $vevent) {
$recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master';
// Use the Unix timestamp returned by getTimestamp as a unique identifier for the recurrence.
$recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimestamp() : 'master';
if ('master' === $recurId) {
$masterObject = $vevent;
}
Expand Down Expand Up @@ -393,7 +397,10 @@ protected function processMessageReply(Message $itipMessage, ?VCalendar $existin
$newObject = $recurrenceIterator->getEventObject();
$recurrenceIterator->next();

if (isset($newObject->{'RECURRENCE-ID'}) && $newObject->{'RECURRENCE-ID'}->getValue() === $recurId) {
// Compare the Unix timestamp returned by getTimestamp with the previously calculated timestamp.
// If they are the same, then this is a matching recurrence, even though its date-time may have
// been expressed in a different format/timezone.
if (isset($newObject->{'RECURRENCE-ID'}) && $newObject->{'RECURRENCE-ID'}->getDateTime()->getTimestamp() === $recurId) {
$found = true;
}
--$iterations;
Expand Down
187 changes: 187 additions & 0 deletions tests/VObject/ITip/BrokerProcessReplyTest.php
Expand Up @@ -253,6 +253,73 @@ public function testReplyPartyCrasher(): void
$this->process($itip, $old, $expected);
}

public function testReplyExistingExceptionRecurrenceIdInUTC(): void
{
$itip = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
METHOD:REPLY
BEGIN:VEVENT
ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
SEQUENCE:2
RECURRENCE-ID:20140725T040000Z
UID:foobar
END:VEVENT
END:VCALENDAR
ICS;

$old = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
RRULE:FREQ=DAILY
DTSTART;TZID=America/Toronto:20140724T000000
DTEND;TZID=America/Toronto:20140724T010000
ATTENDEE:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
END:VEVENT
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
DTSTART;TZID=America/Toronto:20140725T000000
DTEND;TZID=America/Toronto:20140725T010000
ATTENDEE:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
RECURRENCE-ID;TZID=America/Toronto:20140725T000000
END:VEVENT
END:VCALENDAR
ICS;

$expected = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
RRULE:FREQ=DAILY
DTSTART;TZID=America/Toronto:20140724T000000
DTEND;TZID=America/Toronto:20140724T010000
ATTENDEE:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
END:VEVENT
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
DTSTART;TZID=America/Toronto:20140725T000000
DTEND;TZID=America/Toronto:20140725T010000
ATTENDEE;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.0:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
RECURRENCE-ID;TZID=America/Toronto:20140725T000000
END:VEVENT
END:VCALENDAR
ICS;

$this->process($itip, $old, $expected);
}

public function testReplyNewException(): void
{
// This is a reply to 1 instance of a recurring event. This should
Expand Down Expand Up @@ -373,6 +440,126 @@ public function testReplyNewExceptionTz(): void
$this->process($itip, $old, $expected);
}

public function testReplyNewExceptionRecurrenceIdInDifferentTz(): void
{
// This is a reply to 1 instance of a recurring event. This should
// automatically create an exception.
$itip = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
METHOD:REPLY
BEGIN:VEVENT
ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
SEQUENCE:2
RECURRENCE-ID;TZID=Asia/Ho_Chi_Minh:20140725T110000
UID:foobar
END:VEVENT
END:VCALENDAR
ICS;

$old = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
RRULE:FREQ=DAILY
DTSTART;TZID=America/Toronto:20140724T000000
DTEND;TZID=America/Toronto:20140724T010000
ATTENDEE:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
END:VEVENT
END:VCALENDAR
ICS;

$expected = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
RRULE:FREQ=DAILY
DTSTART;TZID=America/Toronto:20140724T000000
DTEND;TZID=America/Toronto:20140724T010000
ATTENDEE:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
END:VEVENT
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
DTSTART;TZID=America/Toronto:20140725T000000
DTEND;TZID=America/Toronto:20140725T010000
ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
RECURRENCE-ID;TZID=America/Toronto:20140725T000000
END:VEVENT
END:VCALENDAR
ICS;

$this->process($itip, $old, $expected);
}

public function testReplyNewExceptionRecurrenceIdInUTC(): void
{
// This is a reply to 1 instance of a recurring event. This should
// automatically create an exception.
$itip = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
METHOD:REPLY
BEGIN:VEVENT
ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
SEQUENCE:2
RECURRENCE-ID:20140725T040000Z
UID:foobar
END:VEVENT
END:VCALENDAR
ICS;

$old = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
RRULE:FREQ=DAILY
DTSTART;TZID=America/Toronto:20140724T000000
DTEND;TZID=America/Toronto:20140724T010000
ATTENDEE:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
END:VEVENT
END:VCALENDAR
ICS;

$expected = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
RRULE:FREQ=DAILY
DTSTART;TZID=America/Toronto:20140724T000000
DTEND;TZID=America/Toronto:20140724T010000
ATTENDEE:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
END:VEVENT
BEGIN:VEVENT
SEQUENCE:2
UID:foobar
DTSTART;TZID=America/Toronto:20140725T000000
DTEND;TZID=America/Toronto:20140725T010000
ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org
ORGANIZER:mailto:bar@example.org
RECURRENCE-ID;TZID=America/Toronto:20140725T000000
END:VEVENT
END:VCALENDAR
ICS;

$this->process($itip, $old, $expected);
}

public function testReplyPartyCrashCreateException(): void
{
// IN this test there's a recurring event that has an exception. The
Expand Down

0 comments on commit d4bf35b

Please sign in to comment.