zaki/laravel-working-calendar

Detect public holidays and country-specific weekends, then calculate previous, next, and offset working days in Laravel.

Maintainers

Package info

github.com/zakipharaon/laravel-working-calendar

pkg:composer/zaki/laravel-working-calendar

Transparency log

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 1

v1.0.1 2026-07-25 09:41 UTC

This package is auto-updated.

Last update: 2026-07-25 09:41:44 UTC


README

zaki/laravel-working-calendar detects public holidays and country-specific weekends, then calculates previous, next, and offset working days.

Features

  • Public and national holiday detection by ISO 3166-1 country code.
  • Country-specific weekend rules.
  • Previous and next working-day calculation.
  • Add or subtract working days.
  • Optional ISO 3166-2 subdivision filtering.
  • Cached online holiday provider.
  • Optional offline provider fallback.
  • Custom holidays and forced working/non-working date overrides.
  • Laravel facade, dependency injection, configuration publishing, and Artisan command.

Requirements

  • PHP 8.2 or later.
  • Laravel 10, 11, 12, or 13.

Country coverage

The package architecture accepts every valid ISO 3166-1 alpha-2 country code. Actual holiday data depends on the configured providers:

  1. Nager.Date is the default online provider.
  2. Yasumi is an optional offline fallback for the countries it supports.
  3. Custom application holidays can correct or extend provider data.
  4. A custom provider can be added for countries or official sources not covered by the bundled providers.

The package throws an exception when every provider fails. It does not silently interpret provider failure as “there are no holidays.”

Installation

After the package is published on Packagist:

composer require zaki/laravel-working-calendar

Laravel discovers the service provider and facade automatically. Publish the configuration when overrides are needed:

php artisan vendor:publish --tag=working-calendar-config

For the optional Yasumi offline fallback:

composer require azuyalabs/yasumi

Local package development

Add a path repository to the Laravel application's composer.json:

{
    "repositories": [
        {
            "type": "path",
            "url": "../laravel-working-calendar",
            "options": {
                "symlink": true
            }
        }
    ]
}

Then run:

composer require zaki/laravel-working-calendar:@dev

Basic usage

use Zaki\WorkingCalendar\Facades\WorkingCalendar;

$status = WorkingCalendar::today(
    countryCode: 'TR',
    timezone: 'Europe/Istanbul',
);

$status->isWorkingDay;
$status->isHoliday;
$status->isWeekend;
$status->previousWorkingDay->toDateString();
$status->nextWorkingDay->toDateString();
$status->holidays;

Inspect a specific date:

$status = WorkingCalendar::inspect(
    date: '2026-04-23',
    countryCode: 'TR',
    timezone: 'Europe/Istanbul',
);

return $status->toArray();

Example response shape:

{
    "date": "2026-04-23",
    "country_code": "TR",
    "subdivision": null,
    "is_working_day": false,
    "is_weekend": false,
    "is_holiday": true,
    "is_forced_working_day": false,
    "is_forced_non_working_day": false,
    "holidays": [
        {
            "date": "2026-04-23",
            "country_code": "TR",
            "name": "National Sovereignty and Children's Day",
            "local_name": "Ulusal Egemenlik ve Çocuk Bayramı",
            "global": true,
            "subdivisions": [],
            "fixed": true,
            "launch_year": 1920,
            "types": ["Public"],
            "source": "nager-date"
        }
    ],
    "previous_working_day": "2026-04-22",
    "next_working_day": "2026-04-24"
}

Direct checks

WorkingCalendar::isHoliday('2026-04-23', 'TR');
WorkingCalendar::isWeekend('2026-07-24', 'SA');
WorkingCalendar::isWorkingDay('2026-04-24', 'TR');

$before = WorkingCalendar::previousWorkingDay('2026-04-23', 'TR');
$after = WorkingCalendar::nextWorkingDay('2026-04-23', 'TR');

$nearest = WorkingCalendar::nearestWorkingDays('2026-04-23', 'TR');

$settlementDate = WorkingCalendar::addWorkingDays(
    date: '2026-04-23',
    days: 3,
    countryCode: 'TR',
);

nearestWorkingDays() returns:

[
    'before' => CarbonImmutable::parse('2026-04-22'),
    'after' => CarbonImmutable::parse('2026-04-24'),
]

Regional holidays

Pass an ISO 3166-2 subdivision code when the selected provider contains regional data:

$status = WorkingCalendar::inspect(
    date: '2026-10-03',
    countryCode: 'DE',
    subdivision: 'DE-BY',
);

Artisan command

php artisan working-calendar:check TR \
    --date=2026-04-23 \
    --timezone=Europe/Istanbul

Regional example:

php artisan working-calendar:check DE \
    --date=2026-10-03 \
    --subdivision=DE-BY

Configuration

Weekend overrides

ISO-8601 weekday numbers are used: Monday is 1 and Sunday is 7.

'weekends' => [
    'TR' => [6, 7],
    'SA' => [5, 6],
    'IN' => [7],
],

Forced working and non-working dates

'forced_working_days' => [
    'TR' => ['2026-10-31'],
],

'forced_non_working_days' => [
    'TR' => ['2026-12-31'],
],

Precedence is:

  1. Forced working date.
  2. Forced non-working date.
  3. Public holiday.
  4. Weekend.

Custom holidays

'custom_holidays' => [
    'TR' => [
        [
            'date' => '2026-12-31',
            'name' => 'Company Closure',
            'local_name' => 'Şirket Tatili',
            'global' => true,
            'types' => ['Company'],
        ],
    ],
],

Regional custom holiday:

[
    'date' => '2026-08-01',
    'name' => 'Regional Closure',
    'global' => false,
    'subdivisions' => ['DE-BY'],
]

Dependency injection

use Zaki\WorkingCalendar\WorkingCalendar;

final class SettlementService
{
    public function __construct(
        private readonly WorkingCalendar $calendar,
    ) {
    }

    public function settlementDate(string $createdAt, string $country): string
    {
        return $this->calendar
            ->addWorkingDays($createdAt, 2, $country)
            ->toDateString();
    }
}

Adding another provider

Create a class implementing HolidayProvider:

use Zaki\WorkingCalendar\Contracts\HolidayProvider;

final class OfficialGovernmentHolidayProvider implements HolidayProvider
{
    public function holidays(
        int $year,
        string $countryCode,
        ?string $subdivision = null,
    ): array {
        // Return a list of Holiday DTOs.
    }
}

Place it first in the provider chain:

'providers' => [
    OfficialGovernmentHolidayProvider::class,
    \Zaki\WorkingCalendar\Providers\NagerDateHolidayProvider::class,
    \Zaki\WorkingCalendar\Providers\YasumiHolidayProvider::class,
],

Production guidance

Holiday rules can change, governments can announce one-off closures, and some religious holidays depend on official declarations. Configure overrides for urgent corrections and use the most authoritative provider available for the country.

For payment, settlement, payroll, or compliance systems, store the country, subdivision, holiday source, calculation date, and resulting working date in an audit log.

Development

composer install
composer check

composer check validates composer.json, lints all PHP files, and runs the test suite. GitHub Actions runs these checks on PHP 8.2, 8.3, 8.4, and 8.5.

Release instructions are in RELEASING.md.

Security

See SECURITY.md.

License

MIT. See LICENSE.