Trabalhando com datas e horários

19 de abril de 2022
Bryan Lima

Coder and Tech Consultant

  • Twitter
  • Facebook
  • LinkedIn
Relógios com diferentes fuso horários

Saber manipular data e hora em qualquer linguagem de programação é importante para evitar vários problemas.

Nesse artigo vou mostrar alguns cenários e como a escolha do formato interfere e pode causar problemas.

Antes de começar vou explicar o que são culturas e o impacto dela na programação.

O que são culturas ?

Culturas são definições de como um valor será interpretado usando um idioma ou cultura da região.

Ao usar uma cultura pode interpretar e exibir os valores como data, hora, direção de texto, pesos, medidas e moedas para o formato que as pessoas daquela idioma ou região estão acostumadas.

Um exemplo é a diferença de como é exibida uma data para as culturas pt-BR e en-GB onde a data 1/2/2022 pode ser 1 de Fevereiro de 2022 em pt-BR ou 2 de janeiro de 2022 em en-GB. Essa ambiguidade, se não tratada, pode ser causar problemas em algum momento.

Em .NET existem algumas formas de usar culturas para conversão de valores que é definir System.Globalization.CultureInfo.CurrentCulture para efeito em toda aplicacão ou Thread, ou informar uma instância de IFormatProvider com a cultura desejada nos métodos que aceitam a interface.

O que é horário em UTC, Local e Fuso Horário ?

Fuso horário é uma padronização mundial onde o mundo é dividido em vários fuso horários.

Horário UTC é uma definição um horário como sendo a base para o mundo, e com a utilização do fuso horário, isso significa que todas as datas e horas apontam para o mesmo momento no tempo, apenas adicionando ou removendo horas do fuso horário.

Horário Local é o uso de UTC com a soma ou subtração do fuso horário para aquela região. Exemplo é para o Brasil onde o fuso horário é definido como +3 horas, isso significa que é necessário adicionar 3 horas para a data e hora serem UTC.

DateTime

Em .NET, DateTime.Now obtem a data e hora local baseado na cultura que aplicação está usando ou detectada na máquina que está executando.

Ao usar DateTime.Now tem que ficar atento à alguns problemas que podem ocorrer:

  • A mesma aplicação pode ter diferentes datas e horas em servidores, onde um servidor está usando UTC e outro Local;
  • Problemas com DST (daylight saving time) onde o horário avança ou recua algumas horas ou minutos, podendo alterar até data;
  • Problemas de sincronização entre servidores por causa da ambiguidade;

Em .NET, ao inves de utilizar DateTime.Now pode utilizar DateTime.UtcNow para evitar problemas de data e hora local, mas continua sem fuso horário explícito e deve ter cuidados ao converter valores por causa do DateTimeKind.

Outro ponto de atenção é com o DateTimeKind em .NET, dependendo de como o dado será tratado ele pode sofrer várias alterações na data e hora.

Observações:

  • Use data e hora em UTC para timestamping, sincronização, logs, etc;
  • Não altere a data e hora que o usuário cadastrar para o formato UTC ou nem adicione fuso horário;
  • Não use data e hora local para sincronização de servidores em diferentes regiões;

DateTime Kind

DateTimeKind significa que a data e hora estão ou serão convertidos usando Unspecified, Local ou UTC.

Como funciona DateTimeKind:

  • Independente do tipo não haverá alteração se usar ToString;
  • Não haverá alteração se o tipo é UTC e usar a função ToUniversalTime;
  • Não haverá alteração se o tipo é Local e usar a função ToLocalTime;
  • Haverá alteração se o tipo é Unspecified ao usar ToUniversalTime ou ToLocalTime;
C#
// Apenas para facilitar a leitura.
var pattern = "yyyy-MM-ddTHH:mm:ss";

// 1. Defina a data como UTC e salve no banco de dados.
var date = DateTime.UtcNow;

// 2. Obtenha a informação de uma fonte externa ou banco de dados.
var externalUtcDate = date.ToString(pattern); // simulação de fonte externa.

// 3. Interprete o valor para um objeto DateTime
var parsedExternalDate = DateTime.Parse(externalUtcDate);

// 4. Altere DateTimeKind para ver a diferença
var currentDate = new DateTime(parsedExternalDate.Ticks, DateTimeKind.Local);

Console.WriteLine("Data original em UTC: " + externalUtcDate);
Console.WriteLine("Kind: " + currentDate.Kind);
Console.WriteLine(currentDate.ToString(pattern)); // sem conversão para outros tipos

Console.WriteLine("Local Kind: " + currentDate.ToLocalTime().Kind);
Console.WriteLine("Local Time: " + currentDate.ToLocalTime().ToString(pattern));

Console.WriteLine("UTC   Kind: " + currentDate.ToUniversalTime().Kind);
Console.WriteLine("UTC   Time: " + currentDate.ToUniversalTime().ToString(pattern));

DateTimeOffset

Tem todas as informações importantes para trabalhar com datas e horas no formato correto.

Como visto, ao utilizar DateTime deve tomar alguns cuidados, e DateTimeOffset é uma alternativa segura porque contem informações de fuso horário que informa a quantidade de horas que precisa ser adicionada ou removida para utilizar ser UTC e evitar ambiguidades.

Ao utilizar DateTimeOffset não precisa se preocupar se a data e hora é UTC ou Local.

Considere utilizar DateTimeOffset ao invés de DateTime.

Ambiguidade

Um problema comum em qualquer linguagem de programação é a ambiguidade no formato da data e hora no momento de interpretar. Um exemplo é data 1/2/2022 onde dependendo da cultura pode ser interpretada como 1 de Fevereiro de 2022 ou 2 de Janeiro de 2022 e tudo isso vai depender de como o valor será interpretado usando a cultura de forma implícita ou explícita.

Existe o problema de conversão de hora também onde 10 horas pode ser 10 da manhã ou 10 da noite, tudo isso depende como será interpretada.

Para evitar esse problema é utilizado o formato ISO 8601 onde não é necessário se preocupar com ambiguidades.

ISO 8601

A ISO 8601 ajuda a eliminar dúvidas que podem resultar das várias convenções de datas, culturas e fusos horários.

As datas são expressadas como ano-mes-dia (yyyy-MM-dd) e as horas como hora-minuto-segundos (HH:mm:ss), ja as frações de milissegundos como .fffffff e no final o time zone como +hora:minuto (HH:mm).

Por exemplo, a data 25 de Dezembro de 2014 às 03:43:12 horas da tarde no Brasil é representada como 2014-12-25T15:43:12+03:00.

O fuso horário pode ter o prefixo positivo (+) ou negativo (-) para informar a quantidade de tempo que tem que adicionar ou remover para chegar ao UTC. Então a data 2014-12-25T15:43:12+03:00 significa que tem que adicionar 3 horas para ser UTC 2014-12-25T18:43:12.

Uma vantagem é que o padrão tem uma notação descendente onde os números maiores são escritos antes dos menores e isso facilita para a leitura, comparação e ordenação de datas e horas por string de uma forma eficiente.

Para usar ISO 8601 em .NET é necessário usar o método ToString passando o parâmetro O como valor. DateTimeOffset.Now.ToString("O")

Observações ao usar ISO 8601:

  • Legível para humanos;
  • Facilmente comparável usando uma simples comparação de strings;
  • As horas são no formato de 24 horas e não de 12 horas.
  • Mais preciso que outros formatos;
  • Ocupa mais espaço que outros formatos;

UNIX Time

É um formato que conta o tempo em segundos desde a data 1970-01-01 e seu formato é muito eficiente para fazer comparações entre valores por só utilizar números ao invés de ter que interpretar strings antes para fazer a comparação depois.

Um exemplo é a conversão de uma data e hora qualquer como 2014-10-21T06:33:27.0000000+00:00 em ISO 8601 que é 1413873207 segundos em UNIX Time.

Em .NET os métodos ToUnixSeconds e ToUnixMiliseconds são utilizados para conversão para Unix Time e estão no objeto DateTimeOffset.

Observações ao usar UNIX Time:

  • Extremamente rápido para comparações entre datas;
  • Ocupa menos espaço do que ISO 8601 e outros formatos;
  • Em algumas linguagens de programação é utilizado 32 bits ou 64 bits;
  • Não é feito para ser lido por humanos;
  • Não guarda informação de fuso horário;

Dicas

  • Não diga para todos usarem apenas UTC porque às vezes pode ser pior do que não usar;
  • Considere utilizar DateTimeOffset ao invés de DateTime;
  • Pode usar UTC para timestamping e hora local para eventos futuros informados pelo usuário;
  • Se o sistema é sensível à alterações de fuso horário ou DST então você deve se cadastrar no ICANN para receber informações sobre alterações já decididas ou discussões em andamento para evitar qualquer surpresa;
  • Se o sistema exporta data e hora é recomendável informar o formato que o dado está para quem vai consumir, ex: ISO 8601, unix seconds, se está em UTC ou Local em outra cultura ex: pt-BR, en-GB, etc.
  • Não altere o formato de data e hora informado pelo usuário e nem adicione fuso horário, trate como fuso horário não especificado e apenas consuma o dado sem usar alguma função que altere para UTC ou local;
  • Coloque o sufixo UTC no nome da propriedade ou coluna no banco de dados para evitar problemas futuros de alguém informar uma data não UTC;

Referências

  1. ISO 8601
PUBLICADO EM: