Issues

ZF-3165: Zend_Date/Zend_TimeSync: Undefined index: offset

Description

I used the timeserver "ntp://ntps1-1.rz.uni-osnabrueck.de" for some tests and it worked fine, but today I got the following error: Notice: Undefined index: offset in [...]Zend\Date.php on line 273

The result is a valid instance of the Zend_TimeSync_Protocol, but the getInfo() method returns an empty array.

The code: ... $serverlist = array( 'ntp://ntps1-1.rz.uni-osnabrueck.de', 'ntp://swisstime.ethz.ch', );

$server = new Zend_TimeSync($serverlist); $servers = $server->getIterator();

Zend_Date::setOptions(array('timesync' => $servers[0])); ...

Comments

Are you sure the timeserver works ? Can you dump us the $server object ?

I don't think that the servers are valid (at the moment), but that's not the problem.

I passed a valid instance of the Zend_TimeSync_Protocol (althougth the server isn't valid), and Zend_Date expects that an array key exists which does not exist - so this should be checked and throw an exception. I know the posted code isn't a good way (got it from an tutorial) - if I use the getServer() method I can check if there is a valid server or not.

Here's the $server object:

object(Zend_TimeSync)#319 (3) { ["_timeservers:protected"]=> array(2) { [0]=> object(Zend_TimeSync_Ntp)#312 (5) { ["_port:protected"]=> int(123) ["_socket:protected"]=> NULL ["_exceptions:protected"]=> NULL ["_timeserver:protected"]=> string(34) "udp://ntps1-1.rz.uni-osnabrueck.de" ["_info:protected"]=> array(0) { } } [1]=> object(Zend_TimeSync_Ntp)#311 (5) { ["_port:protected"]=> int(123) ["_socket:protected"]=> NULL ["_exceptions:protected"]=> NULL ["_timeserver:protected"]=> string(23) "udp://swisstime.ethz.ch" ["_info:protected"]=> array(0) { } } } ["_current:protected"]=> NULL ["_allowedSchemes:protected"]=> array(2) { [0]=> string(3) "Ntp" [1]=> string(4) "Sntp" } }

No, the thing is that Zend_TimeSync should throw an exception when no time can be evaluated, and not Zend_Date.

Where does the code you used come from ???

It can not have worked in my opinion. When you look at the manual you will see where the problem is.

See this code from the manual:


<?php
    require_once 'Zend/TimeSync.php';
    $server = new Zend_TimeSync('0.pool.ntp.org');
    print $server->getDate()->getIso();

]

Zend_Timesync returns you a Zend_Date object and not reverse.

Zend_Date does not return a Zend_Timesync object, but uses it as an option for all instances of Zend_Date to correct times (Zend_Date_DateObject::$_defaultOffset).

{quote}Normally the clocks from servers and computers differ from each other. Zend_Date is able to handle such problems with the help of Zend_TimeSync. You can set a timeserver with Zend_Date::setOptions(array('timesync' => $timeserver)); which will set the offset between the own actual timestamp and the real actual timestamp for all instances of Zend_Date. Using this option does not change the timestamp of existing instances. So best usage is to set it within the bootstrap file.{quote}

See here: http://framework.zend.com/manual/en/…

(PS: Dunno where the code was from)

To get your code working you should simply call getDate after assigning the wished timeserver.

Beside, your code is wrong in another point.

You create a timesync object with 2 servers for redundancy. But then you assign only one server to Zend_Date which results in problems when exact this server can not be reached.

Assign the Zend_TimeSync object to Zend_Date. getIterator makes no sense in this point... the code is wrong.

Also before attaching it to Zend_Date you have to receive the timesync which means that you call getDate and then assign it to Zend_Date.

This way your code will work.

{quote}But then you assign only one server to Zend_Date which results in problems when exact this server can not be reached.{quote} That's right. If a valid server ist found, the code works.

{quote}Assign the Zend_TimeSync object to Zend_Date.{quote} Ever tried this? Then you'll get an exception with the message "Instance of Zend_TimeSync expected" - although it's checked for an instance of "Zend_TimeSync_Protocol" (and "Zend_TimeSync" isn't). So there is definitely an error in the code.

{quote}getIterator makes no sense in this point... the code is wrong.{quote} That's right, as I already mentioned before this is not a good solution, but it's a fact that an valid instance of an object is given and that the Zend_Date class expects that there is a variable which does not exist and which is not checked.

{quote}Also before attaching it to Zend_Date you have to receive the timesync which means that you call getDate and then assign it to Zend_Date.{quote} Sounds strange and unhandy. If that is right, it should be mentioned in the manual - or done automatically by Zend_date::setOption().

It's really neccessary, that the method getDate() ist called, so that's the final soulution, which is not documented:


...
            $serverlist = array(
                'ntp://0.de.pool.ntp.org',
                'ntp://1.de.pool.ntp.org',
                'ntp://2.de.pool.ntp.org',
                'ntp://3.de.pool.ntp.org',
                'ntp://4.de.pool.ntp.org',
            ); 
            $server = new Zend_TimeSync($serverlist);
            $server->getDate(); // sets the valid server
            
            $selected_server = $server->getServer(); //throws an exception if no valid server found
            
            Zend_Date::setOptions(array('timesync' => $selected_server));
...

But there are still errors in Zend_Date: - a wrong instance check (or a wrong error message) - a missing check for the array key "offset"

Here's my fix, which allows to use an instance of "Zend_TimeSync" or "Zend_TimeSync_Protocol", with a corrected exception message, and with a check for the missing key:


...
                    case 'timesync' :
                        if ($value instanceof Zend_TimeSync) {
                            try {
                                $value = $value->getServer();
                            }
                            catch (Zend_TimeSync_Exception $e) {
                                $value->getDate();
                                $value = $value->getServer();
                            }
                        }
                        if (!$value instanceof Zend_TimeSync_Protocol) {
                            require_once 'Zend/Date/Exception.php';
                            throw new Zend_Date_Exception("Instance of Zend_TimeSync_Protocol expected");
                        }
                        $date = $value->getInfo();
                        if (isset($date['offset']))
                        {
                            parent::$_defaultOffset = $date['offset'];
                        }
                        break;
...

This allowes the following use:


...
            $serverlist = array(
                'ntp://0.de.pool.ntp.org',
                'ntp://1.de.pool.ntp.org',
                'ntp://2.de.pool.ntp.org',
                'ntp://3.de.pool.ntp.org',
                'ntp://4.de.pool.ntp.org',
            ); 
            $server = new Zend_TimeSync($serverlist);
            $server->getDate(); // sets the valid server
            
            Zend_Date::setOptions(array('timesync' => $server));
...

or just


...
            $serverlist = array(
                'ntp://0.de.pool.ntp.org',
                'ntp://1.de.pool.ntp.org',
                'ntp://2.de.pool.ntp.org',
                'ntp://3.de.pool.ntp.org',
                'ntp://4.de.pool.ntp.org',
            ); 
            $server = new Zend_TimeSync($serverlist);

            Zend_Date::setOptions(array('timesync' => $server));
...

I havn't had said that we will not fix it... I only showed you a solution to your problem beside this error.

Okay ;)

Fixed with r9460

Updating for the 1.6.0 release.