it-swarm.com.de

MySQL-Datetime-Felder und Sommerzeit - Wie verweise ich auf die "zusätzliche" Stunde?

Ich verwende die Zeitzone Amerika/New York. Im Herbst "fallen" wir eine Stunde zurück - effektiv "gewinnen" wir eine Stunde um 2 Uhr morgens. Am Übergangspunkt geschieht Folgendes: 

es ist 01:59:00 -04: 00
1 Minute später wird es zu:
.__: 01:00:00 -05: 00

Wenn Sie also einfach "1:30 Uhr" sagen, ist es unklar, ob Sie sich auf das erste Mal um 1:30 oder auf das zweite Mal beziehen. Ich versuche, Zeitplandaten in einer MySQL-Datenbank zu speichern und kann nicht bestimmen, wie die Zeiten richtig gespeichert werden.

Hier ist das Problem:
"2009-11-01 00:30:00" wird intern als 2009-11-01 00:30:00 -04: 00 gespeichert
"2009-11-01 01:30:00" wird intern als 2009-11-01 01:30:00 -05: 00 gespeichert.

Dies ist in Ordnung und wird erwartet. Aber wie speichere ich etwas um 01:30:00 -04: 00? Die Dokumentation bietet keine Unterstützung für die Angabe des Offsets. Wenn ich also den Versatz angegeben habe, wurde er ordnungsgemäß ignoriert.

Die einzige Lösung, an die ich gedacht habe, besteht darin, den Server auf eine Zeitzone einzustellen, in der keine Sommerzeit verwendet wird, und die erforderlichen Transformationen in meinen Skripts vorzunehmen (ich verwende dazu PHP). Aber das scheint nicht nötig zu sein.

Vielen Dank für Ihre Anregungen.

76
Aaron

Die Datumstypen von MySQL sind offenkundig fehlerhaft und können all -Zeiten nicht korrekt speichern, es sei denn, Ihr System ist auf eine Zeitzone mit konstantem Versatz eingestellt, wie UTC oder GMT-5. (Ich verwende MySQL 5.0.45)

Dies liegt daran, dass Sie können in der Stunde vor dem Ende der Sommerzeit keine Zeit speichern . Unabhängig davon, wie Sie ein Datum eingeben, jede Datumsfunktion behandelt diese Zeiten so, als ob sie in der Stunde nach der Umstellung wären.

Die Zeitzone meines Systems ist America/New_York. Versuchen wir, 1257051600 zu speichern (So, 01. November 2009, 06:00:00 +0100).

Hier wird die proprietäre INTERVAL-Syntax verwendet:

SELECT UNIX_TIMESTAMP('2009-11-01 00:00:00' + INTERVAL 3599 SECOND); # 1257051599
SELECT UNIX_TIMESTAMP('2009-11-01 00:00:00' + INTERVAL 3600 SECOND); # 1257055200

SELECT UNIX_TIMESTAMP('2009-11-01 01:00:00' - INTERVAL 1 SECOND); # 1257051599
SELECT UNIX_TIMESTAMP('2009-11-01 01:00:00' - INTERVAL 0 SECOND); # 1257055200

Selbst FROM_UNIXTIME() liefert keine genaue Uhrzeit.

SELECT UNIX_TIMESTAMP(FROM_UNIXTIME(1257051599)); # 1257051599
SELECT UNIX_TIMESTAMP(FROM_UNIXTIME(1257051600)); # 1257055200

Seltsamerweise wird DATETIME noch innerhalb der "verlorenen" Stunde gespeichert und zurückgegeben (nur in Stringform!), Wenn DST beginnt (z. B. 2009-03-08 02:59:59). Die Verwendung dieser Daten in jeder MySQL-Funktion ist jedoch riskant:

SELECT UNIX_TIMESTAMP('2009-03-08 01:59:59'); # 1236495599
SELECT UNIX_TIMESTAMP('2009-03-08 02:00:00'); # 1236495600
# ...
SELECT UNIX_TIMESTAMP('2009-03-08 02:59:59'); # 1236495600
SELECT UNIX_TIMESTAMP('2009-03-08 03:00:00'); # 1236495600

Der Imbiss: Wenn Sie zu jeder Zeit im Jahr speichern und abrufen müssen, haben Sie einige unerwünschte Optionen:

  1. Setzen Sie die Zeitzone des Systems auf GMT + einige konstante Verschiebung. Z.B. koordinierte Weltzeit
  2. Termine als INTs speichern (wie Aaron herausgefunden hat, ist TIMESTAMP nicht einmal zuverlässig)

  3. Stellen Sie sich vor, der DATETIME-Typ hat eine Zeitzone mit konstantem Versatz. Z.B. Wenn Sie sich in America/New_York befinden, konvertieren Sie Ihr Datum in GMT-5 außerhalb von MySQL und speichern Sie es als DATETIME (dies stellt sich als wesentlich heraus: siehe Antwort von Aaron). Dann müssen Sie mit den Datums-/Zeitfunktionen von MySQL große Sorgfalt walten lassen, da einige davon ausgehen, dass Ihre Werte von der Systemzeitzone stammen, andere (insbesondere Zeitarithmetikfunktionen) sind "timezone agnostic" (sie können sich so verhalten, als wären die Zeiten UTC).

Aaron und ich vermuten, dass die automatisch generierenden TIMESTAMP-Spalten ebenfalls beschädigt sind. Sowohl 2009-11-01 01:30 -0400 als auch 2009-11-01 01:30 -0500 werden als mehrdeutiger 2009-11-01 01:30 gespeichert.

37
Steve Clay

Ich habe es für meine Zwecke herausgefunden. Ich fasse zusammen, was ich gelernt habe (Entschuldigung, diese Notizen sind wortreich; sie sind für meine künftige Empfehlung ebenso wie alles andere).

Im Gegensatz zu dem, was ich in einem meiner vorherigen Kommentare gesagt habe, verhalten sich die Felder DATETIME und TIMESTAMP do unterschiedlich. TIMESTAMP-Felder (wie in den Dokumenten angegeben) nehmen das, was Sie senden, im Format "JJJJ-MM-TT hh: mm: ss" ein und konvertieren es von Ihrer aktuellen Zeitzone in UTC-Zeit. Das Umgekehrte geschieht transparent, wenn Sie die Daten abrufen. DATETIME-Felder führen diese Konvertierung nicht durch. Sie nehmen alles, was Sie ihnen schicken, und speichern es einfach direkt.

Weder die Feldtypen DATETIME noch TIMESTAMP können Daten genau in einer Zeitzone speichern, die DST beachtet. Wenn Sie "2009-11-01 01:30:00" speichern, haben die Felder keine Möglichkeit zu unterscheiden, welche Version von 1.30 Uhr Sie wollten - die Version -04: 00 oder -05: 00.

Ok, also müssen wir unsere Daten in einer Nicht-DST-Zeitzone (wie UTC) speichern. TIMESTAMP-Felder können aus Gründen, die ich erkläre, nicht genau mit diesen Daten umgehen: Wenn Ihr System auf eine DST-Zeitzone eingestellt ist, ist es möglicherweise nicht das, was Sie in TIMESTAMP eingegeben haben. Selbst wenn Sie Daten senden, die Sie bereits in UTC konvertiert haben, werden die Daten in Ihrer lokalen Zeitzone übernommen und eine weitere Konvertierung in UTC durchgeführt. Diese von TIMESTAMP erzwungene lokale-zu-UTC-Hin-zu-Lokal-Rundreise ist verlustreich, wenn Ihre lokale Zeitzone DST (seit "2009-11-01 01:30:00" 2 verschiedene mögliche Zeitpunkten darstellt).

Mit DATETIME können Sie Ihre Daten in jeder beliebigen Zeitzone speichern, und Sie können sicher sein, dass Sie alles zurücksenden, was Sie senden (Sie werden nicht in die verlustreichen Roundtrip-Conversions gezwungen, die die TIMESTAMP-Felder Ihnen schieben). Die Lösung ist also, ein DATETIME-Feld zu verwenden und vor dem Speichern in das Feld von Ihrer Systemzeitzone in die Nicht-DST-Zone umzuwandeln, in der Sie es speichern möchten (ich denke, UTC ist wahrscheinlich die beste Option). Auf diese Weise können Sie die Konvertierungslogik in Ihre Skriptsprache integrieren, um das UTC-Äquivalent von "2009-11-01 01:30:00 -04: 00" oder "" 2009-11-01 01:30 explizit zu speichern: 00 -05: 00 ".

Beachten Sie außerdem, dass die mathematischen Datums-/Uhrzeitfunktionen von MySQL nicht ordnungsgemäß an den DST-Grenzen funktionieren, wenn Sie Ihre Datumsangaben in einer DST-TZ speichern. Umso mehr Grund, in UTC zu sparen.

Kurz gesagt mache ich das jetzt: 

Beim Abrufen der Daten aus der Datenbank:

Interpretieren Sie die Daten aus der Datenbank explizit als UTC außerhalb von MySQL, um einen genauen Unix-Zeitstempel zu erhalten. Ich verwende dazu die strtotime () - Funktion von PHP oder deren DateTime-Klasse. Innerhalb von MySQL kann dies mit den Funktionen CONVERT_TZ () oder UNIX_TIMESTAMP () von MySQL nicht zuverlässig ausgeführt werden, da CONVERT_TZ nur den Wert 'YYYY-MM-DD hh: mm: ss' ausgibt, der an Mehrdeutigkeitsproblemen leidet Die Eingabe erfolgt in der Systemzeitzone, nicht in der Zeitzone, in der die Daten ACTUALLIG (UTC) gespeichert wurden.

Beim Speichern der Daten in der Datenbank:

Konvertieren Sie Ihr Datum in die genaue UTC-Zeit, die Sie außerhalb von MySQL wünschen. Zum Beispiel: Mit der DateTime-Klasse von PHP können Sie "2009-11-01 1:30:00 EST" eindeutig aus "2009-11-01 1:30:00 EDT" angeben, diese in UTC konvertieren und die korrekte UTC-Zeit speichern in Ihr DATETIME-Feld.

Puh. Vielen Dank für den Beitrag und die Hilfe aller. Hoffentlich erspart dies jemand anderen Kopfschmerzen die Straße hinunter.

Übrigens, ich sehe das auf MySQL 5.0.22 und 5.0.27

62
Aaron

Ich denke, dass link von micahwittman die beste praktische Lösung für diese Einschränkungen von MySQL bietet: Stellen Sie die Sitzungszeitzone auf UTC, wenn Sie eine Verbindung herstellen:

SET SESSION time_zone = '+0:00'

Dann senden Sie einfach Unix-Zeitstempel und alles sollte in Ordnung sein.

10
Steve Clay

Dieser Thread hat mich zu einem Freak gemacht, da wir TIMESTAMP Spalten mit On UPDATE CURRENT_TIMESTAMP (dh: recordTimestamp timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP) verwenden, um geänderte Datensätze und ETL in einem Datawarehouse zu verfolgen.

Falls sich jemand wundert, verhalten sich TIMESTAMP in diesem Fall korrekt und Sie können zwischen den beiden ähnlichen Datumsangaben unterscheiden, indem Sie TIMESTAMP in Unix-Zeitstempel konvertieren:

select TestFact.*, UNIX_TIMESTAMP(recordTimestamp) from TestFact;

id  recordTimestamp         UNIX_TIMESTAMP(recordTimestamp)
1   2012-11-04 01:00:10.0   1352005210
2   2012-11-04 01:00:10.0   1352008810
3
Manuel Darveau

Aber wie speichere ich etwas bis 01:30:00 -04: 00?

Sie können wie folgt in UTC konvertieren:

SELECT CONVERT_TZ('2009-11-29 01:30:00','-04:00','+00:00');


.__ Noch besser, speichern Sie die Datumsangaben als ZEITSTEMPEL Feld. Das wird immer in UTC gespeichert, und UTC kennt die Sommer-/Winterzeit nicht.

Sie können von UTC in localtime mit CONVERT_TZ konvertieren:

SELECT CONVERT_TZ(UTC_TIMESTAMP(),'+00:00','SYSTEM');

Wobei '+00: 00' UTC ist, die 'from' Zeitzone und 'SYSTEM' die lokale Zeitzone des Betriebssystems, in dem MySQL läuft.

3
Andomar

Mysql löst dieses Problem von Natur aus mit der time_zone_name-Tabelle von mysql db. Verwenden Sie CONVERT_TZ während CRUD, um die Uhrzeit zu aktualisieren, ohne sich um die Sommerzeit kümmern zu müssen.

SELECT
  CONVERT_TZ('2019-04-01 00:00:00','Europe/London','UTC') AS time1,
  CONVERT_TZ('2019-03-01 00:00:00','Europe/London','UTC') AS time2;
2
Arpan Jain

Ich habe daran gearbeitet, die Anzahl der Besuche von Seiten zu protokollieren und die Anzahl in der Grafik anzuzeigen (mithilfe des Flot jQuery-Plugins). Ich füllte die Tabelle mit Testdaten und alles sah gut aus, aber ich bemerkte, dass die Punkte am Ende des Graphen um einen Tag frei waren, entsprechend den Bezeichnungen auf der X-Achse. Nach der Prüfung fiel mir auf, dass die Anzahl der Aufrufe für den Tag 2015-10-25 zweimal aus der Datenbank abgerufen und an Flot übergeben wurde. Daher wurde jeder Tag nach diesem Datum um einen Tag nach rechts verschoben.
Nachdem ich eine Weile nach einem Fehler in meinem Code gesucht hatte, wurde mir klar, dass dieses Datum der Zeitpunkt ist, an dem die DST stattfindet. Dann kam ich zu dieser SO Seite ...
Aber die vorgeschlagenen Lösungen waren ein Overkill für das, was ich brauchte oder sie hatten andere Nachteile. Ich bin nicht sehr besorgt darüber, nicht zwischen mehrdeutigen Zeitstempeln unterscheiden zu können. Ich muss nur Datensätze pro Tag zählen und anzeigen.

Zuerst rufe ich den Datumsbereich ab:

SELECT 
    DATE(MIN(created_timestamp)) AS min_date, 
    DATE(MAX(created_timestamp)) AS max_date 
FROM page_display_log
WHERE item_id = :item_id

Dann rufe ich in einer for-Schleife, beginnend mit min_date, endend mit max_date, Schritt für einen Tag (60*60*24) die Zählungen ab:

for( $day = $min_date_timestamp; $day <= $max_date_timestamp; $day += 60 * 60 * 24 ) {
    $query = "
        SELECT COUNT(*) AS count_per_day
        FROM page_display_log
        WHERE 
            item_id = :item_id AND
            ( 
                created_timestamp BETWEEN 
                '" . date( "Y-m-d 00:00:00", $day ) . "' AND
                '" . date( "Y-m-d 23:59:59", $day ) . "'
            )
    ";
    //execute query and do stuff with the result
}

Mein endgültige und schnelle Lösung zu mein Problem bestand darin:

$min_date_timestamp += 60 * 60 * 2; // To avoid DST problems
for( $day = $min_date_timestamp; $day <= $max_da.....

Ich bin also starrt die Schleife nicht zu Beginn des Tages an, sondern zwei Stunden später. Der Tag ist immer noch derselbe, und ich rufe immer noch korrekte Werte ab, da ich die Datenbank explizit nach Datensätzen zwischen 00:00:00 und 23:59:59 des Tages frage, unabhängig von der tatsächlichen Zeit des Zeitstempels. Und wenn die Zeit um eine Stunde springt, bin ich immer noch am richtigen Tag.

Anmerkung: Ich weiß, dass dies ein 5 Jahre alter Thread ist, und ich weiß, dass dies keine Antwort auf die OP-Frage ist, aber es könnte Leuten wie mir helfen, die auf diese Seite gestoßen sind und nach einer Lösung für das beschriebene Problem suchen.

0
Lukas