Mysql
 sql >> Database >  >> RDS >> Mysql

PHP, MySQL e fusi orari

Questa risposta è stata aggiornata per accogliere la taglia. La risposta originale e non modificata è sotto la linea.

Quasi tutti i punti interrogativi aggiunti dal proprietario del bounty sono in relazione a come MySQL e PHP dovrebbero interagire datetime, nel contesto dei fusi orari.

MySQL ha ancora patetico supporto per il fuso orario , il che significa che l'intelligence deve essere lato PHP.

  • Imposta il fuso orario della connessione di MySQL a UTC come documentato nel link sopra. Ciò causerà tutti i datetime gestiti da MySQL, incluso NOW() , da trattare in modo sano.
  • Usa sempre DATETIME , non utilizzare mai TIMESTAMP a meno che tu non richieda espressamente il comportamento speciale in un TIMESTAMP . Questo è meno doloroso di prima.
    • Va ok per memorizzare l'epoca di Unix come un numero intero se hai a, ad esempio per scopi legacy. L'epoca è UTC.
    • Il formato datetime preferito di MySQL viene creato utilizzando la stringa di formato data PHP Y-m-d H:i:s
  • Converti tutti PHP datetime a UTC quando li si memorizza in MySQL, che è una cosa banale come descritto di seguito
  • Le date restituite da MySQL possono essere consegnate in sicurezza al costruttore PHP DateTime. Assicurati di passare anche in un fuso orario UTC!
  • Convertire il PHP DateTime nel fuso orario locale dell'utente on echo , non prima di. Per fortuna il confronto e la matematica di DateTime con altri DateTime terranno conto del fuso orario in cui si trova ciascuno.
  • Sei ancora all'altezza dei capricci del database DST fornito con PHP. Mantieni aggiornate le tue patch PHP e OS! Mantieni MySQL nello stato felice di UTC per rimuovere un potenziale fastidio dell'ora legale.

Questo si rivolge alla maggior parte dei punti.

L'ultima cosa è stupida:

  • Cosa dovrebbe fare una persona se ha precedentemente inserito dei dati (ad es. utilizzando NOW() ) senza preoccuparsi del fuso orario per assicurarsi che tutto rimanga coerente?

Questo è un vero fastidio. Una delle altre risposte ha evidenziato CONVERT_TZ , anche se personalmente l'avrei fatto saltando tra i fusi orari nativi del server e UTC durante le selezioni e gli aggiornamenti, perché sono un duro così.

l'app dovrebbe anche essere in grado di IMPOSTARE/Scegliere l'ora legale di conseguenza per ogni utente.

Non è necessario e non dovresti fallo nell'era moderna.

Le versioni moderne di PHP hanno il DateTimeZone classe, che include la possibilità di elencare i fusi orari denominati . I fusi orari con nome consentono all'utente di selezionare la propria posizione effettiva e di avere il sistema automaticamente determinare le loro regole dell'ora legale in base a quella posizione.

Puoi combinare DateTimeZone con DateTime per alcune funzionalità semplici ma potenti. Puoi semplicemente archiviare e utilizzare tutto dei tuoi timestamp in UTC per impostazione predefinita e convertili nel fuso orario dell'utente visualizzato.

// UTC default
    date_default_timezone_set('UTC');
// Note the lack of time zone specified with this timestamp.
    $nowish = new DateTime('2011-04-23 21:44:00');
    echo $nowish->format('Y-m-d H:i:s'); // 2011-04-23 21:44:00
// Let's pretend we're on the US west coast.  
// This will be PDT right now, UTC-7
    $la = new DateTimeZone('America/Los_Angeles');
// Update the DateTime's timezone...
    $nowish->setTimeZone($la);
// and show the result
    echo $nowish->format('Y-m-d H:i:s'); // 2011-04-23 14:44:00

Utilizzando questa tecnica, il sistema automaticamente selezionare le impostazioni DST corrette per l'utente, senza chiedere all'utente se è attualmente in DST o meno.

È possibile utilizzare un metodo simile per eseguire il rendering del menu di selezione. È possibile riassegnare continuamente il fuso orario per il singolo oggetto DateTime. Ad esempio, questo codice elencherà le zone e i loro orari attuali, in questo momento:

$dt = new DateTime('now', new DateTimeZone('UTC')); 
foreach(DateTimeZone::listIdentifiers() as $tz) {
    $dt->setTimeZone(new DateTimeZone($tz));
    echo $tz, ': ', $dt->format('Y-m-d H:i:s'), "\n";
}

Puoi semplificare notevolmente il processo di selezione usando un po' di magia lato client. Javascript ha un sporco ma funzionale Classe Date, con un metodo standard per ottenere l'offset UTC in pochi minuti . Puoi usarlo per restringere l'elenco dei fusi orari probabili, partendo dal presupposto cieco che l'orologio dell'utente sia corretto.

Confrontiamo questo metodo con il fai da te. Dovresti effettivamente eseguire la matematica sulla data ogni singola volta manipoli un datetime, oltre a spingere una scelta sull'utente che non gli importerà davvero. Questo non è solo sub-ottimale, è pazzo di guano di pipistrello. Costringere gli utenti a indicare quando vogliono il supporto dell'ora legale è creare problemi e confusione.

Inoltre, se si desidera utilizzare il moderno framework PHP DateTime e DateTimeZone per questo, è necessario usa Etc/GMT... deprecato stringhe di fuso orario invece di fusi orari con nome. Questi nomi di zona potrebbero essere rimossi dalle future versioni di PHP, quindi non sarebbe saggio farlo. Dico tutto questo per esperienza.

tl;dr :Usa il moderno set di strumenti, risparmiati gli orrori della matematica degli appuntamenti. Presenta all'utente un elenco di nomi Fusi orari. Memorizza le tue date in UTC , che non sarà in alcun modo influenzato dall'ora legale. Converti datetime nel fuso orario denominato visualizzato selezionato dall'utente , non prima.

Come richiesto, ecco un loop sui fusi orari disponibili che mostrano il loro offset GMT in minuti. Ho selezionato i minuti qui per dimostrare un fatto sfortunato:non tutti gli offset sono in ore intere! Alcuni effettivamente passano mezz'ora avanti durante l'ora legale invece di un'ora intera. L'offset risultante in minuti dovrebbe corrisponde a quello di Date.getTimezoneOffset di Javascript .

$utc = new DateTimeZone('UTC');
$dt = new DateTime('now', $utc); 
foreach(DateTimeZone::listIdentifiers() as $tz) {
    $local = new DateTimeZone($tz);
    $dt->setTimeZone($local);
    $offset = $local->getOffset($dt); // Yeah, really.
    echo $tz, ': ', 
         $dt->format('Y-m-d H:i:s'),
         ', offset = ',
         ($offset / 60),
         " minutes\n";
}