PHPDateTime & DateTimeImmutable

DateTime & DateTimeImmutable

Once a program needs to do more than print the current date, the procedural functions like date() and strtotime() start to feel thin. PHP's object-oriented date API — DateTime, DateTimeImmutable, DateInterval, and DateTimeZone — wraps a moment in time in a proper object, so it can be passed around, compared, and manipulated with named methods instead of manual string parsing and timestamp math.

Creating a DateTime

The DateTime constructor accepts the same flexible, human-readable strings that strtotime() understands. With no argument at all, it represents the current moment.

Constructing DateTime objects

PHP
<?php
$now = new DateTime();
$specific = new DateTime('2025-06-15 14:30:00');
$relative = new DateTime('next Friday');

echo $specific->format('Y-m-d H:i:s'), "\n";
echo $relative->format('l, F jS'), "\n";
2025-06-15 14:30:00
Friday, June 20th

->format() takes exactly the same format characters as the procedural date() function — Y, m, d, H, i, s, and so on — so anything already learned there carries over directly.

DateInterval: representing a span of time

A DateInterval represents a duration — "3 days," "2 months and 10 days," "1 year" — rather than a specific point in time. It's constructed from an ISO 8601 duration string, where P marks the start, letters like Y/M/D cover years/months/days, and a T separates the date portion from a time portion using H/M/S.

Building intervals and applying them

PHP
<?php
$date = new DateTime('2025-01-15');

$threeDays = new DateInterval('P3D');
$oneMonth  = new DateInterval('P1M');
$complex   = new DateInterval('P1Y2M10DT5H'); // 1 year, 2 months, 10 days, 5 hours

$date->add($threeDays);
echo $date->format('Y-m-d'), "\n";

$date->sub($oneMonth);
echo $date->format('Y-m-d'), "\n";
2025-01-18
2024-12-18

->add() moves the date forward by the interval, ->sub() moves it backward, and both correctly account for varying month lengths and leap years — something hand-rolled + N * 86400 arithmetic cannot do reliably.

DateTime is mutable — DateTimeImmutable is not

This is the single most important distinction between the two classes. Calling ->add() or ->sub() on a DateTime object changes that same object in place and returns it for chaining. DateTimeImmutable has the identical method names, but each call returns a brand-new object and leaves the original untouched.

Mutable vs immutable behavior

PHP
<?php
$mutable = new DateTime('2025-01-01');
$result = $mutable->add(new DateInterval('P1D'));

var_dump($mutable === $result);       // same object
echo $mutable->format('Y-m-d'), "\n"; // changed!

$immutable = new DateTimeImmutable('2025-01-01');
$newDate = $immutable->add(new DateInterval('P1D'));

var_dump($immutable === $newDate);        // different objects
echo $immutable->format('Y-m-d'), "\n";  // unchanged
echo $newDate->format('Y-m-d'), "\n";    // the new value
bool(true)
2025-01-02
bool(false)
2025-01-01
2025-01-02
A shared DateTime object can be mutated out from under you
If a `DateTime` instance is stored on an object property or passed into a function that calls `->modify()`, `->add()`, or `->sub()` on it, every other piece of code holding a reference to that same object sees the change too — there is no automatic copy. This has caused real bugs where a "start date" silently became a "current date" after being passed through unrelated code. Prefer `DateTimeImmutable` by default, and only reach for the mutable `DateTime` when something specifically needs to be edited in place.
createFromFormat(): parsing a known, exact format

The regular constructor is forgiving and flexible, which is convenient but risky when the input format is fixed and must be parsed exactly — for example, a date coming from a legacy system in d/m/Y order, which a generic parser could easily misread as month/day. createFromFormat() takes the same format characters as ->format() and parses strictly against that shape.

Parsing an unambiguous, known format

PHP
<?php
$date = DateTimeImmutable::createFromFormat('d/m/Y', '05/03/2025');
echo $date->format('Y-m-d'), "\n"; // day = 5, month = 3

$bad = DateTimeImmutable::createFromFormat('d/m/Y', 'not a date');
var_dump($bad);
2025-03-05
bool(false)

Like strtotime(), createFromFormat() returns false rather than throwing when parsing fails, so the return value should always be checked before calling a method on it.

Comparing dates

DateTime and DateTimeImmutable objects support the standard comparison operators directly — <, >, ==, and so on — because PHP compares their internal properties. This reads far more naturally than comparing raw timestamps.

Comparing two dates

PHP
<?php
$deadline = new DateTimeImmutable('2025-12-31');
$submitted = new DateTimeImmutable('2025-11-20');

if ($submitted < $deadline) {
    $diff = $deadline->diff($submitted);
    echo "On time, with {$diff->days} days to spare\n";
}
On time, with 41 days to spare

->diff() returns a DateInterval describing the gap between two dates, with properties like ->days (total whole days), ->y/->m/->d (broken into years/months/days), and ->invert (1 if the argument was actually earlier than the object it was called on).

  • new DateTime($string) and new DateTimeImmutable($string) both parse the same flexible strings strtotime() accepts.

  • ->format($chars) uses the same format letters as the procedural date() function.

  • DateInterval represents a duration, built from an ISO 8601 string like P1Y2M10D.

  • DateTime::add()/sub() mutate the object in place; DateTimeImmutable::add()/sub() return a new object.

  • createFromFormat() parses strictly against an exact, known format instead of guessing.

  • ->diff() returns a DateInterval describing the gap between two date objects.

Note
Both classes implement the `DateTimeInterface`, so a function that only needs to read a date (format it, compare it, diff it) can type its parameter as `DateTimeInterface` and accept either kind without caring which one the caller used.
Tip
Default to `DateTimeImmutable` in new code, especially for anything stored on an object or passed between functions. Immutability eliminates an entire category of "who changed this date and when" bugs, and the small ergonomic cost of writing `$date = $date->add(...)` instead of `$date->add(...)` is worth paying for the predictability it buys.