---
title: "Preserving Date Integrity with Immutable Carbon"
description: "Carbon is mutable by default, which means a harmless-looking date calculation can silently change the original value. This is why I prefer CarbonImmutable for dates I pass around application code."
canonical: "https://gummibeer.dev/blog/2023/preserving-date-integrity"
---

# Preserving Date Integrity with Immutable Carbon

Carbon is mutable by default, which means a harmless-looking date calculation can silently change the original value. This is why I prefer CarbonImmutable for dates I pass around application code.

Carbon is great. But there is one default I really don't like: `Carbon` is mutable.

That sounds harmless until a method like `->addDay()` appears in code that only looks like a calculation. It doesn't create a changed copy. It changes the object you called it on.

```php
use Carbon\Carbon;

$publishedAt = Carbon::parse('2023-08-09 12:00:00');
$expiresAt = $publishedAt->addDay();

// Both now point to 2023-08-10 12:00:00.
dump($publishedAt->toDateTimeString());
dump($expiresAt->toDateTimeString());
```

Sometimes that is exactly what you want. Most of the time, especially once a date is passed around as application state, I don't want a random calculation to also mutate the original value.

## `readonly` doesn't save you

This gets especially easy to miss with DTOs and value objects.

Take a small `Post` DTO with a `published_at` date and a computed `is_new()` method. A post is new while its publication date is within the last 24 hours:

```php
use Carbon\Carbon;

class Post
{
    public function __construct(
        public readonly Carbon $published_at,
    ) {}

    public function is_new(): bool
    {
        return $this->published_at->addDay()->isFuture();
    }
}
```

At first glance this looks fine. The property is even `readonly`.

But `readonly` only prevents replacing the object stored in the property. It doesn't make that object immutable.

So every call to `is_new()` moves `published_at` one day into the future. A method that should only answer a question changes the state of the object while doing so.

That's the kind of bug I don't want to debug.

## Use `CarbonImmutable`

The fix is boring, which is exactly what I want here: use `CarbonImmutable` instead.

```php
use Carbon\CarbonImmutable;

class Post
{
    public function __construct(
        public readonly CarbonImmutable $published_at,
    ) {}

    public function is_new(): bool
    {
        return $this->published_at->addDay()->isFuture();
    }
}
```

The code inside `is_new()` stays exactly the same. The important difference is that `->addDay()` now returns a new instance and leaves `published_at` untouched.

Now the property behaves the way `readonly` makes it look: the date really doesn't change.

## Hidden mutation gets worse around persistence

Mutable dates become even more annoying once the object is part of state that may later be persisted.

The line mutating the date and the line saving that state don't have to be anywhere near each other. A helper can change a date during a calculation, the mutated object can continue through the application, and some later code can write that value back to the database.

That is the part I dislike most about mutable date objects: the mutation is implicit. There is no assignment that tells me, "this date changes now".

With `CarbonImmutable`, that whole class of bug disappears. If I want to persist a changed date, I have to explicitly use the new instance.

## Handling Carbon objects from external code

Of course I don't control every date object in an application. A package can return `CarbonInterface`, and that concrete instance may still be mutable.

In that case I convert it at the boundary:

```php
use Carbon\CarbonImmutable;
use Carbon\CarbonInterface;

/** @var CarbonInterface $externalCarbon */
$externalCarbon = SomeExternalPackage::getCarbonInstance();

$immutableCarbon = $externalCarbon->toImmutable();

// Or explicitly through CarbonImmutable itself.
$immutableCarbon = CarbonImmutable::instance($externalCarbon);
```

Both give me an immutable instance I can safely pass further into my own code.

For me, that's the useful rule: I don't care whether some package prefers mutable Carbon internally. Once the date enters my application code, I want it immutable unless I have a very specific reason not to.

If you're using Laravel, this can also be configured globally instead of handling it per callsite. I cover that in my follow-up about [using `CarbonImmutable` throughout Laravel](/blog/2023/carbon-techniques-in-laravel/).
