phpdatetimedateinterval

PHP DateInterval inconsistent days count


Take the following PHP code:

<?php
$timezone = new DateTimeZone('UTC');
$startDate = new DateTime('2022-09-26T00:00:00', $timezone);
$endDate1 = new DateTime('2023-02-28T00:00:00', $timezone);
$endDate2 = new DateTime('2023-03-01T00:00:00', $timezone);
$interval1 = $endDate1->diff($startDate);
$interval2 = $endDate2->diff($startDate);
print_r($interval1);
print_r($interval2);

And here's the output:

DateInterval Object
(
    [y] => 0
    [m] => 5
    [d] => 2
    [h] => 0
    [i] => 0
    [s] => 0
    [f] => 0
    [weekday] => 0
    [weekday_behavior] => 0
    [first_last_day_of] => 0
    [invert] => 1
    [days] => 155
    [special_type] => 0
    [special_amount] => 0
    [have_weekday_relative] => 0
    [have_special_relative] => 0
)
DateInterval Object
(
    [y] => 0
    [m] => 5
    [d] => 5
    [h] => 0
    [i] => 0
    [s] => 0
    [f] => 0
    [weekday] => 0
    [weekday_behavior] => 0
    [first_last_day_of] => 0
    [invert] => 1
    [days] => 156
    [special_type] => 0
    [special_amount] => 0
    [have_weekday_relative] => 0
    [have_special_relative] => 0
)

Can someone please explain why first interval's y-m-d is 0-5-2 when days count is 155, but second interval's y-m-d is 0-5-5 even if only 1 day was added (infact the days count is 156)?

I tried to port the same code in other languages (JavaScript and python to be precise) and the result for the first interval is the same as PHP's one: 5 months and 2 days. However the result for the second interval is 5 months and 3 days (as i would expect) and not 5 months and 5 days as PHP says.


Solution

  • PHP finds the difference between two dates by naively computing the difference between all components of a timestamp. (years, months, days, etc. (ref)) So the second interval would look like (y=1, m=-6, d=-25) at this stage. However, this makes little sense to us humans, so PHP tries to normalize this interval by adjusting each field if the next one down would over- or underflow. (ref). But months do not contain a consistent amount of days, so PHP tries to be smart and does day-to-month adjustments based on the length of the month we started in. (ref)

    So if the first date is in February, the days field will be adjusted by 28 days: (y=1, m=-7, d=2), while March has 31 days, yielding (y=1, m=-7, d=5). The next step for both intervals is the month adjustment, which is identical. A year has twelve months, so we get (y=0, m=5, d=2) and (y=0, m=5, d=5).


    Knowing this, we can have some more fun with PHP being weird

    $start = new DateTime('2022-09-26');
    $end = new DateTime('2023-03-01');
    
    $start->diff($end); // +5m 3d
    $end->diff($start); // -5m 5d
    

    EDIT: My first assumption on how PHP worked internally turned out to be incorrect, so the answer above has been rewritten after @KIKO Software posed their question.