So you think you know how to use the DateTime struct in C#? You might be surprised.

C# provides a struct which is a value type called DateTime that implements various interfaces including IComparable, IConvertible, IFormattable, and ISerializable. Internally, it’s just a 64 bit integer. That’s it. All the functionality that I’ve explained in the last two episodes relies on just 64 bits. And really, the DateTime uses just 62 of these bits for the actual date and time. The last 2 bits are used to identify what kind of DateTime this is.

The DateTime struct has no concept of a timezone. Sure, it can track if it’s local or not. But local to where? Microsoft tried to fix this in .Net 2.0 with the DateTimeOffset struct which allows you to specify a local date and time that’s tied to a specific offset from UTC. That seems great. But even the DateTimeOffset struct has problems because while it’s good at specifying a particular instant that’s clear exactly when it occurred, it has no way of knowing what the time will be even a minute later. That’s because you need more than just an offset to know how time should be handled. You really need to know which time zone the DateTimeOffset represents. Only then can you determine if daylight savings time is about to begin or not.

For this, you do have a new TimeZoneInfo class but it remains disconnected from the DateTime and DateTimeOffset structs. It’s very easy to get things mixed up. Listen to the full episode for more or you can read the full transcript below.

Transcript

This episode continues the explanation of DateTime data types. We’re getting to actual implementations now and languages tend to have very different notions of how to deal with dates and times. This episode will describe the C# version of DateTime.

C# provides a struct which is a value type called DateTime that implements various interfaces including IComparable, IConvertible, IFormattable, and ISerializable. Internally, it’s just a 64 bit integer. That’s it. All the functionality that I’ve explained in the last two episodes relies on just 64 bits.

And really, the DateTime needs just 62 of these bits for the actual date and time. The last 2 bits are used to identify what kind of DateTime this is. There are four options although the documentation usually only mentions 3 of these. I’ll come back to this in a moment.

The majority of bits form a potentially really big number. What could possibly need such a large number? You see, the DateTime doesn’t store its value in text. It doesn’t store for example 2020-07-01T12:30Z. What it does is keep track of how many 100 nanosecond intervals have passed since midnight zero hours of January 1st of the year 1 in the Gregorian calendar. That’s why it needs such a large number. There’ve been a lot of 100 nanoseconds since then.

Here’s something I always wondered. Why the year 1? Well, it turns out that there is no year 0 in the Gregorian calendar. That still seems strange to me. I mean, when a person is born they have to go through a whole year, learning how to sit, crawl, and sometimes walk before the first birthday. But that’s the way the calendar works. It brings up an interesting question. In what year should new centuries begin? If the first year was 1, then the second century should start at the year 101 instead of 100. Our current century should have started in the year 2001 based on this.

What do you do if you want to work with dates before year 1? You’re on your own there. You’ll have to make your own class or struct to store the information. You probably don’t need to worry about 100 nanosecond intervals anymore. So that should make it a bit easier.

I’ll mention also that if you ever want to store a DateTime in a SQLServer database, just be aware that SQLServer has a much more limited idea of a DateTime and can only go back to the year 1753.

There’s a lot more to the C# DateTime including a recommendation that you should probably not be using it at all. I’ll continue right after this message from our sponsor.

Let’s start with how you obtain a DateTime instance. Your two primary methods will likely be DateTime.Now to get a DateTime representing the current local date and time and DateTime.UTCNow to get a DateTime representing the current date and time as UTC.

You also have several constructors that allow you to pass in specific year, month, day, hour, minute, and second parameters. Let’s say you called the constructor that only needs a year, month, and day. What time should you get with that? Remember, this is a DateTime struct and not just a Date struct. If you only provide a date, then the time portion gets set to midnight zero hours and the DateTime type is left unspecified.

What does unspecified mean? This gets back to the extra two bits that are used to keep track of whether or not the time is local or UTC. Sometimes, like here when the time was not included at all, then the system doesn’t know what to do with it. So it just makes the type unspecified.

There are other constructors that do allow you to provide hours, minutes, and seconds. But unless you call the constructors that allow you to also specify if the time is local or UTC, then you’ll get an unspecified DateTime here too.

Whether the DateTime is local or UTC really only applies when converting the DateTime between local and UTC values. If you have a local time and try to convert it to a local time, then you get the same value. And if you have a UTC time and try to convert it to UTC, then you also get the same value. But here’s where unspecified comes into the picture. If you try to convert an unspecified DateTime into either local or UTC, then it’ll always convert. It just assumes that since you’re calling convert, then the value must need converting, right? That can definitely bite you.

Then there’s that fourth type that doesn’t appear in the documentation. I found a reference that said this was used to help keep track of local times during the transition periods between daylight savings time and back. I’m not sure what to make of that and here’s why.

The builtin DateTime data type that comes with C# is usually perceived as either a big help or completely misleading when you need to work with dates and times. It doesn’t handle every situation and can make silent decisions for you that may not be what you want. It has some serious flaws. And I don’t think that extra undocumented type will be enough to solve the more fundamental problems. The fact that it needs an unspecified type at all is a big sign of problems to come.

My advice is to use the DateTime type with caution and for basic scenarios only. It’s also good for when you’re only interested in a general idea instead of specific results. It lacks some features needed to be fully useful.

The DateTime struct has no concept of a timezone. Sure, it can track if it’s local or not. But due to compatibility issues, Microsoft had to keep much of the old behavior from before the ability to track local vs. UTC was added. And local to where? Microsoft tried to fix this in .Net 2.0 with the DateTimeOffset struct which allows you to specify a local date and time that’s tied to a specific offset from UTC. That seems great. But how many developers know about DateTimeOffset and how to use it? Most people just use the original DateTime struct and remain unaware of the problems. Microsoft actually recommends that developers use the DateTimeOffset struct for most programming tasks instead of the DateTime struct.

Even the DateTimeOffset struct has problems because while it’s good at specifying a particular instant that’s clear exactly when it occurred, it has no way of knowing what the time will be even a minute later. That’s because you need more than just an offset to know how time should be handled. You really need to know which time zone the DateTimeOffset represents. Only then can you determine if daylight savings time is about to begin or not.

For this, you do have a new TimeZoneInfo class but it remains disconnected from the DateTime and DateTimeOffset structs. It’s very easy to get things mixed up and many developers probably just give up long before this and go back to using the simple DateTime struct.