Wenn gmtime() das Zeitliche segnet
« | 12 Jan 2020 | »Ich weiß leider schon heute, dass mir in diesem Leben nicht mehr genug Zeit bleiben wird, das Zeit-API Chaos weltweit zu lösen.
Seit meinem Einstieg in die C++ Programmierung muss ich mit dem Thema Zeit und Zeitzonen kämpfen und es ist unerträglich, dass es bis ins Jahr 2020 nicht einheitlich möglich war, eine genormte Schnittstelle dafür zu schaffen.
Theoretisch hatten die C
und POSIX
Erfinder einen korrekten Grundstein gesetzt.
Es gibt eine integrale Einheit um Zeitpunkte einfach darzustellen, nämlich den
Integer Typ time_t
, der die Sekunden seit dem 1. 1. 1970 UTC zählt, und dann
haben wir noch mit der struct tm
die Möglichkeit der Umwandlung in den
gregorianischen Kalender,
also DEM Standard-Kalender dieser Welt.
Dass der C Standard nicht auch noch andere Kalender oder Nanosekunden kennt, ist der Prämisse geschuldet eine platzsparende Implementierung zu besitzen und außerdem kann diese Aufgabe auch eine Drittbibliothek übernehmen.
Und Funktionen wie gmtime
,
localtime
, und
mktime
können zwischen
dem Zeitstempel und dem Kalenderdatum hin und her konvertieren.
Die Problemkinder Zeitzonen und
Sommerzeit lassen wir hier mal aus.
Und dann wurde bei diesen APIs die schwersten Fehler überhaupt gemacht.
Denn gmtime
und localtime
schreiben ihr Ergebnis in eine private
globale Variable
und geben einen Pointer darauf zurück.
Das ist deshalb so unglaublich dumm, weil die meisten C APIs immer schon mit dem Konzept arbeiteten, dass man einen Byte-Puffer an eine Funktion übergibt, diese den Puffer befüllt und schon hat man seine Daten an der gewünschten Stelle. Doch nein, Kalender liegen in einer fremden globalen Variable, die bei jedem Aufruf überschrieben wird.
Das bring natürlich jedes Programm zum Absturz (oder zumindest korrumpierte Ergebnisse), welches mit mehr als einem Thread läuft.
Doch dieses Problem wurde in den 90ern erkannt und hier hätte (wie in vielen anderen Beispielen) eine neue API das Problem lösen können. … aber Fehlanzeige!
Microsoft implementierte daher die Funktionen gmtime_s
und localtime_s
,
im Rahmen seiner Safe-CRT, während die POSIX-Community eine vorgeblich
bessere Erfindung machte, nämlich gmtime_r
und
localtime_r
.
Diese beiden APIs tun exakt das gleiche, sie konvertieren einen time_t
in
eine struct tm
, welcher vom Aufrufer allokiert werden muss.
Doch intelligenterweise nutzen die beiden Konventionen eine
gegengleiche Funktionssignatur.
Noch dümmer wurde es, als die Microsoft-API-Namen in den C-Standard wanderten (der von POSIX wiederum ignoriert wird), aber die Signatur der POSIX Routinen trägt.
Am Ende sind alle diese API-Ansätze vollkommen nutzlos für portable Programme.
Und dass C++ dieses Manko erst im kommenden Standard C++20
ausgleichen will, indem es std::chrono
mit ein paar neuen Konvertern ausstatten will halte ich heute, 15 Jahre zu
spät, für eine Frotzelei.
Workaround
Ich hasse so etwas wirklich, aber was bleibt einem übrig außer folgendes zu schreiben:
1#include <time.h> 2 3#ifdef WIN32 4 5inline struct tm *gmtime_r(const time_t *timep, struct tm *result) 6{ 7 if(0 == gmtime_s(result, timep)) 8 { 9 return result; 10 } 11 else 12 { 13 return NULL; 14 } 15} 16 17inline struct tm *localtime_r(const time_t *timep, struct tm *result) 18{ 19 if(0 == localtime_s(result, timep)) 20 { 21 return result; 22 } 23 else 24 { 25 return NULL; 26 } 27} 28#endif
Fazit
Ich bin jedes mal wieder sauer, wenn ich im Zuge eines Projektes über diese
Katastrophe stoße.
Bei all den wirklich gut designten Elementen von C und C++ ist die
“Verwaltung der Zeit” ein Zeugnis von Unwilligkeit untereinander
sich auf einen Standard zu einigen, dass es weh tut.
Und ja, ich weiß … man könnte BOOST
dafür nutzen, welches viele unterschiedliche Ansätze für Zeitrechnungen
bereitstellt.
Aber ich sehe nicht ein, warum die einfache Konvertierung von time_t
auf
struct tm
nicht schon 1998 bzw. spätestens 2011 im Standard aktualisiert
wurde.
Im GATE Projekt ist das jedenfalls schon lange implementiert … mehr als einen Tag Arbeitszeit kostet das schließlich auch nicht!