Dates7 min readExamples run on PHP 8.5.10

date_sub()

Learn how date_sub() subtracts a DateInterval from a DateTime object, including mutation, month overflow and how to keep the original date.

Signature

date_sub(DateTime $object, DateInterval $interval): DateTime
Available since PHP 5.3.07.48.08.18.28.38.48.5All versions →

Purpose

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: the DateTime to modify. It must be a mutable DateTime. A DateTimeImmutable is not accepted by this function; use the DateTimeImmutable::sub() method for that class instead.
  • $interval: a DateInterval describing the amount to subtract. You can create one from an ISO 8601 duration string such as P10D (ten days) or PT36H (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.

Example
<?php
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";
Output
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.

Example
<?php
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";
Output
[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.

Example
<?php
$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";
Output
["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 month with modify().
  • 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 invert flag set describes a negative span, so subtracting it generally moves the date forward. Intervals produced by date_diff() carry this flag, so check it before reusing them.
  • Leap years: P1Y from 29 February 2024 gives a non-existent 29 February 2023, which overflows to 1 March 2023.

Common mistakes

  1. 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.
  2. Passing an immutable object. date_sub() is typed for DateTime. Use the DateTimeImmutable::sub() method, which returns a new object, when working with immutable dates.
  3. Using a negative-looking string. new DateInterval('-P1D') is not the way to express a negative interval in the constructor. Use date_sub() with a positive interval or date_add() for the opposite direction.
  4. Forgetting the T separator. In P1H the H is invalid, because hours must come after T, as in PT1H. Months are P1M, minutes are PT1M.
  5. Assuming month steps are clamped. Subtracting a month from the 29th, 30th or 31st may skip into the next month.
  • date_add(): adds an interval to a DateTime object.
  • 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 the DateTime object 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

Subtract ten days from a date
<?php
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";
Output
2024-03-05 10:30:00
Same object returned and month overflow
<?php
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";
Output
[true,"2024-03-02"]
Clone before subtracting a mixed interval
<?php
$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";
Output
["2024-01-01 12:00","2023-12-31 00:00"]