0

I receive from a server a json with a timestamp in UTC format:

{
    "foo": "2017-11-05T08:30:00"
}

and convert it to a .NET instance using Json.Deserialize. The DateTime property I receive has a valid value with DateTimeKind = Unspecified.

Then I call obj.foo.ToLocalTime() and get 2017-11-05 00:30 AM while my real local time on computer is 2017-11-05 01:30 AM (-7)

Why was 1 hour lost (I assume it's somehow connected to the daylight saving time)?

And how can I fix that on deserialization level, so every instance could be properly converted to valid UTC DateTime instance?

UPDATE: This was happening on Nov 5, 2017 when daylight saving time chance occurs and client (-7) had the pending UTC daylight change while UTC has already passed that point (UTC time was 8:30 while client time is 1:30 AM and we expected the time change at 2:00 AM). Now it works fine without any changes but it worth fixing the server (Z) and the client (UTC) sides.

Mando
  • 11,414
  • 17
  • 86
  • 167
  • You don't need to convert UTC to local time since the Net Library automatically displays UTC date in local time. So use : obj.foo.ToString("yyyy-MM-dd hh:mm t"); – jdweng Nov 05 '17 at 09:00
  • 5
    @jdweng: No, it doesn't. `DateTime` has "kinds" of Utc, Unspecified and Local. It's entirely feasible to keep `DateTime` values in UTC, and they will be maintained in UTC. The OP is receiving a value in "kind" Unspecified, and it won't automatically be converted to local. – Jon Skeet Nov 05 '17 at 09:02
  • 2
    Please provide a [mcve] - noting that the JSON part is probably irrelevant, in that you're likely to be able to reproduce it by explicitly creating the relevant `DateTime`. Also note that without knowing which time zone you're in, it's very hard to help you. (What does `TimeZoneInfo.Local.Id` return?) – Jon Skeet Nov 05 '17 at 09:03
  • Jon : Once a datetime is parsed and it contains the time zone ("2017-11-05T08:30:00") the next time ToString() is used it will be displayed in local time. – jdweng Nov 05 '17 at 09:05
  • 2
    @jdweng the OP states that the DateTime value has Unspecified as DateTimeKind – Sir Rufo Nov 05 '17 at 09:09
  • 1
    @jdweng: That depends on how you parse it, and as Sir Rufo points out, the OP has ended up with a `DateTime` with a kind of Unspecified, so that conversion to local time has *not* happened. – Jon Skeet Nov 05 '17 at 09:14
  • 4
    The main problem is the missing Z at the end of the time value inside the json. That would indicate to be an UTC datetime value and Json.Net will deserialize it to DateTimeKind.Utc. Without the Z there is no information about the timezone and therefore it is Unspecified => working as to be expected – Sir Rufo Nov 05 '17 at 09:20
  • 1
    Described in [ISO8601: Time Zone Designators](https://en.wikipedia.org/wiki/ISO_8601#Time_zone_designators) – Sir Rufo Nov 05 '17 at 09:28
  • 1
    @SirRufo: That's just the parsing resulting in `Unspecified` rather than `Utc` though. The heart of the question (it seems to me, although the OP hasn't given us any more information) is the conversion to UTC giving the "wrong" result. Without a [mcve] - or at the very least a specific time zone - we're not going to make much more progress. – Jon Skeet Nov 05 '17 at 13:56
  • Agree with Jon. Without knowing the local time zone settings of the machine where the code was running, this is impossible to answer correctly. Please give us the output of `tzutil /g` on the command line from that particular computer. Thanks. – Matt Johnson-Pint Nov 05 '17 at 20:36

1 Answers1

4

Your problem is that your timestamp value, "2017-11-05T08:30:00", is completely lacking a time zone designator, either "2017-11-05T08:30:00Z" for UTC or "2017-11-05T08:30:00-07:00" for your local time zone. I.e. your statement that your timestamp is in UTC format is false.

When this happens, Json.NET parses the value into a DateTime with DateTime.Kind == DateTimeKind.Unspecified (which makes sense, since the time zone is, in fact, unspecified in the JSON). Later on, some other code interpreted that to mean local time but since daylight savings time did in fact expire around the time you asked this question in the location (Washington, DC, United States) listed in your profile, things became confused, since DateTime doesn't remember its time zone offset, only whether or not it is in the machine's local time zone. (For more on this limitation see this documentation page as well as What's wrong with DateTime anyway? by Jon Skeet which specifically mentions that certain bugs with the .Net date and time types can occur only during daylight savings changes.) If daylight savings expired between when the timestamp was deserialized and when it was subsequently processed, you would get exactly the bug you are seeing.

That being said, you asked, And how can I fix that on deserialization level, so every instance could be properly converted to valid UTC DateTime instance? This can be done by setting JsonSerializerSettings.DateTimeZoneHandling == DateTimeZoneHandling.Utc during deserialization:

var settings = new JsonSerializerSettings
{
    DateTimeZoneHandling = DateTimeZoneHandling.Utc,
};
var root = JsonConvert.DeserializeObject<RootObject>(json, settings);

The possible values of DateTimeZoneHandling are explained here:

  • Local: Treat as local time. If the DateTime object represents a Coordinated Universal Time (UTC), it is converted to the local time.

  • Utc: Treat as a UTC. If the DateTime object represents a local time, it is converted to a UTC.

  • Unspecified: Treat as a local time if a DateTime is being converted to a string. If a string is being converted to DateTime, convert to a local time if a time zone is specified.

  • RoundtripKind: Time zone information should be preserved when converting.

By using DateTimeZoneHandling.Utc the timestamp will be assumed to be in UTC right from the beginning, so odd behaviors due to daylight savings changes should not occur.

You might also consider fixing the server code so that it properly appends a Z to its timestamp value to indicate the value is, in fact, in UTC.

Sample fiddle showing all four settings in action.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • And you were right about the server, the real issue was there, because `Z` was lost in round trip to SQL server and back causing all UTC dates created treated as Unspecified after round trip and returned to client without `Z`: https://stackoverflow.com/questions/29496306/how-to-specify-that-datetime-objects-retrieved-from-entityframework-should-be-da – Mando Nov 05 '17 at 23:50