Imagem © Mark Boyle | Australia Day Council of NSW.
Imagens propriedade do(s) respectivo(s) artista(s). Todos os direitos reservados.
AT TIME ZONE é um recurso tão legal e eu não tinha notado até recentemente, embora a Microsoft tenha uma página sobre isso desde dezembro.
Eu moro em Adelaide na Austrália. E como mais de um bilhão de outras pessoas no mundo, as pessoas de Adelaide têm que lidar com o fato de estarem em um fuso horário de meia hora. No inverno, estamos UTC+9:30, e no verão estamos UTC+10:30. Exceto que, se você estiver lendo isso no hemisfério norte, precisará lembrar que por 'inverno', quero dizer abril a outubro. O horário de verão é de outubro a abril, e o Papai Noel está sentado na praia com uma bebida gelada, suando através de seu grosso terno vermelho e barba. A menos que ele esteja salvando vidas, é claro.
Na Austrália, temos três fusos horários principais (ocidental, central e oriental), mas isso se estende a cinco no verão, pois os três estados que se estendem até o extremo norte da Austrália (WA, Qld e NT) não tente salvar qualquer luz do dia. Eles estão perto o suficiente do equador para não se importarem, ou algo assim. É muito divertido para o aeroporto de Gold Coast, cuja pista cruza a fronteira NSW-QLD.
Os servidores de banco de dados geralmente são executados em UTC, porque é simplesmente mais fácil não ter que lidar com a conversão entre UTC e local (e o inverso) no SQL Server. Muitos anos atrás, lembro-me de ter que corrigir um relatório que listava os incidentes que ocorreram junto com os tempos de resposta (tenho um blog sobre isso desde então). A medição do SLA foi bastante direta – pude ver que um incidente aconteceu durante o horário de trabalho do cliente e que eles responderam em uma hora. Pude ver que outro incidente ocorreu fora do horário de trabalho e a resposta foi em duas horas. O problema surgiu quando um relatório foi produzido no final de um período em que o fuso horário mudou, fazendo com que um incidente que realmente aconteceu às 17h30 (fora do horário) fosse listado como se tivesse ocorrido às 16h30 (horário interno) . A resposta levou cerca de 90 minutos, o que foi bom, mas o relatório mostrava o contrário.
Tudo isso é corrigido no SQL Server 2016.
Como usar AT TIME ZONE no SQL Server 2016
Agora, com AT TIME ZONE, em vez de dizer:'20160101 00:00 +10:30', posso começar com um valor datetime que não tem deslocamento de fuso horário e usar AT TIME ZONE para explicar que está em Adelaide.
SELECT CONVERT(datetime,'20160101 00:00') AT TIME ZONE 'Cen. Horário Padrão da Austrália'; -- 01-01-2016 00:00:00.000 +10:30
E isso pode ser convertido para o horário americano acrescentando AT TIME ZONE novamente.
SELECT CONVERT(datetime,'20160101 00:00') AT TIME ZONE 'Cen. Horário Padrão da Austrália' AT FUSO HORÁRIO 'Horário Padrão do Leste dos EUA'; -- 31/12/2015 08:30:00.000 -05:00
Agora, eu sei que isso é muito mais prolixo. E preciso converter explicitamente a string para datetime, para evitar um erro dizendo:
O tipo de dados do argumento varchar é inválido para o argumento 1 da função AT TIME ZONE.
Mas, apesar de ser prolixo, eu adoro, porque em nenhum momento precisei descobrir que Adelaide estava em +10:30, ou que Eastern era -5:00 – eu simplesmente precisava saber o fuso horário pelo nome. Descobrir se o horário de verão deveria ser aplicado ou não foi feito para mim, e não precisei fazer nenhuma conversão de local para UTC para estabelecer alguma linha de base.
Ele funciona usando o registro do Windows, que contém todas essas informações, mas, infelizmente, não é perfeito quando se olha para trás no tempo. A Austrália mudou as datas em 2008, e os EUA mudaram suas datas em 2005 – ambos os países economizando a luz do dia durante a maior parte do ano. AT TIME ZONE entende isso. Mas não parece apreciar que na Austrália no ano 2000, graças às Olimpíadas de Sydney, a Austrália começou o horário de verão cerca de dois meses antes. Isso é um pouco frustrante, mas não é culpa do SQL – precisamos culpar o Windows por isso. Acho que o registro do Windows não se lembra do hotfix que ocorreu naquele ano. (Nota para si mesmo:talvez precise pedir a alguém da equipe do Windows para corrigir isso…)
A utilidade continua embora!
Esse nome de fuso horário nem precisa ser uma constante. Eu posso passar variáveis e até usar colunas:
WITH PeopleAndTZs AS( SELECT * FROM (VALUES ('Rob', 'Cen. Australia Standard Time'), ('Paul', 'New Zealand Standard Time'), ('Aaron', 'US Eastern Standard Time' ) ) t (pessoa, tz))SELECT tz.person, SYSDATETIMEOFFSET() AT TIME ZONE tz.tz FROM PeopleAndTZs tz; /* Rob 2016-07-18 18:29:11.9749952 +09:30 Paul 2016-07-18 20:59:11.9749952 +12:00 Aaron 2016-07-18 04:59:11.9749952 -04:00*/
(Porque eu fiz isso pouco antes das 18h30 aqui em Adelaide, que por acaso são quase 21h na Nova Zelândia, onde Paul está, e quase 5h desta manhã na parte leste da América, onde Aaron está.)
Isso me permitiria ver facilmente que horas são para as pessoas onde quer que estejam no mundo e ver quem seria melhor para responder a algum problema, sem precisar realizar nenhuma conversão manual de data e hora. E ainda mais, me permitiria fazer isso pelas pessoas do passado. Eu poderia ter um relatório que analisasse quais fusos horários permitiriam que o maior número de eventos ocorresse durante o horário comercial.
Esses fusos horários estão listados emsys.time_zone_info
, juntamente com o deslocamento atual e se o horário de verão está aplicado no momento.
nome | current_utc_offset | is_currently_dst |
---|---|---|
Horário padrão de Cingapura | +08:00 | 0 |
W. Horário padrão da Austrália | +08:00 | 0 |
Horário padrão de Taipei | +08:00 | 0 |
Horário padrão de Ulaanbaatar | +09:00 | 1 |
Horário padrão da Coreia do Norte | +08:30 | 0 |
Aus Central W. Horário Padrão | +08:45 | 0 |
Horário Padrão Transbaikal | +09:00 | 0 |
Horário padrão de Tóquio | +09:00 | 0 |
Amostragem de linhas de sys.time_zone_info
Eu só estou realmente interessado em qual é o nome, mas de qualquer maneira. E é interessante ver que existe um fuso horário chamado “Aus Central W. Standard Time” que está no quarto de hora. Vai saber. Também vale a pena notar que os lugares são referidos usando seu nome de horário padrão, mesmo que estejam observando o horário de verão. Como Ulaanbaatar nessa lista acima, que não está listado como Ulaanbaatar Daylight Time. Isso pode levar as pessoas a um loop quando começarem a usar AT TIME ZONE.
AT TIME ZONE pode causar problemas de desempenho?
Agora, tenho certeza que você está se perguntando qual o impacto do uso de AT TIME ZONE na indexação.
Em termos de formato do plano, não é diferente de lidar com datetimeoffset em geral. Se eu tiver valores de data e hora, como na coluna AdventureWorks Sales.SalesOrderHeader.OrderDate (na qual criei um índice chamado rf_IXOD), execute esta consulta:
selecione OrderDate, SalesOrderID de Sales.SalesOrderHeader onde OrderDate>=convert(datetime,'20110601 00:00') no fuso horário 'US Eastern Standard Time' e OrderDate
E esta consulta:
selecione OrderDate, SalesOrderID de Sales.SalesOrderHeader onde OrderDate>=convert(datetimeoffset,'20110601 00:00 -04:00') e OrderDate
Em ambos os casos, você obtém planos assim:
Mas se explorarmos um pouco mais de perto, há um problema.
O que usa AT TIME ZONE não usa muito bem as estatísticas. Ele acha que verá 5.170 linhas saindo dessa Busca de Índice, quando na verdade há apenas 217. Por que 5.170? Bem, o post recente de Aaron, “Prestando atenção às estimativas”, explica, referindo-se ao post “Estimativa de cardinalidade para múltiplos predicados” de Paul. 5.170 é 31.465 (linhas na tabela) * 0,3 * sqrt(0,3).
A segunda consulta acerta, estimando 217. Nenhuma função envolvida, veja você.
Isso é provavelmente justo o suficiente. Quero dizer – no momento em que estiver produzindo o plano, ele não terá solicitado ao registro as informações de que precisa, então realmente não sabe quantos estimar. Mas há potencial para isso ser um problema.
Se eu adicionar predicados extras que sei que não podem ser um problema, minhas estimativas caem ainda mais – para 89,9 linhas.
selecione OrderDate, SalesOrderID de Sales.SalesOrderHeader onde OrderDate>=convert(datetime,'20110601 00:00') no fuso horário 'US Eastern Standard Time' e OrderDate=convert(datetimeoffset,'20110601 00:00 +14:00') e OrderDate
Estimar muitas linhas significa que muita memória é alocada, mas estimar muito pouco pode causar pouca memória, com a possível necessidade de um derramamento para corrigir o problema (o que geralmente pode ser desastroso do ponto de vista do desempenho). Leia o post de Aaron para obter mais informações sobre como estimativas ruins podem ser ruins.
Quando considero como lidar com a exibição de valores para essas pessoas de antes, posso usar consultas como esta:
WITH PeopleAndTZs AS( SELECT * FROM (VALUES ('Rob', 'Cen. Australia Standard Time'), ('Paul', 'New Zealand Standard Time'), ('Aaron', 'US Eastern Standard Time' ) ) t (pessoa, tz))SELECT tz.person, o.SalesOrderID, o.OrderDate AT TIME ZONE 'UTC' AT TIME ZONE tz.tzFROM PeopleAndTZs tzCROSS JOIN Sales.SalesOrderHeader oWHERE o.SalesOrderID BETWEEN 44001 AND 44010;
E adquira este plano:
…que não tem tais preocupações – o Compute Scalar mais à direita está convertendo o datetime OrderDate em datetimeoffset para UTC, e o Compute Scalar mais à esquerda está convertendo-o no fuso horário apropriado para a pessoa. O aviso é porque estou fazendo um CROSS JOIN, e isso foi totalmente intencional.
Vantagens e desvantagens de outros métodos de conversão de tempo
Antes de AT TIME ZONE, um dos meus recursos favoritos, mas muitas vezes não apreciados, do SQL 2008 era o tipo de dados datetimeoffset. Isso permite que os dados de data/hora também sejam armazenados com o fuso horário, como '20160101 00:00 +10:30', que é quando comemoramos o Ano Novo em Adelaide este ano. Para ver quando isso foi no Leste dos EUA, posso usar a função SWITCHOFFSET.
SELECT SWITCHOFFSET('20160101 00:00 +10:30', '-05:00'); -- 31/12/2015 08:30:00.0000000 -05:00
Este é o mesmo momento no tempo, mas em uma parte diferente do mundo. Se eu estivesse ao telefone com alguém na Carolina do Norte ou em Nova York, desejando a eles um Feliz Ano Novo porque já passava da meia-noite em Adelaide, eles estariam dizendo “O que você quer dizer? Ainda é hora do café da manhã aqui na véspera de Ano Novo!”
O problema é que, para fazer isso, preciso saber que em janeiro, Adelaide é +10:30 e US Eastern é -5:00. E isso muitas vezes é uma dor. Especialmente se estou perguntando sobre o final de março, início de abril, outubro, início de novembro – aquelas épocas do ano em que as pessoas não podem ter certeza de qual fuso horário as pessoas de outros países estavam porque mudam uma hora para o horário de verão e todos o fazem de acordo com regras diferentes. Meu computador me diz em que fuso horário as pessoas estão agora, mas é muito mais difícil dizer em que fuso horário elas estarão em outras épocas do ano.
Para outras informações sobre a conversão entre fusos horários e como pessoas como Aaron lidaram com o horário de verão, consulte os links a seguir:
- Usando AT TIME ZONE para corrigir um relatório antigo (sou eu!)
- Manipule a conversão entre fusos horários no SQL Server – parte 1
- Manipule a conversão entre fusos horários no SQL Server – parte 2
- Manipule a conversão entre fusos horários no SQL Server – parte 3
E alguma documentação oficial da Microsoft:
- AT TIME ZONE (Transact-SQL)
- sys.time_zone_info (Transact-SQL)
- Ajuda e suporte do horário de verão
Você deve usar AT TIME ZONE?
AT TIME ZONE não é perfeito. Mas é realmente útil – incrivelmente. É flexível o suficiente para aceitar colunas e variáveis como entrada, e vejo um enorme potencial para isso. Mas se isso vai fazer com que minhas estimativas saiam, então vou precisar ter cuidado. Para fins de exibição, isso não deve importar, e é aí que posso ver que é mais útil. Tornar mais fácil converter um valor de exibição de ou para UTC (ou para ou de um horário local), sem que ninguém precise se preocupar com deslocamentos e DST, é uma grande vitória.
Esse é realmente um dos meus recursos favoritos do SQL Server 2016. Há muito tempo venho clamando por algo assim.
Ah, e a maioria desses bilhões de pessoas no fuso horário de meia hora estão na Índia. Mas você provavelmente já sabia disso…