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.