SQL Server en la práctica

Consultar fechas locales sobre datos UTC en SQL Server

Convierte correctamente los límites de un día local a UTC, contempla el horario de verano y conserva consultas que aprovechan los índices.

Un informe de pedidos del domingo necesita aclarar primero a qué zona horaria pertenece ese domingo. Un valor UTC identifica un instante, pero su fecha en el calendario depende de la zona del cliente o de la empresa. Aplicar siempre el mismo desfase puede producir totales convincentes y aun así omitir pedidos durante un cambio de horario. Sumar 24 horas al inicio convertido tampoco resuelve ese problema.

Una convención útil es guardar datetime2 con un contrato explícito de UTC y reflejarlo en un nombre como OrderedAtUtc. Ese tipo no almacena ninguna zona ni impone la convención. Todos los procesos que escriben deben respetarla. datetimeoffset conserva un desfase, pero no una zona identificada con sus reglas históricas y futuras.

Convertir cada límite por separado

Para consultar el 10 de marzo de 2024 en Denver, se construyen primero ambas medianoches locales. Después se convierte cada una a UTC de forma independiente. El ejemplo usa un identificador de zona de Windows; comprueba su disponibilidad en sys.time_zone_info.

DECLARE @LocalDate date = '20240310';
DECLARE @Zone sysname = N'Mountain Standard Time';
DECLARE @StartLocal datetime2(0) = CONVERT(datetime2(0), @LocalDate);
DECLARE @EndLocal datetime2(0) = DATEADD(day, 1, @StartLocal);
DECLARE @StartUtc datetime2(0) = CONVERT(datetime2(0),
    @StartLocal AT TIME ZONE @Zone AT TIME ZONE 'UTC');
DECLARE @EndUtc datetime2(0) = CONVERT(datetime2(0),
    @EndLocal AT TIME ZONE @Zone AT TIME ZONE 'UTC');
SELECT @StartUtc AS StartUtc, @EndUtc AS EndUtc,
       DATEDIFF(hour, @StartUtc, @EndUtc) AS HoursInLocalDay;

El intervalo esperado comienza el 10 de marzo a las 07:00 UTC y termina el 11 a las 06:00 UTC. Ese día local tiene 23 horas. Aunque el nombre contiene "Standard Time", incorpora las reglas del horario de verano. Para el 3 de noviembre de 2024, el intervalo resultante dura 25 horas.

El primer AT TIME ZONE interpreta el datetime2 sin desfase como una hora local de la zona indicada. El segundo transforma el datetimeoffset resultante a UTC. Solo entonces se elimina el desfase, que ya es cero, para comparar con una columna datetime2 cuyo significado acordado es UTC.

En algunas zonas, cambios históricos también afectan a la medianoche. Si el usuario introduce horas de citas, hay que decidir qué hacer con las horas inexistentes y con las repetidas. La resolución automática de una función no tiene por qué coincidir con la política del negocio. Guarda el instante elegido y el contexto necesario para justificarlo.

Comparar con un intervalo semiabierto

La consulta utiliza directamente los límites calculados.

-- Assumes Orders.OrderedAtUtc contains UTC datetime2 values.
SELECT OrderId, OrderedAtUtc, Total
FROM dbo.Orders
WHERE OrderedAtUtc >= @StartUtc
  AND OrderedAtUtc < @EndUtc;

El inicio está incluido y el final excluido. Un pedido que cae exactamente en la siguiente medianoche pertenece al siguiente día. El criterio sigue siendo correcto aunque cambie la precisión del datetime2 y evita inventar una última hora del día como 23:59:59.997.

Un índice que comience por OrderedAtUtc puede servir para recorrer ese rango. Si todas las consultas también filtran un único inquilino, TenantId seguido de OrderedAtUtc puede ser una opción mejor. Decide según los filtros reales. Convertir todas las marcas temporales dentro de WHERE suele dificultar una búsqueda directa por el índice original y multiplica el trabajo de conversión.

Alinea los tipos de los parámetros con la columna. Si los datos existentes usan datetimeoffset, mantén ese tipo en los límites. Valida también que la fecha final sea posterior a la inicial y limita intervalos enormes solicitados por error.

Mantener el significado del tiempo

No mezcles UTC y hora local del servidor en la misma columna. Después puede ser imposible saber qué convención se utilizó en cada fila, especialmente durante la hora repetida de otoño. Revisa importaciones, restricciones DEFAULT y todos los caminos de escritura antes de cambiar los informes.

Una reunión futura a las 09:00 locales plantea un requisito distinto al de registrar cuándo ocurrió un pedido. Las reglas legales de una zona pueden cambiar. Según lo que prometa el producto, guarda la fecha y hora local pretendida junto con la zona identificada y decide cuándo recalcular UTC. Para hechos terminados, conserva el instante real.

Prueba días normales, ambos cambios de horario, filas exactamente en cada límite y clientes de distintas zonas. Compara identificadores de pedidos además de importes: dos errores pueden compensarse en el total. Una consulta temporal correcta depende de una convención coherente en todo el sistema, desde la escritura hasta la presentación.

Referencias técnicas: Microsoft Learn: AT TIME ZONE · Microsoft Learn: datetime2.

Pregunta sobre este artículo

¿Tiene alguna pregunta sobre este tema?

Cuéntenos qué está evaluando o dónde tiene dificultades. Le responderemos con una recomendación práctica.

Inquiries are not enabled in this preview.

Hacer una pregunta sobre este artículo