Skip to content

Commit d4bf35b

Browse files
authored
Merge pull request #652 from gharlan/recurrence-id-timezone
ITip\Broker: handle timezones in replies to exception events
2 parents 8a2dab4 + bee4fa7 commit d4bf35b

File tree

2 files changed

+197
-3
lines changed

2 files changed

+197
-3
lines changed

lib/ITip/Broker.php

+10-3
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,10 @@ protected function processMessageReply(Message $itipMessage, ?VCalendar $existin
333333

334334
// Finding all the instances the attendee replied to.
335335
foreach ($itipMessage->message->VEVENT as $vevent) {
336-
$recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master';
336+
// Use the Unix timestamp returned by getTimestamp as a unique identifier for the recurrence.
337+
// The Unix timestamp will be the same for an event, even if the reply from the attendee
338+
// used a different format/timezone to express the event date-time.
339+
$recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimestamp() : 'master';
337340
$attendee = $vevent->ATTENDEE;
338341
$instances[$recurId] = $attendee['PARTSTAT']->getValue();
339342
if (isset($vevent->{'REQUEST-STATUS'})) {
@@ -346,7 +349,8 @@ protected function processMessageReply(Message $itipMessage, ?VCalendar $existin
346349
// all the instances where we have a reply for.
347350
$masterObject = null;
348351
foreach ($existingObject->VEVENT as $vevent) {
349-
$recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master';
352+
// Use the Unix timestamp returned by getTimestamp as a unique identifier for the recurrence.
353+
$recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimestamp() : 'master';
350354
if ('master' === $recurId) {
351355
$masterObject = $vevent;
352356
}
@@ -393,7 +397,10 @@ protected function processMessageReply(Message $itipMessage, ?VCalendar $existin
393397
$newObject = $recurrenceIterator->getEventObject();
394398
$recurrenceIterator->next();
395399

396-
if (isset($newObject->{'RECURRENCE-ID'}) && $newObject->{'RECURRENCE-ID'}->getValue() === $recurId) {
400+
// Compare the Unix timestamp returned by getTimestamp with the previously calculated timestamp.
401+
// If they are the same, then this is a matching recurrence, even though its date-time may have
402+
// been expressed in a different format/timezone.
403+
if (isset($newObject->{'RECURRENCE-ID'}) && $newObject->{'RECURRENCE-ID'}->getDateTime()->getTimestamp() === $recurId) {
397404
$found = true;
398405
}
399406
--$iterations;

tests/VObject/ITip/BrokerProcessReplyTest.php

+187
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,73 @@ public function testReplyPartyCrasher(): void
253253
$this->process($itip, $old, $expected);
254254
}
255255

256+
public function testReplyExistingExceptionRecurrenceIdInUTC(): void
257+
{
258+
$itip = <<<ICS
259+
BEGIN:VCALENDAR
260+
VERSION:2.0
261+
METHOD:REPLY
262+
BEGIN:VEVENT
263+
ATTENDEE;PARTSTAT=ACCEPTED:mailto:[email protected]
264+
ORGANIZER:mailto:[email protected]
265+
SEQUENCE:2
266+
RECURRENCE-ID:20140725T040000Z
267+
UID:foobar
268+
END:VEVENT
269+
END:VCALENDAR
270+
ICS;
271+
272+
$old = <<<ICS
273+
BEGIN:VCALENDAR
274+
VERSION:2.0
275+
BEGIN:VEVENT
276+
SEQUENCE:2
277+
UID:foobar
278+
RRULE:FREQ=DAILY
279+
DTSTART;TZID=America/Toronto:20140724T000000
280+
DTEND;TZID=America/Toronto:20140724T010000
281+
ATTENDEE:mailto:[email protected]
282+
ORGANIZER:mailto:[email protected]
283+
END:VEVENT
284+
BEGIN:VEVENT
285+
SEQUENCE:2
286+
UID:foobar
287+
DTSTART;TZID=America/Toronto:20140725T000000
288+
DTEND;TZID=America/Toronto:20140725T010000
289+
ATTENDEE:mailto:[email protected]
290+
ORGANIZER:mailto:[email protected]
291+
RECURRENCE-ID;TZID=America/Toronto:20140725T000000
292+
END:VEVENT
293+
END:VCALENDAR
294+
ICS;
295+
296+
$expected = <<<ICS
297+
BEGIN:VCALENDAR
298+
VERSION:2.0
299+
BEGIN:VEVENT
300+
SEQUENCE:2
301+
UID:foobar
302+
RRULE:FREQ=DAILY
303+
DTSTART;TZID=America/Toronto:20140724T000000
304+
DTEND;TZID=America/Toronto:20140724T010000
305+
ATTENDEE:mailto:[email protected]
306+
ORGANIZER:mailto:[email protected]
307+
END:VEVENT
308+
BEGIN:VEVENT
309+
SEQUENCE:2
310+
UID:foobar
311+
DTSTART;TZID=America/Toronto:20140725T000000
312+
DTEND;TZID=America/Toronto:20140725T010000
313+
ATTENDEE;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.0:mailto:[email protected]
314+
ORGANIZER:mailto:[email protected]
315+
RECURRENCE-ID;TZID=America/Toronto:20140725T000000
316+
END:VEVENT
317+
END:VCALENDAR
318+
ICS;
319+
320+
$this->process($itip, $old, $expected);
321+
}
322+
256323
public function testReplyNewException(): void
257324
{
258325
// This is a reply to 1 instance of a recurring event. This should
@@ -373,6 +440,126 @@ public function testReplyNewExceptionTz(): void
373440
$this->process($itip, $old, $expected);
374441
}
375442

443+
public function testReplyNewExceptionRecurrenceIdInDifferentTz(): void
444+
{
445+
// This is a reply to 1 instance of a recurring event. This should
446+
// automatically create an exception.
447+
$itip = <<<ICS
448+
BEGIN:VCALENDAR
449+
VERSION:2.0
450+
METHOD:REPLY
451+
BEGIN:VEVENT
452+
ATTENDEE;PARTSTAT=ACCEPTED:mailto:[email protected]
453+
ORGANIZER:mailto:[email protected]
454+
SEQUENCE:2
455+
RECURRENCE-ID;TZID=Asia/Ho_Chi_Minh:20140725T110000
456+
UID:foobar
457+
END:VEVENT
458+
END:VCALENDAR
459+
ICS;
460+
461+
$old = <<<ICS
462+
BEGIN:VCALENDAR
463+
VERSION:2.0
464+
BEGIN:VEVENT
465+
SEQUENCE:2
466+
UID:foobar
467+
RRULE:FREQ=DAILY
468+
DTSTART;TZID=America/Toronto:20140724T000000
469+
DTEND;TZID=America/Toronto:20140724T010000
470+
ATTENDEE:mailto:[email protected]
471+
ORGANIZER:mailto:[email protected]
472+
END:VEVENT
473+
END:VCALENDAR
474+
ICS;
475+
476+
$expected = <<<ICS
477+
BEGIN:VCALENDAR
478+
VERSION:2.0
479+
BEGIN:VEVENT
480+
SEQUENCE:2
481+
UID:foobar
482+
RRULE:FREQ=DAILY
483+
DTSTART;TZID=America/Toronto:20140724T000000
484+
DTEND;TZID=America/Toronto:20140724T010000
485+
ATTENDEE:mailto:[email protected]
486+
ORGANIZER:mailto:[email protected]
487+
END:VEVENT
488+
BEGIN:VEVENT
489+
SEQUENCE:2
490+
UID:foobar
491+
DTSTART;TZID=America/Toronto:20140725T000000
492+
DTEND;TZID=America/Toronto:20140725T010000
493+
ATTENDEE;PARTSTAT=ACCEPTED:mailto:[email protected]
494+
ORGANIZER:mailto:[email protected]
495+
RECURRENCE-ID;TZID=America/Toronto:20140725T000000
496+
END:VEVENT
497+
END:VCALENDAR
498+
ICS;
499+
500+
$this->process($itip, $old, $expected);
501+
}
502+
503+
public function testReplyNewExceptionRecurrenceIdInUTC(): void
504+
{
505+
// This is a reply to 1 instance of a recurring event. This should
506+
// automatically create an exception.
507+
$itip = <<<ICS
508+
BEGIN:VCALENDAR
509+
VERSION:2.0
510+
METHOD:REPLY
511+
BEGIN:VEVENT
512+
ATTENDEE;PARTSTAT=ACCEPTED:mailto:[email protected]
513+
ORGANIZER:mailto:[email protected]
514+
SEQUENCE:2
515+
RECURRENCE-ID:20140725T040000Z
516+
UID:foobar
517+
END:VEVENT
518+
END:VCALENDAR
519+
ICS;
520+
521+
$old = <<<ICS
522+
BEGIN:VCALENDAR
523+
VERSION:2.0
524+
BEGIN:VEVENT
525+
SEQUENCE:2
526+
UID:foobar
527+
RRULE:FREQ=DAILY
528+
DTSTART;TZID=America/Toronto:20140724T000000
529+
DTEND;TZID=America/Toronto:20140724T010000
530+
ATTENDEE:mailto:[email protected]
531+
ORGANIZER:mailto:[email protected]
532+
END:VEVENT
533+
END:VCALENDAR
534+
ICS;
535+
536+
$expected = <<<ICS
537+
BEGIN:VCALENDAR
538+
VERSION:2.0
539+
BEGIN:VEVENT
540+
SEQUENCE:2
541+
UID:foobar
542+
RRULE:FREQ=DAILY
543+
DTSTART;TZID=America/Toronto:20140724T000000
544+
DTEND;TZID=America/Toronto:20140724T010000
545+
ATTENDEE:mailto:[email protected]
546+
ORGANIZER:mailto:[email protected]
547+
END:VEVENT
548+
BEGIN:VEVENT
549+
SEQUENCE:2
550+
UID:foobar
551+
DTSTART;TZID=America/Toronto:20140725T000000
552+
DTEND;TZID=America/Toronto:20140725T010000
553+
ATTENDEE;PARTSTAT=ACCEPTED:mailto:[email protected]
554+
ORGANIZER:mailto:[email protected]
555+
RECURRENCE-ID;TZID=America/Toronto:20140725T000000
556+
END:VEVENT
557+
END:VCALENDAR
558+
ICS;
559+
560+
$this->process($itip, $old, $expected);
561+
}
562+
376563
public function testReplyPartyCrashCreateException(): void
377564
{
378565
// IN this test there's a recurring event that has an exception. The

0 commit comments

Comments
 (0)