====== Datums und Zeitwerte ====== Datums und Zeitwerte werden intern als Gleitkommazahl((Double oder Float)) 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 sollte((das entscheidet die Regierung, nicht Bill Gates)), kann das zu Problemen führen((wenn der Wert als [[http://en.wikipedia.org/wiki/Coordinated_Universal_Time|UTC]] Zeit gespeichert wird, der beim Konvertieren in die lokale Uhrzeit die Sommerzeit berücksichtigt - und wenn Windows heute und morgen andere Regeln für diese Konvertierung hat, führt das zu unterschiedlichen Ergebnissen)). ===== Berechnungen mit Datumswerten ===== 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 wissen((es könnte die lokale Einstellung verwenden, dann würden Berechnungen aber abhängig der Systemeinstellung zu unterschiedlichen Ergebnissen führen)). 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)).TotalDaysWenn 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 System((Windows)) 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()).TotalDaysZusammenfassend //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 sein((z.B. wenn ich Stunden fakturiere, und in dem erfassten Zeitraum eine Änderung von Sommmer- auf Winterzeit liegt, und ich die 1 Stunde Arbeitszeit nicht verlieren möchte)). Das kann u.U. für den Entwickler ziemlich aufwendig werden, wie das Beispiel mit Serienterminen zeigt. ==== Serientermine ==== 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 verlassen((eine Art //Mandantenzeitzone//)), nur wäre das Problem, dass man dann Werte nicht konsistent am Client berechnen lassen kann((dort ist u.U. eine andere Zeitzone eingestellt als am Server)) 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. ===== Umgang mit Zeitwerten in Businessanwendungen ===== In den Anwendungen der TAV Enterprise Software GmbH geht es um Businessanwendungen und nicht um Wissenschaftliche Anwendungen. Einige raten, die Uhrzeit als [[http://en.wikipedia.org/wiki/Coordinated_Universal_Time|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ücksichtigt((das ist in der Praxis nicht weiter schlimm, es ändert sich ja niemals etwas aus der Vergangenheit, solche Änderungen sind sehr selten, und werden lange vorher angekündigt. Es bleibt also ausreichend Zeit den Anwendern einen Pfad zu geben, die Termine, die in der Zukunft liegen, anzupassen, bzw. wird Windows die geänderten Regeln sicherlich lange Zeit berücksichtigen, noch bevor Anwender anfangen Termine so lange in der Zukunft anzugeben.)). Es gib also 3 Möglichkeiten: - Eine Mandantenzeitzone verwenden und alle Datums-/Zeitwerte in der lokalen Zeit des Mandanten speichern, - Alle Datums-/Zeitwerte in UTC speichern und abhängig des Clients zur Anzeige wandeln, dabei an einigen Stellen((z.B. Berechnung von Serienterminvorkommen)) explizit speichern, welche Zeitzone zur Berechnung verwendet werden soll, - Jeden einzelnen Datums-/Zeitwert in der Uhrzeit der Zeitzone speichern, in der er angegeben ist, gemeinsam mit der Zeitzone. Jede Entscheidung hat Vor- und Nachteile. ==== Mandantenzeitzone ==== 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. ==== Alle Werte als UTC speichern ==== 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. ==== Uhrzeit der Zeitzone speichern, die für die Angabe verwendet wurde, samt Zeitzone selbst ==== 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 selbst((und man bei Änderungen an Sommerzeitregeln nur die Denormalisierung anpassen muss)), kann man die gespeicherten Werte vergleichbar halten und auch Abfragen bauen, die auf die UTC gehen((Abfragen auf die Zeit selbst wären kompliziert, weil 08:00 alles von 20:00 am Vorabend bis 20:00 heute bedeuten kann, abhängig der Zeitzone)). ===== Lösung ===== * Der Mandant hat eine Standard Zeitzone, * Der Mitarbeiter übernimmt die Zeitzone vom Mandanten, sie kann aber jederzeit überschrieben werden, * Datums- und Zeitangaben beinhalten immer die Zeitangaben in der Zeitzone des Mitarbeiters, gemeinsam mit der Zeitzonen Information selbst, damit man hin-und-her konvertieren kann, * Die denormalisierten Daten für Abfragen beinhalten zudem Datum und Uhrzeit im UTC Format um Vergleichbarkeit von den Werten herstellen zu können und konsistente Abfragen über Datumswerte zu ermöglichen. 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 angezeigt((obwohl es 4 Stunden sind, weil die Uhr um 3 Uhr zurück gestellt wird)). 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 [[http://msdn.microsoft.com/en-us/library/system.datetime.touniversaltime.aspx|Zeitkonvertierung in .Net]]: * Bei von-bis Terminen wird der Start und das Ende angegeben und berücksichtigt. * Liegt einer der Daten in der sogenannten //Todeszone//((2:30 am letzten Sonntag im März gibt es nicht)), wird der Datensatz abgelehnt. Wenn es aber durch Änderungen an den Regeln passieren sollte, dass eine alte Angabe in der //Todeszone// liegt, wird einfach der Standard-Offset abgezogen um die UTC-Zeit zu ermitteln. Beim hin-und-her Wandeln kommt dann eine andere lokale Uhrzeit raus((z.B. wird aus 2:30 am letzten März in 2012 1:30 in UTC Zeit, weil wir in der +1 Offset Zeitzone sind, was beim zurück Wandeln in die lokale Zeit 3:30 ist, weil dann Sommerzeit ist mit +2)), * Liegt einer der Daten an einem zweideutigen Zeitpunkt((2:30 am letzten Sonntag im November gibt es zwei mal)), muss der Anwender((in einer späteren Version)) angeben welcher der Beiden gemeint ist((ich gehe fest davon aus, dass es immer nur 2 Möglichkeiten gibt. Der Default wird immer //Standardzeit// sein, und der Anwender wird vorerst nicht die Möglichkeit haben, das zu ändern)), * Bei Dauerangaben ist das schwieriger. Wenn ich am 28. Oktober 2012 um 02:00 einen Termin mit einer Dauer von einer Stunde angebe, wäre die lokale Uhrzeit des Terminendes 03:00, wobei das 03:00 Sommerzeit wäre, nicht die 03:00 Standardzeit, wodurch der Termin eine Dauer von 2 Stunde bekäme. Die Anzeige wäre in beiden Fällen dieselbe, ob die Dauer mit einer oder zwei Stunden angegeben ist. D.h. dass für Berechnungszwecke die Dauer verwendet werden muss, und nicht das Ende Datum/Uhrzeit. Auch Abfragen für Zeiträume müssten das berücksichtigen, wobei man bei den von-Zeiten immer den früheren nehmen muss, bei bis-Zeiten immer den späteren, um alle UTC Datumsangaben zu erhalten((so in der Art, das wird beim Programmieren klar, ist doch nicht so schwer!))