Datums und Zeitwerte werden intern als Gleitkommazahl1) gespeichert. Das Problem ist, dass solch ein Gleitkommawert einen Bezug haben muss und zudem in unterschiedlichen Zeitzonen was anderes bedeuten kann. Hinzu kommt, dass die unterschiedliche Bedeutung sich ändern kann wenn sich die Regeln ändern. Wenn ich z.B. einen Termin im April 2024 für 08:00 eingetragen habe, und sich die Sommerzeitregelung ändern sollte2), kann das zu Problemen führen3).
Die Berechnungen von Datumswerten ist sehr einfach. Der Unterschied zwischen 2 Werten wird z.B. einfach ermittelt, indem die Werte von einander abgezogen werden. Ob es dazwischen eine Zeitumstellung gibt kann das System nicht wissen4). Folgendes führt somit zu einem Wert von 1, obwohl am 28.10.2012 die Uhren eine Stunden zurück gestellt werden:
(new DateTime(2012, 10, 29) - new DateTime(2012, 10, 28)).TotalDays
Wenn man die Zeitumstellung berücksichtigen möchte, muss man das explizit machen. Das funktioniert, indem man den Datums/Zeitwert erst in UTC wandelt. Beim Wandeln wird das System5) die Zeitumstellung berücksichtigen, und folgendes hat zum Ergebnis 1,04166666666667 und nicht 1:
(new DateTime(2012, 10, 29).ToUniversalTime() - new DateTime(2012, 10, 28).ToUniversalTime()).TotalDays
Zusammenfassend tut das System bei Berechnungen immer so, als würde es mit UTC rechnen, berücksichtigt also keine Zeitzonen und Zeitumstellungen von Sommer- auf Winterzeit und zurück. Das wäre auch fürchterlich umständlich. Mit einfachen Methoden kann man das aber explizit von System verlangen, sollte es nötig sein6). Das kann u.U. für den Entwickler ziemlich aufwendig werden, wie das Beispiel mit Serienterminen zeigt.
Wenn ich einen Serientermin definiere, z.B. jeden Montag um 14:00, dann ist es absolut notwendig anzugeben, in welcher Zeitzone ich den Termin berechnet haben möchte. 14:00 in der Deutschen Zeit ist dabei nicht immer gleichbedeutend mit 08:00 in der Zeit an der Ostküste der USA, weil die Zeitumstellung dort unterschiedlich sein kann. Als Anwender möchte ich den Termin immer entweder um 14:00 Deutscher Zeit oder um 08:00 Amerikanischer Zeit errechnet haben. 14:00 Deutscher Zeit könnte auch mal 09:00 Amerikanische Zeit sein, das ist mir aus Anwendersicht aber egal solange mich nur die Deutsche Zeit interessiert. Wenn ich aber 3 Monate in New York arbeite, möchte ich meine Serientermine vielleicht in NY Zeit angeben, weil ich da aus 08:00 nicht plötzlich 09:00 errechnet haben möchte, weil es in Deutschland eine Zeitumstellung gab.
Das Fazit ist, dass man bei der Anlage für Serientermine immer speichern muss, in welcher Zeitzone die Vorkommen des Termins errechnet werden sollen. Man könnte sich auf die Einstellung der Zeitzone am Server verlassen7), nur wäre das Problem, dass man dann Werte nicht konsistent am Client berechnen lassen kann8) und vor allem, dass sich die Berechnung ändern würde, wenn man die Einstellung am Server ändert - und das darf absolut nicht passieren. Angezeigt werden können sie in jeder beliebigen Zeitzone, wichtig ist nur, dass sie konsistent und für den Anwender verständlich berechnet werden, und zwar mit der Zeitzone, mit der es möchte.
In den Anwendungen der TAV Enterprise Software GmbH geht es um Businessanwendungen und nicht um Wissenschaftliche Anwendungen. Einige raten, die Uhrzeit als UTC zu speichern und immer hin-und-her zu konvertieren. Der Anwender gibt 08:00 an, das System konvertiert vor dem Speichern zu 06:00 in UTC. Beim erneuten laden der Daten zur Anzeige wird von 06:00 UTC zurück in die lokale Uhrzeit von 08:00 konvertiert.
Das wäre für einfache Datumsangaben eine vernünftige Lösung, weil die Konvertierung eindeutig ist - bis die Regierung sich dazu entschließt, die Sommerzeit abzuschaffen. In dem Fall würde alle UTC Datums/Uhrzeitwerte, die hinter der Umstellung liegen, anders zurück konvertiert werden nachdem das nächste Windows Update die neuen Regeln berücksichtigt9). Es gib also 3 Möglichkeiten:
Jede Entscheidung hat Vor- und Nachteile.
Das wurde bisher so gemacht, siehe softelligence P.A.C., bzw. die Delphi 6 Version von PRO•M. Die Vor- und Nachteile wurden oben bereits besprochen. Solange man immer nur von einer Zeitzone ausgeht und vom Anwender genügend Mitdenken voraussetzt, dass er die Tage der Zeitumstellung korrekt angibt, bzw. selbst anpasst, ist das die einfachste Lösung. Sobald das Unternehmen aber mal Anwender in Deutschland hat, mal in England, mal in den USA, mal woanders, klappt das nicht mehr. Mitarbeiter in den USA möchten ihre Daten in USA Zeit angeben, und wenn Termine koordiniert werden müssen, muss korrekt von USA in Deutsche Zeit und zurück gerechnet werden.
Man könnte das darüber regeln, dass es pro Zeitzone einen eigenen Mandanten gibt. Das wäre eine Lösung wenn man sich anfangs keine Gedanken gemacht hat, z.B. für die Delphi 6 Version von PRO•M. Es geht aber flexibler.
Die Lösung ist schon sehr gut. Sie hat den Nachteil, dass man die Werte in der Datenbank in UTC Zeit sieht, was in den meisten Fällen nicht der Zeit entspricht, die man selbst angegeben hat. Der andere Nachteil ist der, dass Änderungen an der Sommerzeitregelung die Konvertierung zurück in die ursprüngliche Zeit verändern könnte.
Wenn ich einen Termin am 4. April 2030 um 08:00 in NY angebe, soll das immer so bleiben. Was das in UTC oder Deutscher Zeit ist, ist mir egal. Wenn ich das somit genauso abspeicher, habe ich genau das, was ich möchte. Wenn ich in der Denormalisierung zudem speicher, was der Offset ist (z.B. -5 Stunden für NY), bzw. die UTC Zeit selbst11), kann man die gespeicherten Werte vergleichbar halten und auch Abfragen bauen, die auf die UTC gehen12).
Was ist mit Terminen, die eine Zeitumstellung in einer Zeitzone beinhalten? Ein Termin in Outlook, der in der Deutschen Zeitzone am 28. Oktober von 01:00-04:00 angegeben ist, wird mit einer Dauer von 3 Stunden angezeigt13). Wenn ich mir den Kalender mit US Amerikanischer Ostküstenzeit angucke, ist der Termin von 19:00-23:00 am Vorabend, und auch 4 Stunden und nicht mehr nur 3. Es gibt also auch hierfür einige Regeln, die sich an der Konvertierung von DateTime Werten in .Net anlehnt, siehe Zeitkonvertierung in .Net: