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
$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
$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
$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 valuebool(true) 2025-01-02 bool(false) 2025-01-01 2025-01-02
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
$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
$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)andnew DateTimeImmutable($string)both parse the same flexible stringsstrtotime()accepts.->format($chars)uses the same format letters as the proceduraldate()function.DateIntervalrepresents a duration, built from an ISO 8601 string likeP1Y2M10D.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 aDateIntervaldescribing the gap between two date objects.