sql >> Database >  >> RDS >> Database

Meer over de introductie van tijdzones in langlevende projecten

Enige tijd geleden zijn we begonnen het systeem aan te passen aan de nieuwe markt die ondersteuning voor tijdzones vereist. Het eerste onderzoek werd beschreven in het vorige artikel. Nu is de aanpak enigszins geëvolueerd onder invloed van de realiteit. Dit artikel beschrijft de problemen die tijdens de besprekingen zijn ondervonden en de uiteindelijke beslissing die wordt uitgevoerd.

TL;DR

  • Het is noodzakelijk om termen te onderscheiden:
    • UTC is de lokale tijd in de zone +00:00, zonder het DST-effect
    • DateTimeOffset – lokale tijdverschuiving van UTC ± NN:NN, waarbij de verschuiving de basisverschuiving van UTC is zonder het zomertijdeffect (in C# TimeZoneInfo.BaseUtcOffset)
    • DateTime – lokale tijd zonder informatie over de tijdzone (we negeren het Kind-attribuut)
  • Verdeel het gebruik in extern en intern:
    • Invoer- en uitvoergegevens via API, berichten, export/import van bestanden moeten strikt in UTC (DateTime-type) zijn
    • Binnen het systeem worden de gegevens samen met de offset opgeslagen (type DateTimeOffset)
  • Verdeel het gebruik in de oude code in niet-DB-code (C#, JS) en DB:
    • Niet-DB-code werkt alleen met lokale waarden (DateTime-type)
    • De database werkt met lokale waarden + offset (DateTimeOffset type)
  • Nieuwe projecten (componenten) gebruiken DateTimeOffset.
  • In een database verandert het DateTime-type eenvoudig in DateTimeOffset:
    • In tabelveldtypen
    • In de parameters van opgeslagen procedures
    • Incompatibele constructies zijn opgelost in de code
    • Offset-informatie is gekoppeld aan een ontvangen waarde (eenvoudige aaneenschakeling)
    • Voordat u terugkeert naar de niet-DB-code, wordt de waarde geconverteerd naar lokaal
  • Geen wijzigingen aan niet-DB-code
  • DST wordt opgelost met behulp van CLR Stored Procedures (voor SQL Server 2016 kunt u AT TIME ZONE gebruiken).

Nu meer in detail over de moeilijkheden die werden overwonnen.

“Diepgewortelde” standaarden van de IT-industrie

Het kostte nogal wat tijd om mensen te verlossen van de angst om datums in lokale tijd op te slaan met offset. Enige tijd geleden, als je een ervaren programmeur vraagt:"Hoe kan ik tijdzones ondersteunen?" – de enige optie was:“Gebruik UTC en converteer naar lokale tijd net voor demonstratie”. Het feit dat je voor een normale workflow nog steeds aanvullende informatie nodig hebt, zoals de offset- en tijdzonenamen, zat verborgen onder de motorkap van de implementatie. Met de komst van DateTimeOffset kwamen dergelijke details naar voren, maar de traagheid van de "programmeerervaring" laat niet toe om snel in te stemmen met een ander feit:"Een lokale datum opslaan met een basis UTC-offset" is hetzelfde als het opslaan van UTC. Een ander voordeel van het overal gebruiken van DateTimeOffset stelt u in staat controle over de naleving van .NET Framework- en SQL Server-tijdzones te delegeren, waardoor alleen de momenten van gegevensinvoer en -uitvoer van het systeem aan menselijke controle overblijven. Menselijke controle is de code die door een programmeur is geschreven om met datum/tijd-waarden te werken.

Om deze angst te overwinnen, moest ik meer dan één sessie houden met uitleg, het presenteren van voorbeelden en Proof of Concept. Hoe eenvoudiger en hoe dichter de voorbeelden bij de taken liggen die in het project worden opgelost, hoe beter. Als je in de discussie "in het algemeen" begint, leidt dit tot een complicatie van begrip en tijdverspilling. Kortom:minder theorie – meer praktijk. De argumenten voor UTC en tegen DateTimeOffset kunnen worden gerelateerd aan twee categorieën:

  • “UTC all time” is de standaard en de rest werkt niet
  • UTC lost het probleem met DST op

Opgemerkt moet worden dat noch UTC noch DateTimeOffset het probleem met DST oplost zonder gebruik te maken van informatie over de regels voor het converteren tussen zones, die beschikbaar is via de TimeZoneInfo-klasse in C#.

Vereenvoudigd model

Zoals ik hierboven opmerkte, vinden wijzigingen in de oude code alleen plaats in een database. Dit kan worden beoordeeld aan de hand van een eenvoudig voorbeeld.

Voorbeeld van een model 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'

Het resultaat van de uitvoering van het script is als volgt.

Het voorbeeld laat zien dat met dit model alleen wijzigingen in de database kunnen worden aangebracht, waardoor het risico op defecten aanzienlijk wordt verkleind.

Voorbeelden van functies voor het verwerken van datum-/tijdwaarden

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

Tijdens het aanpassen van SQL-code zijn enkele dingen gevonden die werken voor DateTime, maar niet compatibel zijn met DateTimeOffset:

GETDATE()+1 moet worden vervangen door DATEADD (dag, 1, SYSDATETIMEOFFSET ())

Het DEFAULT-sleutelwoord is niet compatibel met DateTimeOffset, u moet SYSDATETIMEOFFSET()

gebruiken

De constructie ISNULL(date_field, NULL)> 0″ werkt met DateTime, maar DateTimeOffset moet worden vervangen door "date_field IS NOT NULL"

Conclusie of UTC vs DateTimeOffset

Het kan iemand opvallen dat wij, net als in de aanpak met UTC, bij het ontvangen en retourneren van data de conversie regelen. Waarom hebben we dit dan allemaal nodig, als er een beproefde en werkende oplossing is? Hier zijn verschillende redenen voor:

  • Met DateTimeOffset kunt u vergeten waar SQL Server zich bevindt.
  • Hierdoor kunt u een deel van het werk naar het systeem verschuiven.
  • Conversie kan worden geminimaliseerd als DateTimeOffset overal wordt gebruikt en alleen wordt uitgevoerd voordat gegevens worden weergegeven of naar externe systemen worden uitgevoerd.

Deze redenen leken mij essentieel vanwege het gebruik van deze aanpak.

Ik zal graag uw vragen beantwoorden, schrijf alstublieft opmerkingen.


  1. PostgreSQL, SELECTEER van max id

  2. MySQL - ORDER BY-waarden binnen IN()

  3. De waarde van de identiteitskolom retourneren na invoeging in Oracle

  4. Hoe PII te vinden en te maskeren in Elasticsearch