DateTimeImmutable

DateTimeImmutable

When time changes but objects don't

  • Added in: PHP 5.5
  • What it gives you: a class that behaves like DateTime, but doesn't change when modified — it returns a cloned object instead.

What it's about

In PHP, if we change the time of a DateTime object, it changes everywhere it has been passed, because objects in PHP are always passed by reference, and this can lead to unexpected behaviour.

DateTimeImmutable solves this problem — any operation on a date returns a new instance instead of mutating the old one.

When it really makes sense

  • in DTOs / Value Objects — so the time is guaranteed not to change after being passed along;
  • in schedulers, timetables, reports — when you compute many dates from a base one;
  • in business logic — when one date is derived from another (start/end of a period, a deadline, a promotion, etc.).

A problem that's easy to miss

In production code, DateTime is often passed between services and DTOs, or cached in containers.

And here comes the typical story:

class Campaign {
    public function __construct(
        public string $name,
        public DateTime $startsAt,
        public DateTime $endsAt,
    ) {}
}

$base = new DateTime('2025-10-01');
$campaign = new Campaign(
    name: 'Autumn Sale', 
    startsAt: $base, 
    endsAt: $base->modify('+7 days')
);

echo $campaign->startsAt->format('Y-m-d'); // 2025-10-08 ❌
echo $campaign->endsAt->format('Y-m-d');   // 2025-10-08

We expected the start to be October 1, but $base was mutated by modify, and as a result the start and the end are the same.

The same code with DateTimeImmutable works correctly:

$base = new DateTimeImmutable('2025-10-01');
$campaign = new Campaign(
    name: 'Autumn Sale', 
    startsAt: $base, 
    endsAt: $base->modify('+7 days')
);

echo $campaign->startsAt->format('Y-m-d'); // 2025-10-01  ✅
echo $campaign->endsAt->format('Y-m-d');   // 2025-10-08

One more example

class Period {
    public function __construct(
        public DateTimeImmutable $from,
        public DateTimeImmutable $to
    ) {}

    public function days(): int {
        return $this->to->diff($this->from)->days;
    }
}

$period = new Period(
    from: $from = new DateTimeImmutable('2025-06-01'),
    to: $from->modify('+10 days'),
);

echo $period->days(); // 10

No unexpected mutations — the object can be safely cached or passed further.

Conclusion

DateTimeImmutable is the safe choice for time-related logic. It rules out accidental date changes, where one variable leads to unexpected results.

If your date can change, sooner or later it will break something in a report, in the logic or in the finances. Immutable won't let that happen.

← Previous: Generators · Whole series · Next: Named arguments →


First published in Ukrainian in the Telegram channel “Created to die” on October 21, 2025.