Vor einiger Zeit haben wir damit begonnen, das System an den neuen Markt anzupassen, der die Unterstützung von Zeitzonen erfordert. Erste Recherchen wurden im vorherigen Artikel beschrieben. Jetzt hat sich der Ansatz unter dem Einfluss der Realitäten leicht entwickelt. Dieser Artikel beschreibt die Probleme, die während der Diskussionen aufgetreten sind, und die endgültige Entscheidung, die umgesetzt wird.
TL;DR
- Es ist notwendig, Begriffe zu unterscheiden:
- UTC ist die Ortszeit in der Zone +00:00, ohne DST-Effekt
- DateTimeOffset – lokaler Zeitversatz von UTC ± NN:NN, wobei der Versatz der Basisversatz von UTC ohne den DST-Effekt ist (in C# TimeZoneInfo.BaseUtcOffset)
- DateTime – Ortszeit ohne Angabe der Zeitzone (wir ignorieren das Kind-Attribut)
- Unterteilen Sie die Nutzung in extern und intern:
- Ein- und Ausgabedaten über API, Nachrichten, Dateiexporte/-importe müssen ausschließlich in UTC (DateTime-Typ) erfolgen
- Innerhalb des Systems werden die Daten zusammen mit dem Offset (Typ DateTimeOffset) gespeichert
- Unterteilen Sie die Verwendung im alten Code in Nicht-DB-Code (C#, JS) und DB:
- Nicht-DB-Code arbeitet nur mit lokalen Werten (DateTime-Typ)
- Die Datenbank arbeitet mit lokalen Werten + Offset (Typ DateTimeOffset)
- Neue Projekte (Komponenten) verwenden DateTimeOffset.
- In einer Datenbank ändert sich der DateTime-Typ einfach in DateTimeOffset:
- In Tabellenfeldtypen
- In den Parametern gespeicherter Prozeduren
- Inkompatible Konstruktionen werden im Code behoben
- Offset-Informationen werden an einen empfangenen Wert angehängt (einfache Verkettung)
- Vor der Rückkehr zum Nicht-DB-Code wird der Wert in local konvertiert
- Keine Änderungen an Nicht-DB-Code
- DST wird mit CLR Stored Procedures gelöst (für SQL Server 2016 können Sie AT TIME ZONE verwenden).
Nun mehr Details zu den Schwierigkeiten, die überwunden wurden.
„Tief verwurzelte“ Standards der IT-Branche
Es hat ziemlich lange gedauert, den Menschen die Angst zu nehmen, Datumsangaben in Ortszeit mit Offset zu speichern. Wenn Sie vor einiger Zeit einen erfahrenen Programmierer gefragt haben:„Wie unterstützt man Zeitzonen?“ – die einzige Option war:„UTC verwenden und kurz vor der Demonstration in Ortszeit konvertieren“. Dass man für den normalen Workflow noch zusätzliche Informationen benötigt, wie zum Beispiel Offset- und Zeitzonennamen, verbarg sich unter der Haube der Implementierung. Mit dem Aufkommen von DateTimeOffset kamen solche Details heraus, aber die Trägheit der „Programmiererfahrung“ erlaubt es nicht, einer anderen Tatsache schnell zuzustimmen:„Speichern eines lokalen Datums mit einem grundlegenden UTC-Offset“ ist dasselbe wie das Speichern von UTC. Ein weiterer Vorteil der Verwendung von DateTimeOffset überall ermöglicht es Ihnen, die Kontrolle über die Einhaltung von .NET Framework- und SQL Server-Zeitzonen zu delegieren, sodass nur die Momente der Dateneingabe und -ausgabe aus dem System der menschlichen Kontrolle überlassen werden. Menschliche Kontrolle ist der Code, der von einem Programmierer geschrieben wurde, um mit Datums-/Uhrzeitwerten zu arbeiten.
Um diese Angst zu überwinden, musste ich mehr als eine Sitzung mit Erklärungen, Präsentation von Beispielen und Proof of Concept abhalten. Je einfacher und näher die Beispiele an den Aufgaben sind, die im Projekt gelöst werden, desto besser. Wenn Sie in die Diskussion „allgemein“ einsteigen, führt dies zu Verständnisschwierigkeiten und Zeitverschwendung. Kurz gesagt:weniger Theorie – mehr Praxis. Die Argumente für UTC und gegen DateTimeOffset können zwei Kategorien zugeordnet werden:
- „UTC all time“ ist der Standard und der Rest funktioniert nicht
- UTC löst das Problem mit DST
Es sollte beachtet werden, dass weder UTC noch DateTimeOffset das Problem mit DST lösen, ohne Informationen über die Regeln für die Konvertierung zwischen Zonen zu verwenden, die über die TimeZoneInfo-Klasse in C# verfügbar sind.
Vereinfachtes Modell
Wie ich oben erwähnt habe, finden Änderungen im alten Code nur in einer Datenbank statt. Dies kann anhand eines einfachen Beispiels beurteilt werden.
Beispiel eines Modells in T-SQL
// 1) data storage // input data in the user's locale, as he sees them declare @input_user1 datetime = '2017-10-27 10:00:00' // there is information about the zone in the user configuration declare @timezoneOffset_user1 varchar(10) = '+03:00' declare @storedValue datetimeoffset // upon receiving values, attach the user’s offset set @storedValue = TODATETIMEOFFSET(@input_user1, @timezoneOffset_user1) // this value will be saved select @storedValue 'stored' // 2) display of information // a different time zone is specified in the second user’s configuration, declare @timezoneOffset_user2 varchar(10) = '-05:00' // before returning to the client code, values are reduced to local ones // this is how the data will look like in the database and on users’ displays select @storedValue 'stored value', CONVERT(DATETIME, SWITCHOFFSET(@storedValue, @timezoneOffset_user1)) 'user1 Moscow', CONVERT(DATETIME, SWITCHOFFSET(@storedValue, @timezoneOffset_user2)) 'user2 NY' // 3) now the second user saves the data declare @input_user2 datetime // input local values are received, as the user sees them in New York set @input_user2 = '2017-10-27 02:00:00.000' // link to the offset information set @storedValue = TODATETIMEOFFSET(@input_user2, @timezoneOffset_user2) select @storedValue 'stored' // 4) display of information select @storedValue 'stored value', CONVERT(DATETIME, SWITCHOFFSET(@storedValue, @timezoneOffset_user1)) 'user1 Moscow', CONVERT(DATETIME, SWITCHOFFSET(@storedValue, @timezoneOffset_user2)) 'user2 NY'
Das Ergebnis der Skriptausführung sieht wie folgt aus.
Das Beispiel zeigt, dass dieses Modell Änderungen nur in der Datenbank zulässt, was das Fehlerrisiko erheblich reduziert.
Beispiele für Funktionen zur Verarbeitung von Datums-/Uhrzeitwerten
// When receiving values from the non-DB code in DateTimeOffset, they will be local, // but with offset +00:00, so you must attach a user’s offset, but you cannot convert between // time zones. To do this, we translate the value into DateTime and then back with the indication of the offset // DateTime is converted to DateTimeOffset without problems, // so you do not need to change the call of the stored procedures in the client code create function fn_ConcatinateWithTimeOffset(@dto datetimeoffset, @userId int) returns DateTimeOffset as begin declare @user_time_zone varchar(10) set @user_time_zone = '-05:00' // from the user's settings @userId return todatetimeoffset(convert(datetime, @dto), @user_time_zone) end // Client code cannot read DateTimeOffset into variables of the DateTime type, // so you need to not only convert to a correct time zone but also reduce to DateTime, // otherwise, there will be an error create function fn_GetUserDateTime(@dto datetimeoffset, @userId int) returns DateTime as begin declare @user_time_zone varchar(10) set @user_time_zone = '-05:00' // from the user's settings @userId return convert(datetime, switchoffset(@dto, @user_time_zone)) end
Kleine Artefakte
Bei der Anpassung des SQL-Codes wurden einige Dinge gefunden, die für DateTime funktionieren, aber nicht mit DateTimeOffset kompatibel sind:
GETDATE()+1 muss durch DATEADD (day, 1, SYSDATETIMEOFFSET ()) ersetzt werden
Das Schlüsselwort DEFAULT ist mit DateTimeOffset nicht kompatibel, Sie müssen SYSDATETIMEOFFSET()
verwendenDas Konstrukt ISNULL(date_field, NULL)> 0″ funktioniert mit DateTime, aber DateTimeOffset sollte durch „date_field IS NOT NULL“
ersetzt werdenSchlussfolgerung oder UTC vs. DateTimeOffset
Dem einen oder anderen mag auffallen, dass wir uns, wie im Ansatz mit UTC, beim Empfangen und Zurücksenden von Daten mit der Konvertierung befassen. Wozu brauchen wir das alles dann, wenn es eine erprobte und funktionierende Lösung gibt? Dafür gibt es mehrere Gründe:
- DateTimeOffset lässt Sie vergessen, wo sich SQL Server befindet.
- Dadurch können Sie einen Teil der Arbeit auf das System verlagern.
- Die Konvertierung kann minimiert werden, wenn DateTimeOffset überall verwendet wird und es nur vor der Anzeige von Daten oder der Ausgabe an externe Systeme durchführt.
Diese Gründe schienen mir aufgrund der Verwendung dieses Ansatzes wesentlich.
Gerne beantworte ich Ihre Fragen, bitte schreiben Sie Kommentare.