2012-03-14 16 views
11

DateTime :: Diff dovrebbe calcolare un intervallo appropriato e tenere conto del periodo di ora legale (DST) e degli anni bisestili. Anche se a quanto pare non è così. Codice horror:DateTime :: Diff di PHP sbaglia?

$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); 

Stampe "2"! Tieni presente quindi che il tempo UTC = 1h - 2h = 23:05:00 del giorno prima.

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

Stampe "1". DST è successo. Ora UTC = 3h - 1h = 02:05:00.

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

Stampe "2"! Sbagliato?

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

Stampe "3"! Corretta?

Quando l'orologio girato 03:00:00 alla data indicata, tutti gli svedesi girato il loro orologio indietro di un'ora alle 02:00:00. Ciò significa che l'importo totale passato tra le 01:05 e le 03:05 è di tre ore, proprio come il calcolo manuale emesso quando si utilizza UNIX TimeStamp. E molto simile a quello che calcoliamo sulle nostre dita se usiamo un orologio analogico. A maggior ragione quando calcoliamo la differenza tra i due timestamp UTC, ho usato la logica di offset di PHP (!).

Va PHP o avere il mio cervello ha smesso di funzionare correttamente? Un rimprovero da parte di chiunque di voi tutti gli dei che esistono su questo sito mi renderebbe così felice!

Sto utilizzando PHP 5.4 (VC9) su un Apache server. Purtroppo io uso Windows 7 x64 come sistema operativo. Ho testato il mio setup contro tutte le richieste di bug nelle classi Date/Time di PHP (ce ne sono un paio relative a Windows) e posso confermare che il mio sistema non ne ha. Ad eccezione del codice sopra indicato, non ho trovato altri errori. Ho praticamente convalidato tutto il codice e prodotto il libro "PHP Architect's Guide to Date and Time Programming" che avevo da offrire. Quindi devo concludere che deve essere il mio cervello che la strega è inadempiente, ma ho pensato di dargli un primo scatto qui.

+1

Stesso sulla 5.3.6 su OS X e 5.3.9 su Ubuntu – Phil

+0

anche effetti 'DateTime :: sub()' e 'DateTime :: add() ' – Phil

risposta

9

Hai ragione, PHP attualmente non gestisce le transizioni DST ...

segnalazioni di bug #51051 (ancora aperto) e #55253 (fissata in PHP 5.3.9) descrivere i problemi si hanno.

Daniel Convissor ha scritto an RFC trying to address the issue un po 'indietro ma i registri delle modifiche non suggeriscono che questo è stato risolto. Speravo che ciò sarebbe stato corretto in 5.4 ma non vedo alcuna prova che sia stato.

Quando/se viene attuata, sembra che si dovrà aggiungere "DST" o "ST" alla stringa di tempo.

È consigliabile eseguire tutti i calcoli della data in UTC, per evitare questo problema.

This DST best practices post è molto istruttiva anche.

+1

Grazie mille Jonathan, lo apprezzo davvero. Nella RFC che hai collegato, dice "Ottenere questi problemi raddrizzati prima del 5.4 [..] sembra saggio". Poiché ho eseguito tutti i miei test usando 5.4, devo concludere che non è mai successo. Ho inviato sia a Daniel che a Derick e sto portando avanti questo problema nella sua massima estensione. È della massima importanza per l'applicazione su cui sto lavorando in questo momento che ottiene correttamente le differenze di orario. Andrò avanti e scriverò la mia classe che estende DateTime e sovrascrive il metodo diff per produrre risultati accurati. Posterò indietro ogni volta che avrò nuove informazioni! –

+0

Per favore, siamo sul punto di estendere DateTime qui per scrivere una tonnellata di hack in modify(), tra gli altri. – taiganaut

+1

... e sono tornato di nuovo un anno dopo a causa di https://bugs.php.net/bug.php?id=61955 tra gli altri. L'entropia di PHP è unidirezionale; peggiora sempre e solo – taiganaut

1

OK ho ottenuto una classe wrapper funzionante. Calcola il tempo reale passato. Per prima cosa confronta gli offset di UTC e aggiunge o sottrae questa differenza di tempo all'oggetto datetime passato come argomento. Da allora in poi non deve fare altro che chiamare genitore: diff. Bene, ho dovuto introdurre un one-liner per hackerare quello che potrebbe essere un altro bug in PHP (vedi il codice sorgente sotto). DateTimeDiff: metodo diff calcola il tempo reale passato.Per capire che cosa significa, ti consiglio di testare questa lezione usando diverse date e orari e, per aiutare il tuo carico di lavoro, ho incluso anche in fondo a questo commento una semplice pagina HTML che ho scritto. Questo collegamento potrebbe essere un buon punto di partenza per avere alcune idee per le combinazioni di data e ora:

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

Inoltre, prendere atto che quando abbiamo un passaggio indietro in DST, alcune combinazioni di data/ora possono appartenere ad entrambi Fusi orari. Questa ambiguità può rendere i risultati di questa classe diversi da quanto previsto. Quindi se stai seriamente pensando di usare questa classe, sviluppala ulteriormente e chiedi chiarimenti all'utente in questi casi.

Ecco, la classe:

<?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); 
    } 
} 
?> 

e una pagina per i test (visualizzerà i risultati utilizzando sia DateTime :: diff e 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
Problemi correlati