PHPackages                             datomatic/db-opening-hours - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. datomatic/db-opening-hours

ActiveLibrary[Database &amp; ORM](/categories/database)

datomatic/db-opening-hours
==========================

Use spatie/opening-hours package and save it on database to query it

v1.1.0(3w ago)178[1 PRs](https://github.com/datomatic/db-opening-hours/pulls)1MITPHPPHP ^8.2CI passing

Since Aug 21Pushed 3w ago1 watchersCompare

[ Source](https://github.com/datomatic/db-opening-hours)[ Packagist](https://packagist.org/packages/datomatic/db-opening-hours)[ Docs](https://github.com/datomatic/db-opening-hours)[ GitHub Sponsors](https://github.com/Datomatic)[ RSS](/packages/datomatic-db-opening-hours/feed)WikiDiscussions main Synced 2w ago

READMEChangelog (7)Dependencies (26)Versions (11)Used By (1)

Store and query opening hours in the database
=============================================

[](#store-and-query-opening-hours-in-the-database)

[![Latest Version on Packagist](https://camo.githubusercontent.com/8d07f4b8c907502a2300ecb0968958fd1906ca895357b42374da104398e5e0d3/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6461746f6d617469632f64622d6f70656e696e672d686f7572732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/datomatic/db-opening-hours)[![GitHub Tests Action Status](https://camo.githubusercontent.com/d18219911b3d2e57b88d959d3483f5a41d319831aabe8d134a2227428762dd5b/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f6461746f6d617469632f64622d6f70656e696e672d686f7572732f72756e2d74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/datomatic/db-opening-hours/actions?query=workflow%3Arun-tests+branch%3Amain)[![GitHub Code Style Action Status](https://camo.githubusercontent.com/4d4cb4a6439fc98a07a4288a07b7b5153ecf7cadb1eab32bf3af569b3b0489d6/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f6461746f6d617469632f64622d6f70656e696e672d686f7572732f6669782d7068702d636f64652d7374796c652d6973737565732e796d6c3f6272616e63683d6d61696e266c6162656c3d636f64652532307374796c65267374796c653d666c61742d737175617265)](https://github.com/datomatic/db-opening-hours/actions?query=workflow%3A%22Fix+PHP+code+style+issues%22+branch%3Amain)[![Total Downloads](https://camo.githubusercontent.com/fd05e23f151e30de7d6dffb7e7600d81be7d853744f4ed4841f889af8fcf84fb/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6461746f6d617469632f64622d6f70656e696e672d686f7572732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/datomatic/db-opening-hours)

[`spatie/opening-hours`](https://github.com/spatie/opening-hours) is great for describing a weekly schedule in code, but it keeps everything in memory. This package **persists that schedule in your database** and attaches it to any Eloquent model, so you can store opening hours per record, edit them at runtime, and **query them straight from SQL** — "which shops are open right now?", "is this venue open between 10:00 and 12:00 next Monday?" — without loading every model into PHP.

You still get a real `Spatie\OpeningHours\OpeningHours` object whenever you need its full API.

```
$shop->openingHours()->create(['name' => 'default'])->syncWeeklySchedule([
    'monday' => [['start' => '09:00', 'end' => '13:00'], ['start' => '14:00', 'end' => '18:00']],
    'tuesday' => [['start' => '09:00', 'end' => '18:00']],
]);

// Query from the database
Shop::openAt(now())->get();                          // shops open right now
Shop::openBetween('2024-01-01', '10:00', '12:00')->get();
```

What it offers
--------------

[](#what-it-offers)

- **Weekly schedule per model** — attach opening hours to any Eloquent model through a single trait (a shop, a restaurant, an office…). A model can hold more than one schedule (e.g. seasonal), with `latestOpeningHours` / `oldestOpeningHours` helpers.
- **Multiple time ranges per day** — e.g. a lunch break: `09:00-13:00` and `14:00-18:00`.
- **Date exceptions** — override a specific date (holidays, special events). On a date with an exception, only the exception's ranges count for that day.
- **Database-level query scopes** — `openAt`, `closeAt`, `openBetween`, `closedBetween` run as SQL, so you can filter and paginate large tables without hydrating models.
- **Bridge to `spatie/opening-hours`** — turn a stored schedule into a full `OpeningHours` object on demand.
- **Localised weekday labels** — English, Italian and Dutch translations included, backed by a `Day` enum.
- **Swappable models** — point any of the internal models at your own subclass (tenant scoping, extra columns) via config.

Installation
------------

[](#installation)

Install the package via composer:

```
composer require datomatic/db-opening-hours
```

Run the migrations (four tables: `opening_hours`, `opening_hours_days`, `opening_hours_exceptions`, `opening_hours_time_ranges`):

```
php artisan migrate
```

Optionally publish the config file:

```
php artisan vendor:publish --tag="db-opening-hours-config"
```

Optionally publish the translations:

```
php artisan vendor:publish --tag="db-opening-hours-translations"
```

### Config

[](#config)

```
use Datomatic\DatabaseOpeningHours\Models\Day;
use Datomatic\DatabaseOpeningHours\Models\Exception;
use Datomatic\DatabaseOpeningHours\Models\OpeningHour;
use Datomatic\DatabaseOpeningHours\Models\TimeRange;

return [
    /*
     * Every internal relation resolves its model through this map, so you can
     * point any role at a subclass (for example to add tenant scoping or extra
     * columns) without forking the package. A subclass must extend the package
     * model it replaces and keep its table name.
     */
    'models' => [
        'opening_hour' => OpeningHour::class,
        'day'          => Day::class,
        'exception'    => Exception::class,
        'time_range'   => TimeRange::class,
    ],
];
```

Data model
----------

[](#data-model)

TableModelRole`opening_hours``OpeningHour`One schedule, polymorphically linked to your model (`openable`).`opening_hours_days``Day`A weekday (`monday`…`sunday`) inside a schedule.`opening_hours_exceptions``Exception`A date override inside a schedule.`opening_hours_time_ranges``TimeRange`A `start`–`end` window, belonging (polymorphically) to a `Day` or an `Exception`.Weekdays are represented by the `Datomatic\DatabaseOpeningHours\Enums\Day` enum.

Usage
-----

[](#usage)

### Attach opening hours to a model

[](#attach-opening-hours-to-a-model)

Add the `HasOpeningHours` trait to any model:

```
use Datomatic\DatabaseOpeningHours\Traits\HasOpeningHours;
use Illuminate\Database\Eloquent\Model;

class Shop extends Model
{
    use HasOpeningHours;
}
```

This gives the model these relations:

```
$shop->openingHours;        // MorphMany – every schedule attached to the model
$shop->latestOpeningHours;  // MorphOne  – the most recently created schedule
$shop->oldestOpeningHours;  // MorphOne  – the first created schedule
```

### Define a weekly schedule

[](#define-a-weekly-schedule)

Create a schedule and set its weekly hours in one call. `syncWeeklySchedule()` replaces the whole schedule: weekdays missing from the payload (or with empty ranges) are removed, so the argument is always the complete new state.

```
$openingHour = $shop->openingHours()->create(['name' => 'default']);

$openingHour->syncWeeklySchedule([
    'monday'    => [['start' => '09:00', 'end' => '13:00'], ['start' => '14:00', 'end' => '18:00']],
    'tuesday'   => [['start' => '09:00', 'end' => '18:00']],
    'wednesday' => [['start' => '09:00', 'end' => '18:00']],
]);
```

Times accept `9:00`, `09:00` or `09:00:00`; they are normalised before being stored.

Read the schedule back in the same shape:

```
$openingHour->weeklySchedule();
// [
//     'monday'  => [['start' => '09:00', 'end' => '13:00'], ['start' => '14:00', 'end' => '18:00']],
//     'tuesday' => [['start' => '09:00', 'end' => '18:00']],
//     ...
// ]
```

You also get a relation per weekday (returns a `Day` model or `null`):

```
$openingHour->monday;   // Day|null
$openingHour->sunday;   // Day|null
$openingHour->days;     // all Day models, ordered Monday → Sunday
```

### Date exceptions

[](#date-exceptions)

Override a specific date. On that date only the exception's ranges apply — the regular weekday hours are ignored.

```
$exception = $openingHour->exceptions()->create([
    'date'        => '2024-12-25',
    'description' => 'Christmas – short hours',
]);

$exception->timeRanges()->create(['start' => '10:00', 'end' => '12:00']);
```

### Query the database

[](#query-the-database)

The `HasOpeningHours` trait adds SQL query scopes to your model. They accept a date-time string or any `DateTimeInterface`.

```
// Records open at a given instant (weekday hours, or a matching date exception)
Shop::openAt(now())->get();
Shop::openAt('2024-12-25 11:00')->get();

// Records that have opening hours but are closed at that instant
Shop::closeAt(now())->get();

// Records whose hours fully cover a whole window on a date
Shop::openBetween('2024-01-01', '10:00', '12:00')->get();

// Records that have hours but do not cover that window
Shop::closedBetween('2024-01-01', '10:00', '12:00')->get();
```

Notes:

- `openBetween` uses **containment**: a single range must span the window end to end. A `10:00-11:30` request is *not* satisfied by a `09:00-11:00` range.
- `closeAt` / `closedBetween` deliberately exclude models **without any schedule** — no schedule means "no restriction", not "always closed".

### Get a `spatie/opening-hours` object

[](#get-a-spatieopening-hours-object)

When you need the full [`spatie/opening-hours`](https://github.com/spatie/opening-hours) API, build it from the stored schedule. Both the weekly days **and** the date exceptions are fed in, so the object's date-aware methods honour exceptions exactly like the `openAt`/`openBetween` query scopes:

```
$hours = $openingHour->openingHours(); // Spatie\OpeningHours\OpeningHours

$hours->isOpenAt(new DateTime('2024-01-01 11:00')); // true / false
$hours->nextOpen(new DateTime());
$hours->forDay('monday');                            // weekday ranges
$hours->forDate(new DateTime('2024-12-25'));         // ranges for that date, exceptions applied
```

A date exception replaces the weekday ranges for its date; an exception with no ranges makes that date closed.

Testing
-------

[](#testing)

```
composer test
```

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING](CONTRIBUTING.md) for details.

Security Vulnerabilities
------------------------

[](#security-vulnerabilities)

Please review [our security policy](../../security/policy) on how to report security vulnerabilities.

Credits
-------

[](#credits)

- [Alberto Peripolli](https://github.com/trippo)
- [All Contributors](../../contributors)

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

47

—

FairBetter than 93% of packages

Maintenance96

Actively maintained with recent releases

Popularity12

Limited adoption so far

Community14

Small or concentrated contributor base

Maturity59

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 67.9% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~117 days

Recently: every ~175 days

Total

7

Last Release

21d ago

Major Versions

v0.2.0 → v1.0.02026-07-25

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/497169?v=4)[Alberto Peripolli](/maintainers/trippo)[@trippo](https://github.com/trippo)

---

Top Contributors

[![trippo](https://avatars.githubusercontent.com/u/497169?v=4)](https://github.com/trippo "trippo (38 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (10 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (6 commits)")[![Carnicero90](https://avatars.githubusercontent.com/u/78483736?v=4)](https://github.com/Carnicero90 "Carnicero90 (2 commits)")

---

Tags

laravelDatomaticdb-opening-hours

###  Code Quality

TestsPest

Static AnalysisPHPStan, Rector

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/datomatic-db-opening-hours/health.svg)

```
[![Health](https://phpackages.com/badges/datomatic-db-opening-hours/health.svg)](https://phpackages.com/packages/datomatic-db-opening-hours)
```

###  Alternatives

[spatie/laravel-permission

Permission handling for Laravel 12 and up

13.0k107.5M1.6k](/packages/spatie-laravel-permission)[dedoc/scramble

Automatic generation of API documentation for Laravel applications.

2.2k12.6M138](/packages/dedoc-scramble)[wnx/laravel-backup-restore

A package to restore database backups made with spatie/laravel-backup.

214456.3k2](/packages/wnx-laravel-backup-restore)[rawilk/profile-filament-plugin

Profile &amp; MFA starter kit for filament.

3915.5k](/packages/rawilk-profile-filament-plugin)[harris21/laravel-fuse

Circuit breaker for Laravel queue jobs. Protect your workers from cascading failures.

46273.9k](/packages/harris21-laravel-fuse)[lacodix/laravel-model-filter

A Laravel package to filter, search and sort models with ease while fetching from database.

17562.4k](/packages/lacodix-laravel-model-filter)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
