---
title: "Owners and calendars — Opening Hours for Laravel | Roundly"
description: "Make any Eloquent model an owner with the HasOpeningHours trait, give it several named calendars, and hook in its timezone."
url: https://roundly-consulting.com/open-source/docs/opening-hours-for-laravel/owners
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Opening Hours for Laravel](https://roundly-consulting.com/open-source/docs/opening-hours-for-laravel.md)

# Owners and calendars

Implement the OpeningHoursOwner contract with the HasOpeningHours trait on any model — a clinic, a shop, a branch, a person, a meeting room. Override openingHoursTimezone() to evaluate calendars without their own timezone in the owner’s:

```php
use Illuminate\Database\Eloquent\Model;
use RoundlyConsulting\OpeningHours\Concerns\HasOpeningHours;
use RoundlyConsulting\OpeningHours\Contracts\OpeningHoursOwner;

final class Clinic extends Model implements OpeningHoursOwner
{
    use HasOpeningHours;

    // Optional: evaluate calendars without their own timezone in the clinic's.
    public function openingHoursTimezone(): ?string
    {
        return $this->timezone;
    }
}
```

openingHours() is a method that returns the query object, not a relation — reading $clinic->openingHours as a property throws. The relation is openingHoursCalendars(); use it for eager loading and whenLoaded() in resources.

## Named calendars

An owner can have several independent calendars — default, pickup, reception… Every method takes an optional calendar key; without one it uses opening-hours.default\_calendar:

```php
use RoundlyConsulting\OpeningHours\Facades\OpeningHours;

OpeningHours::sync($clinic, ['week' => ['monday' => ['14:00-16:00']]], 'pickup'); // a second calendar

OpeningHours::for($clinic, 'pickup')->isOpenDuring($from, $to);
OpeningHours::has($clinic, 'pickup');        // bool
OpeningHours::calendar($clinic, 'pickup');   // ?Calendar — the live header

// The owner trait is shorthand for the same calls:
$clinic->openingHours('pickup');
$clinic->hasOpeningHours('pickup');
$clinic->openingHoursCalendar('pickup');
$clinic->openingHoursCalendars;              // every calendar of the owner (MorphMany)
```

## Owner API

| Method | Purpose |
| --- | --- |
| `openingHoursCalendars()` | MorphMany relation over every calendar header of the owner — eager load it or use it in whenLoaded(). |
| `openingHoursCalendar(?$calendar)` | The live ?Calendar header for a key; a loaded relation is used without a query. |
| `openingHours(?$calendar)` | The OpeningHours query object; an empty (closed) calendar when the key doesn’t exist yet. |
| `hasOpeningHours(?$calendar)` | Whether the calendar exists. |
| `setOpeningHours($data, ?$calendar, ?$expectedRevision)` | Full replace from an array or CalendarData; returns the new OpeningHours. |
| `editOpeningHours(?$calendar)` | A CalendarBuilder seeded with the current definition; optimistic save(). |
| `openingHoursTimezone()` | Hook — the owner’s timezone; default null. |
| `openingHoursDynamicExceptions()` | Hook — a list of DynamicExceptionProvider; default \[\]. |
| `withOpeningHours(definitions: false)` | Scope — eager-load calendar headers (plus full definitions with definitions: true). |
| `whereOpenAt() / whereOpenThroughout()` | Scopes over materialized intervals (opt-in). |

## Deleting owners

With delete\_with\_owner on (the default), permanently deleting an owner — a model without SoftDeletes, or forceDelete() — removes its calendars too. Soft-deleting the owner keeps them. Mass deletes such as Clinic::query()->delete() fire no model events and leave the calendars behind, because polymorphic owners cannot carry a foreign key.

Using the trait on a model that doesn’t implement OpeningHoursOwner throws InvalidOwnerException.

## 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](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## 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.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/opening-hours-for-laravel.md)
