Carbon mixin to handle business days
Professionally supported nesbot/carbon is now available
composer require cmixin/business-day
First load the mixin in some global bootstrap place of your app:
<?php
use Cmixin\BusinessDay;
BusinessDay::enable('Carbon\Carbon');
// Or if you use Laravel:
// BusinessDay::enable('Illuminate\Support\Carbon');
Business days methods are now available on any Carbon instance used anywhere later.
You can set different holidays lists for different regions (to handle different countries, enterprises, etc.)
We provide a some holidays lists as example that work out of the box: https://github.com/kylekatarnls/business-day/tree/master/src/Cmixin/Holidays
A holidays list file is a PHP file that return an array, each item of the array represent a holiday of the year.
It can be a fixed date such as '25/12'
for Christmas or
a closure that will calculate the date for the given year.
Warning: the format is DD/MM, day slash month,
both on 2 digits, '4/05'
, '04/5'
or '4/5'
will
just be ignored. Only '04/05'
will work and this
is the 4th of May (May the Force be with you), not
the 5th of April.
For example the Martin Luther King Jr. Day (which fall at the third monday of January) can be calculated this way:
function ($year) {
$date = new DateTime("third monday of january $year");
return $date->format('d/m');
}
The same goes for closures, they strictly must return strings with DD/MM format.
Other example for Easter monday:
function ($year) {
$days = easter_days($year) + 1;
$date = new DateTime("$year-03-21 +$days days");
return $date->format('d/m');
}
To select the set of holidays of a region, use:
Carbon::parse('2000-12-25 00:00:00')->isHoliday(); // false
Carbon::setHolidaysRegion('us-national');
Carbon::parse('2000-12-25 00:00:00')->isHoliday(); // true
This will select our national preset for USA (only holidays common to all states).
Before selecting a region the list of holidays is
empty so isHoliday
will return false
for any date.
This method allow you to get the holiday list for a given region passed in argument, if no argument given the list for the current selected region is returned.
Carbon::getHolidays('us-national'); // raw us-national holidays list
Carbon::setHolidaysRegion('fr-national');
Carbon::getHolidays(); // raw fr-national holidays list
It's how you can set your own holidays lists:
Carbon::setHolidays('us-il', array_merge(
Carbon::getHolidays('us-national'),
array(
// Presidents' Day
'presidents-day' => function ($year) {
$date = new DateTime("third monday of february $year");
return $date->format('d/m');
},
// Columbus Day
'columbus-day' => function ($year) {
$date = new DateTime("second monday of october $year");
return $date->format('d/m');
},
// Day after Thanksgiving
'thanksgiving-next-day' => function ($year) {
$date = new DateTime("fourth thursday of november $year +1 day");
return $date->format('d/m');
},
)
));
Carbon::setHolidays('my-enterprise', array_merge(
Carbon::getHolidays('us-is'),
array(
// Lincoln's Birthday
'lincolns-birthday' => '12/02',
)
));
// Then when you select my-enterprise, all us-national,
// us-il and my-enterprise days are enabled
Carbon::setHolidaysRegion('my-enterprise');
You can also pass deep array to setHolidays
to set in the same call holidays dates and either observed flags, names
(in different languages) or both:
Carbon::setHolidays('my-enterprise', array(
'lincolns-birthday' => array(
'date' => '12/02',
'observed' => true,
'name' => array(
'en' => "Lincoln's Birthday",
'fr' => 'Anniversaire du Président Lincoln',
),
),
));
While setHolidays replace the whole holidays list for a given region, addHolidays append holidays to the current list.
Carbon::addHolidays('my-list', array(
'poney' => '20/01',
'swimmingpool' => '21/01',
)));
Carbon::addHolidays('my-list', array(
'boss-birthday' => '10/02',
'boss-birthday-next-day' => '11/02',
)));
Carbon::getHolidays('my-list') // contains 20/01, 21/01, 10/02 and 11/02
As for setHolidays
, addHolidays
handle deep arrays using date, observed and name keys.
Reset all holidays and region previously set.
Returns true
if the date (Carbon instance) is a holiday (in the list
of the selected region), false
else.
Carbon::setHolidaysRegion('us-national');
Carbon::parse('2018-01-15')->isHoliday() // true for 2018
Carbon::parse('2018-01-21')->isHoliday() // false for 2018
Carbon::parse('2019-01-15')->isHoliday() // false for 2019
Carbon::parse('2019-01-21')->isHoliday() // true for 2019
Carbon::isHoliday() // true if today is a holiday
Same as isHoliday
but returns a string id for the given holiday, so you can target specific holidays.
Carbon::setHolidaysRegion('us-national');
Carbon::parse('2018-12-25')->getHolidayId() // "christmas"
Carbon::parse('2018-01-15')->getHolidayId() // "mlk-day"
// Returns false if the day is not a holiday
Carbon::parse('2018-01-21')->getHolidayId() // false
$nextWeekHoliday = Carbon::today()->addWeek()->getHolidayId();
if ($nextWeekHoliday === 'easter' or $nextWeekHoliday === 'christmas') {
echo 'Time to get chocolates.';
}
Returns the name of the holiday in the current locale (or English by default) or false if the day is not a holiday.
Carbon::setHolidaysRegion('fr-national');
Carbon::parse('2018-12-25')->getHolidayName() // "Christmas"
Carbon::parse('2018-12-25')->getHolidayName('fr') // "Noël"
Carbon::setLocale('nl');
Carbon::parse('2018-01-15')->getHolidayName() // "Eerste Kerstdag"
// If the name is not translated in business-day
Carbon::setLocale('de');
Carbon::parse('2018-01-15')->getHolidayName() // "Christmas"
// With Carbon 2, you can use local locale:
Carbon::parse('2018-01-15')->locale('sl')->getHolidayName() // "BoĹľiÄŤ"
Note: be aware, region has no effect to the holiday language name since some regions have multiple languages.
So to get french names of the french holidays, you need both Carbon::setHolidaysRegion('fr-national')
and
Carbon::setLocale('fr_FR')
, the first for the holiday calendar, the second for the language.
Wanna rename a holiday name in a particular language? No problem:
Carbon::parse('2018-12-25')->getHolidayName() // "Christmas"
Carbon::setHolidayName('christmas', 'en', 'Christmas Day');
Carbon::parse('2018-12-25')->getHolidayName() // "Christmas Day"
Returns true
if the date (Carbon instance) is nor a week-end day neither
a holiday, false
else. Week-end days can be configured (see
https://carbon.nesbot.com/docs/#weekend).
Carbon::setHolidaysRegion('us-national');
Carbon::parse('2018-01-15')->isBusinessDay() // false for 2018 (Martin Luther King Jr. Day, sleep)
Carbon::parse('2018-01-21')->isBusinessDay() // false for 2018 (sunday is week-end by default, sleep)
Carbon::parse('2019-01-15')->isBusinessDay() // true for 2019 (tuesday, nothing special, go to work)
Carbon::parse('2019-01-21')->isBusinessDay() // false for 2019 (Martin Luther King Jr. Day, sleep)
Carbon::isBusinessDay() // true if today is a business day
Add days to the date (Carbon instance) to jump to the next business day (skipping holidays and week-ends).
Carbon::setHolidaysRegion('us-national');
echo Carbon::parse('2018-01-12')->nextBusinessDay()->format('Y-m-d'); // 2018-01-16
echo Carbon::parse('2018-01-13')->nextBusinessDay()->format('Y-m-d'); // 2018-01-16
echo Carbon::parse('2018-01-15')->nextBusinessDay()->format('Y-m-d'); // 2018-01-16
echo Carbon::parse('2018-01-16')->nextBusinessDay()->format('Y-m-d'); // 2018-01-17
echo Carbon::parse('2018-01-12 12:30:00')->nextBusinessDay()->format('Y-m-d H:i'); // 2018-01-16 12:30 (time is kept)
// If you don't want to keep the time, just use ->startOfDay()
echo Carbon::nextBusinessDay()->format('Y-m-d'); // returns the next business day date after today (midnight)
Sub days to the date (Carbon instance) to jump to the previous business day (skipping holidays and week-ends).
Carbon::setHolidaysRegion('us-national');
echo Carbon::parse('2018-01-12')->previousBusinessDay()->format('Y-m-d'); // 2018-01-11
echo Carbon::parse('2018-01-13')->previousBusinessDay()->format('Y-m-d'); // 2018-01-12
echo Carbon::parse('2018-01-15')->previousBusinessDay()->format('Y-m-d'); // 2018-01-12
echo Carbon::parse('2018-01-16')->previousBusinessDay()->format('Y-m-d'); // 2018-01-12
echo Carbon::parse('2018-01-15 12:30:00')->previousBusinessDay()->format('Y-m-d H:i'); // 2018-01-12 12:30 (time is kept)
// If you don't want to keep the time, just use ->startOfDay()
echo Carbon::previousBusinessDay()->format('Y-m-d'); // returns the previous business day date before today (midnight)
Returns the current date (Carbon instance) if it's a business day, else add days to jump to the next business day (skipping holidays and week-ends).
Carbon::setHolidaysRegion('us-national');
echo Carbon::parse('2018-01-12')->currentOrNextBusinessDay()->format('Y-m-d'); // 2018-01-12
echo Carbon::parse('2018-01-13')->currentOrNextBusinessDay()->format('Y-m-d'); // 2018-01-16
echo Carbon::parse('2018-01-15')->currentOrNextBusinessDay()->format('Y-m-d'); // 2018-01-16
echo Carbon::parse('2018-01-16')->currentOrNextBusinessDay()->format('Y-m-d'); // 2018-01-16
echo Carbon::parse('2018-01-13 12:30:00')->currentOrNextBusinessDay()->format('Y-m-d H:i'); // 2018-01-16 12:30 (time is kept)
// If you don't want to keep the time, just use ->startOfDay()
echo Carbon::currentOrNextBusinessDay() // equivalent to Carbon::today()->currentOrNextBusinessDay()
Returns the current date (Carbon instance) if it's a business day, else sub days to jump to the previous business day (skipping holidays and week-ends).
Carbon::setHolidaysRegion('us-national');
echo Carbon::parse('2018-01-12')->currentOrPreviousBusinessDay()->format('Y-m-d'); // 2018-01-12
echo Carbon::parse('2018-01-13')->currentOrPreviousBusinessDay()->format('Y-m-d'); // 2018-01-12
echo Carbon::parse('2018-01-15')->currentOrPreviousBusinessDay()->format('Y-m-d'); // 2018-01-12
echo Carbon::parse('2018-01-16')->currentOrPreviousBusinessDay()->format('Y-m-d'); // 2018-01-16
echo Carbon::parse('2018-01-13 12:30:00')->currentOrPreviousBusinessDay()->format('Y-m-d H:i'); // 2018-01-16 12:30 (time is kept)
// If you don't want to keep the time, just use ->startOfDay()
echo Carbon::currentOrPreviousBusinessDay() // equivalent to Carbon::today()->currentOrPreviousBusinessDay()
Add days to the date (Carbon instance) skipping holidays and week-ends.
Carbon::setHolidaysRegion('us-national');
echo Carbon::parse('2018-01-10')->addBusinessDays(4)->format('Y-m-d'); // 2018-01-17
echo Carbon::parse('2018-01-13')->addBusinessDays()->format('Y-m-d'); // 2018-01-16 add 1 business day by default
echo Carbon::addBusinessDays(6)->format('Y-m-d'); // returns the date 6 business days after today (midnight)
Alias addBusinessDays.
Sub days to the date (Carbon instance) skipping holidays and week-ends.
Carbon::setHolidaysRegion('us-national');
echo Carbon::parse('2018-01-17')->subBusinessDays(4)->format('Y-m-d'); // 2018-01-12
echo Carbon::parse('2018-01-15')->subBusinessDays()->format('Y-m-d'); // 2018-01-12 sub 1 business day by default
echo Carbon::subBusinessDays(5)->format('Y-m-d'); // returns the date 5 business days date before today (midnight)
Alias subBusinessDays.
You can define isolated zones that can observe different holidays lists. By default the zone is "default"
.
Carbon::setHolidaysRegion('us-national');
Carbon::setObservedHolidaysZone('Nevada-subsidiary-company');
Carbon::observeAllHolidays();
Carbon::unobserveHoliday('independence-day');
Carbon::setObservedHolidaysZone('Illinois-subsidiary-company');
Carbon::observeAllHolidays();
Carbon::unobserveHolidays(array('labor-day', 'mlk-day'));
Carbon::setObservedHolidaysZone('Nevada-subsidiary-company');
Carbon::parse('2018-01-15')->isObservedHoliday(); // true
Carbon::parse('2018-07-04')->isObservedHoliday(); // false
Carbon::parse('2018-09-03')->isObservedHoliday(); // true
Carbon::setObservedHolidaysZone('Illinois-subsidiary-company');
Carbon::parse('2018-01-15')->isObservedHoliday(); // false
Carbon::parse('2018-07-04')->isObservedHoliday(); // true
Carbon::parse('2018-09-03')->isObservedHoliday(); // false
Get the current zone used for observed holidays.
Carbon::getObservedHolidaysZone(); // "Illinois-subsidiary-company"
Return true if the current day is an observed holiday (holiday as per the holidays list in use and if it's observed as per the current zone).
Carbon::setHolidaysRegion('fr-national');
Carbon::parse('2018-01-15')->isHoliday(); // true
// By default no holiday is observed
Carbon::parse('2018-01-15')->isObservedHoliday(); // false
// Then you can observe a list of holidays
Carbon::unobserveHolidays(array('christmas', 'new-year'));
Carbon::parse('2018-01-15')->isObservedHoliday(); // true
// Observed holidays are kept if you change the holidays region:
Carbon::setHolidaysRegion('us-national');
Carbon::parse('2018-01-15')->isObservedHoliday(); // true
// So you can observe holidays that are not in the current region with no restriction
Observe holidays. You can have multiple set of observed days using setObservedHolidaysZone
.
Carbon::observeHoliday(array('christmas', 'new-year'));
Carbon::observeHoliday('easter');
Alias of observeHoliday
.
Carbon::observeHolidays(array('christmas', 'new-year'));
Carbon::observeHolidays('easter');
Set holidays as not observed. You can have multiple set of observed days using setObservedHolidaysZone
.
Carbon::unobserveHoliday(array('christmas', 'new-year'));
Carbon::unobserveHoliday('easter');
Alias of unobserveHoliday
.
Carbon::unobserveHolidays(array('christmas', 'new-year'));
Carbon::unobserveHolidays('easter');
Remove any previous settings for observed days in the current zone, then make every holidays as observed by default.
// Observe every holidays except Easter
Carbon::observeAllHolidays();
Carbon::unobserveHoliday('easter');
Remove any previous settings for observed days in the current zone, then make every holidays as not observed by default.
// Observe only Easter
Carbon::unobserveAllHolidays();
Carbon::observeHoliday('easter');
The scope of this library is to date business date and time utilities to Carbon, if you think of a feature in this scope, feel free to submit a pull-request.
We will also happily merge any holidays file matching an official country, state or region holidays list.
Based on the work of Christopher "rmblstrp", see Carbon PR #706