README.md
# business-time
[Carbon](https://carbon.nesbot.com/) mixin to handle business days and opening hours
[![Latest Stable Version](https://poser.pugx.org/cmixin/business-time/v/stable.png)](https://packagist.org/packages/cmixin/business-time)
[![GitHub Actions](https://github.com/kylekatarnls/business-time/workflows/Tests/badge.svg)](https://github.com/kylekatarnls/business-time/actions)
[![Code Climate](https://codeclimate.com/github/kylekatarnls/business-time/badges/gpa.svg)](https://codeclimate.com/github/kylekatarnls/business-time)
[![Test Coverage](https://codeclimate.com/github/kylekatarnls/business-time/badges/coverage.svg)](https://codeclimate.com/github/kylekatarnls/business-time/coverage)
[![StyleCI](https://styleci.io/repos/155368756/shield?branch=master&style=flat)](https://styleci.io/repos/155368756)
[Professionally supported nesbot/carbon is now available](https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme)
## Install
```shell
composer require cmixin/business-time
```
## Usage
First load the mixin in some global bootstrap place of your app:
```php
<?php
use Carbon\Carbon;
use Cmixin\BusinessTime;
BusinessTime::enable(Carbon::class);
// Or if you use Laravel:
// BusinessTime::enable('Illuminate\Support\Carbon');
// And you can enable multiple classes at once:
BusinessTime::enable([
Carbon::class,
CarbonImmutable::class,
]);
// As a second argument you can set default opening hours:
BusinessTime::enable(Carbon::class, [
'monday' => ['09:00-12:00', '13:00-18:00'],
'tuesday' => ['09:00-12:00', '13:00-18:00'],
'wednesday' => ['09:00-12:00'],
'thursday' => ['09:00-12:00', '13:00-18:00'],
'friday' => ['09:00-12:00', '13:00-20:00'],
'saturday' => ['09:00-12:00', '13:00-16:00'],
'sunday' => [],
'exceptions' => [
'2016-11-11' => ['09:00-12:00'],
'2016-12-25' => [],
'01-01' => [], // Recurring on each 1st of january
'12-25' => ['09:00-12:00'], // Recurring on each 25th of december
],
// You can use the holidays provided by BusinessDay
// and mark them as fully closed days using 'holidaysAreClosed'
'holidaysAreClosed' => true,
// Note that exceptions will still have the precedence over
// the holidaysAreClosed option.
'holidays' => [
'region' => 'us-ny', // Load the official list of holidays from USA - New York
'with' => [
'labor-day' => null, // Remove the Labor Day (so the business is open)
'company-special-holiday' => '04-07', // Add some custom holiday of your company
],
],
]);
```
[Try in the live editor](https://play.phpsandbox.io/embed/nesbot/carbon?input=%3C%3Fphp%0A%0Ause%20Carbon%5CCarbon%3B%0Ause%20Cmixin%5CBusinessTime%3B%0A%0ABusinessTime%3A%3Aenable(Carbon%3A%3Aclass%2C%20%5B%0A%20%20'monday'%20%3D%3E%20%5B'09%3A00-12%3A00'%2C%20'13%3A00-18%3A00'%5D%2C%0A%20%20'tuesday'%20%3D%3E%20%5B'09%3A00-12%3A00'%2C%20'13%3A00-18%3A00'%5D%2C%0A%20%20'wednesday'%20%3D%3E%20%5B'09%3A00-12%3A00'%5D%2C%0A%20%20'thursday'%20%3D%3E%20%5B'09%3A00-12%3A00'%2C%20'13%3A00-18%3A00'%5D%2C%0A%20%20'friday'%20%3D%3E%20%5B'09%3A00-12%3A00'%2C%20'13%3A00-20%3A00'%5D%2C%0A%20%20'saturday'%20%3D%3E%20%5B'09%3A00-12%3A00'%2C%20'13%3A00-16%3A00'%5D%2C%0A%20%20'sunday'%20%3D%3E%20%5B%5D%2C%0A%20%20'exceptions'%20%3D%3E%20%5B%0A%20%20%20%20'2016-11-11'%20%3D%3E%20%5B'09%3A00-12%3A00'%5D%2C%0A%20%20%20%20'2016-12-25'%20%3D%3E%20%5B%5D%2C%0A%20%20%20%20'01-01'%20%3D%3E%20%5B%5D%2C%20%2F%2F%20Recurring%20on%20each%201st%20of%20january%0A%20%20%20%20'12-25'%20%3D%3E%20%5B'09%3A00-12%3A00'%5D%2C%20%2F%2F%20Recurring%20on%20each%2025th%20of%20december%0A%20%20%5D%2C%0A%20%20'holidays'%20%3D%3E%20%5B%0A%20%20%20%20'region'%20%3D%3E%20'us-ny'%2C%20%2F%2F%20Load%20the%20official%20list%20of%20holidays%20from%20USA%20-%20New%20York%0A%20%20%20%20'with'%20%3D%3E%20%5B%0A%20%20%20%20%20%20'labor-day'%20%3D%3E%20null%2C%20%2F%2F%20Remove%20the%20Labor%20Day%20(so%20the%20business%20is%20open)%0A%20%20%20%20%20%20'company-special-holiday'%20%3D%3E%20'04-07'%2C%20%2F%2F%20Add%20some%20custom%20holiday%20of%20your%20company%20%0A%20%20%20%20%5D%2C%0A%20%20%5D%2C%0A%5D)%3B%0A%0A%24date%20%3D%20Carbon%3A%3Aparse('2019-04-01%2017%3A25')%3B%0A%0Avar_dump(%24date-%3EisOpen())%3B%0Aecho%20%24date-%3EnextClose().%22%5Cn%22%3B%0A%0A%24date%20%3D%20Carbon%3A%3Aparse('2019-04-01%2018%3A25')%3B%0A%0Avar_dump(%24date-%3EisOpen())%3B%0Aecho%20%24date-%3EnextOpen().%22%5Cn%22%3B%0A%0A%2F%2F%20Exception%20for%20Christmas%0A%24date%20%3D%20Carbon%3A%3Aparse('2019-12-25%2017%3A25')%3B%0A%0Avar_dump(%24date-%3EisOpen())%3B%0A%0A%24date%20%3D%20Carbon%3A%3Aparse('2019-12-25%2011%3A25')%3B%0A%0Avar_dump(%24date-%3EisOpen())%3B%0A%0A%24date%20%3D%20Carbon%3A%3Aparse('2018-04-07%2011%3A25')%3B%0A%0Avar_dump(%24date-%3EisBusinessOpen())%3B%20%2F%2F%20use%20isBusinessOpen%20to%20consider%20holidays%20as%20closed%20all%20day%20long)
Business days methods are now available on any Carbon instance
used anywhere later.
## Features
By enabling `BusinessTime` you automatically benefit on every holidays features of `BusinessDay`,
see [cmixin/business-day](https://github.com/kylekatarnls/business-day)
As soon as you set opening hours (using the second parameter of `BusinessTime::enable()`,
`Carbon::setOpeningHours([...])` or `$carbonDate->setOpeningHours([...])`), you'll be able to retrieve opening hours
on any Carbon instance or statically (`$carbonDate->getOpeningHours()` or `Carbon::getOpeningHours()`) as an
instance of `OpeningHours` (`spatie/opening-hours`),
see [spatie/opening-hours](https://github.com/spatie/opening-hours) for complete list of features of this class.
Then with opening hours, you'll get the following methods directly available on Carbon instances:
### Holidays
By default, holidays has no particular opening hours, it will use the opening hours of the current day of week, but
you can use the `'holidaysAreClosed' => true` option to close the business on every holiday that is not specified
otherwise in the `'exceptions'` option. Else you can use a custom exception handler to link holidays or any dynamic
calculation as below:
```php
BusinessTime::enable(Carbon::class, [
'monday' => ['09:00-12:00', '13:00-18:00'],
'tuesday' => ['09:00-12:00', '13:00-18:00'],
'wednesday' => ['09:00-12:00'],
'thursday' => ['09:00-12:00', '13:00-18:00'],
'friday' => ['09:00-12:00', '13:00-20:00'],
'saturday' => ['09:00-12:00', '13:00-16:00'],
'sunday' => [],
'exceptions' => [
function (Carbon $date) {
if ($date->isHoliday()) {
// Or use ->isObservedHoliday() and set observed holidays:
// https://github.com/kylekatarnls/business-day#setobservedholidayszone
switch ($date->getHolidayId()) {
// If the ID "christmas" exists in the selected holidays region and matches the current date:
case 'christmas':
return ['10:00-12:00'];
default:
return []; // All other holidays are closed all day long
// Here you can also pass context data:
return [
'hours' => [],
'data' => [
'reason' => 'Today is ' . $date->getHolidayName(),
],
];
}
}
// Else, typical day => use days of week settings
},
],
]);
Carbon::setHolidaysRegion('us-national');
Carbon::parse('2018-12-25 11:00')->isOpen(); // true matches custom opening hours of Christmas
Carbon::parse('2018-12-25 13:00')->isOpen(); // false
Carbon::parse('2019-01-01 11:00')->isOpen(); // false closed all day long
Carbon::parse('2019-01-02 11:00')->isOpen(); // true not an holiday in us-national region, so it's open as any common wednesday
```
[Try in the live editor](https://play.phpsandbox.io/embed/nesbot/carbon?input=%3C%3Fphp%0A%0Ause%20Carbon%5CCarbon%3B%0Ause%20Cmixin%5CBusinessTime%3B%0A%0ABusinessTime%3A%3Aenable(Carbon%3A%3Aclass%2C%20%5B%0D%0A%20%20%27monday%27%20%3D%3E%20%5B%2709%3A00-12%3A00%27%2C%20%2713%3A00-18%3A00%27%5D%2C%0D%0A%20%20%27tuesday%27%20%3D%3E%20%5B%2709%3A00-12%3A00%27%2C%20%2713%3A00-18%3A00%27%5D%2C%0D%0A%20%20%27wednesday%27%20%3D%3E%20%5B%2709%3A00-12%3A00%27%5D%2C%0D%0A%20%20%27thursday%27%20%3D%3E%20%5B%2709%3A00-12%3A00%27%2C%20%2713%3A00-18%3A00%27%5D%2C%0D%0A%20%20%27friday%27%20%3D%3E%20%5B%2709%3A00-12%3A00%27%2C%20%2713%3A00-20%3A00%27%5D%2C%0D%0A%20%20%27saturday%27%20%3D%3E%20%5B%2709%3A00-12%3A00%27%2C%20%2713%3A00-16%3A00%27%5D%2C%0D%0A%20%20%27sunday%27%20%3D%3E%20%5B%5D%2C%0D%0A%20%20%27exceptions%27%20%3D%3E%20%5B%0D%0A%20%20%20%20function%20(Carbon%20%24date)%20%7B%0D%0A%20%20%20%20%20%20if%20(%24date-%3EisHoliday())%20%7B%0D%0A%20%20%20%20%20%20%20%20%2F%2F%20Or%20use%20-%3EisObservedHoliday()%20and%20set%20observed%20holidays%3A%0D%0A%20%20%20%20%20%20%20%20%2F%2F%20https%3A%2F%2Fgithub.com%2Fkylekatarnls%2Fbusiness-day%23setobservedholidayszone%0D%0A%20%20%20%20%20%20%20%20switch%20(%24date-%3EgetHolidayId())%20%7B%0D%0A%20%20%20%20%20%20%20%20%20%20%2F%2F%20If%20the%20ID%20%22christmas%22%20exists%20in%20the%20selected%20holidays%20region%20and%20matches%20the%20current%20date%3A%0D%0A%20%20%20%20%20%20%20%20%20%20case%20%27christmas%27%3A%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20%5B%2710%3A00-12%3A00%27%5D%3B%0D%0A%20%20%20%20%20%20%20%20%20%20default%3A%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20%5B%5D%3B%20%2F%2F%20All%20other%20holidays%20are%20closed%20all%20day%20long%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20Here%20you%20can%20also%20pass%20context%20data%3A%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20%5B%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%27hours%27%20%3D%3E%20%5B%5D%2C%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%27data%27%20%20%3D%3E%20%5B%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%27reason%27%20%3D%3E%20%27Today%20is%20%27%20.%20%24date-%3EgetHolidayName()%2C%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5D%2C%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%3B%0D%0A%20%20%20%20%20%20%20%20%7D%0D%0A%20%20%20%20%20%20%7D%0D%0A%20%20%20%20%20%20%2F%2F%20Else%2C%20typical%20day%20%3D%3E%20use%20days%20of%20week%20settings%0D%0A%20%20%20%20%7D%2C%0D%0A%20%20%5D%2C%0D%0A%5D)%3B%0D%0A%0D%0ACarbon%3A%3AsetHolidaysRegion(%27us-national%27)%3B%0D%0A%0D%0Avar_dump(Carbon%3A%3Aparse(%272018-12-25%2011%3A00%27)-%3EisOpen())%3B%20%2F%2F%20true%20%20matches%20custom%20opening%20hours%20of%20Christmas%0D%0Avar_dump(Carbon%3A%3Aparse(%272018-12-25%2013%3A00%27)-%3EisOpen())%3B%20%2F%2F%20false%0D%0Avar_dump(Carbon%3A%3Aparse(%272019-01-01%2011%3A00%27)-%3EisOpen())%3B%20%2F%2F%20false%20closed%20all%20day%20long%0D%0Avar_dump(Carbon%3A%3Aparse(%272019-01-02%2011%3A00%27)-%3EisOpen())%3B%20%2F%2F%20true%20%20not%20an%20holiday%20in%20us-national%20region%2C%20so%20it%27s%20open%20as%20any%20common%20wednesday)
### Multiple business-hours
To work with multiple schedules or to use all the features without loading globally the mixin into `Carbon`,
you can create `Schedule` instances:
```php
use Carbon\CarbonImmutable;
use BusinessTime\Schedule;
$us = Schedule::create([
'monday' => ['09:00-12:00', '13:00-18:00'],
'tuesday' => ['09:00-12:00', '13:00-18:00'],
'wednesday' => ['09:00-12:00'],
'thursday' => ['09:00-12:00', '13:00-18:00'],
'friday' => ['09:00-12:00', '13:00-20:00'],
'holidaysAreClosed' => true,
'holidays' => [
'region' => 'us-ny',
'with' => [
'labor-day' => null,
'company-special-holiday' => '04-07',
],
],
]);
$fr = Schedule::create([
'monday' => ['08:00-12:00', '13:00-17:00'],
'tuesday' => ['08:00-12:00', '13:00-17:00'],
'wednesday' => ['08:00-12:00'],
'thursday' => ['08:00-12:00', '13:00-17:00'],
'friday' => ['08:00-12:00', '13:00-17:00'],
'holidaysAreClosed' => true,
'holidays' => [
'region' => 'fr-national',
'with' => [
'company-special-holiday' => '24/8',
],
],
]);
$d = CarbonImmutable::parse('2022-10-21 06:40:00');
echo $us->subOpenHours($d, 1)->format('Y-m-d H:i:s'); // 2022-10-20 17:00:00
echo $fr->subOpenHours($d, 1)->format('Y-m-d H:i:s'); // 2022-10-20 16:00:00
$d = CarbonImmutable::parse('2022-10-20 17:30:00');
var_dump($us->isOpen($d)); // true
var_dump($fr->isOpen($d)); // false
```
[Try in the live editor](https://play.phpsandbox.io/embed/nesbot/carbon?input=%3C%3Fphp%0A%0Ause%20Carbon%5CCarbonImmutable%3B%0Ause%20BusinessTime%5CSchedule%3B%0A%0A%24us%20%3D%20Schedule%3A%3Acreate(%5B%0A%20%20%20%20%27monday%27%20%20%20%20%20%20%20%20%20%20%20%20%3D%3E%20%5B%2709%3A00-12%3A00%27%2C%20%2713%3A00-18%3A00%27%5D%2C%0A%20%20%20%20%27tuesday%27%20%20%20%20%20%20%20%20%20%20%20%3D%3E%20%5B%2709%3A00-12%3A00%27%2C%20%2713%3A00-18%3A00%27%5D%2C%0A%20%20%20%20%27wednesday%27%20%20%20%20%20%20%20%20%20%3D%3E%20%5B%2709%3A00-12%3A00%27%5D%2C%0A%20%20%20%20%27thursday%27%20%20%20%20%20%20%20%20%20%20%3D%3E%20%5B%2709%3A00-12%3A00%27%2C%20%2713%3A00-18%3A00%27%5D%2C%0A%20%20%20%20%27friday%27%20%20%20%20%20%20%20%20%20%20%20%20%3D%3E%20%5B%2709%3A00-12%3A00%27%2C%20%2713%3A00-20%3A00%27%5D%2C%0A%20%20%20%20%27holidaysAreClosed%27%20%3D%3E%20true%2C%0A%20%20%20%20%27holidays%27%20%20%20%20%20%20%20%20%20%20%3D%3E%20%5B%0A%20%20%20%20%20%20%20%20%27region%27%20%3D%3E%20%27us-ny%27%2C%0A%20%20%20%20%20%20%20%20%27with%27%20%20%20%3D%3E%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%27labor-day%27%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3D%3E%20null%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%27company-special-holiday%27%20%3D%3E%20%2704-07%27%2C%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%5D%2C%0A%5D)%3B%0A%0A%24fr%20%3D%20Schedule%3A%3Acreate(%5B%0A%20%20%20%20%27monday%27%20%20%20%20%20%20%20%20%20%20%20%20%3D%3E%20%5B%2708%3A00-12%3A00%27%2C%20%2713%3A00-17%3A00%27%5D%2C%0A%20%20%20%20%27tuesday%27%20%20%20%20%20%20%20%20%20%20%20%3D%3E%20%5B%2708%3A00-12%3A00%27%2C%20%2713%3A00-17%3A00%27%5D%2C%0A%20%20%20%20%27wednesday%27%20%20%20%20%20%20%20%20%20%3D%3E%20%5B%2708%3A00-12%3A00%27%5D%2C%0A%20%20%20%20%27thursday%27%20%20%20%20%20%20%20%20%20%20%3D%3E%20%5B%2708%3A00-12%3A00%27%2C%20%2713%3A00-17%3A00%27%5D%2C%0A%20%20%20%20%27friday%27%20%20%20%20%20%20%20%20%20%20%20%20%3D%3E%20%5B%2708%3A00-12%3A00%27%2C%20%2713%3A00-17%3A00%27%5D%2C%0A%20%20%20%20%27holidaysAreClosed%27%20%3D%3E%20true%2C%0A%20%20%20%20%27holidays%27%20%20%20%20%20%20%20%20%20%20%3D%3E%20%5B%0A%20%20%20%20%20%20%20%20%27region%27%20%3D%3E%20%27fr-national%27%2C%0A%20%20%20%20%20%20%20%20%27with%27%20%20%20%3D%3E%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%27company-special-holiday%27%20%3D%3E%20%2724%2F8%27%2C%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%5D%2C%0A%5D)%3B%0A%0A%24d%20%3D%20CarbonImmutable%3A%3Aparse(%272022-10-21%2006%3A40%3A00%27)%3B%0Avar_dump(%24us-%3EsubOpenHours(%24d%2C%201)-%3Eformat(%27Y-m-d%20H%3Ai%3As%27))%3B%0Avar_dump(%24fr-%3EsubOpenHours(%24d%2C%201)-%3Eformat(%27Y-m-d%20H%3Ai%3As%27))%3B%0A%0A%24d%20%3D%20CarbonImmutable%3A%3Aparse(%272022-10-20%2017%3A30%3A00%27)%3B%0Avar_dump(%24us-%3EisOpen(%24d))%3B%0Avar_dump(%24fr-%3EisOpen(%24d))%3B)
### isOpenOn
Allows to know if the business is usually on open on a given day.
```php
Carbon::isOpenOn('monday') // Returns true if there is at least 1 open range of
// hours set for Monday (in the regular schedule)
// Carbon::MONDAY would also works
$date->isOpenOn('monday') // Same as above but using local config of $date
Carbon::isOpenOn('2020-09-03') // Returns true if there is at least 1 open range of
// hours set for the date 2020-09-03 (considering both
// the regular schedule and the exceptions)
```
### isClosedOn
Opposite of isOpenOn
```php
Carbon::isClosedOn('monday')
Carbon::isClosedOn('2020-09-03')
$date->isClosedOn('monday')
```
### isOpen
Allows to know if the business is usually on open at a given moment.
```php
Carbon::isOpen() // returns true if the business is now open
$carbonDate->isOpen() // returns true if the business is open at the current date and time
if (Carbon::isOpen()) {
$closingTime = Carbon::nextClose()->isoFormat('LT');
echo "It's now open and until $closingTime.";
}
```
### isClosed
Opposite of isOpen
```php
Carbon::isClosed() // returns true if the business is now closed
$carbonDate->isClosed() // returns true if the business is closed at the current date and time
if (Carbon::isClosed()) {
$openingTime = Carbon::nextClose()->calendar();
echo "It's now closed and will re-open $openingTime.";
}
```
### nextOpen
Go to next open-business time.
```php
Carbon::nextOpen() // go to next open time from now
$carbonDate->nextOpen() // go to next open time from $carbonDate
```
### nextClose
Go to next closed-business time.
```php
Carbon::nextClose() // go to next close time from now
$carbonDate->nextClose() // go to next close time from $carbonDate
```
### previousOpen
Go to previous open-business time.
```php
Carbon::previousOpen() // go to previous open time from now
$carbonDate->previousOpen() // go to previous open time from $carbonDate
```
### previousClose
Go to previous closed-business time.
```php
Carbon::previousClose() // go to previous close time from now
$carbonDate->previousClose() // go to previous close time from $carbonDate
```
### addOpenTime
Add the given interval of time taking only into account open ranges of hours.
For instance, if the current day has `["09:00-12:00", "13:30-17:00"]` open range
of hours, adding 2 open hours when it's 11am will actually add 3 hours and 30
minutes (step over the midday break: an hour and a half) and so set the time to
14:30.
```php
Carbon::addOpenTime('2 hours and 30 minutes') // add 2 hours and 30 minutes to now
$carbonDate->addOpenTime('2 hours and 30 minutes') // add 2 hours and 30 minutes to $carbonDate
// Can be used with the same interval definitions than add/sub methods of Carbon
$carbonDate->addOpenTime(235, 'seconds')
$carbonDate->addOpenTime(new DateInterval('PT1H23M45S'))
$carbonDate->addOpenTime(CarbonInterval::hours(3)->minutes(20))
$carbonDate->addOpenTime('2 hours and 30 minutes', BusinessTime::HOLIDAYS_ARE_CLOSED)
// add 2 hours and 30 minutes considering holidays as closed (equivalent than using 'holidaysAreClosed' => true option)
```
### addOpenHours
Add the given number of hours taking only into account open ranges of hours.
```php
Carbon::addOpenHours(3) // add 3 open hours to now
$carbonDate->addOpenHours(3) // add 3 open hours to $carbonDate
$carbonDate->addOpenHours(3, BusinessTime::HOLIDAYS_ARE_CLOSED)
// add 3 open hours considering holidays as closed (equivalent than using 'holidaysAreClosed' => true option)
```
### addOpenMinutes
Add the given number of minutes taking only into account open ranges of hours.
```php
Carbon::addOpenMinutes(3) // add 3 open minutes to now
$carbonDate->addOpenMinutes(3) // add 3 open minutes to $carbonDate
$carbonDate->addOpenMinutes(3, BusinessTime::HOLIDAYS_ARE_CLOSED)
// add 3 open minutes considering holidays as closed (equivalent than using 'holidaysAreClosed' => true option)
```
### addClosedTime
Add the given interval of time taking only into account closed ranges of hours.
```php
Carbon::addClosedTime('2 hours and 30 minutes') // add 2 hours and 30 minutes to now
$carbonDate->addClosedTime('2 hours and 30 minutes') // add 2 hours and 30 minutes to $carbonDate
// Can be used with the same interval definitions than add/sub methods of Carbon
$carbonDate->addClosedTime(235, 'seconds')
$carbonDate->addClosedTime(new DateInterval('PT1H23M45S'))
$carbonDate->addClosedTime(CarbonInterval::hours(3)->minutes(20))
$carbonDate->addClosedTime('2 hours and 30 minutes', BusinessTime::HOLIDAYS_ARE_CLOSED)
// add 2 hours and 30 minutes considering holidays as closed (equivalent than using 'holidaysAreClosed' => true option)
```
### addClosedHours
Add the given number of hours taking only into account closed ranges of hours.
```php
Carbon::addClosedHours(3) // add 3 closed hours to now
$carbonDate->addClosedHours(3) // add 3 closed hours to $carbonDate
$carbonDate->addClosedHours(3, BusinessTime::HOLIDAYS_ARE_CLOSED)
// add 3 closed hours considering holidays as closed (equivalent than using 'holidaysAreClosed' => true option)
```
### addClosedMinutes
Add the given number of minutes taking only into account closed ranges of hours.
```php
Carbon::addClosedMinutes(3) // add 3 closed minutes to now
$carbonDate->addClosedMinutes(3) // add 3 closed minutes to $carbonDate
$carbonDate->addClosedMinutes(3, BusinessTime::HOLIDAYS_ARE_CLOSED)
// add 3 closed minutes considering holidays as closed (equivalent than using 'holidaysAreClosed' => true option)
```
### subOpenTime
Subtract the given interval of time taking only into account open ranges of hours.
```php
Carbon::subOpenTime('2 hours and 30 minutes') // subtract 2 hours and 30 minutes to now
$carbonDate->subOpenTime('2 hours and 30 minutes') // subtract 2 hours and 30 minutes to $carbonDate
// Can be used with the same interval definitions than add/sub methods of Carbon
$carbonDate->subOpenTime(235, 'seconds')
$carbonDate->subOpenTime(new DateInterval('PT1H23M45S'))
$carbonDate->subOpenTime(CarbonInterval::hours(3)->minutes(20))
$carbonDate->subOpenTime('2 hours and 30 minutes', BusinessTime::HOLIDAYS_ARE_CLOSED)
// subtract 2 hours and 30 minutes considering holidays as closed (equivalent than using 'holidaysAreClosed' => true option)
```
### subOpenHours
Subtract the given number of hours taking only into account open ranges of hours.
```php
Carbon::subOpenHours(3) // subtract 3 open hours to now
$carbonDate->subOpenHours(3) // subtract 3 open hours to $carbonDate
$carbonDate->subOpenHours(3, BusinessTime::HOLIDAYS_ARE_CLOSED)
// subtract 3 open hours considering holidays as closed (equivalent than using 'holidaysAreClosed' => true option)
```
### subOpenMinutes
Subtract the given number of minutes taking only into account open ranges of hours.
```php
Carbon::subOpenMinutes(3) // subtract 3 open minutes to now
$carbonDate->subOpenMinutes(3) // subtract 3 open minutes to $carbonDate
$carbonDate->subOpenMinutes(3, BusinessTime::HOLIDAYS_ARE_CLOSED)
// subtract 3 open minutes considering holidays as closed (equivalent than using 'holidaysAreClosed' => true option)
```
### subClosedTime
Subtract the given interval of time taking only into account closed ranges of hours.
```php
Carbon::subClosedTime('2 hours and 30 minutes') // subtract 2 hours and 30 minutes to now
$carbonDate->subClosedTime('2 hours and 30 minutes') // subtract 2 hours and 30 minutes to $carbonDate
// Can be used with the same interval definitions than add/sub methods of Carbon
$carbonDate->subClosedTime(235, 'seconds')
$carbonDate->subClosedTime(new DateInterval('PT1H23M45S'))
$carbonDate->subClosedTime(CarbonInterval::hours(3)->minutes(20))
$carbonDate->subClosedTime('2 hours and 30 minutes', BusinessTime::HOLIDAYS_ARE_CLOSED)
// subtract 2 hours and 30 minutes considering holidays as closed (equivalent than using 'holidaysAreClosed' => true option)
```
### subClosedHours
Subtract the given number of hours taking only into account closed ranges of hours.
```php
Carbon::subClosedHours(3) // subtract 3 closed hours to now
$carbonDate->subClosedHours(3) // subtract 3 closed hours to $carbonDate
$carbonDate->subClosedHours(3, BusinessTime::HOLIDAYS_ARE_CLOSED)
// subtract 3 closed hours considering holidays as closed (equivalent than using 'holidaysAreClosed' => true option)
```
### subClosedMinutes
Subtract the given number of minutes taking only into account closed ranges of hours.
```php
Carbon::subClosedMinutes(3) // subtract 3 closed minutes to now
$carbonDate->subClosedMinutes(3) // subtract 3 closed minutes to $carbonDate
$carbonDate->subClosedMinutes(3, BusinessTime::HOLIDAYS_ARE_CLOSED)
// subtract 3 closed minutes considering holidays as closed (equivalent than using 'holidaysAreClosed' => true option)
```
### getCurrentDayOpeningHours
Returns the opening hours current day settings (first matching exception or else current weekday settings).
```php
BusinessTime::enable(Carbon::class, [
'monday' => [
'data' => [
'remarks' => 'Extra evening on Monday',
],
'hours' => [
'09:00-12:00',
'13:00-18:00',
'19:00-20:00',
]
],
// ...
]);
$todayRanges = Carbon::getCurrentDayOpeningHours(); // Equivalent to Carbon::now()->getCurrentDayOpeningHours()
// You can also get opening hours of any other day: Carbon::parse('2018-01-16')->getCurrentDayOpeningHours()
echo '<h1>Today office open hours</h1>';
$data = $todayRanges->getData();
if (is_array($data) && isset($data['remarks'])) {
echo '<p><em>' . $data['remarks'] . '</em></p>';
}
// $todayRanges is iterable on every time range of the day.
foreach ($todayRanges as $range) {
// TimeRange object have start, end and data properties but can also be implicitly converted as strings:
echo '<p><time>' . $range . '</time></p>';
}
// $todayRanges can also be directly dumped as string
echo '<p>' . $todayRanges . '</p>';
```
[Try in the live editor](https://play.phpsandbox.io/embed/nesbot/carbon?input=%3C%3Fphp%0A%0Ause%20Carbon%5CCarbon%3B%0Ause%20Cmixin%5CBusinessTime%3B%0A%0ABusinessTime%3A%3Aenable(Carbon%3A%3Aclass%2C%20%5B%0D%0A%20%20%27monday%27%20%3D%3E%20%5B%0D%0A%20%20%20%20%27data%27%20%3D%3E%20%5B%0D%0A%20%20%20%20%20%20%27remarks%27%20%3D%3E%20%27Extra%20evening%20on%20Monday%27%2C%0D%0A%20%20%20%20%5D%2C%0D%0A%20%20%20%20%27hours%27%20%3D%3E%20%5B%0D%0A%20%20%20%20%20%20%20%20%2709%3A00-12%3A00%27%2C%0D%0A%20%20%20%20%20%20%20%20%2713%3A00-18%3A00%27%2C%0D%0A%20%20%20%20%20%20%20%20%2719%3A00-20%3A00%27%2C%0D%0A%20%20%20%20%5D%0D%0A%20%20%5D%2C%0D%0A%20%20%2F%2F%20...%0D%0A%5D)%3B%0D%0A%0D%0A%24todayRanges%20%3D%20Carbon%3A%3AgetCurrentDayOpeningHours()%3B%20%2F%2F%20Equivalent%20to%20Carbon%3A%3Anow()-%3EgetCurrentDayOpeningHours()%0D%0A%2F%2F%20You%20can%20also%20get%20opening%20hours%20of%20any%20other%20day%3A%20Carbon%3A%3Aparse(%272018-01-16%27)-%3EgetCurrentDayOpeningHours()%0D%0A%0D%0Aecho%20%22%3Ch1%3EToday%20office%20open%20hours%3C%2Fh1%3E%5Cn%22%3B%0D%0A%24data%20%3D%20%24todayRanges-%3EgetData()%3B%0D%0Aif%20(is_array(%24data)%20%26%26%20isset(%24data%5B%27remarks%27%5D))%20%7B%0D%0A%20%20echo%20%22%3Cp%3E%3Cem%3E%7B%24data%5B%27remarks%27%5D%7D%3C%2Fem%3E%3C%2Fp%3E%5Cn%22%3B%0D%0A%7D%0D%0A%2F%2F%20%24todayRanges%20is%20iterable%20on%20every%20time%20range%20of%20the%20day.%0D%0Aforeach%20(%24todayRanges%20as%20%24range)%20%7B%0D%0A%20%20%2F%2F%20TimeRange%20object%20have%20start%2C%20end%20and%20data%20properties%20but%20can%20also%20be%20implicitly%20converted%20as%20strings%3A%0D%0A%20%20echo%20%22%3Cp%3E%3Ctime%3E%24range%3C%2Ftime%3E%3C%2Fp%3E%5Cn%22%3B%0D%0A%7D%0D%0A%2F%2F%20%24todayRanges%20can%20also%20be%20directly%20dumped%20as%20string%0D%0Aecho%20%22%3Cp%3E%24todayRanges%3C%2Fp%3E%5Cn%22%3B%0D%0A)
### isBusinessOpen / isOpenExcludingHolidays
Equivalent to `isOpen` when `'holidaysAreClosed'` is set to `true`.
Allows to know if the business is usually on open at a given moment and not an holidays. But you also can handle holidays
with a dedicated exception for a finest setting. [See Holidays section](#Holidays)
```php
Carbon::setHolidaysRegion('us-national');
Carbon::isBusinessOpen() // returns true if the business is now open and not an holiday
$carbonDate->isBusinessOpen() // returns true if the business is open and not an holiday at the current date and time
```
### isBusinessClosed / isClosedIncludingHolidays
Equivalent to `isClosed` when `'holidaysAreClosed'` is set to `true`.
Opposite of [isOpenExcludingHolidays](#isOpenExcludingHolidays)
```php
Carbon::setHolidaysRegion('us-national');
Carbon::isBusinessClosed() // returns true if the business is now closed or an holiday
$carbonDate->isBusinessClosed() // returns true if the business is closed or an holiday at the current date and time
```
### nextBusinessOpen / nextOpenExcludingHolidays
Equivalent to `nextOpen` when `'holidaysAreClosed'` is set to `true`.
Go to next open time (considering all holidays as closed time). But prefer to handle holidays with a dedicated
exception for a finest setting. [See Holidays section](#Holidays)
```php
Carbon::setHolidaysRegion('us-national');
echo Carbon::nextBusinessOpen();
echo $carbonDate->nextBusinessOpen();
```
### nextBusinessClose / nextCloseIncludingHolidays
Equivalent to `nextClose` when `'holidaysAreClosed'` is set to `true`.
Go to next closed time (considering all holidays as closed time). But prefer to handle holidays with a dedicated
exception for a finest setting. [See Holidays section](#Holidays)
```php
Carbon::setHolidaysRegion('us-national');
echo Carbon::nextBusinessClose();
echo $carbonDate->nextBusinessClose();
```
### previousBusinessOpen / previousOpenExcludingHolidays
Equivalent to `previousOpen` when `'holidaysAreClosed'` is set to `true`.
Go to previous open time (considering all holidays as closed time). But prefer to handle holidays with a dedicated
exception for a finest setting. [See Holidays section](#Holidays)
```php
Carbon::setHolidaysRegion('us-national');
echo Carbon::previousBusinessOpen();
echo $carbonDate->previousBusinessOpen();
```
### previousBusinessClose / previousCloseIncludingHolidays
Equivalent to `previousClose` when `'holidaysAreClosed'` is set to `true`.
Go to previous closed time (considering all holidays as closed time). But prefer to handle holidays with a dedicated
exception for a finest setting. [See Holidays section](#Holidays)
```php
Carbon::setHolidaysRegion('us-national');
echo Carbon::previousBusinessClose();
echo $carbonDate->previousBusinessClose();
```
### currentOr*
Methods starting with `currentOr` are followed by:
- a **time-direction**: `Next` / `Previous`
- optionally `Business` (meaning holidays are automatically considered as closed no matter the `'holidaysAreClosed'` is true or false)
- a **state** `Open` / `Close`
All `currentOr*` methods return the current date-time if it's in the **state**
(see above), else they return the first date-time (next or previous according
to the given **time-direction**) where a state change to be the chosen **state**
(open / close).
Note: `BusinessOpen` can also be written explicitly as `OpenExcludingHolidays`
and `BusinessClose` as `CloseIncludingHolidays`.
### openOr*
Methods starting with `openOr` are followed by:
- a **time-direction**: `Next` / `Previous`
- optionally `Business` (meaning holidays are automatically considered as closed no matter the `'holidaysAreClosed'` is true or false)
- `Close` (for open-or-next/previous-open, [see currentOr*](#currentOr*))
All `openOr*` methods return the current date-time if it's open, else
they return the first date-time (next or previous according
to the given **time-direction**) the business close.
Note: `BusinessClose` can also be written explicitly as `CloseIncludingHolidays`.
### closedOr*
Methods starting with `closedOr` are followed by:
- a **time-direction**: `Next` / `Previous`
- optionally `Business` (meaning holidays are automatically considered as closed no matter the `'holidaysAreClosed'` is true or false)
- `Open` (for closed-or-next/previous-closed, [see currentOr*](#currentOr*))
All `closedOr*` methods return the current date-time if it's closed, else
they return the first date-time (next or previous according
to the given **time-direction**) the business open.
Note: `BusinessOpen` can also be written explicitly as `OpenExcludingHolidays`.
### diffAsBusinessInterval
Return open/closed interval of time between 2 dates/times.
```php
$start = '2021-04-05 21:00';
$end = '2021-04-05 10:00:00'; // can be date instance, a string representation or a timestamp
$options = 0;
$interval = Carbon::parse($start)->diffAsBusinessInterval($end, $options);
```
The returned `$interval` is an instance of `CarbonInterval`. See https://carbon.nesbot.com/docs/#api-interval
Options are piped flags among:
- `BusinessTime::CLOSED_TIME`:
return the interval of for closed time,
return open time else
- `BusinessTime::RELATIVE_DIFF`:
return negative value if start is before end
- `BusinessTime::HOLIDAYS_ARE_CLOSED`:
automatically consider holidays as closed
- `BusinessTime::USE_DAYLIGHT_SAVING_TIME`:
use DST native PHP diff result instead of real time (timestamp)
Examples:
```php
Carbon::parse($start)->diffAsBusinessInterval($end, BusinessTime::CLOSED_TIME | BusinessTime::HOLIDAYS_ARE_CLOSED | BusinessTime::RELATIVE_DIFF);
// - return relative total closed time between $start and $end
// - considering holidays as closed
// - it will be negative if $start < $end
```
### diffInBusinessUnit
Return open/closed time in the given unit between 2 dates/times.
```php
Carbon::parse('2021-04-05 10:00')->diffInBusinessUnit('hour', '2021-04-05 21:00:00', $options)
```
The first parameter is the unit singular or plural, in any case. The 2 other parameters are
the same as in [`diffAsBusinessInterval`](#diffAsBusinessInterval)
### diffInBusinessHours
Return open/closed number of hours (as a floating number) in the given unit between 2 dates/times.
```php
Carbon::parse('2021-04-05 07:00')->diffInBusinessHours('2021-04-05 10:30', $options)
// return 2.5 if business is open between 8:00 and 12:00
```
The 2 parameters are
the same as in [`diffAsBusinessInterval`](#diffAsBusinessInterval)
### diffInBusinessMinutes
Return open/closed number of minutes (as a floating number) in the given unit between 2 dates/times.
```php
Carbon::parse('2021-04-05 07:00')->diffInBusinessMinutes('2021-04-05 10:30', $options)
```
The 2 parameters are
the same as in [`diffAsBusinessInterval`](#diffAsBusinessInterval)
### diffInBusinessSeconds
Return open/closed number of seconds (as a floating number) in the given unit between 2 dates/times.
```php
Carbon::parse('2021-04-05 07:00')->diffInBusinessSeconds('2021-04-05 10:30', $options)
```
The 2 parameters are
the same as in [`diffAsBusinessInterval`](#diffAsBusinessInterval)
### getCurrentOpenTimeRanges
Get list of ranges that contain the current date-time.
```php
foreach (Carbon::getCurrentOpenTimeRanges() as $timeRange) {
echo 'From: '.$timeRange->start().' to '.$timeRange->end()."\n";
}
foreach ($carbonDate->getCurrentOpenTimeRanges() as $timeRange) {
echo 'From: '.$timeRange->start().' to '.$timeRange->end()."\n";
}
```
### getCurrentOpenTimeRange
Get the first range that contain the current date-time.
```php
$timeRange = Carbon::getCurrentOpenTimeRange();
if ($timeRange) {
echo 'From: '.$timeRange->start().' to '.$timeRange->end()."\n";
}
$timeRange = $carbonDate->getCurrentOpenTimeRange();
if ($timeRange) {
echo 'From: '.$timeRange->start().' to '.$timeRange->end()."\n";
}
```
### getCurrentOpenTimePeriod
Get the first range that contain the current date-time as a `CarbonPeriod`.
While [`getCurrentOpenTimeRange()`](#getCurrentOpenTimeRange) return a `TimeRange`
instance which only handle the time part, `getCurrentOpenTimePeriod()` return
a `CarbonPeriod` instance so you get whole date and time of the start and end.
This is mostly useful when having ranges overflowing midnight:
```php
BusinessTime::enable(Carbon::class, [
'overflow' => true,
'monday' => ['22:00-03:00'],
]);
$timeRange = Carbon::getCurrentOpenTimePeriod(); // with current date and time
$timeRange = Carbon::parse('Monday 23:34')->getCurrentOpenTimePeriod();
echo $timeRange->getStartDate()->format('D G\h'); // Mon 22h
echo $timeRange->getEndDate()->format('D G\h'); // Tue 3h
```
### getCurrentOpenTimeRangeStart
Get the start of the current open time range (if open, holidays ignored).
```php
$start = Carbon::getCurrentOpenTimeRangeStart();
if ($start) {
echo 'Open since '.$start->format('l H:i')."\n";
} else {
echo "Closed\n";
}
$start = $carbonDate->getCurrentOpenTimeRangeStart();
if ($start) {
echo 'Open since '.$start->format('l H:i')."\n";
} else {
echo "Closed\n";
}
```
### getCurrentOpenTimeRangeEnd
Get the end of the current open time range (if open, holidays ignored).
```php
$end = Carbon::getCurrentOpenTimeRangeEnd();
if ($end) {
echo 'Will close at '.$start->format('l H:i')."\n";
} else {
echo "Closed\n";
}
$end = $carbonDate->getCurrentOpenTimeRangeEnd();
if ($end) {
echo 'Will close at '.$start->format('l H:i')."\n";
} else {
echo "Closed\n";
}
```
### getCurrentBusinessTimeRangeStart
Get the start of the current open time range (if open and not holiday).
```php
$start = Carbon::getCurrentBusinessTimeRangeStart();
if ($start) {
echo 'Open since '.$start->format('l H:i')."\n";
} else {
echo "Closed\n";
}
$start = $carbonDate->getCurrentBusinessTimeRangeStart();
if ($start) {
echo 'Open since '.$start->format('l H:i')."\n";
} else {
echo "Closed\n";
}
```
### getCurrentBusinessTimeRangeEnd
Get the end of the current open time range (if open and not holiday).
```php
$end = Carbon::getCurrentBusinessTimeRangeEnd();
if ($end) {
echo 'Will close at '.$start->format('l H:i')."\n";
} else {
echo "Closed\n";
}
$end = $carbonDate->getCurrentBusinessTimeRangeEnd();
if ($end) {
echo 'Will close at '.$start->format('l H:i')."\n";
} else {
echo "Closed\n";
}
```
### Laravel
To enable business-time globally in Laravel, set default openning hours and holidays settings in the config file
**config/carbon.php** (create this file if it does not exist yet):
```php
<?php return [
'opening-hours' => [
'monday' => ['09:00-12:00', '13:00-18:00'],
'tuesday' => ['09:00-12:00', '13:00-18:00'],
'wednesday' => ['09:00-12:00'],
'thursday' => ['09:00-12:00', '13:00-18:00'],
'friday' => ['09:00-12:00', '13:00-20:00'],
'saturday' => ['09:00-12:00', '13:00-16:00'],
'sunday' => [],
'exceptions' => [
'2016-11-11' => ['09:00-12:00'],
'2016-12-25' => [],
'01-01' => [], // Recurring on each 1st of january
'12-25' => ['09:00-12:00'], // Recurring on each 25th of december
],
],
'holidaysAreClosed' => true,
'holidays' => [
'region' => 'us',
'with' => [
'boss-birthday' => '09-26',
'last-monday' => '= last Monday of October',
],
],
];
```
If you use Laravel but don't plan to use this global config to enable business-time,
you may remove it from auto-discovery using:
```json
"extra": {
"laravel": {
"dont-discover": [
"cmixin/business-day",
"cmixin/business-time"
]
}
},
```
### Note about timezones
When you set an holidays region, it does not change the timezone, so if January 1st is an holiday,
`->isHoliday()` returns `true` from `Carbon::parse('2010-01-01 00:00:00.000000)` to
`Carbon::parse('2010-01-01 23:59:59.999999)` no matter the timezone you set for those `Carbon`
instance.
If you want to know if it's holiday or business day in somewhere else in the world, you have
to convert it:
```php
Carbon::parse('2010-01-01 02:30', 'Europe/Paris')->setTimezone('America/Toronto')->isHoliday() // false
Carbon::parse('2010-01-01 12:30', 'Europe/Paris')->setTimezone('America/Toronto')->isHoliday() // true
```
The same goes for opening hours, let's say you want to know if you call center based in Toronto is
available from Tokyo at a given hour (Tokyo timezone), you would get something like:
```php
// Opening hours in Toronto
BusinessTime::enable(Carbon::class, [
'monday' => ['08:00-20:00'],
'tuesday' => ['08:00-20:00'],
'wednesday' => ['08:00-20:00'],
'thursday' => ['08:00-20:00'],
]);
// Can I call the hotline if it's Tuesday 19:30 in Tokyo? > No
Carbon::parse('2019-03-05 20:30', 'Asia/Tokyo')->setTimezone('America/Toronto')->isOpen() // false
// Can I call the hotline if it's Tuesday 22:30 in Tokyo? > Yes
Carbon::parse('2019-03-05 22:30', 'Asia/Tokyo')->setTimezone('America/Toronto')->isOpen() // true
```
[Try in the live editor](https://play.phpsandbox.io/embed/nesbot/carbon?input=%3C%3Fphp%0A%0Ause%20Carbon%5CCarbon%3B%0Ause%20Cmixin%5CBusinessTime%3B%0A%0A%2F%2F%20Opening%20hours%20in%20Toronto%0D%0ABusinessTime%3A%3Aenable(Carbon%3A%3Aclass%2C%20%5B%0D%0A%20%20%27monday%27%20%3D%3E%20%5B%2708%3A00-20%3A00%27%5D%2C%0D%0A%20%20%27tuesday%27%20%3D%3E%20%5B%2708%3A00-20%3A00%27%5D%2C%0D%0A%20%20%27wednesday%27%20%3D%3E%20%5B%2708%3A00-20%3A00%27%5D%2C%0D%0A%20%20%27thursday%27%20%3D%3E%20%5B%2708%3A00-20%3A00%27%5D%2C%0D%0A%5D)%3B%0D%0A%2F%2F%20Can%20I%20call%20the%20hotline%20if%20it%27s%20Tuesday%2019%3A30%20in%20Tokyo%3F%20%3E%20No%0D%0Avar_dump(Carbon%3A%3Aparse(%272019-03-05%2020%3A30%27%2C%20%27Asia%2FTokyo%27)-%3EsetTimezone(%27America%2FToronto%27)-%3EisOpen())%3B%0D%0A%2F%2F%20Can%20I%20call%20the%20hotline%20if%20it%27s%20Tuesday%2022%3A30%20in%20Tokyo%3F%20%3E%20Yes%0D%0Avar_dump(Carbon%3A%3Aparse(%272019-03-05%2022%3A30%27%2C%20%27Asia%2FTokyo%27)-%3EsetTimezone(%27America%2FToronto%27)-%3EisOpen())%3B%0D%0A%0D%0A%2F%2F%20Is%20it%20now%20open%20in%20Toronto%3F%20%3E%20It%20depends%20on%20when%20you%20will%20run%20this%20code%0D%0Avar_dump(Carbon%3A%3Anow(%27America%2FToronto%27)-%3EisOpen())%3B%0D%0A)