Created
February 18, 2023 15:19
-
-
Save nyamsprod/c114715d59403c365964aa76eab181c4 to your computer and use it in GitHub Desktop.
Create a Calendar in PHP using League Period
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types=1); | |
require 'autoload.php'; | |
use League\Period\DatePoint; | |
use League\Period\Period; | |
/** | |
* @phpstan-type CalendarDay array{year:int, month:string, day:int, path:string, withinMonth:bool, selected:bool} | |
* @phpstan-type CalendarWeek array(selectedDate:DateTimeImmutable|null, year:int, isoYear:int, isoWeek:int, days:iterable<CalendarDay>} | |
* @phpstan-type CalendarMonth array{selectedDate:DateTimeImmutable|null, year:int, month:string, weeks:iterable<CalendarWeek>} | |
* @phpstan-type CalendarYear array{selectedDate:DateTimeImmutable|null, year:int, months:iterable<CalendarMonth>} | |
*/ | |
final class Calendar | |
{ | |
/** | |
* @throws Exception | |
* | |
* @return CalendarYear | |
*/ | |
public static function buildYear(DateTimeInterface|int $year, DateTimeInterface|null $selectedDate = null): array | |
{ | |
$yearInterval = match (true) { | |
$year instanceof DateTimeInterface => DatePoint::fromDate($year)->year(), | |
default => Period::fromYear($year), | |
}; | |
if (null == $selectedDate && $year instanceof DateTimeInterface) { | |
$selectedDate = $year; | |
} | |
$selectedDate = self::filterSelectedDate($selectedDate, $yearInterval); | |
/** @return iterable<CalendarMonth> */ | |
$formatMonths = function (Period $period, DateTimeInterface|null $selectedDate): iterable { | |
foreach ($period->splitForward('1 MONTH') as $month) { | |
$firstDayOfMonth = $month->startDate; | |
yield self::buildMonth( | |
(int) $firstDayOfMonth->format('Y'), | |
(int) $firstDayOfMonth->format('n'), | |
$selectedDate | |
); | |
} | |
}; | |
return [ | |
'selectedDate' => $selectedDate, | |
'months' => $formatMonths($yearInterval, $selectedDate), | |
'year' => (int) $yearInterval->startDate->format('Y'), | |
]; | |
} | |
/** | |
* @throws Exception | |
* | |
* @return CalendarMonth | |
*/ | |
public static function buildMonth(int $year, int $month, DateTimeInterface|int|null $selectedDate = null): array | |
{ | |
$monthInterval = Period::fromMonth($year, $month); | |
if (is_int($selectedDate)) { | |
$selectedDate = (new DateTimeImmutable())->setDate($year, $month, $selectedDate); | |
} | |
$selectedDate = self::filterSelectedDate($selectedDate, $monthInterval); | |
/** @return CalendarDay */ | |
$formatDate = fn (DateTimeImmutable $date): array => [ | |
'year' => (int) $date->format('Y'), | |
'month' => $date->format('F'), | |
'day' => (int) $date->format('j'), | |
'path' => $date->format('/Y/m/d'), | |
'withinMonth' => $monthInterval->contains($date), | |
'selected' => null !== $selectedDate && $date == $selectedDate, | |
]; | |
/** @return CalendarWeek */ | |
$formatWeek = fn (Period $week): iterable => [ | |
'selectedDate' => $selectedDate, | |
'year' => (int) $week->startDate->format('Y'), | |
'isoYear' => (int) $week->startDate->format('o'), | |
'isoWeek' => (int) $week->startDate->format('W'), | |
'days' => array_map($formatDate, [...$week->rangeForward('1 day')]), | |
]; | |
/** @return iterable<CalendarWeek> */ | |
$formatWeeks = fn (Period $month): iterable => array_map($formatWeek, [...$month->splitForward('1 week')]); | |
$extendedMonth = $monthInterval | |
->startingOn(DatePoint::fromDate($monthInterval->startDate)->isoWeek()->startDate) | |
->endingOn(DatePoint::fromDate($monthInterval->endDate->sub(new DateInterval('P1D')))->isoWeek()->endDate); | |
return [ | |
'selectedDate' => $selectedDate, | |
'year' => (int) $monthInterval->startDate->format('Y'), | |
'month' => $monthInterval->startDate->format('F'), | |
'weeks' => $formatWeeks($extendedMonth), | |
]; | |
} | |
private static function filterSelectedDate(DateTimeInterface|null $date, Period $period): ?DateTimeImmutable | |
{ | |
if ($date instanceof DateTimeInterface && $period->contains($date)) { | |
return DateTimeImmutable::createFromInterface($date)->setTime(0, 0); | |
} | |
return null; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This class is another implementation of the
Calendar
class from Building a Calendar with CarbonIt differs from the article implementation by:
Carbon\Carbon
usage byLeague\Period
Laravel\Collection
by usingarray_map
or theforeach
construct onIterator
with the help of the spread operatorThe public API is "augmented" by using union types to ease usage and thus can be used with the same Blade component from the article.
the usage is still the same and you can still use
Carbon
if you enjoy the library: