sql >> Databasteknik >  >> RDS >> Database

Mer om introduktion av tidszoner i långlivade projekt

För en tid sedan började vi anpassa systemet till den nya marknaden som kräver stöd för tidszoner. Inledande forskning beskrevs i föregående artikel. Nu har tillvägagångssättet utvecklats något under påverkan av verkligheten. Den här artikeln beskriver de problem som stött på under diskussionerna och det slutgiltiga beslutet som implementeras.

TL;DR

  • Det är nödvändigt att skilja termer:
    • UTC är den lokala tiden i +00:00-zonen, utan sommareffekten
    • DateTimeOffset – lokal tidsförskjutning från UTC ± NN:NN, där förskjutningen är basförskjutningen från UTC utan DST-effekten (i C# TimeZoneInfo.BaseUtcOffset)
    • DateTime – lokal tid utan information om tidszonen (vi ignorerar Kind-attributet)
  • Dela upp användningen i extern och intern:
    • In- och utdata via API, meddelanden, filexporter/-importer måste vara strikt i UTC (DateTime-typ)
    • Inuti systemet lagras data tillsammans med offset (typ DatumTimeOffset)
  • Dela upp användningen i den gamla koden i icke-DB-kod (C#, JS) och DB:
    • Icke-DB-kod fungerar endast med lokala värden (DateTime-typ)
    • Databasen fungerar med lokala värden + offset (DateTimeOffset-typ)
  • Nya projekt (komponenter) använder DateTimeOffset.
  • I en databas ändras DateTime-typen helt enkelt till DateTimeOffset:
    • I tabellfältstyper
    • I parametrarna för lagrade procedurer
    • Inkompatibla konstruktioner är fixade i koden
    • Offsetinformation är kopplad till ett mottaget värde (enkel sammanlänkning)
    • Innan du återgår till icke-DB-koden, konverteras värdet till lokalt
  • Inga ändringar i icke-DB-kod
  • DST löses med CLR Stored Procedures (för SQL Server 2016 kan du använda AT TIME ZONE).

Nu mer i detalj om de svårigheter som övervunnits.

"Djupt rotade" standarder för IT-branschen

Det tog ganska lång tid att befria människor från rädsla för att lagra datum i lokal tid med offset. För en tid sedan, om du frågar en erfaren programmerare:"Hur stödjer man tidszoner?" – det enda alternativet var:"Använd UTC och konvertera till lokal tid strax före demonstrationen". Det faktum att du för ett normalt arbetsflöde fortfarande behöver ytterligare information, såsom offset- och tidszonnamn, gömdes under huven för implementeringen. Med tillkomsten av DateTimeOffset kom sådana detaljer ut, men trögheten i "programmeringsupplevelsen" tillåter inte att snabbt komma överens med ett annat faktum:"Att lagra ett lokalt datum med en grundläggande UTC-offset" är detsamma som att lagra UTC. En annan fördel med att använda DateTimeOffset överallt låter dig delegera kontroll över efterlevnaden av .NET Framework och SQL Server tidszoner, vilket ger mänsklig kontroll endast ögonblicken av datainmatning och utdata från systemet. Mänsklig kontroll är koden skriven av en programmerare för att arbeta med datum/tidsvärden.

För att övervinna denna rädsla var jag tvungen att hålla mer än en session med förklaringar, presentera exempel och Proof of Concept. Ju enklare och närmare exemplen de uppgifter som löses i projektet desto bättre. Om du börjar i diskussionen "i allmänhet" leder detta till en komplikation av förståelse och slöseri med tid. Kortfattat:mindre teori – mer praktik. Argumenten för UTC och mot DateTimeOffset kan relateras till två kategorier:

  • "UTC hela tiden" är standarden och resten fungerar inte
  • UTC löser problemet med sommartid

Det bör noteras att varken UTC eller DateTimeOffset löser problemet med sommartid utan att använda information om reglerna för konvertering mellan zoner, som är tillgänglig via TimeZoneInfo-klassen i C#.

Förenklad modell

Som jag noterade ovan, i den gamla koden, sker ändringar bara i en databas. Detta kan bedömas med ett enkelt exempel.

Exempel på en modell 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 av skriptkörningen blir som följer.

Exemplet visar att denna modell endast tillåter ändringar i databasen, vilket avsevärt minskar risken för defekter.

Exempel på funktioner för bearbetning av datum-/tidsvärden

// 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 av SQL-koden hittades några saker som fungerar för DateTime, men som är inkompatibla med DateTimeOffset:

GETDATE()+1 måste ersättas med DATEADD (dag, 1, SYSDATETIMEOFFSET ())

Nyckelordet DEFAULT är inkompatibelt med DateTimeOffset, du måste använda SYSDATETIMEOFFSET()

ISNULL(date_field, NULL)> 0″-konstruktionen fungerar med DateTime, men DateTimeOffset bör ersättas med “date_field IS NOT NULL”

Slutsats eller UTC vs DateTimeOffset

Någon kanske märker att vi, precis som i tillvägagångssättet med UTC, hanterar konverteringen när vi tar emot och returnerar data. Varför behöver vi då allt detta, om det finns en väl beprövad och fungerande lösning? Det finns flera anledningar till detta:

  • DateTimeOffset låter dig glömma var SQL Server finns.
  • Detta låter dig flytta en del av arbetet till systemet.
  • Konvertering kan minimeras om DateTimeOffset används överallt och endast utförs innan data visas eller matas ut till externa system.

Dessa skäl tycktes mig vara väsentliga på grund av att jag använde detta tillvägagångssätt.

Jag svarar gärna på dina frågor, skriv kommentarer.


  1. Hur man ändrar tabell i SQL Server genom att använda Alter Statement - SQL Server / T-SQL självstudie del 35

  2. Använda Passport med Sequelize och MySQL

  3. MariaDB JSON_VALUE() vs JSON_QUERY():Vad är skillnaden?

  4. PostgreSQL lastbalansering med HAProxy &Keepalved