sql >> Base de Datos >  >> RDS >> Database

Más sobre Introducción de zonas horarias en proyectos de larga duración

Hace un tiempo comenzamos a adaptar el sistema al nuevo mercado que requiere soporte para zonas horarias. La investigación inicial se describió en el artículo anterior. Ahora el enfoque ha evolucionado ligeramente bajo la influencia de las realidades. Este artículo describe los problemas encontrados durante las discusiones y la decisión final que se implementa.

TL;DR

  • Es necesario distinguir términos:
    • UTC es la hora local en la zona +00:00, sin el efecto DST
    • DateTimeOffset:compensación de hora local desde UTC ± NN:NN, donde la compensación es la compensación base de UTC sin el efecto DST (en C# TimeZoneInfo.BaseUtcOffset)
    • DateTime:hora local sin información sobre la zona horaria (ignoramos el atributo Tipo)
  • Divida el uso en externo e interno:
    • Los datos de entrada y salida a través de la API, los mensajes, las exportaciones/importaciones de archivos deben estar estrictamente en UTC (tipo de fecha y hora)
    • Dentro del sistema, los datos se almacenan junto con el desplazamiento (tipo DateTimeOffset)
  • Dividir el uso en el código anterior en código que no sea DB (C#, JS) y DB:
    • El código que no es DB opera solo con valores locales (tipo DateTime)
    • La base de datos trabaja con valores locales + desplazamiento (tipo DateTimeOffset)
  • Los nuevos proyectos (componentes) usan DateTimeOffset.
  • En una base de datos, el tipo DateTime simplemente cambia a DateTimeOffset:
    • Tipos de campos de tabla
    • En los parámetros de los procedimientos almacenados
    • Las construcciones incompatibles se corrigen en el código
    • La información de compensación se adjunta a un valor recibido (concatenación simple)
    • Antes de volver al código que no es DB, el valor se convierte a local
  • No hay cambios en el código que no es DB
  • El horario de verano se resuelve mediante procedimientos almacenados de CLR (para SQL Server 2016 puede usar AT TIME ZONE).

Ahora, con más detalle sobre las dificultades que fueron superadas.

Estándares "profundamente arraigados" de la industria de TI

Tomó bastante tiempo aliviar a las personas del temor de almacenar fechas en hora local con desplazamiento. Hace algún tiempo, si le preguntas a un programador experimentado:"¿Cómo admitir zonas horarias?" – la única opción era:“Usar UTC y convertir a la hora local justo antes de la demostración”. El hecho de que para el flujo de trabajo normal aún necesita información adicional, como los nombres de la zona horaria y el desplazamiento, se ocultó bajo el capó de la implementación. Con el advenimiento de DateTimeOffset, surgieron tales detalles, pero la inercia de la "experiencia de programación" no permite estar rápidamente de acuerdo con otro hecho:"Almacenar una fecha local con un desplazamiento UTC básico" es lo mismo que almacenar UTC. Otra ventaja de usar DateTimeOffset en todas partes le permite delegar el control sobre la observancia de las zonas horarias de .NET Framework y SQL Server, dejando para el control humano solo los momentos de entrada y salida de datos del sistema. El control humano es el código escrito por un programador para trabajar con valores de fecha/hora.

Para superar este miedo, tuve que realizar más de una sesión con explicaciones, ejemplos y pruebas de concepto. Cuanto más simples y cercanos sean los ejemplos a aquellas tareas que se resuelven en el proyecto, mejor. Si comienza en la discusión "en general", esto lleva a una complicación de comprensión y pérdida de tiempo. Brevemente:menos teoría, más práctica. Los argumentos a favor de UTC y en contra de DateTimeOffset se pueden relacionar con dos categorías:

  • “UTC todo el tiempo” es el estándar y el resto no funciona
  • UTC resuelve el problema con DST

Cabe señalar que ni UTC ni DateTimeOffset resuelven el problema con DST sin usar información sobre las reglas para convertir entre zonas, que está disponible a través de la clase TimeZoneInfo en C#.

Modelo simplificado

Como señalé anteriormente, en el código anterior, los cambios ocurren solo en una base de datos. Esto se puede evaluar usando un ejemplo simple.

Ejemplo de un modelo en 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'

El resultado de la ejecución del script será el siguiente.

El ejemplo muestra que este modelo permite realizar cambios solo en la base de datos, lo que reduce significativamente el riesgo de defectos.

Ejemplos de funciones para procesar valores de fecha/hora

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

Pequeños artefactos

Durante el ajuste del código SQL, se encontraron algunas cosas que funcionan para DateTime, pero son incompatibles con DateTimeOffset:

GETDATE()+1 debe reemplazarse con DATEADD (día, 1, SYSDATETIMEOFFSET ())

La palabra clave DEFAULT es incompatible con DateTimeOffset, debe usar SYSDATETIMEOFFSET()

La construcción ISNULL(date_field, NULL)> 0″ funciona con DateTime, pero DateTimeOffset debe reemplazarse con "date_field IS NOT NULL"

Conclusión o UTC frente a DateTimeOffset

Alguien puede notar que, como en el enfoque con UTC, nos ocupamos de la conversión al recibir y devolver datos. Entonces, ¿por qué necesitamos todo esto, si existe una solución probada y funcional? Hay varias razones para esto:

  • DateTimeOffset le permite olvidar dónde se encuentra SQL Server.
  • Esto le permite transferir parte del trabajo al sistema.
  • La conversión se puede minimizar si se usa DateTimeOffset en todas partes, realizándola solo antes de mostrar datos o enviarlos a sistemas externos.

Estas razones me parecieron esenciales debido al uso de este enfoque.

Estaré encantado de responder a sus preguntas, por favor escriba comentarios.