Database
 sql >> Datenbank >  >> RDS >> Database

Mehr über Einführung von Zeitzonen in langlebiges Projekt

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()

verwenden

Das Konstrukt ISNULL(date_field, NULL)> 0″ funktioniert mit DateTime, aber DateTimeOffset sollte durch „date_field IS NOT NULL“

ersetzt werden

Schlussfolgerung 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.