Automated Rector rules to migrate your PHP codebase from Carbon 2 to Carbon 3, with special attention to codebases that do NOT use UTC as the default timezone.
Carbon 3 introduced several breaking changes. Some are simple renames, but others silently change behavior (like createFromTimestamp defaulting to UTC, or diffIn* returning signed floats). These rules automate as much of the migration as possible and leave TODO comments for anything that requires manual judgment.
# Install Rector if you haven't already
composer require rector/rector --dev
# Copy the rules into your project
cp -r src/Rector /path/to/your/project/utils/rector/src/Rector
cp -r src/Set /path/to/your/project/utils/rector/src/SetAdd to your composer.json:
{
"autoload-dev": {
"psr-4": {
"Carbon3Rector\\": "utils/rector/src"
}
}
}Then:
composer dump-autoloadCreate or update your rector.php:
<?php
use Carbon3Rector\Set\CarbonMigrationRules;
use Rector\Config\RectorConfig;
return RectorConfig::configure()
->withPaths([
__DIR__ . '/src',
__DIR__ . '/app',
])
->withRules(CarbonMigrationRules::ALL);Run:
# Preview changes
vendor/bin/rector process --dry-run
# Apply changes
vendor/bin/rector process| Severity | Critical (silent behavior change) |
|---|
Carbon 3 changed createFromTimestamp() default timezone from date_default_timezone_get() to "UTC". For non-UTC codebases, this silently changes every timestamp conversion.
-$date = Carbon::createFromTimestamp($timestamp);
+$date = Carbon::createFromTimestamp($timestamp, date_default_timezone_get());| Severity | Critical (silent behavior change) |
|---|
Same as above but for createFromTimestampMs().
-$date = Carbon::createFromTimestampMs($ms);
+$date = Carbon::createFromTimestampMs($ms, date_default_timezone_get());| Severity | Critical (silent behavior change) |
|---|
diffIn* methods changed from returning positive int to returning signed float. This rule wraps calls in (int) abs(...) to preserve Carbon 2 behavior.
-$seconds = $a->diffInSeconds($b);
+$seconds = (int) abs($a->diffInSeconds($b));
// If already using absolute=true, just casts to int:
-$hours = $a->diffInHours($b, true);
+$hours = (int) $a->diffInHours($b, true);Covered methods: diffInYears, diffInMonths, diffInWeeks, diffInDays, diffInDaysFiltered, diffInHours, diffInMinutes, diffInSeconds, diffInMicroseconds, diffInMilliseconds, diffInWeekdays, diffInWeekendDays, diffInRealHours, diffInRealMinutes, diffInRealSeconds, diffInRealMicroseconds, diffInRealMilliseconds
| Severity | Medium (will cause type errors) |
|---|
create(), createFromFormat(), createFromIsoFormat(), createFromLocaleFormat(), and createFromLocaleIsoFormat() now return null instead of false on failure.
-if (Carbon::createFromFormat('Y-m-d', $input) === false) {
+if (Carbon::createFromFormat('Y-m-d', $input) === null) {| Severity | Medium (will throw exception) |
|---|
isSame*() methods no longer accept zero arguments. The Carbon 2 behavior (compare to "now") is now isCurrent*().
-$date->isSameDay();
+$date->isCurrentDay();
-$date->isSameMonth();
+$date->isCurrentMonth();| Severity | Medium (method removed) |
|---|
minValue() and maxValue() were removed. Replaced by CarbonImmutable-only methods.
-$min = Carbon::minValue();
+$min = CarbonImmutable::startOfTime();
-$max = Carbon::maxValue();
+$max = CarbonImmutable::endOfTime();| Severity | Medium (will cause error) |
|---|
All named argument tz: parameters were renamed to timezone:.
-Carbon::create(2024, 1, 1, tz: 'UTC');
+Carbon::create(2024, 1, 1, timezone: 'UTC');| Severity | Medium (method removed) |
|---|
formatLocalized() (which relied on OS strftime()) was removed. Replaced by isoFormat() with automatic strftime→isoFormat token conversion.
-$date->formatLocalized('%A %d %B %Y');
+$date->isoFormat('dddd DD MMMM YYYY');Adds TODO comments when format strings contain tokens that can't be automatically converted or when the format is dynamic.
| Severity | Low (method removed) |
|---|
Removes setUtf8() calls which no longer exist.
-Carbon::setUtf8(true);
$date = Carbon::now();| Severity | Medium (method removed, needs manual migration) |
|---|
Removes setWeekStartsAt() / setWeekEndsAt() and adds TODO comments with migration guidance.
-Carbon::setWeekStartsAt(Carbon::MONDAY);
+// TODO: [Carbon 3 migration] setWeekStartsAt() was removed in Carbon 3.
+// Pass the day explicitly to startOfWeek(\Carbon\WeekDay::Monday) or use locale-based week start.After applying the automated rules, review the MIGRATION_CHECKLIST.md file included in this package for things that need manual inspection.
Search your codebase for TODO: [Carbon 3 migration] to find all spots that need manual review.
You can run specific rules instead of the full set:
use PhoneBurner\CarbonMigrationRectorRules\CarbonDiffInMethodsToExplicitCastRector;
use Rector\Config\RectorConfig;
return RectorConfig::configure()
->withRules([
CarbonDiffInMethodsToExplicitCastRector::class,
]);After running Rector, use these regex patterns to find any remaining issues:
# Find diffIn* calls that might not have been caught
grep -rn 'diffIn\(Seconds\|Minutes\|Hours\|Days\|Weeks\|Months\|Years\)' src/
# Find createFromTimestamp without explicit timezone
grep -rn 'createFromTimestamp(' src/ | grep -v 'date_default_timezone_get'
# Find any remaining false comparisons with Carbon create methods
grep -rn 'createFromFormat.*=== false\|=== false.*createFromFormat' src/
# Find any remaining formatLocalized calls
grep -rn 'formatLocalized' src/
# Find comparison methods that might receive null/bool
grep -rn '->eq(\|->gt(\|->lt(\|->gte(\|->lte(\|->ne(' src/MIT