EngineerSpock EngineerSpock - 28 days ago 21
C# Question

DateTime and Offset Subtlety

I'm researching the "mechanics" of

DateTime
and related classes in the
BCL
.
I'm interested in the following odd behavior. Consider the following code snippet:

var dt1 = new DateTime(2014, 10, 24, 15, 30, 00, DateTimeKind.Unspecified);
DateTimeOffset dto2 = new DateTimeOffset(dt1, TimeSpan.FromHours(6));
string input = dto2.ToString("o");
DateTime dt6 = DateTime.Parse(input, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind);
Console.WriteLine("DT6:" + dt6.ToString("o"));


I know I should parse the string to the
DateTimeOffset
, but I'm just wondering why the library allows me to do what I do here and the most interesting thing is why the output will be:
"DT6:2014-10-24T13:30:00.00000000+04:00"

What it did during the parsing is that it set the offset to the actual offset at the given period of time in the time zone of my local machine and adjusted the hours by the delta between the initial offset and the resulting one.
It does this no matter what I provide as the
DateTimeStyles
option. The kind of the resulting value is set to
Local
, also no matter either I provide the
Roundtrip
value or not.
So the question is why it sets the kind to
Local
by default and the second question is where
DateTime
stores the offset and if it stores it, why the hell we need the
DateTimeOffset
class?

Answer

A few things:

  • DateTime and DateTimeOffset are both structs, not classes. They are value types.

  • Your question boils down to understanding the behavior of the DateTime.Parse method - not the DateTime struct. I suggest reading the docs for that method. There are lots of good bits of information there that describe the subtlety of behaviors you are seeing.

  • The docs have this to say about the return type in the "Return value" section (emphasis mine):

    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:

    chart

    You're passing a value of "2014-10-24T15:30:00.0000000+06:00" in the s parameter, which contains a time zone offset, so you're affected by the first row in the chart, and the result is converted to your machine's local time zone, whatever that may be.

  • The DateTimeStyles.RoundTripKind flag is not going to have any affect here. You'd only see the effect if your input ended in a Z instead of an offset, such that the third row in the chart above applied. In that case, the RoundTripKind flag ensures DateTimeKind.Utc. Without it you'd still get DateTimeKind.Local.

  • The offset is not stored. The only thing stored in a DateTime is a value combining Ticks and Kind properties into one 64-bit private field.

    As an interesting side note, recognize that since two bits of this field are reserved for Kind, there are four possible states, even though there are only three DateTimeKind values exposed. Indeed, there is a hidden fourth kind, which is described under "DateTime's Deep Dark Secret" in this article, and in the references sources. (Basically, it's a variation on DateTimeKind.Local.)

  • If you want a date/time API that makes better sense, consider using Noda Time.