75 lines
2.8 KiB
PHP
75 lines
2.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Model;
|
|
|
|
/**
|
|
* The six-week skeleton behind a month calendar: always whole weeks, always starting on a Monday.
|
|
*
|
|
* Six weeks rather than "as many as the month needs" so the grid never changes height as you page
|
|
* through it -- a month can span six week rows (a 31-day month starting on a Sunday does), and a
|
|
* calendar that grows and shrinks under the cursor is worse than one with a trailing empty row.
|
|
*
|
|
* This is only the skeleton. Both calendars in this app draw the same grid and then disagree
|
|
* entirely about what goes in a cell: the board shows everybody's occupancy for one kind of
|
|
* resource plus the day you picked, "Meine Buchungen" shows one person's own bookings across all
|
|
* kinds and has no selected day at all. So the payload stays with the caller, which decorates
|
|
* these days by MonthDay::key(). gridStart and gridEnd are public precisely so that decoration
|
|
* can be one range query over the visible window instead of 42 per-day ones.
|
|
*/
|
|
final readonly class MonthGrid
|
|
{
|
|
private const WEEKS = 6;
|
|
|
|
/**
|
|
* @param list<list<MonthDay>> $weeks
|
|
*/
|
|
private function __construct(
|
|
public \DateTimeImmutable $monthStart,
|
|
public \DateTimeImmutable $gridStart,
|
|
public \DateTimeImmutable $gridEnd,
|
|
public array $weeks,
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* The grid containing $date, with "today" injectable so the flags can be tested without
|
|
* waiting for the calendar to roll over. BookingHorizon::latest() takes the same optional
|
|
* argument for the same reason.
|
|
*/
|
|
public static function around(\DateTimeImmutable $date, ?\DateTimeImmutable $today = null): self
|
|
{
|
|
$today = ($today ?? new \DateTimeImmutable('today'))->setTime(0, 0);
|
|
|
|
$monthStart = $date->setTime(0, 0)->modify('first day of this month');
|
|
// "monday this week" is a no-op when the month already starts on a Monday, so this is the
|
|
// whole leading-days calculation -- no weekday arithmetic needed.
|
|
$gridStart = $monthStart->modify('monday this week');
|
|
|
|
$weeks = [];
|
|
$cursor = $gridStart;
|
|
|
|
for ($week = 0; $week < self::WEEKS; ++$week) {
|
|
$days = [];
|
|
|
|
for ($weekday = 0; $weekday < 7; ++$weekday) {
|
|
$days[] = new MonthDay(
|
|
date: $cursor,
|
|
inMonth: $cursor->format('Y-m') === $monthStart->format('Y-m'),
|
|
isToday: $cursor == $today,
|
|
isPast: $cursor < $today,
|
|
isWeekend: (int) $cursor->format('N') >= 6,
|
|
);
|
|
|
|
$cursor = $cursor->modify('+1 day');
|
|
}
|
|
|
|
$weeks[] = $days;
|
|
}
|
|
|
|
// The cursor has already stepped past the grid, hence the day back.
|
|
return new self($monthStart, $gridStart, $cursor->modify('-1 day'), $weeks);
|
|
}
|
|
}
|