sql >> Database teknologi >  >> RDS >> Database

Mere om introduktion af tidszoner i langlivede projekt

For noget tid siden begyndte vi at tilpasse systemet til det nye marked, der kræver understøttelse af tidszoner. Indledende forskning blev beskrevet i den forrige artikel. Nu har tilgangen udviklet sig lidt under indflydelse af realiteterne. Denne artikel beskriver de problemer, der er stødt på under diskussionerne og den endelige beslutning, der er implementeret.

TL;DR

  • Det er nødvendigt at skelne mellem termer:
    • UTC er den lokale tid i +00:00-zonen uden sommertid-effekten
    • DateTimeOffset – lokal tidsforskydning fra UTC ± NN:NN, hvor offset er basisforskydningen fra UTC uden DST-effekten (i C# TimeZoneInfo.BaseUtcOffset)
    • DateTime – lokal tid uden information om tidszonen (vi ignorerer Kind-attributten)
  • Opdel brugen i ekstern og intern:
    • Input og output data via API, meddelelser, fileksport/import skal udelukkende være i UTC (DateTime type)
    • Inde i systemet gemmes dataene sammen med offset (DateTimeOffset type)
  • Opdel brugen af ​​den gamle kode i ikke-DB-kode (C#, JS) og DB:
    • Ikke-DB-kode fungerer kun med lokale værdier (DateTime-type)
    • Databasen arbejder med lokale værdier + offset (DateTimeOffset type)
  • Nye projekter (komponenter) bruger DateTimeOffset.
  • I en database ændres DateTime-typen blot til DateTimeOffset:
    • I tabelfelttyper
    • I parametrene for lagrede procedurer
    • Inkompatible konstruktioner er rettet i koden
    • Offset-oplysninger er knyttet til en modtaget værdi (simpel sammenkædning)
    • Før du vender tilbage til ikke-DB-koden, konverteres værdien til lokal
  • Ingen ændringer til ikke-DB-kode
  • DST løses ved hjælp af CLR Stored Procedures (til SQL Server 2016 kan du bruge AT TIME ZONE).

Nu mere detaljeret om de vanskeligheder, der blev overvundet.

"Dybt rodfæstede" standarder for it-industrien

Det tog ret lang tid at befri folk fra frygt for at gemme datoer i lokal tid med offset. For noget tid siden, hvis du spørger en erfaren programmør:"Hvordan understøtter man tidszoner?" – den eneste mulighed var:"Brug UTC og konverter til lokal tid lige før demonstration". Det faktum, at du til normal arbejdsgang stadig har brug for yderligere information, såsom offset- og tidszonenavne, var skjult under implementeringens hætte. Med fremkomsten af ​​DateTimeOffset kom sådanne detaljer ud, men inertien af ​​"programmeringsoplevelsen" tillader ikke hurtigt at blive enige med en anden kendsgerning:"At gemme en lokal dato med en grundlæggende UTC-offset" er det samme som at gemme UTC. En anden fordel ved at bruge DateTimeOffset overalt giver dig mulighed for at uddelegere kontrol over overholdelse af .NET Framework og SQL Server tidszoner, hvilket kun giver menneskelig kontrol over øjeblikke af datainput og -output fra systemet. Menneskelig kontrol er koden skrevet af en programmør til at arbejde med dato/tidsværdier.

For at overvinde denne frygt var jeg nødt til at holde mere end én session med forklaringer, præsentation af eksempler og Proof of Concept. Jo enklere og tættere eksemplerne er på de opgaver, der løses i projektet, jo bedre. Hvis du starter ud i diskussionen "generelt", fører det til en komplikation af forståelse og spildtid. Kort fortalt:mindre teori – mere praksis. Argumenterne for UTC og imod DateTimeOffset kan relateres til to kategorier:

  • “UTC hele tiden” er standarden, og resten virker ikke
  • UTC løser problemet med sommertid

Det skal bemærkes, at hverken UTC eller DateTimeOffset løser problemet med sommertid uden at bruge information om reglerne for konvertering mellem zoner, som er tilgængelig gennem TimeZoneInfo-klassen i C#.

Forenklet model

Som jeg bemærkede ovenfor, sker ændringer i den gamle kode kun i en database. Dette kan vurderes ved hjælp af et simpelt eksempel.

Eksempel på en model i 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'

Resultatet af scriptudførelsen vil være som følger.

Eksemplet viser, at denne model kun tillader ændringer i databasen, hvilket reducerer risikoen for defekter markant.

Eksempler på funktioner til behandling af dato-/tidsværdier

// 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

Små artefakter

Under justeringen af ​​SQL-koden blev der fundet nogle ting, der fungerer for DateTime, men som er inkompatible med DateTimeOffset:

GETDATE()+1 skal erstattes med DATEADD (dag, 1, SYSDATETIMEOFFSET ())

DEFAULT søgeordet er inkompatibelt med DateTimeOffset, du skal bruge SYSDATETIMEOFFSET()

ISNULL(date_field, NULL)> 0″-konstruktionen fungerer med DateTime, men DateTimeOffset bør erstattes med "dato_field IS NOT NULL"

Konklusion eller UTC vs DateTimeOffset

Nogen vil måske bemærke, at vi, ligesom i tilgangen med UTC, håndterer konverteringen, når vi modtager og returnerer data. Hvorfor har vi så brug for alt dette, hvis der er en velafprøvet og fungerende løsning? Der er flere grunde til dette:

  • DateTimeOffset giver dig mulighed for at glemme, hvor SQL Server er placeret.
  • Dette giver dig mulighed for at flytte en del af arbejdet til systemet.
  • Konvertering kan minimeres, hvis DateTimeOffset bruges overalt, idet det kun udføres før visning af data eller output til eksterne systemer.

Disse grunde forekom mig væsentlige på grund af brugen af ​​denne tilgang.

Jeg vil med glæde besvare dine spørgsmål, skriv venligst kommentarer.


  1. Saml rækker i Oracle SQL-sætning

  2. Betyder rækkefølgen af ​​tabeller i en joinforbindelse, når LEFT (ydre) joins bruges?

  3. Hvordan opretter jeg en kontrolbegrænsning med flere tabeller?

  4. Applikationsbrugere vs. Row Level Security