The Carbon
class for dates and times is a part of Laravel by default, but there's also a less-known class, CarbonPeriod
. It can help generate the ARRAY of datetimes, often useful for report tables and calendars. In this tutorial, let's look at the 7 most practical examples of CarbonPeriod
.
What if you need an array list of working hours limited by start/end times?
Look at this CarbonInterval
snippet:
use Carbon\Carbon;use Carbon\CarbonPeriod; $startPeriod = Carbon::parse('9:00');$endPeriod = Carbon::parse('18:00'); $period = CarbonPeriod::create($startPeriod, '1 hour', $endPeriod);$hours = []; foreach ($period as $date) { $hours[] = $date->format('H:i');}
Result:
array:10 [ 0 => "09:00" 1 => "10:00" 2 => "11:00" 3 => "12:00" 4 => "13:00" 5 => "14:00" 6 => "15:00" 7 => "16:00" 8 => "17:00" 9 => "18:00"]
As you can see, each CarbonPeriod
element is a Carbon
class instance that you can format however you want.
But it's not necessarily about the hours only. You can also change the period to smaller intervals, like "45 minutes":
$period = CarbonPeriod::create($startPeriod, '45 minutes', $endPeriod);
Result:
array:13 [ 0 => "09:00" 1 => "09:45" 2 => "10:30" 3 => "11:15" 4 => "12:00" 5 => "12:45" 6 => "13:30" 7 => "14:15" 8 => "15:00" 9 => "15:45" 10 => "16:30" 11 => "17:15" 12 => "18:00"]
You can also skip the first or last entry using the excludeStartDate
or excludeEndDate
methods.
$period = CarbonPeriod::create($startPeriod, '45 minutes', $endPeriod) ->excludeStartDate() ->excludeEndDate();
array:11 0 => "09:45" 1 => "10:30" 2 => "11:15" 3 => "12:00" 4 => "12:45" 5 => "13:30" 6 => "14:15" 7 => "15:00" 8 => "15:45" 9 => "16:30" 10 => "17:15"]
Or change representation to a 12-hour format:
foreach ($period as $date) { $hours[] = $date->format('h:i A');}
array:11 [ 0 => "09:45 AM" 1 => "10:30 AM" 2 => "11:15 AM" 3 => "12:00 PM" 4 => "12:45 PM" 5 => "01:30 PM" 6 => "02:15 PM" 7 => "03:00 PM" 8 => "03:45 PM" 9 => "04:30 PM" 10 => "05:15 PM"]
Let's say you want to schedule appointments, but have open slots only for Mondays and plan to do so for the current month.
use Carbon\CarbonPeriod; $period = CarbonPeriod::between(now()->startOfMonth(), now()->endOfMonth()) ->filter(fn ($date) => $date->isMonday());$dates = []; foreach ($period as $date) { $dates[] = $date->format('M j, l, Y');}
Result:
array:5 [ 0 => "Jul 3, Monday, 2023" 1 => "Jul 10, Monday, 2023" 2 => "Jul 17, Monday, 2023" 3 => "Jul 24, Monday, 2023" 4 => "Jul 31, Monday, 2023"]
As you can see, you can initialize the period with the ::between($start, $end)
method and then add extra filters.
Of course, there are many more functions, not only ->isMonday()
.
Looking at the Carbon documentation, we can find these:
$dt->isWeekday();$dt->isWeekend();$dt->isTuesday();// ...$dt->isSunday(); $dt->isLastOfMonth();$dt->isYesterday();$dt->isToday();$dt->isTomorrow();$dt->isNextWeek();$dt->isLastWeek(); // ... and more
What if you want to display a list of weeks as table columns? Each week has its number, so here's the example code for the week number with the start and end date of the week beginning on 2023-06-15
to 2023-07-15
.
use Carbon\CarbonPeriod; $period = CarbonPeriod::create('2023-06-15', '1 week', '2023-07-15');$weeks = [];$format = 'F j, Y'; foreach ($period as $date) { $weeks[] = [ 'week' => $date->format('W'), 'start' => $date->startOfWeek()->format($format), 'end' => $date->endOfWeek()->format($format), ];}
Result:
array:5 [ 0 => array:3 [ "week" => "24" "start" => "June 12, 2023" "end" => "June 18, 2023" ] 1 => array:3 [ "week" => "25" "start" => "June 19, 2023" "end" => "June 25, 2023" ] 2 => array:3 [ "week" => "26" "start" => "June 26, 2023" "end" => "July 2, 2023" ] 3 => array:3 [ "week" => "27" "start" => "July 3, 2023" "end" => "July 9, 2023" ] 4 => array:3 [ "week" => "28" "start" => "July 10, 2023" "end" => "July 16, 2023" ]]
Periods can be created from Model attributes when cast to Carbon instances. There's a shortcut method toPeriod()
to make a CarbonPeriod
instance.
For example, the recipient must pick up an order from the post office and choose a date. For this task, a person has five days to choose from, including the day it is delivered.
class Order extends Model{ protected $fillable = ['shipped_at']; protected $casts = [ 'shipped_at' => 'date', ];}
use App\Models\Order; $order = Order::create(['shipped_at' => '2023-07-13']);$period = $order->shipped_at ->toPeriod($order->shipped_at->copy()->addDays(5), '1 day') ->excludeEndDate();$dates = []; foreach ($period as $date) { $dates[] = $date->format('Y-m-d');}
Result:
array:5 [ 0 => "2023-07-13" 1 => "2023-07-14" 2 => "2023-07-15" 3 => "2023-07-16" 4 => "2023-07-17"]
But what if we want to skip weekends and have seven workdays to pick up orders? Three things to add here.
$order->shipped_at->addMonth()
.setRecurrences()
limits that.filter()
dates by weekdays.use App\Models\Order; $order = Order::create(['shipped_at' => '2023-07-13']);$period = $order->shipped_at ->toPeriod($order->shipped_at->copy()->addMonth(), '1 day') ->setRecurrences(7) ->filter(fn ($date) => $date->isWeekday()); $dates = []; foreach ($period as $date) { $dates[] = $date->format('Y-m-d');}
Result:
array:7 [ // test.php:18 0 => "2023-07-13" 1 => "2023-07-14" 2 => "2023-07-17" 3 => "2023-07-18" 4 => "2023-07-19" 5 => "2023-07-20" 6 => "2023-07-21"]
We have an employee list and know their vacation start and end dates. We need to list all employees who will have a vacation in the next 30 days so we can plan our tasks accordingly.
Our example dataset:
Employee::insert([ ['name' => 'Helmer Wilkinson', 'vacation_start' => '2023-01-01', 'vacation_end' => '2023-02-01'], ['name' => 'Kaya Lowe', 'vacation_start' => '2023-07-05', 'vacation_end' => '2023-07-14'], ['name' => 'Royal Yundt', 'vacation_start' => '2023-07-20', 'vacation_end' => '2023-08-01'], ['name' => 'Willow Stark', 'vacation_start' => '2023-09-01', 'vacation_end' => '2023-09-15'],]);
We can pass another period to the overlaps()
method. It can also accept string values $period->overlaps('2023-06-23', '2023-07-04')
and returns a boolean.
$startPeriod = Carbon::parse('2023-07-13');$period = CarbonPeriod::create($startPeriod, $startPeriod->copy()->addMonth()); $onVacation = []; Employee::get()->each(function ($user) use ($period, &$onVacation) { $vacation = $user->vacation_start->toPeriod($user->vacation_end); if ($vacation->overlaps($period)) { $onVacation[] = $user->name; }});
Result:
array:2 [ 0 => "Kaya Lowe" 1 => "Royal Yundt"]
We can find out if a specific date falls into our period. For this example, let's say we want to list customers that have their birthday this month.
Customers dataset:
$customers = [ ['name' => 'Helmer Wilkinson', 'birthday' => '1982-09-27'], ['name' => 'Kaya Lowe', 'birthday' => '1977-04-10'], ['name' => 'Royal Yundt', 'birthday' => '1975-03-30'], ['name' => 'Willow Stark', 'birthday' => '1988-07-27'], ['name' => 'Mortimer Vandervort', 'birthday' => '1982-04-03'], ['name' => 'Keven Morissette', 'birthday' => '1993-03-21'], ['name' => 'Pete Grant', 'birthday' => '1996-08-13'], ['name' => 'Kieran Schuster', 'birthday' => '1994-01-10'], ['name' => 'Rose Torphy', 'birthday' => '1988-04-23'], ['name' => 'Nyasia Senger', 'birthday' => '1993-07-06'],];
Then we can use the contains()
method on a period that returns a boolean.
use Carbon\Carbon;use Carbon\CarbonPeriod; $period = CarbonPeriod::create(now()->startOfMonth(), now()->endOfMonth()); $birthdaysThisMonth = []; foreach ($customers as $customer) { $born = Carbon::parse($customer['birthday']); $birthdayThisYear = $born->copy()->year(now()->format('Y')); if ($period->contains($birthdayThisYear)) { $birthdaysThisMonth[] = sprintf( '%s turns %d this month.', $customer['name'], $born->diffInYears($birthdayThisYear) ); }}
Given that the current month is July, this will be the result:
array:2 [ 0 => "Willow Stark turns 35 this month." 1 => "Nyasia Senger turns 30 this month."]
When a person chooses a certain date for an appointment, you can filter already booked times with the date. This example illustrates that:
use Carbon\Carbon;use Carbon\CarbonPeriod; $bookedAppointments = [ '2023-07-13 10:00', '2023-07-13 14:00', '2023-07-13 15:00',]; $firstAppointment = Carbon::parse('2023-07-13 9:00');$lastAppointment = Carbon::parse('2023-07-13 16:00');$period = CarbonPeriod::create($firstAppointment, '1 hour', $lastAppointment) ->filter(function ($date) use ($bookedAppointments) { return ! in_array($date->format('Y-m-d H:i'), $bookedAppointments); }); $availableTimes = []; foreach ($period as $date) { $availableTimes[] = $date->format('Y-m-d H:i');}
Result:
array:5 [ 0 => "2023-07-13 09:00" 1 => "2023-07-13 11:00" 2 => "2023-07-13 12:00" 3 => "2023-07-13 13:00" 4 => "2023-07-13 16:00"]
So these are just a few examples of CarbonPeriod
. If you want to dive a bit deeper into working date/time, we also have an interesting course: Laravel User Timezones Project: Convert, Display, Send Notifications