Signature
date_diff(DateTimeInterface $baseObject, DateTimeInterface $targetObject, bool $absolute = false): DateIntervalPurpose
date_diff() measures the gap between two points in time. You give it a starting date object and an ending date object, and it returns a DateInterval that describes the gap in years, months, days, hours, minutes, seconds and microseconds. The manual describes it as a procedural alias of DateTime::diff(), so both forms behave the same way. Choose whichever fits your code style: the function in procedural code, the method in object-oriented code.
Signature
Parameters and defaults
$baseObject: the starting point. AnyDateTimeorDateTimeImmutableworks, because both implementDateTimeInterface.$targetObject: the point you compare against. The interval runs from the base to the target.$absolute: defaults tofalse. Whentrue, the interval is always reported as positive andinvertis0, even if the target is earlier than the base.
Return value and failures
The function returns a DateInterval. Its main properties are y, m, d, h, i, s and f, plus invert and days. invert is 1 when the target is earlier than the base (and $absolute is false). days is the total number of whole days between the two dates. It is a different value from d, which is only the leftover day part after whole months and years are taken out.
The function does not return false for a bad comparison. If you pass something that is not a DateTimeInterface, such as a plain string or false from a failed date_create(), PHP throws a TypeError. Validate or construct your objects first.
Example 1: a basic difference
The two dates below are in a leap year. The result is two whole months, five days, six hours and thirty minutes, and 65 full days in total.
date_default_timezone_set('UTC');
$start = date_create('2024-01-15 08:00:00');
$end = date_create('2024-03-20 14:30:00');
$diff = date_diff($start, $end);
echo json_encode([
'y' => $diff->y,
'm' => $diff->m,
'd' => $diff->d,
'h' => $diff->h,
'i' => $diff->i,
'invert' => $diff->invert,
'days' => $diff->days,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n";{"y":0,"m":2,"d":5,"h":6,"i":30,"invert":0,"days":65}
Notice that d is 5 while days is 65. Use days when you want a total count, and the separate fields when you want a calendar-style breakdown.
Example 2: direction and absolute mode
When the target is earlier than the base, the interval is marked as inverted. Passing true as the third argument removes the sign. The %R format character prints + or - based on invert.
date_default_timezone_set('UTC');
$a = new DateTimeImmutable('2024-05-10');
$b = new DateTimeImmutable('2024-05-01');
$signed = date_diff($a, $b);
$absolute = date_diff($a, $b, true);
echo json_encode([
'signedInvert' => $signed->invert,
'signedDays' => $signed->days,
'absoluteInvert' => $absolute->invert,
'absoluteDays' => $absolute->days,
'format' => $signed->format('%R%a days'),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n";{"signedInvert":1,"signedDays":9,"absoluteInvert":0,"absoluteDays":9,"format":"-9 days"}
Both intervals report 9 days. Only the sign information differs.
Example 3: different offsets
The two timestamps below have the same wall-clock text but different UTC offsets. They name different instants, and the difference reflects that.
$base = new DateTimeImmutable('2024-01-01 00:00:00+00:00');
$target = new DateTimeImmutable('2024-01-01 00:00:00-05:00');
$diff = date_diff($base, $target);
echo json_encode([
'hours' => $diff->h,
'days' => $diff->days,
'text' => $diff->format('%R%H:%I'),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n";{"hours":5,"days":0,"text":"+05:00"}
The target is five hours later in absolute time, so the result is a positive five-hour interval and zero full days.
Edge cases
- Argument order matters. Swapping base and target flips
invertbut keeps the magnitude the same. daysversusd. Do not usedas a total day count. A gap of two months and five days hasdequal to 5.- Month lengths vary. Month and day parts depend on the calendar dates involved, so do not convert
minto a fixed number of days yourself. - Time zones. Objects with different zones are compared as instants. If you want to compare local calendar dates, create both objects in the same zone first.
- Immutability. The function does not modify either input object.
- Hand-built intervals. An interval you create yourself with
new DateInterval()does not have a computed total-day value, sodaysis only meaningful on results returned by a diff.
Common mistakes
- Reading
$diff->dand treating it as the full number of days between two dates. - Forgetting
invertand showing a negative gap as positive, or the reverse. - Passing the result of a failed
date_create()directly todate_diff(), which causes aTypeErrorbecausefalseis not a date object. - Comparing a date-only value with a date-and-time value and being surprised by leftover hours. A date string without a time is treated as midnight.
- Mixing time zones unintentionally, for example by building one object from a string with an offset and another from the default zone.
Related functions
DateTime::diff(): the method form of the same operation.date_create(): builds aDateTimefrom a string.date_add()anddate_sub(): apply an interval to a date.DateInterval::format(): render an interval as text.date_default_timezone_set(): set the default zone for parsing strings without an offset.
Recap
date_diff() takes a base and a target date object and returns a DateInterval. Use days for the total day count, invert or %R for direction, and the third argument when you only care about magnitude. Keep time zones consistent, and make sure both arguments are real date objects.
Tested examples
date_default_timezone_set('UTC');
$start = date_create('2024-01-15 08:00:00');
$end = date_create('2024-03-20 14:30:00');
$diff = date_diff($start, $end);
echo json_encode([
'y' => $diff->y,
'm' => $diff->m,
'd' => $diff->d,
'h' => $diff->h,
'i' => $diff->i,
'invert' => $diff->invert,
'days' => $diff->days,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n";{"y":0,"m":2,"d":5,"h":6,"i":30,"invert":0,"days":65}
date_default_timezone_set('UTC');
$a = new DateTimeImmutable('2024-05-10');
$b = new DateTimeImmutable('2024-05-01');
$signed = date_diff($a, $b);
$absolute = date_diff($a, $b, true);
echo json_encode([
'signedInvert' => $signed->invert,
'signedDays' => $signed->days,
'absoluteInvert' => $absolute->invert,
'absoluteDays' => $absolute->days,
'format' => $signed->format('%R%a days'),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n";{"signedInvert":1,"signedDays":9,"absoluteInvert":0,"absoluteDays":9,"format":"-9 days"}
$base = new DateTimeImmutable('2024-01-01 00:00:00+00:00');
$target = new DateTimeImmutable('2024-01-01 00:00:00-05:00');
$diff = date_diff($base, $target);
echo json_encode([
'hours' => $diff->h,
'days' => $diff->days,
'text' => $diff->format('%R%H:%I'),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n";{"hours":5,"days":0,"text":"+05:00"}