Skip to content

Commit

Permalink
Incorrect Timezone Handling in CalDAV Synchronization Causes Time Shi…
Browse files Browse the repository at this point in the history
…fts (#1626)
  • Loading branch information
alextselegidis committed Dec 19, 2024
1 parent b2ed516 commit 8e14176
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 28 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ developers to maintain and readjust their custom modifications on the main proje
- Fix various 1.5.0 API issues (#1562)
- Correct email issues by replacing the internal email library with phpmailer (#1587)
- Fix ICS file mimetype (#1630)
- Incorrect Timezone Handling in CalDAV Synchronization Causes Time Shifts (#1626)



Expand Down
92 changes: 64 additions & 28 deletions application/libraries/Caldav_sync.php
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,37 @@ private function get_unavailability_ics_file(array $unavailability, array $provi
return str_replace('METHOD:PUBLISH', '', $ics_file);
}

/**
* Try to parse the CalDAV event date-time value with the right timezone.
*
* @throws DateMalformedStringException
* @throws DateInvalidTimeZoneException
*/
private function parse_date_time_object(string $caldav_date_time, DateTimeZone $default_timezone_object): DateTime
{
try {
if (str_contains($caldav_date_time, 'TZID=')) {
// Extract the TZID and use it
preg_match('/TZID=([^:]+):/', $caldav_date_time, $matches);
$parsed_timezone = $matches[1];
$parsed_timezone_object = new DateTimeZone($parsed_timezone);
$date_time = preg_replace('/TZID=[^:]+:/', '', $caldav_date_time);
$date_time_object = new DateTime($date_time, $parsed_timezone_object);
} elseif (str_ends_with($caldav_date_time, 'Z')) {
// Handle UTC timestamps
$date_time_object = new DateTime($caldav_date_time, new DateTimeZone('UTC'));
} else {
// Default to the provided timezone
$date_time_object = new DateTime($caldav_date_time, $default_timezone_object);
}

return $date_time_object;
} catch (Throwable $e) {
error_log('Error parsing date-time value (' . $caldav_date_time . ') with timezone: ' . $e->getMessage());
throw $e;
}
}

/**
* Convert the VEvent object to an associative array
*
Expand All @@ -467,45 +498,50 @@ private function get_unavailability_ics_file(array $unavailability, array $provi
*
* @return array
*
* @throws DateMalformedStringException
* @throws Throwable
*/
private function convert_caldav_event_to_array_event(VEvent $vevent, DateTimeZone $timezone_object): array
{
$utc_timezone_object = new DateTimeZone('UTC'); // Convert from UTC to local provider timezone
try {
$caldav_start_date_time = (string) $vevent->DTSTART;
$start_date_time_object = $this->parse_date_time_object($caldav_start_date_time, $timezone_object);
$start_date_time_object->setTimezone($timezone_object); // Convert to the provider timezone

$start_date_time_object = new DateTime((string) $vevent->DTSTART, $utc_timezone_object);
$start_date_time_object->setTimezone($timezone_object);
$caldav_end_date_time = (string) $vevent->DTEND;
$end_date_time_object = $this->parse_date_time_object($caldav_end_date_time, $timezone_object);
$end_date_time_object->setTimezone($timezone_object); // Convert to the provider timezone

$end_date_time_object = new DateTime((string) $vevent->DTEND, $utc_timezone_object);
$end_date_time_object->setTimezone($timezone_object);
// Check if the event is recurring

// Check if the event is recurring
$is_recurring_event =
isset($vevent->RRULE) ||
isset($vevent->RDATE) ||
isset($vevent->{'RECURRENCE-ID'}) ||
isset($vevent->EXDATE);

$is_recurring_event =
isset($vevent->RRULE) ||
isset($vevent->RDATE) ||
isset($vevent->{'RECURRENCE-ID'}) ||
isset($vevent->EXDATE);
// Generate ID based on recurrence status

// Generate ID based on recurrence status
$event_id = (string) $vevent->UID;

$event_id = (string) $vevent->UID;
if ($is_recurring_event) {
$event_id .= '-RECURRENCE-' . random_string();
}

if ($is_recurring_event) {
$event_id .= '-RECURRENCE-' . random_string();
// Return the converted event

return [
'id' => $event_id,
'summary' => (string) $vevent->SUMMARY ?? null ?: '',
'start_datetime' => $start_date_time_object->format('Y-m-d H:i:s'),
'end_datetime' => $end_date_time_object->format('Y-m-d H:i:s'),
'description' => (string) $vevent->DESCRIPTION ?? null ?: '',
'status' => (string) $vevent->STATUS ?? null ?: 'CONFIRMED',
'location' => (string) $vevent->LOCATION ?? null ?: '',
];
} catch (Throwable $e) {
error_log('Error parsing CalDAV event object (' . var_export($vevent, true) . '): ' . $e->getMessage());
throw $e;
}

// Return the converted event

return [
'id' => $event_id,
'summary' => (string) $vevent->SUMMARY,
'start_datetime' => $start_date_time_object->format('Y-m-d H:i:s'),
'end_datetime' => $end_date_time_object->format('Y-m-d H:i:s'),
'description' => (string) $vevent->DESCRIPTION,
'status' => (string) $vevent->STATUS,
'location' => (string) $vevent->LOCATION,
];
}

/**
Expand Down

0 comments on commit 8e14176

Please sign in to comment.