Skip to content

Commit

Permalink
fix(caldav): Fix reminder timezone drift for all-day events
Browse files Browse the repository at this point in the history
Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
  • Loading branch information
ChristophWurst authored and backportbot-nextcloud[bot] committed Feb 9, 2023
1 parent eba386f commit dd1763d
Show file tree
Hide file tree
Showing 3 changed files with 237 additions and 16 deletions.
2 changes: 1 addition & 1 deletion apps/dav/lib/CalDAV/CalDavBackend.php
Original file line number Diff line number Diff line change
Expand Up @@ -675,7 +675,7 @@ public function getCalendarByUri($principal, $uri) {
}

/**
* @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
* @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
*/
public function getCalendarById(int $calendarId): ?array {
$fields = array_column($this->propertyMap, 0);
Expand Down
53 changes: 50 additions & 3 deletions apps/dav/lib/CalDAV/Reminder/ReminderService.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
namespace OCA\DAV\CalDAV\Reminder;

use DateTimeImmutable;
use DateTimeZone;
use OCA\DAV\CalDAV\CalDavBackend;
use OCA\DAV\Connector\Sabre\Principal;
use OCP\AppFramework\Utility\ITimeFactory;
Expand Down Expand Up @@ -221,6 +222,7 @@ public function onCalendarObjectCreate(array $objectData):void {
if (!$vcalendar) {
return;
}
$calendarTimeZone = $this->getCalendarTimeZone((int) $objectData['calendarid']);

$vevents = $this->getAllVEventsFromVCalendar($vcalendar);
if (count($vevents) === 0) {
Expand Down Expand Up @@ -249,7 +251,7 @@ public function onCalendarObjectCreate(array $objectData):void {
continue;
}

$alarms = $this->getRemindersForVAlarm($valarm, $objectData,
$alarms = $this->getRemindersForVAlarm($valarm, $objectData, $calendarTimeZone,
$eventHash, $alarmHash, true, true);
$this->writeRemindersToDatabase($alarms);
}
Expand Down Expand Up @@ -306,6 +308,16 @@ public function onCalendarObjectCreate(array $objectData):void {

try {
$triggerTime = $valarm->getEffectiveTriggerTime();
/**
* @psalm-suppress DocblockTypeContradiction
* https://github.com/vimeo/psalm/issues/9244
*/
if ($triggerTime->getTimezone() === false || $triggerTime->getTimezone()->getName() === 'UTC') {
$triggerTime = new DateTimeImmutable(
$triggerTime->format('Y-m-d H:i:s'),
$calendarTimeZone
);
}
} catch (InvalidDataException $e) {
continue;
}
Expand All @@ -324,7 +336,7 @@ public function onCalendarObjectCreate(array $objectData):void {
continue;
}

$alarms = $this->getRemindersForVAlarm($valarm, $objectData, $masterHash, $alarmHash, $isRecurring, false);
$alarms = $this->getRemindersForVAlarm($valarm, $objectData, $calendarTimeZone, $masterHash, $alarmHash, $isRecurring, false);
$this->writeRemindersToDatabase($alarms);
$processedAlarms[] = $alarmHash;
}
Expand Down Expand Up @@ -363,6 +375,7 @@ public function onCalendarObjectDelete(array $objectData):void {
/**
* @param VAlarm $valarm
* @param array $objectData
* @param DateTimeZone $calendarTimeZone
* @param string|null $eventHash
* @param string|null $alarmHash
* @param bool $isRecurring
Expand All @@ -371,6 +384,7 @@ public function onCalendarObjectDelete(array $objectData):void {
*/
private function getRemindersForVAlarm(VAlarm $valarm,
array $objectData,
DateTimeZone $calendarTimeZone,
string $eventHash = null,
string $alarmHash = null,
bool $isRecurring = false,
Expand All @@ -386,6 +400,16 @@ private function getRemindersForVAlarm(VAlarm $valarm,
$isRelative = $this->isAlarmRelative($valarm);
/** @var DateTimeImmutable $notificationDate */
$notificationDate = $valarm->getEffectiveTriggerTime();
/**
* @psalm-suppress DocblockTypeContradiction
* https://github.com/vimeo/psalm/issues/9244
*/
if ($notificationDate->getTimezone() === false || $notificationDate->getTimezone()->getName() === 'UTC') {
$notificationDate = new DateTimeImmutable(
$notificationDate->format('Y-m-d H:i:s'),
$calendarTimeZone
);
}
$clonedNotificationDate = new \DateTime('now', $notificationDate->getTimezone());
$clonedNotificationDate->setTimestamp($notificationDate->getTimestamp());

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

try {
$iterator = new EventIterator($vevents, $reminder['uid']);
Expand Down Expand Up @@ -517,7 +542,7 @@ private function deleteOrProcessNext(array $reminder,
$alarms = $this->getRemindersForVAlarm($valarm, [
'calendarid' => $reminder['calendar_id'],
'id' => $reminder['object_id'],
], $reminder['event_hash'], $alarmHash, true, false);
], $calendarTimeZone, $reminder['event_hash'], $alarmHash, true, false);
$this->writeRemindersToDatabase($alarms);

// Abort generating reminders after creating one successfully
Expand Down Expand Up @@ -825,4 +850,26 @@ private function getEffectiveRecurrenceIdOfVEvent(VEvent $vevent):int {
private function isRecurring(VEvent $vevent):bool {
return isset($vevent->RRULE) || isset($vevent->RDATE);
}

/**
* @param int $calendarid
*
* @return DateTimeZone
*/
private function getCalendarTimeZone(int $calendarid): DateTimeZone {
$calendarInfo = $this->caldavBackend->getCalendarById($calendarid);
$tzProp = '{urn:ietf:params:xml:ns:caldav}calendar-timezone';
if (!isset($calendarInfo[$tzProp])) {
// Defaulting to UTC
return new DateTimeZone('UTC');
}
// This property contains a VCALENDAR with a single VTIMEZONE
/** @var string $timezoneProp */
$timezoneProp = $calendarInfo[$tzProp];
/** @var VObject\Component\VCalendar $vtimezoneObj */
$vtimezoneObj = VObject\Reader::read($timezoneProp);
/** @var VObject\Component\VTimeZone $vtimezone */
$vtimezone = $vtimezoneObj->VTIMEZONE;
return $vtimezone->getTimeZone();
}
}
Loading

0 comments on commit dd1763d

Please sign in to comment.