$occurrences * @return array{start:int,end:int,days:array,timed:list}>} */ public static function build(array $occurrences, DateTimeImmutable $week): array { $days = []; $first = 480; $last = 1200; for ($i = 0; $i < 7; ++$i) { $key = $week->modify('+' . $i . ' days')->format('Y-m-d'); $days[$key] = ['allDay' => [], 'timed' => []]; foreach ($occurrences as $item) { $startDay = $item->startLocal->format('Y-m-d'); $endDay = $item->endLocal->format('Y-m-d'); if ($startDay > $key || $endDay < $key || ($endDay === $key && $item->endLocal->format('H:i:s') === '00:00:00' && $startDay < $key)) { continue; } if ($item->allDay) { $days[$key]['allDay'][] = $item; continue; } $start = $startDay < $key ? 0 : self::minutes($item->startLocal); $end = $endDay > $key ? 1440 : self::minutes($item->endLocal); // Repeated DST hours and short slots still need a visible card. $end = min(1440, max($start + 20, $end)); $first = min($first, (int) floor($start / 60) * 60); $last = max($last, (int) ceil($end / 60) * 60); $days[$key]['timed'][] = ['occurrence' => $item, 'start' => $start, 'end' => $end, 'lane' => 0, 'lanes' => 1]; } $items = &$days[$key]['timed']; usort($items, static fn (array $a, array $b): int => [$a['start'], -$a['end'], $a['occurrence']->eventId] <=> [$b['start'], -$b['end'], $b['occurrence']->eventId]); $ends = []; $group = []; $groupEnd = -1; foreach ($items as $index => &$item) { if ($item['start'] >= $groupEnd) { foreach ($group as $member) { $items[$member]['lanes'] = count($ends); } $ends = []; $group = []; } $lane = 0; while (isset($ends[$lane]) && $ends[$lane] > $item['start']) { ++$lane; } $ends[$lane] = $item['end']; $item['lane'] = $lane; $group[] = $index; $groupEnd = max($ends); } unset($item); foreach ($group as $member) { $items[$member]['lanes'] = count($ends); } unset($items); } return ['start' => $first, 'end' => $last, 'days' => $days]; } private static function minutes(DateTimeImmutable $date): int { return (int) $date->format('H') * 60 + (int) $date->format('i'); } }