ZF-11438: Zend Date 2038

Description

Though Zend_Date claims to solve the 2038 issue, when I do:

$date = new Zend_Date(); $date->addMonth(350);

I get a date in 1970.

With my limited knowledge of the ZF internals I tracked this down to Zend_Date_DateObject in the (internal) function mktime() where a check is made on $year being between 1901 and 2038. But with months > 12 this would not be the right check resulting in the native mktime() function receiving a date > 2038.

Comments

Could you provide an example?

I attempted to reproduce your issue:


/**
 * @group ZF-11438
 */
public function testAddMonthsDoesNotCauseDateToFallVictimTo2038Bug()
{
    $date = new Zend_Date();
    $date->addMonth(350);
    $this->assertEquals('2040',date("Y", $date->getTimestamp()));
}

However, this test passes when run against SVN trunk.

Sorry to have left this so long, on my system (Ubuntu 11.04, PHP Version 5.3.5-1ubuntu7.2, ZF 1.11.10) this test does not pass. There I was stuck, but looking at the below snippet of the internal mktime function of Zend_Date_DateObject I understand my negative result:

protected function mktime($hour, $minute, $second, $month, $day, $year, $gmt = false)
    {
        // complete date but in 32bit timestamp - use PHP internal
        if ((1901 < $year) and ($year < 2038)) {

            $oldzone = @date_default_timezone_get();
            // Timezone also includes DST settings, therefor substracting the GMT offset is not enough
            // We have to set the correct timezone to get the right value
            if (($this->_timezone != $oldzone) and ($gmt === false)) {
                date_default_timezone_set($this->_timezone);
            }
            $result = ($gmt) ? @gmmktime($hour, $minute, $second, $month, $day, $year)
                             :   @mktime($hour, $minute, $second, $month, $day, $year);
            date_default_timezone_set($oldzone);

            return $result;
        }

This checks for the $year argument but not for the total result of adding months, $years and the other arguments. Unless I miss some subtilities in other parts of the code, this is clearly a bug imho.

E.g. php mktime(0,0,0,350,0,2011) returns false on my system - as does mktime(0,0,0,11,0,2038) but not mktime(0,0,0,1,0,2038) - but these arguments pass the test to qualify for the internal php mktime in the function above. This would not be noticed in most cases because either the year needs to be close to 2038 or the number of months needs to be equivalent to a large number of years, as in my case (I use Zend_Date for actuarial calculations where these extreme cases are not unusual).

Still this does explain why your assertEquals passes ...

Last line of my previous comment should of course be:

Still this does NOT explain why your assertEquals passes ...

Are you using a 32bit OS or 64bit OS as I checked this on Ubuntu 64bit and got:

$date = new Zend_Date(); $date->addMonth(350);

= 3 Nov 2040 22:35:45

Sorry I forgot year 2038+ dates do not work on 32bit machines

32 bit Ubuntu

I now use the following workaround:

// convert months to years to avoid 2038 bug in addMonth() $years = 0;
if (11 < $months) { $years = floor ($months / 12); $months -= 12 * $years; }

with the expected result (date somewhere in 2040)

I reported absolutely the same issue few months earlier, but no response: http://framework.zend.com/issues/browse/…

In description I suggested the solution of this issue. Hope it will help to somebody. For me it works like a charm after mine modifications within internal mktime.