DateTimeOffset é o tipo certo para armazenar. Ele guarda o momento e o deslocamento (offset) em relação ao UTC, então um valor lido em outro fuso horário continua identificando o mesmo instante. A orientação do Microsoft Learn sobre como escolher entre os dois recomenda “considerar DateTimeOffset como o tipo de data e hora padrão para o desenvolvimento de aplicações”.

DateTime registra apenas a hora que o relógio marca, mais um indicador Kind que se perde facilmente na serialização e nas idas e voltas ao banco de dados. Ele continua sendo a escolha certa para uma hora do relógio que deve significar a mesma coisa em qualquer lugar, como um lembrete de almoço às 12:00. Depois que soubermos qual tipo armazenar, o artigo sobre como formatar datas e horas em C# explica como exibi-lo.

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

O que é DateTimeOffset?

DateTimeOffset é uma estrutura que representa um único instante no tempo como data e hora mais o deslocamento em relação ao UTC.

É esse deslocamento que o torna inequívoco. 2026-08-08 14:30 +02:00 e 2026-08-08 12:30 +00:00 são o mesmo instante escrito de duas formas, e compará-los resulta em igualdade, porque o tipo compara o instante em UTC.

Ele expõe a maior parte dos membros do DateTime (Year, Month, Day, Hour, AddDays(), Parse()), além de Offset, UtcDateTime e LocalDateTime.

Uma coisa que ele não armazena é o fuso horário. O mesmo deslocamento de +02:00 é compartilhado por Atenas, Cairo e Joanesburgo em janeiro, e esse deslocamento muda em qualquer fuso que adote o horário de verão. Quando o próprio fuso importa, armazene o identificador do fuso e resolva-o com TimeZoneInfo.

DateTimeOffset.UtcNow e DateTimeOffset.Now são as duas formas que o tipo oferece para obter o instante atual.

A orientação do Microsoft Learn sobre como escolher entre os dois descreve esse mesmo limite nos termos exatos de que precisaríamos: “Um valor DateTimeOffset não está vinculado a um fuso horário específico, mas pode ter origem em diversos fusos horários”.

Para começar, vamos criar um valor DateTimeOffset:

var dateTimeOffset = DateTimeOffset.Now;
Console.WriteLine($"DateTimeOffset: {dateTimeOffset}");

Aqui, declaramos a variável dateTimeOffset, atribuímos a ela o valor DateTimeOffset atual por meio da propriedade estática DateTimeOffset.Now e escrevemos o valor no console:

DateTimeOffset: 8/14/2026 1:11:23 PM +02:00

Na saída, temos o componente DateTime e, além dele, um Offset de +2:00. O Offset indica que o DateTime atual está 2 horas à frente do UTC.

O valor Offset não representa o fuso horário. Com o valor Offset, podemos determinar os subconjuntos de fusos horários em que o valor DateTime se encaixa. Se o fuso horário exato for essencial, é importante armazenar o fuso horário real, porque um mesmo Offset é compartilhado por vários fusos horários.

Vamos entender isso melhor:

static List<TimeZoneInfo> GetTimeZoneFromOffset(TimeSpan offset) =>
    TimeZoneInfo.GetSystemTimeZones()
    .Where(tz => tz.BaseUtcOffset == offset)
    .ToList();

var timeZones = GetTimeZoneFromOffset(dateTimeOffset.Offset);
foreach (TimeZoneInfo timeZone in timeZones)
{
    Console.WriteLine($"Time Zone: {timeZone}");
} 

Aqui, declaramos e implementamos o método estático GetTimeZoneFromOffset(), que recebe um parâmetro do tipo TimeSpan. A partir desse TimeSpan, verificamos quais fusos horários têm esse valor como deslocamento padrão (BaseUtcOffset) e imprimimos os resultados no console.

Vejamos a saída:

Time Zone: (UTC+02:00) Athens, Bucharest
Time Zone: (UTC+02:00) Beirut
Time Zone: (UTC+02:00) Cairo
Time Zone: (UTC+02:00) Chisinau
Time Zone: (UTC+02:00) Gaza, Hebron
Time Zone: (UTC+02:00) Harare, Pretoria
Time Zone: (UTC+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius
Time Zone: (UTC+02:00) Jerusalem
Time Zone: (UTC+02:00) Juba
Time Zone: (UTC+02:00) Kaliningrad
Time Zone: (UTC+02:00) Khartoum
Time Zone: (UTC+02:00) Tripoli
Time Zone: (UTC+02:00) Windhoek

Quando a aplicação estava em execução, o fuso horário era (UTC +2:00) Harare, Pretoria. A saída mostra treze fusos horários cujo deslocamento padrão é +2:00. BaseUtcOffset ignora o horário de verão, então, nesta data de agosto, sete deles, incluindo Atenas e Cairo, estavam em +03:00, enquanto fusos que naquele momento estavam em +02:00, como Madri e Berlim, ficam de fora.

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.

DateTimeOffset vs DateTime: qual é a diferença?

A principal diferença entre as estruturas DateTimeOffset e DateTime está em levar ou não em conta o fuso horário. O DateTimeOffset é considerado um tipo que leva em conta o fuso horário porque, além do componente DateTime, também tem um componente Offset que indica quanto o DateTime difere do UTC.

Esse componente a mais muda três coisas que você vai encontrar em produção. A igualdade e a ordenação comparam o instante UTC subjacente, então dois valores escritos com deslocamentos diferentes ainda podem ser iguais.

A serialização é mais simples, porque o deslocamento viaja dentro do próprio valor. Um DateTime carrega apenas o indicador Kind, que registra só se o valor é UTC, local ou não especificado, e uma coluna de data simples ou uma string de data sem deslocamento não tem onde guardar nem isso.

E a aritmética continua previsível: somar horas a um DateTimeOffset move o instante e não mexe no deslocamento, então uma transição de horário de verão nunca desloca o resultado silenciosamente.

Vamos imprimir os dois tipos no mesmo instante e ver o que cada um guarda:

var dateTime = DateTime.Now;
Console.WriteLine($"DateTime: {dateTime}");

dateTimeOffset = DateTimeOffset.Now;
Console.WriteLine($"DateTimeOffset: {dateTimeOffset}"); 

Aqui, imprimimos os valores DateTime e DateTimeOffset no mesmo instante. Os componentes de data e hora dos dois valores são idênticos. Além dos componentes de data e hora, o valor DateTimeOffset inclui o deslocamento +02:00. Isso significa que o valor do DateTimeOffset está 2 horas à frente do UTC.

Como o DateTime é considerado um tipo que não leva em conta o fuso horário, é essencial ter muito cuidado nas conversões entre fusos horários, porque elas não são tratadas automaticamente. Além disso, o DateTime tem uma propriedade Kind do tipo DateTimeKind, enquanto o DateTimeOffset não tem uma propriedade Kind:

var dateTimeUtc = DateTime.UtcNow;
Console.WriteLine($"DateTime Kind: {dateTimeUtc.Kind}");

var dateTimeLocal = DateTime.Now;
Console.WriteLine($"DateTime Kind: {dateTimeLocal.Kind}");

var dateTimeUnspecified = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Unspecified);
Console.WriteLine($"DateTime Kind: {dateTimeUnspecified.Kind}"); 

Aqui, vemos os valores possíveis que a propriedade Kind dos valores DateTime pode assumir. A propriedade Kind é uma enumeração que pode ser Utc, Local ou Unspecified. Para entender a diferença entre ler um valor em UTC e na hora local, veja DateTime.Now vs DateTime.UtcNow.

O DateTimeOffset não tem uma propriedade Kind, mas tem uma propriedade DateTime. Por meio dessa propriedade DateTime da estrutura DateTimeOffset, podemos acessar a propriedade Kind.

A propriedade DateTime de um DateTimeOffset sempre tem Kind igual a Unspecified.

Semelhanças entre DateTimeOffset e DateTime

Embora as estruturas DateTimeOffset e DateTime tenham diferenças, elas compartilham semelhanças essenciais.

Independentemente das informações de fuso horário, a função principal de DateTimeOffset e DateTime é representar valores de data e hora. Por isso, DateTimeOffset e DateTime compartilham membros comuns, como Year, Month, Day, Hour, Minute, Second e Millisecond, além de alguns métodos comuns, como Parse(), TryParse(), ParseExact(), TryParseExact() e ToString(), entre outros. A comparação de duas instâncias é um dos membros que eles compartilham, e o artigo sobre como comparar valores DateTime explica as regras relacionadas a fuso horário que se aplicam aos dois.

Como converter DateTimeOffset em DateTime?

O DateTimeOffset expõe três propriedades para isso, e escolher a errada é o caminho mais comum para os bugs de fuso horário.

UtcDateTime retorna o instante convertido para UTC, com Kind igual a Utc. É a propriedade certa para gravar em um banco de dados ou enviar a uma API, porque o valor continua correto onde quer que seja lido, e o artigo sobre como converter um DateTime em uma string ISO 8601 mostra o formato serializado.

LocalDateTime converte para o fuso local da máquina e define Kind como Local. Certa para exibição em aplicações desktop, errada para armazenamento: o “local” em questão é o do servidor, não o do usuário.

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.

DateTime retorna a hora do relógio exatamente como está guardada, descarta o deslocamento e deixa Kind como Unspecified. Pelo nome, parece a escolha óbvia, mas quase sempre é a errada: o instante não pode mais ser recuperado.

A conversão no sentido inverso também exige cuidado, porque o deslocamento é opcional. Ao atribuirmos um DateTime, a conversão usa o Kind dele, então um valor com Kind igual a Unspecified recebe silenciosamente o deslocamento do servidor.

Se escrevermos as três propriedades lado a lado, vemos que a diferença entre elas é, em parte, qual Kind sobrevive:

var moment = DateTimeOffset.Now;

var forStorage = moment.UtcDateTime;      // Kind = Utc, safe to persist
var forDisplay = moment.LocalDateTime;    // Kind = Local, server's zone
var raw        = moment.DateTime;         // Kind = Unspecified, offset lost

UtcDateTime é a única que pode ser persistida com segurança; LocalDateTime e o DateTime bruto perdem informação no momento em que são lidos, só que de formas diferentes.

Qual usar: DateTimeOffset ou DateTime?

Use DateTimeOffset por padrão e recorra a DateTime quando houver um motivo concreto.

DateTimeOffset é o tipo certo sempre que o valor registra algo que aconteceu: entradas de auditoria, timestamps de pedidos, envelopes de mensagem, qualquer coisa registrada em log por uma máquina e lida por outra. A comparação e a ordenação continuam corretas entre fusos sem que ninguém precise se lembrar de normalizar os valores.

DateTime é o tipo certo para uma hora do relógio que deve ser a mesma em qualquer lugar. Um lembrete de almoço às 12:00, uma loja que abre às 09:00, um horário semanal recorrente: associar um deslocamento a esses valores os torna errados para quem está em outro fuso.

Para uma data sem hora nenhuma, nenhum dos dois é a melhor resposta: DateOnly existe para isso e elimina toda uma categoria de bugs de meia-noite.

Duas observações práticas. TimeProvider é a forma moderna de obter o instante atual em código testável (veja como testar código dependente de tempo com TimeProvider), substituindo chamadas diretas a DateTimeOffset.Now. E, seja qual for o tipo armazenado, armazene sempre o mesmo. Misturar os dois tipos em colunas diferentes é pior do que qualquer uma das duas escolhas.

CritérioDateTimeOffsetDateTime
O que registraInstante + deslocamento em relação ao UTCHora do relógio + indicador Kind
Identifica um instante inequívocoSimSó quando o Kind sobrevive
Propriedade KindNão tem; é sempre Unspecified no .DateTimeUtc, Local ou Unspecified
Sobrevive à serializaçãoO deslocamento faz parte do valorO Kind se perde com frequência
Armazena o fuso horárioNão, apenas o deslocamentoNão
Comparação de dois valoresCorreta entre fusos diferentesCorreta apenas dentro de um mesmo fuso
Tamanho16 bytes8 bytes
Padrão para código novoSim, recomendação da MicrosoftPara hora do relógio e código legado
Use paraTimestamps de eventos, logs de auditoria, qualquer sistema distribuídoHorários de funcionamento, horários locais recorrentes

Digamos que tenhamos um alarme para nos lembrar de que é hora do almoço, às 12:00. Queremos que o alarme toque às 12:00 independentemente do fuso horário em que estivermos. Nesse caso, o uso do DateTime se justifica, porque a informação de fuso horário não importa.

Normalmente, usamos DateTimeOffset em situações em que precisamos de informações precisas sobre o instante em que um determinado evento ocorreu. Além disso, deveríamos considerar o uso de DateTimeOffset ao trabalhar com sistemas distribuídos acessados a partir de fusos horários diferentes.

Uma situação real em que DateTimeOffset seria a escolha preferida é o registro da data e hora de eventos ou ações em um sistema distribuído. Em um sistema distribuído, os usuários estão espalhados pelo mundo e, possivelmente, em fusos horários diferentes. Por isso, ao registrar a data e hora em que eventos ou ações ocorrem, é fundamental ser preciso para gerar relatórios corretos com essas informações.

Conclusão

Neste artigo, vimos as diferenças e as semelhanças entre as estruturas DateTimeOffset e DateTime em C#. Por fim, examinamos alguns casos de uso comuns de DateTimeOffset e DateTime, com base nas diferenças, nos recursos e nos requisitos.

Testado com .NET 10.0.10.