sql >> Databáze >  >> RDS >> Database

Více o zavedení časových pásem v dlouhodobém projektu

Před časem jsme začali systém přizpůsobovat novému trhu, který vyžaduje podporu časových pásem. Prvotní výzkum byl popsán v předchozím článku. Nyní se tento přístup mírně vyvinul pod vlivem reality. Tento článek popisuje problémy, které se vyskytly během diskusí, a konečné rozhodnutí, které je implementováno.

TL;DR

  • Je nutné rozlišovat pojmy:
    • UTC je místní čas v pásmu +00:00 bez efektu letního času
    • DateTimeOffset – posun místního času od UTC ± NN:NN, kde posun je základní posun od UTC bez efektu DST (v C# TimeZoneInfo.BaseUtcOffset)
    • DateTime – místní čas bez informací o časovém pásmu (atribut Kind ignorujeme)
  • Rozdělte použití na externí a interní:
    • Vstupní a výstupní data prostřednictvím rozhraní API, zprávy, exporty/importy souborů musí být přísně v UTC (typ DateTime)
    • Uvnitř systému jsou data uložena spolu s offsetem (typ DateTimeOffset)
  • Rozdělte použití ve starém kódu na kódy jiné než DB (C#, JS) a DB:
    • Kód jiný než DB funguje pouze s místními hodnotami (typ DateTime)
    • Databáze pracuje s lokálními hodnotami + offset (typ DateTimeOffset)
  • Nové projekty (komponenty) používají DateTimeOffset.
  • V databázi se typ DateTime jednoduše změní na DateTimeOffset:
    • V typech polí tabulky
    • V parametrech uložených procedur
    • Nekompatibilní konstrukce jsou v kódu opraveny
    • Informace o posunu je připojena k přijaté hodnotě (jednoduché zřetězení)
    • Před návratem k jinému kódu než DB je hodnota převedena na místní
  • Žádné změny v kódu mimo DB
  • DST se řeší pomocí uložených procedur CLR (pro SQL Server 2016 můžete použít AT TIME ZONE).

Nyní podrobněji o obtížích, které byly překonány.

„Zakořeněné“ standardy IT průmyslu

Trvalo poměrně hodně času, než se lidé zbavili strachu z ukládání dat v místním čase s posunem. Když se před časem zeptáte zkušeného programátora:"Jak podporovat časová pásma?" – jediná možnost byla:„Použít UTC a převést na místní čas těsně před ukázkou“. Pod pokličkou implementace se skrýval fakt, že pro normální workflow stále potřebujete další informace, jako je offset a názvy časových pásem. S příchodem DateTimeOffset se takové podrobnosti objevily, ale setrvačnost „zkušenosti s programováním“ neumožňuje rychle souhlasit s dalším faktem:„Uložení místního data se základním offsetem UTC“ je stejné jako ukládání UTC. Další výhoda použití DateTimeOffset všude vám umožňuje delegovat kontrolu nad dodržováním časových pásem .NET Framework a SQL Server, přičemž lidské kontrole ponechává pouze okamžiky vstupu a výstupu dat ze systému. Lidské ovládání je kód napsaný programátorem pro práci s hodnotami data/času.

Abych tento strach překonal, musel jsem uspořádat více než jedno sezení s vysvětlením, prezentací příkladů a důkazem konceptu. Čím jednodušší a bližší příklady těm úlohám, které jsou v projektu řešeny, tím lépe. Začnete-li v diskusi „obecně“, vede to ke komplikaci porozumění a ztrátě času. Stručně:méně teorie – více praxe. Argumenty pro UTC a proti DateTimeOffset lze vztáhnout ke dvěma kategoriím:

  • Standardní je „UTC po celou dobu“ a zbytek nefunguje
  • UTC řeší problém s DST

Je třeba poznamenat, že UTC ani DateTimeOffset neřeší problém s DST bez použití informací o pravidlech pro převod mezi zónami, které jsou dostupné prostřednictvím třídy TimeZoneInfo v C#.

Zjednodušený model

Jak jsem poznamenal výše, ve starém kódu se změny dějí pouze v databázi. To lze posoudit pomocí jednoduchého příkladu.

Příklad modelu v 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'

Výsledek spuštění skriptu bude následující.

Příklad ukazuje, že tento model umožňuje provádět změny pouze v databázi, což výrazně snižuje riziko defektů.

Příklady funkcí pro zpracování hodnot data/času

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

Malé artefakty

Během úpravy kódu SQL byly nalezeny některé věci, které fungují pro DateTime, ale nejsou kompatibilní s DateTimeOffset:

GETDATE()+1 musí být nahrazeno DATEADD (den, 1, SYSDATETIMEOFFSET ())

Klíčové slovo DEFAULT není kompatibilní s DateTimeOffset, musíte použít SYSDATETIMEOFFSET()

Konstrukce ISNULL(date_field, NULL)> 0″ funguje s DateTime, ale DateTimeOffset by měl být nahrazen „date_field IS NOT NULL“

Závěr nebo UTC vs. DateTimeOffset

Někdo si může všimnout, že stejně jako v přístupu s UTC se zabýváme konverzí při příjmu a vracení dat. Tak proč to všechno potřebujeme, když existuje osvědčené a fungující řešení? Existuje pro to několik důvodů:

  • DateTimeOffset vám umožňuje zapomenout, kde se SQL Server nachází.
  • To vám umožní přesunout část práce do systému.
  • Převod lze minimalizovat, pokud se všude používá DateTimeOffset, a to pouze před zobrazením dat nebo jejich odesláním do externích systémů.

Tyto důvody se mi zdály zásadní vzhledem k použití tohoto přístupu.

Rád odpovím na vaše dotazy, pište komentáře.


  1. Existuje nějaký způsob, jak zajistit, aby se fond připojení JBoss znovu připojil k Oracle, když se připojení pokazí?

  2. Jak mohu zkrátit datum a čas na serveru SQL Server?

  3. Volání uloženého POSTUPU v Toad

  4. Jak CHAR() funguje v MariaDB