kylekatarnls/laravel-carbon-2
Integrates Carbon 2 into Laravel apps that still ship Carbon 1. Swap the bundled Carbon for the newer API—localization, diff methods, immutable dates—without upgrading your framework. A drop-in compatibility bridge for older Laravel versions.
kylekatarnls/laravel-carbon-2 is a compatibility shim: it lets a Laravel 5.5–5.8 application use nesbot/carbon 2.x even though those framework versions originally pinned Carbon 1. It works by registering a replacement for Illuminate\Support\Carbon (the adapter class extends Carbon 2 while keeping Laravel’s serializeUsing() helper), so existing use Illuminate\Support\Carbon; imports transparently pick up the Carbon 2 API.
Minimal steps:
composer require kylekatarnls/laravel-carbon-2:^1.0
composer show nesbot/carbon # must show 2.x
composer show laravel/framework # must show 5.5–5.8
Then confirm the replacement actually won the autoload race:
// routes/console or tinker
dump(get_parent_class(Illuminate\Support\Carbon::class)); // Carbon\Carbon
dump(Illuminate\Support\Carbon::now() instanceof Carbon\CarbonInterface); // true
dump((new ReflectionClass(Illuminate\Support\Carbon::class))->getFileName());
// path should point at vendor/kylekatarnls/laravel-carbon-2, not illuminate/support
Where to look first:
composer.lock — check for a single Carbon major version. Anything still requiring ^1.x will block install.vendor/kylekatarnls/laravel-carbon-2/ — read the class that overrides Illuminate\Support\Carbon; that’s the entire surface area of this package.config/app.php timezone and any Carbon::setTestNow() usage in tests.First use case: you have a legacy 5.6/5.7 app (or a vendor package) that needs Carbon 2 features — CarbonImmutable, CarbonPeriod, createFromTimestampUTC, isSameDay, diffForHumans(['parts' => 2]), ->locale() chaining, microsecond support — but you can’t jump to Laravel 6 yet. Install the adapter, run the suite, and only then start adopting Carbon 2 APIs. If you are already on Laravel 6+, do not install this — Carbon 2 is native and the last release here is from August 2018.
Illuminate\Support\Carbon::now(), ->addDays(7), ->format(...), isToday() calls keep working; they now resolve against Carbon 2 underneath. Treat the install as a dependency bump, not a refactor.Illuminate\Support\Carbon extends Carbon 2, you can call the new API through it:
use Illuminate\Support\Carbon;
Carbon::createMidnightDate(2024, 1, 1);
Carbon::now()->isSameDay($other);
Carbon::now()->toIso8601ZuluString();
Carbon::now()->diffForHumans(['parts' => 2, 'short' => true]);
CarbonImmutable where mutation bugs hurt. Carbon 2’s immutable type is the main reason to adopt this package. In accessors/transformers that would otherwise mutate a shared instance, instantiate Carbon\CarbonImmutable directly rather than fighting Illuminate\Support\Carbon (which stays mutable).$dates, $casts = ['published_at' => 'datetime'], and automatic created_at/updated_at continue to return Illuminate\Support\Carbon instances. Keep casts as-is; if you want immutable behavior in a model, wrap manually:
public function getPublishedAtAttribute($v) {
return $v ? CarbonImmutable::parse($v) : null;
}
setTestNow(). The static test clock lives on Carbon\Carbon and is inherited by the adapter, so the usual pattern works unchanged:
protected function setUp(): void {
parent::setUp();
Carbon::setTestNow('2024-01-01 09:00:00');
}
protected function tearDown(): void {
Carbon::setTestNow(); // always reset
parent::tearDown();
}
Import it as use Illuminate\Support\Carbon; (or Carbon\Carbon) — they share the same clock.Illuminate\Support\Carbon::serializeUsing(fn ($d) => $d->toIso8601String()) for JSON output, keep calling it exactly as before; the adapter preserves the static $serializer from Laravel’s original class. Add an integration test that asserts a model’s toJson() date format.boot():
Carbon::macro('startOfFiscalYear', function () {
return $this->month >= 4
? $this->copy()->month(4)->startOfMonth()
: $this->copy()->subYear()->month(4)->startOfMonth();
});
Macros land on Carbon\Carbon and are immediately available on Illuminate\Support\Carbon.composer why nesbot/carbon to confirm no package is still pulling Carbon 1, then run the full test suite. When you eventually upgrade to Laravel 6+, composer remove kylekatarnls/laravel-carbon-2 and re-run the suite — Laravel 6’s own Illuminate\Support\Carbon is now the Carbon 2 one, so behavior should be identical.Illuminate\Support\Carbon being loaded ahead of illuminate/support’s. After install, always run composer dump-autoload -o and verify with the ReflectionClass::getFileName() check above. If you see vendor/laravel/framework/src/Illuminate/Support/Carbon.php, the shim lost — check for a stray classmap-authoritative setting or a package that hard-requires illuminate/support.nesbot/carbon: ^1.x, Composer refuses. Diagnose with composer why nesbot/carbon and composer why-not nesbot/carbon ^2.0. You cannot mix Carbon majors in one lock file; static state (setTestNow, setLocale, setToStringFormat) will silently split across two class families if you try.composer.json platform block.serializeUsing() regressions. The adapter re-implements Laravel’s serialize()/__toString() glue. If JSON dates suddenly change shape after install, it’s likely here — write a test asserting json_encode($model) before and after.copy() is deprecated (use clone), diff methods gained $absolute/rounding nuance, and returned types shifted in places. Grep for ->copy(), diffIn, and loose == comparisons on date objects after swapping; the adapter can’t smooth over API semantics that genuinely changed between majors.setTestNow() is process-global. With paratest or long-running workers, un-reset clocks bleed between tests — always clear it in tearDown() and prefer Carbon::setTestNow(Carbon::parse(...)) over string literals for readability.nesbot/carbon itself. This package is glue, not the time library; bugs in date math go upstream to Carbon, bugs in Illuminate\Support\Carbon identity go here (and are unlikely to be fixed given the age).composer dump-autoload, re-run the date-heavy tests. Because Laravel 6 ships its own Carbon 2-backed Illuminate\Support\Carbon, the surface should be identical — but re-verify serializeUsing, macros, and the test clock, since those are the pieces the adapter was papering over.get_class(Illuminate\Support\Carbon::now()) and (new ReflectionClass(...))->getFileName(). Nine times out of ten the shim isn’t loaded, not that the method is missing.How can I help you explore Laravel packages today?