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): DateTimeTwo things to remember:
- It mutates the object in place — the original
DateTimeis 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-10Passing 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.
| Token | Meaning | Example | Duration |
|---|---|---|---|
Y | Years | P3Y | 3 years |
M | Months | P6M | 6 months |
D | Days | P10D | 10 days |
H | Hours | PT4H | 4 hours |
M | Minutes | PT30M | 30 minutes |
S | Seconds | PT45S | 45 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:00Time-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:00Note:
DateIntervalvalidates the format strictly. An invalid string (for example a missingP, orPTwith no time tokens) throws anException. If the duration comes from user input, wrap the constructor in atry...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-05For 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-03Here 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-15This 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-10Conclusion
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.