3 min read
Immutable Dates
Date handling is a frequent source of bugs. Laravel defaults to using Carbon, which is mutable — calling a method like addDay() modifies the original object in place. This can lead to hard-to-track errors when the same date instance is reused. Laravel's support for CarbonImmutable solves this cleanly.
Why Mutable Dates Cause Problems
With standard Carbon, every modification mutates the original:
$start = Carbon::parse('2024-06-01'); $end = $start->addDays(7); // $start is now also 2024-06-08 echo $start->format('Y-m-d'); // 2024-06-08 — not what you expected echo $end->format('Y-m-d'); // 2024-06-08
This is a classic source of bugs, especially when a date variable is shared across multiple calculations.
CarbonImmutable Returns New Instances
CarbonImmutable works identically to Carbon, but every method that would normally mutate the instance returns a brand new one instead:
use Carbon\CarbonImmutable; $start = CarbonImmutable::parse('2024-06-01'); $end = $start->addDays(7); // $start is untouched echo $start->format('Y-m-d'); // 2024-06-01 — safe echo $end->format('Y-m-d'); // 2024-06-08
Configuring Laravel to Use CarbonImmutable
Laravel provides a helper to switch the default date class application-wide. Add this to your AppServiceProvider:
use Illuminate\Support\Facades\Date; use Carbon\CarbonImmutable; class AppServiceProvider extends ServiceProvider { public function boot(): void { Date::use(CarbonImmutable::class); } }
After this, Laravel's now(), model timestamps, and the date() helper all return CarbonImmutable instances.
Laravel's Date Helpers
Laravel ships with several helper functions that respect the configured date class:
// Returns the configured date class (Carbon or CarbonImmutable) $now = now(); // Parse a date string using the configured class $date = Date::parse('2024-12-25'); // Create from a format $date = Date::createFromFormat('d/m/Y', '25/12/2024');
Prefer these helpers over calling Carbon::now() or CarbonImmutable::now() directly. They ensure your application stays consistent if you ever change the default date class.
Practical Example
A common pattern is calculating date ranges for reports:
// ❌ With mutable Carbon — the bug $rangeStart = Carbon::now()->startOfMonth(); $rangeEnd = $rangeStart->endOfMonth(); // Mutates $rangeStart too! $query = Order::whereBetween('created_at', [$rangeStart, $rangeEnd]); // ✅ With CarbonImmutable — safe and clear $rangeStart = now()->startOfMonth(); $rangeEnd = $rangeStart->endOfMonth(); // $rangeStart unchanged $query = Order::whereBetween('created_at', [$rangeStart, $rangeEnd]);
When Mutability Is Actually Fine
There are cases where you intentionally want to build up a date step by step. In those situations, mutable Carbon is perfectly acceptable — just be explicit about it:
// Fine — you are deliberately mutating a local variable $deadline = Carbon::now(); $deadline->addDays(7); $deadline->setTime(23, 59, 59);
The key rule: if you pass a date to another function or reuse it after modification, use CarbonImmutable.
Summary
- Switch your Laravel app to
CarbonImmutableviaDate::use()inAppServiceProvider. - Use
now()andDate::parse()instead of calling Carbon directly. - Treat immutability as the safe default; reach for mutable Carbon only when you have a deliberate reason.