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メルマガ購読の申し込みはこちらから。