W3docs

date_sub()

As any developer knows, working with dates can be a challenging task. Fortunately, PHP has a built-in feature that makes date calculations easier than ever.

Subtracting time from a date in PHP

Subtracting a duration from a date — "30 days ago", "the start of last month", "two hours before now" — is one of the most common date tasks in PHP. The old procedural date_sub() function (an alias of DateTime::sub()) was removed in PHP 8.0, so modern code should use the object-oriented DateTime::sub() (or DateTimeImmutable::sub()) method together with a DateInterval.

This page covers how DateTime::sub() works, how to build the interval string, the mutable-vs-immutable distinction, and the calendar gotchas (month overflow, inverted intervals, DST) that trip people up.

How DateTime::sub() works

sub() takes a single DateInterval argument that describes how much time to remove. Its signature is:

public DateTime::sub(DateInterval $interval): DateTime

Two things to remember:

  • It mutates the object in place — the original DateTime is changed.
  • It also returns the same object, so you can chain calls.
$date = new DateTime('2023-10-15', new DateTimeZone('UTC'));
$interval = new DateInterval('P5D'); // 5 days
$date->sub($interval);
echo $date->format('Y-m-d'); // Outputs: 2023-10-10

Passing an explicit DateTimeZone keeps the result predictable; without one, PHP uses the timezone from date_default_timezone_set() or php.ini.

Building the DateInterval string

DateInterval uses the ISO 8601 duration format: P[n]Y[n]M[n]DT[n]H[n]M[n]S. The P (period) prefix is mandatory, and a T separates the date part from the time part.

TokenMeaningExampleDuration
YYearsP3Y3 years
MMonthsP6M6 months
DDaysP10D10 days
HHoursPT4H4 hours
MMinutesPT30M30 minutes
SSecondsPT45S45 seconds

Note that M means months before the T and minutes after it — a frequent source of bugs. Combine tokens to subtract several units at once:

$date = new DateTime('2023-10-15 12:00:00');
$interval = new DateInterval('P2M1DT3H'); // 2 months, 1 day, 3 hours
$date->sub($interval);
echo $date->format('Y-m-d H:i:s'); // Outputs: 2023-08-14 09:00:00

Time-only intervals work the same way — here, 90 minutes:

$date = new DateTime('2023-10-15 08:30:00');
$date->sub(new DateInterval('PT90M')); // 90 minutes
echo $date->format('Y-m-d H:i:s'); // Outputs: 2023-10-15 07:00:00

Note: DateInterval validates the format strictly. An invalid string (for example a missing P, or PT with no time tokens) throws an Exception. If the duration comes from user input, wrap the constructor in a try...catch.

Mutable vs. immutable: prefer DateTimeImmutable

Because DateTime::sub() changes the object in place, a date you pass around can be modified unexpectedly by code that calls sub() on it. DateTimeImmutable solves this: its sub() leaves the original untouched and returns a new instance.

$date = new DateTimeImmutable('2023-10-15');
$earlier = $date->sub(new DateInterval('P10D'));

echo $date->format('Y-m-d');    // Outputs: 2023-10-15 (unchanged)
echo "\n";
echo $earlier->format('Y-m-d'); // Outputs: 2023-10-05

For most application code, reach for DateTimeImmutable by default and only use the mutable DateTime when you specifically want in-place updates.

Gotchas you should know

Month overflow

Subtracting whole months does not clamp to the last day of the target month — PHP normalizes the overflow into the next month. Subtracting one month from March 31 lands in March, not February:

$date = new DateTime('2023-03-31');
$date->sub(new DateInterval('P1M'));
echo $date->format('Y-m-d'); // Outputs: 2023-03-03

Here P1M first goes to "February 31", which PHP rolls over to March 3 (Feb has 28 days in 2023). If you need the last day of the previous month, use a relative string instead: new DateTime('last day of previous month').

Inverted intervals add instead of subtract

A DateInterval has an invert property. When it is 1, the interval is negative, so sub() effectively adds the duration:

$interval = new DateInterval('P1M');
$interval->invert = 1; // negative interval
$date = new DateTime('2023-10-15');
$date->sub($interval);
echo $date->format('Y-m-d'); // Outputs: 2023-11-15

This matters when you reuse the interval returned by DateTime::diff(), which sets invert automatically based on the direction of the difference.

Daylight saving time

When you subtract days across a DST boundary, PHP adjusts the wall-clock time so the calendar result stays correct. If you instead subtract hours (PT24H), you get exactly 24 hours of elapsed time, which may land on a different clock hour. Choose day-based or hour-based intervals deliberately depending on whether you mean "same time, previous day" or "exactly 24 hours earlier."

Using Carbon for fluent syntax

For large codebases, the Carbon library wraps DateTimeImmutable with readable, chainable helpers. It's optional — the native classes cover the standard cases — but it can make complex logic clearer:

use Carbon\Carbon;

$date = Carbon::parse('2023-10-15');
$newDate = $date->subMonths(2)->subDays(5);
echo $newDate->toDateString(); // Outputs: 2023-08-10

Conclusion

Use DateTime::sub() (or, preferably, DateTimeImmutable::sub()) with a DateInterval to subtract durations from a date. Build the interval with the ISO 8601 P…T… format, keep the months-vs-minutes M distinction in mind, and watch for month overflow and inverted intervals.

To go further, see date_add() for the inverse operation, date_diff() for measuring the gap between two dates, and date_format() for formatting the result.

Practice

Practice
What does DateTime::sub() do with the DateInterval you pass it?
What does DateTime::sub() do with the DateInterval you pass it?
Was this page helpful?