2012-03-14 11 views
11

DateTime :: Diff debe calcular un intervalo adecuado y tener en cuenta el horario de verano (DST) y los años bisiestos. Aunque aparentemente no es así. Código de horror:DateTime de PHP :: ¿Diff se equivoca?

$d1 = new DateTime("2011-10-30 01:05:00", new DateTimeZone("Europe/Stockholm")); 
$d2 = new DateTime("2011-10-30 03:05:00", new DateTimeZone("Europe/Stockholm")); 

echo $d1->getOffset()/(60 * 60); 

¡Imprime '2'! Tenga en cuenta que el tiempo UTC = 1h - 2h = 23:05:00 del día anterior.

echo $d2->getOffset()/(60 * 60); 

Prints '1'. DST sucedió. Hora UTC = 3h - 1h = 02:05:00.

$di = $d1->diff($d2); 
echo "Hours of DateInterval: " . $di->h; 

Prints '2'! ¿Incorrecto?

$hoursofdiff = ($d2->getTimeStamp() - $d1->getTimeStamp())/60/60; 
echo "Calculated difference in hours: $hoursofdiff"; 

Prints '3'! ¿Correcto?

Cuando el reloj marcó 03:00:00 en la fecha indicada, todos los suecos volvieron su reloj una hora a 02:00:00. Eso significa que la cantidad total aprobada entre 01:05 y 03:05 es de tres horas, muy parecido al eco del cálculo manual cuando se usa el TimeStamp de UNIX. Y al igual que calculamos en nuestros dedos si usamos un reloj analógico. Aún más cuando calculamos la diferencia entre las dos marcas de tiempo UTC que obtuve utilizando la propia lógica de compensaciones de PHP (!).

¿Es PHP o mi cerebro dejó de funcionar correctamente? ¡Una reprimenda de cualquiera de ustedes, todos los dioses que existen en este sitio, me haría tan feliz!

Estoy usando PHP 5.4 (VC9) en un servidor Apache. Lamentablemente uso Windows 7 x64 como sistema operativo. He probado mi configuración contra todos los reclamos de errores en las clases de Fecha/Hora de PHP (hay un par relacionado con Windows) y puedo confirmar que mi sistema no tiene ninguno. Excepto por el código mencionado anteriormente, no he encontrado ningún otro error. Validé casi todo el código y saqué el libro "Guía de PHP Architect para la programación de fecha y hora" que tenía que ofrecer. Por lo tanto, debo concluir que tiene que ser mi cerebro el que ha incumplido, pero pensé que primero debería probarlo.

+1

Igual en 5.3.6 en OS X y 5.3.9 en Ubuntu – Phil

+0

también efectos 'DateTime :: sub()' y 'DateTime :: add() ' – Phil

Respuesta

9

Tienes razón, PHP actualmente no maneja las transiciones de horario de verano ...

Los informes de fallos #51051 (aún abierto) y #55253 (fijo en PHP 5.3.9) describen los problemas que tiene.

Daniel Convissor ha escrito an RFC trying to address the issue hace un tiempo, pero los registros de cambios no sugieren que esto haya sido abordado. Esperaba que esto se solucionara en 5.4 pero no veo ninguna evidencia de que haya sido.

Cuando/si se implementa, parece que tendrá que agregar "DST" o "ST" a la cadena de tiempo.

La mejor práctica es hacer todos los cálculos de fecha en UTC, que evita este problema.

This DST best practices post es muy informativo también.

+1

Muchas gracias Jonathan, realmente lo aprecio. En el RFC que vinculó, dice "solucionar estos problemas antes de que 5.4 [..] parezca sabio". Como hice todas las pruebas con 5.4, debo concluir que nunca sucedió. He enviado por correo a Daniel y Derick, y sigo este tema en toda su extensión. Es de suma importancia para la aplicación en la que estoy trabajando ahora que obtiene las diferencias de tiempo de forma adecuada. Continuaré y escribiré mi propia clase que extienda DateTime y anule el método diff para producir resultados precisos. ¡Volveré a publicar cuando tenga nueva información! –

+0

Por favor, estamos a punto de extender DateTime aquí para escribir una tonelada de hacks en modify(), entre otros. – taiganaut

+1

... y estoy de regreso nuevamente un año después debido a https://bugs.php.net/bug.php?id=61955 entre otros. La entropía de PHP es unidireccional; solo empeora. – taiganaut

1

Bien, tengo una clase de contenedor trabajando. Calcula el tiempo real pasado. Primero, compara los desplazamientos de UTC y agrega o resta esta diferencia de tiempo al objeto datetime, pasado como argumento. A partir de entonces no necesita hacer nada más que llamar a parent :: diff. Bien, tenía que presentar un one-liner para hackear lo que podría ser otro error en PHP (ver el código fuente a continuación). El método DateTimeDiff: diff calcula el tiempo REAL pasado.Para entender lo que eso significa, le aconsejo que pruebe esta clase usando varias fechas y horas diferentes y para ayudar a su carga de trabajo. También incluí en la parte inferior de este comentario una página HTML bastante simple que escribí. Este enlace puede ser un buen punto de partida para obtener algunas ideas para la fecha y hora combinaciones:

https://wiki.php.net/rfc/datetime_and_daylight_saving_time

Por otra parte, tener en cuenta que cuando tenemos una transición hacia atrás en el horario de verano, algunas combinaciones de fecha/hora pueden pertenecer a ambas zonas horarias. Esta ambigüedad puede hacer que los resultados de esta clase difieran de lo esperado. Por lo tanto, si está pensando seriamente en usar esta clase, desarrolle más y solicite la aclaración del usuario en estos casos.

Aquí tiene, la clase:

<?php 
class DateTimeDiff extends DateTime 
{ 
    public function diff($datetime, $absolute = false) 
    { 
    // Future releases could fix this bug and if so, this method would become counterproductive. 
    if (version_compare(PHP_VERSION, '5.4.0') > 0) 
     trigger_error("You are using a PHP version that might have adressed the problems of DateTime::diff", E_USER_WARNING); 

    // Have the clock changed? 
    $offset_start = $this->getOffset(); 
    $offset_end = $datetime->getOffset(); 

    if ($offset_start != $offset_end) 
    { 
     // Remember the difference. 
     $clock_moved = $offset_end - $offset_start; 

     // We wouldn't wanna fuck things up for our caller; thus work on a clone. 
     $copy = clone $datetime; 


     if ($clock_moved > 0) 
     { 
      $timestamp_beforesub = $copy->getTimestamp(); 

      // Subtract timedifference from end-datetime should make parent::diff produce accurate results. 
      $copy->sub(DateInterval::createFromDateString("$clock_moved seconds")); 

      // No change occured; sometimes sub() fails. This is a workable hack. 
      if ($timestamp_beforesub == $copy->getTimestamp()) 
       $copy->setTimezone(new DateTimeZone("UTC")); 
     } 

     else // ..else < 0 and its a negative. 
     { 
      $clock_moved *= -1; 

      // Adding that timedifference to end-datetime should make parent::diff produce accurate results. 
      $copy->add(DateInterval::createFromDateString("$clock_moved seconds")); 
     } 

     return parent::diff($copy, $absolute); 
    } // <-- END "if ($offset_start != $offset_end)" 

    return parent::diff($datetime, $absolute); 
    } 
} 
?> 

y una página de prueba (se mostrará resultados utilizando tanto DateTime :: diff y DateTimeDiff :: diff):

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml"> 
<head> 
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
<title>DateTimeDiff-class</title> 

<?php 
if (! (empty($_GET['identifier']) && empty($_GET['start']) && empty($_GET['end']))) 
{ 
    $dt1_new = new DateTimeDiff("{$_GET['start']} {$_GET['identifier']}"); 
    $dt1_old = new DateTime("{$_GET['start']} {$_GET['identifier']}"); 

    $dt2 = new DateTime("{$_GET['end']} {$_GET['identifier']}"); 

    $di_new = $dt1_new->diff($dt2); 
    $di_old = $dt1_old->diff($dt2); 


    // Extract UNIX timestamp and transitional data 
    $timezone_start = $dt1_new->getTimezone(); 
    $timezone_end = $dt2->getTimezone(); 

    $timestamp_start = $dt1_new->getTimeStamp(); 
    $timestamp_end = $dt2->getTimeStamp(); 

    $transitions_start = $timezone_start->getTransitions($timestamp_start, $timestamp_start); 
    $transitions_end = $timezone_end->getTransitions($timestamp_end, $timestamp_end); 

    echo <<<BUILDCONTAINER 

    <script type='text/javascript'> 

     function Container() { } 
     var c_new = new Container; 
     var c_old = new Container; 
     var t_start = new Container; 
     var t_end = new Container; 

    </script> 

BUILDCONTAINER; 

    echo <<<SETTRANSITIONS 

    <script type='text/javascript'> 

     t_start.ts = '{$transitions_start[0]['ts']}'; 
     t_start.time = '{$transitions_start[0]['time']}'; 
     t_start.offset = '{$transitions_start[0]['offset']}'; 

     t_end.ts = '{$transitions_end[0]['ts']}'; 
     t_end.time = '{$transitions_end[0]['time']}'; 
     t_end.offset = '{$transitions_end[0]['offset']}'; 

    </script> 

SETTRANSITIONS; 

    foreach ($di_new as $property => $value) 
     echo "<script type='text/javascript'>c_new.$property = $value</script>"; 

    foreach ($di_old as $property => $value) 
     echo "<script type='text/javascript'>c_old.$property = $value</script>"; 
} 
?> 

<script type='text/javascript'> 

window.onload = function() 
{ 
    if (c_new != null) // <-- em assume everything else is valid too. 
    { 
     // Update page with the results 
     for (var prop in c_new) 
      addtext(prop + ": " + c_new[prop] + " (" + c_old[prop] + ")"); 

     addtext("Read like so.."); 
     addtext("PROPERTY of DateInterval: VALUE using DateTimeDiff::diff ( VALUE using DateTime::diff )"); 

     // Restore values sent/recieved 
     <?php 

      foreach ($_GET as $key => $value) 
       echo "document.getElementById('$key').value = '$value';"; 

     ?> 

     // Display transitiondata (For DateTime start) 
     var p_start = document.getElementById('p_start'); 
     var appendstring = "TS: " + t_start.ts + ", Time: " + t_start.time + ", Offset: " + t_start.offset; 
     p_start.appendChild(document.createTextNode(appendstring)); 

     // Display transitiondata (For DateTime end) 
     var p_end = document.getElementById('p_end'); 
     appendstring = "TS: " + t_end.ts + ", Time: " + t_end.time + ", Offset: " + t_end.offset; 
     p_end.appendChild(document.createTextNode(appendstring)); 
    } 
} 

function addtext() 
{ 
    var p = document.createElement("p"); 
    p.appendChild(document.createTextNode(arguments[0])); 
    document.forms[0].appendChild(p); 
} 

</script> 

</head> 
<body> 
<form action="test2.php" method="get"> 

    <p>Identifier: <input type="text" name="identifier" id="identifier" value="Europe/Stockholm" /></p> 
    <p id="p_start">Start: <input type="text" name="start" id="start" /></p> 
    <p id="p_end">End: <input type="text" name="end" id="end" /></p> 
    <p><input type="submit" /></p> 

</form> 
</body> 
</html> 
4
Cuestiones relacionadas