10

I have a datetime in database which I read using SqlDataReader and then cast it to (DateTime). After the cast its Kind property is DateTimeKind.Unspecified.

Then I have another string which I read from some other source. Its format is like this 2016-01-20T22:20:29.055Z. I do DateTime.Parse("2016-01-20T22:20:29.055Z") and its Kind property is DateTimeKind.Local.

How do I properly parse the both date times for comparison? Do I need to use DateTimeOffsets? How should I parse them?

Thanks

Shikasta_Kashti
  • 701
  • 2
  • 13
  • 28

5 Answers5

13

Because SQLReader cannot reasonably infer a DateTimeKind, it leaves it as unspecified. You'll want to use DateTime.SpecifyKind to change the DateTimeKind on your output from the SQLReader to the appropriate value. This works ok if you are only dealing with UTC and one consistent local time zone; otherwise, you really should be using DateTimeOffset in both your code and the SQL Database.

The string "2016-01-20T22:20:29.055Z" is ISO 8601 compliant and is a UTC date; however, DateTime.Parse with only 1 argument can end up performing a conversion to local time. Per the documentation:

Generally, the Parse method returns a DateTime object whose Kind property is DateTimeKind.Unspecified. However, the Parse method may also perform time zone conversion and set the value of the Kind property differently, depending on the values of the s and styles parameters:

  • If s contains time zone information, the date and time is converted to the time in the local time zone and the Kind is DateTimeKind.Local.
  • If s contains time zone information, and styles includes the AdjustToUniversalflag, the date and time is converted to Coordinated Universal Time (UTC) and the Kind is DateTimeKind.Utc.
  • If s contains the Z or GMT time zone designator, and styles includes the RoundtripKind flag, the date and time are interpreted as UTC and the Kind is DateTimeKind.Utc.

Also see UTC gotchas in .NET and SQL Server in Derek Fowler's blog for additional coverage on the topic.

Community
  • 1
  • 1
JamieSee
  • 12,696
  • 2
  • 31
  • 47
  • 3
    But isn't that's what the "Z" is for? Why does it still come out as `DateTimeKind.Local`. It should have been `DateTimeKind.Utc` because "Z" is UTC, is that not correct? – Shikasta_Kashti Jan 20 '16 at 23:15
  • 1
    @mwahidje Oops, good point. Hold on while I edit the answer to be correct. Thanks for catching it. – JamieSee Jan 20 '16 at 23:29
  • Note that in this particular case, *either* `AdjustToUniversal` or `RoundTripKind` will solve the problem. – Matt Johnson-Pint Jan 22 '16 at 21:09
3

In your second example, 2016-01-20T22:20:29.055Z has timezone information provided with it; the 'Z' at the end indicates that the timestamp is intended for Coordinated Universal Time (UTC). However, DateTime.Parse() will default its conversion using DateTimeKind.Local unless a specific timezone is specified. You can use DateTime.ParseExact to be more specific.

As to why the datetime values in your database are coming out as Unspecified, that's likely because they contain no timezone indication at all. Check to see if your database values specify timezone information, either by using 'Z' at the end or specifying an exact timezone, such as 2016-01-20T22:20:29.055-07:00 (UTC-7).

Chase
  • 934
  • 6
  • 18
  • Even if I specify the timezone for `DateTime.Parse()` ('Z' in my case), it is still `DateTimeKind.Local` and also `DateTime.ParseExact("2016-01-20T22:20:29.055Z", "yyyy-MM-ddThh:mm:ss.fffZ", null)` still comes out as `DateTimeKind.Local` – Shikasta_Kashti Jan 20 '16 at 23:11
  • 2
    By default, `ParseExact` will convert to `DateTimeKind.Local`. You need to use the `AdjustToUniversal` `DateTimeStyle` to convert to UTC. Like so: `DateTime.ParseExact("2016-01-20T22:20:29.055Z", "yyyy-MM-ddTHH:mm:ss.fffZ", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal);` – Chase Jan 20 '16 at 23:17
  • ^^^ is still `DateTimeKind.Local` – Shikasta_Kashti Jan 20 '16 at 23:22
  • 2
    Using the following code produces `DateTimeKind.Utc` for me: `DateTime time = DateTime.ParseExact("2016-01-20T22:20:29.055Z", "yyyy-MM-ddTHH:mm:ss.fffZ", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal); DateTimeKind kind = time.Kind;` – Chase Jan 20 '16 at 23:26
  • I mistook AdjustToUniversal for AssumeUniversal which was producing DateTimeKind.Local. But yes, the above works. Thanks. – Shikasta_Kashti Jan 20 '16 at 23:30
  • 2
    Sorry, originally was AssumeUniversal when I posted it. Was probably editing it to AdjustToUniversal when you saw it. :) – Chase Jan 20 '16 at 23:31
0

You can use something like this:

string format = "ddd dd MMM h:mm tt yyyy";
DateTime dateTime = DateTime.ParseExact(dateString, format,
    CultureInfo.InvariantCulture);

In format variable, you can put the format you want, and pass it to ParseExact function.

Hope it helps.

Zakariaa.B
  • 133
  • 8
0

You are missing the datetime context (offset) in your database. You should persist it either in a datetimeoffset column or in a datetime column but persisting utc datetimes. And always better compare two utc datetimes.

0

I coded a quick C# console app that I pasted in below. This converts a UTC date and time to a string (format similar to the ISO 8601 format described in another post with some extra digits of precision), writes it to a file, reads it from the file (as a string) and then converts it back to a UTC date and time. It then compares the two UTC Date Time objects, which are both of UTC kind, and they match.

class Program
{
    // "2016-01-20T22:20:29.055Z" is ISO 8601 compliant and is a UTC date
    const string dtf = "yyyy-MM-ddTHH:mm:ss.fffffffZ";

    static void Main(string[] args)
    {
        string file = @"c:\temp\file.txt";
        DateTime dt = DateTime.UtcNow;

        
        using (var sw = new System.IO.StreamWriter(file))
        {
            sw.WriteLine(dt.ToString(dtf, System.Globalization.CultureInfo.InvariantCulture));
        }

        DateTime dtin;
        using (var sr = new System.IO.StreamReader(file))
        {
            dtin = DateTime.ParseExact(sr.ReadLine(), dtf, System.Globalization.CultureInfo.InvariantCulture);
        }

        Console.WriteLine(dt.ToString(dtf) + "\r\n" + dtin.ToString(dtf) + "\r\nEquality:" + (dt == dtin));
        Console.ReadLine();
    }
}

tonyb
  • 379
  • 2
  • 6