spatie/opening-hours

repository·master·Indexed 23 days ago

https://github.com/spatie/opening-hours

A PHP helper library to create, query, and format business opening hours. It supports regular schedules, exceptions, holiday ranges, and dynamic filters. Key features include checking if a business is open at a specific time, calculating open/closed durations, finding next/previous opening and closing times, and importing or exporting Schema.org OpeningHoursSpecification structured data.

Tokens
5.7K
Snippets
13
Records
26
Agent score
83%

What's inside spatie/opening-hours

  1. How the OpeningHours core abstractions work together

    master

    The package is designed around the Spatie\OpeningHours\OpeningHours class, which serves as the primary entry point. It manages a schedule composed of several value objects:

    • Time: Represents a single time (e.g., 09:00).
    • TimeRange: Represents a period with a start and an end time (e.g., 09:00-12:00).
    • OpeningHoursForDay: Represents a collection of non-overlapping TimeRanges for a specific day.

    Users should interact with the schedule exclusively through the OpeningHours class to ensure consistency and proper timezone handling.

  2. Use filters for dynamic opening hours

    master

    The filters property allows you to pass callables (closures or function references) that take a DateTime as a parameter and return the settings for that date. This is useful for complex logic like calculating Easter Monday.

    Precedence Rules:

    1. The first filter that returns a non-null value takes precedence.
    2. The filters array has precedence over filters inside the exceptions array.
    3. If a callable is found in the exceptions property, it is automatically added to the filters.

    Warning: Filters are executed for every date checked and cannot be cached. Avoid heavy processing inside filters to prevent performance issues.

    $openingHours = OpeningHours::create([
        'monday' => [
           '09:00-12:00',
        ],
        'filters' => [
            function ($date) {
                $year         = intval($date->format('Y'));
                $easterMonday = new DateTimeImmutable('2018-03-21 +'.(easter_days($year) + 1).'days');
                if ($date->format('m-d') === $easterMonday->format('m-d')) {
                    return []; // Closed on Easter Monday
                }
            },
        ],
    ]);
  3. Get next open or close times

    master

    You can retrieve the next upcoming DateTime when a business will open or close relative to a given time:

    • nextOpen(DateTimeInterface $dateTime): Returns the next DateTime when the business opens.
    • nextClose(DateTimeInterface $dateTime): Returns the next DateTime when the business closes.
    • currentOpenRange(DateTimeInterface $dateTime): Returns a range object if the business is currently open at the given time, otherwise returns null.
    // The next open datetime is tomorrow morning, because we’re closed on 25th of December.
    $nextOpen = $openingHours->nextOpen(new DateTime('2016-12-25 10:00:00')); // 2016-12-26 09:00:00
    
    // The next close datetime is at noon.
    $nextClose = $openingHours->nextClose(new DateTime('2016-12-24 10:00:00')); // 2016-12-24 12:00:00
    
    $now = new DateTime('now');
    $range = $openingHours->currentOpenRange($now);
    
    if ($range) {
        echo "It's open since " . $range->start() . "\n";
        echo "It will close at " . $range->end() . "\n";
    } else {
        echo "It's closed since " . $openingHours->previousClose($now)->format('l H:i') . "\n";
        echo "It will re-open at " . $openingHours->nextOpen($now)->format('l H:i') . "\n";
    }
  4. Retrieve schedule information for days and weeks

    master

    Use these methods to inspect the schedule structure:

    • forDay(string $day): Returns an OpeningHoursForDay object for a specific day (lowercase English name).
    • forDate(DateTimeInterface $dateTime): Returns an OpeningHoursForDay object for a specific date, accounting for exceptions.
    • exceptions(): Returns an array of OpeningHoursForDay objects for all exception dates, keyed by Y-m-d.
    • forWeek(): Returns an array of OpeningHoursForDay objects for a regular week.
    • forWeekCombined(): Returns an array where keys are the first day of a group and values are arrays of days sharing the same hours.
    • forWeekConsecutiveDays(): Returns an array of concatenated adjacent days with the same hours. Warning: This does not loop from Sunday to Monday.
    $openingHours->forDay('monday');
    $openingHours->forDate(new DateTime('2016-12-25'));
    $openingHours->exceptions();
    $openingHours->forWeek();
    $openingHours->forWeekCombined();
    $openingHours->forWeekConsecutiveDays();
  5. Create an OpeningHours instance

    master

    Use OpeningHours::create() to initialize a schedule from an array. If no timezone is provided, the package assumes provided DateTime objects already match the schedule's timezone.

    You can specify an input and output timezone to handle conversions automatically. If an output timezone is provided, methods returning dates (like nextOpen) will convert the result back to the original timezone so the object reflects local time while the internal logic uses the business timezone.

    To handle overlapping ranges during creation, use the 'overflow' => true option in the array or use the createAndMergeOverlappingRanges() shortcut.

    // Basic creation
    $openingHours = OpeningHours::create([
        'monday' => ['09:00-12:00', '13:00-18:00'],
    ]);
    
    // Creation with specific input and output timezones
    $openingHours = OpeningHours::create([
        'monday' => ['09:00-12:00', '13:00-18:00'],
        'timezone' => [
            'input' => 'America/New_York',
            'output' => 'Europe/Oslo',
        ],
    ]);
    
    // Merging overlapping ranges
    $ranges = [
      'monday' => ['08:00-11:00', '10:00-12:00'],
    ];
    $mergedRanges = OpeningHours::mergeOverlappingRanges($ranges); // Monday becomes ['08:00-12:00']
    $openingHours = OpeningHours::createAndMergeOverlappingRanges($ranges);
  6. Query if a business is open or closed

    master

    The OpeningHours object provides several methods to check availability:

    • isOpenOn(string $day): Returns true if the business is open on a specific day of the week (e.g., 'monday').
    • isOpenOn(string $date): Returns true if the business is open on a specific date (e.g., '2016-12-25').
    • isOpenAt(DateTimeInterface $dateTime): Returns true if the business is open at the exact provided date and time.
    // Open on Mondays:
    $openingHours->isOpenOn('monday'); // true
    
    // Closed on Sundays:
    $openingHours->isOpenOn('sunday'); // false
    
    // Closed because it's after hours:
    $openingHours->isOpenAt(new DateTime('2016-09-26 19:00:00')); // false
    
    // Closed because Christmas was set as an exception
    $openingHours->isOpenOn('2016-12-25'); // false
  7. Check if a business is open or closed

    master

    The OpeningHours class provides several methods to check status:

    • isOpen(): Checks if the business is open right now.
    • isClosed(): Checks if the business is closed right now.
    • isOpenAt(DateTimeInterface $dateTime): Checks if open at a specific time.
    • isClosedAt(DateTimeInterface $dateTime): Checks if closed at a specific time.
    • isOpenOn(string $day): Checks if open on a specific day (e.g., 'monday'). If a date string is passed (e.g., '2020-09-03' or '09-03'), it checks both the regular schedule and exceptions.
    • isClosedOn(string $day): Checks if closed on a regular scheduled day.
    • isAlwaysOpen(): Returns true if the business is open 24/7 with no exceptions.
    • isAlwaysClosed(): Returns true if the business is never open (empty schedule).
    $openingHours->isOpen();
    $openingHours->isOpenAt(new DateTime('2016-26-09 20:00'));
    $openingHours->isOpenOn('saturday');
    $openingHours->isOpenOn('2020-09-03');
    
    if ($openingHours->isAlwaysOpen()) {
        echo 'This business is open all day long every day.';
    }
  8. Calculate duration of open or closed time

    master

    Calculate the amount of time between two dates using these methods. All return a float representing the total amount in the specified unit:

    Open Time:

    • diffInOpenHours(DateTimeInterface $startDate, DateTimeInterface $endDate)
    • diffInOpenMinutes(DateTimeInterface $startDate, DateTimeInterface $endDate)
    • diffInOpenSeconds(DateTimeInterface $startDate, DateTimeInterface $endDate)

    Closed Time:

    • diffInClosedHours(DateTimeInterface $startDate, DateTimeInterface $endDate)
    • diffInClosedMinutes(DateTimeInterface $startDate, DateTimeInterface $endDate)
    • diffInClosedSeconds(DateTimeInterface $startDate, DateTimeInterface $endDate)
    $openingHours->diffInOpenHours(new DateTime('2016-12-24 11:00:00'), new DateTime('2016-12-24 16:34:25'));
    $openingHours->diffInClosedHours(new DateTime('2016-12-24 11:00:00'), new DateTime('2016-12-24 16:34:25'));
  9. Use the 'to' separator for ranges

    master

    You can specify multiple days at once using the to separator in the keys for both the regular schedule and exceptions.

    $openingHours = OpeningHours::create([
        'monday to friday' => ['09:00-19:00'],
        'saturday to sunday' => [],
        'exceptions' => [
            // Every year
            '12-24 to 12-26' => [
                'hours' => [],
                'data'  => 'Holidays',
            ],
            // Only happening in 2024
            '2024-06-25 to 2024-07-01' => [
                'hours' => [],
                'data'  => 'Closed for works',
            ],
        ],
    ]);
  10. Attach custom data to opening hours

    master

    You can attach arbitrary data to specific time ranges or exceptions. This data can be a string or any other value (like an associative array).

    To use this, structure your input using either the shorthand (where the value is an array containing the hours and a 'data' key) or the explicit 'hours' key format.

    $openingHours = OpeningHours::create([
        'monday' => [
            'data' => 'Typical Monday',
            '09:00-12:00',
            '13:00-18:00',
        ],
        'tuesday' => [
            '09:00-12:00',
            '13:00-18:00',
            [
                '19:00-21:00',
                'data' => 'Extra on Tuesday evening',
            ],
        ],
        'exceptions' => [
            '2016-12-25' => [
                'data' => 'Closed for Christmas',
            ],
        ],
    ]);
    
    echo $openingHours->forDay('monday')->data; // Typical Monday
    echo $openingHours->forDate(new DateTime('2016-12-25'))->data; // Closed for Christmas
    echo $openingHours->forDay('tuesday')[2]->data; // Extra on Tuesday evening