zaki / laravel-working-calendar
Detect public holidays and country-specific weekends, then calculate previous, next, and offset working days in Laravel.
Package info
github.com/zakipharaon/laravel-working-calendar
pkg:composer/zaki/laravel-working-calendar
Requires
- php: ^8.2
- illuminate/cache: ^10.0 || ^11.0 || ^12.0 || ^13.0
- illuminate/config: ^10.0 || ^11.0 || ^12.0 || ^13.0
- illuminate/console: ^10.0 || ^11.0 || ^12.0 || ^13.0
- illuminate/contracts: ^10.0 || ^11.0 || ^12.0 || ^13.0
- illuminate/http: ^10.0 || ^11.0 || ^12.0 || ^13.0
- illuminate/support: ^10.0 || ^11.0 || ^12.0 || ^13.0
- nesbot/carbon: ^2.72 || ^3.0
Requires (Dev)
- phpunit/phpunit: ^10.5 || ^11.5 || ^12.5 || ^13.2
Suggests
- azuyalabs/yasumi: Provides an optional offline holiday fallback for Yasumi-supported countries.
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:
- Nager.Date is the default online provider.
- Yasumi is an optional offline fallback for the countries it supports.
- Custom application holidays can correct or extend provider data.
- 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:
- Forced working date.
- Forced non-working date.
- Public holiday.
- 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.