Rick Strahl's Web Log

Wind, waves, code and everything in between...
ASP.NET • C# • HTML5 • JavaScript • AngularJs
Contact   •   Articles   •   Products   •   Support   •   Search
Ad-free experience sponsored by:
ASPOSE - the market leader of .NET and Java APIs for file formats – natively work with DOCX, XLSX, PPT, PDF, images and more

Back to Basics: UTC and TimeZones in .NET Web Apps

Most applications we build tend to have date and time data associated with it. If the application is not used just in a single location you are likely have to deal with times zones in your application. While .NET has a good set of date manipulation function, the various time zone conversion routines are a bit of a pain to use. I find myself having to look up what functions need to be called and review my thinking about how to best manage dates frequently. I see others struggling with this often as well, so I decided to take some time to write it down here in a post so I can find this info in one place.

Thinking about Time in .NET Web Apps

Web apps typically require that dates are stored in time zone agnostic fashion. This means storing DateTime values as UTC, so that you have a consistent baseline date value. This means storing dates in UTC format either with generated values using DateTime.UtcNow or, if you are capturing user input dates converting the time zone specific dates to UTC dates using .ToUniversalTime().

However in Web apps, this is not quite so easy because you can’t just use the server’s local time and use .ToUniversalTime() on a captured date input because that time reflects the server’s time, not necessarily the user’s time. So in addition to converting to UTC you also need to be able to convert user local dates to and from specific time zones which you can do with TimeZoneInfo.ConvertTime() and THEN convert that value to UTC.

On the other end when retrieving dates you convert all dates from UTC dates to local dates according to the user’s time zone for which you need to using the TimeZoneInfo class. You need two static methods from it: TimeZoneInfo.FindSystemTimeZoneById() and TimeZoneInfo.ConvertTimeFromUtc(). Also useful are TimeZoneInfo.ConvertTime() which converts between two timezones and TimeZoneInfo.ConvertTimeToUtc(). Finally if you’re building UI to allow users to select Time zones you’ll probably want to use TimeZoneInfo.GetSystemTimeZones().

Let’s see how we can put all of these together.

Simple Conversion Routines

Before looking at more specific examples of how to set this up in a more application specific way in a Web application, let’s look at a couple of easy ways to convert UTC dates to a specific locale via some DateTime extension methods which demonstrate the basic features that .NET provides for time zone conversions.

/// <summary>
/// Returns TimeZone adjusted time for a given from a Utc or local time.
/// Date is first converted to UTC then adjusted.
/// </summary>
/// <param name="time"></param>
/// <param name="timeZoneId"></param>
/// <returns></returns>
public static DateTime ToTimeZoneTime(this DateTime time, string timeZoneId = "Pacific Standard Time")
    TimeZoneInfo tzi = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId);
    return time.ToTimeZoneTime(tzi);

/// <summary>
/// Returns TimeZone adjusted time for a given from a Utc or local time.
/// Date is first converted to UTC then adjusted.
/// </summary>
/// <param name="time"></param>
/// <param name="timeZoneId"></param>
/// <returns></returns>
public static DateTime ToTimeZoneTime(this DateTime time, TimeZoneInfo tzi)
    return TimeZoneInfo.ConvertTimeFromUtc(time, tzi);

To use these extension methods is super easy. The first one works by getting the value from a string:

DateTime time = DateTime.UtcNow.ToTimeZoneTime("Pacific Standard Time");

while the second works of an existing TimeZoneInfo instance:

var tz = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
DateTime time2 = DateTime.UtcNow.ToTimeZoneTime(tz);

The latter is important as you want to cache a TimeZoneInfo instance in order to reduce the overhead of looking up the TimeZoneInfo repeatedly when running these functions in a loop – like displaying a long list of items with dates in them. I’ll talk more at performance later, but suffice it say that compared to direct date access doing time zone conversions is (relatively) slow…

Next let’s look at how we get TimeZones into our applications.

Admin User Interface

In order to properly convert user time zone values you need to figure out which time zone the user is coming from, so you you need to capture a preference for this and save it with a user. I typically capture his as part of my User Admin Interface and let the user pick from a list of the available time zones. This looks something like this on my Profile form:


which drops down a list retrieved with TimeZoneInfo.GetSystemTimeZones:


The code to do this is pretty straight forward. I use ASP.NET MVC 5 and have the code in my ViewModel to create an array of SelectList objects:

public class ProfileViewModel : AppBaseViewModel { public User User { get; set; } public string Password { get; set; } public string PasswordConfirm { get; set; }

public string JsTime { get; set; } public SelectListItem[] TimezoneList { get; set; }
public ProfileViewModel() { var tzs = TimeZoneInfo.GetSystemTimeZones(); TimezoneList = tzs.Select(tz => new SelectListItem() { Text = tz.DisplayName, Value = tz.Id }).ToArray(); } }

The actual TimeZone value is then bound to my User object which has a TimeZone property.

<div class="form-group">
    <div class="input-group ">
        <div class="input-group-addon" title="Timezone to display dates.">
            <i class="fa fa-fw fa-flag"></i>
        @Html.DropDownListFor(mod => Model.User.TimeZone, Model.TimezoneList,
            new {@class = "form-control", title = "Timezone to display dates."})

The model binds to it and the value is stored in the database in this case in MongoDb. The property stores the TimeZoneInfo.Id which is a value like Hawaiian Standard Time or Eastern Standard Time that can be used as keys for looking up timezones. This key is what’s used to find the right time zone to perform any conversions.

Posted in ASP.NET  C#  .NET  Localization  

The Voices of Reason


Chris Hynes
February 10, 2015

# re: Back to Basics: UTC and TimeZones in .NET Web Apps

NodaTime takes a lot of the pain out of dealing with time zones: http://nodatime.org/.

Instead of doing querying for local dates in memory, with EF you can define custom SQL functions like this: http://blog.3d-logic.com/2014/08/11/the-beta-version-of-store-functions-for-entityframework-6-1-1-code-first-available/. NHibernate has a similar capability.

Michael Weinand
February 10, 2015

# re: Back to Basics: UTC and TimeZones in .NET Web Apps

Any thoughts on http://nodatime.org/? We've used that in the past with success to make Timezones a bit easier to deal with.

Rick Strahl
February 10, 2015

# re: Back to Basics: UTC and TimeZones in .NET Web Apps

NodaTime is excellent, but it does requires a full commitment in an application which is often not possible especially in existing applications. I've added a few notes to the post . Thanks for the reminder.

Tom Deleu
February 11, 2015

# re: Back to Basics: UTC and TimeZones in .NET Web Apps

What about DateTimeOffset? Where does that fit in according to your opinion?

Rick Strahl
February 11, 2015

# re: Back to Basics: UTC and TimeZones in .NET Web Apps

@Tom - DateTimeOffset is useful as it provides built-in support for the Offset from Utc, but it doesn't really help simplify the issues addressed in this post. I think DateTimeOffset would be very useful in applications that run in many different physical locations where you both need local dates for user display in a fixed location and Utc dates for storage to the database. In the end if you pull data out of the database you still have to somehow figure out what timezone is used though.

There's a great entry that compares about both DateTime and DateTimeOffset on SO:

James Manning
February 11, 2015

# re: Back to Basics: UTC and TimeZones in .NET Web Apps

"In the end if you pull data out of the database you still have to somehow figure out what timezone is used though."

Nope, that's why the datetimeoffset data type was introduced in SQL Server (linked to in that SO thread, even)


DateTimeOffset keeps the DateTime along with the UTC offset, which datetimeoffset does in SQL Server as well. It's simpler to use that end-to-end instead of having to remember to convert to and from UTC when dealing with the database.

If you're stuck with a legacy situation where only datetime/datetime2 is supported, then yes, storing as UTC is definitely the way to go. If you're using SQL 2008 or later, though, IMHO you should definitely be using datetimeoffset instead of dealing with timezone conversion to and from UTC.

Martin Enzelsberger
February 11, 2015

# re: Back to Basics: UTC and TimeZones in .NET Web Apps

»[NodaTime] is a little more complex [...] because it forces you to think about what kind of date you are dealing with«

I'm pretty sure that's not a negative.
To me this sentence sounds like »catch blocks are great, but they are a little more complex because they force you to think about to do when an exception occurs«.

You *should* be thinking about it, so it's a good thing that NodaTime forces you to do so. To me it's more of an advantage than a disadvantage (if you're dealing with time zones).

February 11, 2015

# re: Back to Basics: UTC and TimeZones in .NET Web Apps

As James says above DateTimeOffset handles the server side storage for you, client-side you can just stick with Date object to handle UTC to local time both ways, especially if you use an SPA and do all the binding client side, this makes timezone handling trivial. Convert to UTC for input from client as early as possible and present back to client as local time as late as possible, no need to complicate things server side

Rick Strahl
February 11, 2015

# re: Back to Basics: UTC and TimeZones in .NET Web Apps

@James, @Johan - DateTimeOffset is not all that useful in server applications where the timezone is fluent. If I enter a date in Hawaii, then later go to Oregon and want to see the date in Oregon timezone format I still have to do conversions. Since DTO only supports a single timezone - I first have to convert to that timezone to save as well so I have up front work to do. You also have to get the date into the right TimeZone for saving first since the server will catch date using server local time. So you end up doing the time zone conversion up front. So in a fluent Web date environment where users are not statically tied to a timezone DateTimeOffset buys very little IMHO. 

To be honest I've not used DateTimeOffset much at all, because of the above issues, but maybe I'm missing something obvious :-)

@Johan - yes client side data display is easier if you push data down as UTC with JSON and then just display the data, which is nice since the browser knows what timezone to use. But that only addresses one of the scenarios - and which is basically the easiest one (ie. calling user.GetUserTime()). It doesn't address querying on the server, it doesn't address grouping or assignment for display values that are often pushed down from views in the server (I tend to pre-format dates as strings on the server in my view objects).

February 11, 2015

# re: Back to Basics: UTC and TimeZones in .NET Web Apps

Funny timing - I have been globalizing a Vacation tracking system for the past week and am using nodatime. I first tried the built in .NET classes you describe above, but I found the list of available time zones (which I believe are read from the server registry) to be terribly lacking and insufficient for my project. Further research led me to the tz database of IANA/Olson time zones which were perfect for what I was trying to accomplish. A little more research and I happened upon NodaTime. Within an hour I had it working with an external tz database. This is awesome because I can automate the updates to the db. Once I got my app working with NodaTime, I spent the majority of my time analyzing the dates and datetime values in the system to determine how best to store the dates. This portion of the project seems to require the most thought and is still ongoing. I was surprised to see this blog post today in my inbox and was glad to see that you mentioned NodaTime. Good luck everyone with your projects and keep up the helpful blog posts.

Luke Puplett
February 13, 2015

# re: Back to Basics: UTC and TimeZones in .NET Web Apps

I tend to write more web services than web sites, though they work the same way, so the same logic should apply and the same problems.

Web services tend to use date-time formats that capture and serialize the local time and the offset from UTC, and no other time-zone information.

This is stored as DATETIMEOFFSET in MSSQL - indeed, EF for a while, removed support for the old DateTime, forcing DTO!

This information has captured a moment in time that has all the information to be able to show what a person thought the time was, historically. Even if their local daylight savings time rules are changed, such as for the USA, I remember the impact on IT when working on a trade-floor in 2005:


The problem with not persisting the offset is that the programmer is at the mercy of the current algorithm in whatever library, when retroactively applying it to an historic timestamp. Will the algorithm know that daylight savings time was different back then?

As you can imagine, this is very important legally. I believe this is why ISO 8601/RFC 3339 has come to be the standard for date exchange and persistence.

....I think. Seriously, its one of those mind-boggling problems that invokes a fair bit of self doubt.

In any case, Microsoft are de-emphasising DateTime in favour of DateTimeOffset, probably for similar reasons. Used correctly, it is, I guess "non destructive".

Thanks for the blog post, its a useful tool for rationalizing and scenario planning.

All the best,


James Manning
February 18, 2015

# re: Back to Basics: UTC and TimeZones in .NET Web Apps

@Rick - WRT "Since DTO only supports a single timezone - I first have to convert to that timezone to save" and "have to get the date into the right TimeZone for saving first" - those are not correct (and very unfortunate that those were the reasons you've avoided using it).

The whole point is that since the data type stores the offset with the datetime, then the same column can store any timezone. You don't have to do *any* timezone conversions at any point if you don't want. You get a DateTimeOffset from someone in Hawaii that's got a UTC offset of -10 and you can store it as-is and still happily allow Oregon people to store in -8/-7, EST people to store -5/-4, etc. If SQL Server forced developers/users to convert to a particular timezone for saving the datatype would have no benefit over just saving as datetime2 and telling people to store as UTC (certainly the best practice if you're stuck using datetime/datetime2).

The *only* time you need to do any conversion is strictly when displaying, and only if you want to display it in a different timezone than what it already is (for instance, displaying it in the user's timezone regardless of what the originating timezone was). You can do comparisons, queries, etc all without having to do any conversions.

In your enter-in-Hawaii/display-in-Oregon scenario, with datetime or datetime2, you would typically convert twice, once on the write path to convert to UTC for storage (since your storage has no support to encode the UTC offset, so you would either encode it as a separate column, or more likely, store the version with no offset), then again on the read path to convert the UTC to Oregon.

If you use datetimeoffset instead, you don't need to convert on your write path at all. It comes in as offset -10 from your Hawaii person, -8 half the time from your Oregon people, -7 the rest of the time, etc. and you just store it like that. On your read path, you can display it with the original offset if you want (something that's not really an option if you forced conversion to UTC on the write path), or convert it to local time (just like you would in the datetime/datetime2 scenario)

I think Bart Duncan said it best, IMHO :)


When should you use datetimeoffset instead of datetime? The answer is: you should almost always use datetimeoffset. I’ll make the claim that there is only a single case where datetime is clearly the best data type for the job, and that’s when you actually require an ambiguous time

Rick Strahl
February 19, 2015

# re: Back to Basics: UTC and TimeZones in .NET Web Apps

@James - I think I understand what DateTimeOffset does - it stores the DT *and* a timezone offset of when the data is captured for that particular tz. Makes sense. But for Web apps you NEVER capture time that way. You want to capture the date in UTC and the offset is irrelevant because it represents the server's time not the client's time.

Even if you DID store the offset from the original users timezone (which means you'd have to do the conversion up front because the server's not running that users timezone) you still get *only that timezone*. Not the timezone that a user of the date might want to see at a later time.

I guess I don't see how DTO helps if the time value's offset is fixed to a specific timezone when the application always adjusts to the user's timezone preference which mostly will not be the original DTO offset. You STILL have to do these conversions for EVERY user and if I do that then what does DTO buy me? Nothing except more storage required.

I also don't agree with this:

> The *only* time you need to do any conversion is strictly when displaying

because if you do certain datetime operations like Date queries that group by day or month or less granual time increment, that requires that you adjust those queries for that timezone. Otherwise you're going to include the wrong time range. So there are a number of places where this matters - almost every date query with user input in particular since those are typically done based on day ranges.

Andrei Ignat
February 20, 2015

# re: Back to Basics: UTC and TimeZones in .NET Web Apps

Wrote also about JsonResult in MVC and the corresponding problems in reading in HTML .

February 24, 2015

# re: Back to Basics: UTC and TimeZones in .NET Web Apps

Hi Rick,

I'm with you on the datetime offset/datetime issue. On the server I only want to deal with UTC dates.
But I wonder why you don't use the javascript method getTimezoneOffset() of a Date on the client to get the timezone of the user ?


Matt Roberts
April 09, 2015

# re: Back to Basics: UTC and TimeZones in .NET Web Apps

Stellar job, this was a really nice summary of some techniques I've used and then forgotten about and had to re-google, thanks!

Matt Johnson
April 19, 2015

# re: Back to Basics: UTC and TimeZones in .NET Web Apps

Great post. Thanks for bringing awareness to my favorite subject! Though if you don't mind, I'm going to tear it apart. :) Allow me to clarify a few points:

TimeZoneInfo.ConvertTimeFromUtc will throw an exception if passed a DateTime with Local kind. If it's unspecified, it will assume UTC. So the ToTimeZoneTime extension method will not behave as it says in the comments, accepting a Utc or local time. The input time parameter must be in terms of UTC. The way you used it, coming from DateTime.UtcNow, is just fine.

Regarding caching - It's good to hold on to a TimeZoneInfo object for as long as you need it within the local scope. However, I don't recommend building your own cache, as the FindSystemTimeZoneById method already uses a lookup cache internally.

Next, with regard to displaying the time zone names in a drop-down - While there's nothing inherently wrong with your approach, it's important to note that the DisplayName property will be localized by the OS language of the server (which might not be English). The usual localization methods in .NET don't apply to the TimeZoneInfo object. In a multilingual app, one may need to use the TimeZoneInfo.Id to look up a display name from a resource file, rather than use the DisplayName property. Alternatively, one could use my TimeZoneNames library. (http://mj1856.github.io/TimeZoneNames/)

With regard to capturing the user's default time zone. Sorry, but the approach you describe is flawed. The problem is that every implementation of JavaScript is free to present time zones in whatever manner they wish. The spec is not refined enough to rely on it having any particular set of values. In particular, the example you show of "Hawaiian Standard Time" mapping back to the TimeZoneInfo ID is just a coincidence. Worldwide, typically they will NOT match up. Also, the JavaScript names will indeed switch out for DST, while the TimeZoneInfo.Id will not. So "Eastern Standard Time" will always be the ID for the US Eastern time zone, even when JavaScript shows "Eastern Daylight Time". Additionally, some browsers will just show an abbreviation (HST, EST, EDT, etc.), or may localize the time zone string to the user's language.

Time zone detection is actually quite difficult, which is why it makes sense to ask your user in a drop-down list. There are libraries like jsTimeZoneDetect which try to guess your time zone, but it is not 100% accurate. There is the JavaScript getTimeZoneOffset method, but that only returns the offset associated with the date you called it on - which is not enough to determine the actual time zone. There's also a newer way, supported by Chrome and Opera, but not IE, FF, or Safari: Intl.DateTimeFormat().resolvedOptions().timeZone - Hopefully that will catch on in other browsers, but today - there's no universal way to detect the time zone.

Moving along - The GetUserTime method looks fine, but in your GetUtcUserTime method, there's a bug. You call ToUniversalTime on the result of the time zone conversion. So that's DateTime.ToUniversalTime, which will still use the time zone of the server - not the destination time zone. You would need to use TimeZoneInfo.ConvertTimeToUtc to avoid that problem, rather than ToUniversalTime. However, I'm a bit confused why you would need this method to begin with, or what it's actually doing. If the time is in the user's time zone, then you would just have one call to TimeZoneInfo.ConvertTimeToUtc. If the time is in the server's time zone, then you would just have one call to DateTime.ToUniversalTime. The mix here doesn't make a lot of sense.

Moving further down your post, in the section on Date range queries - again, calling ToUniversalTime on a DateTime will use the server's local time zone, not the user's time zone. So the query boundaries are not correctly aligned to the user. Again, you'd need to use TimeZoneInfo.ConvertTimeToUtc.

In the User Captured Time section, the DateMath function could just be a single call to TimeZoneInfo.ConvertTimeToUtc, passing in the user's time zone. The manual manipulation of offsets is not only unnecessarily, but it's slightly flawed in that you expect the "offset" and "offsetLocal" values to be reflecting the offset for the same "start" time, but that's defined by the user's time zone - so you're not really referring to the same instant. This will show up if either time zone is near a DST transition. It's possible that your local time zone could be on one side of the transition, and the user's time zone could be on the other side.

The same easy-to-make mistake is in you AdjustTimeZoneOffset class. It will work if the "localTime" parameter has Kind=Local, or Kind=Utc, but not necessarily will it be correct in all cases when Kind=Unspecified. Think of it this way - the TimeZoneInfo.GetUtcOffset(DateTime) *prefers* to work with Unspecified kinds, as those mean that the time is relative to to the time zone of that particular TimeZoneInfo object. When it gets a Local or Utc kind, it converts to that time zone first. (This is inverted from most of the other methods that assume Unspecified means Local or Utc).

The theme throughout all of this feedback is that you should be working directly with the user's time zone and UTC - avoiding your own servers local time zone whenever possible. Really, the time zone of the server should be considered irrelevant. Avoid any use of "local" time (DateTime.Now, DateTime.ToUniversalTime, DateTime.ToLocalTime, DateTimeKind.Local, etc.) and everything will be much simpler.

Nitpick: "DayLight Savings Time" => "Daylight Saving Time". Daylight is one word, and the second word should have no "s" at the end. Think "I'm saving daylight during this time" - which is arguable, but nonetheless, that's the term's origin.

With regard to DateTimeOffset - I'll disagree with you completely. DateTimeOffset is essential in a web application, and it addresses much more than just a single scenario. You're also not tied to a single time zone, as the offset can be adjusted to any time zone you might be in. The offset doesn't track the time zone, just the offset for the time zone that happens to be in effect. Remember, there's much more to a time zone than just an offset, as many time zones switch between more than one offset due to daylight saving time.

The essential part you're missing about DateTimeOffset is that it's the only way to effectively disambiguate between ambiguous local values. For example, if you have 2014-11-02 01:00 as a DateTime, and you're in the US Pacific time zone, you have a problem because you don't know if that's PDT (-07:00), or PST (-08:00). The DateTimeOffset keeps the offset intact, so you affirmatively know which of the two possibilities you're using. If you store a UTC time, or if the time zone doesn't use DST (like Hawaii), then you're OK. But in cases where the time zone uses DST, not storing the offset could mean that you are losing data - you'd potentially be using a value as an hour before or after the intended moment.

You're right about the performance of DateTime.Now, and TimeZoneInfo. In fact Noda Time is one of the ways you can improve upon performance, as it is thoroughly optimized for perf.

Speaking of Noda Time, I'll disagree with you that you have to replace everything throughout your system. Sure, if you do, you'll have a lot less opportunity to make mistakes, but you certainly can just use Noda Time where it makes sense. I've personally worked on systems that needed to do time zone conversions using IANA time zones (ex. "America/Los_Angeles"), but tracked everything else in DateTime and DateTimeOffset types. It's actually quite common to see Noda Time used extensively in application logic, but left completely out of the DTOs and persistence layers. In some technologies, like Entity Framework, you couldn't use Noda Time directly if you wanted to - because there's no where to hook it up. Others, such as Json.Net, Dapper, and RavenDB have extension libraries for Noda Time so you can use it there if you want to, but there's still no hard requirement that you do.

In general, I think your post is great in that you are covering most of the areas of concern, where attention needs to be paid to converting between time zones. But like many things, the devil is in the details. Even you, who are more familiar with these functions than most, still were able to make mistakes like calling ToUniversalTime on a non-local DateTime, or calling GetUtcOffset with a value from a different time zone. Noda Time won't let you get into trouble, because the API prevents you from calling methods that have confusing or ambiguous behavior.

Hope this feedback was helpful.

Matt Johnson
April 19, 2015

# re: Back to Basics: UTC and TimeZones in .NET Web Apps

I've blogged with detail in response to one particularly problematic part of this blog post.


Ron Birk
June 09, 2015

# re: Back to Basics: UTC and TimeZones in .NET Web Apps

Using "var dt = new Date().toString();" doesn't seem to work on Chinese browsers (for example) and also not on IE9. As the time zone will either be localized or not shown at all depending on language and browser version.

Luke Puplett
April 20, 2017

# re: Back to Basics: UTC and TimeZones in .NET Web Apps

Reading these comments again, unless Rick is not explaining clearly enough (it's arduous to explain this stuff) then I think Rick is mistaken and that a system using DTO entirely will work for his needs.

@Rick, the DTO is effectively an ISO1806 structure. The offset it stores is from UTC, not from the local time. I believe you do have all the information needed to convert or display or review in many years time, captured in that single format.

(Local time + difference to UTC at the time)

That's all you'll ever need. Come up with a good scenario where that wouldn't work, I can't.

I think storing as UTC is where everyone gets it wrong, it's destructive, it moves the time observed by the user and then throws away the offset.

Edgar Ricárdez
September 09, 2017

# re: Back to Basics: UTC and TimeZones in .NET Web Apps

Hi @Rick, thanks for this post.

With the client value: Central Daylight Time (Mexico), I get TimeZoneExceptionNotFound.



West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2017