Skip to content

Instantly share code, notes, and snippets.

@alfonsogarciacaro
Created October 14, 2019 15:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alfonsogarciacaro/69f863f2f7d278be011a9f3f8dc8f08c to your computer and use it in GitHub Desktop.
Save alfonsogarciacaro/69f863f2f7d278be011a9f3f8dc8f08c to your computer and use it in GitHub Desktop.
.NET DateTime pitfalls or how an API design can shape developers' minds

.NET DateTime pitfalls or how an API design can shape developers' minds

What's in a DateTime? Or more specifically what's in .NET System.DateTime? Here is a class that we use every day yet few people really understand. I too had a wrong understanding of it until very recently and it's only now that I feel confident enough (or maybe it's just an illusion?) to write this post to organize my thoughts and try to help others, including my future self.

DISCLAIMER: This article contains some simplifications (e.g. UTC is not formally timezone 0) for the sake of a better explanation.

DateTimes are known for being particularly tricky in computing, this is because they mix two similar yet different concepts. Let's start by defining the simplest of them. We'll use another word to make the distinction more clear: timestamp. What's a timestamp in computing terms? It's just a value in a unidimensional coordinate system that defines how far is a point from the coordinate origin. The origin (e.g. January 1, 1970 00:00:00 UTC in UNIX) and the unit (e.g. milliseconds or ticks) may vary depending on the implementation.

Unfortunately we poor humans have a hard time to locate a point in time using a big numeric value (picture that: "You should be here by 1571060783064!"). Because of that most timestamp implementations have a way to represent it in relation with the tool that most of us use to regulate our routines: the Gregorian calendar. And here's where the problem starts because this is a solar calendar and as we know the Sun rises for everybody but not at the same time, so each day starts at a different time in multiple parts of the world known as timezones. So when we represent a timestamp as a human-readable datetime, we must always attach the timezone used for the conversion if we don't want to lose information along the way.

So far you're probably thinking I'm only talking about obvious things and wasting your time, but it still important to have these concepts clear in order to prevent many of the common pitfalls: a timestamp is an absolute numeric value stored in your computer, database, etc, while a datetime is a view of this data composed of three parts: the date, the time of the day, and a timezone.

What are those pitfalls? Well, the class used in .NET to work with timestamps is System.DateTime. This class has an extra value, the DateTimeKind, to indicate if the value should be represented as a UTC or a Local time. Because in .NET DateTime is an immutable value, if you want to represent a local time as standard (UTC) or vice versa, you must create a copy of the value with a different kind. This is an unfortunate API design, because it leads developers to think that UTC and Local DateTimes are two different things, when in fact they are just alternative views to represent the same timestamp.

Yes, I know there's also DateTimeKind.Unspecified but this is just a bad idea that makes everything more complicated so let me ignore it and please avoid any situation in your program that make you deal with it.

Some developers (myself in the past included) think of a datetime with DateTimeKind.Local as having no timezone. This is not right, a local datetime has the timezone set in the computer where the program is running. This confusion is likely exacerbated by the fact you need to use another, seldom-used, API to get the current timezone: System.TimeZone.CurrentTimeZone, and that you cannot use DateTime to represent a timestamp in timezones other than the current machine and UTC ones.

If you see local and UTC times as different things, you'll spend time trying to pick the right choice in multiple situations, like storing a timestamp in a database. It doesn't really matter. Most database engines should handle dates with different timezones just fine. If for your app is important to be able to compare timestamps in a quick look, uniform all of them to UTC. If you prefer to retain the information of where the data is coming from, keep the timezone (although if you need to know the exact location you will need another specific field for that).

There's an argument however in favor of keeping timestamps with timezone in the database. It may happen that some datetimes get to the server in non-ISO format, and the server may attach its local timezone to them. In the era of cloud computing where most of the times we don't know where exactly our server is, this is very dangerous. If you examine the data and notice the timezone of some timestamps doesn't correspond with the origin of the data, it's easier to spot this problem.

Other times, the requirements of our program include dates (with or without time) and our developer brains tend to classify them immediately as timestamps, even if this is not always the case. Some examples of dates that are not timestamps are:

  • New Year: this happens on Jan 1st at exactly 0:00, but it's not a timestamp because it occurs every year at different moments in each parts of the world. Only if we talk about a specific year and a specific place (e.g. 2020 New Year in Mumbai) can we convert it to a timestamp.
  • Your birthday: if you happen to be born in a country and then move with your family to another timezone, your relatives won't wait until the date actually comes in your country of origin to say "happy birthday" to you.

It's still possible to use DateTime to handle these, but keep in mind for that you will have to include extra information (time of the day and a timezone) that may or may not make sense in your application.

The takeaways

  • Don't worry too much about using UTC or Local datetimes internally or when exchanging data between server and client, they're just alternative ways of representing an absolute timestamp and JSON serializers should be able to handle both (as long as you use ISO format)
  • Just, when presenting and receiving data from the user, most of the times you'll want to use its local representation
  • A caveat: when comparing datetimes in .NET, check they both have the same DateTimeKind (which shouldn't be necessary but...)
  • If you need to represent a timestamp with multiple timezones (in a world clock app for example), use DateTimeOffset
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment