8

I have two DateTime variables. Each has a timezone stored in the variable so that when I ToString with format including zzz I get a string including +01:00.

At design time I do not know what the timezones will be and I am expecting the variables to have different timezones from each other.

I want to compare the two DateTime values so that I know which is more recent.

For example, if variable A is 2015-07-04T02:00:00+03:00 and variable B is 2015-07-03T18:00:00-07:00 then B > A.

What do I write in C# to tell me this? (I would prefer not to use a third party library.)

(To the SO question-closing zealots: I have spent several hours investigating this using Google, MSDN and SO and am confused. I cannot find a very similar question to this on SO. I am confident that answers to this question will help others.)

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
cja
  • 9,512
  • 21
  • 75
  • 129

2 Answers2

13

You said:

I have two DateTime variables. Each has a timezone stored in the variable so that when I ToString with format including zzz I get a string including +01:00.

This is a common misunderstanding. DateTime doesn't have a time zone stored in the variable. It only has a Kind property, which is of type DateTimeKind, and can be either Utc, Local, or Unspecified.

When calling ToString, the zzz format specifier uses the Kind property to determine which offset to display.

  • When the Kind is DateTimeKind.Utc, the offset is always +00:00.

  • When the Kind is DateTimeKind.Local, the offset is determined from the local time zone on the computer where the code is executing. For example, my computer is set to US Pacific time, so the offset will be either -08:00 or -07:00 depending on whether daylight saving time is in effect or not.

  • When the Kind is DateTimeKind.Unspecified, the behavior is the same as if it were Local. Keep in mind that other methods treat Unspecified in different ways - this is just the particular behavior of the zzz specifier.

MSDN actually says:

For this reason, the "zzz" format specifier is not recommended for use with DateTime values.

Going back to your question:

At design time I do not know what the timezones will be and I am expecting the variables to have different timezones from each other.

Then you cannot use DateTime. You should instead use DateTimeOffset, as it retains a specific time zone offset instead of using a DateTimeKind.

For example, if variable A is 2015-07-04T02:00:00+03:00 and variable B is 2015-07-03T18:00:00-07:00 then B > A. What do I write in C# to tell me this?

DateTimeOffset a = DateTimeOffset.Parse("2015-07-04T02:00:00+03:00");
DateTimeOffset b = DateTimeOffset.Parse("2015-07-03T18:00:00-07:00");

bool result = b > a;  // true

See also: DateTime vs DatetimeOffset


Furthermore

As Gustav pointed out, you can use just DateTime, as long as you convert back to universal time before comparing. This works due to DateTime's hidden fourth state (more here). The state is set properly during parsing, and is taken into account when ToUniversalTime is called. Then comparison has valid UTC times to operate from.

DateTime A = DateTime.Parse("2015-11-01T01:00:00-07:00");
DateTime B = DateTime.Parse("2015-11-01T01:00:00-08:00");

Console.WriteLine(A.ToUniversalTime().ToString("'A: 'yyyy'-'MM'-'dd hh:mm:ss"));
Console.WriteLine(B.ToUniversalTime().ToString("'B: 'yyyy'-'MM'-'dd hh:mm:ss"));
Console.WriteLine( B.ToUniversalTime() > A.ToUniversalTime() );
Console.WriteLine( B > A );

And the result:

A: 2015-11-01 08:00:00
B: 2015-11-01 09:00:00
True
False

If your local time zone is set to Pacific Time, you'll get the above results. However, if it's set to something else - it's possible you will get True for the last result, because the values may have been parsed to different local times in your time zone, even though they'd be the same local time in the Pacific time zone.

Using DateTimeOffset is still simpler, going through less conversions, and not being affected by the local time zone.

Community
  • 1
  • 1
Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
  • Thank you for such a comprehensive answer, and for commenting on the other answer, which I believed initially, being as I am in GMT+1. – cja Jul 07 '15 at 10:50
0

Did you try this?

var A = DateTime.Parse("2015-07-04T02:00:00+03:00");
var B = DateTime.Parse("2015-07-03T18:00:00-07:00");

Console.WriteLine( B > A );
GreenEyedAndy
  • 1,485
  • 1
  • 14
  • 31
  • But why use `var` for `DateTime`? – Gustav Jul 04 '15 at 13:06
  • 2
    This code will appear to work, but since the resulting `DateTime` values will have been converted to the *local* time zone, it could fail depending on the DST transitions of the local time zone. – Matt Johnson-Pint Jul 04 '15 at 18:38
  • 1
    @Gustav - `var` is just shorthand for implicit typing. See [msdn for details](https://msdn.microsoft.com/en-us/library/bb383973.aspx). – Matt Johnson-Pint Jul 04 '15 at 18:39
  • How could it possible fail? Both times will be converted to the same timezone. No reason for a down vote. – Gustav Jul 04 '15 at 20:51
  • I know. But why use implicit typing when explicit will be the natural choice? DateTime isn't that exotic.. – Gustav Jul 04 '15 at 20:54
  • I saw your comment on using DateTimeOffset. That would of course be the primary choice. – Gustav Jul 04 '15 at 20:59
  • @Gustav - Consider A = `2015-11-01T01:00:00-08:00`, B = `2015-11-01T01:00:00-07:00`, local time zone is US Pacific time. Both times are valid, since this is the fall-back transition. B comes later, but in this case B>A will be false since the offsets are not accounted for in the comparison. You could adjust A or B to other offsets with equivalent date/time values and get similar incorrect results. The point is, *comparison* is not valid for this scenario unless you use `DateTimeOffset`. – Matt Johnson-Pint Jul 05 '15 at 18:55
  • @MattJohnson - Actually, I would have thought you were right, but I ran the code in LinqPad with both the original dates and your latest, and to my surprise it looks to me as the timezone is read and applied correctly by method `Parse`. I appended it to your answer for readability. – Gustav Jul 06 '15 at 10:10
  • 1
    @Gustav - My bad, I mixed up `A` and `B`. 01:00-7 is the DST time, so it comes before 01:00-8. Regarding your code though, the key is that you call `ToUniversalTime` before comparison. It works due to [the hidden fourth state](http://referencesource.microsoft.com/#mscorlib/system/datetime.cs,40) ([more here](http://codeblog.jonskeet.uk/2012/05/02/more-fun-with-datetime/)). The `>` operator doesn't use kind, so it doesn't take that state into account, but `ToUniversalTime` does. – Matt Johnson-Pint Jul 06 '15 at 16:46
  • @MattJohnson - Thanks, those links are very informative. I haven't encountered those two special bits before. It's nice to see how date and time handling has evolved since _Visual Basic_. – Gustav Jul 06 '15 at 18:14