Spaß mit der Zeitumstellung

Okay, diese Stolperfalle habe ich mir letztlich selbst gebaut – woran man nicht alles denken muss.

Ein Kunde betreibt auf seinem Server einige Dienste, die eine Art „Lebenszeichen“ in Form eines Unix-Timestamps (für Unwissende: Die Zahl der Sekunden seit dem 1. Januar 1970) in einer MySQL-Tabelle hinterlassen. Da diese Dienste besonders wichtig für ihn sind, haben wir die Nagios-Überwachung seines Servers um einen Check für diese Dienste erweitert, der sich die Differenz zwischen der dort gespeicherten Zeit und der aktuellen zeit in Sekunden heraussucht. Hierbei darf ein gewisser Schwellenwert nicht überschritten werden, sonst gibt es einen Alarm. Und so sieht’s aus:

SELECT name,
       text,
       UNIX_TIMESTAMP(NOW())-text AS diff
  FROM setup
 WHERE name LIKE "%\_time"

Ich rechne hier die aktuelle Zeit mittels UNIX_TIMESTAMP() in einen Unix-Timestamp um, damit ich Sekunden mit Sekunden vergleiche – daraus ergibt sich dann einfach eine Differenz. Das klappte auch hervorragend.

Bis zur Zeitumstellung am Sonntag. Denn hier zeigt sich letztlich, dass die Zeit „2009-10-25 02:17:00“ durchaus nicht eindeutig ist: Es gibt sie nämlich um 2:17 Uhr vor der Zeitumstellung, und es gibt sie um 2:17 nach der Zeitumstellung, also um „gefühlt 3:17 Uhr“. Die Unix-Zeit, die ja einfach nur stupide Sekunden zählt, läuft aber eben einfach weiter. Das bringt einen zur konsequenterweise logischen, aber trotzdem erstaunlichen Situation, dass zwei Unix-Timestamps die gleiche lokale Zeit ergeben können. Konkret:

1256429820 => 2009-10-25 02:17:00 (vor der Zeitumstellung)
1256437020 => 2009-10-25 02:17:00 (nach der Zeitumstellung)

Daraus ergibt sich wiederum, dass sich aus der lokalen Zeit „2009-10-25 02:17:00“ nicht auf einen Unix-Timestamp schließen lässt, sondern gleich auf zwei, weil die lokale Zeit eben keine Angabe enthält, ob man sich gerade vor oder nach der Zeitumstellung befindet.

Nun befindet sich MySQL in meinem Script in genau der Situation: Es muss einen Unix-Timestamp aus „2009-10-25 02:17:00“ bilden, und das kann es nicht verlässlich. Der springende Punkt liegt aber schon beim NOW(): An sich weiß MySQL nämlich durchaus, ob es sich vor oder nach der Zeitumstellung befindet. Aber in dem Moment, wo NOW() diese aktuelle Zeit in einen String konvertiert hat, ist diese Information faktisch verloren – man sieht sie ja dem String nicht mehr an. UNIX_TIMESTAMP() kann also letztlich nur raten – und rät, die Zeitumstellung sei schon gelaufen.

Das führt zu der ärgerlichen Situation, dass das obige SQL-Statement in der fraglichen Stunde zwischen 2 und 3 Uhr plötzlich der Meinung ist, die zu checkenden Dienste seien bereits eine Stunde überfällig. Ab Punkt 2 Uhr liefert UNIX_TIMESTAMP(NOW()) nämlich bereits den Timestamp für 2 Uhr nach Zeitumstellung.

Um so ärgerlicher, weil ich viel einfacher auch einfach nur UNIX_TIMESTAMP() ohne Argument hätte schreiben können. Das liefert dann nämlich den aktuellen Timestamp, und dann sogar den richtigen, weil hier ja MySQL „die lokale Zeit“ (von der es weiß, ob sie vor oder nach der Umstellung liegt) in einen Timestamp konvertiert und keinen String, dem diese Information fehlt.

Aber ich denke, ich habe meine Strafe bereits bekommen: Am Sonntag um kurz nach 2 Uhr nachts von der Überwachung aus dem Schlaf geklingelt zu werden, dürfte hoffentlich ausreichend gewesen sein. Das Nagios-Check-Script ist dann inzwischen auch angepasst.