DateTime.Now retorna a hora atual que o relógio da máquina marca. DateTime.UtcNow retorna o mesmo instante expresso em UTC. Em uma máquina duas horas à frente do UTC, os dois valores diferem em duas horas, e ambos descrevem o mesmo momento.

A recomendação, em poucas palavras: armazene UtcNow e exiba Now. Todo o resto deste artigo é o raciocínio por trás dessa frase e as armadilhas que aparecem quando um valor atravessa a fronteira de uma máquina, de um banco de dados ou de uma mudança de horário de verão.

Para baixar o código-fonte deste artigo, você pode acessar nosso repositório no GitHub.

Vamos começar.

Qual é a diferença entre DateTime.Now e DateTime.UtcNow?

DateTime.Now retorna a data e a hora atuais da máquina que executa o código, expressas na hora local dessa máquina. DateTime.UtcNow retorna o mesmo instante expresso no Tempo Universal Coordenado. Em uma máquina duas horas à frente do UTC, os dois valores têm duas horas de diferença, e ambos estão corretos.

A diferença que importa no código não é o número, e sim o indicador que vai junto com ele. Now retorna um valor cuja propriedade Kind é Local; UtcNow retorna um valor cuja Kind é Utc. Toda conversão posterior lê esse indicador para decidir se ajusta o valor ou o deixa como está.

Now também é o mais custoso dos dois. Ele lê o mesmo relógio UTC subjacente e depois aplica o deslocamento (offset) do fuso horário local, incluindo a regra de horário de verão que estava em vigor naquele instante, então faz estritamente mais trabalho que UtcNow.

A consequência prática: o UTC é o padrão seguro para tudo o que for armazenado, comparado ou enviado para além de uma fronteira.

O UTC é um formato universal para representar data e hora como alternativa à hora local. Também é conhecido como o fuso horário GMT+00.

Vejamos um exemplo para entender melhor a diferença:

var now = DateTime.Now;
var utcNow = DateTime.UtcNow;

Console.WriteLine($"Local Now: {now}");
Console.WriteLine($"UTC Now: {utcNow}");

Agora, vamos executar a aplicação:

Local Now: 8/30/2026 11:52:52 AM
UTC Now: 8/30/2026 9:52:52 AM

A primeira mensagem na tela varia de acordo com a nossa hora local. A hora local de cada sistema depende do fuso horário atribuído a ele. Nos casos mais comuns, deixamos o padrão, um valor que corresponde à área geográfica em que moramos (no nosso caso, UTC+2), mas também podemos configurá-lo com um valor personalizado, se necessário. Os dois valores aqui são impressos com o ToString() padrão; tratamos separadamente de como formatar o valor depois de obtê-lo, assim como da aritmética disponível para valores DateTime.

A segunda mensagem está no formato UTC. Ela apresenta a data e a hora atuais em UTC, ou no fuso horário GMT+00. Sempre podemos considerar o UTC como a base para calcular todos os outros fusos horários, como um formato que não depende de fuso horário:

Um instante mostrado de três formas: UTC 09:52:52 com Kind Utc, hora local 11:52:52 com Kind Local e 11:52:52 com Kind Unspecified, sem deslocamento.

Aqui, vemos que a hora local do computador está 2 horas à frente do UTC. Os dois primeiros valores são o mesmo momento escrito de duas formas. O terceiro tem os mesmos dígitos, mas sem o indicador, e esse é o caso com que a maior parte do código realmente se depara.

Qual usar: DateTime.Now ou DateTime.UtcNow?

Use DateTime.UtcNow para todo valor que a aplicação guarda: colunas de banco de dados, timestamps de log, trilhas de auditoria, expiração de cache, tempo de vida de tokens e tudo o que atravessa a fronteira de um processo. O UTC não tem deslocamento a perder nem transições de horário de verão, então dois valores registrados com uma hora de intervalo têm, de fato, uma hora de diferença.

Use DateTime.Now apenas na ponta, onde uma pessoa lê o resultado. Converta a partir do valor UTC armazenado no momento da exibição, usando o fuso horário do leitor, e não aquele que por acaso é o do servidor.

A regra resiste ao teste das implantações reais porque as máquinas mudam. Um código que armazena DateTime.Now produz um histórico no notebook de um desenvolvedor e outro diferente em um servidor configurado em UTC, a partir do mesmo código-fonte e sem nenhum erro em lugar nenhum.

The Web API Production Checklist, e-book gratuito em inglês

E-book gratuito

Sua Web API está pronta para produção?

33 itens para verificar antes de implantá-la, com a correção de cada um. Um PDF gratuito de 76 páginas para .NET 10.

O e-book está em inglês.

Baixe o checklist gratuito

PDF gratuito. Um único e-mail para enviá-lo. Cancele a inscrição quando quiser.

Quando o deslocamento realmente precisa ser preservado junto com o instante, em vez de descartado, DateTimeOffset é o tipo mais adequado para a tarefa.

Para os casos em que o próprio deslocamento é um dado, e não ruído, tratamos separadamente de DateTimeOffset e de como ele se compara com DateTime.

SituaçãoO que chamarPor quê
Gravar um valor em um banco de dadosDateTime.UtcNowNenhum deslocamento a perder e nenhuma lacuna ou repetição na transição do horário de verão
Gravar o timestamp de uma entrada de log ou de auditoriaDateTime.UtcNowAs entradas continuam ordenáveis entre servidores em fusos horários diferentes
Retornar uma hora em uma resposta de APIDateTime.UtcNowNão cabe ao servidor adivinhar o fuso horário de quem consome a API
Comparar dois instantes armazenadosValores DateTime.UtcNowDois valores Local capturados em fusos horários diferentes não são comparáveis
Exibir uma hora para uma pessoaDateTime.Now ou converter o valor UTC armazenadoO único lugar em que a hora local é a resposta certa
Medir quanto tempo algo levouNenhum dos dois: use StopwatchA hora do relógio pode dar saltos; a de um relógio de tempo decorrido, não
Manter o deslocamento junto com o instanteDateTimeOffsetRegistra o momento e o deslocamento com que foi capturado
Obter a hora em código testávelTimeProvider.GetUtcNow()Uma propriedade estática não pode ser substituída com facilidade em um teste

Dois temas relacionados têm artigos próprios: Stopwatch, para medir o tempo decorrido, e DateOnly e TimeOnly, quando só metade do valor importa.

Como saber se um DateTime é UTC ou local?

Leia a propriedade Kind. DateTime.Kind retorna um valor DateTimeKind que é Local, Utc ou Unspecified, e é a única informação dentro da estrutura que registra qual dos três um determinado valor deveria ser.

Unspecified é, ao mesmo tempo, o caso comum e o perigoso. É o que um DateTime carrega depois de ser convertido a partir de uma string sem fuso horário, lido de volta de uma coluna de banco de dados que não armazena o deslocamento ou criado com new DateTime(2022, 1, 9). O valor parece completo, mas o indicador não diz absolutamente nada.

O indicador também não faz parte do valor. Dois valores DateTime com os mesmos ticks e Kind diferentes são iguais na comparação, e alguns serializadores ou descartam o indicador na saída, ou inventam um na volta.

Portanto, Kind só responde à pergunta de forma confiável para um valor que nunca saiu do processo. Em qualquer outro caso, a resposta tem de vir de uma convenção que o código estabelece e documenta.

O caso da conversão de strings é o que mais encontramos, então vale a pena saber exatamente o que obtemos ao converter uma string em DateTime, já que é daí que vêm os valores Unspecified.

Ao desenvolver aplicações, podemos receber valores DateTime de fontes externas que não controlamos. Talvez não saibamos o formato usado antes da transferência.

Digamos que recebamos o valor 8/30/2026 9:52:52 AM. Como não sabemos muito sobre o remetente, esse valor pode ser qualquer coisa: pode vir de um servidor em UTC+02, de um em UTC-03 ou de qualquer outro fuso horário. Nesse caso, a propriedade DateTime.Kind do valor convertido é Unspecified.

A propriedade DateTime.Kind indica se um valor DateTime está representado como Local, UTC ou nenhum dos dois. Essa propriedade retorna um valor da enumeração DateTimeKind do C#, e os valores possíveis são Local, Utc ou Unspecified.

Vejamos um exemplo de como verificar o formato de uma data:

var now = DateTime.Now;
var utcNow = DateTime.UtcNow;

Console.WriteLine(now.Kind);
Console.WriteLine(utcNow.Kind);

A saída deve mostrar o Kind Local e, em seguida, o Utc.

Como converter entre hora local e UTC?

ToUniversalTime() converte um valor local em UTC, e ToLocalTime() converte um valor UTC de volta para a hora local. Os dois retornam um novo DateTime em vez de modificar o original, os dois definem a propriedade Kind do resultado e os dois aplicam a regra de horário de verão que estava em vigor no instante convertido.

Cada método verifica Kind primeiro e não faz nada quando o valor já está no Kind de destino. Chamar ToLocalTime() em um valor que já é Local simplesmente retorna o valor sem alteração, e é por isso que chamar qualquer um dos métodos duas vezes não causa problema.

É em Unspecified que a coisa se complica, porque os dois métodos discordam sobre ele de propósito. ToLocalTime() trata um valor sem indicador como se fosse UTC, enquanto ToUniversalTime() trata o mesmo valor como se fosse local. Passe o mesmo DateTime sem indicador para os dois e os resultados vão em direções opostas, sem nenhum aviso e sem nenhuma exceção.

var now = DateTime.Now;
var utcNow = DateTime.UtcNow;

if (utcNow.Kind == DateTimeKind.Utc)
{
    var oldKind = utcNow.Kind;
    var utcToLocal = utcNow.ToLocalTime();
    var newKind = utcToLocal.Kind;

    Console.WriteLine($"Converted {utcNow} from {oldKind} to {newKind}: {utcToLocal}");
}
if (now.Kind == DateTimeKind.Local)
{
    var oldKind = now.Kind;
    var localToUtc = now.ToUniversalTime();
    var newKind = localToUtc.Kind;

    Console.WriteLine($"Converted {now} from {oldKind} to {newKind}: {localToUtc}");
}

O primeiro bloco if converte o valor UTC para o formato local e exibe uma mensagem correspondente sobre como a conversão foi feita.

The Web API Production Checklist, e-book gratuito em inglês

E-book gratuito

Sua Web API está pronta para produção?

33 itens para verificar antes de implantá-la, com a correção de cada um. Um PDF gratuito de 76 páginas para .NET 10.

O e-book está em inglês.

Baixe o checklist gratuito

PDF gratuito. Um único e-mail para enviá-lo. Cancele a inscrição quando quiser.

No sentido inverso, o segundo bloco if converte o valor local em um valor universal, e a mensagem dá mais detalhes.

Essa divisão está documentada; não é folclore. A referência de DateTime.ToUniversalTime da Microsoft dá a regra para um valor sem indicador: “O objeto DateTime atual é considerado uma hora local, e a conversão é feita como se Kind fosse Local.”

As verificações com if neste exemplo são o que o mantém correto quando os valores vêm de qualquer origem que não seja Now ou UtcNow. Sem elas, um valor Unspecified seria convertido pelos dois métodos, em direções opostas, e a tabela abaixo mostra exatamente o que cada método faz para cada Kind.

Kind do valor de origemO que ToLocalTime() fazO que ToUniversalTime() faz
UtcConverte para a hora localNada: retorna o valor sem alteração
LocalNada: retorna o valor sem alteraçãoConverte para UTC
UnspecifiedPresume que o valor é UTC e depois convertePresume que o valor é local e depois converte

Os dois métodos sempre definem a propriedade Kind do resultado: ToLocalTime() retorna Local e ToUniversalTime() retorna Utc, então chamar qualquer um deles duas vezes é seguro.

Como obter a hora atual em código testável?

DateTime.UtcNow é uma propriedade estática que lê o relógio da máquina, então qualquer método que a chame diretamente não pode ser testado com facilidade em um momento escolhido. Testes que precisam de “daqui a uma hora” acabam fazendo uma pausa (sleep) ou comparando o resultado com uma margem de tolerância.

O .NET 8 adicionou TimeProvider exatamente para isso. É uma classe abstrata com uma instância System que lê o relógio real e um método GetUtcNow() que retorna um DateTimeOffset. O código recebe um TimeProvider como parâmetro do construtor em vez de chamar DateTime.UtcNow, o código de produção passa TimeProvider.System e um teste passa um fake ajustado para o instante de que o teste precisar.

TimeProvider também abstrai temporizadores e atrasos, o que é importante para tudo o que faz consultas periódicas, novas tentativas ou expira de acordo com uma programação.

Em projetos voltados para versões anteriores ao .NET 8, não precisamos escrever nossa própria abstração: o pacote Microsoft.Bcl.TimeProvider fornece o mesmo tipo a partir do .NET Framework 4.6.2 e do .NET Standard 2.0, então o padrão é idêntico em todos os lugares.

Tratamos disso em detalhes no artigo sobre como testar código dependente de tempo com TimeProvider no .NET, incluindo o provedor fake e o suporte a temporizadores.

Para a abordagem anterior ao TimeProvider e a estrutura de uma abstração de relógio escrita à mão, veja como sobrescrever DateTime.Now durante os testes no .NET.

Conclusão

Neste artigo, aprendemos a usar os formatos universal e local do DateTime, qual é a diferença entre eles e também como verificar se um valor DateTime foi criado no formato universal ou no formato local.

Testado com o .NET 10.0.10.