Actually, it is doing exactly what you asked it to do. From the point of view of the server, you passed it an "unspecified" DateTime
. In other words, just year, month day, etc., withoutout any context. If you look at the .Kind
property, you will see that it is indeed DateTimeKind.Unspecified
.
When you call .ToUniversalTime()
on an unspecified kind of DateTime
, .NET will assume the context of the local time zone of the computer that the code is running on. You can read more about this in the documentation here. Since your server is set to UTC then regardless of the input kind, all three kinds will yield the same result. Basically, it's a no-op.
Now, you said that you wanted the time to reflect the user's time zone. Unfortunately, that is information that the server doesn't have. There's no magic HTTP header that carries time zone details.
There are ways to achieve this effect though, and you have some options.
JavaScript Method
This is probably the easiest option, but it does require JavaScript.
- Take the local date and time input from your user and parse it into a JavaScript
Date
class.
- For easier parsing, you might consider using a
moment
instead, from the moment.js library.
- Get the UTC date and time from the
Date
or moment
, and pass it to your server.
- This is relatively easy in JavaScript, so I'll spare you the details.
- There are many formats you can pass it, but the preferred way is as an ISO8601 timestamp. Example:
2013-09-17T08:00:00.000Z
- Don't forget the
Z
at the end. That designates that the time is in UTC.
- Since it is passed to you as UTC, you can just store it without any conversion.
- When retrieving it, you again pass UTC to the browser, load it into a
Date
or a moment
with JavaScript, and then emit the local date and time.
- I highly recommend you try moment.js if you take this approach. It can be done without it, but that can be much more complicated and error prone.
.NET Method using Windows Time Zones
If you aren't going to invoke JavaScript, then you will need to ask the user for their time zone. This can work well in a larger application, such as on the user's profile page.
- Use
TimeZoneInfo.GetSystemTimeZones
to build a drop-down list.
- For each
TimeZoneInfo
item in the list, use the .Id
for the value, and the .DisplayName
for the text.
Then you can use this value when you want to convert the time.
TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById(yourUsersTimeZone);
DateTime utc = TimeZoneInfo.ConvertTimeToUtc(theInputDatetime, tz);
This will work with Windows time zones, which are a Microsoft creation, and have some drawbacks. You can read about a few of their deficiencies in the timezone tag wiki.
.NET Method using IANA Time Zones
If you want to use the more standard IANA time zones, such as America/New_York
or Europe/London
, then you can use a library like Noda Time. It offers a much better API for working with date and time than the built-in framework. There's a bit of a learning curve, but if you're doing anything complicated it is well worth the effort. As an example:
DateTimeZone tz = DateTimeZoneProviders.Tzdb["America/New_York"];
var pattern = LocalDateTimePattern.CreateWithInvariantCulture("yyyy-MM-dd HH:mm:ss");
LocalDateTime dt = pattern.Parse("2013-09-17 04:00:00").Value;
ZonedDateTime zdt = tz.AtLeniently(dt);
Instant utc = zdt.ToInstant();
About Daylight Saving Time
Regardless of which of these three approaches you take, you will have to deal with the problems created by Daylight Saving Time. Each of these samples shows a "lenient" approach, where if the local time you specify is ambiguous or invalid, that some rule is followed so you still get some valid moment in time. You can see this directly with the Noda Time approach when I called AtLeniently
. But it occurs with the other ones also - it's just implicit. In JavaScript, the rules can vary per browser, so don't expect consistent results.
Depending on what kind of data you're collecting, you may decide it's perfectly acceptable to make this kind of assumption. But in many cases it's not appropriate to assume. In that case, you may need to either alert your user that the input time is invalid, or ask them which of two ambiguous times they meant.
In .Net, you can check for this with TimeZoneInfo.IsInvalidTime
and TimeZoneInfo.IsAmbiguousTime
.
For an example of how daylight saving time works, see here. In the "spring-forward" transition, a time during the transition is invalid. In the "fall-back" transition, a time during the transition is ambiguous - that is, it could have happened either before or after the transition.