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;
// 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;