In PHP, what is the difference between DateTime and DateTimeImmutable, and what bug does calling modify() on a shared DateTime cause?
answer
- same API, different return
- DateTime changes $this
- Immutable returns a new object
- both implement DateTimeInterface
- 8.5 warns on a discarded result
basics
~20 sDateTime's modify(), add(), sub(), setDate() and setTimezone() change the object itself and return it; DateTimeImmutable's return a new object and leave the original alone. So $end = $start->modify('+7 days') on a DateTime silently moves $start too.
solid answer
~40 sBoth classes implement `DateTimeInterface` and share almost the same API, but `DateTime` is mutable: `modify()`, `add()`, `sub()`, `setDate()`, `setTime()` and `setTimezone()` change the object and return that same object. `DateTimeImmutable` returns a new instance from each of them and never changes the original. The classic bug is `$end = $start->modify('+7 days')` with a `DateTime`: `$end` and `$start` are the same object, both now a week later. The same happens when a function receives a `DateTime` and adjusts it, because objects are passed as handles and the caller sees the change. The mirror bug with the immutable class is calling `$date->modify('+1 day');` and ignoring the result; since PHP 8.5 those methods carry `#[\NoDiscard]`, so that line raises a warning. Modern code defaults to `DateTimeImmutable` and type-hints `DateTimeInterface`.
code
php · 14 lines<?php
declare(strict_types=1);
$start = new DateTime('2026-03-01');
$end = $start->modify('+7 days'); // same object as $start
echo $start->format('Y-m-d'), "\n"; // 2026-03-08: start moved
var_dump($start === $end); // bool(true)
$from = new DateTimeImmutable('2026-03-01');
$to = $from->modify('+7 days'); // new object
echo $from->format('Y-m-d'), ' -> ', $to->format('Y-m-d'), "\n";
// 2026-03-01 -> 2026-03-08
$from->modify('+1 day'); // PHP 8.5: warning, result discardedgo deeper
Recall that DateTime methods change the object itself while DateTimeImmutable methods return a new object, and that you must assign that result.
Explain the aliasing bug from $end = $start->modify(...), why objects passed to functions share the change, and how createFromMutable() and createFromInterface() help.
Set team defaults: DateTimeImmutable internally, DateTimeInterface in signatures, and treat the PHP 8.5 NoDiscard warning as a real bug report.
Frame immutability as a design choice for shared values, and plan how a large code base with DateTime on entities moves to immutable dates safely.
## Two classes, one API PHP's date extension ships two concrete classes for a point in time: **`DateTime`** and **`DateTimeImmutable`**. Both implement **`DateTimeInterface`**, both are created the same way (`new DateTimeImmutable('2026-03-01 09:00', $zone)`), and both have `format()`, `getTimestamp()`, `diff()` and friends. The difference is entirely in the methods that change the date. | Method | `DateTime` | `DateTimeImmutable` | |---|---|---| | `modify('+1 day')` | changes `$this`, returns `$this` | returns a new object | | `add()` / `sub()` | changes `$this` | returns a new object | | `setDate()` / `setTime()` | changes `$this` | returns a new object | | `setTimezone()` | changes `$this` | returns a new object | | Ignoring the return value | still works | the change is lost | ## The mutable-object bug Because a `DateTime` method returns the same object it changed, this code looks right and is wrong: 1. `$start = new DateTime('2026-03-01');` 2. `$end = $start->modify('+7 days');` 3. Both variables now hold the **same object**, set to 8 March. The start date is gone. The same trap appears across function boundaries. PHP passes objects as **handles**: a function that receives a `DateTime` and calls `modify()` on it changes the caller's object too. A helper such as `endOfBillingPeriod(DateTime $d)` can quietly move the invoice date stored on an entity. With `DateTime` the only protection is to `clone` before changing, which every caller has to remember. ## The immutable-object bug, and the 8.5 warning `DateTimeImmutable` removes the aliasing problem, but it has its own mistake: writing `$date->modify('+1 day');` on a line by itself. The method returns the new date, the result is thrown away, and `$date` is unchanged. Since **PHP 8.5**, the changing methods of `DateTimeImmutable` (`modify()`, `add()`, `sub()`, `setTimezone()`, `setTime()`, `setDate()` and others) are marked with the new **`#[\NoDiscard]`** attribute. Calling one and discarding the result emits a warning that the return value should be used, because the method does not modify the object itself. If you really mean to discard it, a `(void)` cast says so explicitly. ## Converting between them - `DateTimeImmutable::createFromMutable($dateTime)` makes an immutable copy of a `DateTime`. - `DateTime::createFromImmutable($immutable)` goes the other way. - `createFromInterface()` on either class accepts any `DateTimeInterface`. This lets you keep `DateTimeImmutable` inside your code while still accepting or returning `DateTime` where a library insists on it. ## What to use - Default to **`DateTimeImmutable`** for new code: values that cannot change under you are easier to share, cache and reason about. - Type-hint parameters as **`DateTimeInterface`** when you only read the date, so callers may pass either class. - Treat any remaining `DateTime` as a local working object, and `clone` it before handing it out. ## Where the mutable bug hides in real code The aliasing bug rarely looks as obvious as two lines in a row. Typical hiding places: - A **getter that returns an internal `DateTime`**, such as `$order->getCreatedAt()`. Any caller that calls `modify()` on the result changes the order itself. - A **shared instance** created once, for example a "today" object kept in a service or a static cache and adjusted by one caller for its own calculation. - A **loop** that reuses one `DateTime` to generate a schedule and stores it in an array on each pass: every array element ends up as the same object with the last date. With `DateTimeImmutable` each of these becomes safe by construction, because nobody can change the object they were given. That is the real argument for immutability: it removes a whole category of action at a distance, not just one typo. ## A quick review checklist 1. Search for `new DateTime(` and ask whether the object can ever escape the function that created it. 2. Check getters that return dates: they should return `DateTimeImmutable`, or a clone. 3. Check every `modify()`, `add()` and `sub()` on an immutable date: its result must be assigned. ## Interview summary The expected answer names the mechanism, not just the verdict: `DateTime` methods mutate and return `$this`, so assigning the result creates an alias; `DateTimeImmutable` methods return a fresh object, so the result must be assigned. Mentioning the PHP 8.5 `#[\NoDiscard]` warning shows you know the immutable class's failure mode as well.
- A method accepts DateTime $due and calls $due->modify('+1 day') to compute a reminder date. What goes wrong for the caller?Objects are passed as handles, so the method changes the caller's own `DateTime`: the due date stored on the invoice or entity moves forward by a day. The fix is to accept `DateTimeInterface`, convert with `DateTimeImmutable::createFromInterface()` and work on the immutable copy, or at least `clone` before modifying.
- Why does PHP 8.5 warn about $date->modify('+1 day'); on a DateTimeImmutable but not on a DateTime?The immutable class's changing methods are marked `#[\NoDiscard]` in PHP 8.5, because their only effect is the returned object, so discarding it is almost always a bug. A `DateTime` call changes the object in place, so ignoring its return value is normal and carries no attribute.
saying these in an interview costs you the question
- DateTime::modify() returns a modified copy and leaves the original alone
- DateTimeImmutable::modify() changes the object in place
- Passing a DateTime to a function gives the function its own copy
- Immutable dates cannot be shifted, so they are only for constants
- DateTime and DateTimeImmutable cannot be converted into each other