generateTimezone(), ]; foreach ($days as $day) { if ($this->isWeekend($day) || ($day['isUrlaub'] ?? false)) { continue; } $dayDate = clone $day['date']; $startAndEndTimes = $this->getWorkTimesFromHomeAssistant($dayDate); if ($startAndEndTimes === null) { $eventStart = clone $day['date']; $eventStart->setTime(9, 0, 0); $eventEnd = clone $day['date']; $eventEnd->setTime(17, 30, 0); } else { [$eventStart, $eventEnd] = $startAndEndTimes; } $isHomeOffice = $day['isFtk'] ?? false; $location = $isHomeOffice ? self::KAMEN_ADDRESS : self::DUSSELDORF_ADDRESS; $name = match ($isHomeOffice) { true => 'CHECK24 🏠', false => 'CHECK24 🏢', }; $icsContent[] = 'BEGIN:VEVENT'; $icsContent[] = 'UID:' . $this->generateUid($eventStart); $icsContent[] = 'DTSTAMP:' . $this->formatDateTime(new DateTimeImmutable('now', new DateTimeZone(self::TIMEZONE))); $icsContent[] = 'DTSTART;TZID=' . self::TIMEZONE . ':' . $this->formatDateTimeLocal($eventStart); $icsContent[] = 'DTEND;TZID=' . self::TIMEZONE . ':' . $this->formatDateTimeLocal($eventEnd); $icsContent[] = 'SUMMARY:' . $name; $icsContent[] = 'LOCATION:' . $location; $icsContent[] = 'END:VEVENT'; } $icsContent[] = 'END:VCALENDAR'; return implode("\r\n", $icsContent); } private function getWorkTimesFromHomeAssistant(DateTimeInterface $date): ?array { $startOfDay = DateTimeImmutable::createFromInterface($date)->setTime(0, 0, 0); $typicalEndOfDay = DateTimeImmutable::createFromInterface($date)->setTime(17, 30, 0); try { $stateHistory = $this->homeAssistantClient->getEntityStateHistory( self::PERSON_ENTITY, $startOfDay, $typicalEndOfDay ); if (empty($stateHistory) || empty($stateHistory[0])) { return null; } $startTime = null; $endTime = null; $states = $stateHistory[0]; foreach ($states as $i => $state) { if ($state['state'] === self::OFFICE_STATE) { $startTime = new DateTimeImmutable($state['last_changed']); for ($j = $i + 1; $j < count($states); $j++) { if ($states[$j]['state'] !== self::OFFICE_STATE) { $endTime = new DateTimeImmutable($states[$j]['last_changed']); break; } } if ($startTime !== null && $endTime === null) { $endTime = clone $startTime; $endTime = $endTime->modify('+9 hours'); } break; } } if ($startTime === null || $endTime === null) { return null; } $startTime = $startTime->setTimezone(new DateTimeZone('Europe/Berlin')); $endTime = $endTime->setTimezone(new DateTimeZone('Europe/Berlin')); return [$startTime, $endTime]; } catch (RuntimeException) { return null; } } private function isWeekend(array $day): bool { return $day['weekday'] >= 6; } private function formatDateTime(DateTimeImmutable $dateTime): string { return $dateTime->setTimezone(new DateTimeZone('UTC'))->format('Ymd\THis\Z'); } private function formatDateTimeLocal(DateTimeInterface $dateTime): string { return $dateTime->format('Ymd\THis'); } private function generateUid(DateTimeInterface $dateTime): string { return $dateTime->format('Ymd') . '-' . md5($dateTime->format('Ymd') . '-CHECK24') . '@check24.de'; } private function generateTimezone(): string { return implode("\r\n", [ 'BEGIN:VTIMEZONE', 'TZID:' . self::TIMEZONE, 'X-LIC-LOCATION:' . self::TIMEZONE, 'BEGIN:DAYLIGHT', 'TZOFFSETFROM:+0100', 'TZOFFSETTO:+0200', 'TZNAME:CEST', 'DTSTART:19700329T020000', 'RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU', 'END:DAYLIGHT', 'BEGIN:STANDARD', 'TZOFFSETFROM:+0200', 'TZOFFSETTO:+0100', 'TZNAME:CET', 'DTSTART:19701025T030000', 'RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU', 'END:STANDARD', 'END:VTIMEZONE', ]); } }