Updated on
DateTimeOffset is the one to store. It carries the moment and its offset from UTC, so a value read back in another time zone still identifies the same instant. Microsoft’s own guidance says to “consider DateTimeOffset as the default date and time type for application development” (Compare types related to date and time, Microsoft Learn).
DateTime only records the clock reading, plus a Kind flag that is easily lost across serialisation and database round trips. It stays the right choice for a wall-clock time that should mean the same thing everywhere, like a 12:00 lunch reminder. Once we know which type to store, formatting dates and times in C# covers how to display it.
What Is DateTimeOffset?
DateTimeOffset is a struct that represents a single moment in time as a date and time together with its offset from UTC.
That offset is what makes it unambiguous. 2026-08-08 14:30 +02:00 and 2026-08-08 12:30 +00:00 are the same instant written two ways, and comparing them gives equality, because the type compares the underlying UTC moment rather than the digits on the clock.
It exposes the same members as DateTime (Year, Month, Day, Hour, AddDays(), Parse()), plus Offset, UtcDateTime, and LocalDateTime.
One thing it deliberately does not store is the time zone. An offset of +02:00 is shared by Athens, Cairo, and Johannesburg, and it changes for any zone observing daylight saving. Where the zone itself matters, for a recurring meeting that must stay at 09:00 local through a DST change, store the zone identifier alongside, and use TimeZoneInfo to resolve it.
DateTimeOffset.UtcNow and DateTimeOffset.Now are the two ways to get the current moment.
Microsoft states the same limit in the same words we would need: “A DateTimeOffset value isn’t tied to a particular time zone, but can originate from a variety of time zones” (Compare types related to date and time, Microsoft Learn). The demo below shows exactly that.
To start, let’s define a DateTimeOffset struct:
var dateTimeOffset = DateTimeOffset.Now;
Console.WriteLine($"DateTimeOffset: {dateTimeOffset}");
Here we define a dateTimeOffset variable and assign it to the current DateTimeOffset value using the DateTimeOffset.Now static property, and write the value to the console:
DateTimeOffset: 8/14/2026 1:11:23 PM +02:00
Here we have our DateTime component, and in addition, we have an Offset of +2:00. The Offset indicates that the current DateTime is 2 hours ahead of UTC.
The Offset value does not represent the time zone. The Offset value can be used to determine subsets of time zones where the DateTime value lies. If the exact time zone is essential it is important to store the actual time zone because Offset overlap time zones.
Let’s get a better understanding of this:
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}");
}
Here we declare and implement the GetTimeZoneFromOffset() static method that takes a parameter of type TimeSpan. Given that we check which time zones the TimeSpan belongs to and print the results to the console.
Let’s take a look at the output:
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
When the application was running, the time zone was (UTC +2:00) Harare, Pretoria. Based on the output we have thirteen timezones that have +2:00 Offset.
DateTimeOffset vs DateTime: What Is the Difference?
The major difference between the DateTimeOffset and DateTime structs is in time zone awareness. DateTimeOffset is considered time zone aware because in addition to the DateTime component, it also has an Offset component that indicates how the DateTime differs from UTC:
var dateTime = DateTime.Now;
Console.WriteLine($"DateTime: {dateTime}");
var dateTimeOffset = DateTimeOffset.Now;
Console.WriteLine($"DateTimeOffset: {dateTimeOffset}");
Here, we print the DateTime and DateTimeOffset value at the same time. The date and time components of both values are identical. In addition to the date and time components the DateTimeOffset value represents the offset +02:00. This means that the value of the DateTimeOffset is 2 hours ahead of the UTC.
Since the DateTime is considered time zone unaware it is essential to be very cautious when handling time zone conversions because it is not handled automatically. Additionally, the Datetime has a Kind property of type DateTimeKind whereas on the other hand the DateTimeOffset does not have a Kind property:
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}");
In this case, we look at the possible values the Kind property of our DateTime values can take. The Kind property is an enum which can be Utc, Local and Unspecified. For the difference between reading a value in UTC versus local time, see DateTime.Now vs DateTime.UtcNow.
Although DatetimeOffset does not have Kind property directly, but it has a DateTime property. Through this DateTime property in the DatetimeOffset struct we can access the Kind property.
The DateTimeOffset always has its Kind property set to Unspecified.
DateTimeOffset and DateTime Similarities
Whilst the DateTimeOffset and DateTime structs have differences they share core similarities.
Regardless of the time zone information DateTimeOffset and DateTime both allow us to represent date and time values as their primary function. As a result, DateTimeOffset and DateTime share common members that include Year, Month, Day, Hour, Minute, Second, Millisecond, along with some common methods, such as Parse(), TryParse(), ParseExact(), TryParseExact(), and ToString() but not exhaustive. Comparing two instances is one of the members they share β see comparing DateTime values for the zone-aware rules that apply to both.
How Do We Convert DateTimeOffset to DateTime?
DateTimeOffset exposes three properties for this, and picking the wrong one is how time zone bugs get introduced.
UtcDateTime returns the moment converted to UTC, with Kind set to Utc. This is the one to use when writing to a database or an API, because the value stays correct wherever it is read. It’s also what most APIs expect if we go on to serialise it β see converting a DateTime to an ISO 8601 string for the exact format.
LocalDateTime converts to the machine’s local zone and sets Kind to Local. Correct for display, wrong for storage: the “local” it means is the server’s, not the user’s.
DateTime returns the clock reading exactly as held, discarding the offset and leaving Kind as Unspecified. It looks like the obvious choice because of its name and is almost always the wrong one: the instant is now unrecoverable.
Going the other way is safer, because the offset must be supplied. Assigning a DateTime implicitly uses its Kind, so a value with Kind of Unspecified silently picks up the server’s offset.
Written side by side, the difference between the three properties is exactly which Kind survives:
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 is the one safe to persist; LocalDateTime and the raw DateTime both discard information the moment they run, just in different ways.
Should We Use DateTimeOffset or DateTime?
Default to DateTimeOffset and reach for DateTime when we can name the reason.
DateTimeOffset is right whenever the value records something that happened: audit entries, order timestamps, message envelopes, anything logged by one machine and read by another. Comparison and ordering stay correct across zones without anyone remembering to normalise.
DateTime is right for a wall-clock time that should read the same everywhere. A 12:00 lunch reminder, a shop opening at 09:00, a recurring weekly slot: attaching an offset to those makes them wrong for anyone in another zone.
For a date with no time at all, neither is the best answer: DateOnly and TimeOnly exist for that and remove a whole class of midnight bugs.
Two practical notes. TimeProvider is the modern way to get the current moment in testable code (see testing time-dependent code with TimeProvider), replacing direct DateTimeOffset.Now calls. And whichever type we store, store one consistently. Mixed columns are worse than either choice.
| Criterion | DateTimeOffset | DateTime |
|---|---|---|
| What it records | Instant + offset from UTC | Clock reading + Kind flag |
| Identifies a unique moment | Yes | Only when Kind survives |
| Kind property | None, always Unspecified on its .DateTime | Utc, Local, or Unspecified |
| Survives serialisation | Offset is part of the value | Kind is frequently lost |
| Stores the time zone | No, offset only | No |
| Comparing two values | Correct across zones | Correct only within one zone |
| Size | 16 bytes | 8 bytes |
| Default for new code | Yes, Microsoft's recommendation | For wall-clock and legacy code |
| Use it for | Event timestamps, audit logs, anything distributed | Birthdays, opening hours, recurring local times |
Let’s say we have an alarm to remind us that it is lunchtime at 12:00. We would want the alarm to go off at 12:00 regardless of your timezone. In this case, the use of the DateTime is justified because time zone information is not important.
We commonly use DateTimeOffset in scenarios where accurate information about the instance a particular event occurred is required. Additionally, we should also consider using DateTimeOffset when working with distributed systems that are accessed from different time zones.
A real-world situation where DateTimeOffset would be the preferred choice is when capturing the date and time for events or actions within a distributed system. With a distributed system users are all over the world and possibly in different time zones. As a result, when we capture the date and time of occurrence for events or actions it is crucial to be precise in order to accurately report on the information.
Conclusion
In this article, we have looked at the differences and similarities between DateTimeOffset and DateTime structs in C#. Lastly, we examined some common use cases for DateTimeOffset and DateTime while guiding ourselves with the differences, capabilities, and requirements.
Tested with .NET 10.0.10.
