Livewireを使用してカレンダーをリアクティブにしたところで、次はある店舗のカレンダーと見立てて休日と営業時間の表示をします。2つパッケージを使います。1つは、Laravelのパッケージをたくさん提供することで有名なspatieのopening-hoursのパッケージ、もう1つはいろいろな国の祭日のデータを提供するパッケージyasumiです。
欲しいもの
まず、完成品のカレンダーはこんなものです。
この店舗のカレンダーでは休日がピンクの背景色となっています。店舗は日曜日が定休日で祭日も休みとなります。さらに、このカレンダーを作成した当日を10/25としてそれ以降の営業日においての営業時間を表示しています。
opening-hours:営業時間
営業時間のデータを扱うにはこのパッケージが最適です。
https://github.com/spatie/opening-hours
このパッケージでは以下のように、曜日ごとの営業時間の配列を渡してOpeningHoursのインスタンスを作成します。
use Spatie\OpeningHours\OpeningHours;
$openingHours = OpeningHours::create([
'monday' => ['09:00-12:00', '13:00-18:00'],
'tuesday' => ['09:00-12:00', '13:00-18:00'],
'wednesday' => ['09:00-12:00'],
'thursday' => ['09:00-12:00', '13:00-18:00'],
'friday' => ['09:00-12:00', '13:00-20:00'],
'saturday' => ['09:00-12:00', '13:00-16:00'],
'sunday' => [],
'exceptions' => [
'2024-11-11' => ['09:00-12:00'],
'2024-12-25' => [],
'01-01' => [], // 毎年の1月1日は休み
'12-25' => ['09:00-12:00'], // 毎年の12月25日は午前中だけ
],
]);
上では、
- 毎週、月、火、木、金、土が同じ営業時間で、水曜日が朝だけの定期的な営業時間です。日曜は、空の配列ということで休みです
- 非定期は、exceptionsの配列に入れます。2024-11-11のように特定な日にちで指定できますし、01-01のように毎年繰り返す日にちの営業時間も指定が可能です
さて、この営業時間を含むインスタンスをこしらえると、それに対していろいろなことを問うことが可能となります。
例えば、
月曜日は開いている?
$openingHours->isOpenOn('monday');
true
日曜日は開いている?
$openingHours->isOpenOn('sunday');
false
10/30日の19:00は開いている?
$openingHours->isOpenAt(new DateTime('2024-10-30 19:00:00'));
false
クリスマスの日は開いている?
$openingHours->isOpenOn('2024-12-25');
false
などなど、もっといろいろな関数があります。
そしてもちろん、特定な日を指定して、その日の営業時間のデータの取得も可能です。
$openHours = $openingHours->forDate(new DateTime('2024-10-25'));
$hours = [];
foreach($openHours as $hour) {
$hours[] = $hour->format();
}
print_r($hours);
Array
(
[0] => 09:00-12:00
[1] => 13:00-20:00
)
これが先のカレンダーで必要とされる関数となります。
注意することは1つ、このopening-hoursで使用される関数に渡す日付は皆、馴染みのCarbonではなくphpのDateTimeであることです。
もちろん、CarbonもDateTimeのWrapperのようなもので、
Carbon\Carbon::create("2025-10-25")->toDateTime();
DateTime @1761350400 {#3156
date: 2025-10-25 00:00:00.0 UTC (+00:00),
}
とDateTimeに簡単に変換が可能です。
yasumi:祭日
祭日は国のよって違いますので、以下のパッケージを使います。
https://github.com/azuyalabs/yasumi
yasumiはもちろん日本語の「休み」からの由来です。
早速、今年の日本の祭日を出してみましょう!
use Yasumi\Yasumi;
// 国名と年を渡すだけ
$yasumi = Yasumi::create("Japan", "2024");
$holidays = $yasumi->getHolidayDates();
print_r($holidays);
Array
(
[newYearsDay] => 2024-01-01
[comingOfAgeDay] => 2024-01-08
[nationalFoundationDay] => 2024-02-11
[substituteHoliday:nationalFoundationDay] => 2024-02-12
[emperorsBirthday] => 2024-02-23
[vernalEquinoxDay] => 2024-03-20
[showaDay] => 2024-04-29
[constitutionMemorialDay] => 2024-05-03
[greeneryDay] => 2024-05-04
[childrensDay] => 2024-05-05
[substituteHoliday:childrensDay] => 2024-05-06
[marineDay] => 2024-07-15
[mountainDay] => 2024-08-11
[substituteHoliday:mountainDay] => 2024-08-12
[respectfortheAgedDay] => 2024-09-16
[autumnalEquinoxDay] => 2024-09-22
[substituteHoliday:autumnalEquinoxDay] => 2024-09-23
[sportsDay] => 2024-10-14
[cultureDay] => 2024-11-03
[substituteHoliday:cultureDay] => 2024-11-04
[laborThanksgivingDay] => 2024-11-23
)
国名と年を渡すだけで祭日のデータ得られます。このデータを、先のopeningHoursのexceptionsに含んだら、店舗の休日となりますね。
店舗のカレンダー
ということで、これら2つのパッケージを使い以下のLivewireのコンポーネントとなります。
namespace App\Livewire;
use Carbon\Carbon;
use Yasumi\Yasumi;
use Livewire\Component;
use Spatie\OpeningHours\OpeningHours;
class Calendar extends Component
{
public int $year;
public int $month;
public $calendar = [];
public function mount()
{
$this->year ??= Carbon::now()->year;
$this->month ??= Carbon::now()->month;
}
public function generateCalendar(OpeningHours $openingHours): void
{
$startOfMonth = Carbon::createFromDate($this->year, $this->month, 1);
$endOfMonth = $startOfMonth->copy()->endOfMonth();
$startDayOfWeek = $startOfMonth->dayOfWeek;
$totalDays = $endOfMonth->day;
$this->calendar = [];
$week = [];
for ($i = 0; $i < $startDayOfWeek; $i++) {
$week[] = null;
}
$today = Carbon::today();
for ($day = 1; $day <= $totalDays; $day++) {
$date = Carbon::createFromDate($this->year, $this->month, $day);
$dayInfo = [
'day' => $day,
'isHoliday' => ! $openingHours->isOpenOn($date->toDateString()),
];
$dayInfo['hours'] = [];
if ($date->greaterThanOrEqualTo($today)) {
$hours = $openingHours->forDate($date->toDateTime());
foreach ($hours as $hour) {
$dayInfo['hours'][]= $hour->format();
}
}
$week[] = $dayInfo;
if (count($week) == 7) {
$this->calendar[] = $week;
$week = [];
}
}
if (count($week) > 0) {
while (count($week) < 7) {
$week[] = null;
}
$this->calendar[] = $week;
}
}
public function previousMonth(): void
{
$this->month--;
if ($this->month < 1) {
$this->month = 12;
$this->year--;
}
}
public function nextMonth(): void
{
$this->month++;
if ($this->month > 12) {
$this->month = 1;
$this->year++;
}
}
public function openingHours(): OpeningHours
{
$yasumi = Yasumi::create('Japan', $this->year);
// 以下で祭日を取得して、['2024-01-01' => [], '2024-01-08' => [] ...]の配列に変換
$holidays = collect($yasumi->getHolidayDates())
->mapWithKeys(function ($date) { return [$date => []];})
->all();
$dates = [
'monday' => ['09:00-12:00', '13:00-18:00'],
'tuesday' => ['09:00-12:00', '13:00-18:00'],
'wednesday' => ['09:00-12:00'],
'thursday' => ['09:00-12:00', '13:00-18:00'],
'friday' => ['09:00-12:00', '13:00-20:00'],
'saturday' => ['09:00-12:00', '13:00-16:00'],
'sunday' => [],
'exceptions' => $holidays,
];
return OpeningHours::create($dates);
}
public function render(): \Illuminate\View\View
{
$openingHours = $this->openingHours();
$this->generateCalendar($openingHours);
return view('livewire.calendar');
}
}
コンポーネントのブレードは、以下となります。
<div class="container mx-auto p-4">
<h1 class="text-2xl font-bold mb-4 text-center">{{ $year }}年{{ $month }}月</h1>
<div class="flex justify-between mb-4">
<button wire:click="previousMonth" class="bg-blue-500 text-white px-4 py-2 rounded">前</button>
<button wire:click="nextMonth" class="bg-blue-500 text-white px-4 py-2 rounded">次</button>
</div>
<table class="min-w-full bg-white border border-gray-200">
<thead>
<tr>
<th class="py-2 px-4 border border-gray-200 bg-gray-100">日</th>
<th class="py-2 px-4 border border-gray-200 bg-gray-100">月</th>
<th class="py-2 px-4 border border-gray-200 bg-gray-100">火</th>
<th class="py-2 px-4 border border-gray-200 bg-gray-100">水</th>
<th class="py-2 px-4 border border-gray-200 bg-gray-100">木</th>
<th class="py-2 px-4 border border-gray-200 bg-gray-100">金</th>
<th class="py-2 px-4 border border-gray-200 bg-gray-100">土</th>
</tr>
</thead>
<tbody>
@foreach ($calendar as $week)
<tr>
@foreach ($week as $day)
@if (is_null($day))
<td class="py-2 px-4 border border-gray-200"></td>
@else
<td class="py-2 px-4 border border-gray-200 align-top {{ $day['isHoliday'] ? 'bg-red-100' : '' }}">
{{ $day['day'] }}
<div class="text-sm text-blue-500">
{!! implode('<br>', $day['hours']) !!}
</div>
</td>
@endif
@endforeach
</tr>
@endforeach
</tbody>
</table>
</div>
もちろん、以下の作業を忘れずに。
$ composer require spatie/opening-hours $ composer require azuyalabs/yasumiメルマガ購読の申し込みはこちらから。

