173 lines
5.9 KiB
PHP
173 lines
5.9 KiB
PHP
<?php
|
|
|
|
namespace App\Service;
|
|
|
|
use App\HomeAssistant\HomeAssistantClient;
|
|
use DateTimeImmutable;
|
|
use DateTimeInterface;
|
|
use DateTimeZone;
|
|
use RuntimeException;
|
|
|
|
final class CalendarExportService
|
|
{
|
|
private const string DUSSELDORF_ADDRESS = 'CHECK24 \nHeinricht-Heine-Allee 53\n40213 Düsseldorf\nDeutschland';
|
|
private const string KAMEN_ADDRESS = 'Privat \nHeidkamp 21\n59174 Kamen\nDeutschland';
|
|
private const string COMPANY_NAME = 'CHECK24';
|
|
private const string TIMEZONE = 'Europe/Berlin';
|
|
private const string PERSON_ENTITY = 'person.tim';
|
|
private const string OFFICE_STATE = 'CHECK24';
|
|
|
|
public function __construct(
|
|
private readonly HomeAssistantClient $homeAssistantClient
|
|
) {
|
|
}
|
|
|
|
public function generateIcsContent(array $days, array $absences): string
|
|
{
|
|
$icsContent = [
|
|
'BEGIN:VCALENDAR',
|
|
'VERSION:2.0',
|
|
'PRODID:-//CHECK24//Work Calendar//EN',
|
|
'CALSCALE:GREGORIAN',
|
|
'METHOD:PUBLISH',
|
|
$this->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',
|
|
]);
|
|
}
|
|
} |