NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Opening Hours for Laravel

Querying opening hours

OpeningHours::for($owner) — or the owner’s openingHours() — returns an immutable query object for the default (or a named) calendar. Instant inputs accept any DateTimeInterface; every returned instant is a CarbonImmutable in the calendar’s timezone:

use Carbon\CarbonImmutable;
use RoundlyConsulting\OpeningHours\Facades\OpeningHours;

$hours = OpeningHours::for($clinic);                      // default calendar — or $clinic->openingHours()
$hours->isOpen();                                         // now
$hours->isOpenAt(CarbonImmutable::parse('2026-10-25 02:30', 'Europe/Bratislava'));
$hours->nextOpen();                                       // ?CarbonImmutable, calendar timezone
$hours->nextClose();
$hours->previousOpen();
$hours->previousClose();
$hours->currentPeriod();                                  // ?OpeningPeriod (coalesced run)
$hours->currentRange();                                   // ?TimeRange as defined (label, capacity)
$hours->nextPeriod();
$hours->withOutputTimezone('UTC')->nextClose();           // same answer, reported in UTC
OpeningHours::for($clinic, 'pickup')->isOpenDuring($from, $to);

Navigation rules

  • Navigation is strict: nextOpen($t) is after $t, previousOpen($t) is before it.
  • Navigation looks search_days local days after (or before) the day of $t — pass searchDays: to override per call; nothing found there returns null (nextOpen($monday, searchDays: 1) sees Monday and Tuesday). A period that opens inside the window still reports its real nextClose(), even past midnight. An always-open calendar has no nextClose().
  • Periods are half-open: open at their start, closed at their end.
  • Touching ranges (09–12 + 12–13) count as one period for navigation, as do Monday 22:00–24:00 and Tuesday 00:00–06:00.
  • currentPeriod() returns the coalesced run as an OpeningPeriod (start, end, label, source, capacity); currentRange() returns the range as defined, with its label and capacity intact.

More checks

$hours->isClosed();                 // now
$hours->isClosedAt($at);
$hours->nextOpen($after, searchDays: 30);   // override search_days per call
$hours->nextOpen($monday, searchDays: 1);   // sees Monday and Tuesday
$hours->isAlwaysOpen();
$hours->isAlwaysClosed();
$hours->timezone();                 // DateTimeZone the calendar is evaluated in
$hours->revision();                 // ?int — null for in-memory calendars
$hours->definition();               // CalendarData

$period = $hours->currentPeriod();  // ?OpeningPeriod
$period?->start;                    // CarbonImmutable
$period?->end;
$period?->label;

“Now” comes from Laravel’s Date facade, so Carbon::setTestNow() and travelTo() work in tests.

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.