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.
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:

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.

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 gratuitoPDF 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ção | O que chamar | Por quê |
|---|---|---|
| Gravar um valor em um banco de dados | DateTime.UtcNow | Nenhum 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 auditoria | DateTime.UtcNow | As entradas continuam ordenáveis entre servidores em fusos horários diferentes |
| Retornar uma hora em uma resposta de API | DateTime.UtcNow | Não cabe ao servidor adivinhar o fuso horário de quem consome a API |
| Comparar dois instantes armazenados | Valores DateTime.UtcNow | Dois valores Local capturados em fusos horários diferentes não são comparáveis |
| Exibir uma hora para uma pessoa | DateTime.Now ou converter o valor UTC armazenado | O único lugar em que a hora local é a resposta certa |
| Medir quanto tempo algo levou | Nenhum dos dois: use Stopwatch | A hora do relógio pode dar saltos; a de um relógio de tempo decorrido, não |
| Manter o deslocamento junto com o instante | DateTimeOffset | Registra o momento e o deslocamento com que foi capturado |
| Obter a hora em código testável | TimeProvider.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.

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 gratuitoPDF 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 origem | O que ToLocalTime() faz | O que ToUniversalTime() faz |
|---|---|---|
Utc | Converte para a hora local | Nada: retorna o valor sem alteração |
Local | Nada: retorna o valor sem alteração | Converte para UTC |
Unspecified | Presume que o valor é UTC e depois converte | Presume 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.