Tem casos em que somente configurar tudo pra UTC não é o suficiente. Um exemplo é esse que vc citou:
Em geral a recomendação de "sempre use UTC" (ou a variação "sempre use timestamps") é válida para a maioria dos casos. Uma "boa prática" muito recomendada é a de converter para UTC o mais cedo possível (assim que recebe o dado, por exemplo) e converter para qualquer outro timezone no último instante (ex: para mostrar a data e hora para o usuário, usando seu fuso horário).
De fato, para muitos casos isso resolve. Mas "sempre" usar uma "boa prática" não vai funcionar 100% das vezes. Um exemplo são datas em eventos futuros.
Vamos supor que estamos em 2016, e temos um sistema no qual usuários podem cadastrar eventos futuros. Então um usuário cadastra um evento que ocorrerá em 31 de outubro de 2018, às 10h, no Horário Oficial de Brasília.
Em 2016, a regra do horário de verão brasileiro dizia que ele começaria no terceiro domingo de outubro. Ou seja, em 30 de outubro de 2018, já seria horário de verão, e o offset usado (a diferença com relação a UTC) é de duas horas atrás do UTC (-02:00
).
Portanto, a data/hora e offset do evento futuro seria 2018-10-31T10:00-02:00
, o que em UTC corresponde a 2018-10-31T12:00Z
(o "Z" no final significa que a data/hora está em UTC, de acordo com o formato definido pela norma ISO 8601). Então você segue a "boa prática" e grava o valor em UTC no banco. Quando algum usuário quer saber a data e hora do evento, você consulta o valor (que está em UTC: 2018-10-31T12:00Z
) e converte para o timezone do usuário (se for o Horário de Brasília, o resultado será 2018-10-31T10:00-02:00
). Até aqui, tudo certo.
Mas tem um detalhe, que nem sempre levamos em conta: o horário de verão é definido pelo governo, e ele pode mudar de ideia a qualquer momento (basta ver o histórico). E nesse caso, mudou mesmo: a regra do Horário Brasileiro de Verão foi mudada por um decreto publicado em dezembro de 2017. Segundo este decreto, a partir de 2018 o início do horário de verão passaria a ser no primeiro domingo de novembro.
Portanto, pelas novas regras, 31 de outubro de 2018 não está mais em horário de verão, o que significa que neste dia o Horário de Brasília ainda está 3 horas atrás de UTC (ou seja, o offset é -03:00
). E convertendo o valor em UTC que havia sido gravado no banco (2018-10-31T12:00Z
) para o offset -03:00
, o resultado é 2018-10-31T09:00-03:00
(9 da manhã, uma hora antes do que foi cadastrado pelo usuário). Lembre-se que UTC é um padrão que define um horário a partir do qual os fusos horários se baseiam, e não sofre os efeitos do horário de verão. Ele é um valor absoluto, e se algum timezone muda suas regras (como o offset utilizado em cada época do ano), a diferença entre a hora local e UTC também mudará.
Nesse caso, a solução seria guardar um DATETIME
com a data e hora local (2018-10-31T10:00
) e gravar o timezone separadamente. Se um usuário só quer saber quando será o evento, mostre a data e hora, e informe em qual timezone aquele dia e horário se referem, caso essa informação seja relevante para os usuários. Se quiser converter para UTC, a maioria das linguagens possui alguma API de data que faz essa conversão, usando as regras do timezone em questão.
Para o timezone, a maioria das linguagens e APIs possuem suporte ao Time Zone Database da IANA, que define identificadores como America/Sao_Paulo
, Europe/London
e Asia/Tokyo
. Cada um desses identificadores possui o histórico de alterações do horário local de determinada região (America/Sao_Paulo
, por exemplo, corresponde ao Horário de Brasília).
Cada vez que algum governo resolve mudar as regras do fuso horário de alguma região, a IANA atualiza seu banco e lança uma nova versão. Todas as linguagens e sistemas que usam o TZBD da IANA possuem algum mecanismo para atualizar seus dados, conforme a IANA disponibiliza essas atualizações. No exemplo acima, a mudança da regra do horário de verão brasileiro foi lançada na versão 2018c (em janeiro de 2018).
Ou seja, se você tiver gravado a data e hora local separada do timezone, bastaria atualizar o TZBD para que a conversão para UTC passasse a dar o valor correto. Já se tivesse gravado a data e hora em UTC, teria que analisar caso a caso e alterar as datas manualmente (e não é para mudar todas as datas entre outubro e novembro, somente aquelas que foram cadastradas antes de você atualizar o TZDB).
Neste artigo do Jon Skeet tem um exemplo bem mais completo sobre esse problema.