DateTime.Parse("2012-09-30T23:00:00.0000000Z") always converts to DateTimeKind.Local

C#asp.net.NetDatetimeUtc

C# Problem Overview


I want to parse a string that represent a DateTime in UTC format.

My string representation includes the Zulu time specification which should indicate that the string represent a UTC time.

var myDate = DateTime.Parse("2012-09-30T23:00:00.0000000Z");    

From the above I would expect myDate.Kind to be DateTimeKind.Utc, instead it is DatetimeKind.Local.

What am I doing wrong and how to Parse a string that represents a UTC time?

Many thanks!

C# Solutions


Solution 1 - C#

I would use my Noda Time project personally. (Admittedly I'm biased as the author, but it would be cleaner...) But if you can't do that...

Either use DateTime.ParseExact specifying the exact format you expect, and include DateTimeStyles.AssumeUniversal and DateTimeStyles.AdjustToUniversal in the parse code:

using System;
using System.Globalization;

class Test
{
    static void Main()        
    {
        var date = DateTime.ParseExact("2012-09-30T23:00:00.0000000Z",
                                       "yyyy-MM-dd'T'HH:mm:ss.fffffff'Z'",
                                       CultureInfo.InvariantCulture,
                                       DateTimeStyles.AssumeUniversal |
                                       DateTimeStyles.AdjustToUniversal);
        Console.WriteLine(date);
        Console.WriteLine(date.Kind);
    }
}

(Quite why it would adjust to local by default without AdjustToUniversal is beyond me, but never mind...)

EDIT: Just to expand on my objections to mattytommo's suggestion, I aimed to prove that it would lose information. I've failed so far - but in a very peculiar way. Have a look at this - running in the Europe/London time zone, where the clocks go back on October 28th in 2012, at 2am local time (1am UTC):

DateTime local1 = DateTime.Parse("2012-10-28T00:30:00.0000000Z");
DateTime local2 = DateTime.Parse("2012-10-28T01:30:00.0000000Z");
Console.WriteLine(local1 == local2); // True

DateTime utc1 = TimeZoneInfo.ConvertTimeToUtc(local1);
DateTime utc2 = TimeZoneInfo.ConvertTimeToUtc(local2);
Console.WriteLine(utc1 == utc2); // False. Hmm.

It looks like there's a "with or without DST" flag being stored somewhere, but I'll be blowed if I can work out where. The docs for TimeZoneInfo.ConvertTimeToUtc state

> If dateTime corresponds to an ambiguous time, this method assumes that it is the standard time of the source time zone.

That doesn't appear to be the case here when converting local2...

EDIT: Okay, it gets even stranger - it depends which version of the framework you're using. Consider this program:

using System;
using System.Globalization;

class Test
{
    static void Main()        
    {
        DateTime local1 = DateTime.Parse("2012-10-28T00:30:00.0000000Z");
        DateTime local2 = DateTime.Parse("2012-10-28T01:30:00.0000000Z");
        
        DateTime utc1 = TimeZoneInfo.ConvertTimeToUtc(local1);
        DateTime utc2 = TimeZoneInfo.ConvertTimeToUtc(local2);
        Console.WriteLine(utc1);
        Console.WriteLine(utc2);

        DateTime utc3 = local1.ToUniversalTime();
        DateTime utc4 = local2.ToUniversalTime();
        Console.WriteLine(utc3);
        Console.WriteLine(utc4);
    }
}

So this takes two different UTC values, parses them with DateTime.Parse, then converts them back to UTC in two different ways.

Results under .NET 3.5:

28/10/2012 01:30:00 // Look - we've lost information
28/10/2012 01:30:00
28/10/2012 00:30:00 // But ToUniversalTime() seems okay...
28/10/2012 01:30:00

Results under .NET 4.5 beta:

28/10/2012 00:30:00 // It's okay!
28/10/2012 01:30:00
28/10/2012 00:30:00
28/10/2012 01:30:00

Solution 2 - C#

As usual, Jon's answer is very comprehensive. That said, nobody has yet mentioned DateTimeStyles.RoundtripKind. If you want to convert a DateTime to a string and back to the same DateTime (including preserving the DateTime.Kind setting), use the DateTimeStyles.RoundtripKind flag.

As Jon said, the correct thing to do is to use the "O" formatter when converting a DateTime object to a string. This preserves both the precision and timezone information. Again, as Jon said, use DateTime.ParseExact when converting back. But if you use DateTimeStyles.RoundtripKind, you always get back what you put in:

var now = DateTime.UtcNow;
var strNow = now.ToString("O");
var newNow = DateTime.ParseExact(strNow, "O", CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind);

In the above code, newNow is a exact same time as now, including the fact that it is UTC. If run the same code except substitute DateTime.Now for DateTime.UtcNow, you'll get an exact copy of now back as newNow, but this time as a local time.

For my purposes, this was the right thing since I wanted to make that sure that whatever was passed in and converted is converted back to the exact same thing.

Solution 3 - C#

Use the TimeZoneInfo class using the following:

var myDate = TimeZoneInfo.ConvertTimeToUtc(DateTime.Parse("2012-09-30T23:00:00.0000000Z"));

Solution 4 - C#

You can use the following format for parser method: yyyy-MM-ddTHH:mm:ss.ffffffK

This shall properly process time zone information at the end (http://msdn.microsoft.com/en-us/library/8kb3ddd4(v=vs.80).aspx">starting from .NET 2.0).

RE: http://en.wikipedia.org/wiki/ISO_8601">ISO 8601

Solution 5 - C#

Ran into a similar issue before and several hours (and pulled hairs) later ended up using [DateTime.SpecifyKind][1]:

DateTime.SpecifyKind(inputDate, DateTimeKind.Utc);

I believe someone also eluded to this in a comment above as well. [1]: https://msdn.microsoft.com/en-us/library/system.datetime.specifykind(v=vs.110).aspx

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionGiuseppe RomagnuoloView Question on Stackoverflow
Solution 1 - C#Jon SkeetView Answer on Stackoverflow
Solution 2 - C#Simon GillbeeView Answer on Stackoverflow
Solution 3 - C#mattytommoView Answer on Stackoverflow
Solution 4 - C#port443View Answer on Stackoverflow
Solution 5 - C#vandshView Answer on Stackoverflow