0

These two lines produces the same result (results are in my timezone) which is expected:

new DateTime(1970, 1, 1).ToLocalTime().AddHours(10000) // [21.02.1971 17:00:00]
new DateTime(1970, 1, 1).AddHours(10000).ToLocalTime() // [21.02.1971 17:00:00]

Now these two lines produces two different results:

new DateTime(1970, 1, 1).ToLocalTime().AddHours(100000) // [29.05.1981 17:00:00]
new DateTime(1970, 1, 1).AddHours(100000).ToLocalTime() // [29.05.1981 18:00:00]

That seem really strange and I cannot quite understand why this is happening. I know Jon Skeet would argue that we should not use DateTime at all since it's not good enough and use nodatime instead, but out of curiosity it's interesting to know why exactly is this happening.

Logically the point in time does not change depending on the order of operation and it's still the same time, though apparently it is not in C#.

P.S. ToUniveralTime behaves the same way and produces 2 different results in the second example (off by one hour).

Ilya Chernomordik
  • 27,817
  • 27
  • 121
  • 207
  • 4
    One is in Daylight saving time and the other in standard time. – jdweng Mar 21 '19 at 08:55
  • Why is it still different if we use `ToUniversalTime`? How is order of operations changes the daylight saving time? – Ilya Chernomordik Mar 21 '19 at 08:57
  • @IlyaChernomordik DST changes the implied offset of the value. If you used DateTimeOffset instead of DateTime you could see this. How did you test UTC though? Did you pass a DateTimeKing.UTC parameter to the constructor, or created some value (that may be affected by DST) then called ToUniversalTime ? – Panagiotis Kanavos Mar 21 '19 at 08:59
  • What timezone are you in? – canton7 Mar 21 '19 at 09:01
  • 1
    The order doesn't change the results, it is the date of the year. In January you are in Standard Time while in May you are in Daylight Saving Time. – jdweng Mar 21 '19 at 09:03
  • I think I got it: the difference is the date that is being converted, because 13:00 in January is not "same" as 13:00 in June when the conversion happens. Thanks for the answer, I can accept it if you want to post. – Ilya Chernomordik Mar 21 '19 at 09:17

1 Answers1

3

I'm assuming you're in the UK (where we spent the winter on UTC, but switch to UTC+1 in the summer). Therefore at midnight on 1/1/1970 we're on UTC.

  • new DateTime(1970, 1, 1).ToLocalTime() does nothing, except changes Kind to Local, because DateTime assumes that we specified a point in time in UTC. We then add 10,000 hours, which brings us to a point in winter time.
  • new DateTime(1970, 1, 1).AddHours(10,000) also brings us to a point in winter time UTC, so .ToLocalTime() again does nothing, except change Kind to Local.

However, things get different when we add 100,000 hours, since that brings us to a point after daylight savings kicks in.

  • new DateTime(1970, 1, 1).ToLocalTime().AddHours(100000) changes Kind to Local as before, and then adds 100,000 hours's worth of ticks. There's no appreciation that we moved from UTC to UTC+1.
  • new DateTime(1970, 1, 1).AddHours(100000) however brings us to a point in time where local time and UTC differ, because we're after the 29th March 1981 and daylight savings has kicked in. .ToLocalTime() therefore spots that there's a 1-hour difference and adds it, resulting in a time which is 1 hour ahead.

It's worth remember how DateTime behaves. It represents an instant in time - a number of ticks since an epoch. It also has a Kind property, which says whether that time is based on local time, UTC, or is "Unspecified" (i.e. you haven't told it).

For example, new DateTime(1980, 4, 1, 0, 0, 0, DateTimeKind.Local) (I chose a date after DST), and new DateTime(1980, 4, 1, 0, 0, 0, DateTimeKind.Utc) have the same value for their Ticks property, and they print the same string. However, calling .ToLocalTime() does different things: in the first case it does nothing (because the DateTime is already in local time), but in the second it adds an hour's worth of ticks to the DateTime.

So remember, always keep track of what Kind is, and be aware of the effect that it has when converting between UTC/local time. DateTime itself isn't particularly smart, but the timezone conversion methods do have a lot of special-case logic around Kind which can throw you.

I'd argue that you should probably be using DateTimeOffset instead of DateTime - that shows you what timezone you're currently in, and does away with the woolly notions of "Unspecified" and "Whatever timezone my PC is in". Try repeating this experiment with DateTimeOffsets.

canton7
  • 37,633
  • 3
  • 64
  • 77
  • Thanks for the answer, it looks like it is correct and we should do all time calculations in UTC before converting to avoid these problems – Ilya Chernomordik Mar 21 '19 at 09:25
  • 1
    @IlyaChernomordik I'd simply use `DateTimeOffset` instead - a lot of these problems magically disappear. – canton7 Mar 21 '19 at 09:26
  • Yeah, DateTimeOffset yields correct results (same point in time) regardless of order. Does the same logic that you described in answer not apply to this type? I assume it's because it's just a fixed 1:00 offset e.g. that does not take DST into account at all – Ilya Chernomordik Mar 21 '19 at 09:28