feat: teamer season disposition count in info modal
addresses #869dv94n4
This commit is contained in:
1 parent
39e6f35374
commit
86c5d23181
8 files changed
+344
-2
No files matched your search
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Config;
|
||||
|
||||
use Carbon\CarbonImmutable;
|
||||
|
||||
/**
|
||||
* The recurring season window, as configured by the "season_start"/"season_end" parameters.
|
||||
*
|
||||
* A season is a MM-DD..MM-DD window that normally wraps the turn of the year (01.11.–01.04.)
|
||||
* and is named after the calendar year it starts in - "Saison 2024/25". It is used to bucket
|
||||
* a teamer's dispositions per season in the info modal; the date that decides the bucket is
|
||||
* the assignment's effective start, mirroring App\Repository\Filter\SeasonPeriodFilter.
|
||||
*
|
||||
* Both ends are inclusive: an assignment starting on 01.04. still belongs to the season that
|
||||
* began the previous 01.11. A start date between the two (02.04.–31.10. for the default
|
||||
* window) belongs to no season and is reported separately.
|
||||
*/
|
||||
final class SeasonCalendar
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $seasonStart,
|
||||
private readonly string $seasonEnd,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* The calendar year the season containing $date starts in, or null when $date lies
|
||||
* outside any season window.
|
||||
*/
|
||||
public function seasonYearFor(\DateTimeInterface $date): ?int
|
||||
{
|
||||
$day = CarbonImmutable::instance($date)->startOfDay();
|
||||
$year = (int) $day->format('Y');
|
||||
|
||||
$start = $this->boundaryFor($this->seasonStart, $year);
|
||||
$end = $this->boundaryFor($this->seasonEnd, $year);
|
||||
|
||||
// Wrapping window (the default 01.11.–01.04. case): the season straddles New Year.
|
||||
if ($end < $start) {
|
||||
if ($day >= $start) {
|
||||
return $year;
|
||||
}
|
||||
|
||||
if ($day <= $end) {
|
||||
return $year - 1;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Non-wrapping window: kept correct should the config ever describe a window that
|
||||
// stays within one calendar year.
|
||||
return $day >= $start && $day <= $end ? $year : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* null -> "außerhalb der Saison"; 2024 -> "Saison 2024/25".
|
||||
*/
|
||||
public function label(?int $seasonYear): string
|
||||
{
|
||||
if (null === $seasonYear) {
|
||||
return 'außerhalb der Saison';
|
||||
}
|
||||
|
||||
return sprintf('Saison %d/%02d', $seasonYear, ($seasonYear + 1) % 100);
|
||||
}
|
||||
|
||||
private function boundaryFor(string $monthDay, int $year): CarbonImmutable
|
||||
{
|
||||
$boundary = CarbonImmutable::createFromFormat('Y-m-d', sprintf('%d-%s', $year, $monthDay));
|
||||
|
||||
if (false === $boundary instanceof CarbonImmutable) {
|
||||
throw new \InvalidArgumentException(sprintf('Season boundary "%s" is not a valid MM-DD date.', $monthDay));
|
||||
}
|
||||
|
||||
return $boundary->startOfDay();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user