Wenn gmtime() das Zeitliche segnet

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!