php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #62327 Calculation error in DateTime::add() or DateTime::diff()
Submitted: 2012-06-14 19:04 UTC Modified: 2021-10-14 13:08 UTC
Votes:2
Avg. Score:4.0 ± 1.0
Reproduced:1 of 1 (100.0%)
Same Version:2 (200.0%)
Same OS:2 (200.0%)
From: helmut at qweb dot at Assigned: cmb (profile)
Status: Not a bug Package: Date/time related
PHP Version: Irrelevant OS: Linux, Windows
Private report: No CVE-ID: None
 [2012-06-14 19:04 UTC] helmut at qweb dot at
Description:
------------
The task was to shift a date-interval like [ begin: 2012-05-07, end: 2012-06-01 ] to another begin date like 2012-05-21 - an end date 2012-06-15 is expected (first example). The function shift_datetimeobject() uses DateTime::diff() and DateTime::add() to calculate the end date - it fails in the 3rd of 4 attempts.

The second function shift_timestamp() uses timestamp to solve the same task. it works like expected.

The bug was found on Windows Server 2008/Apache 2.4.2/PHP 5.4.4 and on Debian Linux squeeze/PHP 5.3.3-7+squeeze9 with Suhosin-Patch/cli and libapache2-mod-php5 5.3.3-7+squeeze9

Test script:
---------------
function shift_datetimeobject($begin1, $end1, $begin2) {
    
    $dt_begin1 = new DateTime($begin1);
    $dt_end1 =  new DateTime($end1);

    $span = $dt_begin1->diff($dt_end1);

    $dt_return = new DateTime($begin2);
    return array($dt_return->add($span), $span);
}
function shift_timestamp($begin1, $end1, $begin2) {
    
    $dt_begin1 = new DateTime($begin1);
    $st_begin1 = $dt_begin1->getTimestamp();

    $dt_end1 =  new DateTime($end1);
    $st_end1 = $dt_end1->getTimestamp();

    $span = $st_end1 - $st_begin1;

    $dt_begin2 = new DateTime($begin2);
    $st_begin2 = $dt_begin2->getTimestamp();

    $st_end2 = $st_begin2 + $span;
    $dt_end2 = new DateTime();
    return $dt_end2->setTimestamp($st_end2);   
}

$params = array(
    array('2012-05-07', '2012-06-01', '2012-05-21'),
    array('2012-06-11', '2012-07-13', '2012-06-18'),
    array('2012-06-25', '2012-07-27', '2012-07-02'),
    array('2012-07-09', '2012-07-27', '2012-07-16'),
);
foreach ($params as $p) {
    $erg_object = shift_datetimeobject($p[0], $p[1], $p[2]);
    $erg_stamp  =      shift_timestamp($p[0], $p[1], $p[2]);    
    printf("Params: %s, %s, %s\n", $p[0], $p[1], $p[2]);
    printf('$erg_stamp     = %s' . "\n", $erg_stamp->format('Y-m-d'));
    printf('$erg_object[0] = %s' . "\n", $erg_object[0]->format('Y-m-d'));
    printf("span (object)  = %s\n", $erg_object[1]->format('%a days %H:%I:%S'));
    if ($erg_object[0] == $erg_stamp) {
        echo "OK";
    } else {
        echo "WRONG!";
    }
    echo "\n\n";
}

Expected result:
----------------
Params: 2012-05-07, 2012-06-01, 2012-05-21
Result: 2012-06-15

Params: 2012-06-11, 2012-07-13, 2012-06-18
Result: 2012-07-20

Params: 2012-06-25, 2012-07-27, 2012-07-02
Result: 2012-08-03

Params: 2012-07-09, 2012-07-27, 2012-07-16
Result: 2012-08-03

Actual result:
--------------
Params: 2012-05-07, 2012-06-01, 2012-05-21
$erg_stamp     = 2012-06-15
$erg_object[0] = 2012-06-15
span (object)  = 25 days 00:00:00
OK

Params: 2012-06-11, 2012-07-13, 2012-06-18
$erg_stamp     = 2012-07-20
$erg_object[0] = 2012-07-20
span (object)  = 32 days 00:00:00
OK

Params: 2012-06-25, 2012-07-27, 2012-07-02
$erg_stamp     = 2012-08-03
$erg_object[0] = 2012-08-04
span (object)  = 32 days 00:00:00
WRONG!

Params: 2012-07-09, 2012-07-27, 2012-07-16
$erg_stamp     = 2012-08-03
$erg_object[0] = 2012-08-03
span (object)  = 18 days 00:00:00
OK

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2017-07-09 13:54 UTC] xKhorasan+php at gmail dot com
Submitted a pull request to derickr/timelib.
https://github.com/derickr/timelib/pull/15
 [2021-07-27 03:52 UTC] antonino dot spampinato86 at gmail dot com
Is the expected result, diff is likely to be unique for two dates. The php arithmetic is not one to one like the human one, in fact a month can be 30 days, 31, 28 or 29 if leap year. I show you the raw code, where I have voluntarily created and overwritten the variable $interval, if you try to comment on the second one will get an incorrect result but this is because the calculation is not one to one but as I explained above (diff is almost always unique ).

https://3v4l.org/MKeKE
This not a bug :)
 [2021-10-14 13:08 UTC] cmb@php.net
-Status: Open +Status: Not a bug -Assigned To: +Assigned To: cmb
 [2021-10-14 13:08 UTC] cmb@php.net
Indeed, this is not a bug, because a DateInterval is not
necessarily a time span in seconds.

> Params: 2012-06-25, 2012-07-27, 2012-07-02
> Result: 2012-08-03

The difference between the first two dates is 1 month and 2 days.
Adding that to the third date gives 2012-07-04.  See
<https://3v4l.org/7d0dn>.
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Tue Mar 19 02:01:28 2024 UTC