C# offers many ways to handle date and time values. Due to the increase in the number of globalized applications all over the internet, it is paramount that dates and times of events are accurate and handled effectively. In this article, we will look at the differences and similarities between DatetimeOffset and DateTime. We will not cover the details of the DateTime struct. But if you’d like to learn more, check out our DateTime articles.

To download the source code for this article, you can visit our GitHub repository.

Let’s get the ball rolling. 

What Is DateTimeOffset?

DateTimeOffset is a struct in C# that represents a specific point in time, which consists of date, time, and offset that shows how much it varies from Coordinated Universal Time (UTC). 

DateTimeOffset consists of two main components. Firstly, the DateTime component, which is a DateTime struct that contains the properties year, month, date, hours, minutes, seconds, and milliseconds to represent the date. The DateTime struct in DateTimeOffset has its kind property explicitly set to Unspecified

Secondly, the Offset component shows how far a DateTime is from UTC. The Offset value can either be positive or negative depending on whether the date is ahead or behind UTC.

Don't like the ads? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!

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: 7/25/2023 1:41:11 AM +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.

It is important to note that 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) Damascus
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 fourteen timezones that have +2:00 Offset

DateTimeOffset and DateTime Differences

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.

Although DatetimeOffset does not have Kind property directly it is important to note that it has 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.

DateTimeOffset and DateTime Best Practices

DateTimeOffset generally has more use cases than DateTime. As a result according to Microsoft, we should regard DateTimeOffset as the default date and time type for our application development. Whether to use DateTimeOffset or DateTime largely depends on the requirements.

Generally, we should consider using DateTime when working with legacy systems that have existing implementations with DateTime. This is purely based on the logic that migrating the entire codebase from DateTime to DateTimeOffset might require significant effort. Another scenario where DateTime is pretty useful is when we are working with dates and times that don’t use time zone information or are within a single time zone. 

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.
Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!