Como lidar com fusos horários e sincronizar seu software com clientes internacionais


Ao desenvolver algum software, você pode não pensar nos fusos horários no início. A menos que você viva em um país que precise lidar com vários fusos horários, como Estados Unidos ou Rússia.

Recentemente, deparei-me com um problema envolvendo fusos horários. Houve alguns testes de unidade fazendo afirmações sobre datas que costumavam trabalhar no meu escritório na França, mas não estavam trabalhando no Marrocos para novos membros de nossa equipe.

XBOX em Oferta

Aqui está o teste de unidade trabalhando na França, mas não no Marrocos

‌Esta foi uma oportunidade para aprender como lidar corretamente com datas e horas de software internacional. Neste artigo, apresentarei problemas de fuso horário e compartilharei algumas regras a seguir.

Introdução rápida aos fusos horários

Como a Terra é uma espécie de esfera, o sol nasce no Japão enquanto se põe na América. Se todos usassem o tempo global, digamos 09:00 seria o nascer do sol no Japão, mas para os americanos seria o pôr do sol. Não é muito útil.

Para garantir que o horário seja coordenado com o sol para todos, é necessário mudar o horário global de acordo com a sua localização. Como resultado, o globo se divide em fusos horários e cada um recebe uma Deslocamento. Esse deslocamento é um número de minutos para adicionar ao horário global para obter o fuso horário. Pode ser positivo ou negativo.

Fusos horários padrão do mundo – Ilustração de Hellerick a partir de Wikimedia Commons

O tempo global é chamado UTC, significa Tempo Universal Coordenado. Você também pode ouvir sobre GMT que é um fuso horário sem nenhum deslocamento.

Por exemplo, quando é 10:50 na UTC, também é 03:50 em San Francisco com um -0700 deslocamento e 18:50 em Pequim com um +0800 Deslocamento. No entanto, a mudança não ocorre apenas em horas inteiras: a compensação do Nepal é +0545. Você pode conferir em Wikipedia.

Além dessa compensação, que vem com o fuso horário, alguns países também mudam os relógios duas vezes por ano. Horário de verão ou verão adiciona uma hora ao deslocamento do fuso horário antes do verão. Em seguida, o relógio é redefinido para o fuso horário no inverno. O objetivo é tornar o dia mais longo.

A maneira mais comum de descobrir um fuso horário é usando o Banco de dados de fuso horário da IANA. Você acaba com uma string como Europe/Paris seguindo o padrão Área / Cidade. Além disso, a Microsoft mantém sua própria Banco de Dados Microsoft Time Zone usado em seus sistemas operacionais. Mas isso pode causar problemas ao executar aplicativos .NET Core de plataforma cruzada.

A IANA ainda é a opção. O banco de dados da Microsoft não é atualizado com frequência, contém menos histórico, nomes de fuso horário bastante curiosos (por exemplo: Romantic Standard Time) e é propenso a erros. Por exemplo, tente não confundir Arab , Arabic e Arabian Standard Time. Para mais detalhes sobre cada banco de dados e suas diferenças, confira este artigo.

Uma última coisa: há muitas maneiras de escrever uma data. Felizmente, o Especificação ISO 8601 define uma regra comum para formatação de data.

November 11, 2018 at 12:51:43 AM (in a time zone at UTC+00:00)
2018-11-05T12:51:43Z <- Z stands for UTC

November 11, 2018 at 12:51:43 AM (in a time zone at UTC +07:30)
2018-11-05T12:51:43+0730

Como os computadores lidam com datas

Os computadores só podem executar operações usando números. Isso significa que 2020-08-01 +1 não é igual a 2020-08-02 e não pode ser tratado.

Para trabalhar com datas mais facilmente, podemos representar datas como números. Isso é o que timestamps são tudo sobre. É o número de milissegundos decorridos de uma data predefinida (ou época) para a data especificada.

Ótimo, vamos escolher uma época então! Na verdade, a época comum já foi definida e seu valor é 1 de janeiro de 1970 (meia-noite UTC).

Para garantir que você entendeu, execute o snippet anterior no seu navegador. O que? Você não obteve o mesmo resultado?

Ok, eu trapacei um pouco para obter esse resultado ... eu deveria Thu Jan 01 1970 01:00 GMT+0100 porque o fuso horário do meu computador está definido para Europa / Paris.

Na verdade, esse momento com carimbo de data e hora zero é meia-noite em Greenwich, mas também 05:45 em Mumbai e até 1969-12-31T16:30 em São Francisco, quando você considera o deslocamento do fuso horário.

Regra nº 1: os carimbos de data e hora são apenas para salvar, não para exibir. É considerado no UTC porque não inclui nenhum deslocamento ou fuso horário.

Você não conseguiu a data "certa" antes porque o JavaScript usa seu fuso horário local para mostrar a data / hora mais precisa.

Agora, tente o seguinte trecho. Tenho certeza de que você obterá o mesmo resultado que eu:

Sim, o carimbo de data / hora zero é 1970-01-01T00:00:00 às UTC para todos em todo o mundo. Ainda assim, não é verdade se você escolher outro fuso horário.

Resumindo, toString mostra a data usando seu fuso horário local enquanto toUTCString é baseado no UTC. Também não se deixe enganar por toISOString que é o mesmo que toUTCString mas gera o formato ISO 8601 (seu nome deve ser toUTCISOString)

Eu recomendo o comando date para converter um segundo carimbo de data / hora (não milissegundos) em uma sequência legível. O uso desse comando com a opção UTC garante que ele não leve em consideração o fuso horário do computador / navegador.

# Linux
$ date -d @1586159897 -u 
Mon Apr  6 07:58:17 UTC 2020

# For Osx users
$ date -r 1586159897 -u 

Vamos consertar nosso teste de unidade

O problema que encontrei com os fusos horários estava nos meus testes de unidade. Reserve um tempo para ler e entender o que deve afirmar:

Neste teste, o objetivo é verificar se setHours define as horas e os minutos da data para zero (meia-noite). Primeiro escolho um carimbo de data e hora aleatório que não é à meia-noite. Em seguida, compare o resultado com o registro de data e hora do mesmo dia à meia-noite.

Na verdade, está funcionando - mas somente se o deslocamento do seu fuso horário for +0200 (incluindo DST) neste momento. Por exemplo, não está funcionando para a África / Casablanca ( +0100 incluindo DST). Vamos ver como esses carimbos de data e hora são impressos:

É isso, a data UTC dos dois resultados não é a mesma. Isso também significa que os carimbos de data e hora resultantes também não são os mesmos.

Como você pode ver, o deslocamento para Paris é +0200 e +0100 para Casablanca. Mas ambos exibem meia-noite com toString. Isso significa que o setHours A função usa o fuso horário do computador para executar a operação. E toString exibe a data usando seu fuso horário.

Este não é o único problema com este teste: e se você executar esse teste em San Fransisco? Certo, o dia seria 2020-07-31 para as duas datas por causa do -0700 Deslocamento.

A maneira mais segura de tornar esse teste confiável e funcionar em todo o mundo é usar uma data no fuso horário local. Você não usará carimbos de data e hora para definir datas iniciais.

Podemos aprimorar a regra anterior sobre carimbos de data e hora:

Regra nº 2: as datas das strings são adequadas para exibição usando o fuso horário e os cálculos do usuário. Eles não estão no UTC, mas geralmente incluem um deslocamento.

Mantenha-o atualizado no lado do servidor

A regra sobre carimbos de data e hora ainda se aplica no lado do servidor. No entanto, a segunda regra sobre o uso de datas de sequência não pode ser usada.

De fato, em alguns casos, com tecnologias como PHP, Java e Rails, as páginas são renderizadas no lado do servidor (SSR) Isso significa que todo o HTML é gerado pelo servidor e não tem idéia do fuso horário do cliente. Pense no servidor - nada mais é do que um computador no mundo. Ele também possui seu próprio fuso horário, mas não é necessariamente o mesmo que o fuso horário do cliente.

Regra nº 3: os servidores podem conhecer o fuso horário do cliente ou enviar uma data no UTC. O fuso horário do servidor não importa.

O novo Java 8 Date / Time é considerado uma das APIs mais compreensíveis e claras que ajuda você a lidar com a data. Não vou explicar como funciona aqui, mas vamos rever alguns pontos interessantes.

LocalDateTime, OffsetDateTime e ZonedDateTime são as 3 classes fornecidas para calcular e exibir data e hora. Não mais Date ou DateTime que exibem a data local e a data UTC.

Os exemplos a seguir são extraídos de este artigo incrível (escrito por Jonas Konrad), que descreve a API Java 8 Date / Time com vários exemplos. A propósito, muito obrigado a ele, ele gentilmente me deixou citar suas partes de código!

Vejamos as diferenças entre as três classes:

Há uma pequena mas importante diferença entre OffsetDateTime e ZonedDateTimevocê percebeu isso?

Como o próprio nome diz, OffsetDateTime está ciente apenas de um deslocamento entre a data local e o UTC. Isso significa que ele lida com o horário de verão diferente de uma data anexada a um fuso horário.

O exemplo com um fuso horário parece ser o comportamento correto. Na verdade, ambos estão corretos porque adicionar 1 dia pode significar:

  • Adicione 1 dia e mantenha a mesma hora (lida com o horário de verão com ZonedDateTime)
  • Adicione 24 horas à data atual (com OffsetDateTime)

Lembre-se da Regra nº 1 sobre carimbos de data e hora? Você deve usar apenas um carimbo de data / hora UTC para salvar. A API Java fornece um Instant classe que é um registro de data e hora que você pode obter de qualquer uma das três classes usadas para exibir a data.

Pensamentos finais

Neste artigo, você aprendeu que os carimbos de data e hora são para salvar (Regra nº 1) e as datas das cadeias de caracteres são para exibição (Regra nº 2). Você notou que o número de segundos da época é um número bastante grande?

Por isso, depois do Problema com o Bug do Milênio do Unix (Y2K) vem o Problema no Y2K38 que significa o ano de 2038. No 2038-01-19T03:14:07Z o registro de data e hora (em segundos) atingirá o máximo para números inteiros assinados de 32 bits 2,147,483,647 . Ele se tornará um número negativo depois de adicionar mais um segundo.

Este sinal indica janeiro de 1900 em vez de janeiro de 2000 - Foto de Wikipedia Commons

"Nos fóruns, as pessoas dizem que não se importam porque seu software não será usado por 20 anos sem ser reescrito. Bem, isso pode ser verdade, mas ainda vamos pensar em algumas soluções (com MySQL):

  • Atualizar TIMESTAMP digite para números inteiros assinados de 64 bits
  • Salvar datas UTC em DATETIME colunas em vez de TIMESTAMP

Ambas as soluções têm suas vantagens e desvantagens. O primeiro parece um hack que relata o problema mais tarde. No entanto, ele corrige o problema por um tempo muito, muito longo. Seu software será descontinuado e não será mais usado quando o problema ocorrer novamente.

A segunda solução funciona por uma quantidade quase infinita de tempo (até 9999-12-31T23:59:59Z)

Usando TIMESTAMP recomendado para logs, enquanto DATETIME é melhor para outras necessidades. Lembre-se de que um carimbo de data e hora não pode armazenar uma data antes de 1970-01-01T00:00:00Z e não depois 2038-01-19T03:14:07Z. Isso significa que você deve usar DATETIME para salvar datas distantes no passado e no futuro.

Além disso, no MySQL TIMESTAMPs são armazenados no UTC, mas exibidos de acordo com um fuso horário especificado (e convertidos em UTC antes de salvar). Esse mecanismo é útil quando você precisa obter uma data local e não existe com DATETIME.

Uma última palavra sobre moment.js, uma biblioteca popular para lidar com datas. Eu experimentei um problema pela primeira vez e queria avisar sobre isso:

Ambos console.logs produzirá 2020-08-02 00:00. Se você está acostumado a programação funcional, espera hours e minutes para retornar um novo objeto de momento porque eles são funções puras. Não é o caso - eles modificam a data de entrada e a retornam para facilitar o encadeamento.

Obrigado por ler até o fim. Espero que esta minha experiência tenha sido útil para você. A propósito, não estou muito confiante sobre a escolha entre TIMESTAMP e DATETIME, não hesite em compartilhar sua experiência!

Se você achou este artigo útil, compartilhe-o nas mídias sociais para ajudar outras pessoas a encontrá-lo e mostrar seu apoio! 👊

Não se esqueça de verificar meu página do autor para próximos artigos 🙏



Fonte

Leave a Reply

Your email address will not be published. Required fields are marked *