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であることです。
もちろん、CarbonDateTimeの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
メルマガ購読の申し込みはこちらから。

By khino