Mysql
 sql >> Base de données >  >> RDS >> Mysql

PHP, MySQL et fuseaux horaires

Cette réponse a été mise à jour pour tenir compte de la prime. La réponse originale non modifiée se trouve sous la ligne.

Presque tous les points d'interrogation ajoutés par le propriétaire de la prime concernent la manière dont les datetimes MySQL et PHP doivent interagir, dans le contexte des fuseaux horaires.

MySQL a toujours pathétique prise en charge du fuseau horaire , ce qui signifie que l'intelligence doit être côté PHP.

  • Définissez votre fuseau horaire de connexion MySQL à UTC comme documenté dans le lien ci-dessus. Cela entraînera toutes les dates et heures gérées par MySQL, y compris NOW() , à manipuler avec discernement.
  • Toujours utiliser DATETIME , n'utilisez jamais TIMESTAMP sauf si vous exigez très expressément le comportement spécial dans un TIMESTAMP . C'est moins douloureux qu'avant.
    • C'est ok pour stocker l'heure d'époque Unix sous forme d'entier si vous avez à, comme à des fins d'héritage. L'époque est UTC.
    • Le format datetime préféré de MySQL est créé à l'aide de la chaîne de format de date PHP Y-m-d H:i:s
  • Convertir tous Datetimes PHP en UTC lors de leur stockage dans MySQL, ce qui est une chose triviale comme indiqué ci-dessous
  • Les dates et heures renvoyées par MySQL peuvent être transmises en toute sécurité au constructeur PHP DateTime. Assurez-vous également de passer dans un fuseau horaire UTC !
  • Convertir le PHP DateTime au fuseau horaire local de l'utilisateur sur echo , pas avant. Heureusement, la comparaison de DateTime et les calculs avec d'autres DateTimes prendront en compte le fuseau horaire dans lequel chacun se trouve.
  • Vous êtes toujours à la hauteur de la base de données DST fournie avec PHP. Gardez vos correctifs PHP et OS à jour ! Gardez MySQL dans l'état heureux de l'UTC pour supprimer un désagrément potentiel de l'heure d'été.

Cela répond à la plupart des points.

La dernière chose est un doozy :

  • Que doit faire quelqu'un s'il a déjà inséré des données (par exemple, en utilisant NOW() ) sans se soucier du fuseau horaire pour s'assurer que tout reste cohérent ?

C'est une vraie gêne. L'une des autres réponses a souligné MySQL CONVERT_TZ , même si je l'aurais personnellement fait en sautant entre les fuseaux horaires natifs du serveur et UTC pendant les sélections et les mises à jour, parce que je suis hardcore comme ça.

l'application devrait également être en mesure de définir/choisir l'heure d'été en conséquence elle-même pour chaque utilisateur.

Vous n'avez pas besoin et ne devriez pas faire cela à l'ère moderne.

Les versions modernes de PHP ont le DateTimeZone class, qui inclut la possibilité de lister les fuseaux horaires nommés . Les fuseaux horaires nommés permettent à l'utilisateur de sélectionner son emplacement réel et de faire en sorte que le système soit automatiquement déterminer leurs règles d'heure d'été en fonction de cet emplacement.

Vous pouvez combiner DateTimeZone avec DateTime pour certaines fonctionnalités simples mais puissantes. Vous pouvez simplement stocker et utiliser tous de vos horodatages en UTC par défaut , et convertissez-les dans le fuseau horaire de l'utilisateur affiché.

// 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

En utilisant cette technique, le système va automatiquement sélectionnez les paramètres d'heure d'été corrects pour l'utilisateur, sans demander à l'utilisateur s'il est actuellement ou non en mode d'heure d'été.

Vous pouvez utiliser une méthode similaire pour afficher le menu de sélection. Vous pouvez continuellement réaffecter le fuseau horaire pour l'objet DateTime unique. Par exemple, ce code listera les zones et leurs heures actuelles, en ce moment :

$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";
}

Vous pouvez grandement simplifier le processus de sélection en utilisant un peu de magie côté client. Javascript a un inégal mais fonctionnel Classe Date, avec une méthode standard pour obtenir le décalage UTC en minutes . Vous pouvez l'utiliser pour réduire la liste des fuseaux horaires probables, en supposant aveuglément que l'horloge de l'utilisateur est correcte.

Comparons cette méthode à le faire vous-même. Vous auriez besoin d'effectuer des calculs de date à chaque fois vous manipulez une date et une heure, en plus de pousser un choix à l'utilisateur dont il ne se souciera pas vraiment. Ce n'est pas seulement sous-optimal, c'est fou de guano de chauve-souris. Forcer les utilisateurs à signifier quand ils veulent une prise en charge DST est source de problèmes et de confusion.

De plus, si vous souhaitez utiliser le framework PHP DateTime et DateTimeZone moderne pour cela, vous devez utiliser Etc/GMT... obsolète chaînes de fuseau horaire au lieu de fuseaux horaires nommés. Ces noms de zone peuvent être supprimés des futures versions de PHP, il serait donc imprudent de le faire. Je dis tout cela par expérience.

tl;dr :Utilisez l'ensemble d'outils modernes, épargnez-vous les horreurs des calculs de date. Présenter à l'utilisateur une liste de nommés fuseaux horaires. Stockez vos dates en UTC , qui ne sera en aucun cas affecté par l'heure d'été. Convertir les dates et heures dans le fuseau horaire nommé sélectionné par l'utilisateur sur l'affichage , pas plus tôt.

Comme demandé, voici une boucle sur les fuseaux horaires disponibles affichant leur décalage GMT en minutes. J'ai sélectionné les minutes ici pour démontrer un fait malheureux :tous les décalages ne sont pas en heures entières ! Certains changent en fait une demi-heure d'avance pendant l'heure d'été au lieu d'une heure entière. Le décalage résultant en minutes devrait correspond à celui de Date.getTimezoneOffset de 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";
}