3

If I try:

DateTime.Now.Subtract(DateTime.UtcNow)

I would expect the result to be very close to zero. But it's not, instead it's the time zone difference, in my case, -4 hours. There is a .Kind -- the DateTime KNOWS the timezones are different. Why doesn't it track this for me? Is there a flavor of Subtract that DOES use Kind correctly?

(For reference, a good rundown of what each one outputs can be seen at: https://stackoverflow.com/a/3229429/237091)

Community
  • 1
  • 1
Scott Stafford
  • 43,764
  • 28
  • 129
  • 177
  • 1
    `DateTime.Now` returns the current system `DateTime`, `DateTime.UtcNow` also applies the timezone conversion to `UTC`. There is no internal representation of timezones with `DateTime`. Perhaps you need `DateTimeOffset`. – Oded Aug 21 '12 at 19:55
  • 1
    You would get zero if you ran it in Reykjavik, Iceland – Erix Aug 21 '12 at 19:56
  • 1
    Welcome to the world of `DateTime`. The "kind" is ignored. In .NET 1 it didn't even exist, and it's too late to make things logical now. – Jeppe Stig Nielsen Aug 21 '12 at 19:57
  • 2
    You might want to read [Noda Time - What's wrong with DateTime anyway?](http://noda-time.blogspot.dk/2011/08/what-wrong-with-datetime-anyway.html). – Jeppe Stig Nielsen Aug 21 '12 at 20:00
  • @exacerbatedexpert: Nope, it's entirely reasonable to do all kinds of arithmetic on local values. In many cases I'd argue that's *more sensible* than doing it in UTC. The arithmetic shown *here* doesn't make sense, but other arithmetic certainly can. – Jon Skeet Aug 21 '12 at 20:23
  • @exacerbatedexpert: You can create an invalid local time or even an ambiguous one. Rather than converting local time to UTC before performing arithmetic, I prefer to perform all arithmetic *in* local time, and then *if required* convert to UTC, taking appropriate courses of action if necessary. For example, if a meeting occurs "same time next week" it makes sense to add a week to the *local time*, not UTC (which might end up being at a different local time). See http://noda-time.googlecode.com/hg/docs/userguide/arithmetic.html for my suggestions. – Jon Skeet Aug 21 '12 at 21:15
  • @exacerbatedexpert: Basically, arithmetic doesn't always want to be performed in terms of *elapsed* time, which is what you get if you always convert to UTC and back again. Given how badly screwed up `DateTime` is, I don't really want to accept "best practices" from the same team who came up with the type ;) – Jon Skeet Aug 21 '12 at 21:16

3 Answers3

6

Eh? The Kind property does not alter date math. It is only used by time zone methods.

You get exactly the result I would expect you to get. Not sure I understand why you were expecting zero.

Jonathan Wood
  • 65,341
  • 71
  • 269
  • 466
  • It would make *sense* for it to alter date math though - if only to throw an exception. Fundamentally IMO it doesn't make sense to compare a UTC date/time with a local date/time - or at least you'd want to specify what type of arithmetic you're trying to perform. – Jon Skeet Aug 21 '12 at 20:01
  • @JonSkeet: Please no more exceptions! We have a hard enough time trying to handle all possible exceptions as it is. Yes, I guess I can see how it could make sense if `Kind` were more strongly implemented. – Jonathan Wood Aug 21 '12 at 20:07
  • 1
    @JonathanWood: You shouldn't be trying to handle it - you should fix your code so it doesn't happen! (It would be an `ArgumentException` - which pretty much always means "your code is broken".) You should only be *handling* exceptions which are due to external circumstances etc. – Jon Skeet Aug 21 '12 at 20:09
  • @JonSkeet: Unfortunately, we are not perfect programmers. And we sometimes don't realize, for example, cases where a value could actually be null. For us, this is the real world. And we have a huge effort making sure all exceptions are dealt with. – Jonathan Wood Aug 21 '12 at 20:12
  • 1
    @JonathanWood: We're not perfect which is exactly why exceptions are so good. Because if I'm making a logical error, I'd almost always rather *abort* what I'm doing than keep going with the wrong value. If you don't know whether or not something is null, check for it. If you know it's meant to *not* be null, validate it. – Jon Skeet Aug 21 '12 at 20:21
  • Or you have a DateTimeUtc type instead, and get the compiler to stop your foolish cross-timezone comparisons early enough that you don't have to catch weird exceptions... – Scott Stafford Aug 21 '12 at 20:28
  • @ScottStafford: Exactly - it's nice if the type itself indicates the kind of data stored in it :) – Jon Skeet Aug 21 '12 at 21:18
4

There is a .Kind -- the DateTime KNOWS the timezones are different. Why doesn't it track this for me?

Because DateTime is fundamentally broken (and there's more...). IMO it should complain if you try to subtract a value of one kind from another. But no, it just uses the uninterpreted date/time in each value. Very few operations actually take any notice of the Kind, unfortunately. (If you use TimeZoneInfo, those operations do take notice of it.)

Kind was hacked into .NET 2.0; before then a DateTime value didn't even know what kind it was - if you used:

dt = dt.ToLocalTime().ToLocalTime().ToLocalTime();

it would apply the same offset change several times. The BCL team found a couple of spare bits in the binary representation, and used it for Kind.

Basically, I feel your pain. Personally I would prefer it if operations like this threw an exception - subtracting a UTC DateTime from a local DateTime or vice versa makes little sense, IMO.

As an entirely biased plug, you could use Noda Time which separates the ideas of Instant, LocalDate, LocalTime, LocalDateTime, OffsetDateTime and ZonedDateTime, and doesn't let you perform non-sensical arithmetic. Our aim is to provide a saner API than the BCL one. That doesn't necessarily mean we've succeeded, of course :)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
0

Each DateTime object represents a local time (as opposed to a UTC time plus a time zone offset). Even if the Kind property equals UTC, it's just storing the local time at the zero time zone. If it were not a local time, there would be no reason for the UtcNow property.

DateTime does not even store the timezone. If Kind equals UTC, then at least you know it's timezone is zero, but if Kind is local or unspecified, there is no way of knowing the timezone (the Kind property equals Unspecified by default).

Therefore, the Subtract method cannot incorporate the timezone into its calculation because the timezone is unknown.

Steven Doggart
  • 43,358
  • 8
  • 68
  • 105
  • No, please look at the documentation for `DateTime.Kind`. – Jon Skeet Aug 21 '12 at 19:58
  • @JonSkeet, I understand the `Kind` property. What did I say that is inaccurate? – Steven Doggart Aug 21 '12 at 20:01
  • "Each DateTime object represents a local time" - for a DateTime.Kind of Utc, the date/time represented is UTC, not local time. While I suspect I know what you may mean, you've at least expressed it in a very confusing way. – Jon Skeet Aug 21 '12 at 20:02
  • Somewhat. Not ideal, but then neither is `DateTime` :( `Subtract` certainly *could* take the time zone into account in the `UTC - Local` case, although that would arguably be even worse. As I've said elsewhere, throwing an exception would be better IMO. – Jon Skeet Aug 21 '12 at 20:22