Dit antwoord is bijgewerkt om tegemoet te komen aan de premie. Het originele, onbewerkte antwoord is onder de streep.
Bijna alle vraagpunten die door de bounty-eigenaar zijn toegevoegd, hebben betrekking op hoe MySQL- en PHP-datetimes zouden moeten interageren, in de context van tijdzones.
MySQL heeft nog steeds zielig tijdzone ondersteuning , wat betekent dat de intelligentie PHP-side moet zijn.
- Stel uw MySQL verbindingstijdzone in naar UTC, zoals gedocumenteerd in de bovenstaande link. Dit zorgt ervoor dat alle datetimes worden afgehandeld door MySQL, inclusief
NOW()
, verstandig te behandelen. - Gebruik altijd
DATETIME
, gebruik nooitTIMESTAMP
tenzij u zeer uitdrukkelijk het speciale gedrag in eenTIMESTAMP
. vereist . Dit is minder pijnlijk dan vroeger.- Het is oke om de Unix-tijdperktijd op te slaan als een geheel getal als je hebt aan, zoals voor legacy-doeleinden. Het tijdperk is UTC.
- De voorkeursdatum/tijd-indeling van MySQL wordt gemaakt met behulp van de PHP-tekenreeks voor datumindeling
Y-m-d H:i:s
- Converteer alles PHP-datetimes naar UTC wanneer ze worden opgeslagen in MySQL, wat een triviaal iets is, zoals hieronder wordt beschreven
- Datetimes die worden geretourneerd door MySQL kunnen veilig worden overgedragen aan de PHP DateTime-constructor. Zorg ervoor dat u ook in een UTC-tijdzone passeert!
- Converteer de PHP DateTime naar de lokale tijdzone van de gebruiker on echo , niet eerder. Gelukkig houden DateTime-vergelijking en wiskunde met andere DateTimes rekening met de tijdzone waarin ze zich bevinden.
- Je bent nog steeds aan de grillen van de DST-database die met PHP wordt geleverd. Houd uw PHP- en OS-patches up-to-date! Houd MySQL in de gelukzalige staat van UTC om een mogelijke DST-ergernis weg te nemen.
Dat richt zich tot de meeste van de punten.
Het laatste is een doozy:
- Wat moet iemand doen als hij eerder gegevens heeft ingevoerd (bijvoorbeeld met
NOW()
) zonder je zorgen te maken over de tijdzone om ervoor te zorgen dat alles consistent blijft?
Dit is een echte ergernis. Een van de andere antwoorden wees op MySQL's CONVERT_TZ
, hoewel ik het persoonlijk zou hebben gedaan door tijdens selecties en updates tussen server-native en UTC-tijdzones te springen, omdat ik zo hardcore ben.
de app zou ook in staat moeten zijn om de DST dienovereenkomstig zelf in te stellen/kiezen voor elke gebruiker.
U hoeft en mag niet doe dit in de moderne tijd.
Moderne versies van PHP hebben de DateTimeZone class, inclusief de mogelijkheid om tijdzones met een naam weer te geven . Met benoemde tijdzones kan de gebruiker zijn werkelijke locatie selecteren en het systeem automatisch bepalen hun DST-regels op basis van die locatie.
Je kunt DateTimeZone combineren met DateTime voor een aantal eenvoudige maar krachtige functionaliteit. U kunt eenvoudig alle . opslaan en gebruiken van uw tijdstempels in UTC standaard en converteer ze naar de tijdzone van de gebruiker die wordt weergegeven.
// 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
Door deze techniek te gebruiken, zal het systeem automatisch selecteer de juiste zomertijd-instellingen voor de gebruiker, zonder de gebruiker te vragen of ze zich momenteel in de zomertijd bevinden.
U kunt een vergelijkbare methode gebruiken om het selectiemenu weer te geven. U kunt de tijdzone voor het enkele DateTime-object voortdurend opnieuw toewijzen. Deze code toont bijvoorbeeld de zones en hun huidige tijden, op dit 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";
}
U kunt het selectieproces aanzienlijk vereenvoudigen door wat magie aan de clientzijde te gebruiken. Javascript heeft een vlekkerig maar functioneel Datumklasse, met een standaardmethode om de UTC-offset in minuten te krijgen . U kunt dit gebruiken om de lijst met waarschijnlijke tijdzones te verfijnen, in de blinde veronderstelling dat de klok van de gebruiker goed staat.
Laten we deze methode vergelijken met het zelf doen. Je zou eigenlijk elke keer de datumberekening moeten uitvoeren je manipuleert een datetime, naast het opdringen van een keuze aan de gebruiker waar ze niet echt om geven. Dit is niet alleen suboptimaal, het is krankzinnig. Gebruikers dwingen aan te geven wanneer ze DST-ondersteuning willen, is vragen om problemen en verwarring.
Verder, als je hiervoor het moderne PHP DateTime en DateTimeZone framework wilt gebruiken, moet je gebruik verouderde Etc/GMT...
tijdzone-tekenreeksen
in plaats van benoemde tijdzones. Deze zonenamen kunnen uit toekomstige PHP-versies worden verwijderd, dus het zou onverstandig zijn om dat te doen. Ik zeg dit allemaal uit ervaring.
tl;dr :Gebruik de moderne toolset, bespaar jezelf de verschrikkingen van datumwiskunde. Presenteer de gebruiker een lijst met genoemde tijdzones. Bewaar uw datums in UTC , die op geen enkele manier door DST wordt beïnvloed. Converteer datetimes naar de door de gebruiker geselecteerde benoemde tijdzone op het display , niet eerder.
Zoals gevraagd, is hier een lus over de beschikbare tijdzones met hun GMT-offset in minuten. Ik heb hier minuten geselecteerd om een ongelukkig feit aan te tonen:niet alle offsets zijn in hele uren! Sommigen schakelen tijdens de zomertijd zelfs een half uur vooruit in plaats van een heel uur. De resulterende offset in minuten moet overeenkomen met die van Javascript's Date.getTimezoneOffset
.
$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";
}