Skip to content

Commit 7aa7868

Browse files
Merge pull request #36192 from nextcloud/fix/caldav-reminders/fix-timezone-drift-all-day-reminders
fix(caldav): Fix reminder timezone drift for all-day events
2 parents 674f4e8 + b5f7afd commit 7aa7868

3 files changed

Lines changed: 237 additions & 16 deletions

File tree

apps/dav/lib/CalDAV/CalDavBackend.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -658,7 +658,7 @@ public function getCalendarByUri($principal, $uri) {
658658
}
659659

660660
/**
661-
* @return array{id: int, uri: string, '{http://calendarserver.org/ns/}getctag': string, '{http://sabredav.org/ns}sync-token': int, '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set': SupportedCalendarComponentSet, '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp': ScheduleCalendarTransp }|null
661+
* @return array{id: int, uri: string, '{http://calendarserver.org/ns/}getctag': string, '{http://sabredav.org/ns}sync-token': int, '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set': SupportedCalendarComponentSet, '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp': ScheduleCalendarTransp, '{urn:ietf:params:xml:ns:caldav}calendar-timezone': ?string }|null
662662
*/
663663
public function getCalendarById(int $calendarId): ?array {
664664
$fields = array_column($this->propertyMap, 0);

apps/dav/lib/CalDAV/Reminder/ReminderService.php

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
namespace OCA\DAV\CalDAV\Reminder;
3333

3434
use DateTimeImmutable;
35+
use DateTimeZone;
3536
use OCA\DAV\CalDAV\CalDavBackend;
3637
use OCA\DAV\Connector\Sabre\Principal;
3738
use OCP\AppFramework\Utility\ITimeFactory;
@@ -221,6 +222,7 @@ public function onCalendarObjectCreate(array $objectData):void {
221222
if (!$vcalendar) {
222223
return;
223224
}
225+
$calendarTimeZone = $this->getCalendarTimeZone((int) $objectData['calendarid']);
224226

225227
$vevents = $this->getAllVEventsFromVCalendar($vcalendar);
226228
if (count($vevents) === 0) {
@@ -249,7 +251,7 @@ public function onCalendarObjectCreate(array $objectData):void {
249251
continue;
250252
}
251253

252-
$alarms = $this->getRemindersForVAlarm($valarm, $objectData,
254+
$alarms = $this->getRemindersForVAlarm($valarm, $objectData, $calendarTimeZone,
253255
$eventHash, $alarmHash, true, true);
254256
$this->writeRemindersToDatabase($alarms);
255257
}
@@ -306,6 +308,16 @@ public function onCalendarObjectCreate(array $objectData):void {
306308

307309
try {
308310
$triggerTime = $valarm->getEffectiveTriggerTime();
311+
/**
312+
* @psalm-suppress DocblockTypeContradiction
313+
* https://github.com/vimeo/psalm/issues/9244
314+
*/
315+
if ($triggerTime->getTimezone() === false || $triggerTime->getTimezone()->getName() === 'UTC') {
316+
$triggerTime = new DateTimeImmutable(
317+
$triggerTime->format('Y-m-d H:i:s'),
318+
$calendarTimeZone
319+
);
320+
}
309321
} catch (InvalidDataException $e) {
310322
continue;
311323
}
@@ -324,7 +336,7 @@ public function onCalendarObjectCreate(array $objectData):void {
324336
continue;
325337
}
326338

327-
$alarms = $this->getRemindersForVAlarm($valarm, $objectData, $masterHash, $alarmHash, $isRecurring, false);
339+
$alarms = $this->getRemindersForVAlarm($valarm, $objectData, $calendarTimeZone, $masterHash, $alarmHash, $isRecurring, false);
328340
$this->writeRemindersToDatabase($alarms);
329341
$processedAlarms[] = $alarmHash;
330342
}
@@ -363,6 +375,7 @@ public function onCalendarObjectDelete(array $objectData):void {
363375
/**
364376
* @param VAlarm $valarm
365377
* @param array $objectData
378+
* @param DateTimeZone $calendarTimeZone
366379
* @param string|null $eventHash
367380
* @param string|null $alarmHash
368381
* @param bool $isRecurring
@@ -371,6 +384,7 @@ public function onCalendarObjectDelete(array $objectData):void {
371384
*/
372385
private function getRemindersForVAlarm(VAlarm $valarm,
373386
array $objectData,
387+
DateTimeZone $calendarTimeZone,
374388
string $eventHash = null,
375389
string $alarmHash = null,
376390
bool $isRecurring = false,
@@ -386,6 +400,16 @@ private function getRemindersForVAlarm(VAlarm $valarm,
386400
$isRelative = $this->isAlarmRelative($valarm);
387401
/** @var DateTimeImmutable $notificationDate */
388402
$notificationDate = $valarm->getEffectiveTriggerTime();
403+
/**
404+
* @psalm-suppress DocblockTypeContradiction
405+
* https://github.com/vimeo/psalm/issues/9244
406+
*/
407+
if ($notificationDate->getTimezone() === false || $notificationDate->getTimezone()->getName() === 'UTC') {
408+
$notificationDate = new DateTimeImmutable(
409+
$notificationDate->format('Y-m-d H:i:s'),
410+
$calendarTimeZone
411+
);
412+
}
389413
$clonedNotificationDate = new \DateTime('now', $notificationDate->getTimezone());
390414
$clonedNotificationDate->setTimestamp($notificationDate->getTimestamp());
391415

@@ -471,6 +495,7 @@ private function deleteOrProcessNext(array $reminder,
471495
$vevents = $this->getAllVEventsFromVCalendar($vevent->parent);
472496
$recurrenceExceptions = $this->getRecurrenceExceptionFromListOfVEvents($vevents);
473497
$now = $this->timeFactory->getDateTime();
498+
$calendarTimeZone = $this->getCalendarTimeZone((int) $reminder['calendar_id']);
474499

475500
try {
476501
$iterator = new EventIterator($vevents, $reminder['uid']);
@@ -517,7 +542,7 @@ private function deleteOrProcessNext(array $reminder,
517542
$alarms = $this->getRemindersForVAlarm($valarm, [
518543
'calendarid' => $reminder['calendar_id'],
519544
'id' => $reminder['object_id'],
520-
], $reminder['event_hash'], $alarmHash, true, false);
545+
], $calendarTimeZone, $reminder['event_hash'], $alarmHash, true, false);
521546
$this->writeRemindersToDatabase($alarms);
522547

523548
// Abort generating reminders after creating one successfully
@@ -825,4 +850,26 @@ private function getEffectiveRecurrenceIdOfVEvent(VEvent $vevent):int {
825850
private function isRecurring(VEvent $vevent):bool {
826851
return isset($vevent->RRULE) || isset($vevent->RDATE);
827852
}
853+
854+
/**
855+
* @param int $calendarid
856+
*
857+
* @return DateTimeZone
858+
*/
859+
private function getCalendarTimeZone(int $calendarid): DateTimeZone {
860+
$calendarInfo = $this->caldavBackend->getCalendarById($calendarid);
861+
$tzProp = '{urn:ietf:params:xml:ns:caldav}calendar-timezone';
862+
if (!isset($calendarInfo[$tzProp])) {
863+
// Defaulting to UTC
864+
return new DateTimeZone('UTC');
865+
}
866+
// This property contains a VCALENDAR with a single VTIMEZONE
867+
/** @var string $timezoneProp */
868+
$timezoneProp = $calendarInfo[$tzProp];
869+
/** @var VObject\Component\VCalendar $vtimezoneObj */
870+
$vtimezoneObj = VObject\Reader::read($timezoneProp);
871+
/** @var VObject\Component\VTimeZone $vtimezone */
872+
$vtimezone = $vtimezoneObj->VTIMEZONE;
873+
return $vtimezone->getTimeZone();
874+
}
828875
}

0 commit comments

Comments
 (0)