Consultar datas locais em dados UTC no SQL Server
Calcule os limites UTC de um dia local, trate mudanças de horário e use intervalos semiabertos para preservar consultas eficientes no SQL Server.
Um relatório de pedidos de domingo precisa definir primeiro a qual fuso horário esse domingo pertence. Um instante UTC é único, mas a data apresentada ao cliente depende do fuso adotado. Aplicar um deslocamento fixo pode gerar totais aparentemente corretos enquanto exclui pedidos durante uma mudança de horário. Somar 24 horas ao início já convertido também pode selecionar o intervalo errado.
Uma convenção prática é armazenar datetime2 com significado explicitamente definido como UTC, usando um nome como OrderedAtUtc. O tipo não registra um fuso e não impõe essa convenção. Todos os processos de gravação precisam segui-la. datetimeoffset registra um deslocamento, mas não substitui um fuso nomeado com suas regras históricas.
Converter os dois limites separadamente
Para consultar o dia 10 de março de 2024 em Denver, primeiro construímos as duas meias-noites locais. Só depois cada limite é convertido para UTC de forma independente. O exemplo usa um identificador de fuso do Windows; confira os nomes disponíveis em 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;
Os limites esperados são 10 de março às 07:00 UTC e 11 de março às 06:00 UTC. Portanto, esse dia local tem 23 horas. Embora o nome contenha "Standard Time", ele inclui as regras de horário de verão. Para 3 de novembro de 2024, o mesmo procedimento produz um intervalo de 25 horas.
O primeiro AT TIME ZONE interpreta o datetime2 sem deslocamento como horário local do fuso informado. O segundo converte o datetimeoffset resultante para UTC. Somente depois removemos o deslocamento, agora zero, para comparar o resultado com uma coluna datetime2 definida como UTC.
Algumas mudanças históricas de fuso também afetam a meia-noite. Se a aplicação recebe horários locais de compromissos, precisa decidir como tratar horários inexistentes ou repetidos. A escolha automática da função pode não corresponder à regra de negócio. Registre o instante escolhido e o contexto original necessário para explicar essa escolha.
Usar um intervalo semiaberto
A consulta de produção compara diretamente a coluna com os limites calculados.
-- Assumes Orders.OrderedAtUtc contains UTC datetime2 values.
SELECT OrderId, OrderedAtUtc, Total
FROM dbo.Orders
WHERE OrderedAtUtc >= @StartUtc
AND OrderedAtUtc < @EndUtc;
O limite inicial é inclusivo e o final é exclusivo. Um pedido exatamente na próxima meia-noite pertence ao dia seguinte. A regra continua correta quando a precisão do datetime2 muda e evita inventar um último horário do dia, como 23:59:59.997.
Um índice iniciado por OrderedAtUtc pode apoiar essa leitura por intervalo. Se cada consulta também restringe um único cliente de uma aplicação multitenant, TenantId seguido de OrderedAtUtc pode funcionar melhor. A decisão depende dos predicados reais. Converter cada horário armazenado dentro de WHERE geralmente dificulta o acesso direto pelo índice original e repete a conversão para muitas linhas.
Mantenha os tipos dos parâmetros compatíveis com a coluna. Se os dados existentes usam datetimeoffset, preserve esse tipo nos limites. Valide ainda que o fim venha depois do início e imponha limites para períodos gigantes solicitados por engano.
Preservar o significado dos horários
Não misture valores UTC e horários locais do servidor na mesma coluna. Uma conversão posterior não consegue necessariamente recuperar a convenção utilizada em cada registro, principalmente na hora repetida durante a volta do horário de verão. Revise importações, valores padrão e todos os caminhos de gravação.
Um compromisso futuro às 09:00 locais é diferente de registrar quando ocorreu um pedido. Regras legais de fuso podem mudar. Conforme a promessa do produto, pode ser necessário guardar a data e hora locais pretendidas e o nome do fuso, definindo quando recalcular o instante UTC. Para acontecimentos concluídos, preserve o instante real.
Teste dias comuns, as duas transições de horário, registros exatamente nos limites e clientes de fusos diferentes. Compare os identificadores dos pedidos além dos totais: dois erros opostos podem se cancelar numericamente. A correção depende de um contrato temporal consistente entre gravação, consulta e apresentação, e não apenas de uma expressão de conversão.
Referências técnicas: Microsoft Learn: AT TIME ZONE · Microsoft Learn: datetime2.