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

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

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.
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ério | DateTimeOffset | DateTime |
|---|---|---|
| O que registra | Instante + deslocamento em relação ao UTC | Hora do relógio + indicador Kind |
| Identifica um instante inequívoco | Sim | Só quando o Kind sobrevive |
| Propriedade Kind | Não tem; é sempre Unspecified no .DateTime | Utc, Local ou Unspecified |
| Sobrevive à serialização | O deslocamento faz parte do valor | O Kind se perde com frequência |
| Armazena o fuso horário | Não, apenas o deslocamento | Não |
| Comparação de dois valores | Correta entre fusos diferentes | Correta apenas dentro de um mesmo fuso |
| Tamanho | 16 bytes | 8 bytes |
| Padrão para código novo | Sim, recomendação da Microsoft | Para hora do relógio e código legado |
| Use para | Timestamps de eventos, logs de auditoria, qualquer sistema distribuído | Horá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.