dedlfix: Richtiges Datensatz-Updatedatum in einer SQL DB

Beitrag lesen

Tach!

Vor 3 Tagen habe ich gefragt: "Was für ein Kommando muss ich nach dem Verbindungsaufbau durch mysqli noch darunter schreiben, um meine gewünschte Zeitzone festzulegen?" Da sagtest Du: "Soweit ich weiß, geht das mit einem Statement 'SET time_zone = timezone'".

SET ...  ist ein SQL-Statement wie SELECT/INSERT/UPDATE/DELETE ... Die setzt du ganz normal mit mit der Methode query() ab. In deinem Fall mit $db-> vor den Methodennamen.

OK, ich _war_ auf der Seite, dort steht auch nur die Syntax 'mysql> SET time_zone = timezone;'.

Das 'mysql>' ist der Prompt von einem Programm namens 'mysql'. Das ist eine Art Shell, in der man direct SQL-Statements absetzen kann. Nach dem 'mysql>' steht das eigentliche Statement, das du auch anderweitig (z.B. über query()) absetzen kannst.

Ich bin völliger Anfänger und habe keine Ahnung, wo und wie das jetzt einzubauen ist. Darum habe ich auch extra nach der Syntax gefragt!

Du hattest es in deinem vorigen Posting ja schon ziemlich richtig, als du die Zeitzone mit dem Zahlenwert gesetzt hast. Am Ende muss das so aussehen.

$db=@new mysql('mysql5.example.com','db_foo','password','db_foo');  
$db->set_charset('utf8');  
$db->query("SET time_zone='Europe/Athens'");

Besser ist noch, das Ergebnis vom query()-Aufruf auszuwerten, das im Fehlerfall false ist. Ursachen können ja die nicht gefüllte Timezone-Tabelle und Schreibfehler beim Zonen-Namen sein.

SET liefert kein Resultset. Du kannst für dessen Aufruf die Methode exec() nehmen.
Aha. Was heißt jetzt "kannst"?

Mein Fehler, entschuldige bitte. Vergiss das exec(), eine solche Methode gibts bei mysqli nicht. Bei PDO (eine Datenbankabstraktion) kann man zwischen Statements mit und ohne Resultset unterscheiden. Bei mysqli nimmt man in beiden Fällen query().

Definiere dein Geschehen.
Das habe ich doch! *seufz* Ich weiß nicht, wie ich meine Problematik noch genauer erklären soll.

Es war nicht direkt nachvollziehbar. Das wäre gewesen, wenn du genau deinen Versuch und das Ergebnis beschrieben hättest, also die verwendeten Statements und wie du dir das Ergebnis anschaust. (PMA als Anzeigewerkzeug stellt sich im weiteren Verlauf raus.)

Bei mir hat sowohl mit der +/-0:00-Syntax als auch nach dem Füllen der Timezone-Tabellen mit Zonen-Namen alles bestens funktioniert.
SET time_zone='Europe/Berlin';
INSERT INTO test (t) VALUES ('1970-01-01 03:00:00');
SELECT t, UNIX_TIMESTAMP(t) FROM test;

Ausgabe; 1970-01-01 03:00:00, 7200

Und jetzt steige ich völlig aus. Bedeutet das, dass das überhaupt nicht zum Verbindungsaufbau hingehört sondern in den Query? Ich habe das getestet und es kommt natürlich eine Fehlermeldung.

Das waren reineweg die SQL-Statements, wie man sie am mysql-Prompt ausführen kann, oder auch beim PMA in sein "SQL"-Feld einkippen kann. Letzten Endes brauchst du nur einmal die Zeitzone zu setzen, was praktischerweise direkt nach dem Verbindungsaufbau erfolgen sollte. Für Tests kann man jedoch die Zeitzonen auch mal öfter ändern. Allerdings musst du jedes Statement in seinem eigenen query() aufrufen. Das ist eine aus Sicherheitsgründen eingebaute Einschränkung in MySQL, damit man nicht über SQL-Injection ein weiteres Statement an das eigentlich auszuführende angehängt bekommt. Für mehrere Statements muss man mysqli::multi_query() nehmen, aber lass das mal lieber, denn damit ist das Resultset-Handling gleich wieder eine Runde komplizierter. Nimm also pro Statement ein query().

So war der Query vorher - funktionierend und ohne Fehlermeldung:
$query="SELECT id, time, datum, nameFROMtabelleWHEREid = 15";
So, jetzt erweitere ich das wie von Dir hingeschrieben direkt im Query:
$query="SET time_zone='Europe/Athens' SELECT id, time, datum, nameFROM15_testWHEREid = 15";
Und es kommt die Fehlermeldung "Parse error: syntax error, unexpected T_STRING in..."!

Da fehlt mindestens das Semikolon als Trennung zwischen den zwei Statements, und dann gehts trotzdem nicht, wie grad gesagt.

SET time_zone='UTC';
SELECT t, UNIX_TIMESTAMP(t) FROM test;

Ausgabe; 1970-01-01 02:00:00, 7200

Abgesehen davon, dass das bei mir nicht funktioniert - verstehe ich das richtig? Du läßt Dir praktisch das, was in der Tabelle steht, 2 mal ausgeben. Einmal als Timestamp (also in der Form '0000-00-00 00:00:00'), so wie es gespeichert ist, und einmal gleich von SQL umgewandelt von Timestamp auf Unix-Timestamp?

Wie es gespeichert ist, spielt im Prinzip keine Rolle. Aber da ein TIMESTAMP 4 Byte belegt, kann man davon ausgehen, dass er intern ebenfalls wie ein Unix-Timestamp als Sekunden angebender Integer abgelegt ist. Wenn er "normal" abgefragt wird, dann wird der Wert ja formatiert und unter Berücksichtigung der Zeitzone ausgegeben. Dabei findet also die Umwandlung statt. Die Funktion UNIX_TIMESTAMP(t) liefert hingegen den intern abgelegten Integer-Wert.

Der Unix-Timestamp-Wert ändert sich nicht, denn er gibt ja immer die Sekunden in UTC aus
Der Unix-Zeitstempel gibt mir doch Auskunft über die vergangenen Sekunden seit Donnerstag, den 1. Januar 1970 00:00 Uhr UTC. Aber diese Zeit ist doch für alle gleich, egal, wo man sich auf der Welt befindet. Deshalb ist mir Deine Definition "Sekunden in UTC" nicht ganz klar.

Genau das wollte ich mit der Verkürzung auf "Sekunden in UTC" aussagen.

Dann machst du vielleicht was falsch oder ziehst die falschen Schlüsse.
Glaub mir, lieber Dedlfix, ich recherchiere seit Tagen und versuche alles, um Deine Hinweise so zu realisieren, dass ich das mit den Zeitzonen handeln kann!

Mit Glauben kann ich dir schlecht helfen. Ich muss auch nachvollziehen können, wo deine Probleme und Verständnisschwierigkeiten liegen. Sonst kann ich mehr oder weniger nur pauschal antworten und bin in meinen Erklärungen drei Schritte voraus. Aber wir bekommen das schon hin. Letztlich ist die Lösung einfach, nur die Fallstricke müssen bekannt sein und wie man sie umgeht.

Der PMA macht auch nur ein SELECT und hat einen Verbindungsaufbau, bei dem er ohne weiteres keine Zeitzone setzt. Bei mir sehe ich ohne vorheriges SET im PMA immer die Lokalzeiten. Über den Startbildschirm vom PMA kann man sich die Variablen anzeigen lassen, und sogar bearbeiten (Version 3.5.2.2). Ich sehe bei 'time zone' ein SYSTEM und bei 'system time zone' ein CEST. Bearbeite ich 'time zone' zu UTC, sehe ich beim einfachen Anzeigen nun die UTC-Werte. Man muss das aber nicht dort ändern, sondern kann das angezeigte SQL-Statement bearbeiten und ein 'SET time_zone...;' voranstellen.

OK, diesen Absatz habe ich jetzt an die 10 Mal gelesen, ich glaube, ich hab jetzt endlich verstanden, wie er gemeint ist. Nur bzgl. dem "sich die Variablen anzeigen lassen": Auf meiner PMA Startseite steht "phpMyAdmin - 2.11.11.3". Das erklärt dann wohl, wieso ich diesen Punkt nirgendwo finden kann.

Beim PMA kann man bestimmte Menüpunkte auch wegkonfigurieren. Das ist oft so geschehen, wenn du eine vom Hoster gestellte Version verwendest. Bei der 2er Version waren die Systemvariablen über die Hauptseite zu erreichen. Da gab es einen eigenen Menüpunkt irgendwo neben solchen wie "Datenbanken" und "Rechte". Ob man sie auch dauerhaft für die aktuelle PMA-Sitzung ändern könnte, weiß ich nicht, aber zumindest ansehen konnte man sich die Variablen.

Eigentlich wollte ich damit nur sagen, dass der PMA keine Zeitzone setzt und damit alles gemäß der Zone von MySQL bekommt, die im DBMS eingestellt ist. Wenn du die Variablen nicht findst, kannst du die Default-Zeitzone auch über SELECT @@global.time_zone, @@session.time_zone abfragen, sowohl im PMA als auch wenn du ein derartiges Statement absetzt. Beide Werte sollten gleich sein und vermutlich 'SYSTEM' ergeben, und SELECT @@global.system_time_zone, @@session.system_time_zone zeigt vermutlich 'CEST' an.

PS: Was ich natürlich _nicht_ weiß, ist, ob das $db->query("set time_zone='+02:00'"); auch wirklich der richtige Weg zur Festlegung einer Zeitzone ist.
Jein.
Du hast mir doch ein paar Zeilen darüber geschrieben, dass das falsch ist, weil "SET kein Resultset liefert"! Jetzt ist es nur mehr ein jein?

Diese Antwort bezog sich rein auf die Zeitzonenangabe als Zahlenwert. In ihr bezog ich mich nicht auf die Art und Weise des Aufrufs. Mit dem Zahlenwert bekommst du nur zeitweise richtige Ergebnisse, deshalb das Jein.

Du brauchst also die gefüllten Zeitzonentabellen, um das Zeitzonenhandling namensbasiert dem MySQL überlassen zu können.
Und wie finde ich heraus, ob das bei mir der Fall ist?

Vermutlich sind bei dir die entsprechenden Tabellen nicht gefüllt, denn ein Setzen nach Namen misslingt ja, eins nach Zahlen klappt. So verhielt es auch auch bei mir.

Sonst geht nur UTC-Speicherung in MySQL mit Lokal-Umrechnung in PHP.
Ja aber wenn dem so ist, dann ist doch unser ganzer tagelanger Diskurs völlig umsonst, oder?

Ja, wenn du deinen Hoster nicht dazu bringen kannst, die Tabellen zu füllen, dann geht der Weg leider nicht.

Wozu dann überhaupt das ganze umständliche Hin-und-her?

Nun, ich hatte am Anfang der Diskussion auch noch nicht alles notwendige Detailwissen parat. Und manchmal können durchaus auch die Problemlösungsversuche in Sackgassen enden.

Ist es da nicht am unkompiziertesten, einen Zeitpunkt als UTC Zeitstempel zu speichern, den ohne irgend eine Umwandlung von der DB ausgeben zu lassen und dann mit php in einen Zeitpunkt einer Zeitzone seiner Wahl umzuwandeln?

Am unkompliziertesten ist, wenn der Hoster deine Zeitzone als Default konfiguriert hat und man keine Umkonfiguration nach anderenorts befürchten muss. Aber auch in dem Fall muss noch die Geschichte mit der Magie der TIMESTAMP-Felder beachtet werden.

Danach kommt der Fall, dass der Server eine andere Zeitzone hat. Dann wird zusätzlich das Setzen der Zeitzone nach dem Verbindungsaufbau benötigt. Und die gefüllten Zeitzonentabellen. Wenn der Hoster schon nach Billigland umzieht, dann soll er die gefälligst füllen, denn das kostet außer einem ganz kleinem Bisschen Arbeit nichts.

Die "meiste" Arbeit macht es, wenn du UTC-Sekunden als Integer (sprich: Unix-Timestamps) speicherst und selbst umrechnest. Das hält sich aber auch in überschaubaren Grenzen. Schöner fände ich, die Möglichkeiten MySQLs nutzen zu können.

Was mich zu meiner letzten Frage führt: Du schreibst etwas weiter oben von der Funktion 'gmdate()'. Aber wenn ich besagten UTC Timestamp in einen Datum-Zeit-Stempel einer bestimmten Zeitzone bringen möchte, dann mache ich das doch so, dass ich den String in die Einzelteile zerlege und dann mit date() und mktime() arbeite. zB:

gmdate() formatiert genauso wie date() einen Unix-Timestamp in ein gewünschtes Format, nur dass date() gemäß der konfigurierten Zeitzone ausgibt und gmdate() gemäß UTC. mktime() brauchst du in dem Fall nicht. Das nimmst du nur, wenn du einen Zeitpunkt in Einzelteilen rumliegen hast und davon den Unix-Zeitstempel haben willst. Oder auch gmmktime(), wenn der Zeitpunkt in UTC vorliegt.

dedlfix.