Open source
Opening Hours for Laravel
composer require roundly-consulting/opening-hours-for-laravelOverview
A native opening-hours engine for Laravel. Give any Eloquent model one or more named calendars — weekly hours with lunch breaks and overnight ranges, seasonal schedules, one-off and yearly exceptions, Easter-relative holidays — and ask it anything through one OpeningHours facade: open now, next opening, hours on a given date, a grouped week, schema.org data. It answers correctly across clock changes under one explicit DST rule, and turns the same hours into availability checks and bookable slots fed by any busy-period source. MIT-licensed and built only on Laravel and Roundly’s own foundation packages — zero third-party dependencies.
What you get
Real-world weekly hours
Several ranges per day, overnight ranges (22:00-02:00), 24-hour and closed days — with several named calendars per owner.
Seasons, exceptions & holidays
Seasonal schedules with priorities, race-safe single exceptions for dates, spans and yearly holidays, and Easter-relative or custom movable holidays.
Facade, DI or actions
One OpeningHours facade, an injectable manager or single-purpose actions — and OpeningHours::fake() records every write without touching the database.
Complete query API
isOpenAt, nextOpen, nextClose, forDate, forWeek, open durations and schema.org structured data.
One explicit DST rule
Every wall-clock boundary resolves the same way in every zone — checked against a brute-force oracle across real transitions.
Availability & slots
Capacity, buffers, minimum notice and a horizon, fed by any busy-period source — no booking package required.
Production plumbing
Validation rule, fluent builder with optimistic concurrency, revision-keyed cache, events, API resources and opt-in SQL scopes.
Documentation
Installation
Install via Composer, publish and run the six migrations, and optionally publish the config and the en/sk translations.
Configuration
Every config key, its default and env variable — timezones, search windows, limits, cache, API output, pruning and materialization.
Owners and calendars
Make any Eloquent model an owner with the HasOpeningHours trait, give it several named calendars, and hook in its timezone.
The OpeningHours facade
Every write and read through one facade — sync, edit, exceptions(), delete(force:), refresh, validate — and the query object it returns.
DI and actions
Inject OpeningHoursManager instead of the facade, or call an action directly — the facade-to-action map and what the fake does and doesn’t see.
Defining opening hours
Save hours from an array — weekly ranges, seasonal schedules and exceptions — in the same canonical shape the validation rule accepts.
Fluent builder and concurrency
Edit hours with CalendarBuilder and ScheduleBuilder, guard concurrent edits with revisions, and build calendars in memory.
Querying opening hours
Ask whether an owner is open at any instant, find the next or previous opening and closing, and report answers in any timezone.
Days, weeks and structured data
Read hours for any date or weekday, render grouped weeks with the Weekday enum and translations, and publish schema.org opening hours.
Spans and durations
List opening periods between two instants, test whole spans, and measure open or closed time in real elapsed seconds.
Exceptions and holidays
Close or change hours on single dates, spans and yearly holidays, add Easter-relative holidays, and plug in any custom rule.
Validation and HTTP input
Validate opening-hours payloads with ValidOpeningHours, get every problem under its nested key, and validate programmatically.
Availability and slots
Check whether a time can be booked and list free slots — with capacity, buffers, minimum notice and a horizon, from any busy source.
Busy-period providers
Feed availability from Eloquent queries, arrays, closures or your own provider — with no coupling to any booking package.
API resources
Ready-made JSON resources for live status, editable definitions, day hours and slots — you own the routes and controllers.
Caching and eager loading
Revision-keyed definition caching with nothing to invalidate, a per-request memo, and list pages without N+1 queries.
Events
React when an owner’s hours change or are removed — both events dispatch after commit with queue-safe scalar payloads.
SQL scopes
Filter owners that are open now or open throughout a span directly in SQL, using opt-in materialized intervals.
Artisan commands
Inspect any owner’s hours from the terminal, prune past exceptions and soft-deleted rows, and refresh materialized intervals.
Importing week arrays
Import hours stored in the widely used weekday-keyed array format, with labels and exceptions — a data import, not a rewrite.
Timezones and DST
How a calendar’s timezone is resolved, how answers are reported in any zone, and the one explicit rule for clock changes.
Extending
Swap in your own model subclasses, hook the owner’s timezone and dynamic exceptions, and plug in custom providers.
Error handling
Every package exception extends one base class — catch them together, or handle stale writes and oversized queries on their own.
Security notes
What the package guards for you — foreign ids, hidden meta, bound columns, size limits — and what stays your responsibility.
Testing
Record every write with OpeningHours::fake() and assert syncs, exceptions, deletes and refreshes — or freeze time and test against real rows.
Requirements
PHP 8.4+, Laravel 12 or 13, and SQLite, PostgreSQL or MySQL — no PHP extensions and no third-party libraries.
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 cryptoBy 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.