Signature
date_sub(DateTime $object, DateInterval $interval): DateTimePurpose
date_sub() is the procedural alias of DateTime::sub(). It takes a mutable DateTime object and a DateInterval, then moves the date and time backwards by the amount the interval describes. Use it when you already work with function-style date code and need to step back by days, months, years, hours, minutes or seconds.
The important point is that the object you pass in is changed in place. The function does not build a new date for you.
Signature
Parameters and defaults
$object: theDateTimeto modify. It must be a mutableDateTime. ADateTimeImmutableis not accepted by this function; use theDateTimeImmutable::sub()method for that class instead.$interval: aDateIntervaldescribing the amount to subtract. You can create one from an ISO 8601 duration string such asP10D(ten days) orPT36H(thirty-six hours).
Neither parameter has a default value; both are required.
Return value and failure cases
The function returns the same DateTime object that you passed in, after modification. That makes chained procedural calls possible, but it also means the return value is not a copy. Passing a value of the wrong type, such as a string instead of a DateTime, raises a TypeError. Building the interval can fail earlier: new DateInterval('bad') throws an exception before date_sub() is ever called.
Example 1: subtracting days
The interval P10D removes ten calendar days. The time of day stays unchanged because the interval has no time part.
date_default_timezone_set('UTC');
$date = date_create('2024-03-15 10:30:00');
$interval = new DateInterval('P10D');
date_sub($date, $interval);
echo $date->format('Y-m-d H:i:s'), "\n";2024-03-05 10:30:00
Example 2: the object is mutated and returned
The returned value is the very same object. This example also shows month arithmetic. Subtracting one month from 31 March asks for 31 February, which does not exist in 2024. PHP lets the extra days overflow into the following month, so the result is 2 March rather than the last day of February.
date_default_timezone_set('UTC');
$date = date_create('2024-03-31');
$result = date_sub($date, new DateInterval('P1M'));
echo json_encode([$result === $date, $date->format('Y-m-d')]), "\n";[true,"2024-03-02"]
Example 3: keeping the original with clone
Because date_sub() changes its argument, clone the object first when you need both the old and new values. This example also mixes date and time parts in a single interval.
$original = date_create('2024-01-01 12:00:00', new DateTimeZone('UTC'));
$copy = clone $original;
date_sub($copy, new DateInterval('P1DT12H'));
echo json_encode([$original->format('Y-m-d H:i'), $copy->format('Y-m-d H:i')]), "\n";["2024-01-01 12:00","2023-12-31 00:00"]
Edge cases
- Month overflow: as shown above, subtracting months can land on a day that does not exist in the target month. The result rolls forward instead of being clamped to the month end. If you need end-of-month behaviour, check the day yourself or use a different approach such as a relative format like
last day of previous monthwithmodify(). - Time zones: the arithmetic is performed in the time zone attached to the object. Date-based parts of an interval work on wall-clock time, so results near daylight saving changes may not match a plain count of 24-hour blocks. Set the time zone explicitly when results must be reproducible.
- Inverted intervals: an interval with its
invertflag set describes a negative span, so subtracting it generally moves the date forward. Intervals produced bydate_diff()carry this flag, so check it before reusing them. - Leap years:
P1Yfrom 29 February 2024 gives a non-existent 29 February 2023, which overflows to 1 March 2023.
Common mistakes
- Expecting a new object. Code such as
$past = date_sub($now, $i);leaves both variables pointing to the same, modified object. Clone first if you need to keep$now. - Passing an immutable object.
date_sub()is typed forDateTime. Use theDateTimeImmutable::sub()method, which returns a new object, when working with immutable dates. - Using a negative-looking string.
new DateInterval('-P1D')is not the way to express a negative interval in the constructor. Usedate_sub()with a positive interval ordate_add()for the opposite direction. - Forgetting the
Tseparator. InP1HtheHis invalid, because hours must come afterT, as inPT1H. Months areP1M, minutes arePT1M. - Assuming month steps are clamped. Subtracting a month from the 29th, 30th or 31st may skip into the next month.
Related functions
date_add(): adds an interval to aDateTimeobject.DateTime::sub(): the method form that this function aliases.DateTimeImmutable::sub(): returns a new modified object and leaves the original untouched.date_diff(): calculates the interval between two dates.date_create(): creates theDateTimeobject to modify.date_interval_create_from_date_string(): builds an interval from a relative text description.
Recap
date_sub($object, $interval) moves a mutable DateTime backwards by a DateInterval and returns that same object. Clone the date if the original value still matters, set the time zone explicitly for predictable output, and remember that month and year subtraction can overflow into the next month when the target day does not exist.
Tested examples
date_default_timezone_set('UTC');
$date = date_create('2024-03-15 10:30:00');
$interval = new DateInterval('P10D');
date_sub($date, $interval);
echo $date->format('Y-m-d H:i:s'), "\n";
2024-03-05 10:30:00
date_default_timezone_set('UTC');
$date = date_create('2024-03-31');
$result = date_sub($date, new DateInterval('P1M'));
echo json_encode([$result === $date, $date->format('Y-m-d')]), "\n";
[true,"2024-03-02"]
$original = date_create('2024-01-01 12:00:00', new DateTimeZone('UTC'));
$copy = clone $original;
date_sub($copy, new DateInterval('P1DT12H'));
echo json_encode([$original->format('Y-m-d H:i'), $copy->format('Y-m-d H:i')]), "\n";
["2024-01-01 12:00","2023-12-31 00:00"]