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

Days, weeks and structured data

Day and week views are wall-clock views: they show ranges as defined, with exceptions applied wherever a real date is involved:

use RoundlyConsulting\OpeningHours\Enums\Weekday;

$hours->forDate('2026-12-24')->toString();     // "Closed" or "09:00–12:00" (translated)
$hours->forDate('2026-12-24')->ranges;         // list<TimeRange>
$hours->forDate('2026-12-24')->source;         // DaySource::Schedule|Exception|Dynamic|None
$hours->isOpenOnDate('2026-12-24');
$hours->isOpenOn('monday');                    // regular schedule, exceptions ignored
$hours->forWeekday(Weekday::Monday);
$hours->forWeek()->grouped();                  // list<WeekdayGroup>: Mon–Fri · Sat · Sun, each with its hours
$hours->forWeek()->keyed();                    // ['monday' => DayHours, …]
$hours->forWeekOf('2026-09-30');               // the seven actual dates, exceptions applied
$hours->forPeriod('2026-12-20', '2026-12-31');
$hours->regularClosingDays();                  // list<Weekday>
$hours->toStructuredData()->toJson();          // schema.org OpeningHoursSpecification

isOpenOnDate() checks whether any range starts on that date; isOpenOn() and forWeekday() read the regular schedule and ignore exceptions. A DateTimeInterface passed where a date is expected is converted to the calendar timezone first.

Rendering days and groups

DayHours::toString(rangeSeparator, timeSeparator, locale, closedText) renders a day. Without closedText a closed day reads the translated “Closed” and a single all-day range “Open 24 hours”; any explicit closedText — even '' — gives literal output. forWeek()->grouped() folds identical consecutive days into groups (grouped(false) also merges non-consecutive ones), each with a translated label() such as Mon–Fri:

$day = $hours->forDate('2026-12-24');

$day->toString();                            // "09:00–12:00, 13:00–18:00", or the translated "Closed"
$day->toString(',', '-', closedText: '');    // "09:00-12:00,13:00-18:00" — literal, '' when closed
$day->toString(locale: 'sk');
$day->isOpenAllDay();
$day->totalMinutes();
$day->label;                                 // an exception's label, e.g. "Christmas"

foreach ($hours->forWeek()->grouped() as $group) {
    echo $group->label().' '.$group->hoursText();   // "Mon–Fri 08:00–17:00"
}

Structured data

toStructuredData() emits schema.org OpeningHoursSpecification items for JSON-LD. 24:00 becomes 23:59, overnight ranges keep opens 22:00 / closes 02:00, a seasonal schedule adds validFrom and validThrough, and one-off exceptions within api.upcoming_exceptions_days add dated items (closed days open and close at 00:00):

$hours->toStructuredData()->toJson();
// [{"@type":"OpeningHoursSpecification","dayOfWeek":"https://schema.org/Monday","opens":"09:00","closes":"17:00"}, …]

$jsonLd = [
    '@context' => 'https://schema.org',
    '@type' => 'MedicalClinic',
    'name' => $clinic->name,
    'openingHoursSpecification' => $hours->toStructuredData()->toArray(),
];

Weekday enum

Weekday is a string-backed enum with parsing, ordering and translation helpers, plus the enums-for-laravel helpers such as toOptions() for select inputs:

use RoundlyConsulting\OpeningHours\Enums\Weekday;

Weekday::fromKey('Mon');                 // Weekday::Monday
Weekday::ordered(Weekday::Sunday);       // [Sunday, Monday, …, Saturday]
Weekday::Friday->translated('sk');       // localized long name
Weekday::Friday->translated(short: true);
Weekday::Saturday->isWeekend();          // true
Weekday::toOptions();                    // value => label map for select inputs

Translations

Weekday names (long and short), “Closed”, “Open 24 hours”, the unavailable reasons and every validation message ship in English and Slovak under the opening-hours namespace. Publish them with the opening-hours-translations tag to reword or add a locale.

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.