DateTime no C# e .NET
O DateTime no C# é a estrutura que o .NET oferece para representar datas e horas, e quase toda aplicação precisa dela em algum momento. Neste guia, você vai entender como ela funciona por dentro, como criar valores, formatar a saída, fazer cálculos e lidar com culturas e fusos horários.
O que é o DateTime no C#
O DateTime no C# é um tipo de dados struct, ou seja, um tipo de valor. Por isso, ele é composto por outros tipos e métodos e sempre começa com um valor padrão.
Na prática, a estrutura System.DateTime armazena um número de ticks. Cada tick equivale a 100 nanossegundos contados a partir da meia-noite de 1º de janeiro do ano 1 do calendário gregoriano.
Além disso, o tipo guarda uma informação chamada Kind, que indica se o valor representa a hora local, o tempo universal coordenado (UTC) ou nenhum dos dois. Esse detalhe parece pequeno, porém faz toda a diferença quando a aplicação roda em servidores de regiões diferentes.
O intervalo suportado vai de DateTime.MinValue, em 01/01/0001 00:00:00, até DateTime.MaxValue, em 31/12/9999 23:59:59.9999999. Em outras palavras, a estrutura cobre qualquer data que uma aplicação comercial possa precisar.
Criando uma variável DateTime
Para criar um DateTime, basta usar o construtor padrão, como no exemplo a seguir.
var date = new DateTime();
Como se trata de um tipo de valor, uma variável DateTime nunca é nula. Então, mesmo sem atribuir nada, ela já nasce com um valor inicial, que podemos verificar assim:
Console.WriteLine(date);
// 1/1/0001 12:00:00 AM
Esse é o valor inicial de qualquer DateTime no C#, idêntico a DateTime.MinValue. Caso você precise de uma data opcional, use o tipo anulável DateTime?, que aceita null.
Repare também que o formato exibido segue a cultura do sistema onde o código executou. No exemplo, a máquina usava a cultura americana, por isso o mês aparece antes do dia e a hora vem no padrão AM/PM.
Obtendo data e hora atual com DateTime no C#
Agora que sabemos criar a variável, podemos atribuir a data e a hora atuais. Para isso, o DateTime no C# oferece a propriedade estática DateTime.Now.
Note que Now é uma propriedade e não um método, então não leva parênteses. Para exibir a data e a hora local, o código fica assim:
var date = DateTime.Now;
Console.WriteLine(date);
// 6/17/2021 8:10:09 AM
Já para obter a data e a hora no padrão global, usamos DateTime.UtcNow. A sigla UTC significa Coordinated Universal Time, ou tempo universal coordenado.
var date = DateTime.UtcNow;
Console.WriteLine(date);
// 6/17/2021 11:10:09 AM
Observe que as duas chamadas retornaram o momento exato da execução, mas com três horas de diferença. Isso acontece porque o computador do exemplo estava no horário de Brasília, que fica três horas atrás do UTC.
Além disso, o valor de Now traz Kind igual a Local, enquanto o de UtcNow traz Kind igual a Utc. Segundo a documentação oficial da propriedade DateTime.Now, a resolução dessas propriedades depende do relógio do sistema operacional e costuma ficar entre 0,5 e 15 milissegundos.
Por esse motivo, a documentação não recomenda medir desempenho com o DateTime no C#. Nesse caso, a classe Stopwatch é a ferramenta certa.
Quando usar Now e quando usar UtcNow
Use DateTime.Now apenas quando o objetivo for exibir a hora para o usuário daquela máquina. Para gravar dados, registrar logs ou comparar momentos, prefira DateTime.UtcNow.
A própria Microsoft orienta salvar e compartilhar valores DateTime sempre em UTC. Afinal, apenas um valor em UTC identifica o mesmo instante em qualquer servidor ou fuso horário.
Se a sua regra de negócio depende de datas, vale também desacoplar a obtenção da hora atual. O artigo como testar data e hora em .NET mostra como fazer isso para escrever testes confiáveis.
Criando datas específicas com o construtor
O DateTime formata os valores com base no sistema em que o código executa, mas você pode alterar esse comportamento por configuração. Antes disso, porém, vamos ver como atribuir uma data específica.
Para atribuir valores a uma variável DateTime no C#, basta passar os valores como parâmetros do construtor:
var date = new DateTime(2020, 07, 02, 22, 59, 59);
// 7/2/2020 10:59:59 PM
Nesse exemplo, seguimos a ordem ano, mês, dia, hora, minutos e segundos. Existem outras sobrecargas, por exemplo com milissegundos ou com um DateTimeKind, e todas estão listadas na documentação dos construtores de DateTime.
Veja como informar explicitamente que a data representa um instante em UTC:
var utcDate = new DateTime(2020, 07, 02, 22, 59, 59, DateTimeKind.Utc);
Console.WriteLine(utcDate.Kind);
// Utc
Vale lembrar que o construtor do DateTime no C# valida os valores. Por isso, uma chamada como new DateTime(2021, 2, 30) lança uma ArgumentOutOfRangeException, porque fevereiro não tem 30 dias.
Explorando as propriedades do DateTime
Lembra que o DateTime é uma struct composta por outros tipos e métodos? Podemos explorar essa estrutura para obter informações prontas, sem nenhum cálculo manual.
Por exemplo, para descobrir em qual dia da semana a data acima cai:
Console.WriteLine(date.DayOfWeek);
// Thursday
Também é possível ler valores avulsos, como o mês:
Console.WriteLine(date.Month);
// 7
Da mesma forma, existem as propriedades Year, Day, Hour, Minute, Second e Millisecond. Além delas, DayOfYear retorna o dia do ano, Date devolve apenas a data com a hora zerada e TimeOfDay devolve a hora como um TimeSpan.
Console.WriteLine(date.DayOfYear);
// 184
Console.WriteLine(date.Date);
// 7/2/2020 12:00:00 AM
Console.WriteLine(date.TimeOfDay);
// 22:59:59
Antes de criar um método novo para extrair alguma informação de uma data, verifique se ele já existe na documentação do DateTime. Na maioria das vezes, a estrutura já resolve o problema.
Formatando um DateTime no C#
Nos exemplos anteriores, você viu como exibir a data no formato padrão. Agora veremos como controlar exatamente a forma da saída, que é uma das tarefas mais comuns com o DateTime no C#.
Especificadores personalizados
Podemos montar o formato com alguns caracteres especiais. Os principais são y para ano, M para mês, d para dia, H para hora no formato 24 horas, m para minutos, s para segundos, f para frações de segundo e z para o deslocamento do fuso horário.
Repare que M maiúsculo representa o mês, enquanto m minúsculo representa os minutos. Da mesma forma, H maiúsculo usa o relógio de 24 horas e h minúsculo usa o de 12 horas.
Com esses caracteres, formatamos a data usando String.Format:
var formattedDate = String.Format("{0:dd/MM/yyyy HH:mm:ss ff z}", date);
// 02/07/2020 22:59:59 00 -3
O mesmo resultado pode vir do método ToString ou da interpolação de strings, que costuma deixar o código mais legível:
var withToString = date.ToString("dd/MM/yyyy HH:mm");
var withInterpolation = $"{date:dd/MM/yyyy HH:mm}";
// 02/07/2020 22:59
Quantidade de letras também importa. Assim, MM gera o mês com dois dígitos, MMM gera o nome abreviado e MMMM gera o nome completo do mês, sempre de acordo com a cultura.
Formatos padrão do DateTime no C#
Além dos especificadores personalizados, o .NET traz formatos padrão de uma única letra. Um deles é o r, que segue a especificação RFC 1123, muito usada em cabeçalhos HTTP:
var formattedDate = String.Format("{0:r}", date);
// Thu, 02 Jul 2020 22:59:59 GMT
Atenção, porém: o formato r escreve "GMT" no fim, mas não converte o valor. Portanto, chame ToUniversalTime antes de formatar caso a data esteja em hora local.
Outro formato útil é o s, chamado de sortable, que segue a norma ISO 8601 e produz textos que ficam em ordem cronológica quando ordenados:
var formattedDate = String.Format("{0:s}", date);
// 2020-07-02T22:59:59
Por fim, o formato o, conhecido como round-trip, inclui as frações de segundo e o deslocamento do fuso. Ele é a melhor escolha quando você precisa gravar a data como texto e depois ler o valor de volta sem perder informação.
Os formatos r, s e o usam sempre a cultura invariável. Em contrapartida, formatos como d, D, g e G mudam conforme a cultura ativa.
Manipulação de datas com DateTime no C#
Até aqui, aprendemos a definir e exibir datas de várias formas. Quando precisamos calcular datas e horas, o DateTime no C# já traz métodos prontos para isso.
O ponto mais importante é que o DateTime no C# é imutável. Ou seja, métodos como AddDays não alteram a variável original, mas retornam uma nova instância com o resultado.
Por isso, é preciso guardar o retorno do método em uma variável:
var date = DateTime.Now;
var futureDate = date.AddDays(2);
Console.WriteLine(futureDate);
// 6/19/2021 8:10:09 AM
Para remover valores, usamos o mesmo método com um número negativo:
var date = DateTime.Now;
Console.WriteLine(date.AddDays(-20));
// 5/28/2021 8:10:09 AM
Perceba que, ao retirar mais dias do que o mês atual possui, o método recuou para o mês anterior automaticamente. Usar o método existente é a forma correta de trabalhar, porque ele já trata meses com tamanhos diferentes e anos bissextos.
Outros métodos de soma
Além de AddDays, a estrutura oferece AddYears, AddMonths, AddHours, AddMinutes, AddSeconds e AddMilliseconds. Também existe o método Add, que recebe um TimeSpan e soma qualquer intervalo de uma só vez.
var start = new DateTime(2021, 1, 31);
var nextMonth = start.AddMonths(1);
Console.WriteLine(nextMonth);
// 2/28/2021 12:00:00 AM
Nesse caso, como fevereiro de 2021 não tem dia 31, o método ajustou o resultado para o último dia válido do mês. Esse comportamento evita erros comuns de quem tenta somar meses manualmente.
Diferença entre duas datas
Para descobrir o intervalo entre duas datas com o DateTime no C#, basta subtrair uma da outra. O resultado é um TimeSpan, que expõe propriedades como Days, Hours e TotalDays.
var orderDate = new DateTime(2021, 6, 1);
var deliveryDate = new DateTime(2021, 6, 17);
TimeSpan interval = deliveryDate - orderDate;
Console.WriteLine(interval.TotalDays);
// 16
Da mesma forma, os operadores de comparação funcionam normalmente. Assim, expressões como deliveryDate > orderDate retornam um valor booleano sem nenhuma conversão extra.
Só tome cuidado ao comparar valores com Kind diferentes. A comparação usa apenas os ticks e ignora o Kind, então compare sempre datas no mesmo referencial, de preferência em UTC.
Cultures e TimeZones no DateTime no C#
Como sabemos, o horário muda de acordo com a região do planeta. Dentro do C#, lidamos com essas diferenças por meio da classe TimeZoneInfo.
Já os formatos de data e hora de cada região recebem o nome de culturas. No C#, trabalhamos com eles por meio da classe CultureInfo, do namespace System.Globalization.
Aplicando a cultura pt-BR
No início, vimos que o retorno da data não estava no padrão brasileiro. Este é o momento de corrigir isso.
Primeiro, obtemos a data atual com DateTime.UtcNow. Em seguida, convertemos o resultado para string com o método ToString, passando como parâmetro o retorno de CultureInfo.CreateSpecificCulture, que recebe o nome da cultura desejada.
using System;
using System.Globalization;
Console.WriteLine(DateTime.UtcNow.ToString(CultureInfo.CreateSpecificCulture("pt-BR")));
// 17/06/2021 11:10:09
Também é possível definir a cultura para toda a aplicação de uma vez. Para isso, atribua a cultura desejada a CultureInfo.DefaultThreadCurrentCulture logo no início do programa.
var culture = new CultureInfo("pt-BR");
CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.CurrentCulture = culture;
Para ver a lista completa de culturas disponíveis no sistema, você pode consultar o próprio .NET com CultureInfo.GetCultures:
foreach (var culture in CultureInfo.GetCultures(CultureTypes.SpecificCultures))
Console.WriteLine($"{culture.Name} {culture.DisplayName}");
Convertendo fusos horários com TimeZoneInfo
A cultura muda apenas a aparência da data. Para mudar o horário em si, convertendo de UTC para o horário de Brasília, por exemplo, usamos a classe TimeZoneInfo.
// No Windows o identificador é "E. South America Standard Time"
// No Linux e no macOS o identificador é "America/Sao_Paulo"
var brasilia = TimeZoneInfo.FindSystemTimeZoneById("E. South America Standard Time");
var brasiliaTime = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, brasilia);
Console.WriteLine(brasiliaTime.ToString(new CultureInfo("pt-BR")));
O método FindSystemTimeZoneById lança TimeZoneNotFoundException quando não encontra o identificador. Por isso, envolva a chamada em um bloco try sempre que o código puder rodar em sistemas operacionais diferentes.
Na prática, um fluxo seguro com o DateTime no C# é gravar tudo em UTC e converter para o fuso do usuário apenas na hora de exibir. Dessa forma, o banco de dados guarda um valor único e sem ambiguidade.
Convertendo texto em DateTime no C#
O caminho inverso também é comum, porque formulários, arquivos CSV e APIs entregam datas como texto. Para transformar uma string em DateTime no C#, usamos Parse, TryParse ou ParseExact.
O método TryParse é o mais seguro, já que retorna false em vez de lançar uma exceção quando o texto é inválido:
var culture = new CultureInfo("pt-BR");
if (DateTime.TryParse("02/07/2020", culture, DateTimeStyles.None, out var parsedDate))
Console.WriteLine(parsedDate.Month);
// 7
Sem informar a cultura, o mesmo texto poderia virar 7 de fevereiro em uma máquina americana. Portanto, sempre passe a cultura quando o formato de entrada for conhecido.
Já o ParseExact exige que o texto siga exatamente o formato informado. Ele é útil quando você recebe dados de um sistema externo com layout fixo.
var exactDate = DateTime.ParseExact("2020-07-02 22:59", "yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture);
Boas práticas com DateTime no C#
Depois de conhecer os recursos, vale reunir alguns cuidados que evitam bugs difíceis de rastrear. Eles valem para qualquer projeto que usa o DateTime no C#.
Primeiro, grave e trafegue datas em UTC. Em seguida, converta para o fuso do usuário somente na camada de apresentação.
Segundo, lembre que o DateTime é imutável e sempre guarde o retorno de métodos como AddDays. Esse é um dos erros mais comuns de quem está começando.
Terceiro, informe a cultura em toda conversão entre texto e data. Assim, o comportamento não muda quando a aplicação sobe para um servidor configurado em outro idioma.
Por fim, quando o deslocamento em relação ao UTC precisa viajar junto com o valor, considere a estrutura DateTimeOffset. Ela representa um ponto no tempo sem ambiguidade, mesmo fora do fuso local.
Se você quer aprofundar a base da linguagem antes de avançar nesses tópicos, o curso Fundamentos do C# cobre tipos de valor, structs e muito mais.
Conclusão
O DateTime no C# parece simples à primeira vista, mas esconde detalhes importantes sobre tipos de valor, imutabilidade, culturas e fusos horários. Entender cada um deles evita datas trocadas, horários errados e testes frágeis.
Em resumo, você viu como criar valores, obter a data atual, formatar com especificadores personalizados e padrão, somar intervalos e calcular diferenças. Também aprendeu a aplicar a cultura pt-BR, converter fusos com TimeZoneInfo e transformar texto em data com segurança.
Com essas práticas, o DateTime no C# deixa de ser fonte de bugs e passa a ser uma ferramenta confiável no seu dia a dia.
FAQ
Qual é o valor padrão de um DateTime no C#?
O valor padrão é 01/01/0001 00:00:00, igual a DateTime.MinValue. Isso acontece porque o DateTime é uma struct e nunca é nulo.
Qual a diferença entre DateTime.Now e DateTime.UtcNow?
DateTime.Now retorna a hora local da máquina, enquanto DateTime.UtcNow retorna a hora no tempo universal coordenado. Para gravar dados, prefira sempre o UTC.
Por que AddDays não altera minha variável?
O DateTime é imutável, então AddDays retorna uma nova instância. Atribua o resultado a uma variável para usar a nova data.
Como exibir a data no formato brasileiro?
Use ToString com CultureInfo.CreateSpecificCulture("pt-BR") ou um formato personalizado como dd/MM/yyyy. Também é possível definir a cultura padrão de toda a aplicação.
Como converter UTC para o horário de Brasília?
Obtenha o fuso com TimeZoneInfo.FindSystemTimeZoneById e chame TimeZoneInfo.ConvertTimeFromUtc. O identificador muda entre Windows e Linux.